ffmpeg

🎬 FFmpeg 多媒体处理指南

📖 核心概念

💡 什么是 FFmpeg?

FFmpeg 是一套开源的数字音视频记录、转换和流化解决方案。它就像是多媒体处理领域的 “瑞士军刀”,几乎可以处理所有格式的音视频文件。

1. 容器与编码 (Container vs Codec)

  • 容器 (Container): 就像一个盒子(如 .mp4, .mkv, .avi),里面装着视频流、音频流和字幕流。
  • 编码 (Codec): 就像压缩算法(如 H.264, AAC, H.265),决定了盒子里的内容如何被压缩和存储。

2. 流 (Streams)

一个多媒体文件通常包含多个流:

  • Stream #0:0 (video): 视频流,负责画面。
  • Stream #0:1 (audio): 音频流,负责声音。
  • PTS/DTS:
    • PTS (Presentation Time Stamp): 告诉播放器在什么时候显示这一帧。
    • DTS (Decoding Time Stamp): 告诉解码器在什么时候解码这一帧(主要用于 B 帧)。
graph LR
    A[Input File .mp4] --> B[Demuxer 解封装]
    B --> C[Encoded Packets]
    C --> D[Decoder 解码]
    D --> E[Raw Frames YUV/PCM]
    E --> F[Filter Graph 滤镜处理]
    F --> G[Encoder 编码]
    G --> H[Muxer 封装]
    H --> I[Output File .mkv]

🛠️ 参数速查表

🛠️ 最佳实践

始终显式指定 -pix_fmt yuv420p 以保证最大兼容性(QuickTime, Windows Media Player 等)。

参数分类参数含义常见值 / 说明
基础-i输入文件input.mp4
-y自动覆盖默认为询问,脚本中使用很安全
视频-c:v视频编码器libx264, libx265, copy (流复制)
-crf恒定质量因子18~28 (值越小质量越高,x264 默认 23)
-preset编码速度预设ultrafast (快/大), medium (默认), veryslow (慢/小)
-r帧率25, 30, 60
-pix_fmt像素格式yuv420p (必选以防兼容性问题)
-tag:v视频标签hvc1 (Apple 设备播放 H.265 必需)
-bfB帧数量0 (低延迟), 2 (默认压缩高)
音频-c:a音频编码器aac, mp3, copy
-ar采样率16000, 44100, 48000
色彩-color_range颜色范围tv/1 (Limited 16-235), pc/2 (Full 0-255)
-colorspace颜色空间 (Matrix)bt709 (HD), bt2020nc (HDR), bt470bg (SD)
-color_primaries颜色原色bt709 (HD), bt470bg (SD),bt2020 (HDR)
-color_trc传输特性 (Transfer)bt709 (SDR), smpte2084 (PQ HDR)
滤镜-vf视频滤镜scale=1920:-2, transpose=1 (旋转)

💻 命令行实战

1. 格式转换 (Transcoding)

将视频转换为 H.264 编码,设置合理的画质和兼容性:

ffmpeg -i input.mkv -c:v libx264 -crf 23 -preset slow -c:a aac -b:a 128k -pix_fmt yuv420p output.mp4

2. 视频剪切 (Cutting)

⚠️ seek 原理

-ss 放在 -i 之前是关键!这样 FFmpeg 会使用关键帧快速定位(Input Seeking),速度极快且时间戳会被重置,但是这个方式不是精确剪裁的。

# 从 00:01:30 开始,截取 45 秒
ffmpeg -ss 00:01:30 -i input.mp4 -t 45 -c copy output.mp4

精确剪裁

ffmpeg -i input.mp4 \
-vf "trim=start=25:duration=30,setpts=PTS-STARTPTS" \
-af "atrim=start=25:duration=30,asetpts=PTS-STARTPTS" \
-c:v libx264 -crf 23 -preset medium \
-c:a aac -ar 44100 \
output.mp4

3. 画面旋转 (Rotation)

使用 transpose 滤镜:

  • 1: 顺时针 90°
  • 2: 逆时针 90°
# 顺时针旋转 90 度
ffmpeg -i input.mp4 -vf "transpose=1" -c:a copy output.mp4

4. 视频缩放 (Scaling)

将视频宽度固定为 1080,高度自动计算(保持比例):

ffmpeg -i input.mp4 -vf "scale=1080:-2" output.mp4

5. 流操作 (Stream Operations)

🛠️ 常用参数
  • -vn: 禁用视频 (Video None)
  • -an: 禁用音频 (Audio None)
  • -shortest: 以较短的流为准结束 (防止静止画面或无声片段)

提取音频 (Extract Audio)

# 提取为无损 WAV (aac, 16000Hz)
ffmpeg -i input.mp4 -vn -c:a aac -ar 16000 output.wav

