Skip to main content

使用 WebCodecs 和 @remotion/media-parser 处理视频

parseMedia() 能够从音频和视频中提取轨道和样本,并将其以适合与 WebCodecs API 一起使用的格式呈现。

最小示例

🌐 Minimal example

以下代码片段是如何将 parseMedia() 与 WebCodecs 一起使用的基本示例,不适用于生产环境。

🌐 The following snippet is a basic, non-production ready example of how to use parseMedia() with WebCodecs.

Reading video frames
// ⚠️ Simple, but non-production ready example import {parseMedia, MediaParserOnAudioTrack, MediaParserOnVideoTrack} from '@remotion/media-parser'; const result = await parseMedia({ src: 'https://remotion.media/video.mp4', onVideoTrack: ({track}) => { const decoder = new VideoDecoder({ output: console.log, error: console.error, }); decoder.configure(track); return (sample) => { decoder.decode(new EncodedVideoChunk(sample)); }; }, });

排队的重要性

🌐 The importance of queueing

考虑以下视频处理流程,它将视频进行转换,类似于convertMedia()的做法。

🌐 Consider the following video processing pipeline, which converts a video, similarly to what convertMedia() does.

Media ParsingUser processingFrame sorterVideoDecoderAudioDecoderVideoEncoderWrite to fileFastAudioEncoderSlowSlowFastUnknown speedUser processingUnknown speed

该流水线由不同的步骤组成,每个步骤的速度各不相同。
例如,媒体的解析要比视频的解码快得多。

🌐 The pipeline consists of different steps, each of which have different speeds.
For example, the parsing of the media is much faster than the decoding of the video.

这可能导致视频解码器前出现“交通堵塞”,许多样本迅速排队。
由于这会导致内存积累,它会对页面性能产生负面影响。

🌐 This can lead to a "traffic jam" in front of the video decoder, where many samples are queueing quickly.
Since this will cause memory to build up, it will negatively impact the performance of the page.

将整个流程连接在一起并对每个步骤进行限制,使其执行速度不会超过下一步,这是有道理的。 在上面的例子中,需要管理六个队列,其中任何一个出现故障都会导致内存失控。

🌐 It makes sense to connect the whole pipeline together and throttle each step so it does not perform faster than the next step.
In the example above, six queues are required to be managed, with each one of them malfunctioning causing memory to run away.

Remotion 如何帮助你使用 WebCodecs

🌐 How Remotion helps you work with WebCodecs

由于正确处理排队是一项具有挑战性的任务,我们提供了帮助你使用 WebCodecs 的辅助工具。

🌐 Since handling queueing correctly is a challenging task, we offer helpers that help you work with WebCodecs.

Remotion 的原语旨在为你提供功能,以合理地实现排队:

🌐 Remotion's primitives are designed to offer you functionality to implement queueing in a sensible way:

  • parseMedia() 中,回调是异步的,只要它们没有解析,解析过程就不会继续。
  • 辅助函数 createAudioDecoder()createVideoDecoder() 为你提供了一个可等待的辅助方法 waitForQueueToBeLessThan()
  • 对于处理视频和音频帧以及编码视频和音频,我们目前还没有提供辅助工具,但希望很快能提供。

实际考虑

🌐 Practical considerations

如果你在使用 parseMedia() 和编解码器时,需要对你的实现进行以下考虑。

🌐 If you use parseMedia() with codecs, make the following considerations for your implementation.

检查浏览器是否有 VideoDecoderAudioDecoder

🌐 Check if browser has VideoDecoder and AudioDecoder

截至2025年5月,除Safari外,所有主流浏览器都支持VideoDecoderAudioDecoder,Safari仅支持VideoDecoderAudioDecoder在Safari技术预览版中受到支持,这意味着很快所有主流浏览器将完全支持WebCodecs。

🌐 As of May 2025, all major browsers support VideoDecoder and AudioDecoder except Safari, which has support for VideoDecoder only.
AudioDecoder is supported in Safari Technology Preview, meaning that soon, all major browsers will support WebCodecs fully.

