实时视频处理与 WebCodecs 和流:处理管道

这个系列文章我们来介绍一位海外工程师如何探索 WebRTC 音视频技术,对于想要开始学习音视频技术的朋友,这些文章是份不错的入门资料,本篇介绍实时视频处理与 WebCodecs 和 流。

—— 来自公众号“关键帧Keyframe”的分享

学习和提升音视频开发技术,推荐你加入我们的知识星球:【关键帧的音视频开发圈】,加入后你就能:

  • 下载 30+ 个开箱即用的「音视频及渲染 Demo 源代码」
  • 下载包含 500+ 知识条目的完整版「音视频知识图谱」
  • 下载包含 200+ 题目的完整版「音视频面试题集锦」
  • 技术和职业发展咨询 100% 得到回答
  • 获得简历优化建议和大厂内推
实时视频处理与 WebCodecs 和流:处理管道

WebRTC 曾经只是关于捕获一些媒体并将其从 A 点发送到 B 点。机器学习改变了这一点。现在,使用 ML 在实时中分析和操作媒体已经成为一种常态,例如虚拟背景、增强现实、噪声抑制、智能裁剪等。为了更好地适应这一不断增长的趋势,网络平台开始暴露其底层平台,以赋予开发者更多的访问权限。结果不仅是在现有 API 内提供了更多的控制,还出现了一系列新的 API,如可插入流、WebCodecs、流、WebGPU 和 WebNN。

那么,所有这些新 API 是如何协同工作的呢?这正是 W3C 专家 François Daoust 和 Dominique Hazaël-Massieux(Dom)决定要弄清楚的问题。说到 W3C,它就是制定网络标准的世界组织。François 和 Dom 是长期的标准制定者,他们对帮助塑造今天的网络有着深厚的历史。

这是探讨 WebCodecs 和流在实时视频处理中未来的两篇文章系列的第一篇。本文第一部分回顾了使用现有和最新的网络 API 构建多步骤视频处理管道的步骤和可能遇到的坑。第二部分将探讨视频帧的实际处理过程。

我对这些指南对这些前沿方法所提供的深度和见解感到兴奋——希望你会喜欢!

  • 关于处理管道
  • 演示
    • 注意:这些 API 是新的,可能无法在你的浏览器中运行
    • 时间统计
  • WebCodecs 在客户端处理中的作用
    • 画布怎么样?我们需要 WebCodecs 吗?
    • WebCodecs 的优势
    • 关于容器化媒体的注意事项
  • 使用流处理流
  • 管道链
    • 回压
    • 创建管道
  • 管道就像多米诺骨牌游戏
    • 多个工作者?
    • 学习:现在使用单个工作者
    • 到 <canvas>元素
    • 到 <video>元素
    • 注意事项和警告
    • 与 WebTransport 一起到云端
    • 从头开始
    • 从摄像头或 WebRTC 轨道
    • 从 WebTransport 流
    • WebTransport 怎么样?
    • 数据通道怎么样?
    • 生成流
    • 转换流
    • 发送/渲染流
    • 处理回压
    • 工作者、TransformStream 和 VideoFrame
  • 测量性能
  • 如何处理 VideoFrames

1、关于处理管道

在简单的 WebRTC 视频会议场景中,一个设备捕获的音频和视频流被发送到另一个设备,可能经过一些中间服务器。从麦克风和摄像头捕获原始音频和视频流依赖于 getUserMedia。然后原始媒体流需要被编码以便传输并发送到接收端。接收到的流必须被解码才能渲染。下面的图示说明了产生的视频处理管道。在实践中,Web 应用程序无法看到这些单独的编码/发送和接收/解码步骤——它们被包含在 WebRTC API 的核心中,并由浏览器控制。

实时视频处理与 WebCodecs 和流:处理管道

