视频通信近实时生成字幕项目实践

1、概述

这两天做了一个视频通信近实时字幕生成工具,前端通过浏览器打开摄像头,生成用户画面,根据用户的语音近实时自动生成字幕展示在画面下方。对于没有接触过音视频处理的我来说,刚开始还是有点懵的,虽然借助了 chatgpt,但是还是走了一段时间的弯路。不过花了大概一天时间还是比较完美的实现了,还是非常有成就感的。谨以此记录最终成功的版本的实现思路和实现过程,文末附带源码和源码启动过程。

2、环境准备

第四节「详细过程」中会有这些工具安装或者申请教程

ffmpeg,一个强大的视频处理工具,此次主要用它来实现视频转成音频。

阿里云 OSS bucket

阿里云 语音识别项目

本地 golang 运行环境

3、实现思路

  • 前端使用 WebRTC 调起摄像头,与后端建立 websocket 连接,每隔三秒发送一段视频二进制流到后端;

  • 后端将视频流保存到本地,使用 ffmpeg 将本地视频转换成音频;

  • 把音频上传到阿里云 OSS 对象存储服务器中;

  • 获取到音频的访问地址;调用阿里云的语音识别功能的 sdk 解析出音频对应的文字内容;

  • 后端通过 websocket 把文字内容回传给前端,前端进行字幕展示。

3.1 提示

谨以此提示来降低心理压力,看起来此项目设计到前后端项目的开发和部署,但是其实不对此工具不用产生太大的压力,因为很多操作都有现成工具可以借用。

虽然此次项目需要同时开发前后端,但是对于此次工具的开发,不需要把前端部署到服务器,只需编写一个简单的 html,用浏览器渲染打开即可。

chatpgt 可以一定程度上加快我们的问题解决过程,但是也不要全信它的内容,亲身经历被它坑了好多次。

github 上已有一些优秀的开源项目,比如此次所借用的开源项目wxbool/video-srt ,大大加快了项目的开发速度。

前后端 websocket 交互的实现也比较简单,几行代码就可以搞定。

4、详细过程

4.1 工具准备和安装

4.1.1 安装 ffmpeg

在 Mac 上安装方式是 brew install ffmpeg(其他操作系统可以自行寻找安装教程),安装过程可能比较久,我安装了大概 40 分钟。

安装完毕执行ffmpeg -version ,输出如下信息说明安装成功。

4.1.2 创建阿里云的 RPM 用户

登录阿里云账号后,访问 https://ram.console.aliyun.com/users,创建用户

随后在进入用户首页,点击「创建 AcessKey」,身份验证通过后,会创建一个 RAM用户的 AcessKeyAccessKey Secret,立刻把两个参数记录下来,因为这个 AccessKey Secret 只在创建时显示,后续不支持查看。

4.1.3 创建阿里云 OSS bucket

访问 OSS对象存储,点击立即开通,然后创建 bucket ,由于后续语音识别会访问 bucket 中的文件,而语音识别只能访问到公开的资源,所以还需要设置 bucket 的开放范围为「公开」

给 RPM 用户添加完全控制权限,否则后面运行代码时 oss 会报错 StatusCode=403, ErrorCode=AccessDenied, ErrorMessage="You have no right to access this object because of bucket acl.",

4.1.4 创建阿里云智能语音交互项目

访问 录音文件识别,点击立即开通,然后创建项目,获取到项目AppKey,记录下来。

4.1.5 golang 环境安装

wget "https://studygolang.com/dl/golang/go1.18.3.darwin-amd64.tar.gz" -O go.tar.gz
tar -C /usr/local -xfz go.tar.gz
sudo echo 'export GOROOT=/usr/local/go' >> ~/.zshrc
sudo echo 'export GOPATH=~/go' >> ~/.zshrc
sudo echo 'export PATH=$GOPATH/bin:$GOROOT/bin:$PATH' >> ~/.zshrc
source ~/.zshrc

执行 go version 输出版本信息说明安装成功

4.2 前端实现

只有一个 html 页面,通过 websocket 跟后端建立连接,进行数据交互,包含一些必要的 dom 节点,以及三个按钮。

