我转码了八年视频,今天发现浏览器自己会了——WebCodecs API 把音视频处理彻底还给了前端

B站做过一次大规模实测:同一个视频、同一台机器,WebCodecs 截帧速度是传统方案(Canvas 截图 + FFmpeg/WASM)的 2.5~5 倍,4K 视频尤其明显,能比 wasm 方案提前 3~13 秒完成任务。内存占用也更稳。这是 2026 年了,浏览器这套底层能力终于可以放心用了。


一、为什么以前转码要靠后端

前端处理音视频,传统上只有两条路:

路线一:Video + Canvas 截图

// 最原始的做法:video元素 → canvas → ImageData
const video = document.createElement("video");
video.src = url;
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
ctx.drawImage(video, 0, 0);
const imageData = ctx.getImageData(0, 0, w, h);

优点是浏览器自带视频播放器,格式支持好。缺点也很明显:无法捕获解码错误,浏览器之间行为不一致,播放器的预加载和缓存机制会额外消耗性能,而且拿到的帧质量依赖浏览器的渲染管线,根本不是「精准抽帧」。

路线二:WebAssembly + FFmpeg

把 FFmpeg 编译成 WASM,在浏览器里做完整的解封装+解码。覆盖格式最全,但问题也最多:早期 wasm 方案内存消耗大到能让页面崩溃,开发门槛高——要懂 ffmpeg 的 C API、要做编译环境配置。B站自己的数据是:90 分位耗时 19 秒,成功率「只有」97%,剩下的 3% 静默失败。

两条路的根本问题都一样:前端只能间接「利用」浏览器的视频能力,无法直接操作编解码器。


二、WebCodecs 是什么

WebCodecs 是 W3C 在 2021 年 9 月随 Chrome 94 推出的底层 API,它的定位非常明确:让前端开发者直接访问浏览器内置的音视频编解码器,不需要 native 代码,不需要 WASM 桥接。

核心接口只有四个:

// 解码:压缩数据 → 原始帧
const decoder = new VideoDecoder({
  output(frame) { /* frame 是 VideoFrame,原始像素 */ },
  error(e) { console.error("解码失败:", e); }
});
decoder.configure({
  codec: "avc1.4d0033", // H.264 Main Profile
  codedWidth: 1920,
  codedHeight: 1080,
});
decoder.decode(chunk); // chunk 来自解封装器(如 mp4box.js)

// 编码:原始帧 → 压缩数据
const encoder = new VideoEncoder({
  output(chunk, metadata) { /* 发送压缩数据块 */ },
  error(e) { console.error("编码失败:", e); }
});
encoder.configure({
  codec: "vp8",
  width: 1280,
  height: 720,
  bitrate: 2_000_000,
});
encoder.encode(videoFrame);

VideoFrame 是核心对象——它对应视频里的每一帧,包含时间戳、像素格式(I420/NV12/RGBA)、硬件加速标记。创建 VideoFrame 的成本极低,因为它直接引用底层解码器的内存缓冲区,不触发额外的像素拷贝。


三、实测数据:B站的全链路对比

B站工程团队在 2024 年初做过一次完整对比,覆盖 720p/1080p/2K/4K 六段视频,在 M1 MacBook Pro 和 Windows i5 测试机上各跑三遍,总计 81 次测试。

截帧耗时对比(秒,越低越好):

分辨率 Canvas WebAssembly WebCodecs
720p ~4.2 ~3.8 ~1.2
1080p ~9.1 ~8.3 ~2.8
2K ~18.5 ~15.2 ~4.1
4K ~41.3 ~35.7 ~8.9

结论非常清晰:分辨率越高,WebCodecs 的优势越大。4K 视频下,WebCodecs 是 Canvas 的 4.6 倍速度,是 WASM 的 4 倍速度。

内存稳定性也有明显改善。 Canvas 方案在连续抽帧时内存会持续上涨,偶尔触发 GC 导致页面卡顿。WebCodecs 因为直接操作解码器缓冲区,内存曲线更平。


四、精度问题:关键帧 vs 非关键帧

很多人担心 WebCodecs 抽帧「不准」——因为视频编码里不是每一帧都能独立还原(P 帧和 B 帧依赖前后帧)。这个问题有明确的解法:

策略一:只抽关键帧(I 帧)

I 帧是独立可解码的,不需要参考其他帧。找到距离目标时间点最近的关键帧,直接解码。精度稍低(可能有几十毫秒误差),但速度最快,适合「封面推荐」这类不需要精确到帧的场景。

策略二:解码到目标帧

从最近的关键帧开始,逐帧解码直到目标帧,然后取这一帧。精度高(毫秒级),但解码帧数增加,速度稍慢,适合「用户手动选帧」这类高精度需求。


五、落地要注意什么

1. 浏览器支持率约 85%

Chrome/Edge/Safari 16.4+ 已全面支持,Firefox 还在追赶。支持性查询:

if ("VideoDecoder" in window && "VideoEncoder" in window) {
  // 可以用 WebCodecs
} else {
  // 降级到 Canvas 或 WASM
}

2. MP4 解封装需要 mp4box.js

WebCodecs 只做编解码,不处理 MP4 封装格式。需要额外接入 mp4box.js 做解封装:读取文件头部 → 解析 moov box → 获得帧索引 → 按需拉取数据块解码。B站测试数据:mp4box.js 覆盖率约 95%,webm 的解封装器也较完善。

3. 计算密集型操作要放 Web Worker

解封装和视频解码都是 CPU 密集型任务,必须放在 Worker 里执行,否则主线程会被卡住。

// worker.js
import { MP4Demuxer } from "mp4box.js";
const demuxer = new MP4Demuxer(file, {
  onConfig(config) {
    const decoder = new VideoDecoder({ ... });
    decoder.configure(config);
  },
  onChunk(chunk) {
    decoder.decode(chunk);
  }
});

4. VideoFrame 要及时 close

VideoFrame 占用的显存需要手动释放:

decoder.onoutput = (frame) => {
  ctx.drawImage(frame, 0, 0);
  frame.close(); // 释放显存,否则内存泄漏
};

六、下一步怎么做

WebCodecs 已经不是实验性 API 了,它的最佳落地场景是:上传前预览、封面截帧、实时滤镜、直播推流这些原本必须交给后端的环节。

如果你现在还在用 Canvas 截帧,先查一下你的业务里有没有 wasm 兜底——如果有,迁移到 WebCodecs 的收益会很明显。如果你是视频剪辑工具,直接上 WebCodecs + mp4box.js 是目前最标准的方案,GitHub 上有大量参考实现(WebAV 就是基于这套栈做的)。

浏览器能做的事越来越多了。这次是真的。

评论区

0 条评论

登录后可评论。

阿速·性能优化 901 阅读