# 直接提取音频流 (不转码,速度最快)
ffmpeg -i input.mp4 -vn -c:a copy output.aac

# 指定音频单通道 -ac 1

音视频合并 (Muxing)

将无声视频和音频文件合并:

ffmpeg -i video.mp4 -i audio.wav -c:v copy -c:a aac -shortest output.mp4

6. 帧操作 (Frame Operations)

视频转图片 (Extract Frames)

每秒提取 1 张图片 (-r 1):

# %05d 生成 00001.jpg, 00002.jpg ...
ffmpeg -i input.mp4 -r 1 -f image2 image-%05d.jpg

图片合成视频 (Images to Video)

将序列图片合并为视频:

ffmpeg -f image2 -r 25 -i image-%05d.jpg -c:v libx264 -pix_fmt yuv420p output.mp4

如果图片中间可能缺帧的话:

ffmpeg -r 25 -pattern_type glob -i "*.jpg" -c:v libx264 -pix_fmt yuv420p output.mp4

7. 视频信息 (Video Informations)

读取视频基本信息,包括宽、高、平均帧率、帧数、旋转信息:

# windows ps1脚本
ffprobe.exe -v error -select_streams v:0 -show_entries stream=width,height,avg_frame_rate,nb_frames:stream_tags=rotate:stream_side_data_list -of json @args

# 示例输出结果:
## 不带旋转的视频:
{
    "programs": [
    ],
    "streams": [
        {
            "width": 1080,
            "height": 1920,
            "avg_frame_rate": "25/1",
            "nb_frames": "11400",
            "tags": {
            }
        }
    ]
}
## 带旋转的:
{
    "programs": [
    ],
    "streams": [
        {
            "width": 3840,
            "height": 2160,
            "avg_frame_rate": "25/1",
            "nb_frames": "11400",
            "tags": {
            },
            "side_data_list": [
                {
                    "side_data_type": "Display Matrix",
                    "displaymatrix": "\n00000000:            0      -65536           0\n00000001:        65536           0           0\n00000002:            0   251658240  1073741824\n",
                    "rotation": 90
                }
            ]
        }
    ]
}

🐍 Python 自动化实战 (ffmpeg-python)

利用 ffmpeg-python 库可以将复杂的滤镜链逻辑化。以下皆为 Production-Ready 的代码片段。

1. 智能分辨率调整 (Smart Resizing)

自动判断长短边,将短边统一缩放到 1080p,适合处理竖屏/横屏混杂的素材。

import ffmpeg

def transcode_to_1080p(input_path, output_path):
    probe = ffmpeg.probe(input_path)
    video_stream = next(s for s in probe["streams"] if s["codec_type"] == "video")
    W, H = int(video_stream["width"]), int(video_stream["height"])
    
    # 逻辑:短边定为 1080,长边自适应 (-2 保证是 2 的倍数)
    if W <= H:
        scale_filter = {"w": 1080, "h": -2}
    else:
        scale_filter = {"w": -2, "h": 1080}
        
    (
        ffmpeg
        .input(input_path)
        .filter("scale", **scale_filter)
        .output(output_path, vcodec='libx265', crf=24, tag__v='hvc1') # 适配 Apple
        .run(overwrite_output=True)
    )

2. 音画同步校正 (Sync Adjustment)

当视频画面和声音对不上(声画不同步)时,分别对音频和视频流进行 trim (剪切) 和 setpts (时间戳重置) 操作。音画同步帧数可以利用SyncNet计算。

💡 算法逻辑
  • 声音比画面早 (frame_shift > 0): 延迟视频 = 剪掉音频开头 + 剪掉视频结尾。
  • 画面比声音早 (frame_shift < 0): 延迟音频 = 剪掉视频开头 + 剪掉音频结尾。
  • 核心滤镜: trim/atrim (物理剪切), setpts/asetpts (逻辑时间轴修正)。
def adjust_sync(input_path, output_path, shift_sec):
    """
    shift_sec > 0: 声音早, 需延迟视频
    shift_sec < 0: 画面早, 需延迟音频
    """
    probe = ffmpeg.probe(input_path)
    duration = float(probe['format']['duration'])
    
    inp = ffmpeg.input(input_path)
    v = inp.video
    a = inp.audio
    
    if shift_sec > 0:
        # 音频截掉开头 (atrim start),视频截掉结尾 (trim end)
        a = a.filter('atrim', start=shift_sec).filter('asetpts', 'PTS-STARTPTS')
        v = v.filter('trim', end=duration - shift_sec).filter('setpts', 'PTS-STARTPTS')
    else:
        # 视频截掉开头,音频截掉结尾
        abs_shift = abs(shift_sec)
        v = v.filter('trim', start=abs_shift).filter('setpts', 'PTS-STARTPTS')
        a = a.filter('atrim', end=duration - abs_shift).filter('asetpts', 'PTS-STARTPTS')

    ffmpeg.output(v, a, output_path).run()

