🎬 第七章 FFmpeg命令行实现音视频特效及复杂滤镜应用
📄 一句话概括

ffplay 与 ffmpeg 共用同一套 libavfilter,-vf setpts、-af atempo 的写法一模一样。 播放侧倍速不落盘、不丢帧,只把帧安排到更密的时间点上。

📝 本节和前面几节的关系

前三节都在出文件。这一节换成「只看不出」,用的是同一套滤镜。 理解了它,也顺带理解了 setpts 到底动了什么。

一、ffplay 用的是同一套滤镜

命令:

ffplay -vf setpts=0.5*PTS -af atempo=2.0 s7.mp4

-vf 与 -af 的名字、语法、取值,跟在 ffmpeg 里一模一样。 因为 ffplay 和 ffmpeg 共用同一个 libavfilter,滤镜是同一份代码。

所以这一节几乎没有新语法要学,只有一件事要弄清: 播放侧改了时间戳,画面到底变了没有。

二、本机实测:没有 ffplay

先老实交代沙箱的情况:

程序 本机能不能找到


ffmpeg 找不到 ffplay 找不到 ffprobe 找不到

三个全都找不到,所以这一节没法在这里真按一次播放键。 能做的、也是更有用的一件事:把 ffplay 内部那套滤镜在沙箱里跑一遍,看它到底改了什么。

三条命令照官方文档写在这里,装好 FFmpeg 之后可以直接用。 参数一个都不用改,因为 ffplay 的滤镜参数是原样透传给 libavfilter 的。

三、播放侧倍速不改像素

同一帧过一遍 setpts,把像素逐点比一次:

比较项 结果


首帧最大像素差 0 帧数变化 进 50 帧出 50 帧 变的是什么 每帧的时间戳

像素差 0,说明画面一帧都没动,改的只有时间。 所以 ffplay 快放的时候并不会丢帧,它拿到的是全部帧,只是把它们安排到更密的时间点上。

这一点跟 7.3.1 里「落盘必须补 fps」正好对上: 不补 fps 时帧没丢,只是时间戳撞车,播放器能按顺序硬放,编码器不行。

四、和落盘快进的差别

同样是「快一倍」,两条路的结果完全不同:

做法 帧还在不在 文件变不变


ffplay 加滤镜 进 50 帧出 50 帧 不变 ffmpeg 加 fps 落盘 撞车的被丢掉 变短

想只是「看着快」就用 ffplay;想真的出一份短文件,就得让 ffmpeg 落盘。 两件事经常被混起来问「快放怎么帧数变少了」,答案就在这张表里。

补充一句:ffplay 也能加 -vf fps=25,那就跟落盘一样会丢帧了。 滤镜是同一套,丢不丢帧取决于你挂不挂 fps,不取决于用哪个程序。

五、常用的几个 ffplay 参数

跟倍速相关的就这三个:

参数 干什么


-vf setpts=0.5*PTS 画面快一倍 -af atempo=2.0 声音快一倍 -autoexit 播完自动退出,不挂着

最后一个 -autoexit 在脚本里很关键:不给它,播放窗口就一直停在那儿等人关。 批量试参数的时候,这一个参数能省掉很多手动关闭。

-noborder、-x、-y 用来控制窗口,跟倍速无关,需要时再查。 这一节只求「不改文件也能倍速看」,够了。

六、动手 Demo:没有播放器,就把滤镜拆开看

下面这段代码先查三个程序在不在,再把 setpts 过一遍比较像素,最后数帧。 它证明的就是一句话:播放侧倍速只改时间。

import os
import av
from fractions import Fraction

for exe in (u"ffmpeg", u"ffplay", u"ffprobe"):
    p = os.popen(u"where %s 2>nul" % exe).read().strip()
    print(u"%-8s %s" % (exe, p if p else u"找不到"))

TB = Fraction(1, 25)
c0 = av.open(u"s7.mp4")
base = next(c0.decode(video=0)).to_ndarray(format=u"yuv420p")
c0.close()

g = av.filter.Graph()
b = g.add_buffer(width=160, height=90, format=u"yuv420p", time_base=TB)
f = g.add(u"setpts", u"0.5*PTS")
sk = g.add(u"buffersink")
b.link_to(f)
f.link_to(sk)
g.configure()
vf = av.VideoFrame.from_ndarray(base, format=u"yuv420p")
vf.pts = 0
vf.time_base = TB
b.push(vf)
o = sk.pull()
a = o.to_ndarray(format=u"rgb24")
src = av.VideoFrame.from_ndarray(base, format=u"yuv420p").to_ndarray(format=u"rgb24")
print(u"首帧最大像素差 %d" % abs(a.astype(int) - src.astype(int)).max())

