2021年,Chromium团队在Chrome 94中正式发布了WebCodecs API。这个看似低调的新特性,实际上解决了困扰Web开发者十五年的根本问题:浏览器视频处理要么是"黑盒"(无法控制细节),要么是"低效"(需要WebAssembly搬运编解码器)。

在此之前,如果想在浏览器中对视频进行逐帧处理——比如实现一个Web端视频编辑器、实时滤镜、或者低延迟直播推流——开发者面临的是一个两难选择:使用MediaRecorder等高层API,获得的是封装好的、无法干预的"黑盒";或者引入FFmpeg.wasm,下载几兆的WebAssembly代码,牺牲性能和功耗,来换取对编解码过程的控制权。

WebCodecs打破了这个死循环。它直接暴露浏览器内置的编解码器,支持硬件加速,让JavaScript代码能够精确控制每一帧视频的编码参数、获取原始像素数据、构建完整的媒体处理管道。根据Remotion团队的基准测试,在M2 MacBook Air上,WebCodecs的视频转码速度比FFmpeg.wasm快15倍以上——这不是数量级的优化,而是质变。

浏览器视频处理的三代演进

要理解WebCodecs的价值,需要先回顾浏览器视频处理能力的演进历史。这不是一个线性的"越来越好"的故事,而是三种不同设计哲学的博弈。

第一代:完全黑盒(HTMLVideoElement + Canvas)

最早的方案也是最简单的:用<video>元素播放视频,用<canvas>drawImage()逐帧抓取画面进行处理。这个方案的问题是显而易见的——开发者无法保证处理所有帧。requestAnimationFramerequestVideoFrameCallback的回调时机取决于浏览器的渲染节奏,如果处理逻辑耗时过长,帧就会被跳过。对于一个需要在精确时间点插入特效的视频编辑器来说,这种"丢帧"是不可接受的。

更重要的是,这个方案无法处理编码层面的问题。你拿到了像素数据,但无法控制视频是如何被压缩的。

第二代:半透明黑盒(MediaRecorder + MSE + WebRTC)

MediaRecorder API让开发者能够录制视频流并输出编码后的数据。Media Source Extensions(MSE)允许动态地向<video>元素喂入媒体片段。WebRTC则解决了实时通信场景下的音视频传输。

这三个API各有用途,但共享一个共同的特征:它们在特定场景下高效,但在需要精确控制时捉襟见肘。

MediaRecorder的编码参数控制非常有限。你可以指定videoBitsPerSecondmimeType,但无法控制关键帧间隔、编码预设、码率控制模式等底层参数。输出格式也受限——你只能得到浏览器选定的容器格式,无法自定义封装逻辑。

MSE专注于播放,它的设计目标是让开发者能够实现自适应码率流媒体(如HLS、DASH),而不是让开发者介入解码过程。你喂给它的是编码后的媒体片段,它负责解码和播放——解码过程完全封装。

WebRTC在实时通信场景表现出色,但它的编码器配置同样受限。而且,WebRTC的信令和传输逻辑与编解码器紧耦合,如果你的需求只是"用WebRTC的编码器",很难单独提取出来使用。

第三代:白盒控制(WebCodecs)

WebCodecs的设计哲学截然不同:它不解决特定场景的问题,而是暴露底层能力。

WebCodecs的核心接口:
- VideoEncoder:将VideoFrame编码为EncodedVideoChunk
- VideoDecoder:将EncodedVideoChunk解码为VideoFrame  
- VideoFrame:原始视频帧,可来自Canvas、摄像头、或原始像素数据
- EncodedVideoChunk:编码后的视频数据块
- AudioEncoder/AudioDecoder:音频编解码
- ImageDecoder:图像解码(支持GIF动画等)

这意味着开发者需要自己处理更多事情:封装/解封装(muxing/demuxing)、时间戳管理、缓冲控制。但作为回报,开发者获得了对编解码过程的完全控制。

WebCodecs的核心架构

WebCodecs的架构设计围绕着"帧"(Frame)这个核心概念展开。所有的编解码操作都以帧为基本单位——编码器消费帧、输出编码块;解码器消费编码块、输出帧。

帧的生命周期:VideoFrame

