多媒体格式与 FFmpeg 完全指南:从「容器 vs 编码」到命令行实战

前言

这篇文章想解决三个一直纠缠在一起的问题:

第一,格式太多,记不住也不理解。mp4、mkv、avi、mov、webm 是什么关系?h264、hevc、av1 又是什么?mp3、aac、opus、flac 谁能互相转换、转换会不会掉音质?

第二,手机上录的音频到底是什么格式。为什么 iPhone 分享出来是 .m4a,某些安卓老机型是 .amr,而微信语音文件明明后缀是 .amr 却怎么都打不开?

第三,FFmpeg 到底是个什么东西。为什么所有做音视频的人最后都绕不开它,而它的命令行又让人一看就想关窗口?

我的答案不打算拆成三篇独立文章,因为这三件事其实是同一件事的三个切面:理解了「封装」和「编码」是两层独立的东西,前两个问题就自动解开了;而 FFmpeg 之所以强大,恰恰是因为它把这两层各自做成了可自由组合的独立模块。

本文的格式对比基于公开标准与各平台官方文档,FFmpeg 相关数据写于 2026 年 10 月(当时最新稳定版为 9.0.2)。命令语法在 4.x—9.x 上基本通用,但个别选项在新版本已被移除或改名,我在第七章专门列了这部分差异。所有不确定的参数,都建议用「先查再动手」的方法确认,方法在 6.2 节。


一、最重要的一个认知:容器 ≠ 编码

这是整篇文章的地基,也是最多人长期搞混的一件事。一旦想通,后面所有格式名词都会各归其位。

1.1 两层各自独立的东西

编码(Codec,编解码器) 解决的是「原始数据怎么压缩」。一段 3 秒的 1080p 视频,不压缩的话是 1920×1080×3 字节/像素×30 帧/秒×3 秒 ≈ 1.1 GB;一段 1 分钟的 44.1kHz/16bit/立体声音频是 44100×2×2×60 ≈ 10 MB。这种体积没法存储和传输,于是要压缩。压缩算法本身,就是编码。

容器(Container,封装格式) 解决的是「压缩好的数据怎么装箱」。编码产出的是两条(或更多)互不知情的数据流——视频流、音频流、字幕流。容器负责规定:这些流按什么结构写进同一个文件、时间戳怎么对齐、元数据(标题、时长、轨道语言)放在哪、播放器怎么随机跳转到第 3 分钟。

所以 .mp4 不是「一种视频」,它是一种包装盒的规格;.h264 也不是「一种视频」,它是里面那件商品的型号。

1.2 一个类比

把视频文件想成一份快递:

概念 类比 例子
编码(Codec) 商品本身 H.264、H.265、AV1、AAC、MP3、Opus
容器(Container) 包装箱 MP4、MKV、AVI、MOV、WebM、FLV、TS
文件后缀 箱子上贴的标签 .mp4、.mkv
码率 / 分辨率 / 采样率 商品的规格参数 5 Mbps、1920×1080、48 kHz
元数据 快递单信息 标题、作者、时长、轨道语言

标签是可以贴错的。一个后缀写着 .mp4 的文件,里面装的可能是 H.265 编码的视频;一个 .avi 里也可能塞着 H.264。很多「这个视频下载下来后缀不对,改一下就能播了」的玄学经验,本质就是标签错了而箱子没错,或者更常见——箱子和标签都不匹配,改后缀纯粹是自欺欺人。

1.3 用 ffprobe 拿到「身份证」

不要相信文件名,要看文件里的流信息。这是 FFmpeg 自带的一个小程序:

1
2
3
ffprobe -v error -show_entries stream=index,codec_type,codec_name,\
width,height,r_frame_rate,sample_rate,channels,bit_rate \
-show_entries format=format_name,duration,bit_rate -of default=nw=1 input.mp4

输出大致长这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[STREAM]
index=0
codec_name=h264 ← 真正的视频编码
codec_type=video
width=1920
height=1080
r_frame_rate=30000/1001
bit_rate=4823104
[/STREAM]
[STREAM]
index=1
codec_name=aac ← 真正的音频编码
codec_type=audio
sample_rate=48000 ← 采样率
channels=2 ← 声道数
[/STREAM]
[STREAM]
index=2
codec_name=subrip ← 还有一条字幕流
codec_type=subtitle
[/STREAM]
[FORMAT]
format_name=mov,mp4,m4a,3gp,ism ← 容器:属于 ISO Base Media 这一族
duration=125.483000
[/FORMAT]

看懂这个输出,你就比 90% 的人更理解什么叫「一个视频文件」了。

顺带一个细节:format_name 里为什么一口气列了一串 mov,mp4,m4a,3gp?因为这几种容器同属 ISO Base Media File Format(ISO/IEC 14496-12) 这一族,只是各自做了裁剪和扩展。MOV 是苹果的原型,MP4、3GP、M4A 都是它的后代。所以你会看到 iPhone 的录音是 .m4a——那本质上就是一个「只装音频的 MP4 盒子」。

1.4 还有一个层次:裸流

在「编码」和「容器」之下,还有最原始的一层:裸流(Elementary Stream / Raw Data),就是只有编码数据、没有任何容器结构的东西。

类型 典型后缀 说明
视频裸流 .h264、.hevc、.av1, .ivf 有编码但没有时间戳索引,通常无法定位、时长不明
压缩音频流 .mp3、.aac(ADTS)、.opus 帧头自带同步字,可以顺序播,但元数据能力弱
未压缩原始数据 .yuv、.pcm、.raw 完全没压缩,体积巨大,只在算法调试时出现
带容器的 .wav、.flac、.m4a 前面三种加上一个极简容器

.mp3 是个特例,它既是编码名也是容器名(MPEG Audio Layer III 帧 + ID3 标签)。而 .wav 严格说是「PCM 编码 + RIFF 容器」——这就是为什么 FFmpeg 里 pcm_s16le 是编码器名、wav 是格式名,两者是分开的两个参数。


二、常见多媒体格式全景图

这一节是「字典」,不需要一次读完,建议当查阅表收藏。

2.1 视频封装格式

格式 全称/归属 常见编码 主要能力 典型场景 一句话点评
MP4 MPEG-4 Part 14(ISO BMFF) H.264/H.265/AV1 + AAC 多轨、章节、元数据 全网通吃的默认选择 兼容性之王,扩展受限
MKV Matroska(开源标准) 几乎任意 无限多轨、内封字幕/章节、可纠错 高清片源、归档 万能工具箱,但浏览器不原生支持
WebM Google 主导(Matroska 子集) VP8/VP9/AV1 + Vorbis/Opus 免专利、为流媒体优化 YouTube、Web 前端 与 AV1/VP9 绑定的未来派
AVI 微软,1992 任意(老编码为主) 结构简陋、无时间戳索引规范 老资源、采集卡 上古遗物,文件大且易损坏不可修复
MOV 苹果 QuickTime ProRes/H.264 + PCM/AAC 多轨、专业编解码 剪辑工程中间格式 ProRes 的最佳载体
FLV Adobe Flash H.264 + AAC/MP3 极简、适合追加 老直播推流 随 Flash 一起退了场
TS / M2TS MPEG Transport Stream H.264/H.265 + AAC/AC-3 抗丢包、可无限拼接、无索引 数字电视、HLS、蓝光 直播与广播的底色
RM/RMVB RealNetworks RealVideo 私有 2000 年代下载站 已死,只有考古价值
WMV/ASF 微软 VC-1/WMV DRM 友好 早期 Windows Media 已边缘化
3GP 3GPP(ISO BMFF 子集) H.263/H.264 + AMR/AAC 为功能机省空间 彩信、老手机录像 安卓通话录音的老家
GIF CompuServe,1987 LZW + 调色板 256 色、帧延迟、循环 表情包 不是视频,是带调色板的图像序列