如果要添加例如移除用户背景的功能,最可扩展且尊重隐私的选项是在视频流被发送到网络之前在客户端进行。此操作需要访问视频流的原始像素。换句话说,它需要在捕获步骤和编码步骤之间进行。同样,在接收端,你可能希望为用户提供一个调整颜色和对比度的选项,这也需要在解码和渲染步骤之间访问原始像素。如下图所示,这在结果视频管道中添加了一个额外的处理步骤。

实时视频处理与 WebCodecs 和流:处理管道

这使得 Dominique Hazaël-Massieux 和我思考 Web 应用程序如何构建这样的媒体处理管道。

主要问题是原始帧不能随意地暴露给网络应用程序。原始帧是:

  • 大——每帧几 MB,
  • 众多——每秒 25 帧或更多,
  • 不容易暴露——通常需要从 GPU 读回到 CPU,
  • 浏览器需要在内部处理各种像素格式(RGBA、YUV 等)和色域。

因此,尽可能地,操纵网络上的视频流的网络技术(HTMLMediaElement、WebRTC、getUserMedia、Media Source Extensions)将它们视为不透明对象,并隐藏底层像素。这使得网络应用程序在实践中难以创建媒体处理管道。

幸运的是,WebCodecs 中的 VideoFrame接口可能会有所帮助,特别是如果将其与 MediaStreamTrackProcessor对象结合使用,该对象定义了使用流的可插入媒体处理,创建了 WebRTC 和 WebCodecs 之间的桥梁。WebCodecs 允许访问和处理媒体帧的原始像素。实际处理可以使用许多技术,从传统的 JavaScript 到 WebAssembly、WebGPU 或 Web 神经网络 API(WebNN)。

处理后,你可以通过相同的桥梁返回到 WebRTC。也就是说,WebCodecs 还可以让你通过其 VideoEncoder和 VideoDecoder接口控制管道中的编码/解码步骤。这些接口可以让你完全控制管道中的所有步骤:

  • 对于在保持低延迟的同时将处理后的图像传输到某处,你可以考虑 WebTransport 或 WebRTC 的 RTCDataChannel
  • 对于渲染,你可以通过 drawImage直接渲染到画布,使用 WebGPU,或者通过 VideoTrackGenerator(同样定义在使用流的可插入媒体处理中)通过 <video>元素进行渲染。

受到 Bernard Aboba 的示例代码的启发——他是 WebCodecs 和 WebTransport 规范的联合编辑,也是 W3C 的 WebRTC 工作组的联合主席——Dominique 和我决定花一些时间探索创建媒体处理管道。首先,我们希望通过更好地理解视频像素格式和色域等媒体概念,这些我们自认为是网络专家,但我们不是媒体专家,我们也倾向于将媒体流视为不透明的野兽。其次,我们想评估是否还存在技术上的差距。最后,我们想了解在哪里以及何时会进行复制,并在此过程中收集一些性能指标。

本文描述了我们的方法,提供了我们产生的演示代码的亮点,并分享了我们的经验。代码不应被视为权威或甚至是正确的(尽管我们希望它是),它只是我们在媒体处理世界中的一次短暂旅程的结果。请注意讨论的技术仍然处于起步阶段,尚未在浏览器之间实现互操作性。希望这会很快改变!

注意:由于时间关系,我们没有涉及音频。音频帧占用的内存较少,但每秒的帧数更多,而且对定时问题更敏感。音频帧是使用 Web 音频 API 处理的。将音频加入这个组合中会非常有趣,即使是仅仅为了探索音频/视频同步的需求。

2、演示

我们的演示探讨了创建视频处理管道,捕获性能指标,评估选择特定技术处理帧的影响,并提供了关于操作在哪里进行以及何时进行复制的见解。处理操作会遍历帧中的所有像素并“对它们进行操作”(它们实际做什么在这里并不重要)。不同的处理技术用于测试目的,而不是因为它们对于手头的问题一定是好的选择。

