Skip to main content

使用 WebCodecs 和 @remotion/media-parser 解码视频

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

warning

不稳定的 API:此软件包是实验性的。我们可能随时更改 API,直到我们删除此通知。

最小示例

读取视频帧
tsx
import {parseMedia, OnAudioTrack, OnVideoTrack} from '@remotion/media-parser';
 
const onVideoTrack: OnVideoTrack = (track) => {
const videoDecoder = new VideoDecoder({output: console.log, error: console.error});
videoDecoder.configure(track);
 
return (sample) => {
videoDecoder.decode(new EncodedVideoChunk(sample));
};
};
 
const result = await parseMedia({
src: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
onVideoTrack,
});
读取视频帧
tsx
import {parseMedia, OnAudioTrack, OnVideoTrack} from '@remotion/media-parser';
 
const onVideoTrack: OnVideoTrack = (track) => {
const videoDecoder = new VideoDecoder({output: console.log, error: console.error});
videoDecoder.configure(track);
 
return (sample) => {
videoDecoder.decode(new EncodedVideoChunk(sample));
};
};
 
const result = await parseMedia({
src: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
onVideoTrack,
});

这有什么用处?

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

要使用 WebCodecs 解码视频,需要理解和解析视频的二进制格式。
这是一项需要大量领域知识的细致任务。

视频文件通常以两种容器格式之一出现:ISO BMFF(.mp4、.mov)或 Matroska(.webm、.mkv)。
像 mp4box.js 这样的库很好地解析了这些容器,但是仅限于特定的容器格式,这意味着您需要混合多个库。

parseMedia() 允许读取任意视频文件(将来:任意媒体文件)并与之交互,而不受容器、视频编解码器和音频编解码器的限制。

它使用现代 Web API,如 fetch()ReadableStream 和可调整大小的 ArrayBuffers,并返回设计用于与 WebCodecs API 一起使用的数据结构。

Remotion 会转换到 WebCodecs 吗?

在可预见的未来不会 - Remotion 目前使用 FFmpeg 和无头浏览器渲染视频。
FFmpeg 与 WebCodecs 一样快(它们共享相同的代码)- 因此没有必要转换到 WebCodecs。

Remotion 无法在浏览器中导出视频,因为浏览器没有用于捕获视口的 API。
一个例外是 canvas 元素,但是 Remotion 支持所有绘制到视口的方式:HTML、CSS、SVG 和 Canvas。

我们对WebCodecs感兴趣,因为它仍然有潜力解决许多开发人员的问题,而@remotion/media-parser作为一个整体可以解决许多用户的问题。

另请参阅:我可以在浏览器中渲染视频吗?.

实际考虑

如果您在实现中使用了带有编解码器的parseMedia(),请考虑以下内容。

检查浏览器是否支持@remotion/media-parser

Remotion需要fetch()和Resizeable ArrayBuffer API。
在使用parseMedia()之前,请检查您的运行时是否支持这些API。

tsx
const canUseMediaParser = typeof fetch === 'function' && typeof new ArrayBuffer().resize === 'function';
tsx
const canUseMediaParser = typeof fetch === 'function' && typeof new ArrayBuffer().resize === 'function';

检查浏览器是否具有VideoDecoderAudioDecoder

Chrome同时具有VideoDecoderAudioDecoder
Firefox仅在启用dom.media.webcodecs.enabled标志时才支持VideoDecoderAudioDecoder
Safari支持VideoDecoder,但不支持AudioDecoder。您可以解码视频轨道但无法解码音频轨道。

note

如果此信息已过时,请帮助改进此页面

如果浏览器不支持相应的解码器,您可以选择不接收样本。

拒绝样本
tsx
import type {OnAudioTrack, OnVideoTrack} from '@remotion/media-parser';
 
const onVideoTrack: OnVideoTrack = (track) => {
if (typeof VideoDecoder === 'undefined') {
return null;
}
 
const videoDecoder = new VideoDecoder({output: console.log, error: console.error});
// ...
};
 
const onAudioTrack: OnAudioTrack = (track) => {
if (typeof AudioDecoder === 'undefined') {
return null;
}
 
const audioDecoder = new AudioDecoder({output: console.log, error: console.error});
// ...
};
拒绝样本
tsx
import type {OnAudioTrack, OnVideoTrack} from '@remotion/media-parser';
 