选择建议:做产品分发一律 MP4;做本地高清归档用 MKV;做 Web 免版权方案用 WebM+AV1;做 HLS 直播切片用 TS/fMP4。

2.2 视频编码标准

编码这一层的命名规则是「标准组织定的规范名」+「具体实现(编码器)名」。H.264 是规范,x264 是 FFmpeg 里最常用的开源实现,NVENC 是显卡硬件实现。同一条规范,不同编码器压出来的质量和速度能差很多。

规范 别名 出现 相对压缩率 授权 常见编码器 现状
H.262 MPEG-2 Part 2 1995 基准 1.0× 专利池 mpeg2video DVD、有线电视
H.264 AVC 2003 ≈ 0.5× 专利池 x264、NVENC、VideoToolbox 绝对主流,硬件全覆盖
MPEG-4 SP ASP/DivX/Xvid 1999 ≈ 0.6× 专利池 libxvid 老片源
VP8 / VP9 — 2010 / 2013 ≈ 0.5× / 0.35× 免版税 libvpx-vp9 WebM、YouTube
H.265 HEVC 2013 ≈ 0.35× 多个专利池叠加 x265、硬件 4K/HDR 主力,专利费是最大争议
H.266 VVC 2020 ≈ 0.25× 专利池,费率仍高 VVenC 落地慢,硬件支持稀少
AV1 — 2018 ≈ 0.3× 免版税 SVT-AV1、aomenc 联盟推动,趋势所在
AV2 — 制定中 目标再降 30% 免版税 — 尚不可用于生产
ProRes / DNxHD — 2005 / 2004 几乎不压(帧内) 商用授权 专业剪辑 中间码流,换编辑性能
FFV1 / DNxHR — — 无损 开源 内置 档案保存、质检
Motion JPEG MJPEG — 帧内独立 JPEG 免 内置 监控摄像头、采集卡

为什么新标准能一直「压得更小」? 因为压缩的本质是消除冗余,而消除冗余的手段一直在变强:

  • 帧内预测:一张图里相邻像素高度相关,于是只记残差。变换(DCT/ADST)+ 量化 + 熵编码(CABAC)是主力。量化是唯一「真正丢信息」的环节,也是质量的总阀门。
  • 帧间预测:相邻帧 90% 内容一样,于是参考前面的帧,只记「这块往左移了 3 个像素」。这就是运动估计/运动补偿。新标准的运动划分越来越细(H.264 的 16×16 块 → HEVC 的 64×64 CTU + 四叉树 → VVC 的多类型树),是压缩率提升的主要来源。
  • 环路滤波与后续工具:去块滤波、SAO、ALF、仿射运动、GPM 等等,一项项把残差继续磨小。

于是就有了 I / P / B 帧和 GOP 的概念,这个必须懂,因为它直接决定剪切精度:

帧类型 内容 独立可解码 体积
I 帧(关键帧) 完整一帧图像 ✅ 最大
P 帧 参考前方帧的差值 ❌ 中
B 帧 参考前后帧的差值 ❌ 最小

GOP(Group of Pictures) = 两个 I 帧之间的帧数。GOP 越长压缩率越高,但可定位点越稀疏。这解释了 FFmpeg 里两个反复出现的现象:-c copy 剪切只能落在 I 帧上(不然后面的 P 帧找不到参考);直播低延迟要把 GOP 设短(-g 48)而点播存 archive 可以把 GOP 拉长换码率。

2.3 图像与「非音视频」多媒体

多媒体不等于影音。以下几类在工程里也常需要 FFmpeg 处理:

格式 特性 用在哪
PNG 无损、支持 alpha UI、截图
JPEG 有损、色彩管理复杂 照片
WebP 有损+无损、支持动画 网页(FFmpeg 9.0 起原生解动画 WebP)
AVIF 基于 AV1,压缩最强 新网页
HEIC 基于 HEVC,苹果默认照片格式 iPhone 相册
BMP/TIFF 简单/专业 中间产物、印刷
SVG 矢量文本,不是位图,FFmpeg 不解 图标

顺带澄清一个高频误解:SVG 不是「图片格式」的同级概念,它是 XML 描述的几何图形;而 GIF 也不是视频。同理 MIDI 不是录音,是乐谱。

2.4 音频:先懂三个参数,再看格式

音频比视频简单,因为声音只有一个维度。决定未压缩数据量的只有三件事:

1
2
3
每秒字节数 = 采样率 × 位深 ÷ 8 × 声道数
44100 Hz × 16 bit ÷ 8 × 2 = 176400 B/s ≈ 1411 kbps ← 这就是 CD 音质的原始码率
16000 Hz × 16 bit ÷ 8 × 1 = 32000 B/s ≈ 256 kbps ← 语音识别的标准输入
  • 采样率:每秒采多少个点。44.1k(CD)、48k(视频工业标准)、16k(语音/ASR)、8k(电话)。按奈奎斯特定理,能记录的最高频率是采样率的一半,所以 8k 电话音质天然录不下 4kHz 以上的声音——这也是电话里人声「闷」的根本原因。
  • 位深:每个点用几个 bit 量化幅度。16 bit ≈ 96 dB 动态范围,24 bit ≈ 144 dB(实际受噪声限制)。人耳听不出区别,但混音时留 headroom 需要它。
  • 声道:单声道(mono)、立体声(stereo)、5.1、7.1,以及基于对象的 Atmos / DTS:X。语音场景立体声是纯浪费——多一倍体积,还多一倍 ASR 前处理麻烦。
编码 类型 典型码率 延迟 主要战场 备注
PCM 无压缩 700–1400 kbps 极低 中间处理、WAV 体积大
MP3 有损 128–320 kbps 高(约 50 ms+) 音乐分发、老设备 1990s 之王,专利已过期,编码慢
AAC 有损 96–256 kbps 中 苹果生态、流媒体、手机录音 MP3 的官方继任者,同码率明显更好
HE-AAC 有损 24–64 kbps 中 广播、低带宽 AAC + SBR/PS 参数立体声
Opus 有损 6–510 kbps 极低 WebRTC、语音通话、游戏 IETF 标准、免版税、一个格式通吃语音和音乐
Vorbis 有损 80–300 kbps 中 Ogg、游戏音效 免版税,被 Opus 抢了位置
FLAC 无损 500–900 kbps 中 音乐存档 压缩比约 50–60%,工业标准
ALAC 无损 类似 FLAC 中 苹果生态 语音备忘录「无损」档就是它
APE/WavPack/TAK 无损 — — 发烧友 小众
AMR-NB/WB 有损(语音专用) 4.75–12.2 / 6.6–23.85 kbps 极低 2G/3G 通话 只为人声优化,放音乐效果灾难
G.711 / G.722 有损(电话) 64 / 48–64 kbps 极低 VoIP、PSTN 电话网的通用语言
AC-3 / E-AC-3 有损多声道 384–640 / 可变 — DVD / 蓝光 杜比数字
DTS / TrueHD / Atmos 多声道 高 — 影碟 环绕声
WMA 有损/无损 — — 老 Windows 边缘化
MIDI 不是录音 极小 — 电子乐谱 存的是「哪个键按多久」
Silk v3 有损(语音) 2.4–40 kbps 自适应 < 20 ms 微信/QQ 语音 专有封装,下一章细说