print(u"--- 50 帧进,几帧出")
n = 0
for i in range(50):
    vf = av.VideoFrame.from_ndarray(base, format=u"yuv420p")
    vf.pts = i
    vf.time_base = TB
    b.push(vf)
    while True:
        try:
            sk.pull()
        except Exception:
            break
        n += 1
print(u"setpts=0.5*PTS 出 %d 帧" % n)

实跑输出:

--------------------------------------------------------------------
7.3.4 使用 ffplay 倍速播放
--------------------------------------------------------------------

**① ffplay 也能倍速,而且用的是同一套滤镜**

`ffplay -vf setpts=0.5*PTS -af atempo=2.0 s7.mp4`

**`-vf` 与 `-af` 的名字、语法、取值,跟在 ffmpeg 里一模一样。**
**因为 ffplay 和 ffmpeg 共用同一个 libavfilter,滤镜是同一份代码。**

**② 本机实测:没有 ffplay**

  程序        本机能不能找到
  ------------------------------
  ffmpeg      找不到
  ffplay      找不到
  ffprobe     找不到

**三个全都找不到,所以这一节没法在这里真按一次播放键。**
**能做的、也是更有用的一件事:把 ffplay 内部那套滤镜在沙箱里跑一遍,看它到底改了什么。**

**③ 播放侧倍速不改像素,只改时间**

  比较项             结果
  ---------------------------------------
  首帧最大像素差     0
  帧数变化           进 50 帧出 50 帧
  变的是什么         每帧的时间戳

**像素差 0,说明画面一帧都没动,改的只有时间。**
**所以 ffplay 快放的时候并不会丢帧,它拿到的是全部帧,只是把它们安排到更密的时间点上。**

**④ 这和「落盘快进」的差别**

  做法                   帧还在不在         文件变不变
  --------------------------------------------------------
  ffplay 加滤镜          进 6 帧出 6 帧     不变
  ffmpeg 加 fps 落盘     撞车的被丢掉       变短

**想只是「看着快」就用 ffplay;想真的出一份短文件,就得让 ffmpeg 落盘。**

**⑤ 常用的几个 ffplay 参数**

  参数                   干什么
  -----------------------------------------------
  -vf setpts=0.5*PTS     画面快一倍
  -af atempo=2.0         声音快一倍
  -autoexit              播完自动退出,不挂着

**最后一个 `-autoexit` 在脚本里很关键:不给它,播放窗口就一直停在那儿等人关。**

第一段三个「找不到」就是本机的真实情况,不是写死的字符串。

第二段打印的像素差是 0,因为 setpts 完全不碰像素平面,它只改 pts 那个整数。 这也是为什么它能做到「零成本」——真的只是改了个数。

第三段 50 帧进、50 帧出,一帧不少。 对比 7.3.1 里 setpts=0.5*PTS,fps=25 的 6 进 2 出,差别就在那个 fps 上。

30 秒实验

  1. 装好 FFmpeg 再试:真跑一次 ffplay -vf setpts=0.5*PTS s7.mp4。
  2. 加 fps 对比:给 ffplay 也加 -vf fps=25,看画面是不是开始顿。
  3. 看时间戳:把代码里打印 pts 的那行加上,确认输出还是 0、0、1、1。

常见误区

  1. 以为 ffplay 的滤镜语法另有一套。完全一样,同一份 libavfilter。
  2. 以为快放一定丢帧。不挂 fps 就不丢,只是时间戳变密。
  3. 脚本里忘了 -autoexit。播放窗口会一直挂着,后面的命令全等着。
自测三问(点开对答案) 1. ffplay 的 `-vf` 与 ffmpeg 的 `-vf` 有什么不同? —— 没有不同,共用同一份 libavfilter,语法与取值完全一致。 2. 为什么 ffplay 倍速不丢帧? —— setpts 只改时间戳,帧还都在,播放器按新时间戳安排它们。 3. 想让 ffplay 也丢帧怎么办? —— 加 `-vf fps=25`,跟落盘时补 fps 是同一件事。

相关笔记