VideoFrame是WebCodecs最重要的数据结构。它代表一个未压缩的视频帧,包含像素数据和元数据(时间戳、色彩空间、尺寸等)。

创建VideoFrame有三种方式:

// 方式1:从Canvas创建
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// ... 绘制操作
const frame = new VideoFrame(canvas, { timestamp: performance.now() * 1000 });

// 方式2:从摄像头流创建(通过MediaStreamTrackProcessor)
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const track = stream.getVideoTracks()[0];
const processor = new MediaStreamTrackProcessor(track);
const reader = processor.readable.getReader();
const { value: frame } = await reader.read();

// 方式3:从原始像素数据创建
const pixels = new Uint8Array(width * height * 4); // RGBA数据
const frame = new VideoFrame(pixels, {
  timestamp: 0,
  codedWidth: width,
  codedHeight: height,
  format: 'RGBA'
});

VideoFrame的一个关键特性:它是可转移对象(Transferable)。这意味着可以通过postMessage将其从主线程转移到Worker,而无需拷贝底层的像素数据。这对于构建高性能的视频处理管道至关重要。

但这也带来了一个容易出错的细节:VideoFrame持有GPU或系统内存资源,必须显式调用close()释放。忘记关闭帧会导致显存泄漏,最终导致解码器阻塞。

// 错误示例:忘记关闭帧
while (true) {
  const { value: frame, done } = await reader.read();
  if (done) break;
  encoder.encode(frame, { keyFrame: false });
  // frame.close(); // 忘记调用!
}

// 正确示例
while (true) {
  const { value: frame, done } = await reader.read();
  if (done) break;
  encoder.encode(frame, { keyFrame: false });
  frame.close(); // 必须显式释放
}

编码器:VideoEncoder

VideoEncoder的工作流程是异步的。调用encode()方法时,帧被加入内部队列,实际的编码工作在后台进行。编码完成后,通过回调函数输出编码块。

const encoder = new VideoEncoder({
  output: (chunk, metadata) => {
    // chunk是EncodedVideoChunk
    // metadata包含decoderConfig等信息
    handleEncodedChunk(chunk, metadata);
  },
  error: (e) => {
    console.error('编码错误:', e);
  }
});

// 配置编码器
await encoder.configure({
  codec: 'avc1.42001E', // H.264 Baseline Profile
  width: 1920,
  height: 1080,
  bitrate: 5_000_000, // 5 Mbps
  framerate: 30,
  latencyMode: 'realtime' // 低延迟模式
});

// 编码帧
encoder.encode(frame, { keyFrame: false });

编码器的配置参数直接决定了输出视频的质量和性能。这里有一个重要的权衡:硬件编码器速度更快,但压缩效率通常低于软件编码器。同样的码率下,硬件编码可能产生更大的文件或更低的画质。对于存储为主的场景,软件编码可能是更好的选择;对于实时通信,硬件编码的低延迟优势更重要。

latencyMode是WebCodecs特有的参数。设置为'realtime'时,编码器会优先保证低延迟,可能牺牲质量或增加码率。设置为'quality'时则相反。

解码器:VideoDecoder

VideoDecoder的工作方式与编码器对称:接收编码块,输出帧。

const decoder = new VideoDecoder({
  output: (frame) => {
    // 渲染或处理帧
    renderFrame(frame);
    frame.close(); // 重要:处理完后立即释放
  },
  error: (e) => {
    console.error('解码错误:', e);
  }
});

await decoder.configure({
  codec: 'avc1.42001E',
  codedWidth: 1920,
  codedHeight: 1080,
  description: avcCData // H.264的AVCC配置数据
});

// 解码
const chunk = new EncodedVideoChunk({
  type: 'key', // 'key'或'delta'
  timestamp: 0,
  data: encodedData
});
decoder.decode(chunk);

解码器的一个重要细节:硬件解码器通常有严格的缓冲区限制。如果输出的帧没有及时消费(调用close()),解码器会因为缓冲区满而停止输出。这是构建实时视频管道时最常见的性能陷阱之一。

硬件加速的工作原理