一句话原则:无损(FLAC/ALAC/WAV)用于存档和再处理;有损(AAC/Opus/MP3)用于分发;超低码率实时语音(Opus/AMR/Silk)用于通话。有损格式之间的互相转换不可逆——把 MP3 转成 FLAC 不会得到无损音质,只是把已经丢掉的东西用更大的盒子重新装一遍,体积翻几倍而信息量为零。这是格式转换里最常见的一种徒劳。


三、手机录出来的音频,到底是什么格式

现在回答第二个问题。手机里其实有三套完全独立的录音链路,用的是三种不同的编码,这是很多人困惑的根源。

3.1 第一套:自带「录音机 / 语音备忘录」App

iPhone(语音备忘录):默认是 AAC 编码、封装在 .m4a(ISO BMFF 容器)里。苹果开放了一个音质开关,路径是 设置 → 语音备忘录 → 音频质量:

  • 已压缩:AAC,体积小,日常够用(默认档)
  • 无损:ALAC(Apple Lossless),仍然写成 .m4a 容器,但里面的编码从 AAC 换成了 ALAC

注意两点:一是这个设置只影响之后的录音,已经录完的文件不可能「逆向变无损」;二是无损档体积会明显变大,长录音涨得很快。想拿到原始文件,用录音界面 ... 菜单的「存储到文件」,或 AirDrop;不要走微信传,会被二次压缩。iPhone 上还有「增强录音」降噪开关,本质是软件层的波束成形与降噪,不改变落盘格式。

Android:情况复杂得多,因为原生录音 App 由各厂商自己实现。

Android 官方框架层(MediaRecorder)保证可用的编码器是:AAC-LC(写成 .m4a/.mp4/.3gp)、AMR-NB / AMR-WB(写成 .3gp/.amr)、PCM/WAV(.wav,Android 4.1 起可编码)、FLAC(仅解码)。有个耐人寻味的空缺:Android SDK 长期不内置 MP3 编码器(专利授权原因),只内置解码器。所以在安卓上想录 MP3,往往要靠厂商私有 codec 或第三方 App 里自带的 LAME。

落到实际机型上,各家录音 App 的默认输出大致是:

品牌/系统 常见默认落盘 典型参数
iPhone 语音备忘录 .m4a(AAC),可切 .m4a(ALAC) 44.1 kHz
小米 / Redmi .m4a 或 .aac 48 kHz,录音机可选「高品质」
华为 / 荣耀 .m4a(较老机型有 .amr) 48 kHz
vivo / iQOO / OPPO .m4a 48 kHz
三星 .m4a 48 kHz
原生 AOSP / 部分工程机 .3gp(AMR) 8k 或 16k
第三方录音 App(RecForge、Ferrite 等) .wav / .flac / .ogg(Opus) 自选 可自定义

规律很清楚:近几年的安卓和 iOS 都收敛到了 .m4a 容器 + AAC 编码。所以你现在拿起手机录一段音,最可能拿到的就是 .m4a。但「最可能」不等于「一定」——跨机型时永远不要假设,先 ffprobe 一下。这一条我在实际项目里吃过亏:同一批采集数据里混着 .m4a(AAC 48k)、.wav(PCM 16k) 和 .amr(AMR-NB 8k),用一条固定参数的命令批处理,结果一部分文件被上采样、一部分被下采样,喂进 ASR 后准确率莫名掉了一截。

3.2 第二套:视频录像的伴音

手机拍视频时,音频走的是 CamcorderProfile 规定的路径,Android 官方推荐档位就是 AAC-LC:低画质档单声道 24 kbps,标准画质立体声 128 kbps,720p 立体声 192 kbps,采样率 8–48 kHz。这也是 MP4 里最常见 h264 + aac 组合的来源。

顺带一个对做 Web 端播放器有用的规范约束:3GP/MP4 做流式播放时,moov box 必须排在 mdat 之前。这一条正是第六章 -movflags +faststart 的由来。

3.3 第三套:IM 语音消息——完全不同的一层

这是最大的坑。微信、QQ 的语音消息不是标准 AMR,也不是 AAC。它们底层用的是从 Skype 开源出去的 Silk v3 编码器(后来与 CELT 合并成 Opus,但 v3 版本仍在用)。

关键特征是:后缀是伪装出来的。微信语音文件常以 .amr、.aud 结尾,QQ 语音是 .slk,但文件头写着真话:

1
2
微信语音文件头:  #!SILK_V3           (9 字节)
QQ 语音文件头: 0x02 #!SILK_V3 (10 字节)

Silk 的压缩思路很「针对人声」:不记波形,而是记「账」——用线性预测(LPC)描述声道这个滤波器,用长时预测(LTP)记录基音周期,只对预测误差做噪声整形量化(NSQ)再算术编码。代价是极低码率(自适应 2.4–40 kbps)和极低延迟(< 20 ms),收益是弱网通话不断音;但代价二是FFmpeg 官方构建并不内置 Silk v3 解码器,所以任何常规播放器、剪辑软件、转码服务对着这批文件全线阵亡,必须先用 Silk SDK 解成裸 PCM(s16le、默认 24 kHz、单声道)再交给 FFmpeg。社区有 silk-v3-decoder 这类项目专门补这段路。

所以:如果你要做「语音文件批量转写」这类需求,第一步不是写 FFmpeg 命令,而是先确认文件头到底是不是标准编码。 一个 head -c 16 文件名 | xxd 能省掉两小时。

3.4 为什么手机偏爱 AAC 而不是 MP3

四个理由,都不是「厂商随便选的」:

  1. 同码率音质明显更好。MP3 的滤波器组和码位分配是 1990 年代初的产物,在低码率下容易出现「水声」伪影;AAC 允许每帧独立选择立体声/联合立体声、精度分级。
  2. 可伸缩性。AAC 家族有 LC、HE-AAC(v1,加 SBR 高频重建)、HE-AACv2(再加 PS 参数立体声)、ELD(低延迟),同一个标准能从 16 kbps 电话质量覆盖到 320 kbps 高质量。
  3. 延迟与硬件原生支持。手机 SoC 里都有 AAC 硬编解码模块,功耗极低,这是续航敏感的移动设备必需的。
  4. MP3 编码器在安卓上因专利问题长期缺位,而 AAC 有官方支持。

3.5 录音参数该怎么选(工程视角)

