我正在开发一个客户端项目,该项目允许用户提供视频文件并对其应用基本操作。我正在尝试可靠地从视频中提取帧。目前我有一个 <video>
我正在将选定的视频加载到其中,然后按如下方式拉出每一帧:
- 追寻至始
- 暂停视频
- 绘制
<video>
到<canvas>
- 使用
.toDataUrl()
从画布中捕获帧 - 向前搜索 1 / 30 秒(1 帧)。
- 冲洗并重复
这是一个相当低效的过程,更具体地说,事实证明它不可靠,因为我经常卡住帧。这似乎是因为它在绘制到画布之前没有更新实际的 <video>
元素。
我宁愿不必将原始视频上传到服务器只是为了分割帧,然后将它们下载回客户端。
非常感谢任何有关更好的方法的建议。唯一的警告是我需要它与浏览器支持的任何格式一起工作(在 JS 中解码不是一个很好的选择)。
原文由 The Busy Wizard 发布,翻译遵循 CC BY-SA 4.0 许可协议
[2021更新] :自从这个问题(和答案)首次发布以来,这方面的事情发生了变化,终于到了更新的时候了; 这里公开的方法已经 过时了,但幸运的是一些新的或即将到来的 API 可以帮助我们更好地提取视频帧:
最有前途和最强大的一个,但仍在开发中,有很多限制: WebCodecs
这个新的 API 释放了对媒体解码器和编码器的访问权限,使我们能够从视频帧(YUV 平面)访问原始数据,这对于许多应用程序来说可能比渲染帧更有用;而对于那些需要渲染帧的,这个API暴露的 VideoFrame 接口可以直接绘制到
但是有一个问题,除了目前的低支持之外,这个 API 需要输入已经被解复用。
有一些在线分离器,例如对于 MP4 视频, GPAC 的 mp4box.js 会有很大帮助。
可以 在提案的 repo 中找到完整的示例。
关键部分包括
请注意,由于 MediaCapture Transform 的 MediaStreamTrackProcessor ,我们甚至可以抓取 MediaStream 的帧。这意味着我们应该能够组合 HTMLMediaElement.captureStream() 和此 API 以获得我们的 VideoFrames,而无需多路分解器。然而,这仅适用于少数编解码器,这意味着我们将以读取速度提取帧……
无论如何,这是一个在最新的基于 Chromium 的浏览器上工作的示例,打开了
chrome://flags/#enable-experimental-web-platform-features
:最容易使用,但浏览器支持相对较差,并且受浏览器丢帧影响: HTMLVideoElement.requestVideoFrameCallback
这个方法允许我们在 HTMLVideoElement 上绘制新帧时安排回调。
它比 WebCodecs 级别更高,因此可能会有更多的延迟,而且我们只能以读取速度提取帧。
对于您的 Firefox 用户,Mozilla 的非标准 HTMLMediaElement.seekToNextFrame()
顾名思义,这将使您的
将其与 seeked 事件相结合,我们可以构建一个循环,该循环将抓取源的每一帧,速度比读取速度更快(是的!)。
但是这种方法是专有的,仅在基于 Gecko 的浏览器中可用,而不在任何标准轨道上可用,并且将来可能会在它们实现上面公开的方法时被删除。
但就目前而言,它是 Firefox 用户的最佳选择:
最不可靠的,随着时间的推移确实停止工作: HTMLVideoElement.ontimeupdate
策略 暂停 - 绘制 - 播放 - 等待时间更新 曾经是(在 2015 年)一种非常可靠的方式来了解何时将新框架绘制到元素上,但从那时起,浏览器对这个触发的事件进行了严格的限制速度很快,现在我们可以从中获取的信息不多了……
我不确定我是否仍然可以提倡使用它,我没有检查 Safari(这是目前唯一没有解决方案的)如何处理这个事件(他们对媒体的处理对我来说很奇怪),并且有一个很好的在大多数情况下,一个简单的
setTimeout(fn, 1000 / 30)
循环实际上更可靠。