WebCodecs的性能优势来自硬件加速。现代CPU和GPU都内置了专用的视频编解码电路——Intel的Quick Sync Video、NVIDIA的NVENC/NVDEC、AMD的VCE/VCN、Apple的Media Engine。这些专用电路在处理视频编解码时,比通用CPU快数倍,功耗更低。

WebCodecs自动利用这些硬件加速器,但开发者需要理解其中的权衡:

启动延迟与吞吐量

硬件编解码器的启动延迟通常比软件编解码器高。初始化硬件电路、分配显存、建立DMA通道都需要时间。但对于持续的视频流,硬件加速器的吞吐量优势明显。

这意味着:如果只需要处理几帧图像,软件编解码可能更快;但如果处理一段视频,硬件加速的优势会随着时长增加而放大。

跨平台差异

不同操作系统和硬件平台对视频编解码的支持差异很大:

  • H.264(AVC):几乎所有设备都支持硬件编解码,是最安全的选择
  • H.265(HEVC):Windows和macOS支持良好,Linux取决于驱动,部分浏览器需要许可证
  • VP9:Chrome支持良好,Safari支持有限
  • AV1:最新硬件(Intel Arc、NVIDIA RTX 40系列、Apple M3+)开始支持硬件编解码

WebCodecs提供了isConfigSupported()静态方法来检测支持情况:

const config = {
  codec: 'av01.0.08M.10', // AV1 Main Profile
  width: 1920,
  height: 1080,
  bitrate: 3_000_000
};

const { supported, config: supportedConfig } = await VideoEncoder.isConfigSupported(config);
if (!supported) {
  // 回退到其他编解码器
  config.codec = 'vp09.00.10.08'; // VP9
}

GPU/CPU选择的隐式决策

WebCodecs不直接暴露"使用GPU还是CPU"的开关,而是由浏览器根据配置和硬件能力自动决定。但配置参数会影响这个决策:

  • 分辨率很低(如320x180)时,浏览器可能选择软件编解码,因为GPU的启动开销超过收益
  • 高分辨率(4K及以上)几乎必然使用硬件加速
  • latencyMode: 'realtime'更倾向于硬件编码
  • 某些编码参数组合(如高量化参数、特定预设)可能只在软件编码中可用

性能基准:WebCodecs vs FFmpeg.wasm

理论分析之后,来看实际性能数据。Remotion团队在M2 MacBook Air上进行了一组对比测试,将WebCodecs与FFmpeg.wasm(SIMD版本)进行直接对比:

测试场景1:MP4转WebM(H.264 → VP8)

  • 测试视频:Big Buck Bunny,1080p,10分钟
  • WebCodecs(通过@remotion/webcodecs):平均7.4秒
  • FFmpeg.wasm:平均113.3秒
  • 加速比:15.3倍

测试场景2:AV1 WebM转MP4(AV1 → H.264)

  • 测试视频:AV1编码的Big Buck Bunny
  • WebCodecs:平均4秒
  • FFmpeg.wasm:平均20.3秒
  • 加速比:5.1倍

来源:remotion-dev/webcodecs-benchmark

这个差距的本质原因不是"WebAssembly慢",而是"FFmpeg的硬件优化无法移植到WebAssembly"。FFmpeg包含大量针对特定CPU架构(x86 AVX、ARM NEON)的手写汇编优化,这些代码在编译到WebAssembly时会被剥离,因为WebAssembly必须跨架构兼容。

而WebCodecs直接调用浏览器内置的编解码器——这些编解码器由操作系统和硬件厂商提供,已经针对特定平台深度优化。

真实设备性能分布

WebAV项目的作者在不同设备上测试了视频合成性能(10分钟1080p视频,添加文字叠加后导出):

设备 WebCodecs FFmpeg(本地) 剪映App
M1 MacBook Pro 约60秒 约30秒 约35秒
Ryzen 7 5800 + RTX 3080 约90秒 约45秒 约50秒
i9-9900k + RTX 2070 约120秒 约60秒 约70秒
i5-8265U(核显) 约300秒 约200秒 不支持

来源:WebCodecs性能表现及优化思路

可以看到,在Apple Silicon设备上,WebCodecs的性能已经非常接近原生应用。这得益于Apple M系列芯片的Media Engine与WebCodecs的良好集成。

