2
0
0

02-ffmpeg 转码与剪辑实战:容器、编码、滤镜与画质

2026-10-22
2026-10-22

📝 笔记摘要:ffmpeg 转码与剪辑实战,容器、编码、滤镜与画质

ffmpeg 真正开始干活的命令,参数都能归到五步流水线的某一步上:拆包、解码、滤镜、编码、封装。这一篇按这条线走,把日常最高频的几类活儿拆开——怎么把音视频拆成裸流再装回去、怎么切原始数据、怎么裁剪合并、怎么在图片和视频之间来回转、滤镜到底怎么用、画质参数怎么调。

每一条命令都标了「为什么这么写」。参数记不住的时候,知道它管哪一步,比背命令本身管用。

ffmpeg 系列 共 3 篇,这是第 2 篇
  01 ffmpeg 基础 安装与工作原理
  02-ffmpeg 转码与剪辑实战 容器、编码、滤镜与画质 ← 本篇
  03 ffmpeg 录制与直播 录屏、摄像头与推流

1. 格式分解与复用

容器编码码流三层结构图:第一层容器 mp4 mkv flv ts m3u8 决定文件怎么排布,第二层编码 H.264 H.265 VP9 AV1 AAC 决定画面怎么压缩,第三层码流 elementary stream 是真正的二进制数据;下方代码块展示用 -map 把视频轨和音频轨抽成裸流再用 -f h264 -f aac 装回 mp4 的过程,右侧卡片说明裸流没有索引需要 -bsf:v h264_mp4toannexb 补 Annex B 头,最底部两张卡片对比换容器不损画质和换编码会损画质

「格式分解与复用」这件事,拆开就是三个词:容器、编码、码流。

容器 container mp4、mkv、flv、ts、m3u8
  决定文件怎么排布:几个轨道、每轨多长、字幕挂哪、索引怎么存
  容器本身不决定画质,它更像一个信封
编码 codec H.264、H.265、VP9、AV1、AAC、MP3
  决定画面怎么被压缩、压得多狠
  码流 elementary stream 真正的那串 0 和 1
  抽出来单独存就是 .h264、.aac 这种裸流

1.1 拆:把轨道抽成裸流

# 抽视频轨
ffmpeg -i in.mp4 -map 0:v -c copy video.h264

# 抽音频轨
ffmpeg -i in.mp4 -map 0:a -c copy audio.aac

-map 0:v 里的 0 是第一个输入
  v 选视频轨、a 选音频轨、s 选字幕轨
  不加 -map 时 ffmpeg 会自动挑一条「最佳」轨道
  多轨文件里自动挑经常挑错,要精确控制就得显式写 -map
  -map 0:a? 末尾的 ? 表示「有就选,没有也继续」
-c copy 是拆包不拆编码
  码流原样抽出来,一个字节都没变
  所以画质零损失,过程也几乎不花时间
裸流没有索引
  它不知道该按什么节奏播,也没法拖进度条
  要能 seek,得先补 Annex B 头
  -bsf:v h264_mp4toannexb 把 MP4 的长度前缀格式转成裸流格式

1.2 装:把裸流塞回容器

ffmpeg -f h264 -i video.h264 -f aac -i audio.aac -c copy out.mp4

这就是「复用」的完整含义:拆出来存档,以后要什么容器现套,不用重新压一遍。

-f h264 是告诉 ffmpeg 这个文件是什么格式
  裸流没有扩展名能认,必须手动指定 demuxer
  不指定的话 ffmpeg 只能瞎猜,猜错就报错
-c copy 依然不能省
  不写它就会把裸流重新编码一遍——白拆了
这个玩法的实际价值
  一份素材,多种投放格式:母版拆成裸流存着,上传要 mp4 套一个、直播要 flv 套一个、归档要 ts 套一个
  批量转码省时间:同一条视频要发三个平台,拆一次套三次,比转三遍快得多
  代价是裸流没有索引,不能直接拖进度条

