H.265 (HEVC) 网页播放:WebAssembly + FFmpeg 实现浏览器端硬解/软解兼容方案
📉 前言:浏览器对 H.265 的'爱恨情仇'
为什么 <video src="video.h265.mp4"> 在 Chrome 里放不出来?因为 H.265 的专利池太深了。只有 Safari (即使是 iOS) 和 Edge (需硬件支持) 原生支持较好。
我们的目标是构建一套混合解码方案:
- 优先硬解 (WebCodecs):如果浏览器支持硬件加速(如 Chrome 94+ 的 WebCodecs),直接调用 GPU,性能起飞。
- 降级软解 (Wasm + FFmpeg):如果不支持,自动切换到 WebAssembly 版的 FFmpeg 进行 CPU 软解,利用 SIMD 指令集加速。
播放器架构图
graph TD
A[视频流 H.265] --> B(解封装 Demuxer)
B --> C{浏览器支持 WebCodecs?}
C -- Yes --> D[WebCodecs API GPU 解码]
C -- No --> E[Wasm + FFmpeg CPU 解码]
D --> F[Canvas WebGL 渲染]
E --> F
🛠️ 一、编译 FFmpeg 为 WebAssembly
这是最困难的一步。我们需要使用 Emscripten 将 C 语言编写的 FFmpeg 编译成 .wasm 文件。
关键编译参数
为了性能,必须开启 Multithreading (多线程) 和 SIMD (单指令多数据流)。
# Docker 环境下编译示例
emcc \
-Llibavcodec -Llibavutil -Llibswscale \
-I. \
-o ffmpeg-decoder.js \
src/decoder.c \
-s WASM=1 \
-s USE_PTHREADS=1 \
-s PTHREAD_POOL_SIZE=4 \
-s SIMD=1 \
-s ALLOW_MEMORY_GROWTH=1 \
-O3
注意:
src/decoder.c是你需要编写的 C 语言胶水代码,用于暴露 FFmpeg 的avcodec_send_packet和avcodec_receive_frame接口给 JS 调用。
🧬 二、核心实现:Web Worker 中的解码循环
解码是 CPU 密集型任务,绝对不能放在主线程,否则页面会卡死。我们需要在 Web Worker 中运行 Wasm。
1. 初始化解码器 (Worker.js)
importScripts('ffmpeg-decoder.js');
let decoderModule;
let codecContext;
// 初始化 Wasm 模块
Module().then(module => {
decoderModule = module;
// 调用 C 导出的初始化函数
codecContext = decoderModule.();
({ : });
});
self. = () {
{ type, data } = e.;
(type === ) {
ptr = decoderModule.(data.);
decoderModule..(data, ptr);
ret = decoderModule.(codecContext, ptr, data.);
(ret === ) {
yuvData = ();
({ : , : yuvData }, [yuvData.]);
}
decoderModule.(ptr);
}
};


