2708 字
约 9 分钟
17
S27-X06 · ffmpeg 与 ffprobe:媒体处理的命令行基础

一个 mp4 过来,要压字幕、切片段、抽音轨、转码给不同端播放——图形界面也能点出来,但"批量、可重复、在没桌面的服务器上无人值守跑完",只有命令行这条路。ffmpeg 负责改,ffprobe 负责看。跳过后者直接猜参数,结果通常是切点偏了、画面躺倒、字幕变方块。

顺序:先立起输入输出模型,再用 ffprobe 摸清底账,然后才是操作、批量化与并发。


1. 输入输出模型:-i 只声明输入,输出靠位置区分

ffmpeg 的命令行是一串"选项 + 位置参数",选项只对紧随其后的那个文件生效。理解这一点,"-ss 该放前面还是后面"就不再需要背。

成分 写法 说明
输入 -i INPUT.mp4 可出现多次,每次声明一个输入
输入专属选项 写在对应 -i 之前 -ss、-t、-f、-readrate
输出 直接给文件名,无前缀 最后一个位置参数通常是输出
输出专属选项 写在输出文件名之前 -c:v、-crf、-y
全局选项 任意位置 -hide_banner、-nostdin、-loglevel
# 最朴素的一次调用
ffmpeg -i input.mp4 output.mkv

# 两个输入:输入 0 的视频 + 输入 1 的音频
ffmpeg -i video.mp4 -i audio.m4a -map 0:v:0 -map 1:a:0 -c copy merged.mp4

-map 是流选择的核心。不写 -map 时走默认流选择:每个类型挑一条"最佳"流,于是多音轨、多字幕的容器常丢掉你要的那条。

-map 写法 含义
-map 0 第 0 个输入的全部流
-map 0:v / -map 0:a 只挑视频 / 只挑音频
-map 0:a:1 第 0 个输入的第 1 条音频(下标从 0 起)
-map -0:s 排除第 0 个输入的字幕流(负号即排除)
-map 0:v -map 0:a? 问号表示该类型不存在也不报错

-c 是 -c:v / -c:a / -c:s 的通用写法。-c copy 表示所有流原样复制,此时不能对画面做任何滤镜或缩放。


2. ffprobe 看什么:容器、流、时长、帧率

默认输出是给人看的一坨文本,真正好用的是把结构序列化出来。

# 容器层:格式名、总时长、总码率、元数据
ffprobe -v error -show_format input.mp4

# 流层:每一条流的编码参数
ffprobe -v error -show_streams input.mp4

# 结构化输出,便于脚本解析
ffprobe -v error -print_format json -show_format -show_streams input.mp4
想看的东西 取值方式 常见形态
容器格式 format.format_name mov,mp4,m4a,3gp,3g2,mj2
时长 format.duration(秒,字符串) 123.456000
视频编码 streams[].codec_name h264 / hevc / av1
分辨率 width × height 可能被旋转元数据翻转
帧率 r_frame_rate / avg_frame_rate 30000/1001 这类分数
像素格式 pix_fmt yuv420p / yuvj420p
# 只问视频流的几个关键字段,输出稳定,适合塞进脚本
ffprobe -v error -select_streams v:0 \
  -show_entries stream=codec_name,width,height,r_frame_rate,pix_fmt \
  -of csv=p=0 input.mp4

# 看旋转元数据:手机竖拍视频几乎都有
ffprobe -v error -select_streams v:0 -show_entries side_data=rotation -of default=nw=1 in.mp4

帧率是分数:30000/1001 约等于 29.97;r_frame_rate 是名义值,avg_frame_rate 是实际平均,可变帧率素材上两者不一致。rotation 出现在 side_data 里,常见 -90、90、180——播放器读到它会把画面转过来,但像素本身没转;一旦这个标记没被写进输出,画面就"躺"下来了。


3. 常用操作:八条命令覆盖八成需求

# 1) 转码:H.264 + CRF 质量模式 + yuv420p 保证兼容
ffmpeg -i in.mp4 -c:v libx264 -crf 23 -preset medium -pix_fmt yuv420p -c:a aac -b:a 128k out.mp4