1.3 换容器:只要一行

# mkv 转 mp4,画质分毫不动
ffmpeg -i in.mkv -c copy -movflags +faststart out.mp4

-movflags +faststart 要加上
  它把 mp4 的索引(moov atom)提到文件头
  不加上也能播,但网页上要等整个文件下完才开始播
  加了之后能边下边播,这是发到网站上的必要动作
换容器不会让文件变小
  码流一个字节没动,装进什么容器都那么大
  想变小就得换编码,那是另一条命令
换容器也不会让画质变好
  画质是编码决定的,容器只是个信封

1.4 顺手会用到的几条

# 只要音频,不要画面
ffmpeg -i in.mp4 -vn -c:a copy out.aac

# 换成新音轨,画面不动
ffmpeg -i video.mp4 -i new.mp3 -map 0:v -map 1:a -c copy out.mp4

# 转成 wav,音频不重编码
ffmpeg -i in.mp4 -vn -c:a pcm_s16le out.wav

2. 处理原始文件

裸流不是 ffmpeg 特有的东西,但它是最容易踩坑的一类输入——因为裸流没有头,所有信息都得靠命令行告诉 ffmpeg。

# 裸 YUV 视频:必须说清尺寸、帧率、像素格式
ffmpeg -f rawvideo -pix_fmt yuv420p -s 1920x1080 -r 30 -i raw.yuv -c:v libx264 out.mp4

# 裸 PCM 音频:必须说清采样率、声道数、采样格式
ffmpeg -f s16le -ar 44100 -ac 2 -i audio.pcm -c:a aac out.m4a

# WAV:格式能自识别,不用写这么细
ffmpeg -i in.wav -c:a libmp3lame out.mp3

为什么裸流必须写这么多参数
  mp4 有容器头,里面记着分辨率、帧率、采样率,ffmpeg 读得出来
  裸流就是一片像素,ffmpeg 只能靠你告诉它才能正确解释
  参数写错不会报错,只会出花屏、变花、或者只有一个头的长度
  尺寸写错的后果最直观:-s 1920x1080 写成 1280x720,画面会斜着切
-f s16le 里的 le 是小端
  对应的大端是 -f s16be,搞反了音频会变成一片噪音
从容器导出成裸流
  ffmpeg -i in.mp4 -f rawvideo -pix_fmt yuv420p out.yuv
  没有 -c copy,rawvideo 是「原始」的意思,不是「复制」——它会真的解一遍再存
两路输入对不齐的时候加 -shortest
  视频 10 秒、音频 15 秒,加了它输出按短的走
  不加的话输出取最长的那头,多出来的那几秒是黑的或者没声音

3. 裁剪与合并

3.1 裁剪:-ss 放前放后是两件事

# 快切:跳到 00:01:30 再开始解
ffmpeg -ss 00:01:30 -i in.mp4 -t 10 -c copy out.mp4

# 准切:从头解到 00:01:30 再丢掉前面的
ffmpeg -i in.mp4 -ss 00:01:30 -t 10 -c copy out.mp4

-ss 在 -i 前面:输入端跳转
  ffmpeg 直接跳到那个时间点附近开始解,快
  但起点落在关键帧上——视频每隔几秒才有一个关键帧
  结果可能是 00:01:27 开始而不是 00:01:30,前面多出来几秒
-ss 在 -i 后面:输出端跳转
  从头解一遍,解到那个点再把前面的丢掉,帧精确,慢
  配合 -c copy 时依然会带上多余的关键帧内容,因为关键帧没法从中间切开
要「精确到帧 + 不重编码」,基本做不到
  GOP 就是一组的起点,中间切开必须重编一整组
  务实的做法:
   要快 → 前置 -ss + -c copy,接受几秒误差
   要准 → 前置 -ss 定位到关键帧之前,再重编码裁准,这条最常用
裁完时间戳可能负数,加 -avoid_negative_ts make_zero
  -avoid_negative_ts make_zero 把时间戳整体平移到从 0 开始
  不加以 cue 结尾的 mp4 播出来开头是黑的,很多播放器会跳过前几秒