该演示允许用户:

  1. 选择一个输入源以创建初始的 VideoFrame 流:要么是从头开始使用 OffscreenCanvas创建的类似 Nyan 猫的动画,要么是来自摄像头的实时流。用户还可以选择视频流的分辨率和帧率。
  2. 使用 WebAssembly 处理视频帧以将绿色替换为蓝色。
  3. 使用纯 JavaScript 处理视频帧以将其转换为黑白。
  4. 添加一个 H.264 编码/解码转换阶段,使用 WebCodecs。
  5. 使用常规 JavaScript 在流中引入轻微的延迟。
  6. 在视频的右下角添加一个覆盖层,编码帧的时间戳。覆盖层是使用 WebGPU 和 WGSL 添加的。
  7. 添加中间步骤,将帧强制复制到 CPU 内存或 GPU 内存,以评估帧在内存中的位置对转换的影响。
实时视频处理与 WebCodecs 和流:处理管道

一旦你点击“开始”按钮,管道就会运行,结果流会在 <video>元素中显示在屏幕上。而这就是全部了!我们关注的是实现这一点所需的代码以及我们从收集性能指标和调整参数中获得的见解。让我们深入了解!

2.1、注意:这些 API 是新的,可能无法在你的浏览器中运行

本文讨论并在演示中使用的技术仍然处于“新兴”阶段(至少截至 2023 年 3 月)。演示目前仅在启用了 WebGPU 的 Google Chrome Canary 中运行(在 chrome://flags/ 中设置了“不安全的 WebGPU”标志)。希望演示很快也能在其他浏览器中运行。使用 WebCodecs 的视频处理在 Safari(16.4)的技术预览版中可用,并在 Firefox 中开发中。WebGPU 也在 Safari 和 Firefox 中开发中。一个更大的未知数是其他浏览器对使用流进行的 MediaStreamTrack 可插入媒体处理的支持。例如,参见 Firefox 中的这个跟踪错误。

2.2、时间统计

时间统计报告在页面底部结束时以及作为对象报告到控制台(这需要打开开发者工具面板)。如果存在叠加层,还会报告每个帧的显示时间。

实时视频处理与 WebCodecs 和流:处理管道

我们将在性能测量部分进一步讨论这一点。

3、WebCodecs 在客户端处理中的作用

WebCodecs 是演示的核心,也是我们用来构建媒体管道的关键技术。在更深入地了解这一点之前,反思在这种情况下使用 WebCodecs 的价值可能是有用的。其他方法同样有效。

3.1、画布怎么样?我们需要 WebCodecs 吗?

事实上,自 <video>和 <canvas>元素被添加到 HTML 以来,网络上的客户端原始视频帧处理一直是可能的,方法如下:

  1. 将视频渲染到 <video>元素上。
  2. 使用 drawImage将 <video>元素的内容定期绘制到 <canvas>上,例如使用 requestAnimationFrame或更近的 requestVideoFrameCallback,它在视频帧被呈现以供合成时通知应用程序并提供有关该帧的元数据。
  3. 每当 <canvas>更新时处理其内容。

我们没有将这种方法集成到演示中。其他事情中,性能将取决于处理是否发生在主线程之外。我们将需要使用 OffscreenCanvas在工作者中处理内容,可能与 grabFrame调用相结合,将视频帧发送到工作者。

3.2、WebCodecs 的优势

画布方法的一个缺点是无法保证所有视频帧都会被处理。如果应用程序通过挂钩 requestVideoFrameCallback来查看,他们可以知道错过了多少帧,但根据定义,错过帧已经被错过了。另一个缺点是一些代码(drawImage或 grabFrame)需要在主线程上运行以访问 <video>元素。

WebGL 和 WebGPU 也提供了从 <video>元素直接导入视频帧作为纹理的机制,例如通过 WebGPU 中的 importExternalTexture方法。如果处理逻辑可以完全在 GPU 上运行,这种方法效果很好。

WebCodecs 给应用程序提供了直接处理视频帧的句柄和编码/解码它们的机制。这允许应用程序从头创建帧,或从传入流创建帧,前提是该流是非容器化的形式。

3.3、关于容器化媒体的注意事项

重要的一点是,媒体流通常被封装在媒体容器中。容器可能包含其他流以及计时和其他元数据。在 WebRTC 场景中,媒体流不使用容器,但大多数存储的媒体文件和网络上流媒体的媒体使用自适应流媒体技术(例如 DASH、HLS),它们是容器化的形式(例如 MP4、ISOBMFF)。WebCodecs 只能用于非容器化的流。希望将 WebCodecs 用于容器化媒体的应用程序需要自行添加额外的逻辑,以从容器中提取媒体流(和/或将流添加到容器中)。有关媒体容器格式的更多信息,我们建议参考 Armin Trattnig 的《容器文件格式的终极指南》。

4、使用流处理流

因此,直接处理视频帧对于创建媒体处理管道似乎是很有用的。它提供了处理每个步骤中原子数据块的句柄。

4.1、管道链

WHATWG 流专门设计用于创建处理这种原子块的管道链。这在 MDN 页面上的流 API 概念中有所说明:

实时视频处理与 WebCodecs 和流:处理管道

由 Mozilla 贡献者提供的流 API 概念图,授权为 CC-BY-SA 2.5。

WHATWG 流也被考虑中的一些技术用作底层结构,例如 WebTransportVideoTrackGenerator和 MediaStreamTrackProcessor

4.2、回压

最后,流提供了现成的回压和排队机制。根据 WHATWG 流标准,回压被定义为

根据链能够处理块的速度来规范原始源的流。

当链中的一个步骤无法接受更多块进入其队列时,它会发送一个信号,该信号通过管道链向后传播,直到源,告诉源调整其新块的生产速度。有了回压,无需担心队列溢出,流量将自然适应处理能够运行的最大速度。

4.3、创建管道

一般来说,使用流创建媒体处理管道可以翻译为:

  1. 创建一个 VideoFrame对象流—— somehow
  2. 使用 TransformStream创建处理步骤——根据需要进行组合
  3. 发送/渲染结果流或 VideoFrame对象—— somehow

当然,魔鬼在 somehow 中。一些技术可以直接摄入或消化 VideoFrame对象流——不是所有技术都可以。需要连接器。

5、管道就像多米诺骨牌游戏

我们发现通过一种多米诺骨牌游戏来可视化可能性是有用的:

多米诺骨牌的左侧是一种输入类型。图的右侧显示了输出类型。主要有三种类型的多米诺骨牌:

  1. 生成器,
  2. 转换器,和
  3. 消费者。

只要你将一个多米诺骨牌的输入与前一个多米诺骨牌的输出匹配,你就可以以任何你喜欢的方式组装它们来创建管道。让我们更详细地看看它们:

5.1、生成流

5.1.1、从零开始

你可以从画布的内容(或者实际上是一个字节缓冲区)创建一个 VideoFrame。然后,为了生成一个流,只需以一定的速率将帧写入一个 WritableStream。在我们的代码中,这在 worker-getinputstream.js 文件中实现。该逻辑使用 W3C 标志创建一个类似 Nyan 猫的动画。正如我们稍后将描述的,我们利用了 WHATWG 流的回压机制,通过等待写入器就绪:

await writer.ready;
constframe=newVideoFrame(canvas,…);
writer.write(frame);

5.1.2、从摄像头或 WebRTC 轨道

在 WebRTC 上下文中,视频流的来源通常是通过调用 getUserMedia从摄像头获得的 MediaStreamTrack,或从对等方接收的。MediaStreamTrackProcessor对象(MSTP)可以用来将 MediaStreamTrack转换为 VideoFrame对象流。

注意:MediaStreamTrackProcessor只在工作者上下文中公开……理论上,但 Chrome 目前只在主线程上公开它。

5.1.3 从 WebTransport 流

WebTransport 创建 WHATWG 流,因此不需要运行任何流转换。也就是说,传输原始解码帧是相当低效的,因为它们的大小。因此,所有媒体流都以编码形式通过云端传输!因此,WebTransportReceiveStream通常包含编码块,被解释为 EncodedVideoChunk。要回到 VideoFrame对象流,每个块需要通过一个 VideoDecoder。在 worker-transform.js 文件中可以找到简单的块编码/解码逻辑(没有 WebTransport)。

5.1.4、WebTransport 怎么样?

演示尚未集成 WebTransport。我们鼓励你查看 Bernard Aboba 的 WebCodecs/WebTransport 示例。这里介绍的示例和方法都有限,因为它们只使用一个流来发送/接收编码帧。现实世界的 applications 应用程序可能会更复杂,以避免头部行阻塞问题。它们可能会并行使用多个传输流,每个帧一个。在接收端,然后需要在单独的流上接收的帧进行重新排序和合并,以重建一个独特的编码帧流。IETF 的 Media over QUIC(moq)工作组正在开发这样一个低延迟媒体交付解决方案(在原始 QUIC 或 WebTransport 上)。

5.1.5、数据通道怎么样?

RTCDataChannel 也可以用来传输编码帧,但需要注意一些适配逻辑,以连接 RTCDataChannel 与流。

5.2、转换流

一旦你有了一个 VideoFrame对象流,视频处理可以被结构化为一个 TransformStream,它以 VideoFrame作为输入,并输出一个更新的 VideoFrame。转换流可以根据需要进行链式连接,尽管最好尽可能减少需要访问像素的步骤,因为访问视频帧中的像素通常意味着遍历数百万像素(例如,对于全高清视频帧的 1920 * 1080 = 2 074 600像素)。

注意:第二部分探讨了可以在底层用于处理像素的技术,并回顾了性能考虑因素。

5.3、发送/渲染流

一些应用程序只需要从流中提取信息——例如在手势检测的情况下。然而,在大多数情况下,最终流需要被渲染或发送到某处。

5.3.1、到 <canvas>元素

VideoFrame可以直接绘制到画布上。简单!

canvasContext.drawImage(frame,0,0);

将帧渲染到画布上为应用程序提供了完全控制何时显示这些帧。这对于需要将视频流与某个其他内容同步时特别有用,例如覆盖层和/或音频。一个缺点是,如果你的目标是最终实现一个媒体播放器,你将不得不从头开始重新实现这个媒体播放器。这意味着添加控制、支持轨道、无障碍等功能。这不是一个简单的任务……

5.3.2、到 <video>元素

一个 VideoFrame对象流不能被注入到 <video>元素中。幸运的是,一个 VideoTrackGenerator(VTG)可以用来将流转换为 MediaStreamTrack,然后可以将其注入到 <video>元素中。

5.3.4、仅适用于工作者

注意 VideoTrackGenerator只在工作者上下文中公开……理论上,但至于 MediaStreamTrackProcessor,Chrome 目前只在主线程上公开它。

5.3.5、VideoTrackGenerator是新的 MediaStreamTrackGenerator

另外注意:VideoTrackGenerator曾经称为 MediaStreamTrackGenerator。Chrome 的实现尚未跟上新名称,因此我们的代码仍然使用旧名称!

5.3.6、与 WebTransport 一起到云端

WebTransport 可以用来将结果流发送到云端。如前所述,发送未编码的视频帧到 WebTransportSendStream将需要太多带宽。它们需要使用 WebCodecs 中定义的 VideoEncoder接口进行编码。在 worker-transform.js 文件中可以找到简单的帧编码/解码逻辑(没有 WebTransport)。

6、处理回压

流配备了现成的回压机制。当队列积压时,信号会通过管道链向上传播到源,以指示可能是时候放慢速度或丢弃一个或多个帧了。这种机制非常方便,避免在管道中积累大量的已解码视频帧,这可能会耗尽内存。1 秒钟的全高清视频以每秒 25 帧的速度解码后占用的内存为 200MB。

该 API 还使网络应用程序能够实现自己的缓冲策略。如果你需要实时处理实时流,你可以选择丢弃无法及时处理的帧。或者,如果你需要转换录制的媒体,那么你可以放慢速度并处理所有帧,无论需要多长时间。

一个结构上的限制是回压信号只在使用 WHATWG 流的部分管道中传播。当信号碰到其他东西时就会停止。例如,MediaStreamTrack不公开 WHATWG 流接口。因此,如果管道中使用了 MediaStreamTrackProcessor,它会接收回压信号,但信号不会超出它传播。缓冲策略被强加:当需要为新帧腾出空间时,队列中最旧的帧将从队列中移除。

换句话说,如果你最终在管道中有一个 VideoTrackGenerator后面跟着一个 MediaStreamTrackProcessor,回压信号将由 MediaStreamTrackProcessor处理,并且不会传播到 VideoTrackGenerator之前的源。你可能不需要创建这样的管道,但我们在编写演示时不慎陷入了这种配置。请记住,这并不等同于身份转换。

7、工作者、TransformStream 和 VideoFrame

到目前为止,我们已经组装了多米诺骨牌,而没有明确说明底层代码将在哪里运行。除了 getUserMedia外,我们讨论的所有组件都可以在工作者中运行。在主线程之外运行它们要么是良好的实践,要么在 VideoTrackGenerator和 MediaStreamTrackProcessor的情况下被强制执行——尽管注意这些接口实际上只在 Chrome 的当前实现中在主线程上可用。

7.1、多个工作者?

现在,如果我们有线程,为什么要限制自己只使用一个工作者,而不是创建更多呢?尽管媒体管道描述了一系列步骤,但乍一看,尝试并行运行不同的步骤似乎是很有用的。

要在一个工作者中运行处理步骤,工作者需要获得初始的 VideoFrame对象流,这些对象可能是在另一个工作者中创建的。工作者通常不共享内存,但 postMessageAPI 可以用于跨工作者通信。一个 VideoFrame不是一个简单的对象,但它被定义为一个可传输对象,这意味着它可以被高效地从一个工作者传递到另一个工作者,而无需复制底层帧数据。

注意:传输会分离正在被传输的对象,这意味着发出 postMessage调用的工作者将不能再使用被传输的对象。

在一个工作者中运行处理步骤的一种方法是在处理步骤的结尾对每个和每一个 VideoFrame调用 postMessage,将其传递给下一步。从性能角度来看,虽然 postMessage并不一定慢,但 API 是基于事件的,事件仍然会引入延迟。更好的方法是在创建管道时一次性传递 VideoFrame对象流。这是可能的,因为 ReadableStreamWritableStream和 TransformStream也是可传输对象。连接输入和输出流到另一个工作者的代码可以变成:

worker.postMessage({
    type: 'start',
    inputStream: readableStream,
    outputStream: writableStream
}, [readableStream, writableStream]);

现在,流被传递的事实并不意味着从这些流读取或写入的块本身也被传输了。块实际上是被序列化的。细微差别很小(并且对性能的影响应该是最小的),但对于 VideoFrame对象来说特别重要。为什么?因为一个 VideoFrame需要通过调用其 close方法来显式关闭,以释放 VideoFrame指向的底层媒体资源。

当一个 VideoFrame被传递时,其close方法在发送方自动调用。当一个VideoFrame被序列化时,尽管底层媒体资源没有被克隆,但VideoFrame对象本身被克隆了,并且现在需要在发送方和接收方各调用一次close方法。接收方没有问题:在那里调用close()是预期的。然而,发送方有一个问题:一个调用像controller.enqueue(frame)在附加到传递到另一个工作者的可读流的TransformStream中将触发序列化过程,但该过程是异步的,并且没有方法可以告诉它何时完成。换句话说,在发送方,代码不能简单地是:

controller.enqueue(frame);
frame.close(); // Too early!

如果你这样做,浏览器会在实际序列化帧时正确地抱怨它无法克隆它,因为该帧已经被关闭了。然而,发送方需要在某个时候关闭该帧。如果你不这样做,会发生以下两种情况之一:

  1. 浏览器会报告它遇到了悬空的 VideoFrame实例,这表明存在内存泄漏,或者
  2. 在处理几个帧后管道会冻结。

当 VideoFrame与硬件解码数据绑定时会发生管道冻结。硬件解码器使用非常有限的内存缓冲区,所以暂停直到已经解码的帧的内存被释放。这是一个已知的问题。正在进行讨论以扩展 WHATWG 流,添加一个新的机制,允许它显式地转移帧的所有权,以便发送方不需要再担心该帧。例如,参见 Transferring Ownership Streams Explained 提议。

注意:在 Chrome 中,同步关闭帧有时在实践中有效,这取决于底层处理管道。我们发现很难重现使浏览器决定立即克隆帧或延迟它的条件。据我们所知,无论如何,代码都不应该工作。

7.2、学习:现在使用单个工作者

现在,最好还是坚持只在一个工作者中处理 VideoFrame对象流。演示确实使用了多个工作者,它在处理管道的末尾跟踪帧实例以关闭。然而,我们这样做只是因为我们最初不知道创建多个工作者会是问题,并且需要这样的变通方法。

8、测量性能

VideoFrame实例的时间戳属性为个别帧提供了一个很好的标识符,并允许应用程序在整个管道中跟踪它们。时间戳甚至在编码(和分别解码)过程中幸存下来,与 VideoEncoder(和分别与 VideoDecoder)一起使用。

在建议的管道模型中,转换步骤是一个在编码或解码帧上操作的 TransformStream。运行转换步骤所花费的时间因此仅仅是转换函数所花费的时间,或者更准确地说,是直到该函数调用 controller.enqueue(transformedChunk)将更新后的帧发送到管道下游的时间。演示中集成了一个通用的 InstrumentedTransformStream 类,它扩展了 TransformStream以在静态缓存中记录每个帧的开始和结束时间。该类是 TransformStream的即插即用替代品:


const transformStream = new InstrumentedTransformStream({
    name: 'super-duper',
    transform(chunk, controller) {
        const transformedChunk = doSomethingWith(chunk);
        controller.enqueue(transformedChunk);
    }
});

记录的时间随后被输入到一个通用的 StepTimesDB 类的实例中,以计算每个步骤的最小、最大、平均和中位数时间,以及在队列中花费的时间。

这对于使用 WHATWG 流的管道部分效果很好,但一旦管道使用不透明流,例如当帧被馈送到 VideoTrackGenerator时,我们就失去了跟踪个别帧的能力。特别是,没有简单的方法可以知道视频帧何时实际显示到 <video>元素中。requestVideoFrameCallback函数报告了许多有趣的时间戳,但没有帧被呈现以供合成的时间戳。

演示中实现的变通方法是在帧的右下角的覆盖层中编码帧的时间戳,然后在 requestVideoFrameCallback回调被调用时将渲染到 <video>元素的帧的相关部分复制到 <canvas>元素以解码时间戳。这并不完美——在回调函数调用之间可能会错过帧,但它总比没有好。

在演示运行结束时写入控制台的典型统计数据如下所示:

视频帧处理测试的控制台输出示例

这些时间是每个处理步骤和每个帧的。统计包括每个步骤每个帧的平均花费时间。在这个例子中:背景移除花费了 22ms,添加覆盖层 1ms,编码 8 ms,解码 1ms,帧在显示上停留了 38ms

9、如何处理 VideoFrames

本文探讨了使用 WebCodecs 和流创建实时视频处理管道,以及处理回压、管理 VideoFrame 生命周期和测量性能的考虑因素。下一步是开始实际处理这样的管道将公开的 VideoFrame 对象。请继续关注,这是第二部分的主题:网络上的视频帧处理——WebAssembly、WebGPU、WebGL、WebCodecs、WebNN 和 WebTransport。

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论