3. 单图生成视频 (Loop Image)

将某一张特定的帧无限循环,生成一段静止视频,并铺上静音轨道。

def image_to_video(image_path, output_path, duration=5):
    # loop=1: 循环输入流
    # t=duration: 控制输出时长
    v = ffmpeg.input(image_path, loop=1, t=duration).video
    # 生成静音音轨
    a = ffmpeg.input('anullsrc', f='lavfi', t=duration).audio
    
    ffmpeg.output(v, a, output_path, r=25, pix_fmt='yuv420p').run()

⚖️ 深度解析 & FAQ

1. CRF vs Bitrate (码率控制)

在 FFmpeg 中,控制视频质量和文件大小主要有两种模式:CRF (恒定速率因子)Bitrate (指定码率)

CRF (Constant Rate Factor) - 恒定质量模式

这是 x264 和 x265 编码器的默认推荐模式

  • 原理: 目标是保持整个视频的视觉质量恒定。编码器会根据画面的复杂程度自动分配码率:复杂场景(动作快、细节多)分配更多码率,简单场景(静止、纯色)分配更少码率。
  • 参数: -crf <数值>
    • 范围: 0-51。
    • 0: 无损压缩 (Lossless)。
    • 23: x264 的默认值,平衡了画质和体积。
    • 28: x265 的默认值。
    • 规律: 数值越,画质越,文件越。通常建议在 18-28 之间调整。+/- 6 大概会导致码率翻倍或减半。
  • 适用场景: 本地归档、存储,不限制文件最终大小,只关心画质。

-b:v (Video Bitrate) - 指定码率模式

  • 原理: 强制编码器试图达到指定的平均码率。
  • 参数: -b:v <码率> (例如 -b:v 2M, -b:v 5000k)
  • 变种:
    • CBR (Constant Bitrate): 恒定码率。需要配合 -minrate-maxrate 设置为相同值。适用于流媒体直播,确保带宽稳定。
    • VBR (Variable Bitrate): 可变码率。通常配合 -maxrate-bufsize 使用(受限 VBR),限制峰值码率,防止网络拥塞。
  • 适用场景: 网络流媒体播放(需要控制带宽)、存储空间受限(必须把文件控制在特定大小内)。

对比

模式参数场景分析
CRF (恒定质量)-crf 23本地归档这里的“恒定”指视觉质量。编码器在复杂场景分配高码率,简单场景分配低码率。文件大小不可控
Bitrate (指定码率)-b:v 2M直播/推流强制限制带宽。可能导致复杂场景(如爆炸、烟雾)出现马赛克。通常配合 -maxrate-bufsize 使用 (VBR)。

2. OpenCV 读取问题

现象: 使用 ffmpeg -hwaccel cuda (NVIDIA 硬编) 生成的视频,OpenCV 的 cv2.CAP_PROP_POS_FRAMES 随机定位经常失败。 原因: 硬件编码器生成的 GOP (Group of Pictures) 结构可能不包含足够的信息供 OpenCV 的 CPU 解码器快速索引。 解决:

  1. 尽量使用 CPU 编码 (libx264).
  2. 显式设置较小的 GOP 大小 (-g 25)。

3. H.265 在 Apple 设备上的黑屏

若使用 libx265 编码后的视频在 Mac/iPhone 上无法播放(或只有声音无画面),通常是因为缺少 tag 信息。 解决: 必须添加参数 -tag:v hvc1

4. 色彩空间详解 (Color Management)

在处理不同分辨率和动态范围的视频时,必须正确设置色彩参数,否则会出现画面发灰色彩溢出。经常遇到的 “709” 和 “601” 指的就是色彩标准。

标准常用分辨率参数组合 (Matrix/Primaries/Transfer)
Rec. 601SD (PAL/NTSC, <720p)-colorspace bt470bg -color_primaries bt470bg -color_trc bt709
Rec. 709HD (720p, 1080p, SDR 4K)-colorspace bt709 -color_primaries bt709 -color_trc bt709
Rec. 2020UHD (4K HDR)-colorspace bt2020nc -color_primaries bt2020 -color_trc smpte2084
⚠️ 范围 (Range)
  • TV (Limited, 16-235): 绝大多数电影/电视内容的标准 (-color_range tv)。
  • PC (Full, 0-255): 游戏录制或计算机生成的图像 (-color_range pc)。 混用会导致黑色变成深灰色(Full 转 Limited 错误),或者暗部细节丢失变成死黑(Limited 转 Full 错误)。

5. 清空时间码信息

如果用Premier对帧的话,Premier会读取时间码相关的信息,导致视频不是从零开始,这时候需要清空时间码信息:

ffmpeg -i input.mp4 -c copy -map_metadata -1 -write_tmcd 0 output.mp4

🔗 参考资料