目的 采样率 声道 编码 理由
语音转文字(ASR) 16 kHz 单声道 16 bit PCM 或 AAC 128k 绝大多数 ASR 模型内部就是 16k;给 48k 只会白算一遍重采样
会议/采访留档 48 kHz 单声道或立体声 FLAC 或 AAC ≥160k 留重处理余地
音乐 / 歌唱 48 kHz 或更高 立体声 无损(WAV/FLAC) 后期混音需要 headroom
网络分发 / 前端播放 44.1k 或 48k 立体声 AAC 128–192k 或 Opus 96k 兼容性与体积平衡
实时通话 16–24 kHz 单声道 Opus / Silk 低延迟优先
手机随手录 默认 默认 默认(AAC/.m4a) 后续用 FFmpeg 统一归一化即可

这里有一个反直觉但很重要的实践建议:如果你要在手机上录音然后再处理,不要为了「音质」去开无损档,而要为「下游用途」去选格式。给 ASR 用的一路 48 kHz 立体声 ALAC,转写准确率不会比 16 kHz 单声道 AAC 高一分一毫,文件却大十几倍;而真正影响识别效果的,往往是麦克风格栅被手指挡住、房间混响、以及离嘴太远——这些是格式完全救不了的。


四、FFmpeg 是什么:What

4.1 一句话定义与出身

FFmpeg 是一个免费开源、跨平台的音视频处理框架,以及基于这个框架构建的一整套命令行工具。

它由法国程序员 Fabrice Bellard(用的化名是 Gérard Lantau)在 2000 年发起,首次发布 2000 年 12 月 20 日,最早的目标是写一个能对抗昂贵商业编码器的开源 MPEG-4 编码器。名字里的「FF」是 Fast Forward(快进),是对 MPEG 标准组织开的一个玩笑——FFmpeg = 快进版的 MPEG。它的 logo 是一段之字形扫描图案,恰好是 MPEG 熵编码读取 DCT 系数的顺序,属于典型的工程师式冷幽默。

项目与 MPlayer 社区血缘极深,很多开发者同时是两边成员。2004 年起由 Michael Niedermayer 主导。2011 年 3 月因治理分歧,一部分开发者分裂出去另建了 Libav 项目(Debian 一度把命令改名为 avconv),后来 Libav 已停止活跃开发,FFmpeg 重新统一了这条线——这也是今天你看到的 ffmpeg 命令而不是 avconv 的原因。协议上是 LGPL 2.1+,如果编译时链入 x264 等 GPL 组件则整体变为 GPL。

写本文时的最新版本是 9.0「Lei」(2026 年 8 月 4 日发布,补丁版 9.0.2)。这个代号破例没有用科学家名字(此前依次是 al-Khwarizmi、Ada、Lorentz、Riemann、Von Neumann、Dijkstra、Péter、Huffman、Hoare),而是纪念中国传媒大学的博士生雷霄骅——他在 2013–2016 年间用中文博客逐行拆解 FFmpeg 源码、写了三百多篇带完整可编译示例的文章,帮助了整整一代中文音视频工程师入门,2016 年因过劳猝逝,年仅 26 岁。提案里那句话被社区反复引用:

传播知识的人,和创造知识的人一样值得被记住。

9.0 本身也是二十年来最大的一次地基重构:七个库的 ABI 全部提升主版本号(libavcodec 到 63),swscale 被彻底重写为「算子分解 + 后端编译」架构(新增 x86 SIMD、AArch64 NEON、Vulkan 等五个后端),Vulkan 计算编解码铺开,新增原生动画 WebP 解码,移除了一批积累多年的废弃选项。这些变更会影响老脚本,我列在第七章。

4.2 组成部分:三个可执行程序 + 一个库族

FFmpeg 不是一个单体程序,而是「一个库族 + 它的三个命令行门面」。

组件 作用
ffmpeg 主程序,转码/剪辑/混流/滤镜/采集/推流
ffprobe 只分析不输出媒体,查流信息、帧信息、时间戳
ffplay 基于 SDL 的极简播放器,写代码时用来快速验证一条流能不能解
ffserver 曾经的 HTTP 流媒体服务器,因内部 API 维护成本高,2018 年 1 月被移除(替代品是把输出指到 HLS/DASH 切片或用 nginx-rtmp)
库 职责
libavutil 基础件:内存、日志、数学、AVFrame/AVPacket/AVRational 等核心数据结构
libavcodec 心脏:数百种编解码器的实现(含原生 FFV1、Snow),汇编优化,硬件加速接口
libavformat 容器:mux(封装)与 demux(解封装),也管协议层(HTTP/RTMP/HLS/RTP)
libavfilter 滤镜图:缩放、裁剪、水印、变速、降噪、字幕烧录、混音、画面拼接
libswscale 图像缩放 + 像素格式/色彩空间转换(YUV ↔ RGB)
libswresample 音频重采样(48k→16k)+ 采样格式 + 声道布局转换
libavdevice 设备采集:Linux V4L2、Windows DirectShow、macOS AVFoundation
libpostproc 老式视频后处理(去块、去噪声)

理解了这张表,就理解了 FFmpeg 的哲学:每一层都能单独调用。你在命令行里写的每个参数,其实都在指定「用哪个 demuxer、哪个解码器、哪些滤镜、哪个编码器、哪个 muxer」。

4.3 一条数据流水线:记住这个顺序

任何一次转码,内部都是这条链:

1
2
3
4
5
6
7
8
输入文件/流
↓ libavformat demux(拆箱) → 分离出视频包/音频包/字幕包
↓ libavcodec decode(解码) → AVPacket → AVFrame(解压成原始帧)
↓ libswscale / libswresample → 缩放、换像素格式、重采样、改声道
↓ libavfilter filter(处理) → 加水印、裁剪、变速、烧字幕、降噪
↓ libavcodec encode(编码) → AVFrame → AVPacket(压回压缩包)
↓ libavformat mux(封装) → 写进目标容器
输出文件/流

几乎所有 FFmpeg 参数都能挂到这条链的某一环上。这也是为什么它能「用一条命令做无穷多的事」——它是一条可自由配置的流水线,不是一个功能菜单。

4.4 谁在用它

FFmpeg 的地位接近「多媒体领域的 libc」。你几乎一定用过它而不自知:

  • 播放器:VLC、mpv、MPlayer、PotPlayer、KMPlayer 的解码后端
  • 剪辑/创作:HandBrake、Shotcut、Kdenlive、OBS Studio(推流编码)、Blender、GIMP、Audacity
  • 浏览器:Chrome/Firefox 处理部分非专利格式的解码路径
  • 平台服务:YouTube、Bilibili、TikTok 等视频上传后的转码集群,历史上都大量依赖 FFmpeg
  • AI 工具链:OpenAI 的 Whisper 自己不解码音频文件,它调用 FFmpeg 把 mp4/m4a/mp3 先解成 16 kHz 单声道 PCM,再送进模型。moviepy、torchvision、decord、很多 TTS 数据集预处理脚本同样依赖它
  • 手机系统:Android 的媒体框架本身就带 FFmpeg 衍生代码

一句话:FFmpeg 是音视频行业的事实标准基础设施。这也是学它最大的投资回报——你学的不是「一个工具」,而是整个行业的通用中间层。


五、为什么要用 FFmpeg:Why

5.1 如果没有它

