-vf reverse 把整段顺序翻过来,逐像素一致、没有重编码失真。 代价是全片缓存在内存里,而且必须先 flush 才会吐帧。
7.3 改的是时间轴快慢,这一节把时间轴整个倒过来。 倒放是「时间方向」这类特效里最纯粹的一个。
一、命令只有一个词
倒放就一个滤镜,没有参数:
ffmpeg -i s7.mp4 -vf reverse back.mp4
reverse 把整段片子的顺序翻过来:最后一帧变成第一帧,第一帧变最后一帧。
它没有参数,也没有倍数——倒放就是倒放。
想「倒放的同时变慢」,得在后面再接 setpts,两个滤镜串起来。 顺序有意义:先 reverse 再 setpts,得到的是倒放后的慢速版。
二、真翻了吗:逐帧比对
「看起来倒了」不算证据,把输出帧和输入帧逐一比一遍才算:
输出第几帧 最像输入第几帧 平均差
0 5 0 1 4 0 2 3 0 3 2 0 4 1 0 5 0 0
六行全对上,顺序整个翻了过来,而且是逐像素相等(平均差 0)。 这比「看起来倒了」强得多:像素级一致,说明它只是换了顺序,没重编码失真。
顺带一句:reverse 拿到的必须是「按显示顺序排好」的帧。 解码器会做重排序,所以直接从文件解出来的帧天然是对的;手工拼的裸流不一定。
三、不 flush 就一帧都拿不到
这是本节最重要的一条:
怎么取 出来几帧
边喂边取 0 喂完再 push(None) 取 6
因为 reverse 得先把整段收完,才知道最后一帧是哪一帧。 收完之前它一帧都不吐,看起来就像「reverse 不工作」。
网上那句「PyAV 里 reverse 出 0 帧」的结论就是这么来的—— 不是滤镜不行,是没告诉它「流结束了」。
这条经验对所有「要等全片」的滤镜都成立:reverse、areverse、palettegen、 还有一些统计类滤镜,都得 flush 之后才有输出。
四、代价是内存
它得把整段缓存在内存里,缓存多大可以算:
片长与分辨率 缓存的原始帧
160×90 两秒 1.0 MB 1280×720 三十秒 988.8 MB 1920×1080 六十秒 4449.5 MB
一分钟的 1080p 就要 4.3 GB,一小时的能到 261 GB,机器直接废掉。 所以 reverse 只适合短片头、短片段,长片要倒放得先切段再拼。
算法是「宽 × 高 × 1.5 × 帧率 × 秒数」——那个 1.5 是 yuv420p 的 Y 全采样加两个半采样的色度平面。换个像素格式,系数就得改。
五、声音也要一起倒
命令写成这样,画面与声音一起倒:
ffmpeg -i s7.mp4 -vf reverse -af areverse back.mp4
只写 reverse 不写 areverse,画面倒着放、声音正着放,嘴型全对不上。
音频那边叫 areverse,多一个 a 前缀,参数一样没有。
音频也是全缓存的,一小时的立体声 44.1 kHz 大约 1.2 GB,比视频省得多。 但两头都缓存,长片子依旧扛不住。
六、动手 Demo:六帧、两次取法、一次缓存估算
下面这段代码先试不 flush 与 flush 两种取法,再把输出帧逐一对回输入帧。 最后算三档片长的缓存占用。
import av
from fractions import Fraction
TB = Fraction(1, 25)
c0 = av.open(u"s7.mp4")
yv, rg = [], []
for fr in c0.decode(video=0):
yv.append(fr.to_ndarray(format=u"yuv420p"))
rg.append(fr.to_ndarray(format=u"rgb24"))
if len(yv) >= 6:
break
c0.close()
def run(flush):
g = av.filter.Graph()
b = g.add_buffer(width=160, height=90, format=u"yuv420p", time_base=TB)
rv = g.add(u"reverse")
sk = g.add(u"buffersink")
b.link_to(rv)
rv.link_to(sk)
g.configure()
out = []
for i, arr in enumerate(yv):
vf = av.VideoFrame.from_ndarray(arr, format=u"yuv420p")
vf.pts = i
vf.time_base = TB
b.push(vf)
while True:
try:
o = sk.pull()
except Exception:
break
out.append(o.to_ndarray(format=u"rgb24"))
if flush:
b.push(None)
while True:
try:
o = sk.pull()
except Exception:
break
out.append(o.to_ndarray(format=u"rgb24"))
return out
print(u"--- 不 flush 与 flush")
print(u"不 flush -> %d 帧" % len(run(False)))
out = run(True)
print(u"flush -> %d 帧" % len(out))
print(u"--- 逐帧对照")
for i, o in enumerate(out):
d = [float(abs(o.astype(int) - r.astype(int)).mean()) for r in rg]
print(u"出 %d 最像入 %d 平均差 %d" % (i, d.index(min(d)), int(min(d))))
print(u"--- 缓存占用(原始帧,未压缩)")
for w, h, sec in ((160, 90, 2), (1280, 720, 30), (1920, 1080, 60)):
print(u"%dx%d %d 秒 -> %.1f MB" % (w, h, sec, w * h * 1.5 * 25 * sec / 1048576.0))
实跑输出:
--------------------------------------------------------------------
7.5 视频倒放
--------------------------------------------------------------------
**① 命令只有一个词**
`ffmpeg -i s7.mp4 -vf reverse back.mp4`
**`reverse` 把整段片子的顺序翻过来:最后一帧变成第一帧,第一帧变最后一帧。**
**它没有参数,也没有倍数——倒放就是倒放。**
**② 真翻了吗?逐帧比对**
输出第几帧 最像输入第几帧 平均差
--------------------------------------------
0 5 0
1 4 0
2 3 0
3 2 0
4 1 0
5 0 0
**六行全对上,顺序整个翻了过来,而且是逐像素相等(平均差 0)。**
**这比「看起来倒了」强得多:像素级一致,说明它只是换了顺序,没重编码失真。**
**③ 一个必须知道的坑:不 flush 就一帧都拿不到**
怎么取 出来几帧
-------------------------------------
边喂边取 0
喂完再 push(None) 取 6
**因为 reverse 得先把整段收完,才知道最后一帧是哪一帧。**
**收完之前它一帧都不吐,看起来就像「reverse 不工作」。**
**网上那句「PyAV 里 reverse 出 0 帧」的结论就是这么来的——**
**不是滤镜不行,是没告诉它「流结束了」。**
**④ 代价是内存**
**它得把整段缓存在内存里,缓存多大可以算:**
片长与分辨率 缓存的原始帧
-------------------------------------
160×90 两秒 1.0 MB
1280×720 三十秒 988.8 MB
1920×1080 六十秒 4449.5 MB
**一分钟的 1080p 就要 4.3 GB,一小时的能到 261 GB,机器直接废掉。**
**所以 reverse 只适合短片头、短片段,长片要倒放得先切段再拼。**
**⑤ 声音也要一起倒**
**命令写成这样,画面与声音一起倒:**
`ffmpeg -i s7.mp4 -vf reverse -af areverse back.mp4`
**只写 `reverse` 不写 `areverse`,画面倒着放、声音正着放,嘴型全对不上。**
第一段两行就是本节那张小表:不 flush 是 0 帧,flush 之后 6 帧。 这个 0 帧非常容易让人误判成「滤镜不支持」。
第二段六行完全对上:出 0 最像入 5、出 5 最像入 0,平均差全是 0。 说明它只是改了顺序,一个像素都没重算。
第三段三行是缓存估算,跟正文那张表一致。 数字大得吓人,但这就是「无损倒放」绕不过去的代价。
30 秒实验
- 改帧数:把
>= 6改成>= 20,看对照表是不是变成 0 对 19。 - 去掉 flush:把
run(True)改成run(False),看是不是 0 帧。 - 加音轨:给命令加
-af areverse,量两条流的时长。
常见误区
- 不 flush 就说 reverse 坏了。它要等流结束才吐帧。
- 拿它处理长片。全片缓存在内存里,一小时的 1080p 要几百 GB。
- 只倒画面不倒声音。要配
-af areverse。
自测三问(点开对答案)
1. reverse 为什么必须 flush 才有输出? —— 它要先看完最后一帧才知道倒过来的第一帧是谁,收完才吐。 2. 倒放会不会损失画质? —— 不会,实测逐像素平均差为 0,只是换了顺序。 3. 一小时的 1080p 倒放要多少内存? —— 按宽×高×1.5×帧率×秒数算,约 261 GB,根本放不下。- ☐ (本节完,下一节 7.6 视频翻转与旋转)