# 2) 截取片段:-ss 在 -i 前为快速定位
ffmpeg -ss 00:01:30 -i in.mp4 -t 20 -c copy clip.mp4

# 3) 抽音轨,不重编码
ffmpeg -i in.mp4 -map 0:a:0 -c:a copy out.m4a

# 4) 抽帧:每秒一张
ffmpeg -i in.mp4 -vf fps=1 frames/frame-%04d.jpg

# 5) 缩放与裁剪
ffmpeg -i in.mp4 -vf "scale=1280:-2" out.mp4
ffmpeg -i in.mp4 -vf "crop=640:480:100:50" out.mp4

# 6) 压字幕:软字幕(可关)与硬字幕(烧进画面)
ffmpeg -i in.mp4 -i sub.srt -map 0 -map 1 -c copy -c:s mov_text soft.mp4
ffmpeg -i in.mp4 -vf "subtitles=sub.srt" -c:a copy hard.mp4

# 7) 拼接:先统一格式,再走 concat 解复用器
printf "file 'a.mp4'\nfile 'b.mp4'\n" > list.txt
ffmpeg -f concat -safe 0 -i list.txt -c copy joined.mp4

需求 关键参数 备注
压字幕(可开关) -c:s mov_text mp4 容器只认 mov_text
压字幕(烧画面) -vf "subtitles=file.srt" 必须重编码视频
抽帧 -vf fps=1 也可 -vf select='eq(n\,100)' 抽第 100 帧
缩放 scale=1280:-2 -2 自动算高且保证偶数,-1 不保证
拼接 concat 解复用器 各段编码参数须一致,否则先统一转码
只换容器 -c copy 最快,但受关键帧与编码兼容性限制

4. 转码三要素:编码器、码率控制、分辨率

要素 可选值 影响
编码器 libx264、libx265、libsvtav1、硬件编码器 兼容性 vs 体积 vs 速度
码率控制 -crf、-b:v、-qp 决定文件多大、质量稳不稳
分辨率 scale= 像素量按面积变化,是体积的第一杠杆

-crf 是质量优先模式:值越小画质越好、文件越大,常用区间 18–28。它的问题是码率随内容浮动,动作场面可能飙高、静止画面又低得离谱;要落进固定码率约束就得配 -maxrate / -bufsize。

-preset 控制编码速度与压缩效率的取舍,ultrafast → veryslow 依次变慢、压缩率变高,默认 medium;同一 -crf 下换 preset 画质基本不变、体积变化。它只对软件编码器有意义。


5. -c copy:无损、极快,但有硬约束

# 换容器不重编码
ffmpeg -i in.mkv -c copy out.mp4

# 截取时保持无损
ffmpeg -ss 00:05:00 -i in.mp4 -to 00:05:30 -c copy clip.mp4
约束 表现 处理
切点必须落在关键帧 -c copy 截取会吸附到前一个关键帧,可能提前几秒 去掉 -c copy 重编码截取
编码与容器兼容性 H.265 塞进老设备只认的 mp4,播放器黑屏 换编码器重编码
字幕格式 mov_text 以外的字幕塞 mp4 失败 先转格式或换 mkv
元数据丢失 旋转标记、章节可能不落袋 需要时显式处理或先转正像素
无法应用滤镜 任何 -vf / -af 都要重编码 去掉 -c copy

切点偏移不是 bug,是容器结构的必然:非关键帧必须依赖前面的关键帧才能解码,无损复制不能凭空造出关键帧。


6. 批量处理脚本:命名规范 + 失败跳过

#!/usr/bin/env bash
set -uo pipefail
SRC_DIR="input"; DST_DIR="output"; LOG="$DST_DIR/batch.log"
mkdir -p "$DST_DIR"; ok=0; fail=0

