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

fade 只管两头:t=in 从黑到亮、t=out 从亮到黑。 时间按秒(d、st)或者按帧(n、s)写,二选一。

📝 本节和前面几节的关系

7.9、7.10 都在摆画面,这一节开始动时间轴上的亮度。 它是所有转场效果里最简单的一个,也是后面字幕、水印收尾的常用件。

一、只有两个方向

fade 做的事只有一件:让画面从黑到亮,或者从亮到黑。 方向由 t 决定,就两个值:

t 写什么 方向 从哪开始 到哪结束


in 淡入 全黑 原画面 out 淡出 原画面 全黑

没有第三个值。想要「淡到白」「淡到红」,靠 color 参数换目标色, 而不是再找一个 t 的取值。

fade 的名字容易让人以为它「让画面消失」,其实它是 「按时间把亮度乘一个从 0 到 1 的系数」——淡入是从 0 乘到 1,淡出反过来。

二、参数表

短名与长名都认,写哪个都行:

参数 长短名 写什么 默认


t type in 或 out 必填 d duration 持续多少秒 — n nb_frames 持续多少帧 — st start_time 从第几秒开始 0 s start_frame 从第几帧开始 0 color — 淡到哪个颜色 black alpha — 1 表示只淡 alpha 0

d 与 n 是二选一,st 与 s 也是二选一:一个是秒,一个是帧。 两套都写也行,但两套混着算容易错,建议整条命令统一用秒。

为什么用秒更稳:换帧率时秒不用改,帧数得重算。 25 帧的 0.5 秒是 12.5 帧,30 帧的 0.5 秒是 15 帧——写 d=0.5 两边都对。

三、按帧数写的实测

t=in:s=0:n=10 跑一遍,逐帧看亮度是原帧的几成:

第几帧 画面均值 是原帧的几成


0 0 0% 1 14 10% 2 28 20% 5 70 50% 8 114 80% 10 143 100% 11 144 100%

n=10 给出 11 个台阶(第 0 帧到第 10 帧),第 11 帧就完全满幅了。 台阶是线性的:每帧加 10%,第 10 帧到 100%,一点不差。

注意「是原帧的几成」这一列不能拿全片最亮那帧当基准—— 素材本身每帧的亮度就在变(红条在右移),必须逐帧比同序号的原帧。 这是量任何「亮度随时间变化」的效果都要守的一条。

四、按秒写与 st 的位置

t=out:st=1.0:d=0.5 跑出来是这样:

项 值


1.0 秒对应第几帧 25 从第几帧开始掉 26 到第几帧落地 38

第 25 帧正好踩在 1.0 秒上,此时系数还是 1,所以看着没变; 第 26 帧开始掉,到第 38 帧归零。中间正好 13 个帧距。

0.5 秒在 25 帧率上是 12.5 个帧距,落在 13 帧上—— fade 按时间戳算,不按整数帧表算,所以它不会「四舍五入到 12 帧」。

这也解释了为什么 st=1.5:d=0.5 在两秒片子上是安全的: 1.5+0.5=2.0,正好到片尾,最后一帧刚好归零。写成 d=0.6 就超了。

五、动手 Demo:十帧淡入、一次淡出定位

下面这段代码先把 n=10 的淡入跑一遍,逐帧算「是原帧的几成」; 再用 st=1.0 定位淡出是从第几帧开始的。

import av
from fractions import Fraction

TB = Fraction(1, 25)
c0 = av.open(u"s7.mp4")
BASE = [fr.to_ndarray(format=u"yuv420p") for fr in c0.decode(video=0)]
c0.close()

def run(filters, n=50):
    g = av.filter.Graph()
    b = g.add_buffer(width=160, height=90, format=u"yuv420p", time_base=TB)
    p = b
    for name, args in filters:
        f = g.add(name, args) if args else g.add(name)
        p.link_to(f)
        p = f
    sk = g.add(u"buffersink")
    p.link_to(sk)
    g.configure()
    out = []
    for i in range(n):
        vf = av.VideoFrame.from_ndarray(BASE[i % len(BASE)], format=u"yuv420p")
        vf.pts = i
        vf.time_base = TB
        b.push(vf)
        while True:
            try:
                out.append(sk.pull().to_ndarray(format=u"rgb24"))
            except Exception:
                break
    b.push(None)
    while True:
        try:
            out.append(sk.pull().to_ndarray(format=u"rgb24"))
        except Exception:
            break
    return out

ref = run([], 14)
o = run([(u"fade", u"t=in:s=0:n=10")], 14)