const onVideoTrack: OnVideoTrack = (track) => {
if (typeof VideoDecoder === 'undefined') {
return null;
}
 
const videoDecoder = new VideoDecoder({output: console.log, error: console.error});
// ...
};
 
const onAudioTrack: OnAudioTrack = (track) => {
if (typeof AudioDecoder === 'undefined') {
return null;
}
 
const audioDecoder = new AudioDecoder({output: console.log, error: console.error});
// ...
};

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

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

检查浏览器是否支持编解码器
tsx
import type {OnAudioTrack, OnVideoTrack} from '@remotion/media-parser';
 
const onVideoTrack: OnVideoTrack = async (track) => {
const videoDecoder = new VideoDecoder({output: console.log, error: console.error});
 
const {supported} = await VideoDecoder.isConfigSupported(track);
if (!supported) {
return null;
}
 
// ...
};
 
const onAudioTrack: OnAudioTrack = async (track) => {
const audioDecoder = new AudioDecoder({output: console.log, error: console.error});
 
const {supported} = await AudioDecoder.isConfigSupported(track);
if (!supported) {
return null;
}
 
// ...
};
检查浏览器是否支持编解码器
tsx
import type {OnAudioTrack, OnVideoTrack} from '@remotion/media-parser';
 
const onVideoTrack: OnVideoTrack = async (track) => {
const videoDecoder = new VideoDecoder({output: console.log, error: console.error});
 
const {supported} = await VideoDecoder.isConfigSupported(track);
if (!supported) {
return null;
}
 
// ...
};
 
const onAudioTrack: OnAudioTrack = async (track) => {
const audioDecoder = new AudioDecoder({output: console.log, error: console.error});
 
const {supported} = await AudioDecoder.isConfigSupported(track);
if (!supported) {
return null;
}
 
// ...
};
note

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

错误处理

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

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

错误处理
tsx
import type {OnVideoTrack} from '@remotion/media-parser';
 
const onVideoTrack: OnVideoTrack = async (track) => {
const videoDecoder = new VideoDecoder({output: console.log, error: console.error});
 
return async (sample) => {
if (videoDecoder.state === 'closed') {
return;
}
};
};
错误处理
tsx
import type {OnVideoTrack} from '@remotion/media-parser';
 
const onVideoTrack: OnVideoTrack = async (track) => {
const videoDecoder = new VideoDecoder({output: console.log, error: console.error});
 
return async (sample) => {
if (videoDecoder.state === 'closed') {
return;
}
};
};
note
  • 对于AudioDecoder也适用相同的逻辑。
  • 你仍然应执行先前提到的检查,但它们在此示例中被省略了。

排队样本

提取样本是快速部分,解码它们是慢速部分。
如果队列中有太多样本,将会对页面的性能产生负面影响。

幸运的是,在解码器忙碌时,解析过程可以暂时暂停。
为此,将样本处理函数设置为异步。Remotion将在继续处理文件之前等待它。

这将使尚未需要的样本不会保留在内存中,从而保持解码过程的高效性。

一次仅保留10个样本在队列中
tsx
import type {OnVideoTrack} from '@remotion/media-parser';
 
const onVideoTrack: OnVideoTrack = async (track) => {
const videoDecoder = new VideoDecoder({output: console.log, error: console.error});
 
return async (sample) => {
if (videoDecoder.decodeQueueSize > 10) {
let resolve = () => {};
 
const cb = () => {
resolve();
};
 
await new Promise<void>((r) => {
resolve = r;
videoDecoder.addEventListener('dequeue', cb);
});
videoDecoder.removeEventListener('dequeue', cb);
}
 
videoDecoder.decode(new EncodedVideoChunk(sample));
}
};
一次仅保留10个样本在队列中
tsx
import type {OnVideoTrack} from '@remotion/media-parser';
 
const onVideoTrack: OnVideoTrack = async (track) => {
const videoDecoder = new VideoDecoder({output: console.log, error: console.error});
 
return async (sample) => {
if (videoDecoder.decodeQueueSize > 10) {
let resolve = () => {};
 
const cb = () => {
resolve();
};
 
await new Promise<void>((r) => {
resolve = r;
videoDecoder.addEventListener('dequeue', cb);
});
videoDecoder.removeEventListener('dequeue', cb);
}
 
videoDecoder.decode(new EncodedVideoChunk(sample));
}
};
note
  • 对于AudioDecoder也适用相同的逻辑。
  • 你仍然应执行先前提到的检查,但它们在此示例中被省略了。

