我们虽然走的很慢,但从未停止过脚步。
一、什么是WebCodecs
WebCodecs是浏览器提供的一套音视频编解码的API。相比较于我们自行编写的wasm进行软编软解,Web Codecs可以利用浏览器自带的FFmpeg,其执行效率是高于webassembly的,而且可以充分利用GPU。但其缺点就是编解码的兼容性较差,对于浏览器未支持或者未开放的编解码格式,都无法进行处理。关于WebCodecs的使用其实非常简单,基本上就是配置一个编码器(或者解码器),喂给它视频帧(或者编码块),得到编码块(或者视频帧)。
二、Web编解码器API出现的原因
现在已经有很多 Web API 进行媒体操作:Media Stream API、Media Recording API、Media Source API、WebRTC API,但是没有提供一些底层 API 给到 Web 开发者进行帧操作或者对已经编码的视频进行解封装操作。
很多音视频编辑器为了解决这个问题,使用了 WebAssembly 把音视频编解码带到了浏览器,但是有个问题是现在的浏览器很多已经在底层支持了音视频编解码,并且还进行了很多硬件加速的调优,如果使用 WebAssembly 重新打包这些能力,似乎浪费人力和计算机资源。
所以就诞生了WebCodecs API,暴露媒体 API 来使用浏览器已经有的一些能力,例如:
- 视频和音频解码
- 视频和音频编码
- 原始视频帧
- 图像解码器
三、使用WebCodecs需要进行的配置
- 使用Chrome >= 94的版本,在Chrome地址栏输入:chrome://flags/#enable-experimental-web-platform-features,并将其设置成Enabled

可以通过以下方式判断当前的浏览器是否支持WebCodecs:
javascript
// 通过 VideoEncoder API 检查当前浏览器是否支持
if ('VideoEncoder' in window) {
// 支持 WebCodecs API
}
- 使用集显来运行Chrome浏览器(英伟达的显卡不支持):
设置 --> 显示 --> 图形设置 --> 浏览 --> 选择软件(快捷方法即可)--> 选项 --> 选择合适的显卡即可。
1、设置 --> 显示 --> 图形设置

2、浏览 --> 选择软件

3、选择软件--> 选项 --> 选择合适的显卡

四、WebCodecs的视频编解码处理流程
frames
是视频处理的核心,因此 WebCodecs 大多数类要么消耗 frames
要么生产 frames
。Video encoders 把 frames
转换为 encoded chunks
,Video Decoders 把 encoded chunks
转换为 frames
。这一切都在非主线程异步处理,所以可以保证主线程速度。
当前,在 WebCodecs 中,在页面上显示 frame
的唯一方法是将其转换为 ImageBitmap并在 canvas 上绘制位图或将其转换为WebGLTexture。
WebCodecs的整体处理流程如下:

1、编码(Encoding)
现在有两种方法把已经存在的图片转换为 VideoFrame
-
一种是通过 ImageBitmap 创建 frame。
-
第二种是通过
VideoTrackReader
设置方法来处理从[MediaStreamTrack](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack)
产生的 frame,当需要从摄像机或屏幕捕获视频流时,这个 API 很有用。
ImageBitmap
第一种是直接从 ImageBitmap 创建 frame。只需调用 new VideoFrame()
构造函数并为其提供 bitmap 和展示时间戳。

javascript
let cnv = document.createElement('canvas');
// draw something on the canvas
...
let bitmap = createImageBitmap(cnv);
let frameFromBitmap = new VideoFrame(bitmap, { timestamp: 0 });
VideoTrackReader