javascript 脚本包含四部分,

  • 第一部分是使用 navigator.mediaDevices.getUserMedia 打开用户的媒体设备,这个工具函数底层是通过 WebRTC 来实现的,随后跟后端建立 websocket 连接,使用 ws.onmessage 将获取到的后端消息添加上 dom 节点里
  • 后三部分分别是三个按钮所绑定的函数,
    • startGenerageSubtitle() ,绑定「启动字幕生成」按钮,功能是启动字幕的生成,函数内部会启动定时器,以三秒为周期,记录用户媒体的视频流,通过 websocket 对象发送到后端
    • stopGenerageSubtitle(),绑定「停止生成字幕」按钮,功能是停止生成字幕,删除定时器,终止视频流的记录和发送。
    • clearGenerageSubtitle(),绑定「清空字幕」按钮,清空 html 页面已有的字幕,清除 dom 元素的节点内容。
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>字幕生成</title>
</head>
<body>
<h1>椿辉近实时字幕生成工具</h1>
<div>
  <div style="width: 700px; float: left; display: block">
    <video id="video" autoplay></video>
    <button id="startButton" onclick="startGenerageSubtitle()">启动字幕生成</button>
    <button id="stopButton"  onclick="stopGenerageSubtitle()">停止生成字幕</button>
    <button id="clearButton"  onclick="clearGenerageSubtitle()">清空字幕</button>
    <p id="subtitle" style="text-align: center"></p>
  </div>
  <div  style="width: 500px; float: left; display: block">
    <h3>所有字幕</h3>
    <p id="result"></p>
  </div>
</div>

<script>
  const video = document.getElementById('video');
  const result = document.getElementById('result');
  const subtitle = document.getElementById('subtitle');
  let ws = null;
  let mediaRecorder = null;
  let isRecording = false;
  let intervalId = null;
  // 获取用户媒体设备
  navigator.mediaDevices.getUserMedia({ video: true, audio: true })
          .then((stream) => {
            console.log("ws ===>", ws);
            ws = new WebSocket('ws://localhost:8080');
            video.srcObject = stream;
            // 建立WebSocket连接
            ws.onopen = function (){
              console.log('===> WebSocket连接已经建立');
            };
            ws.onmessage = function(map) {
              let newP = document.createElement("p");//创建一个p标签
              newP.innerText = map.data;
              result.appendChild(newP);
              subtitle.textContent = map.data;
              console.log(map.data);
            }
          })
          .catch((err) => {
            console.log(err);
          });
  // 启动字幕生成
  function startGenerageSubtitle() {
    if (isRecording) {
      console.log('===> 已经在生成字幕');
      return;
    }
    console.log('===> 开始生成字幕');
    isRecording = true;
    // 获取用户媒体设备
    navigator.mediaDevices.getUserMedia({ video: true, audio: true })
            .then((stream) => {
              console.log("每3秒发送一次视频流数据")
              // 每3秒发送一次视频流数据
              intervalId = setInterval(() => {
                const mediaRecorder = new MediaRecorder(stream, {
                  mimeType: 'video/webm;codecs=h264'
                });
                mediaRecorder.addEventListener('dataavailable', (event) => {
                  if (event.data.size > 0) {
                    // 发送数据到后端
                    ws.send(event.data);
                  }
                });
                mediaRecorder.start();
                // console.log("mediaRecorder.start===", mediaRecorder)
                setTimeout(() => {
                  // console.log("mediaRecorder.stop===", mediaRecorder)
                  mediaRecorder.stop();
                }, 3000);
              }, 3000);
            })
            .catch((err) => {
              console.log(err);
            });
  }
  // 停止生成字幕
  function stopGenerageSubtitle() {
    if (!isRecording) {
      console.log('===> 没有在生成字幕');
      return;
    }
    console.log('===> 停止生成字幕');
    isRecording = false;
    clearInterval(intervalId);
    // mediaRecorder.stop();
  }

  // 清空字幕
  function clearGenerageSubtitle() {
    subtitle.textContent = "";
    result.innerHTML = "<p></p>";
  }
</script>
</body>
</html>

4.3 后端实现

借助了一个开源项目wxbool/video-srt ,这个开源项目可以把本地视频文件转成音频(通过 ffmpeg 实现),传到 OSS,并调用阿里的语音识别服务获取到字幕信息,我对他进行了一些改造,加入了服务的监听启动,随后使用 websocket 接收前端视频流,把视频流转存成本地视频文件,最后调用了 video-srt 的原有逻辑代码,完成了视频流字幕的提取生成。下面是一些关键代码。

