浏览器对 H.265(HEVC)的支持一直是个痛点。虽然 Safari 和部分 Edge 版本支持较好,但 Chrome 等主流内核往往需要付费授权或硬件支持才能原生解码。为了在网页端实现流畅播放,我们需要构建一套混合解码方案:优先调用 WebCodecs API 利用 GPU 硬解,若不支持则自动降级到 WebAssembly 版的 FFmpeg 进行 CPU 软解。
架构设计
整体流程分为解封装、解码和渲染三个环节。核心在于根据浏览器能力动态选择解码路径。
graph TD
A[视频流 H.265] --> B{浏览器支持 WebCodecs?}
B -- Yes --> C[WebCodecs API VideoDecoder]
C --> D[GPU 解码 VideoFrame]
B -- No --> E[FFmpeg Wasm + SIMD]
E --> F[CPU 解码 YUV420]
D --> G[Canvas WebGL 渲染]
F --> G
编译 FFmpeg 为 WebAssembly
这是最关键的一步。我们需要使用 Emscripten 将 C 语言编写的 FFmpeg 编译成 .wasm 文件。为了性能,必须开启多线程和 SIMD 指令集加速。
关键编译参数:
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 语言胶水代码,用于暴露 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._init_h265_decoder();
postMessage({ type: 'ready' });
});
self.onmessage = function(e) {
const { type, data } = e.data;
if (type === 'decode') {
ptr = decoderModule.(data.);
decoderModule..(data, ptr);
ret = decoderModule.(codecContext, ptr, data.);
(ret === ) {
yuvData = ();
({ : , : yuvData }, [yuvData.]);
}
decoderModule.(ptr);
}
};


