基于ffmpeg和wasm的flv直播播放器实现思路

背景介绍

目前cocos自带mp4播放器,能实现基本的播放需求,但是没有支持直播功能,而且对于视频文件的播放也是先下载再播放,没有做到边下载变播放的分片下载,

h5这块由于受到浏览器的限制,目前支持直播的方案大致分为flv/ts流/ws等

视频本身可以理解为一个压缩过的二进制流,视频解码的目的就是解压缩,把视频数据还原成原始的像素,声音解码就是把 mp3/aac 等格式还原成原始的 PCM 格式。

FFmpeg是一套老牌的、跨平台音视频处理工具,历史悠久,功能强大,性能卓著,市场上有大量基于 FFmpeg 的编解码器和播放器。

本文参考手淘、花椒等FFMpeg实现的h5播放器,基于cocos实现了一套支持flv的直播播放器组件,支持2d的sprite和3d的meshrender,大体结构是这样的

wasm兼容性


从es6和wasm的对比看,目前的覆盖率已经可以商用了

cocos加载wasm的方式有3种

1.wasm和js胶水放在构建模版里面修改index.ejs

普通require就可以,需要注意跨域

2.合成一个文件做成ffmpeg.mjs,用cocos的lib导入方式使用

3.做出asset文件放在资源包里,让wasm和js胶水文件合成一个文件,后缀用.bin的方式加载

通过下载的资源生成一个worker

为什么选择flv

准确来说,flv是一种视频封装协议,能够支持文件形式和流形式。

FLV文件体积小巧,清晰的FLV视频1分钟在1MB左右,一部电影在100MB左右,是普通视频文件体积的三分之一。再加上CPU占有率低、视频质量良好等特点使其在网络上盛行,所以网上的几家著名视频共享网站均采用FLV格式文件提供视频。

具体区别如下:

1.文件格式不同:flv是流式文件,mp4是容器式文件。

2.解析方式不同:flv文件可以边传输边解析,而mp4文件则不行。

3.观看方式不同:flv文件可以一边下载一边观看,而mp4文件则需要全部下载完毕才能够观看。flv格式不用全部下载完毕,就支持解析观看等,这也是它使用越来越广泛的原因。

4.直播流支持: flv可以支持直播流,mp4不行

5.hls直播流对比: hls的ts切片流相比,延迟更小,延迟能做到1-2s,而hls直播流也是通过生成切片的方式传输,延迟会更高。

6.ws等对比: flv比较通用,各大cdn和直播网站都支持flv方式出流,ws等比较定制化。

视频播放流程

视频播放的流程包括:

1.下载视频流,为解码视频做准备

2.解封装,将视频流按照封装协议解开,分解信息头/音频流/视频流

3.解码,分离后的数据流解码成音频和视频元数据

4.播放,将分离的视频流进行图片渲染,音频流进行声音播放

播放器整体实现过程

下载阶段

视频文件分片下载

flv文件支持分片下载播放,下载分片之前需要获取视频文件的大小

监听onreadystatechange事件的头部返回信息获取长度

var len = xhr.getResponseHeader(“Content-Length”);

在浏览器模式下通过xmlhttprequest实现

var xhr = new XMLHttpRequest;

xhr.open(‘get’, url, true);

xhr.responseType = ‘arraybuffer’;

xhr.setRequestHeader(“Range”, “bytes=” + start + “-” + end);

直播通过fetch的流式chunk请求方式

xmlhttprequest的流式下载支持不太好,因此最终选择了fetch方式

解封装方式

解封装的过程其实就是解析文件协议的过程,flv协议的格式可以参考这里,【流媒体协议】图解 FLV 协议 快速入门 - 知乎

我们只需要解析头部type=18和分离视频type=8/视频type=9,因为协议比较简单,因此跳解性能非常好。

然后将分离的音视频数据传入ffmpeg的decoder

解码

音频解码: 浏览器有音频接口

const AudioContext = window.webkitAudioContext || window.AudioContext;

可以通过decodeAudioData来解码,也可以通过ffmpeg来解码,但是decodeAudioData的解码性能是浏览器内置的因此可能会采用硬接,看浏览器是否支持,这个可以自己选择,目前使用的是ffmpeg的软解方式,好处是可以做重采样,生成最适合浏览器的采样率,比方web是48000hz,ios是41000hz,兼容性更好。


视频解码:

目前对h264,h265的解码库都比较复杂,都是通过wasm的方式处理的,没有js的原生库,而且性能上wasm也比较好

我们的目标是渲染到sprite组件或者3d节点上,通过修改builtin-sprite的effect支持2d渲染和builtin-unlit的effect支持3d渲染

yuv成rgb的转化公式我参考了论坛videotexture的demo,用3.6.2的effect转化处理的最终结果a值强制设置为1.0


性能

视频解码方式:硬解和软解,硬解由于受到各种专利限制和浏览器限制,很难做到兼容性覆盖,因此软解是比较合适的方案。

而基于ffmpeg和wasm的解码是通过cpu运算实现的软解,并且由于浏览器对wasm多线程的兼容性问题,

ffmpeg编译wasm的时候关闭多线程,需要的cpu资源比较多,在我iphonex的设备上,播放单视频流占有大概在50%左右。

为什么只选择了flv,ffmpeg可以支持各种视频格式

ffmpeg确实支持很多的视频格式,而且最初我也是这么设计的,但是在测试过程中存在一些和预期不一致的地方,ffmpeg自带缓冲区,解码出来的流不是稳定的,有可能在某一帧返回10个音频帧或者视频帧,

解码过程是在worker里面处理的,postmessage到主线程的时机不可控制,尤其是碰巧视频和音频同时出来,因此有可能造成卡帧,因此最终还是选择了上层解封装,ffmpeg只负责解码,这种方式出包比较稳定

微信

微信自己封装了一套api,而且对wasm的支持不太友好,需要很大的改造,等完成了再分享

原生

原生不支持wasm和worker,音频播放的方式也不一样,需要用c++的jsb和多线程来重新处理,而且在原生端可以开启ffmpeg的多线程模式,性能提升会更大

demo和组件地址

感兴趣的童鞋可以请我喝几杯咖啡,给我补几根头发

https://store.cocos.com/app/detail/4471

注意:web的播放器都有跨域问题,需要配置出流地址支持跨域
音视频相关内容涉及的会比较多,只能简单介绍了,先到这里吧。
video

17赞

好东西!已经下单!

网易直播FLV流,无法播放。

你看看是不是网易不支持跨域或者需要域名白名单

更新内容
//20230228
v0.1.0,新增微信小游戏支持

MARK,收藏+1

image 有问题大家可以加群提供售后

http 地址修改了好像没有生效

demo里面要刷新后先设置,再播放

能支持2.3的版本能使用吗?

需要自己修改,2x版本对纹理生成不友好,性能损失有点大

我这边尝试了微信播放直播流但是微信onChunkReceived 到的数据不能被 inputflv 方法解析为type 9 也就是视频数据,也就无法播放,这个该怎么解决

可以提供个流地址,晚上我回去试一下,demo里面的能播放吗?

你的出流方式是什么样的也可以提供一下

demo 里面的不行
我是用 rtmpd 流服务器
OBS推流

要出flv的流,单纯的rtmp流不行的,试试srs服务器

加我微信 ankyecc, 提供订单号提供技术支持

我在网页浏览可以,出的流是 http-flv


demo 的地址也不行

微信要上真机测试