3.2 合并:两种做法,选一种

做法一:concat demuxer——把文件名写进一个列表文件。

# list.txt
file 'part1.mp4'
file 'part2.mp4'
file 'part3.mp4'
ffmpeg -f concat -safe 0 -i list.txt -c copy out.mp4

要求所有文件的编码参数完全一致
  同样的编码、分辨率、帧率、像素格式、时间基
  参数不一致就可能出花屏、音画不同步,或者直接报错
  好处是不重编码,速度飞快
Windows 上有两条坑
  路径里的反斜杠要写成正斜杠:file 'D:/videos/part1.mp4'
   反斜杠在列表文件语法里是转义符,写成 D:\videos\part1.mp4 会被吃掉
  绝对路径要加 -safe 0,否则默认拒绝读取列表文件外面的路径
列表文件里不能有别的内容
  不许加空行、不许加注释,多一个空行 ffmpeg 就会报错

做法二:concat filter——参数不一样也能合。

ffmpeg -i a.mp4 -i b.mp4 \
  -filter_complex "[0:v][0:a][1:v][1:a]concat=n=2:v=1:a=1[v][a]" \
  -map "[v]" -map "[a]" out.mp4

为什么能用 filter
  因为 filter 可以先把两路都标准化再接——分辨率、采样率、帧率都能在接之前改齐
  [0:v][0:a][1:v][1:a] 按顺序列出要接的轨道:a 的视频、a 的音频、b 的视频、b 的音频
  concat=n=2:v=1:a=1 n=2 两段、v=1 视频轨、a=1 音频轨
  [v][a] 给输出的两路起名字,用 -map 引出来
代价是它必须重编码
  滤镜图里没有 -c copy 的位置
分辨率不一样要先统一
  [1:v]scale=1280:720,setsar=1[v1] 改完还得起个新名字
  setsar=1 别漏,像素宽高比不统一的话合并出来的画面会被拉扁

3.3 顺手再给两条

# 音频淡入淡出
ffmpeg -i in.mp4 -af "afade=t=in:st=0:d=2,afade=t=out:st=58:d=2" out.mp4

# 只取一段音频出来
ffmpeg -i in.mp4 -vn -ss 30 -t 60 -c:a copy out.aac

4. 图片视频互转

4.1 图片 → 视频

# 一张图做成 5 秒视频
ffmpeg -loop 1 -i pic.png -t 5 -r 30 -c:v libx264 -pix_fmt yuv420p out.mp4

# 一串图片合成视频(文件名必须是 img001.png img002.png 这种连续编号)
ffmpeg -framerate 30 -i img%03d.png -c:v libx264 -pix_fmt yuv420p out.mp4

-loop 1 在 -i 前面
  让 ffmpeg 无限循环读这张图,不写它图片只会被读一帧,视频只有 1/30 秒
  时长靠 -t 控制,或者用 -shortest 配音频
-r 30 定帧率
  和后面的 -framerate 30 是一回事,只是作用的位置不同
-pix_fmt yuv420p 一定要写
  否则编出来的是 yuv444p,多数播放器和手机放不了
  这是「本地能播、发出去别人播不了」的头号原因

4.2 视频 → 图片

# 每秒抽一帧
ffmpeg -i in.mp4 -vf fps=1 out_%04d.png

# 只抽第一帧
ffmpeg -i in.mp4 -frames:v 1 cover.jpg

# 抽第 10 秒那一帧
ffmpeg -ss 10 -i in.mp4 -frames:v 1 shot.png

out_%04d.png 的 %04d 是编号占位
  输出成 out_0001.png、out_0002.png……
  不写编号 ffmpeg 会报错,因为它不知道该叫什么
-vf fps=1 是按时间抽帧
  不是「每 1 帧抽 1 帧」,是「每秒抽 1 帧」
  抽场景切换用 select='gt(scene,0.4)',效果好得多