真实世界的格式是碎片化的:视频规范十几套、编码标准十几套、容器十几种,再乘以厂商的私有扩展(iPhone 的 HEIC、安卓的 Silk、监控的 H.264 变种)。如果没有统一层,工程师要面对的处境是:

  • 想播一个文件 → 得先判断它是哪种容器套哪种编码,再引对应 SDK
  • 想批量转换 → 每个格式一个工具、一套参数
  • 想跨平台 → Windows/mac/Linux/Android 的媒体 API 完全不同
  • 想省成本 → 商业编码器授权费按路数或按量计

FFmpeg 提供的解法是:把全部复杂度吃进一个库,对外只暴露一个入口、一套抽象。你的程序只需要说「输入这个、输出那个、参数是这样」。

5.2 四条核心价值

① 一个入口通吃所有格式。 一条 ffmpeg -i input -c:v libx264 output.mp4 里,输入侧可以是 mp4/mkv/ts/webm/RTMP 地址/摄像头设备,输出侧可以是任意容器。这个 -i 前后可以装下整个多媒体世界。

② 可编程、可批量、可嵌入。 这是 GUI 工具永远跨不过去的门槛。真实需求往往是「每天早上把 OSS 上新增的 500 个录音全部转成 16k 单声道 WAV 再喂给 ASR」,而不是「把这一个视频转成 MP4」。FFmpeg 是一个进程,能被 subprocess 调用,能挂 cron,能进 CI,能封成 HTTP 服务。

③ 免费开源、跨平台、无部署成本。 单个静态可执行文件,几百行代码的部署难度。Windows/Linux/macOS 命令语法一致。

④ 参数级完全可控。 GUI 工具为了易用,把码率、GOP、preset、色彩空间、声道布局全藏起来了。当你遇到「B 站上传后画面偏色」「手机播放没声音」「同一段视频有人卡有人顺」这类问题时,只有能直接操作这些参数的工具才能定位。

5.3 和其他方案对比

方案 上手难度 批量/自动化 可控性 成本 适合谁
格式工厂、Adaptive、ShanaEncoder(GUI) 极低 ❌ 手动 低 免费/低价 偶尔转一个文件
剪映 / PR / 达芬奇 导出 低 ❌ 中(但藏在菜单里) 订阅/买断 做内容,非做系统
云转码服务(MPS / 阿里云媒体处理) 低 ✅ 中 按量付费,长期贵 不想自建、峰值波动大
专用单功能工具(mkvmerge、HandBrake) 低 部分 中 免费 只做某一件固定事
FFmpeg CLI 高 ✅✅ ✅✅ 全暴露 免费 要处理 100 个文件或要写进程序
FFmpeg 库(C API) 很高 ✅ ✅✅✅ 免费 自研播放器/转码服务

选择标准其实很朴素:

一年转两次视频,别学 FFmpeg,用 GUI。
一周转二十次,学十条命令就够。
要写进代码里、要处理一批文件、要复现别人的一个效果——FFmpeg 是唯一答案。

5.4 要付出的代价

诚实地说清楚缺点,才不会让人学一半就骂:

  1. 学习曲线陡。不是「参数多」的问题,而是它要求你先建立第六章那套「容器/编码/流/时间戳/滤镜图」的心智模型。不懂 GOP 就永远理解不了为什么 -c copy 剪不准。
  2. 参数命名不一致。-c:v、-b:a、-ac、-ar、-vf、-af 是缩写历史堆积,没有统一规律,只能靠查。
  3. 报错信息对新手不友好。Invalid data found when processing input 是最常遇到的一句话,而它可能意味着文件损坏、缺解码器、容器不匹配中的任何一种。
  4. 文档分散且部分过时。官方文档准确但极其干燥;网上教程良莠不齐、大量抄自十年前的版本;很多参数在新版本被弃用(下面第七章)。
  5. 默认构建不一定含所有编码器。精简版可能没有 libmp3lame、libx265、libfdk_aac,报 Unknown encoder 时先想这个,别怀疑语法。

5.5 一个真实的技术判断

FFmpeg 的强大是「通用性」,代价是「它不是所有场景的最优解」。硬件编码器(NVENC/QSV/VideoToolbox)在同等 CPU 占用下能给出的吞吐,与软件 x264 走完全相同参数得到的压缩率之间要权衡;专业调色需要达芬奇而不是滤镜;做非破坏性快剪需要的是剪辑软件。

FFmpeg 的定位是流水线和胶水,不是创作台。 想清楚这一点,就不会拿它做不该它做的事。


六、怎么样:How

这一节从装到能用,最后用一个完整例子把全篇串起来。

6.1 安装

Windows(三种,推荐第一种):

1
2
3
4
5
6
7
8
# ① winget(最简单)
winget install Gyan.FFmpeg

# ② 手动下载 gyan.dev 或 BtbN 的 release 压缩包,解压后把 bin 目录加进 PATH
# 注意:压缩包里的 bin 目录必须整体加进 PATH,因为它依赖同目录下的 dll

# ③ conda(不想污染系统时推荐,我在项目里就是这么用的)
conda install -c conda-forge ffmpeg

macOS / Linux:

1
2
3
brew install ffmpeg          # macOS,会带上 x264/x265/opus/lame 等
sudo apt install ffmpeg # Debian/Ubuntu
sudo apt install ffmpeg libavcodec-extra # 需要更全的编解码支持

Python 项目内(不想让每个部署环境手动装时):

1
pip install imageio-ffmpeg   # 自带一个静态 ffmpeg,可执行文件路径由 API 提供

验证:

1
ffmpeg -version

第一行看版本,重点看下面几行里有没有 --enable-libx264、--enable-libmp3lame、--enable-libopus——这决定了你的构建能用哪些编码器,绝大多数 Unknown encoder 报错都能靠这一行答案解决。

6.2 最重要的能力不是背参数,是会查

四条自查命令,比背 200 条参数有用得多:

1
2
3
4
5
6
7
8
9
10
11
12
13
# ① 这个文件里到底是什么?(先看这个再动手)
ffprobe -hide_banner input.mp4

# ② 支持哪些容器格式?
ffmpeg -hide_banner -formats | grep -i matroska

# ③ 支持哪些编解码器?D=解码 E=编码
ffmpeg -hide_banner -codecs | grep -i opus
# → DE.LS opus (Opus (Open Standard, lossy, low latency))

# ④ 滤镜怎么用?(这一条最被低估)
ffmpeg -hide_banner -h filter=loudnorm
ffmpeg -hide_banner -filters | grep -i fade

遇到任何网上抄来的命令跑不通,第一反应应该是查本地版本的帮助,而不是怀疑自己。-h full 能列出所有选项。

6.3 命令骨架,以及一条铁律

1
ffmpeg [全局选项] [-i 输入1] [选项...] [-i 输入2] [选项...] ... 输出文件

铁律:所有选项只对写在它之后的第一个文件生效。 这是新手第一大坑。

1
2
3
4
5
6
7
8
9
10
11
# ✅ 正确:-ss 在 -i 之前 → 属于「输入侧」选项,先定位再解码,又快又准
ffmpeg -ss 00:01:00 -i in.mp4 out.mp4

# ⚠️ 也合法但语义不同:-ss 在 -i 之后 → 属于「输出侧」选项,
# 会把前 60 秒解码出来再丢掉,慢
ffmpeg -i in.mp4 -ss 00:01:00 out.mp4

