WasmVideoPlayer:用 WASM 在浏览器里播 H265 视频

H265 编码的视频压缩率比 H264 高一倍左右,同样的画质占用带宽更少。但浏览器对 H265 的支持一直是个问题,原生支持的浏览器几乎没有。

原因有两个:一是 H265 解码对性能要求更高,软解吃力,硬解兼容性差;二是专利费的问题,导致各大浏览器厂商对 H265 兴趣不大,转而押注更开放的 AV1 编码。

这就造成了一个尴尬的局面:APP 端可以靠硬件解码流畅播放 H265,但 Web 端基本没戏。

正文顶部截图

WasmVideoPlayer 这个项目就是为了解决这个问题。它用 WASM + FFmpeg + WebGL + Web Audio 这套组合,在浏览器里实现了一个能播放 H265 视频的播放器。

Star 数 1390,不算特别高,但这个项目本身定位就是技术验证,just for fun。

技术栈拆解

整个播放器由四个核心技术支撑:

WASM 负责在浏览器里跑原生 C/C++ 代码。项目用 Emscripten 工具链把 FFmpeg 编译成 WASM 模块,这样就能在浏览器里调用 FFmpeg 的解码能力。

FFmpeg 做解封装和解码。用的是 3.3 版本,理论上能支持 FFmpeg 的各种内置 codec,这里主要针对 H265 编码、MP4 封装做了优化。编译时只按需编译最少的模块,减小产物体积。

WebGL 负责渲染。FFmpeg 解码出来的视频数据是 YUV 格式,普通的 Canvas 2D 只能画 RGB,需要做颜色空间转换。用 WebGL 可以把这个过程放到 GPU 上,性能好很多。

Web Audio 负责音频播放。FFmpeg 解码出 PCM 格式的音频数据,直接喂给 Web Audio API 播放。

README区域截图

线程模型

播放器用了三个线程:

  • 主线程:界面控制、播放控制、下载控制、音视频渲染、音视频同步
  • 解码线程(Decoder Worker):音视频数据的解封装、解码
  • 下载线程(Downloader Worker):下载视频文件的 chunk

线程之间通过 postMessage 异步通信。传输大量数据(比如视频帧)时用 Transferable 接口传引用,避免拷贝带来的性能损耗。

这里有个限制:WASM 对多线程(pthread)的支持还不成熟,各浏览器都还在试验阶段,所以原生代码里不能写 pthread,只能用 Web Worker 来实现多线程。

缓冲控制

缓存控制是这个播放器的关键。因为 WASM 目前没法用多线程同步,FFmpeg 的同步读数据接口必须保证能返回数据,否则就会报错退出。

项目做了两层缓存:

  1. 文件元信息获取前的数据缓存
  2. 解码帧缓存

数据不足时停止解码,进入 Buffer 状态;数据够了继续解码播放,返回 Play 状态。这样能保证 FFmpeg 任何时候读数据都能拿到,不会崩。

下载也有速率控制。拿到文件码率后,以码率的一定倍数下载,不会无限制占用带宽。

音视频同步

音频数据直接给 Web Audio,通过它的 API 获取当前播放时间戳,作为时间基准来同步视频帧。

视频帧时间落后就立刻渲染,时间早就 delay。delay 用 setTimeout 实现。缓冲控制的另一个意义就是控制视频渲染频率,如果 setTimeout 的视频帧太多,内存会暴涨。

实际效果

目前这个播放器支持 MP4/FLV 文件播放、HTTP-FLV 流播放。接口比较基础:play、pause、resume、stop、fullscreen,seek 还没实现。

浏览器兼容性方面,Chrome、Firefox、Edge 的较新版本都能跑,360 浏览器、搜狗浏览器这些 webkit 内核的也支持。

主要问题是 H265 解码的 CPU 占用比较高,毕竟没有硬件加速。另外如果不及时传递音频数据,AudioContext 的 currentTime 可能导致音视频不同步。

适用场景

这个项目更适合做技术验证和学习。如果你想了解 WASM + FFmpeg 在浏览器端的实现方式,这个项目的代码结构清晰,模块划分合理,值得读一读。

如果要在生产环境用,还需要做不少优化,特别是性能和兼容性方面。但作为探索 Web 端视频播放可能性的参考,这个项目提供了完整的实现思路。

如果要在生产环境用,还需要做不少优化,特别是性能和兼容性方面。但作为探索 Web 端视频播放可能性的参考,这个项目提供了完整的实现思路。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