WebCodecs > VideoEncoder:从编码帧创建视频

Ami*_*mit 17 video html5-video webcodecs

我想从上传到我的网站的多个图像创建一个视频文件。

到目前为止,我所做的就是拍摄这些图像,将它们一一绘制在画布上,然后使用 APIMediaRecorder来记录它们。不过,空闲时间还是很多的。

相反,我想使用VideoEncoderAPI。

我创建了一个编码器,将每个块保存为缓冲区:

const chunks = [];

let encoder = new VideoEncoder({
  output: (chunk) => {
    const buffer = new ArrayBuffer(chunk.byteLength)
    chunk.copyTo(buffer);
    chunks.push(buffer);
  },
  error: (e) => console.error(e.message)
});
Run Code Online (Sandbox Code Playgroud)

并使用我的设置对其进行配置:

encoder.configure({
  codec: 'vp8',
  width: 256,
  height: 256,
  bitrate: 2_000_000,
  framerate: 25
});
Run Code Online (Sandbox Code Playgroud)

然后,我将每个图像编码为帧:

const frame = new VideoFrame(await createImageBitmap(image));
encoder.encode(frame, {keyFrame: true});
frame.close();
Run Code Online (Sandbox Code Playgroud)

最后,我尝试用它创建一个视频:

await encoder.flush();

const blob = new Blob(chunks, {type: 'video/webm; codecs=vp8'});
const url = URL.createObjectURL(blob);
Run Code Online (Sandbox Code Playgroud)

但是,该 URL 块无法播放。如果我尝试下载它,VLC 不会显示它。如果我将其设置为元素的源video,我会得到:

DOMException:该元素没有受支持的源。

如何将多个帧编码为可播放的视频?

我如何知道支持哪些编解码器/blob 类型?

最小繁殖

以下代码笔是将上述代码连接并连接成一个函数。 https://codepen.io/AmitMY/pen/OJxgPoG?editors=0010

Mac*_*iej 14

VideoEncoder以及 WebCodecs API 中的其他类为您提供了将图像编码为视频流中的帧的方法,但是编码只是创建可播放的多媒体文件的第一步。像这样的文件可能包含多个流 - 例如,当您有一个带声音的视频时,它已经至少有一个视频和一个音频流,所以总共有两个。您需要额外的容器格式来存储流,以便您不必在单独的文件中发送流。要从任意数量的流(甚至只有一个)创建容器文件,您需要一个多路复用器(简称复用器)。很好的主题总结可以在这个Stack Overflow 答案中找到,但要引用重要的部分:

\n
\n
    \n
  1. 创建多媒体文件时,您使用编码器算法对视频和音频数据进行编码,然后使用复用器将流一起放入文件(容器)中。为了播放文件,解复用器分解流并将它们输入解码器以获得视频和音频数据。
  2. \n
  3. Codec的意思是编码器/解码器,是与容器格式不同的概念。许多容器格式可以容纳许多不同类型的格式(AVI 和 QuickTime/MOV 非常通用)。其他格式仅限于一种或两种媒体类型。
  4. \n
\n
\n

您可能会想“我只有一​​个流,我真的需要一个容器吗?” 但多媒体播放器希望接收到的数据(从文件读取的数据或通过网络传输的数据)采用容器格式。即使您只有一个视频流,您仍然需要将其打包到容器中以便他们识别。

\n

将字节缓冲区连接到一大块数据中是行不通的:

\n
const blob = new Blob(chunks, {type: \'video/webm; codecs=vp8\'});\n
Run Code Online (Sandbox Code Playgroud)\n

在这里,您尝试将所有块粘合在一起并告诉浏览器将其解释为 WebM 视频(视频/webm MIME 类型),但它无法做到这一点,因为它的格式不正确。这又是错误的根源。为了使其工作,您必须将相关元数据附加到块(通常格式化为具有特定格式的二进制数据缓冲区,具体取决于容器和编解码器的类型)并将其传递给复用器。如果您使用旨在处理原始视频流(例如来自 WebCodecs API 的视频流)的多路复用库,那么它可能会您处理元数据。作为一名程序员,您很可能不必手动处理此问题,但是如果您想更多地了解整个过程,那么我建议您阅读有关各种容器格式中存在的元数据(例如,此答案下面的 VC.Ones 评论) 。

\n

遗憾的是,到目前为止,复用器似乎还不是 WebCodecs API 的一部分。API官方存储库中的示例使用该函数作为编码器输出回调:muxAndSend()

\n
const videoEncoder = new VideoEncoder({\n  output: muxAndSend,\n  error: onEncoderError,\n});\n
Run Code Online (Sandbox Code Playgroud)\n

在上面的代码中我们可以看到这个函数需要由程序员提供(原注释):

\n
// The app provides a way to serialize/containerize encoded media and upload it.\n// The browser provides the app byte arrays defined by a codec such as vp8 or opus\n// (not in a media container such as mp4 or webm).\nfunction muxAndSend(encodedChunk) { ... };\n
Run Code Online (Sandbox Code Playgroud)\n

是有关向浏览器添加多路复用支持的讨论的链接,是跟踪此功能的官方存储库中的一个问题。截至目前,似乎还没有针对您的问题的内置解决方案。

\n

为了解决这个问题,您可以使用第三方库,例如mux.js或类似的库(这里是他们的“基本用法”示例的链接,可能会对您有所帮助)。或者,该项目声称用VideoEncoder编码数据创建 WebM 容器。他们的演示描述的摘录似乎正是您想要实现的目标(除了使用网络摄像头作为源VideoFrame,而不是画布):

\n
\n

当您单击“开始”按钮时,浏览器将要求您\xe2\x80\x99授予捕获相机和麦克风的权限。然后,来自每个的数据被传递给两个独立的工作人员,他们使用 WebCodecs 浏览器 API 将视频编码为 VP9,将音频编码为 Opus。

\n

来自每个工作器的编码视频和音频被传递到第三个工作器,该工作器将其复用为 WebM 格式。

\n
\n

我无法为您提供代码示例,因为我自己没有使用任何提到的库,但我确信在了解编码器和复用器之间的关系后,您应该能够自己解决问题。

\n

编辑: \n我找到了另一个可能对您有帮助的库。根据他们的自述文件:

\n
\n

支持什么:

\n
    \n
  • MP4 视频复用(获取已编码的 H264 帧并将其包装在 MP4 容器中)
  • \n
  • 通过 WebCodecs 进行 MP4/H264 编码和复用
  • \n
\n
\n

我在网上找到的许多库和源似乎都是基于 WASM 的,通常用 C 或其他语言编译为本机代码来实现。这可能是因为存在处理各种媒体格式的大型库(首先想到的是ffmpeg ),这就是它们的编写内容。JS 库通常被编写为与所述本机代码的绑定以避免重新发明轮子。此外,我认为性能也可能是一个因素。

\n

免责声明:虽然您在代码示例中使用 video/webm 作为 MIME 类型,但您没有明确说明您希望输出的文件格式是什么,因此我允许自己引用一些生成其他格式的库。

\n

编辑2:

\n

David Kanal 下面的回答提供了另一个可用于混合 WebM 的库的示例。

\n

  • 我会投票,因为他需要关键帧数据的容器格式是正确的。错误/缺失的是 **(1)** 相信这些基于 WASM 的代码是**复用** 所必需的(可以用纯 Javascript 完成)。它们用 C 实现不是为了速度,而是因为它们使用预先存在的 C 代码(如 FFmpeg 或类似代码)来增强其能力。WebCodecs **确切**旨在取代编码时对这些 WASM 解决方法的需求。 (3认同)
  • **(2)** 在混合任何内容之前,他的原始关键帧需要其格式的元数据。例如:**VP8** 关键帧在混合到 webM 之前需要 VP8 或 **webP** 标头。为了制作一个,他只需要创建一个包含 20 个值(字节)的数组,然后在这 20 个值之后复制/粘贴 Blob 自己的数组值。_例如:_ `52 49 46 46 AA AA AA AA 57 45 42 50 56 50 38 20 BB BB BB BB` 是您将四个值 **0xAA** 替换为关键帧字节的 **12 + SIZE** 的位置(如32位整数)和四个**0xBB**只是关键帧的**SIZE**。Size 表示数组的长度。此时数据已混合到 webP 中。 (2认同)
  • **(3)** 类似的设置也可用于 H.264 关键帧。为此,您需要大约 40 个字节用于 **SPS** 和 **PPS** 等,任何 MP4 复用器都希望存在于 H264 流中。SPS 将包含帧宽度/高度等数字,这些数字在创建时会传输到 MP4 标头。WebCodecs 不制作 SPS 和 PPS(在 JS 中,您可以根据画布大小等编写自己的数组值)...所以这就是所缺少的,请注意,Asker 仍然需要准备原始关键帧数据**还带有* * 包含之前需要元数据(_例如:_ **webP header** 或 **H.264 header**)。 (2认同)
  • 感谢@VC.One 提供的宝贵信息。为了解决你的观点:(1)是我忘记提及的事情,很快就会添加到我的答案中。关于 (2) 和 (3),我假设提供复用器功能的库将处理元数据,以便能够与 WebCodecs 生成的输出一起使用。检查其中之一,我发现编码器的输出回调[确实调用了一个函数](https://github.com/mattdesl/mp4-wasm/blob/master/src/extern-post.js#L166) `writeAVC()` 似乎将 SPS 和 PPS 元数据写入缓冲区。只有在那之后,数据才会被发送到实际的复用器。 (2认同)

Dav*_*nal 8

更新(2023-04-13):

制作了 MP4 的复用器:https ://github.com/Vanilagy/mp4-muxer

更新(2022-11-10):

由于我为此主题找到的库不足以满足我的需求,因此我创建了自己的库: https: //github.com/Vanilagy/webm-muxer

这是一个纯 TypeScript 中的全功能WebM 混合器(视频 + 音频),不需要大量的 wasm 文件。自述文件中详细解释了用法。该库为我的基于浏览器的游戏中的视频录制功能提供支持。


我想我应该在这个话题上花两分钱,因为我最近在努力解决OP提到的完全相同的事情。

我设法找到了渲染和导出 WebM 文件的解决方案,尽管没有音频。

我在这里找到了 W3C 的官方示例:https://w3c.github.io/webcodecs/samples/capture-to-file/capture-to-file.html。它捕获网络摄像头的视频流并将其保存为磁盘上的 .webm 文件。深入研究代码,负责获取编码视频块并将其写入(混合)到可播放的 WebM 中的代码是webm-writer2.js

将该文件包含在站点中后,编写 WebM 文件所需要做的就是:

// Acquire `fileHandle` somewhere, I use
// https://developer.mozilla.org/en-US/docs/Web/API/Window/showSaveFilePicker

let fileWritableStream = await fileHandle.createWritable();

// This WebMWriter thing comes from the third-party library
let webmWriter = new WebMWriter({
    fileWriter: fileWritableStream,
    codec: 'VP9',
    width: width,
    height: height
});

let encoder = new VideoEncoder({
    output: chunk => webmWriter.addFrame(chunk),
    error: e => console.error(e)
});
// Configure to your liking
encoder.configure({
    codec: "vp09.00.10.08",
    width: width,
    height: height,
    bitrate: bitrate,
    latencyMode: 'realtime'
});
Run Code Online (Sandbox Code Playgroud)

然后,像往常一样使用将帧泵入编码器encoder.encode(videoFrame)

希望这对某人有帮助。