02-ffmpeg 转码与剪辑实战:容器、编码、滤镜与画质
📝 笔记摘要:ffmpeg 转码与剪辑实战,容器、编码、滤镜与画质
ffmpeg 真正开始干活的命令,参数都能归到五步流水线的某一步上:拆包、解码、滤镜、编码、封装。这一篇按这条线走,把日常最高频的几类活儿拆开——怎么把音视频拆成裸流再装回去、怎么切原始数据、怎么裁剪合并、怎么在图片和视频之间来回转、滤镜到底怎么用、画质参数怎么调。
每一条命令都标了「为什么这么写」。参数记不住的时候,知道它管哪一步,比背命令本身管用。
ffmpeg 系列 共 3 篇,这是第 2 篇
01 ffmpeg 基础 安装与工作原理
02-ffmpeg 转码与剪辑实战 容器、编码、滤镜与画质 ← 本篇
03 ffmpeg 录制与直播 录屏、摄像头与推流
1. 格式分解与复用
「格式分解与复用」这件事,拆开就是三个词:容器、编码、码流。
容器 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=1n=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 2jpeg 质量,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=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 25NVIDIA
-c:v h264_qsv -preset medium -global_quality 25Intel 核显
-c:v h264_amf -quality quality -rc cqp -qp_i 25AMD
-c:v h264_vaapiIntel 核显的 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(越慢越小)两条调。