抽出来的图当封面,记得指定质量
  -q:v 2 jpeg 质量,2 最高,5 差不多,10 明显糊

4.3 视频 → GIF

# 第一遍:生成调色板
ffmpeg -i in.mp4 -vf "fps=10,scale=480:-1:flags=lanczos,palettegen" palette.png

# 第二遍:用调色板套色
ffmpeg -i in.mp4 -i palette.png -lavfi "fps=10,scale=480:-1:flags=lanczos[x];[x][1:v]paletteuse" out.gif

为什么要两遍
  GIF 只有 256 色,直接转出来的颜色会很难看
  第一遍先统计出整段视频的通用调色板,第二遍所有帧共用这一套
  颜色一下就干净了,文件还更小
尺寸要压
  GIF 体积对分辨率极其敏感,480 宽是常见的折中
  帧率也要压,10 帧就够,超过 15 体积涨得很快
  flags=lanczos 是缩放算法,比默认锐一点,边缘不毛躁

5. 滤镜

滤镜作用位置与分类图:解码之后编码之前夹着滤镜环节,滤镜分画面尺寸、文字图形、时间节奏、画质颜色四类列出 scale、crop、pad、transpose、drawtext、drawbox、overlay、fps、setpts、select、trim、eq、unsharp、deblock、format;下方代码块演示多个滤镜用逗号串联和用 filter_complex 叠水印的写法,红色提示说明带 -c copy 时滤镜完全不生效

滤镜夹在解码和编码中间,改的是原始帧。加水印、改分辨率、抽帧、混字幕都在这一步做。

最常见的一类:尺寸
  scale=1280:-1 缩放,宽度固定 1280,高度按比例自动算(-1 就是「保持比例」)
  crop=iw/2:ih/2 裁剪,取左上半(iw 输入宽、ih 输入高)
  pad=1920:1080 补黑边,把画面统一成 16:9
  transpose=1 旋转 90 度
最常用的一类:文字与图形
  drawtext=fontfile=...:text='...' 写字、打水印
  drawbox=x=0:y=0:w=100:h=50:color=black 画色块,遮住不该露的部分
  overlay 把一张图叠上去,logo 和水印都靠它
做特效的一类:时间与节奏
  fps=30 改帧率
  setpts=PTS/2 改时间戳,做两倍快放
  select='gt(scene,0.4)' 按条件抽帧,抽场景切换
  trim=0:30 按时间裁段落
修画质的一类:颜色
  eq=brightness=0.05:contrast=1.1 亮度、对比度、饱和度
  unsharp=5:5:1.0 锐化
  deblock 去色块,老片修复常用
  format=yuv420p 统一像素格式

5.1 滤镜串起来用

# 逗号串起来,顺序有讲究
ffmpeg -i in.mp4 -vf "scale=1280:-1,crop=1280:720,eq=brightness=0.05" out.mp4

# 叠水印:水印在前,画面在后
ffmpeg -i in.mp4 -i logo.png -filter_complex "[1][0]overlay=W-w-20:20" out.mp4

滤镜顺序会改结果,不是随便串
  先裁剪再缩放处理的数据量小、速度快;先缩放再裁剪裁出来的位置会偏
overlay 的输入顺序是「被叠加的在后」
  [1][0] 是水印在前、画面在后;写反了会得到一条纯色画面
  W-w-20:20 右上角,往左 20 像素、往下 20 像素
-vf 和 -af 是单流简写
  -vf 视频滤镜、-af 音频滤镜
  要同时处理多路就得用 -filter_complex
  用方括号把每路输出命名,否则 ffmpeg 不知道哪条输出接哪条

5.2 drawtext 的字体

# Windows:路径里的冒号要转义
ffmpeg -i in.mp4 -vf "drawtext=fontfile='C\:/Windows/Fonts/msyh.ttc':text='hello':x=20:y=20" out.mp4

