逐帧后退,以及它通常为什么很慢
对任何播放器来说,前进一帧都只是一帧的工作量。后退一帧却不是:为了显示一帧,可能要解码几百帧;在关键帧间隔很长的文件上,每按一次就要一秒甚至更久。下面说明这个开销从何而来、我们测到了什么,以及 Frame Player 为了让后退一帧和前进一帧差不多快所做的两件事。
一帧一帧地查看视频,动画师用它检查节奏,教练用它找到脚离地的那一瞬间,审片的人用它标记剪辑点,任何人都可以用它找到那一张值得保存的清晰画面。向前很容易。向后则是大多数播放器卡住的地方:按一下键,等;再按一下,再等。这不是哪个播放器的 bug,而是由视频的压缩方式决定的,不过可以绕过去。
为什么后退一帧比前进一帧开销更大
现代视频文件中几乎没有哪一帧是完整存储的。每隔一段会有一个关键帧,即一幅完整的画面;它之后直到下一个关键帧的每一帧,都只存储与周围帧之间的差异。关键帧和依赖它的那些帧合称一个图像组(GOP),一个图像组的长度从半秒到十秒都有可能。
所以解码器只能从关键帧开始往前走。要显示下一帧,它再应用一个差异就行。要显示上一帧,它却没有可以反向应用差异的对象,只能回到关键帧,再往前解码,一直解到屏幕上这一帧的前一帧:
| 要显示 | 解码器需要做的事 |
|---|---|
| 下一帧 | 解码一帧 |
| 上一帧,紧跟在关键帧之后 | 从那个关键帧开始解码几帧 |
| 上一帧,位于 60 fps 下 10 秒图像组的末尾 | 从关键帧开始重新解码整个图像组:为了显示一帧,最多要解码 600 帧 |
这就是为什么后退一帧的开销取决于文件,而不是播放器:图像组为两秒的光盘电影逐帧起来很省力,而视频网站提供的长图像组文件就很费力。而一个对此不做任何处理的播放器,每按一次都要重新付出全部代价。
后退一帧的实测开销
mpv(Frame Player、IINA、mpv.net 等播放器内部的引擎)正是这样后退的:每按一次就是一次跳到上一帧的精确跳转,也就是从关键帧开始解码。我们在五个测试文件上计时,分别使用软件解码和 Mac 的硬件解码器:
| 后退一帧 | 软件解码 | 硬件解码 |
|---|---|---|
| 1080p,24 fps,H.264,2 秒图像组 | 60 ms | 190 ms |
| 1080p,60 fps,H.264,10 秒图像组 | 160 ms | 1.9 s |
| 1080p,60 fps,VP9,5 秒图像组 | 260 ms | 1.3 s |
| 1080p,60 fps,AV1,10 秒图像组 | 1.0 s | 2.3 s |
| 4K HEVC,10 秒图像组 | 1.6 s | 1.8 s |
使用 mpv 0.41 在 Apple Silicon Mac(M3 Max)上测量,测试文件是生成的 60 秒视频,所以绝对数值偏乐观,有参考意义的是比例。前进一帧在所有情况下都只需一个帧周期:60 fps 下 17 ms,24 fps 下 42 ms。
第二列出人意料。硬件解码会让后退一帧慢两到十倍,尽管它看视频时更快。硬件解码器是为稳定地连续输出帧而设计的,它交回帧时有一段延迟,而一帧的跳转掩盖不了这段延迟。大多数播放器以及大多数 mpv 配置都开着硬件解码播放,所以最慢的情况恰恰是最常见的情况。
Frame Player 怎么做
两件事,每件都在发布前测量过,也都只在有帮助的地方才启用。
1. 逐帧时使用软件解码
一次逐帧过程中的第一次后退,会把解码器从显卡硬件切换到软件。切换本身大约需要 10 ms,紧接着的下一步就已经以软件速度运行。在一个图像组为两秒的真实 4K HEVC 资源上,仅这一项就把后退一帧从 230–410 ms 降到了 130–220 ms。
按下播放时,硬件解码会回来,而且只在视频仍处于暂停时回来,这是花了最长时间才做对的部分。如果在播放过程中切换解码器,送进来的是已经在路上的数据,来自图像组的中间,缺少它所依赖的关键帧。硬件 AV1 或 VP9 解码器遇到这种情况会失败,而 mpv 随后会悄悄地在这个文件剩下的部分一直使用软件解码。所以播放按钮、空格键、单击画面和媒体键,都会等硬件解码回来之后才让视频动起来。
这个等待是实实在在的开销:VP9 文件上 0.4–0.6 秒,H.264 电影上 0.7–0.8 秒,AV1 文件上 1.4–1.6 秒,而普通的继续播放只需 5–50 ms。我们也测量了另一种做法,即直接用软件解码继续播放,结果更糟,而且持续更久:
| 逐帧之后继续播放 | 软件 | 硬件 |
|---|---|---|
| H.264 电影 | 一个核心的 25% | 10% |
| 1080p,60 fps,VP9 | 33–42% | 8–12% |
| 1080p,60 fps,AV1 | 46% | 9–11% |
| 1440p,60 fps,VP9 | 45% | 11% |
同一台电脑,两种情况都没有丢帧。系统报告的功耗也是同样的比例。
为了省下仅有一次的一秒钟等待,让剩下的整部电影多耗三到四倍的电,在笔记本上不是一笔划算的交易。
2. 在逐帧仍然很慢的文件上,一次性反向解码
软件解码解决了图像组较短的电影。但对于图像组长达十秒的文件,即使用软件解码,每按一次也要一秒,它就无能为力了。对于这类文件,Frame Player 会把 mpv 切换为反向解码:mpv 把整个图像组解码一次,保存这些帧,再倒序交出。第一次按键为整个图像组付出代价;之后每按一次只需一个帧周期,和前进一帧一样。
开启和关闭反向解码本身也有开销,所以播放器在时机上很谨慎:
- 只在已经表现出很慢的文件上。任何一次耗时超过 100 ms 的后退都会给文件做上标记,之后再快的一步也不会清除这个标记:紧跟在关键帧之后的一步总是很快,说明不了下一步会怎样。
- 只从连续第二次后退开始。单次后退,或者后退与前进交替进行,都保持普通方式:掉转方向的开销超过一次普通的逐帧,每次换方向都付出这个代价,会让来回逐帧变得更慢而不是更快。
- 只在内存预算之内。保存一个解码后的图像组代价很高:一个 24 fps、8-bit 4K、十秒的图像组就是 3 GB,而我们测得的 mpv 内存占用与这个计算结果完全一致。在掉转方向之前,播放器会测量你所在位置附近的图像组有多长(只读取数据包的头部,只需几毫秒),以及电脑有多少内存,只有当最长的图像组能放进内存的四分之一(上限 4 GB)时才会继续。否则它会继续用普通方式逐帧。
- 关闭声音。否则 mpv 也会反向解码音频。在一个带 5.1 音轨的 4K 资源上,这让开始前的等待变成了三倍(2.0 秒对 0.7 秒),而且每四十步中就有一步会跳过五帧。按下播放时音轨会回来,但只在解码器重新转为正向之后,因为在反向解码时开启的音轨会落在下一个关键帧上,而不是当前帧。
结果是,在那个带 5.1 音轨的 4K HEVC 资源上:开始需要 0.69 秒,之后每一步 37–48 ms,和前进一帧差不多;只有在跨入前一个图像组的那一步用了 340 ms。按下播放会从你停下的那一帧继续,音轨和硬件解码都会恢复。
其他一切也必须知情
反向解码的播放器必须确保不留下任何还在反向的状态。从进度条或快捷键发起的跳转,会先把 mpv 转回正向并交还解码器,因为 mpv 允许一个待处理的跳转取代另一个,否则丢掉的就会是你发起的跳转;其他任何移动画面的操作,比如章节、一起看的房间,都会在生效的那一刻被察觉,mpv 随即转回正向。而且所有这些设置都只针对当前文件,所以下一个文件打开时,mpv 自己就会把它们重置:下一集绝不会一开始就从最后一帧倒着播放。
种子
反向解码只在这台电脑磁盘上的文件上运行,所以它覆盖本地文件和种子,包括仍在下载的种子,只是那里需要更加小心。种子中尚未下载的部分在读取时不会出错,而是读出一串零。所以在仍在下载的种子上,播放器在每次后退之前都会检查已下载部分的分布图,只有当它覆盖了要解码的图像组时才使用反向解码。你刚看过的内容总是已经下载好的;空缺是你向前跳到尚未下载的位置时,落点之前的那一段。在那里,播放器会用普通方式逐帧;如果某一步需要等待其他用户的数据,它会明确说明,而不是看起来像卡住了。在线视频流始终用普通方式逐帧。
使用方法
- 逗号键后退一帧,句点键前进一帧,两个系统上都一样;按住任一键可以连续逐帧。两者都可以在快捷键编辑器中重新绑定。
- S 保存屏幕上的这一帧,Shift+S 连同字幕一起保存,Ctrl+C(Mac 上是 ⌘C)把它复制到剪贴板。保存的是播放器解码出的那一帧,HDR 色调映射也一并保留。
- 反向解码可以在“设置 › 视频 › 快速逐帧后退”中关闭;逐帧时使用软件解码没有开关,因为它除了速度之外什么也不改变。
- 投屏到电视时,电脑上没有可以逐帧浏览的画面,所以播放器会提示在投屏结束之前无法逐帧。
本页的数据都是在 Mac 上测量的。在 Windows 上,播放器做的是同样的事:逐帧时使用软件解码,在同样的内存预算内反向解码,只是用的是显卡自己的解码器而不是 Apple 的,我们还没有在那里计时。
常见问题
用哪些键逐帧?
逗号键后退一帧,句点键前进一帧,Windows 和 macOS 上都一样。两个键都可以按住连续逐帧,也都可以在快捷键编辑器中改成其他按键。如果视频正在播放,逐帧会将其暂停。
为什么第一次后退比之后几次慢?
第一次后退是普通的后退,从前一个关键帧开始解码,播放器会给它计时。如果这一步很慢,而你又后退了一次,播放器会把回到关键帧的整段内容解码一次并保存下来,之后每按一次都只需大约一帧的工作量。你感觉到的等待就是这次一次性解码:在我们测量的 4K 电影上是 0.7 秒,关键帧间隔更长的文件还会更久。
为什么逐帧之后按播放要等一下?
逐帧期间,视频以软件解码,因为这样后退一帧要快好几倍。按下播放时,会先把解码交还给显卡硬件,在关键帧间隔很长的文件上,这最多需要一秒半。如果改为一直用软件解码,剩下的整部电影都要多耗三到四倍的电。
快速逐帧会占用多少内存?
占用量等于回到关键帧的那段视频解码后的大小,最多不超过电脑内存的四分之一或 4 GB(取较小者)。文件的关键帧间隔太大、放不下时,播放器会改用普通方式后退。快速方式可以在“设置 › 视频 › 快速逐帧后退”中关闭。
为什么后退逐帧时字幕消失了?
播放器反向解码时,不会读取存储在视频文件内部的字幕,所以这几步里看不到字幕。只要你向前逐帧或按下播放,字幕就会回来。
投屏到电视时能逐帧吗?
不能。投屏期间由电视解码视频,电脑上没有可以逐帧浏览的画面,所以播放器会提示在投屏结束之前无法逐帧。