项目根路径的 main.go 以 http 服务监听 8080 端口的形式启动服务,接口的回调处理函数是 RecognizeHandler2

RecognizeHandler2() 函数的代码逻辑在根路径下的 handler.go 中,用 websocket 来处理这个 http 接口,循环读取前端的视频流,把视频流存储成一个本地视频文件,调用 getSubtitle() 函数提取视频文件中的字幕, getSubtitle() 封装了原开源项目 wxbool/video-srt 的既有能力。

5、效果演示

关闭所有代理,否则调用阿里云的 SDK 可能超时,以及访问阿里云的 OSS 也可能超时。

5.1 启动前的参数设置

如果你想运行本项目,请先拉取 luoChunhui-1024/video-subtitle 项目到本地,把项目根目录的 config.ini 中的各种参数替换成刚才让你记录下来的那些阿里云配置。

#字幕相关设置
[srt]
#智能分段处理:true(开启) false(关闭)
intelligent_block=true

#阿里云Oss对象服务配置
#文档:https://help.aliyun.com/document_detail/31827.html?spm=a2c4g.11186623.6.582.4e7858a85Dr5pA
[aliyunOss]
# OSS 对外服务的访问域名
endpoint=oss-cn-beijing.aliyuncs.com
# 存储空间(Bucket)名称
bucketName=my-test-bucket-lch
# 存储空间(Bucket 域名)地址
bucketDomain=my-test-bucket-lch.oss-cn-beijing.aliyuncs.com
accessKeyId=LTAI5t7A8mUG4JX5QUcKBuon
accessKeySecret=49onfEooPnlpfkHPfW3j6TBEDviYmu

#阿里云语音识别配置
#文档:
[aliyunClound]
# 在管控台中创建的项目Appkey,项目的唯一标识
appKey=5Xcb7kOlcSFAF248
accessKeyId=LTAI5t7A8mUG4JX5QUcKBuon
accessKeySecret=49onfEooPnlpfkHPfW3j6TBEDviYmu

5.2 启动运行

先在后端项目的根路径对项目进行编译,编译完成后在项目根路径会生成一个 output 可执行文件

go build -tags="recorder" -mod=mod -o output

直接执行这个可执行文件,即可启动后端服务

./output

随后通过浏览器打开项目中的 html/index.html 文件,过程中可能会询问获取麦克风和摄像头权限,允许即可,这样前端也启动完成了。

提示:Mac 可以直接在浏览器的地址栏输入 html 页面的绝对路径来打开 html 页面

5.3 效果展示

整体界面如下,由于本人样貌丑陋,为了不影响大家学习的心情,所以打了马赛克。

点击「启动字幕生成」按钮,则会开始每三秒给后端发送一次视频流,后端经过大概 6~8 秒的处理,把视频字幕返回给前端进行展示。所以字幕相较于画面中的语音,是有 8~9 秒的延迟的。

画面右侧会展示已有的字幕,画面最下方则仅展示最新的字幕。

点击「停止字幕生成」按钮,终止给后端发送视频流的定时器。但是点击启动字幕生成按钮可以再次启动定时器,进行字幕生成。

点击「清空字幕」按钮,会同时清空画面右侧的「所有字幕」和画面下方的最新字幕。

6、项目地址

github:https://github.com/luoChunhui-1024/video-subtitle

7、参考和致谢

特别感谢 wxbool/video-srt 项目,本项目后端的大部分都是直接借用了该项目,也特别感谢 chatgpt,虽然它提供的代码和方式坑了我很多次,但是仍旧给我提供了很大的帮助。

其他参考

阿里云智能语音交互帮助文档错误码查询

golang服务端与web前端使用websocket通信

Golang使用WebSocket通信

通过使用WebSocket使前后端数据交互

webRTC结合webSocket实时通信

WebRTC 从实战到未来!前端如何实现一个最简单的音视频通话?

WebRTC API:MediaDevices.getUserMedia()

实时websocket视频流存储

创建和插入DOM节点

实时语音识别-websocket API(百度的产品,这次其实没有用上)

实时语音转写 API 文档(讯飞的产品,这次也没用上)

posted @ 2023-03-19 18:48  Lucky小黄人^_^  阅读(387)  评论(4编辑  收藏  举报