23、编解码硬件加速:WebGL与VideoFrame、硬件编码器调用、性能对比

聊到WebRTC的编解码,很多同学第一反应是“软编软解够用了吧?”

嗯,在PC上确实还行。但放到移动端、低功耗设备、或者高分辨率场景下,软编软解就成了性能瓶颈。我最早做视频会议项目时,用软编码推720p,CPU直接飙到80%,手机烫得能煎鸡蛋。后来切到硬件加速,CPU降到20%,帧率还稳了。

说白了,硬件加速不是锦上添花,是刚需。

WebGL与VideoFrame:浏览器里的硬解通道

先讲VideoFrame。这是WebCodecs API的核心对象,代表一帧未压缩的视频数据。你可以从摄像头、Canvas、或者网络流里拿到它。

关键点:VideoFrame本身不关心数据是软解还是硬解来的。但浏览器底层会尽量用硬件去解码,尤其是H.264和H.265。

我习惯这么用:

// 从MediaStreamTrack获取VideoFrame
const track = stream.getVideoTracks()[0];
const processor = new MediaStreamTrackProcessor({ track });
const reader = processor.readable.getReader();

while (true) {
  const { value: frame, done } = await reader.read();
  if (done) break;
  
  // frame 就是 VideoFrame 对象
  console.log(frame.format, frame.codedWidth, frame.codedHeight);
  frame.close(); // 记得释放
}

这里有个坑——VideoFrame用完必须close()。我曾经在循环里忘了关,结果内存泄漏,页面跑几分钟就崩了。嗯,血的教训。

那WebGL呢?它主要负责渲染。你可以把VideoFrame直接传给WebGL纹理,用GPU做色彩转换、缩放、甚至美颜滤镜。这样CPU就彻底解放了。

// 将VideoFrame绘制到WebGL
const gl = canvas.getContext('webgl2');
const texture = gl.createTexture();

// 使用VideoFrame的createImageBitmap或直接传入texImage2D
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, videoFrame);

你想想看,整个流程就是:硬解 → VideoFrame → WebGL渲染。CPU几乎不参与像素操作。

硬件编码器调用:从VideoFrame到编码流

编码这边,WebCodecs提供了VideoEncoder接口。浏览器会优先选择硬件编码器,前提是系统支持。

我建议这样初始化:

const encoder = new VideoEncoder({
  output: (chunk) => {
    // chunk是EncodedVideoChunk,可以直接塞给RTCPeerConnection
    console.log('编码输出:', chunk.byteLength, 'bytes');
  },
  error: (e) => console.error('编码错误:', e)
});

const config = {
  codec: 'avc1.42001E', // H.264 baseline
  width: 1280,
  height: 720,
  bitrate: 2_000_000,
  framerate: 30,
  hardwareAcceleration: 'prefer-hardware' // 关键!
};

await encoder.configure(config);

小技巧:hardwareAcceleration有三个值:prefer-hardwareprefer-softwareno-preference。我一般用prefer-hardware,如果浏览器不支持硬编,会自动降级到软编。

编码时,把VideoFrame喂进去:

encoder.encode(frame, { keyFrame: false });
frame.close(); // 编码器内部会拷贝数据,这里可以安全释放

我曾经遇到一个问题:某些Android设备上,硬编出来的H.264流,解码端花屏。排查后发现是编码配置里的avc1.42001Eavc1.64001E混用了。不同profile的兼容性不一样,建议统一用avc1.42001E(baseline),兼容性最好。

性能对比:硬编硬解 vs 软编软解

直接上数据。我在一台MacBook Pro M1上做了测试,推720p 30fps的视频流:

方案 CPU占用 编码延迟 功耗 画质(SSIM)
软编(OpenH264) 45% 8ms 0.92
硬编(VideoToolbox) 12% 3ms 0.91
软解(FFmpeg) 38% 6ms
硬解(VideoToolbox) 8% 2ms

看到没?硬编硬解在CPU和功耗上优势巨大,画质几乎没有损失。SSIM只差了0.01,人眼根本看不出来。

注意:硬编硬解不是万能的。低码率场景下,硬编的码控算法不如软编精细。我做过对比,在500kbps以下,软编的细节保留更好。所以如果你做的是低带宽场景,建议软编。

核心逻辑流程图

下面这张图,概括了硬件加速的完整链路:

硬件加速编解码核心链路 摄像头采集 VideoFrame 硬件解码器 WebGL渲染 硬件编码器 编码流输出 软件编解码 CPU处理 硬编/硬解 软编/软解(备选) 虚线表示备选路径,浏览器自动降级

从图里能看出来,硬件加速的核心就是让GPU和专用编解码芯片干活,CPU只做轻量调度。虚线是备选路径——如果硬件不支持,浏览器会自动切到软编软解。

避坑指南

  • 硬编不支持所有分辨率:比如某些芯片只支持16的倍数。我遇到过编码1280x718直接报错,改成1280x720就好了。
  • 硬解帧率不稳定:低端设备上,硬解第一帧可能延迟较大。建议预解码2-3帧再渲染。
  • VideoFrame生命周期:不要跨异步操作持有VideoFrame。它背后是GPU显存,不及时close会泄漏。
  • WebGL上下文丢失:移动端切后台时,WebGL上下文可能丢失。监听webglcontextlost事件,重建纹理。

我的习惯:在编码前,先调用VideoEncoder.isConfigSupported()检查配置是否支持硬编。不支持就降级到软编,或者降低分辨率。

好了,硬件加速这块就聊到这儿。核心就三点:用VideoFrame接硬解、用VideoEncoder调硬编、用WebGL做渲染。性能差距摆在那,不用白不用。


公众号:蓝海资料掘金营,微信deep3321