# ❌ 常见错误:想给两个输入分别设参数,但位置写错导致参数串味
ffmpeg -i a.mp4 -ss 10 -i b.mp4 out.mp4
# 这里 -ss 10 作用于 a.mp4 还是 b.mp4?它属于紧随其后的输入之前的位置,
# 实际绑定的是 a.mp4 之后、b.mp4 之前 → 归 a.mp4。想给 b 设就得重排顺序。

6.4 高频参数速查表

参数 含义 备注
-i 输入 可以有多个,之后用 0:、1: 索引
-c copy 不重编码,直接搬运流 改封装专用,秒级完成,质量零损失
-c:v libx264 / -c:a aac 指定视频/音频编码器 :v :a :s 分别选流类型
-crf 23 恒定质量(0–51,默认 23) 越小质量越好、文件越大;23 是 x264 默认,HEVC 默认 28
-preset medium 速度/压缩率权衡 ultrafast → veryslow,越慢压得越小
-b:v 2M / -b:a 128k 目标码率 用于 CBR/2-pass 场景
-r 30 帧率
-s 1280x720 / -vf scale=1280:-2 分辨率 -2 保证是偶数(编码要求)
-ar 16000 音频采样率
-ac 1 声道数 1=单声道,2=立体声
-pix_fmt yuv420p 像素格式 不写这个经常出现「本地能播、别人打不开」
-ss / -t / -to 起始时间 / 时长 / 结束时间 -t 和 -to 别同时用
-vn / -an / -sn 丢弃视频/音频/字幕 提音频用 -vn,静音用 -an
-map 手动选流 -map 0:a:1 取第 1 个输入的第 2 条音频轨
-vf / -af 视频/音频滤镜链 逗号串联
-filter_complex 多输入多输出的滤镜图 需要标签时用
-movflags +faststart 把 moov 挪到文件头 网上可播/手机可播的关键
-metadata title="x" 写元数据
-map_metadata -1 清空元数据 抹掉拍摄设备、GPS 等信息
-y / -n 覆盖 / 拒绝覆盖已存在的输出 默认是交互式询问
-loglevel error -stats 只看错误和进度 输出干净,脚本里必加
-tune film|animation|grain 场景微调 x264/x265 支持

6.5 一个完整的实战例子:手机会议录音的三件产物

场景(很真实,也是我做项目时踩过的):用 iPhone 录了三段会议 .m4a,现在需要三种输出——

  1. meeting.mp3:合并成一个、音量归一化,扔车里/蓝牙耳机听;
  2. meeting_16k.wav:16 kHz 单声道 PCM,喂给 Whisper 做转写;
  3. meeting.png:一张波形图,放进周报里说明「这次会开了多久、有没有大段空白」。

第 0 步:先搞清楚手里是什么。

1
2
for %f in (*.m4a) do @ffprobe -v error -show_entries \
stream=codec_name,sample_rate,channels -show_entries format=duration "%f"

结果:三段都是 aac / 44100 Hz / 2ch。注意它们采样率一致,这是好事——如果第一段是 44.1k、第二段是 48k,就不能用 -c copy 拼接(同一容器里塞两条不同采样率的音频流,播放器会当成同一条处理,结果要么变速要么静音)。这是最容易翻车的地方。

第 1 步:统一参数(保险做法,也顺便省掉后续所有坑)

1
2
3
ffmpeg -y -i m1.m4a -ar 48000 -ac 1 -c:a pcm_s16le u1.wav
ffmpeg -y -i m2.m4a -ar 48000 -ac 1 -c:a pcm_s16le u2.wav
ffmpeg -y -i m3.m4a -ar 48000 -ac 1 -c:a pcm_s16le u3.wav

第 2 步:拼接。两条路:

1
2
3
4
5
6
7
8
9
10
11
# 路线 A:concat demuxer(快,但要求各文件参数完全一致,且用同一种容器)
# 在同一个目录下生成清单文件
for %f in (u1.wav u2.wav u3.wav) do @echo file '%~f'>> list.txt
ffmpeg -y -f concat -safe 0 -i list.txt -c copy joined.wav

# 路线 B:concat filter(更稳,重编码,能容忍参数不同)
ffmpeg -y -i m1.m4a -i m2.m4a -i m3.m4a -filter_complex \
"[0:a]aresample=48000,aformat=sample_fmts=fltp:channel_layouts=mono[a0];\
[1:a]aresample=48000,aformat=sample_fmts=fltp:channel_layouts=mono[a1];\
[2:a]aresample=48000,aformat=sample_fmts=fltp:channel_layouts=mono[a2];\
[a0][a1][a2]concat=n=3:v=0:a=1[a]" -map "[a]" joined.wav

我平时用路线 B 更多——它把「参数归一化」和「拼接」合成一步,不需要中间文件,也不怕清单文件路径写错。路线 A 快在不需要解码重编,几十分钟的录音几秒完成。

第 3 步:产出 ① 可听的 MP3(带响度归一化和淡入淡出)

1
2
3
ffmpeg -y -i joined.wav \
-af "loudnorm=I=-16:TP=-1.5:LRA=11,afade=t=in:st=0:d=1,afade=t=out:st=5390:d=3" \
-ar 44100 -c:a libmp3lame -q:a 4 meeting.mp3

三个要点:

  • loudnorm=I=-16:TP=-1.5:LRA=11 是 EBU R128 响度归一化,I 是综合响度(流媒体常用 -14 到 -16 LUFS)。不做这一步的话,三段录音因为麦克风距离不同,音量会一段响一段轻,听感极差。这是比「提高采样率」重要一百倍的音质改善。
  • -q:a 4 是 VBR 模式,约 165 kbps,语音足够;固定码率写法是 -b:a 192k。
  • afade 的 st=5390 要按实际时长算(这里假设总长 5393 秒)。想要自动,可以先用 ffprobe -v error -show_entries format=duration -of csv=p=0 joined.wav 取值再拼进命令。

第 4 步:产出 ② ASR 输入 WAV

1
ffmpeg -y -i joined.wav -ar 16000 -ac 1 -c:a pcm_s16le -sample_fmt s16 meeting_16k.wav

pcm_s16le 是「16 bit 小端无压缩 PCM」,wav 是它的容器。这是几乎所有 ASR 模型的通用输入格式。顺带做个诊断——找出录音里超过 3 秒的静音段(判断是不是有台设备根本没录上):

1
ffmpeg -i joined.wav -af "silencedetect=noise=-35dB:d=3" -f null - 2>&1 | findstr silence

第 5 步:产出 ③ 波形图

1
2
ffmpeg -y -i joined.wav -filter_complex "showwavespic=s=1600x400:colors=#c8a064" \
-frames:v 1 meeting.png

顺手把整条链封成一个 Python 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import subprocess, shlex

def run(*args: str) -> None:
cmd = ["ffmpeg", "-y", "-hide_banner", "-loglevel", "error", "-stats", *args]
print(" ".join(shlex.quote(c) for c in cmd))
subprocess.run(cmd, check=True)