内存管理:显存泄漏的陷阱

VideoFrame持有的像素数据通常存储在GPU显存中(硬件解码时)或系统内存中(软件解码时)。这些资源不会自动释放——JavaScript的垃圾回收器不会追踪GPU资源。

这是一个常见的新手错误模式:

// 问题代码:帧在队列中累积
const frameQueue = [];

decoder.addEventListener('output', (frame) => {
  frameQueue.push(frame);
});

function renderNextFrame() {
  if (frameQueue.length > 0) {
    const frame = frameQueue.shift();
    ctx.drawImage(frame, 0, 0);
    frame.close();
  }
  requestAnimationFrame(renderNextFrame);
}

问题在于:如果渲染速度跟不上解码速度,帧会在队列中累积,占用大量显存。当显存耗尽时,解码器会停止输出新帧,整个管道冻结。

正确的做法是实现显存压力控制:

const MAX_QUEUE_SIZE = 3;

decoder = new VideoDecoder({
  output: (frame) => {
    if (frameQueue.length >= MAX_QUEUE_SIZE) {
      // 队列已满,丢弃最旧的帧
      const oldFrame = frameQueue.shift();
      oldFrame.close();
    }
    frameQueue.push(frame);
  },
  error: handleError
});

编码端也有类似问题。VideoEncoder的encodeQueueSize属性表示等待编码的帧数量:

if (encoder.encodeQueueSize > 2) {
  // 编码器忙不过来,跳过当前帧
  frame.close();
} else {
  encoder.encode(frame, { keyFrame });
  frame.close();
}

与WebGPU和WebGL的协同

WebCodecs解决了编解码问题,但视频处理往往还涉及像素操作——色彩空间转换、滤镜、特效叠加等。这时候就需要与WebGPU或WebGL协同工作。

VideoFrame作为WebGPU纹理

WebGPU提供了importExternalTexture()方法,可以直接将VideoFrame导入为GPU纹理,无需CPU拷贝:

const device = await navigator.gpu.requestAdapter().then(a => a.requestDevice());
const context = canvas.getContext('webgpu');

// 从VideoFrame创建外部纹理
const externalTexture = device.importExternalTexture({ source: videoFrame });

// 在着色器中采样
const bindGroup = device.createBindGroup({
  layout: bindGroupLayout,
  entries: [
    { binding: 0, resource: externalTexture }
  ]
});

// 渲染完成后释放帧
videoFrame.close();

这种零拷贝的集成方式对于实时视频处理管道至关重要。传统的Canvas方式需要drawImage将帧绘制到Canvas,再通过getImageDatatexImage2D将数据传回GPU——中间涉及CPU拷贝。

WebGL集成

对于WebGL,WebCodecs帧可以通过texImage2D直接上传:

const gl = canvas.getContext('webgl2');
const texture = gl.createTexture();

gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, videoFrame);

WebGL工作组还提出了WEBGL_webcodecs_video_frame扩展提案,旨在提供更高效的WebCodecs帧导入方式,但目前尚未广泛实现。

容器格式的挑战:Muxing/Demuxing

WebCodecs只处理编解码,不处理容器格式。如果你想读取一个MP4文件,需要先解封装(demux)提取H.264编码流;如果想把编码后的视频保存为文件,需要封装(mux)成MP4或WebM容器。

这是WebCodecs与FFmpeg.wasm最大的使用体验差异:FFmpeg一行命令搞定转码,WebCodecs需要自己处理容器。

常用解封装库

社区已经涌现出多个针对WebCodecs优化的解封装库:

  • mp4box.js:功能完整的MP4解析库,支持ISO Base Media File Format
  • @remotion/media-parser:Remotion团队开发的解封装库,支持MP4、WebM、MOV等
  • demuxer:轻量级的媒体解封装库
import { parseMedia } from '@remotion/media-parser';

const { videoTracks, audioTracks } = await parseMedia(arrayBuffer);

// 获取编码配置
const videoTrack = videoTracks[0];
const codecConfig = {
  codec: videoTrack.codec,
  codedWidth: videoTrack.width,
  codedHeight: videoTrack.height,
  description: videoTrack.description // AVCC/AV1配置数据
};

封装器