for f in "$SRC_DIR"/*.mp4; do
  [ -e "$f" ] || continue                       # 目录为空时跳过
  base=$(basename "$f" .mp4)
  out="$DST_DIR/${base}.h264.crf23.mp4"
  [ -s "$out" ] && { echo "skip $out" >> "$LOG"; continue; }

  tmp="$out.part"
  if ffmpeg -hide_banner -loglevel error -nostdin -y -i "$f" \
       -c:v libx264 -crf 23 -preset medium -pix_fmt yuv420p \
       -c:a aac -b:a 128k \
       "$tmp" >> "$LOG" 2>&1; then
    mv -f "$tmp" "$out"; ok=$((ok+1))
  else
    rm -f "$tmp"; fail=$((fail+1)); echo "FAIL $f" >> "$LOG"
  fi
done
echo "done ok=$ok fail=$fail" >> "$LOG"

要点:-nostdin 阻止 ffmpeg 抢标准输入(循环里没有它,任务偶尔会卡住);先判断输出非空可断点续跑;失败时删掉半成品,否则下次会被"已存在"逻辑跳过;输出先写 .part 再 mv,保证磁盘上不出现半截文件。命名建议带"来源 + 处理特征",如 原始名.h264.crf23.1280.mp4,日后能直接看出是哪个参数生成的。

 'ffmpeg -hide_banner -loglevel error -nostdin -y -i "$1" -c:v libx264 -crf 23 -c:a aac \

7. 性能:硬件加速的有无与并发度

# 能力自检:本机 ffmpeg 编入了哪些编码器与硬件加速
ffmpeg -hide_banner -encoders | grep -Ei 'nvenc|vaapi|qsv|videotoolbox'
ffmpeg -hide_banner -hwaccels

# 显式指定线程数
ffmpeg -i in.mp4 -c:v libx264 -threads 8 -crf 23 out.mp4
维度 观察 处理方向
软件编码 默认只吃一部分核心,libx265 明显更慢 调 -threads,或用更快的 -preset
硬件编码 h264_nvenc、h264_vaapi、h264_videotoolbox 等 用上面的自检命令确认是否编入
解码瓶颈 高分辨率解码同样吃 CPU 配 -hwaccel cuda / -hwaccel vaapi 卸载
并发度 单进程未必吃满核 多进程并行,进程数控制在核数 1–2 倍内

硬件加速的收益取决于素材、驱动与编码器实现,需在本机用真实素材对比、按当前版本核对,不要照搬别处结论。


8. 排错:六个高频现场

现象 原因 处理
截取起点比预期早 -c copy 只能从关键帧开始 去掉 -c copy 重编码,或把 -ss 放到 -i 之后精确解码定位
转码后画面躺倒 旋转元数据未保留 用 transpose 滤镜把像素转正,或显式重建 rotation 元数据
字幕变方块或乱码 字幕文件非 UTF-8,或字体缺失 转成 UTF-8;硬字幕用 subtitles=sub.srt:force_style='FontName=字体'
拼接后音画不同步 各段参数不一致 先统一编码、分辨率、帧率、采样率再 concat
脚本执行到某个文件卡住 默认不覆盖输出,等待交互确认 加 -y,并配 -nostdin

9. 边界:什么场景该用剪辑软件

场景 更适合 原因
逐帧精确剪点、多轨混音 剪辑软件 关键帧约束与滤镜链表达力有限
批量、无人值守、参数固定 ffmpeg 可脚本化、可复现
抽帧、切封面、生成预览图 ffmpeg 一行命令,无需 GUI

一句话:人要盯画面就用剪辑软件,机器要反复跑就用 ffmpeg。


10. 自测

  1. -ss 写在 -i 前面和后面,行为差别在哪?
  2. 一个 mp4 有两条音轨,用 -c copy 转 mkv 后只剩一条,最可能是什么原因?
  3. r_frame_rate 显示 30000/1001,实际帧率是多少?
  4. 用 -c copy 截取 30 秒片段,为什么实际时长常常不等于 30 秒?
  5. 批量脚本里为什么建议加 -nostdin?
  6. 手机竖拍视频转码后画面躺倒,该从哪个字段入手排查?
S27-X06 · ffmpeg 与 ffprobe:媒体处理的命令行基础
https://www.clxhxhhr.top/posts/5779/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。