def phone_m4a_to_products(files, out_prefix):
inputs = sum((["-i", f] for f in files), [])
# 1) 统一并拼接
n = len(files)
chain = ";".join(
f"[{i}:a]aresample=48000,aformat=sample_fmts=fltp:channel_layouts=mono[a{i}]"
for i in range(n)
)
chain += ";" + "".join(f"[a{i}]" for i in range(n)) + f"concat=n={n}:v=0:a=1[a]"
run(*inputs, "-filter_complex", chain, "-map", "[a]", "joined.wav")
# 2) 听得版本
run("-i", "joined.wav", "-af", "loudnorm=I=-16:TP=-1.5:LRA=11",
"-c:a", "libmp3lame", "-q:a", "4", f"{out_prefix}.mp3")
# 3) ASR 版本
run("-i", "joined.wav", "-ar", "16000", "-ac", "1",
"-c:a", "pcm_s16le", f"{out_prefix}_16k.wav")
# 4) 波形图
run("-i", "joined.wav", "-filter_complex", "showwavespic=s=1600x400",
"-frames:v", "1", f"{out_prefix}.png")

一个例子讲完了全篇的核心概念:容器与编码是两层(第 0 步的探测)、参数归一化先于合并(采样率不一致不能 copy)、流选择与滤镜图([0:a]、concat)、响度是比码率更值音质杠杆(loudnorm)、下游用途决定格式(16k/单声道给 ASR)、FFmpeg 可以当可编程的中间件(Python 封装)。

6.6 十余组高频命令

以下按使用频率排序。全部只依赖语法通用性,跑不通先回 6.2 查本地帮助。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# 1|改封装不重编码(最快,零质量损失)
ffmpeg -i in.mkv -c copy out.mp4
ffmpeg -i in.mov -c copy -pix_fmt yuv420p out.mp4 # 修「本地能播别人打不开」

# 2|转码 / 压缩(体积减半的常规操作)
ffmpeg -i in.mp4 -c:v libx264 -crf 24 -preset slow -c:a aac -b:a 128k out.mp4
ffmpeg -i in.mp4 -c:v libx265 -crf 28 -tag:v hvc1 out_hevc.mp4 # hvc1 标签给苹果用
ffmpeg -i in.mp4 -c:v libsvtav1 -crf 34 -b:v 0 out_av1.webm # -b:v 0 让 CRF 生效

# 3|精确剪切(重编码才准)
ffmpeg -ss 00:01:00 -to 00:01:30 -i in.mp4 -c copy clip.mp4 # 快,但切点对齐关键帧
ffmpeg -i in.mp4 -ss 00:01:00 -to 00:01:30 -c:v libx264 clip.mp4 # 帧级精确,慢

# 4|批量切片(做缩略图墙、并行转码、分段上传)
ffmpeg -i in.mp4 -c copy -f segment -segment_time 600 -reset_timestamps 1 seg_%03d.mp4

# 5|音频提取与替换
ffmpeg -i in.mp4 -vn -c:a copy out.m4a # 原样提取
ffmpeg -i in.mp4 -vn -ac 1 -ar 16000 out.wav # 提取 + 给 ASR
ffmpeg -i video.mp4 -i voice.m4a -map 0:v -map 1:a -c copy -shortest out.mp4
ffmpeg -i in.mp4 -map 0:v -map 0:a:1 -c copy only_second_track.mp4

# 6|音量与响度
ffmpeg -i in.wav -af "volume=2.5" louder.wav # 线性放大(会削顶)
ffmpeg -i in.wav -af "loudnorm=dynaudnorm" out.wav # 动态响度
ffmpeg -i in.m4a -af "highpass=f=80,lowpass=f=7000,afftdn=nr=10" clean.wav # 语音去噪

# 7|变速 / 变调
ffmpeg -i in.mp4 -filter_complex "[0:v]setpts=0.5*PTS[v];[0:a]atempo=2.0[a]" \
-map "[v]" -map "[a]" 2x.mp4 # 2 倍速
# 单个 atempo 实例只接受 0.5–2.0,要 4 倍速就串联两个:atempo=2.0,atempo=2.0

# 8|抽帧 / 生成封面 / 做 GIF
ffmpeg -ss 0.5 -i in.mp4 -frames:v 1 -update 1 cover.png # 取某一帧当封面
ffmpeg -i in.mp4 -vf "fps=1" frames/f_%04d.png # 每秒抽一帧
ffmpeg -ss 3 -t 5 -i in.mp4 -vf "fps=12,scale=480:-1:flags=lanczos,\
split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" out.gif # 调色板两步法,质量差别巨大

# 9|加水印 / 文字 / 字幕
ffmpeg -i in.mp4 -i logo.png -filter_complex \
"overlay=W-w-20:H-h-20" -c:a copy out.mp4
# Windows 下 drawtext 必须显式给 fontfile,路径冒号要转义
ffmpeg -i in.mp4 -vf "drawtext=fontfile='C\:/Windows/Fonts/msyh.ttc':\
text='API街溜子':fontsize=36:fontcolor=white@0.6:x=w-tw-30:y=h-th-30" out.mp4
ffmpeg -i in.mp4 -vf "subtitles=sub.srt:force_style='FontSize=22'" hard.mp4 # 烧死
ffmpeg -i in.mp4 -i sub.srt -c copy -c:s mov_text out.mp4 # 软字幕

# 10|让 MP4 能边下边播(Web / 手机必备)
ffmpeg -i in.mp4 -c copy -movflags +faststart out.mp4

# 11|生成测试素材(不用真找视频就能调参)
ffmpeg -f lavfi -i "sine=frequency=440:duration=5" beep.wav
ffmpeg -f lavfi -i "testsrc=size=1280x720:rate=30:duration=10" test.mp4
ffmpeg -f lavfi -i "color=c=black:s=1280x720:r=30:d=10" -f lavfi \
-i "anullsrc=r=48000:cl=stereo" -shortest silent.mp4

# 12|从设备直接采集(录屏 / 摄像头)
ffmpeg -f dshow -i video="Integrated Camera" -f dshow -i audio="麦克风 (Realtek)" out.mp4

# 13|拉流 / 推流
ffmpeg -re -i in.mp4 -c copy -f flv rtmp://server/live/stream
ffmpeg -i rtmp://server/live/stream -c copy -f segment -segment_time 60 out_%03d.ts

# 14|一行批处理(Windows cmd)
for %f in (*.mov) do ffmpeg -i "%f" -c:v libx264 -crf 23 -an "mp4\%~nf.mp4"
# PowerShell
Get-ChildItem *.mov | ForEach-Object { ffmpeg -y -i $_.Name -c:v libx264 -crf 23 -an "mp4\$($_.BaseName).mp4" }
# Bash
find . -name '*.mov' -print0 | xargs -0 -P 4 -I{} ffmpeg -y -i {} -c:v libx264 -crf 23 out.mp4

6.7 两遍编码:需要精确目标体积时才用

CRF 是「质量恒定、体积随缘」;如果目标是「必须压到 50 MB 以内」,就要用固定码率 + 两遍:

1
2
3
4
# 第一遍只统计复杂度,不输出文件(Linux/macOS 用 /dev/null,Windows 用 NUL)
ffmpeg -y -i in.mp4 -c:v libx264 -b:v 1500k -preset slow -pass 1 -an -f mp4 NUL
# 第二遍真正编码
ffmpeg -y -i in.mp4 -c:v libx264 -b:v 1500k -preset slow -pass 2 -c:a aac -b:a 128k out.mp4

不过今天更多人的做法是:-crf 试一次,看输出体积,再按线性关系微调用 -b:v。两遍编码在多数场景性价比已经不高。