# Linux
ffmpeg -i in.mp4 -vf "drawtext=fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf:text='hello'" out.mp4

不写 fontfile 在 Windows 上大概率报「Cannot find a valid font」
  Windows 版没有 fontconfig,不会自己去字体目录里找
  必须给绝对路径
路径里的冒号要写成 \:
  冒号是 ffmpeg 滤镜参数的分隔符,不转义的话它会把 C 当成一个参数名
  Windows 路径里的反斜杠也建议改成正斜杠,省掉一层转义
滤镜参数里的引号和外层引号会打架
  外层用双引号,里面的 text 用单引号,这是最省事的组合

💡 补充几个容易忽略的:
-c copy 会让滤镜彻底哑火
  写了 -vf 它也不报错,只是画面毫无变化
  要滤镜就必须去掉 -c copy,让编码器真的跑起来
滤镜名字先用 -filters 查
  ffmpeg -hide_banner -filters 列全部可用滤镜
  名字拼错不报错,它会当成未知滤镜然后告诉你找不到——但拼错一个相近的名字可能静默通过
-vf 里带逗号的参数要整体引号包住
  多个滤镜用逗号分隔,参数值里自己带逗号(比如 drawtext 的 text)要用反斜杠转义
滤镜链调不动的时候,先单独测一个
  一次串三个,出问题都不知道是哪一步,拆开一个个加
官方滤镜文档每条都列了可用选项
  ffmpeg.org/ffmpeg-filters.html,比任何二手教程都准

6. 画质:-crf 和 -preset

# 体积优先
ffmpeg -i in.mp4 -c:v libx264 -crf 28 -preset veryfast -c:a aac -b:a 128k out.mp4

# 画质优先
ffmpeg -i in.mp4 -c:v libx264 -crf 18 -preset slow -c:a aac -b:a 192k out.mp4

-crf 是画质档位,不是码率
  范围 0~51,越小越好
  18 视觉无损,体积大
  23 ffmpeg 默认值,够用
  28 明显能看出压缩痕迹,体积小
  用 CRF 的好处是体积会自己浮动——静止画面占得少,动态画面占得多,比固定码率画质稳定
-preset 是速度和质量的取舍
  ultrafast → superfast → veryfast → faster → medium → slow → slower → veryslow
  越慢,同画质下体积越小,代价是编码时间成倍增加
  同一个 -crf 下,veryslow 能比 ultrafast 小 20% 以上
-crf 和 -b:v 是两种控制方式
  写 -crf 就别写 -b:v,两个一起写会打架
  要固定码率(比如直播推流)才用 -b:v 4M
硬件编码能快很多
  -c:v h264_nvenc -preset p4 -cq 25 NVIDIA
  -c:v h264_qsv -preset medium -global_quality 25 Intel 核显
  -c:v h264_amf -quality quality -rc cqp -qp_i 25 AMD
  -c:v h264_vaapi Intel 核显的 VAAPI 路径
  硬件编码同样画质下体积会大一些,但速度快一个数量级

🔥 一句话总结:ffmpeg 的命令就是往「拆包→解码→滤镜→编码→封装」五步里填参数,-map 0:v -c copy 拆裸流、-f h264 -i ... -c copy 装回去,拆出来再装回去就是「格式分解与复用」;裸流没有头,尺寸帧率采样率必须用命令行补齐;裁剪要分清 -ss 在 -i 前(快、可能带多余关键帧)还是在后(准、慢),精确到帧且不重编码基本做不到;合并用 concat demuxer(参数必须一致、Windows 路径要正斜杠 + -safe 0)或 concat filter(参数可不同但要重编码);图片视频互转的成败关键是 -loop 1 和 -pix_fmt yuv420p;滤镜改的是原始帧,overlay 输入顺序是「被叠加的在后」、drawtext 在 Windows 必须给 fontfile 且冒号要转义;画质靠 -crf(越小越好,18 无损、23 默认、28 偏糊)加 -preset(越慢越小)两条调。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或者给予支持!