8

视频通信近实时生成字幕项目实践 - Lucky小黄人^_^

 1 year ago
source link: https://www.cnblogs.com/hi3254014978/p/17233926.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

2、环境准备

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

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

阿里云 OSS bucket

阿里云 语音识别项目

本地 golang 运行环境

3、实现思路

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

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

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

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

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

1456655-20230319183054632-1787117356.png

3.1 提示

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

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

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

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

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

4、详细过程

4.1 工具准备和安装

4.1.1 安装 ffmpeg

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

1456655-20230319183109025-2055370564.png

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

1456655-20230319183117076-392274826.png

4.1.2 创建阿里云的 RPM 用户

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

1456655-20230319183124997-607093576.png

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

1456655-20230319183134746-985737240.png

4.1.3 创建阿里云 OSS bucket

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

1456655-20230319183143096-1586400614.png

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

1456655-20230319183149654-1186911080.png

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

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

1456655-20230319183157645-1347237760.png

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

1456655-20230319183242889-956341974.png

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

1456655-20230319183248637-749680251.png

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 效果展示

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

1456655-20230319183318977-176261152.png

点击「启动字幕生成」按钮,则会开始每三秒给后端发送一次视频流,后端经过大概 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 文档(讯飞的产品,这次也没用上)

__EOF__


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK