您现在的位置是:首页 >技术教程 >ffmpeg转码数据流通过websocket传给浏览器播放网站首页技术教程

ffmpeg转码数据流通过websocket传给浏览器播放

MorrisMao 2024-09-16 12:01:04
简介ffmpeg转码数据流通过websocket传给浏览器播放

背景

浏览器支持的视频格式有限。而ffmpeg有很强的格式转换功能。那我们能不能通过ffmpeg把不支持的视频转成浏览器可以支持的视频呢?

方案

要实现以上方案要解决几个问题:

  1. 如何实时获取ffmpeg的转换后的数据
  2. 如何将数据实时传给浏览器
  3. 浏览器收到数据后如何播放

如何实时获取ffmpeg的转换后的数据

正常我们通过 ffmpeg 是可以将一个视频文件转换成另一种视频文件。
如下是将一个其他编码的mp4文件编码格式进行转换

ffmpeg -i 1.mp4  -c:v libx264 -f mp4 mp4-264.mp4

但我们怎么通过程序调用ffmpeg来获取转换后的数据呢。
ffmpeg 提供了一个pipe的功能可以将数据传给标准输入输出
nodejs 可以通过spawn来调用ffmpeg获取输出流

let ffmpeg = spawn('ffmpeg', //ffmpeg 自己从官网上载,改成自己路径,或配置成全局变量
    [
    '-i', 'html/1.mp4',
    '-c:v', 'libx264', // 如果原视频已经是264格式可以不转
    '-movflags', 'frag_keyframe+empty_moov+default_base_moof', // 转成fragment mp4
    '-f', 'mp4',
    'pipe:1' // 输出流
  ]);
  ffmpeg.stdout.on('data', chunk=>{
    console.log("data")
  })

如何将数据实时传给浏览器

现在转换后的视频数据拿到了,怎么将数据传给浏览器呢。
浏览器实时数据的传输一般想到的是用websocket.
我这边想到的是用两条websocket. 一条传输控制命令,一条传输视频数据。
websocket 支持二进制数据传输和文本数据传输。这里控制命令用文本类型。视频数据用的是二进制数据类型。

ffmpeg.stdout.on('data', chunk=>{
    console.log("data")
    //转码后的数据用二制制传输
    ws.send(chunk, {binary: true, mask: false});
  })

浏览器收到数据后如何播放

视频流的播放这里用到了MediaSourceExtension(MSE). https://developer.mozilla.org/en-US/docs/Web/API/MediaSource
需要注意的是,MSE只支持 fragmented mp4 用ffmpeg指定转framented mp4参数

 -movflags frag_keyframe+empty_moov+default_base_moof    // 转成fragment mp4
let video = document.querySelector('video');
let mediasource = new MediaSource();
video.src = URL.createObjectURL(mediasource);

可将websocket接收到数据传给Mediasource

 ws.onmessage = (event) => {
        sourceBuffer.appendBuffer(event.data);
      };

由于播放速度和转码的数据不一致,所以要根据播放进度来控制进度。这里用到刚提到的控制websocket, 通过监听播放进度和缓存的时间来判断是否要从后端拉取数据.

 function getData() {
      controlWs.send("get")
    }
    
// updateend会在往MediaSource放数据后调用, 可以读取到缓存时间 
sourceBuffer.addEventListener('updateend', () => {
          let buffered = sourceBuffer.buffered
          for (let i = 0; i < buffered.length; i++) {
            bufferTime = buffered.end(i); // 缓存了多少时间的数据
          }
          /**
           *readyState
           * 0 = HAVE_NOTHING - 没有关于音频/视频是否就绪的信息
           1 = HAVE_METADATA - 关于音频/视频就绪的元数据
           2 = HAVE_CURRENT_DATA - 关于当前播放位置的数据是可用的,但没有足够的数据来播放下一帧/毫秒
           3 = HAVE_FUTURE_DATA - 当前及至少下一帧的数据是可用的
           4 = HAVE_ENOUGH_DATA - 可用数据足以开始播放
           * */
          if (video.readyState !== 4) {// 数据不够拉数据
            getData();
          }
        });

 video.addEventListener('timeupdate', (e)=>{
        console.log("timeupdate: " , bufferTime ,  video.currentTime,  video.readyState);
        // 缓存10秒
        if (bufferTime - video.currentTime < 10) {
          getData()
        }
      })

完整代码

https://gitee.com/morris-mao/websocket-video

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。