【问题标题】:WebCodecs > VideoEncoder: Create video from encoded framesWebCodecs > VideoEncoder:从编码帧创建视频
【发布时间】:2022-01-15 16:37:35
【问题描述】:

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

到目前为止,我所做的只是拍摄这些图像,在画布上一一绘制,然后使用MediaRecorder API 记录它们。但是,有很多空闲时间。

相反,我想使用VideoEncoder API。

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

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)
});

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

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

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

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

最后,我尝试从中创建一个视频:

await encoder.flush();

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

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

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

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

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

最小复制

下面的代码笔是上面的代码,连接并加入一个函数。 https://codepen.io/AmitMY/pen/OJxgPoG?editors=0010

【问题讨论】:

  • 您有完整的可测试代码来重新创建您的问题吗?或者可能提供输出视频(下载的 blob)的链接,以便我们检查编码有什么问题(例如: 可能缺少 webM 标头)。
  • @VC.One 我已经添加了一个最小的复制代码笔。

标签: video videoencoder webcodecs


【解决方案1】:

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

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

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

将字节缓冲区加入一大块数据是行不通的:

const blob = new Blob(chunks, {type: 'video/webm; codecs=vp8'});

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

遗憾的是,复用器目前似乎还不是 WebCodecs API 的一部分。 API的official repository中的Example使用muxAndSend()函数作为编码器输出回调:

const videoEncoder = new VideoEncoder({
  output: muxAndSend,
  error: onEncoderError,
});

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

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

Here 是关于向浏览器添加复用支持的讨论链接,here 是官方 repo 中跟踪此功能的问题。截至目前,您的问题似乎没有内置解决方案。

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

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

来自每个工作人员的编码视频和音频被传递到第三个工作人员,该工作人员将其混合成 WebM 格式。

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

编辑: 我找到了another library,这可能会对您有所帮助。根据他们的自述文件:

支持的内容:

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

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

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

【讨论】:

  • 我会投赞成票,因为他需要关键帧数据的容器格式是正确的。 (1) 错误/缺失的是 muxing 需要这些基于 WASM 的代码的信念(可以在纯 Javascript 中完成)。它们在 C 中实现不是为了速度,而是因为它们使用预先存在的 C 代码(如 FFmpeg 或类似代码)来增强它们的能力。 WebCodecs 恰好旨在取代编码时对这些 WASM 变通办法的需求。
  • (2) 在混合任何东西之前,他的原始关键帧需要格式的元数据。例如:一个 VP8 关键帧需要一个 VP8 或 webP 标头才能混合到 webM 中。要制作一个,他只需要创建一个包含 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。大小表示数组的长度。此时数据现在被混合到 webP 中。
  • (3) 类似的设置也可用于 H.264 关键帧。为此,您需要大约 40 个字节用于 SPSPPS 等,任何 MP4 复用器都希望在 H264 流中存在这些字节。 SPS 将包含像帧宽度/高度这样的数字,这些数字在创建时会传输到 MP4 标头。 WebCodecs 不生成 SPS 和 PPS(在 JS 中,您可以根据画布大小等编写自己的 Array 值)......所以这就是缺少的,注意 Asker 仍然需要准备原始关键帧数据 它是包含之前的预期元数据(例如: webP 标头H.264 标头)。
  • 感谢@VC.One 提供的宝贵信息。为了解决您的观点:(1)是我忘记提及的内容,不久将添加到我的答案中。关于 (2) 和 (3),我假设提供复用器功能的库将处理元数据,以便能够与 WebCodecs 生成的输出一起使用。检查其中一个我发现编码器does call a function 的输出回调名为writeAVC(),它似乎将SPS 和PPS 元数据写入缓冲区。只有在那之后,数据才会发送到实际的复用器。
  • 我还假设如果复用 API 成为标准的一部分,API 将处理元数据以及与 WebCodecs 无缝工作。因此,我只允许自己简单地提及元数据和格式。我尝试更多地关注编程问题,同时解释基本概念而没有太多细节。尽管如此,我可能应该在回答中提到,这个主题不仅仅是我所描述的,我很快也会这样做。
猜你喜欢
  • 2013-06-13
  • 2017-04-04
  • 1970-01-01
  • 2012-05-25
  • 2021-01-04
  • 1970-01-01
  • 2012-11-12
  • 1970-01-01
  • 2016-01-19
相关资源
最近更新 更多