处理拉伸视频

有些视频在内部的尺寸与呈现的尺寸不同。
例如,此示例视频 的编码宽度为1440,但呈现宽度为1920。

处理拉伸视频
tsx
import type {OnVideoTrack} from '@remotion/media-parser';
 
const onVideoTrack: OnVideoTrack = async (track) => {
const videoDecoder = new VideoDecoder({output: console.log, error: console.error});
videoDecoder.configure(track);
 
return async (sample) => {
console.log(sample)
// {
// codedWidth: 1440,
// codedHeight: 1080,
// displayAspectWidth: 1920,
// displayAspectHeight: 1080,
// ...
// }
};
};
处理拉伸视频
tsx
import type {OnVideoTrack} from '@remotion/media-parser';
 
const onVideoTrack: OnVideoTrack = async (track) => {
const videoDecoder = new VideoDecoder({output: console.log, error: console.error});
videoDecoder.configure(track);
 
return async (sample) => {
console.log(sample)
// {
// codedWidth: 1440,
// codedHeight: 1080,
// displayAspectWidth: 1920,
// displayAspectHeight: 1080,
// ...
// }
};
};

这意味着帧在内部以4:3的宽高比编码,但应以16:9的宽高比显示。
通过将codedWidthcodedHeightdisplayAspectWidthdisplayAspectHeight全部传递给new EncodedVideoChunk(),解码器应正确处理拉伸。

处理旋转

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

VideoDecoder无法为您旋转视频,因此您可能需要自行处理,例如通过将其绘制到画布上。
幸运的是,parseMedia()返回轨道的旋转:

处理拉伸视频
tsx
import type {OnVideoTrack} from '@remotion/media-parser';
 
const onVideoTrack: OnVideoTrack = async (track) => {
console.log(track.rotation) // -90
return null
};
处理拉伸视频
tsx
import type {OnVideoTrack} from '@remotion/media-parser';
 
const onVideoTrack: OnVideoTrack = async (track) => {
console.log(track.rotation) // -90
return null
};

查看这里的示例,了解如何将视频帧转换为位图并进行旋转。

理解视频的不同维度

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

处理拉伸视频
tsx
import type {OnVideoTrack} from '@remotion/media-parser';
 
const onVideoTrack: OnVideoTrack = async (track) => {
console.log(track);
// {
// codedWidth: 1440,
// codedHeight: 1080,
// displayAspectWidth: 1920,
// displayAspectHeight: 1080,
// width: 1080,
// height: 1900,
// ...
// }
 
return null
};
处理拉伸视频
tsx
import type {OnVideoTrack} from '@remotion/media-parser';
 
const onVideoTrack: OnVideoTrack = async (track) => {
console.log(track);
// {
// codedWidth: 1440,
// codedHeight: 1080,
// displayAspectWidth: 1920,
// displayAspectHeight: 1080,
// width: 1080,
// height: 1900,
// ...
// }
 
return null
};

其含义是:

  • codedWidthcodedHeight 是视频在编解码器内部格式中的尺寸。
  • displayAspectWidthdisplayAspectHeight 是视频应该显示的缩放尺寸,但尚未应用旋转。
note

这些不一定是视频呈现给用户的实际尺寸,因为尚未应用旋转。
这些字段的命名是这样的,因为它们对应于应传递给 new EncodedVideoChunk() 的内容。

  • widthheight 是播放器显示视频时的尺寸。

Google Chrome 的怪癖

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

tsx
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().
tsx
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().

在您的实现中考虑这一点。

Safari 性能

我们发现在我们的参考实现中,Safari 在解码完整的 Big Buck Bunny 视频时会出现问题。欢迎提供建议,否则我们鼓励您考虑是否以及要支持 WebCodecs API 的哪些部分。

参考实现

一个包含许多不同编解码器和边缘情况的测试平台可在此处找到。
按照这些说明在本地运行测试平台。```

许可证提醒

与 Remotion 本身一样,此软件包根据 Remotion 许可证 进行许可。
简而言之:个人和小团队可以使用此软件包,但4人以上的团队需要一份 公司许可证