了解如何使用 requestVideoFrameCallback()
在浏览器中更高效地处理视频。
HTMLVideoElement.requestVideoFrameCallback()
方法允许网页作者注册回调
。
这样,开发者可以对视频、
例如视频处理、在画布上绘制、视频分析
或与外部音频源同步。
与 requestAnimationFrame() 的区别
操作,例如使用
drawImage()
通过该 API 进行的更新将尽最大努力同步
与屏幕上播放视频的帧速率进行比较
不同于
window.requestAnimationFrame()
、
通常每秒触发 60 次左右
requestVideoFrameCallback()
会绑定到实际视频帧速率,并设置一个重要的
例外情况:
回调的有效运行速率是视频速率之间的较小速率 以及浏览器的速率 这意味着 25fps 的视频在以 60Hz 渲染的浏览器中播放 就会以 25Hz 触发回调 同一 60Hz 浏览器中的 120fps 视频将触发 60Hz 回调。
如何命名?
由于它与 window.requestAnimationFrame()
相似,该方法最初
已提议为“video.requestAnimationFrame()
”
并重命名为
requestVideoFrameCallback()
,已商定
。。
功能检测
if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
// The API is supported!
}
浏览器支持
浏览器支持
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
聚酯纤维
requestVideoFrameCallback()
方法的 polyfill
共
Window.requestAnimationFrame()
和
HTMLVideoElement.getVideoPlaybackQuality()
可用。使用该功能之前,请注意
README
中提及的限制。
使用 requestVideoFrameCallback() 方法
如果您曾使用过 requestAnimationFrame()
方法,您会立即对 requestVideoFrameCallback()
方法很熟悉。
您只需注册一次初始回调,然后在回调触发时重新注册。
const doSomethingWithTheFrame = (now, metadata) => {
// Do something with the frame.
console.log(now, metadata);
// Re-register the callback to be notified about the next frame.
video.requestVideoFrameCallback(doSomethingWithTheFrame);
};
// Initially register the callback to be notified about the first frame.
video.requestVideoFrameCallback(doSomethingWithTheFrame);
在回调中,now
是一个 DOMHighResTimeStamp
metadata
的会员级别为VideoFrameMetadata
字典(具有以下属性):
DOMHighResTimeStamp
类型的presentationTime
: 用户代理提交框架进行合成的时间。DOMHighResTimeStamp
类型的expectedDisplayTime
: 用户代理预计显示帧的时间。unsigned long
类型的width
: 视频帧的宽度(以媒体像素为单位)。unsigned long
类型的height
: 视频帧的高度(以媒体像素为单位)。double
类型的mediaTime
: 以呈现帧的秒数表示的媒体呈现时间戳 (PTS),例如该帧在video.currentTime
时间轴上的时间戳。unsigned long
类型的presentedFrames
: 针对组合提交的帧数。允许客户端确定VideoFrameRequestCallback
实例之间是否遗漏了帧。double
类型的processingDuration
: 从向解码器提交与此帧具有相同呈现时间戳 (PTS) 的编码包(例如,与mediaTime
相同)到解码帧可以呈现之前经过的时长(以秒为单位)。
对于 WebRTC 应用,可能还会显示其他属性:
DOMHighResTimeStamp
类型的captureTime
: 对于来自本地或远程源的视频帧,这是指相机捕获帧的时间。 对于远程来源,捕获时间是根据时钟同步和 RTCP 发送器报告估算的时间 将 RTP 时间戳转换为捕获时间。DOMHighResTimeStamp
类型的receiveTime
: 对于来自远程来源的视频帧,这是指接收编码帧的时间 由平台指定,即通过网络接收属于该帧的最后一个数据包的时间。unsigned long
类型的rtpTimestamp
: 与此视频帧关联的 RTP 时间戳。
此列表尤为值得关注的是 mediaTime
。
Chromium 的实现使用音频时钟作为支持 video.currentTime
的时间源。
而 mediaTime
直接由帧的 presentationTimestamp
填充。
如果您想以可重现的方式准确识别帧,则应使用 mediaTime
。
包括准确识别遗漏的帧。
如果一切看起来有点偏离...
垂直同步(简称 Vsync)是一种图形技术,可将视频的帧速率和显示器的刷新频率同步。
由于 requestVideoFrameCallback()
在主线程上运行,但从本质上讲,视频合成是在合成器线程上进行,
所有来自此 API 的一切都是尽力而为,浏览器不提供任何严格保证。
可能的情况是,API 可能会比视频帧的呈现时间晚一个 vsync。
通过该 API 对网页所做的更改只需执行一次 vsync 操作即可显示在屏幕上(与 window.requestAnimationFrame()
相同)。
因此,如果您不断更新网页上的 mediaTime
或帧编号,并将其与
与编号的视频帧进行比对,最终视频看上去会比前一帧
实际情况是,帧在 vsync x 处准备就绪,在 Vsync x+1 处会触发回调,而该帧则渲染在 Vsync x+1 时。
并且回调中所做的更改将在 Vsync x+2 处呈现。
您可以检查回调是否延迟了 vsync(并且帧已呈现在屏幕上)
方法是检查 metadata.expectedDisplayTime
大约是 now
还是一个 vsync 将来。
如果它与 now
的误差在 5 到 10 微秒以内,则表示帧已渲染;
如果 expectedDisplayTime
大约是未来 16 毫秒(假设您的浏览器/屏幕以 60Hz 的频率刷新),则
那么您就与帧同步了
演示
我创建了一个 Glitch 上的演示 展示了画布上各帧的绘制方式 视频的帧速率 其中记录帧元数据以进行调试。
let paintCount = 0;
let startTime = 0.0;
const updateCanvas = (now, metadata) => {
if (startTime === 0.0) {
startTime = now;
}
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
const elapsed = (now - startTime) / 1000.0;
const fps = (++paintCount / elapsed).toFixed(3);
fpsInfo.innerText = `video fps: ${fps}`;
metadataInfo.innerText = JSON.stringify(metadata, null, 2);
video.requestVideoFrameCallback(updateCanvas);
};
video.requestVideoFrameCallback(updateCanvas);
总结
用户执行帧级处理很长时间了,但无法访问实际帧,
仅显示 video.currentTime
个结果。
requestVideoFrameCallback()
方法在此解决方法的基础上进行了极大改进。
致谢
requestVideoFrameCallback
API 由
Thomas Guilbert。
此帖子由 Joe Medley 审核
以及 Kayce Basques 的作品。
主打图片,作者
Unsw 的 Denise Jans。