使用 WebCodecs 和 @remotion/media-parser 解码视频
parseMedia()
能够从音频和视频中提取轨道和样本,以适合与 WebCodecs API 一起使用的格式。
不稳定的 API:此软件包是实验性的。我们可能随时更改 API,直到我们删除此通知。
最小示例
读取视频帧tsx
import {parseMedia ,OnAudioTrack ,OnVideoTrack } from '@remotion/media-parser';constonVideoTrack :OnVideoTrack = (track ) => {constvideoDecoder = newVideoDecoder ({output :console .log ,error :console .error });videoDecoder .configure (track );return (sample ) => {videoDecoder .decode (newEncodedVideoChunk (sample ));};};constresult = awaitparseMedia ({src : 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',onVideoTrack ,});
读取视频帧tsx
import {parseMedia ,OnAudioTrack ,OnVideoTrack } from '@remotion/media-parser';constonVideoTrack :OnVideoTrack = (track ) => {constvideoDecoder = newVideoDecoder ({output :console .log ,error :console .error });videoDecoder .configure (track );return (sample ) => {videoDecoder .decode (newEncodedVideoChunk (sample ));};};constresult = awaitparseMedia ({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';
检查浏览器是否具有VideoDecoder
和AudioDecoder
Chrome同时具有VideoDecoder
和AudioDecoder
。
Firefox仅在启用dom.media.webcodecs.enabled
标志时才支持VideoDecoder
和AudioDecoder
。
Safari支持VideoDecoder
,但不支持AudioDecoder
。您可以解码视频轨道但无法解码音频轨道。
如果此信息已过时,请帮助改进此页面。
如果浏览器不支持相应的解码器,您可以选择不接收样本。
拒绝样本tsx
import type {OnAudioTrack ,OnVideoTrack } from '@remotion/media-parser';constonVideoTrack :OnVideoTrack = (track ) => {if (typeofVideoDecoder === 'undefined') {return null;}constvideoDecoder = newVideoDecoder ({output :console .log ,error :console .error });// ...};constonAudioTrack :OnAudioTrack = (track ) => {if (typeofAudioDecoder === 'undefined') {return null;}constaudioDecoder = newAudioDecoder ({output :console .log ,error :console .error });// ...};
拒绝样本tsx
import type {OnAudioTrack ,OnVideoTrack } from '@remotion/media-parser';constonVideoTrack :OnVideoTrack = (track ) => {if (typeofVideoDecoder === 'undefined') {return null;}constvideoDecoder = newVideoDecoder ({output :console .log ,error :console .error });// ...};constonAudioTrack :OnAudioTrack = (track ) => {if (typeofAudioDecoder === 'undefined') {return null;}constaudioDecoder = newAudioDecoder ({output :console .log ,error :console .error });// ...};
检查浏览器是否支持编解码器
并非所有浏览器都支持parseMedia()
发出的所有编解码器。
最佳方法是使用AudioDecoder.isConfigSupported()
和VideoDecoder.isConfigSupported()
来检查浏览器是否支持编解码器。
这些是异步API,幸运的是onAudioTrack
和onVideoTrack
也允许异步代码。
检查浏览器是否支持编解码器tsx
import type {OnAudioTrack ,OnVideoTrack } from '@remotion/media-parser';constonVideoTrack :OnVideoTrack = async (track ) => {constvideoDecoder = newVideoDecoder ({output :console .log ,error :console .error });const {supported } = awaitVideoDecoder .isConfigSupported (track );if (!supported ) {return null;}// ...};constonAudioTrack :OnAudioTrack = async (track ) => {constaudioDecoder = newAudioDecoder ({output :console .log ,error :console .error });const {supported } = awaitAudioDecoder .isConfigSupported (track );if (!supported ) {return null;}// ...};
检查浏览器是否支持编解码器tsx
import type {OnAudioTrack ,OnVideoTrack } from '@remotion/media-parser';constonVideoTrack :OnVideoTrack = async (track ) => {constvideoDecoder = newVideoDecoder ({output :console .log ,error :console .error });const {supported } = awaitVideoDecoder .isConfigSupported (track );if (!supported ) {return null;}// ...};constonAudioTrack :OnAudioTrack = async (track ) => {constaudioDecoder = newAudioDecoder ({output :console .log ,error :console .error });const {supported } = awaitAudioDecoder .isConfigSupported (track );if (!supported ) {return null;}// ...};
除了先前提到的检查之外,还要执行这些检查。
错误处理
如果发生错误,您将在传递给VideoDecoder
或AudioDecoder
构造函数的error
回调中收到错误。
解码器的state
将切换为"closed"
,但您仍将收到样本。
如果解码器处于"closed"
状态,则应停止将它们传递给VideoDecoder。
错误处理tsx
import type {OnVideoTrack } from '@remotion/media-parser';constonVideoTrack :OnVideoTrack = async (track ) => {constvideoDecoder = newVideoDecoder ({output :console .log ,error :console .error });return async (sample ) => {if (videoDecoder .state === 'closed') {return;}};};
错误处理tsx
import type {OnVideoTrack } from '@remotion/media-parser';constonVideoTrack :OnVideoTrack = async (track ) => {constvideoDecoder = newVideoDecoder ({output :console .log ,error :console .error });return async (sample ) => {if (videoDecoder .state === 'closed') {return;}};};
- 对于
AudioDecoder
也适用相同的逻辑。 - 你仍然应执行先前提到的检查,但它们在此示例中被省略了。
排队样本
提取样本是快速部分,解码它们是慢速部分。
如果队列中有太多样本,将会对页面的性能产生负面影响。
幸运的是,在解码器忙碌时,解析过程可以暂时暂停。
为此,将样本处理函数设置为异步。Remotion将在继续处理文件之前等待它。
这将使尚未需要的样本不会保留在内存中,从而保持解码过程的高效性。
一次仅保留10个样本在队列中tsx
import type {OnVideoTrack } from '@remotion/media-parser';constonVideoTrack :OnVideoTrack = async (track ) => {constvideoDecoder = newVideoDecoder ({output :console .log ,error :console .error });return async (sample ) => {if (videoDecoder .decodeQueueSize > 10) {letresolve = () => {};constcb = () => {resolve ();};await newPromise <void>((r ) => {resolve =r ;videoDecoder .addEventListener ('dequeue',cb );});videoDecoder .removeEventListener ('dequeue',cb );}videoDecoder .decode (newEncodedVideoChunk (sample ));}};
一次仅保留10个样本在队列中tsx
import type {OnVideoTrack } from '@remotion/media-parser';constonVideoTrack :OnVideoTrack = async (track ) => {constvideoDecoder = newVideoDecoder ({output :console .log ,error :console .error });return async (sample ) => {if (videoDecoder .decodeQueueSize > 10) {letresolve = () => {};constcb = () => {resolve ();};await newPromise <void>((r ) => {resolve =r ;videoDecoder .addEventListener ('dequeue',cb );});videoDecoder .removeEventListener ('dequeue',cb );}videoDecoder .decode (newEncodedVideoChunk (sample ));}};
- 对于
AudioDecoder
也适用相同的逻辑。 - 你仍然应执行先前提到的检查,但它们在此示例中被省略了。
处理拉伸视频
有些视频在内部的尺寸与呈现的尺寸不同。
例如,此示例视频 的编码宽度为1440,但呈现宽度为1920。
处理拉伸视频tsx
import type {OnVideoTrack } from '@remotion/media-parser';constonVideoTrack :OnVideoTrack = async (track ) => {constvideoDecoder = newVideoDecoder ({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';constonVideoTrack :OnVideoTrack = async (track ) => {constvideoDecoder = newVideoDecoder ({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的宽高比显示。
通过将codedWidth
、codedHeight
、displayAspectWidth
和displayAspectHeight
全部传递给new EncodedVideoChunk()
,解码器应正确处理拉伸。
处理旋转
WebCodecs似乎不考虑旋转。
例如,这个使用iPhone录制的视频 具有应该以90度旋转显示的元数据。
VideoDecoder
无法为您旋转视频,因此您可能需要自行处理,例如通过将其绘制到画布上。
幸运的是,parseMedia()
返回轨道的旋转:
处理拉伸视频tsx
import type {OnVideoTrack } from '@remotion/media-parser';constonVideoTrack :OnVideoTrack = async (track ) => {console .log (track .rotation ) // -90return null};
处理拉伸视频tsx
import type {OnVideoTrack } from '@remotion/media-parser';constonVideoTrack :OnVideoTrack = async (track ) => {console .log (track .rotation ) // -90return null};
查看这里的示例,了解如何将视频帧转换为位图并进行旋转。
理解视频的不同维度
正如刚才提到的,一些视频可能会被拉伸或旋转。
在极端情况下,可能会遇到具有三个不同维度的视频。
处理拉伸视频tsx
import type {OnVideoTrack } from '@remotion/media-parser';constonVideoTrack :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';constonVideoTrack :OnVideoTrack = async (track ) => {console .log (track );// {// codedWidth: 1440,// codedHeight: 1080,// displayAspectWidth: 1920,// displayAspectHeight: 1080,// width: 1080,// height: 1900,// ...// }return null};
其含义是:
codedWidth
和codedHeight
是视频在编解码器内部格式中的尺寸。displayAspectWidth
和displayAspectHeight
是视频应该显示的缩放尺寸,但尚未应用旋转。
这些不一定是视频呈现给用户的实际尺寸,因为尚未应用旋转。
这些字段的命名是这样的,因为它们对应于应传递给 new EncodedVideoChunk()
的内容。
width
和height
是播放器显示视频时的尺寸。
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人以上的团队需要一份 公司许可证。