不过,检查浏览器是否支持解码器仍然是一个好主意。 你可以通过在回调中返回 null 来选择跳过一个轨道。

🌐 Still, it is a good idea to check if the browser supports the decoders.
You can opt to skip a track by returning null from the callback.

Rejecting samples
import type {MediaParserOnAudioTrack, MediaParserOnVideoTrack} from '@remotion/media-parser'; const onVideoTrack: MediaParserOnVideoTrack = ({track}) => { if (typeof VideoDecoder === 'undefined') { return null; } const videoDecoder = new VideoDecoder({ output: console.log, error: console.error, }); // ... }; const onAudioTrack: MediaParserOnAudioTrack = ({track}) => { if (typeof AudioDecoder === 'undefined') { return null; } const audioDecoder = new AudioDecoder({ output: console.log, error: console.error, }); // ... };

检查浏览器是否支持该编解码器

🌐 Check if the browser supports the codec

并非所有浏览器都支持 parseMedia() 发出的所有编解码器。 最好的方法是使用 AudioDecoder.isConfigSupported()VideoDecoder.isConfigSupported() 来检查浏览器是否支持该编解码器。 这些都是异步 API,幸运的是 onAudioTrackonVideoTrack 也允许使用异步代码。

🌐 Not all browsers support all codecs that parseMedia() emits.
The best way is to use AudioDecoder.isConfigSupported() and VideoDecoder.isConfigSupported() to check if the browser supports the codec.
These are async APIs, fortunately onAudioTrack and onVideoTrack allow async code as well.

Checking if the browser supports the codec
import type {MediaParserOnAudioTrack, MediaParserOnVideoTrack} from '@remotion/media-parser'; const onVideoTrack: MediaParserOnVideoTrack = async ({track}) => { const videoDecoder = new VideoDecoder({ output: console.log, error: console.error, }); const {supported} = await VideoDecoder.isConfigSupported(track); if (!supported) { return null; } // ... }; const onAudioTrack: MediaParserOnAudioTrack = async ({track}) => { const audioDecoder = new AudioDecoder({ output: console.log, error: console.error, }); const {supported} = await AudioDecoder.isConfigSupported(track); if (!supported) { return null; } // ... };
note

除了先前提到的检查之外,还要进行这些检查。

错误处理

🌐 Error handling

如果发生错误,你会在传递给 VideoDecoderAudioDecoder 构造函数的 error 回调中收到错误。解码器 state 将切换到 "closed",但是你仍然会收到样本。

🌐 If an error occurs, you get the error in the error callback that you passed to the VideoDecoder or AudioDecoder constructor.
The decoder state will switch to "closed", however, you will still receive samples.

如果解码器处于 "closed" 状态,你应该停止将它们传递给 VideoDecoder。

🌐 If the decoder is in "closed" state, you should stop passing them to VideoDecoder.

Error handling
import type {MediaParserOnVideoTrack} from '@remotion/media-parser'; const onVideoTrack: MediaParserOnVideoTrack = async ({track}) => { const videoDecoder = new VideoDecoder({ output: console.log, error: console.error, }); return async (sample) => { if (videoDecoder.state === 'closed') { return; } }; };
note
  • 相同的逻辑也适用于 AudioDecoder
  • 你仍然应该执行之前提到的检查,但在这个例子中省略了这些检查。

处理旋转

🌐 Handling rotation

WebCodecs 似乎没有考虑旋转。例如,这个 用 iPhone 录制的视频 的元数据表明它应该以 90 度旋转显示。

🌐 WebCodecs do not seem to consider rotation.
For example, this video recorded with an iPhone has metadata that it should be displayed at 90 degrees rotation.

VideoDecoder 无法为你旋转视频,所以你可能需要自己操作,例如将视频绘制到画布上。
幸运的是,parseMedia() 会返回轨道的旋转角度:

Handling stretched videos
import type {MediaParserOnVideoTrack} from '@remotion/media-parser'; const onVideoTrack: MediaParserOnVideoTrack = async ({track}) => { console.log(track.rotation); // -90 return null; };

请参阅此处了解如何将视频帧转换为位图并进行旋转的示例。

🌐 See here for an example of how a video frame is turned into a bitmap and rotated.

理解视频的不同维度

🌐 Understanding the different dimensions of a video

正如刚才提到的,一些视频可能会被拉伸或旋转。
在极端情况下,你可能会遇到一个具有三种不同尺寸的视频。

🌐 As just mentioned, some videos might be stretched or rotated.
In an extreme case, it is possible that you stumble opon a video that has three different dimensions.

Handling stretched videos
import type {MediaParserOnVideoTrack} from '@remotion/media-parser'; const onVideoTrack: MediaParserOnVideoTrack = async ({track}) => { console.log(track); // { // codedWidth: 1440, // codedHeight: 1080, // displayAspectWidth: 1920, // displayAspectHeight: 1080, // width: 1080, // height: 1900, // ... // } return null; };

它的意思是:

🌐 The meaning of it is:

  • codedWidthcodedHeight 是该视频在编解码器内部格式中的尺寸。
  • displayAspectWidthdisplayAspectHeight 是视频显示时的缩放尺寸,但旋转尚未应用。 :::note 这些不一定是视频呈现给用户时的实际尺寸,因为旋转尚未应用。 这些字段之所以这样命名,是因为它们对应于应传递给 new EncodedVideoChunk() 的值。 :::
  • widthheight 是视频在播放器中显示的尺寸。

谷歌浏览器的怪癖

🌐 Google Chrome quirks

我们发现,截至目前,AudioDecoder.isConfigSupported() 并不是百分之百可靠。例如,Chrome 标记此配置为支持,但仍然会抛出错误。

🌐 We find that as of now, AudioDecoder.isConfigSupported() are not 100% reliable. For example, Chrome marks this config as supported, but then throws an error nonetheless.

const config = {codec: 'opus', numberOfChannels: 6, sampleRate: 44100};
console.log(await AudioDecoder.isConfigSupported(config)); // {supported: true}

const decoder = new AudioDecoder({error: console.error, output: console.log});

decoder.configure(config); // Unsupported configuration. Check isConfigSupported() prior to calling configure().

在你的实现中考虑到这一点。

🌐 Consider this in your implementation.

为什么选择 WebCodecs?

🌐 Why WebCodecs?

WebCodecs 是在浏览器中解码视频的最快方式。 WebAssembly 解决方案需要去除 CPU 特定的优化,并且无法利用硬件加速。

🌐 WebCodecs is the fastest way to decode videos in the browser.
WebAssembly solutions need to strip the CPU-specific optimizations and cannot benefit from hardware acceleration.

在我们测试的任何情况下,使用 Remotion Media Parser 和 WebCodecs 的速度至少比使用 WebAssembly 解决方案快 3 倍。

🌐 Using Remotion Media Parser and WebCodecs is at least 3 times faster than using WebAssembly solutions in any scenario we tested.

WebCodecs 已内置于浏览器中,这意味着解码器和编码器无需加载。
到 2025 年晚些时候,WebCodecs 将在所有主要浏览器中得到支持。

🌐 WebCodecs are built into the browser, meaning decoders and encoders do not need to be loaded.
Later in 2025, WebCodecs will be supported in all major browsers.

使用 @remotion/webcodecs

🌐 With @remotion/webcodecs

@remotion/webcodecs 是一个使用 @remotion/media-parser 提供浏览器视频处理的包。
它为你处理各种浏览器的怪癖和复杂的实现细节。

参考实现

🌐 Reference implementation

一个包含许多不同编解码器和边缘案例的测试平台可在 这里 获取。
按照 这些说明 在本地运行测试平台。

🌐 A testbed with many different codecs and edge cases is available here.
Follow these instructions to run the testbed locally.

许可证提醒

🌐 License reminder

像 Remotion 本身一样,这个软件包是根据 Remotion 许可 授权的。 简而言之:个人和小团队可以使用这个软件包,但 4 人及以上的团队 需要公司许可

🌐 Like Remotion itself, this package is licensed under the Remotion License.
TL;DR: Individuals and small teams can use this package, but teams of 4+ people need a company license.