封装相对更复杂,需要处理时间戳、同步、容器结构等。目前最成熟的方案是结合使用mux.js(HLS.js项目的一部分)和WebCodecs:

import mux from 'mux.js';

const transmuxer = new mux.mp4.Transmuxer();

// 将编码块送入封装器
transmuxer.push({
  data: chunkData,
  track: 1,
  pts: chunk.timestamp,
  dts: chunk.timestamp
});

transmuxer.flush();

transmuxer.on('data', (segment) => {
  // 输出MP4片段
  const mp4Data = segment.data;
});

插入式流:构建实时处理管道

WebCodecs与MediaStreamTrack Insertable Media Processing(俗称"Insertable Streams")API结合,可以构建完整的实时视频处理管道:

flowchart LR
    A[摄像头 MediaStream] --> B[MediaStreamTrackProcessor]
    B --> C[ReadableStream<br/>VideoFrame]
    C --> D[TransformStream<br/>帧处理]
    D --> E[VideoEncoder]
    E --> F[EncodedVideoChunk]
    F --> G[网络传输/存储]
    
    H[网络接收] --> I[EncodedVideoChunk]
    I --> J[VideoDecoder]
    J --> K[ReadableStream<br/>VideoFrame]
    K --> L[TransformStream<br/>后处理]
    L --> M[MediaStreamTrackGenerator]
    M --> N[video元素]

MediaStreamTrackProcessor将MediaStreamTrack转换为VideoFrame流,MediaStreamTrackGenerator则反向操作——将帧流转换为MediaStreamTrack,可以附加到<video>元素或发送给WebRTC。

// 完整的实时处理管道示例
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const track = stream.getVideoTracks()[0];

// 创建处理器和生成器
const processor = new MediaStreamTrackProcessor({ track });
const generator = new MediaStreamTrackGenerator({ kind: 'video' });

// 处理管道
const transformStream = new TransformStream({
  async transform(frame, controller) {
    // 处理帧(如背景虚化、滤镜等)
    const processedFrame = await processFrame(frame);
    controller.enqueue(processedFrame);
    frame.close();
  }
});

// 连接管道
processor.readable
  .pipeThrough(transformStream)
  .pipeTo(generator.writable);

// 输出到video元素
const processedStream = new MediaStream([generator]);
videoElement.srcObject = processedStream;

实际应用案例

Clipchamp:浏览器端视频编辑器

Clipchamp在被收购前就已经在生产环境中使用WebCodecs。作为一款完全运行在浏览器中的视频编辑器,WebCodecs让它能够:

  • 实现精确的帧级编辑(而非依赖服务端渲染)
  • 支持多种编解码器,无需预转码
  • 利用硬件加速实现流畅的预览

Clipchamp的工程师Sören Balko在W3C Media Production Workshop上分享了他们的经验:WebCodecs让他们能够将原本需要服务器完成的转码工作完全移到客户端,降低了服务器成本,同时改善了用户体验(无需等待上传和转码)。

Remotion:程序化视频生成

Remotion是一个使用React生成视频的框架。他们开发了@remotion/webcodecs包,让用户能够在浏览器中完成视频转码、压缩、裁剪等操作——完全离线,无需服务器。

根据Remotion团队的测试,WebCodecs在转码任务上的性能已经超过了他们之前使用的FFmpeg.wasm方案。更重要的是,WebCodecs的API设计让用户能够在转码过程中精确控制每一帧——比如在特定时间点插入水印、实现复杂的过渡效果。

Framecrafter:开源视频编辑器

Framecrafter是一个基于WebCodecs构建的开源视频编辑器项目。它的架构展示了WebCodecs在实际产品中的应用模式:使用Web Worker运行视频处理管道,主线程只负责UI交互,通过postMessage传递VideoFrame。

浏览器兼容性与未来展望

截至2026年初,WebCodecs的浏览器支持情况如下:

浏览器 支持状态 备注
Chrome 94+ 完整支持 首发浏览器
Edge 94+ 完整支持 基于Chromium
Safari 16.4+ 部分支持 视频编解码支持良好,音频支持有限
Safari 26.1+ 完整支持 音视频编解码完整支持
Firefox 开发中 预计2026年发布
Firefox Android 不支持 暂无计划

