WebCodecs的使用,注意事项以及基本音视频相关原理

我们虽然走的很慢,但从未停止过脚步。

一、什么是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 需要两个对象:

  • 带有 outputerror 两个方法的初始化对象,传递给 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 和上面类似,需要传递 initconfig 两个对象

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 帧关键帧。

相关推荐
moxiaoran575316 分钟前
uni-app萌宠案例学习笔记--页面布局和CSS样式设置
前端·css·uni-app
CrissChan1 小时前
Pycharm 函数注释
java·前端·pycharm
小小小小宇2 小时前
Vue.nextTick()笔记
前端
小约翰仓鼠3 小时前
vue3子组件获取并修改父组件的值
前端·javascript·vue.js
Lin Hsüeh-ch'in3 小时前
Vue 学习路线图(从零到实战)
前端·vue.js·学习
烛阴3 小时前
bignumber.js深度解析:驾驭任意精度计算的终极武器
前端·javascript·后端
计蒙不吃鱼4 小时前
一篇文章实现Android图片拼接并保存至相册
android·java·前端
全职计算机毕业设计4 小时前
基于Java Web的校园失物招领平台设计与实现
java·开发语言·前端
啊~哈4 小时前
vue3+elementplus表格表头加图标及文字提示
前端·javascript·vue.js