javascript
const framesFromStream = [];
const stream = navigator.mediaDevices.getUserMedia({ ... });
const vtr = new VideoTrackReader(stream.getVideoTracks()[0]);
vtr.start((frame) => {
framesFromStream.push(frame);
});
注意
使用 VideoEncoder 将 frame 编码为 EncodedVideoChunk 对象,VideoEncoder 需要两个对象:
- 带有
output
和error
两个方法的初始化对象,传递给VideoEncoder
后无法修改 - Encoder 配置对象,其中包含输出视频流的参数。可以稍后通过调用
configure()
来更改这些参数
javascript
const init = {
output: handleChunk,
error: (e) => {
console.log(e.message);
}
};
let config = {
codec: 'vp8',
width: 640,
height: 480,
bitrate: 8_000_000, // 8 Mbps
framerate: 30,
};
let encoder = new VideoEncoder(init);
encoder.configure(config);
config中可以配置的参数如下:
-
codec:包含有效编解码器字符串的字符串。
-
width:在任何比例调整之前,以像素为单位表示每个输出EncodedVideoChunk的宽度的整数。
-
height:在任何比例调整之前,以像素为单位表示每个输出EncodedVideoChunk的高度的整数。
-
displayWidth:一个整数,表示每个输出EncodedVideoChunk在显示时的预期显示宽度,以像素为单位。
-
displayHeight:一个整数,表示每个输出EncodedVideoChunk在显示时的预期显示高度,以像素为单位。
-
hardwareAcceleration:配置此编解码器的硬件加速方法的提示。是以下三种其一:
"no-preference"
:不使用任何加速方法。"prefer-hardware"
:使用硬件(显卡)进行加速(硬解码)。"prefer-software"
:使用内存进行加速(软解码)。
-
bitrate:一个整数,包含编码视频的平均比特率,单位为比特每秒。
-
framerate:一个整数,包含期望的帧速率,单位为帧/秒。
-
alpha:一个字符串,指示在编码之前是否应该保留或丢弃VideoFrame输入的alpha分量。
"discard"
(default):丢弃。"keep"
:保持。
-
scalabilityMode:包含编码可伸缩性模式标识符的字符串,该标识符在WebRTC中定义。
-
bitrateMode:包含比特率模式的字符串。
"constant"
:常量、常数。"variable"
(default):变量。
-
latencyMode:包含值的字符串,该值配置此编解码器的延迟行为。
"quality"
(default):优质的。"realtime"
:实时的。
设置好 encoder 后,可以接受 frames 了,当开始从 media stream 接受 frames 的时候,传递给 VideoTrackReader.start()
的 callback 就会被执行,把 frame 传递给 encoder,需要定时检查 frame 防止过多的 frames 导致处理问题。注意:encoder.configure()
和 encoder.encode()
会立即返回,不会等待真正处理完成。如果处理完成 output()
方法会被调用,入参是 encoded chunk
。再注意: encoer.encode()
会消耗掉 frame,如果 frame 需要后面使用,需要调用 clone
来复制它。
javascript
let frameCounter = 0;
let pendingOutputs = 0;
const vtr = new VideoTrackReader(stream.getVideoTracks()[0]);
vtr.start((frame) => {
if (pendingOutputs > 30) {
// 有太多帧正在处理中,编码器承受不过来,不添加新的处理帧了
return;
}
frameCounter++;
pendingOutputs++;
const insert_keyframe = frameCounter % 150 === 0;
encoder.encode(frame, { keyFrame: insert_keyframe });
});
最后就是完成 handleChunk 方法,通常,此功能是通过网络发送数据块或将它们封装到到媒体容器中。
javascript
function handleChunk(chunk) {
let data = new Uint8Array(chunk.data); // actual bytes of encoded data
let timestamp = chunk.timestamp; // media time in microseconds
let is_key = chunk.type == 'key'; // can also be 'delta'
pending_outputs--;
fetch(`/upload_chunk?timestamp=${timestamp}&type=${chunk.type}`,
{
method: 'POST',
headers: { 'Content-Type': 'application/octet-stream' },
body: data
});
}
有时候需要确保所有 pending 的 encoding 请求完成,调用 flush()
。
javascript
encoder.flush();
2、解码(Decoding)
设置 VideoDecoder
和上面类似,需要传递 init
和 config
两个对象
javascript
const init = {
output: handleFrame,
error: (e) => {
console.log(e.message);
},
};
const config = {
codec: "vp8",
codedWidth: 640,
codedHeight: 480,
};
const decoder = new VideoDecoder(init);
decoder.configure(config);
config中可以配置的参数如下:
-
codec:包含有效编解码器字符串的字符串。
-
description:包含编解码器特定字节序列的ArrayBuffer、TypedArray或DataView,通常称为额外数据。
-
codedWidth:在任何比例调整之前,以像素为单位表示VideoFrame宽度的整数,包括任何不可见的填充。
-
codedHeight:在任何比例调整之前,以像素为单位表示VideoFrame高度的整数,包括任何不可见的填充。
-
displayAspectWidth:一个整数,表示显示时以像素为单位的VideoFrame的水平维度。
-
displayAspectHeight:一个整数,表示显示时以像素为单位的VideoFrame的垂直维度。
-
colorSpace:表示VideoColorSpace的对象,包含以下成员:
primaries(表示视频样本色域的字符串):
"bt709"
"bt470bg"
"smpte170m"
transfer(表示传输特征的字符串):
"smpte170m"
"bt709"
"iec61966-2-1"
matrix(表示矩阵系数的字符串):
"rgb"
"bt709"
"bt470bg"
"smpte170m"
-
hardwareAcceleration:关于使用硬件加速方法的提示:
"no-preference"
:不使用硬件进行加速。"prefer-hardware"
:使用硬件(显卡)进行加速。"prefer-software"
:使用软件(内存)进行加速。
-
optimizeForLatency:一个布尔值。如果为真,这是一个提示,选择的解码器应该被优化,以最小化EncodedVideoChunk对象的数量,在一个VideoFrame输出之前必须被解码。
设置好 decoder
之后就可以给它喂 EncodedVideoChunk
对象了,通过 [BufferSouce](https://developer.mozilla.org/en-US/docs/Web/API/BufferSource)
来创建 chunk
javascript
const responses = await downloadVideoChunksFromServer(timestamp); // 异步请求数据
for (let i = 0; i < responses.length; i++) {
const chunk = new EncodedVideoChunk({
timestamp: responses[i].timestamp,
data: new Uint8Array(responses[i].body),
});
decoder.decode(chunk);
}
decoder.flush();
渲染 decoded frame
到页面上分为三步:
- 把
VideoFrame
转换为[ImageBitmap](https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap)
。 - 等待合适的时机显示 frame。
- 将 image 画到 canvas 上。
当一个 frame 不再需要的时候,调用 destroy()
在垃圾回收之前手动销毁他,这可以减少页面内存占用。
javascript
frame.destroy();
整个解码的流程如下图:

五、WebCodecs的兼容性问题
由于WebCodecs还未成为w3c标准,所以WebCodecs 存在很多浏览器和硬件方面的兼容性问题。
1、浏览器的兼容性

2、硬件的兼容性

- Note 1: Intel Macs support HEVC Rext software decoding of 8 ~ 12b 400, 420, 422, 444 contents. Apple Silicon Mac supports HEVC Rext hardware decoding of 8 ~ 10b 400, 420, 422, 444 contents, and software decoding of 12b 400, 420, 422, 444 contents on macOS 13+.
- Note 2: Intel Gen10 GPUs support HEVC Rext hardware decoding of 8b 420, 8b 422, 8b 444, 10b 420, 10b 422, 10b 444 contents on Windows. Gen11+ GPUs additionally support HEVC Rext hardware decoding of 12b 420, 12b 422, 12b 444 contents.
- Note 3: Although NVIDIA GPUs support HEVC Rext hardware decoding of 8 ~ 12b non-422 contents via CUVIA or NVDEC, but because they did not provide a D3D11 interface, thus Chromium will not support it in the future.
- Note 4: HEVC 8b 444, 12b 422, 12b 444 support requires Chrome >=
117.0.5866.0
. - Note 5: HEVC 8b 422 support requires Chrome >=
118.0.5956.0
.
3、操作系统的兼容性
- macOS Big Sur (11.0) and above.
- Windows 8 and above.
- Android 5.0 and above.
- Chrome OS (Only supports GPUs that support VAAPI interface, eg: Intel GPU).
- Linux (Chrome version >=
108.0.5354.0
, and only supports GPUs that support VAAPI interface, eg: Intel GPU).
六、与WebCodecs的功能相似的产品
1、FFmpeg
FFmpeg 是一个开源的跨平台多媒体处理工具集,它由一组用于处理音视频数据的库和工具组成。FFmpeg 提供了丰富的功能,包括音视频的录制、转码、编解码、流媒体传输和处理等。
2、WebAssembly
WebAssembly(缩写为Wasm)是一种可移植、效率高且安全的二进制格式,用于在Web浏览器中运行高性能的代码。它是一种低级别的虚拟机抽象,可以将原生代码(如C、C++、Rust等)编译为可在浏览器中执行的格式。
在前端使用 C++ 和 WebAssembly 也可以对音视频进行编解码(软解)
七、音视频相关的原理和概念
1、音视频录制的原理

2、音视频播放的原理

3、图形格式
RGB

对于一幅图像,一般使用整数表示方法来进行描述,比如计算一张的RGB_888图像的大小,可采用如下方式:
1280×720 * 3 = 2.637 MB,4分钟就达到了15G的容量。
假如是一部90分钟的电影,每秒25帧,则一部电影为
2.637MB90分钟60秒*25FPS= 347.651GB
YUV

用途:主要用于视频信号的压缩、传输和存储,和向后相容老式黑白电视。
- "Y" 表示明亮度(Luminance或Luma),也称灰阶值;
- "U"和"V"表示的则是色度(Chrominance或Chroma)
相较于RGB,可以计算一帧为1280×720的视频帧,用YUV420P的格式来表示,其数据量的大小如下: 4 2 -> 1 + 0.5 = 1.5
1280 * 720 * 1 + 1280 * 720 * 0.5 = 1.318MB
如果fps(1秒的视频帧数目)是25,按照一般电影的长度90分钟来计算,那么这部电影用YUV420P的数据格式来表示的话,其数据量的大小就是:1.318MB * 25fps * 90min * 60s = 173.76GB。
4、相关的概念
视频码率、视频帧率、视频分辨率
- 视频码率(kb/s):是指视频文件在单位时间内使用的数据流量,也叫码流率。码率越大,说明单位时间内取样率越大,数据流精度就越高。
- 视频帧率(fps):通常说一个视频的25帧,指的就是这个视频帧率,即1秒中会显示25帧。帧率越高,给人的视觉就越流畅。
- 视频分辨率:分辨率就是我们常说的640x480分辨率、1920x1080分辨率,分辨率影响视频图像的大小。
I帧、P帧、B帧
-
I 帧不需要参考其他画面而生成,解码时仅靠自己就重构完整图像。
- I 帧图像采用帧内编码方式。
- I 帧所占数据的信息量比较大。
- I 帧图像是周期性出现在图像序列中的,出现频率可由编码器选择。
- I 帧是 P 帧和 B 帧的参考帧 (其质量直接影响到同组中以后各帧的质量)。
- I 帧是帧组GOP的基础帧(第一帧),在一组中只有一个 I 帧。
- I 帧不需要考虑运动矢量。
-
P 帧根据本帧与相邻的前一帧(I 帧或 P 帧)的不同点来压缩本帧数据,同时利用了空间和时间上的相关性。
- P 帧属于前向预测的帧间编码。它需要参考前面最靠近它的I帧或 P 帧来解码。**
-
B 帧图像采用双向时间预测,可以大大提高压缩倍数。
使用WebCodece时,所要求的传入编解码器的参数必须为一个 I 帧关键帧。