七、六个反复咬人的坑

坑 1|输出文件名和输入是同一个 —— 文件会被毁掉。
FFmpeg 打开输出时就 truncate 目标文件,之后再读输入。同名等于自杀,而且边读边写常导致输出损坏。
✅ 对策:永远输出到新文件名或子目录;需要「原地转」就先写临时文件再替换(脚本里 move /y 或用 tmp_ 前缀)。-y 只是覆盖已存在的输出,不会保护同名输入。

坑 2|-c copy 剪切不精确。
不是 bug,是编码原理决定的——只能从 I 帧开始。
✅ 对策:要么接受误差、要么重编码、要么先用 -force_key_frames 在需要的点插入关键帧再 copy。

坑 3|mp4 的 moov 在文件尾部 → Windows 播放器/微信/手机打不开,网页不能边下边播。
这是我印象最深的一个坑:同一份脚本产出的 mp4,本地 ffplay 一切正常,发到 Windows 上就是打不开。原因是默认写法把索引表(moov)写在文件末尾,任何需要 seek 的播放端必须先能读到结尾。
✅ 对策:**所有输出 mp4/mov 一律加 -movflags +faststart**。
✅ 修已存在的文件,无损且秒完:ffmpeg -i bad.mp4 -c copy -movflags +faststart good.mp4。
✅ 顺带说明为什么第六章的音频产物是 .mp3 / .wav——那两类没有 moov 问题,所以「发不出去的 mp4」这一类坑在纯音频工作流里通常以别的形式出现。

坑 4|Unknown encoder 'libx265' / 'libmp3lame'。
不是你写错了,是这个 FFmpeg 构建没编进那个第三方库。
✅ 对策:ffmpeg -hide_banner -codecs | findstr 265 确认;缺就换一个完整构建(gyan.dev 的 -full 版本、conda install -c conda-forge ffmpeg、或自己带 --enable-libx265 编译)。

坑 5|画面没问题但「只有声音没有图像」或「发出去打不开」= 像素格式。
编码器默认可能输出 yuv444p / 10 bit,而绝大多数硬件解码器和播放器只保证 yuv420p 8 bit。
✅ 对策:加 -pix_fmt yuv420p。这是「我电脑能播,别人不能」最常见的两个原因之一(另一个是坑 3)。

坑 6|音频进 ASR 效果差,但明明录音很清晰。
通常是采样率/声道与模型预期不匹配,或者用了 aresample 之外的错误下采样路径(比如直接把 48k 当 16k 用,声音会变慢变调)。
✅ 对策:**显式写 -ar 16000 -ac 1**,并在处理前用 ffprobe 打印真实采样率;不要相信文件名和来源设备的「默认设置」,尤其当数据来源混杂了不同品牌手机。

额外一条|版本导致的「网上教程跑不通」。
FFmpeg 大约每 6 个月一个大版本,每次都会弃用和移除一批东西。9.0(2026 年 8 月)里几个会直接咬老脚本的变化:

  • 移除 CLI 选项 -vsync(改用 -fps_mode),一并移除 -top、-qphist、-filter_complex_script;
  • 默认开始校验 TLS 对端证书——原来连自签证书能跑的拉流脚本会直接失败(临时可加 -tls_verify 0,但更该修证书);
  • 移除独立 CELT 解码器(不影响 Opus)、移除 Sonic 编解码器、移除 OpenMAX 与 libnpp 系滤镜(scale_npp/transpose_npp 等,N 卡改用 transpose_cuda);
  • NVENC 移除旧 preset 别名与遗留码控模式,不再支持 11.1 之前的 SDK;
  • 七个库 ABI 全部升主版本,自己链 libav* 的程序必须重新编译。

官方发布节奏是「主版本约 6 个月一次,中间点版本只修 bug」。生产环境建议锁定一个点版本(如 9.0.2),别追 git master;从源码自己编译的人,官方反而建议直接用 master,因为安全补丁先落在开发分支。想提 PR 的话注意:开发已迁到 code.ffmpeg.org(Forgejo 实例),GitHub 上的 PR 官方明确说会被忽略。


八、一张决策小抄

我要做的事 命令模板
换个容器(不损画质) ffmpeg -i in -c copy -movflags +faststart out.mp4
压小体积、画质优先 ffmpeg -i in -c:v libx264 -crf 24 -preset slow -c:a aac -b:a 128k -movflags +faststart out.mp4
目标更小的体积 同上换 -c:v libx265 -crf 28 -tag:v hvc1,或 -c:v libsvtav1 -crf 34 -b:v 0
剪一段(要精确) ffmpeg -i in -ss 00:01:00 -to 00:01:30 -c:v libx264 out.mp4
剪一段(要快) ffmpeg -ss 00:01:00 -to 00:01:30 -i in -c copy out.mp4
只要音频、给 ASR ffmpeg -i in -vn -ar 16000 -ac 1 -c:a pcm_s16le out.wav
只要音频、给别人听 ffmpeg -i in -vn -af loudnorm=I=-16 -c:a libmp3lame -q:a 4 out.mp3
合成一段音频 ffmpeg -i a -i b -filter_complex "[0:a][1:a]concat=n=2:v=0:a=1[a]" -map "[a]" out.wav
改分辨率 ffmpeg -i in -vf scale=1280:-2 -c:a copy out.mp4
加水印 ffmpeg -i in -i logo.png -filter_complex "overlay=W-w-20:H-h-20" -c:a copy out.mp4
烧字幕 ffmpeg -i in -vf "subtitles=sub.srt" out.mp4
取封面帧 ffmpeg -ss 0.5 -i in -frames:v 1 -update 1 cover.png
查文件真实内容 ffprobe -hide_banner in
查本地版本支持什么 ffmpeg -codecs / -formats / -filters / -h full

结语

回头看这三件事:

多媒体格式不是一堆要背的名字,而是两个正交维度的组合——「怎么压」× 「怎么装」。任何格式名,你只要问出「它是容器还是编码」,位置就确定了。

手机录音之所以让人困惑,是因为手机里有三条独立链路:自带录音 App(AAC/ALAC in M4A)、视频伴音(AAC in MP4)、IM 语音(Silk v3,后缀是假的)。它们的取舍逻辑完全不同——前两条为「听」优化,第三条为「在弱网下实时说清楚一句话」优化,所以不能相互替代。

FFmpeg 之所以值得学,是因为它是唯一一个把这两个维度都完整暴露出来、并且可编程的工具。它的学习曲线陡,陡在需要你先把世界看成「容器 + 编码 + 滤镜 + 时间戳」,而不是看成「一个视频文件」。这个心智模型一旦建立,你获得的不只是会用一条命令,而是能判断「这个视频为什么打不开」这类问题的答案在哪。

而它背后那条人文线索也值得记一笔:一个 26 岁的中国博士生,用三年时间在博客上把 FFmpeg 拆成了三百多篇带可编译示例的中文教程;他去世后第十年,这个项目用他的名字命名了自己的主版本。开源世界对「把墙拆成梯子的人」的回报,就是把它写进版本号里。

动手建议:不要通读这篇就完事。找一段手机录音,跑一遍 ffprobe,看它的真实编码是什么,然后亲手做一遍第 6.5 节的三条输出。这一小时比看十篇教程有用。