来源:caniuse.com/webcodecs

对于需要跨浏览器支持的项目,推荐使用特性检测和降级策略:

async function getVideoEncoder() {
  if ('VideoEncoder' in window) {
    return { type: 'webcodecs', encoder: VideoEncoder };
  }
  // 降级到MediaRecorder或FFmpeg.wasm
  if (typeof MediaRecorder !== 'undefined') {
    return { type: 'mediarecorder' };
  }
  // 最后降级到FFmpeg.wasm
  const { FFmpeg } = await import('@ffmpeg/ffmpeg');
  return { type: 'ffmpeg', ffmpeg: new FFmpeg() };
}

WebCodecs的未来方向

W3C WebCodecs工作组正在推进多个扩展:

  • WebCodecs for WebCodecs:增强与WebRTC的集成,允许将编码块直接注入RTCRtpSender
  • 屏幕内容编码工具:针对屏幕录制场景优化AV1编码
  • HDR支持:扩展对HDR10、Dolby Vision等高动态范围格式的支持
  • 多轨道处理:简化多音视频轨道的同步处理

与其他方案的权衡决策

WebCodecs不是银弹。在选择视频处理方案时,需要考虑实际需求:

选择WebCodecs当:

  • 需要精确控制编码参数(关键帧间隔、码率模式、编码预设)
  • 需要逐帧处理(视频编辑、特效、实时滤镜)
  • 需要低延迟编码(实时通信、直播推流)
  • 需要处理多种编解码器格式
  • 目标用户使用现代浏览器(Chrome、Edge、Safari 16.4+)

选择MediaRecorder当:

  • 只需要简单的录制功能,对编码参数没有特殊要求
  • 需要最快的开发速度
  • 目标用户可能使用旧版浏览器

选择FFmpeg.wasm当:

  • 需要复杂的容器格式支持(MKV、FLV等非标准格式)
  • 需要FFmpeg特有的滤镜和功能(复杂滤镜图、字幕烧录)
  • 文件处理场景,非实时
  • 可以接受较长的处理时间和较大的初始下载

选择服务端处理当:

  • 视频文件很大,客户端处理不现实
  • 需要保证处理结果一致性(客户端硬件差异可能导致质量波动)
  • 有保密需求,原始视频不能发送到客户端

WebCodecs代表了Web平台在媒体处理能力上的一个重要里程碑。它让浏览器第一次拥有了与原生应用相当的音视频处理能力,这不是渐进式的改进,而是能力边界的扩展。当你下一次需要在浏览器中处理视频时,不必再纠结于"黑盒"还是"低效"——WebCodecs已经给出了第三种答案。


参考资料

  1. WebCodecs W3C Specification. https://www.w3.org/TR/webcodecs/
  2. Video processing with WebCodecs | Chrome Developers. https://developer.chrome.com/docs/web-platform/best-practices/webcodecs
  3. WebCodecs API - MDN Web Docs. https://developer.mozilla.org/en-US/docs/Web/API/WebCodecs_API
  4. Real-Time Video Processing with WebCodecs and Streams. https://webrtchacks.com/real-time-video-processing-with-webcodecs-and-streams-processing-pipelines-part-1/
  5. WebCodecs性能表现及优化思路. https://fenghen.me/posts/2024/07/27/webcodecs-performance-benchmark/
  6. remotion-dev/webcodecs-benchmark GitHub. https://github.com/remotion-dev/webcodecs-benchmark
  7. Clearing up WebCodecs misconceptions | Remotion. https://www.remotion.dev/docs/webcodecs/misconceptions
  8. Improving Clipchamp’s in-browser video editing pipeline with WebCodecs. https://www.w3.org/2021/03/media-production-workshop/talks/soeren-balko-clipchamp-webcodecs.html
  9. Video Frame Processing on the Web – WebAssembly, WebGPU, WebGL, WebCodecs. https://webrtchacks.com/video-frame-processing-on-the-web-webassembly-webgpu-webgl-webcodecs-webnn-and-webtransport/
  10. MediaStreamTrack Insertable Media Processing using Streams - W3C. https://www.w3.org/TR/mediacapture-transform/