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

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 文档(讯飞的产品,这次也没用上)

发表评论

相关文章