print(u"--- fade in,n=10")
for i in range(12):
    r = o[i].mean() / max(ref[i].mean(), 1e-6)
    print(u"  第 %2d 帧 均值 %3.0f  是原帧的 %3.0f%%" % (i, o[i].mean(), r * 100))

ref2 = run([], 50)
o2 = run([(u"fade", u"t=out:st=1.0:d=0.5")], 50)
first = None
for i in range(50):
    r = o2[i].mean() / max(ref2[i].mean(), 1e-6)
    if first is None and abs(r - 1.0) > 0.02:
        first = i
print(u"--- fade out,st=1.0 d=0.5")
print(u"  第 25 帧是 1.0 秒,从第 %d 帧开始掉,到第 %d 帧落地" % (first, first + 12))

实跑输出:

--------------------------------------------------------------------
7.11.1 fade 滤镜的参数说明
--------------------------------------------------------------------

**① 一个滤镜,两个方向**

**`fade` 只有一件事要做:让画面从黑到亮,或者从亮到黑。方向由 `t` 决定。**

  t 写什么     方向     从哪开始     到哪结束
  -----------------------------------------------
  in           淡入     全黑         原画面
  out          淡出     原画面       全黑

**就两个值,没有第三个。想要「淡到白」「淡到红」,靠 `color` 参数换目标色。**

**② 参数表**

  参数      长短名          写什么               默认
  -----------------------------------------------------------
  t         type            in 或 out            必填
  d         duration        持续多少秒           必填其一
  n         nb_frames       持续多少帧           必填其一
  st        start_time      从第几秒开始         0
  s         start_frame     从第几帧开始         0
  color     —               淡到哪个颜色         black
  alpha     —               1 表示只淡 alpha     0

**`d` 与 `n` 二选一,`st` 与 `s` 二选一:一个按时间写,一个按帧数写。**
**两者混着写也行,但容易算错,建议全用秒或者全用帧。**

**③ 按帧数写,实测十帧走完**

  第几帧     画面均值     是原帧的几成
  ----------------------------------------
  0          0            0%
  1          14           10%
  2          28           20%
  3          42           30%
  4          56           40%
  5          70           50%
  6          85           60%
  7          99           70%
  8          114          80%
  9          129          90%
  10         143          100%
  11         144          100%

**基准不能拿「全片最亮的那一帧」,因为素材每帧本身亮度就在变(红条在右移)。**
**所以这里先跑一遍不过滤镜的片子,再逐帧比——第 0 帧是 0%,第 10 帧满,就是 11 个台阶。**

**④ 按秒写,用哪一帧开始由 `st` 定**

  写法                   从第几帧开始变     到第几帧变完
  ----------------------------------------------------------
  t=out:st=1.0:d=0.5     26                 38

**1.0 秒在 25 帧率上正好是第 25 帧。第 25 帧还满着,从第 26 帧开始掉,到第 38 帧落地。**
**0.5 秒是 12.5 个帧距,摊在 13 帧上——fade 按时间戳算,不按整数帧表算。**

前十二行就是本节那张表的现场版,10%、20%、30% 一路爬上去, 到第 10 帧正好 100%。

算比例这一步是关键:o[i] / ref[i],两边都用同一个序号。 要是拿全片最大值当分母,红条右移带来的亮度变化会把这个比例搅乱。

最后一行把淡出的起点定位到帧号上:st=1.0 落在第 25 帧, 第 26 帧才开始掉——差这一帧是因为第 25 帧的系数还是 1。

30 秒实验

  1. 改帧数:把 n=10 改成 n=25,看是不是一秒才淡完。
  2. 改起点:把 st=1.0 改成 st=0.5,看开始掉的是第几帧。
  3. 超片长:把 st=1.5:d=0.8 喂进去,看最后落到多少。

常见误区

  1. d 写成「到第几秒结束」。它是持续多长,不是结束时刻。
  2. 拿全片最大值当亮度基准。素材本身每帧亮度在变,必须逐帧比。
  3. st+d 超过片长。最后停在半黑,看着像「淡出没做完」。
自测三问(点开对答案) 1. `fade=t=in:d=0.5` 的 0.5 是什么意思? —— 淡化持续 0.5 秒,不是「到 0.5 秒结束」。 2. `d` 与 `n` 有什么区别? —— `d` 按秒,`n` 按帧。换帧率时 `d` 不用改。 3. `st=1.0` 在 25 帧率上是第几帧? —— 第 25 帧,从第 26 帧才开始变暗。

相关笔记