7、FFmpeg滤镜链实战:使用滤镜实现视频缩放、裁剪、旋转、添加水印,以及音频混音与变速
说实话,滤镜是FFmpeg里最让我着迷的部分。你想想看,一个命令行工具,居然能像搭积木一样把各种音视频效果串起来。我最早接触FFmpeg滤镜时,觉得这东西不就是加个特效嘛,后来才发现——滤镜链的设计思路,其实跟咱们写代码时的管道模式一模一样。
这一章,咱们就动手实操。我会带着你用滤镜链完成视频缩放、裁剪、旋转、加个水印,再顺带把音频的混音和变速也搞定。嗯,都是实战中高频用到的场景。
filtergraph,它用字符串描述滤镜之间的连接关系。每个滤镜节点处理一帧数据,然后传给下一个节点。
7.1 视频缩放:从720p到1080p,一个scale搞定
视频缩放,说白了就是改变画面的宽高。我项目中经常遇到这种情况:用户上传的视频分辨率五花八门,我得统一缩放到某个标准尺寸。FFmpeg的 scale 滤镜就是干这个的。
基本用法:
ffmpeg -i input.mp4 -vf "scale=1920:1080" output.mp4
这里 -vf 是视频滤镜链的入口。 scale=1920:1080 表示宽1920,高1080。但有个坑——如果你直接写死宽高,画面比例可能会被拉伸变形。我刚开始就犯过这个错,结果人脸都变宽了。
scale=1280:-1 或者 scale=-1:720,让FFmpeg自动计算另一边,保持原始宽高比。那个 -1 就是「自动计算」的意思。
如果你想强制缩放到某个尺寸,同时保持比例不变,可以用 force_original_aspect_ratio 参数:
ffmpeg -i input.mp4 -vf "scale=1920:1080:force_original_aspect_ratio=decrease" output.mp4
这个参数会保证画面完整显示,不足的部分用黑边填充。嗯,有点像咱们看电影时上下黑边的效果。
7.2 视频裁剪:只取画面的一部分
裁剪,就是从原画面中切出一块矩形区域。我记得有一次做直播项目,需要把摄像头画面中的某个固定区域截取出来做特写,用的就是 crop 滤镜。
语法:
crop=out_w:out_h:x:y
其中 out_w 和 out_h 是裁剪后的宽高, x:y 是裁剪区域的左上角坐标。比如:
ffmpeg -i input.mp4 -vf "crop=640:480:100:50" output.mp4
这表示从坐标(100, 50)开始,裁剪出640x480的区域。
如果你想裁剪画面中心区域,可以用 in_w/2 和 in_h/2 这种表达式:
ffmpeg -i input.mp4 -vf "crop=640:480:(in_w-640)/2:(in_h-480)/2" output.mp4
这样不管输入视频多大,都能自动居中裁剪。
7.3 视频旋转:转个90度,或者任意角度
旋转滤镜有两个常用方案: transpose 和 rotate。
transpose 只能做90度、180度、270度旋转,但速度快,适合手机竖屏视频转横屏这种场景:
ffmpeg -i input.mp4 -vf "transpose=1" output.mp4
参数 1 表示顺时针旋转90度。 2 是逆时针90度, 3 是180度。
如果你需要任意角度旋转,就得用 rotate 滤镜。比如旋转45度:
ffmpeg -i input.mp4 -vf "rotate=45*PI/180" output.mp4
注意, rotate 的角度单位是弧度。我每次写这个都要心里默念一遍「弧度=角度×π/180」。另外,旋转后画面边缘会出现黑边,你可以用 fillcolor 参数改成其他颜色:
ffmpeg -i input.mp4 -vf "rotate=45*PI/180:fillcolor=white" output.mp4
rotate 处理一个4K视频,结果发现CPU占用飙到100%,帧率掉到个位数。后来换成 transpose 配合硬件加速,才搞定。所以,能不用 rotate 就别用,除非你真的需要任意角度。
7.4 添加水印:用overlay把图片叠上去
水印,说白了就是一张小图叠在大图上。FFmpeg的 overlay 滤镜就是干这个的。我做过一个视频平台,所有用户上传的视频都要自动加上平台Logo,用的就是这个。
基本用法:
ffmpeg -i input.mp4 -i logo.png -filter_complex "overlay=10:10" output.mp4
这里 -filter_complex 是复杂滤镜链的入口,因为涉及两个输入流(视频+图片)。 overlay=10:10 表示水印放在距离左上角10像素的位置。
如果你想放在右下角,可以这样:
ffmpeg -i input.mp4 -i logo.png -filter_complex "overlay=W-w-10:H-h-10" output.mp4
W 和 H 是主视频的宽高, w 和 h 是水印的宽高。这个表达式就是「主视频右下角,再往左上偏移10像素」。
enable 参数做时间控制,或者用 between 表达式。比如只在第5秒到第10秒显示水印:
overlay=10:10:enable='between(t,5,10)'
7.5 音频混音:把两路声音合在一起
音频混音,就是把多个音频流混合成一路。比如给视频配背景音乐,或者把解说和原声混在一起。FFmpeg的 amix 滤镜就是专门干这个的。
假设你有一个视频文件 video.mp4 和一个音乐文件 bgm.mp3,想把它们混在一起:
ffmpeg -i video.mp4 -i bgm.mp3 -filter_complex "[0:a][1:a]amix=inputs=2:duration=first" output.mp4
这里 [0:a] 和 [1:a] 分别引用第一个输入和第二个输入的音频流。 amix 的参数 inputs=2 表示混合两路音频, duration=first 表示以第一个音频的时长为准(也就是视频原声的长度)。
如果你想调整两路音频的音量比例,可以用 volume 滤镜先处理一下:
ffmpeg -i video.mp4 -i bgm.mp3 -filter_complex "[0:a]volume=1.0[a0];[1:a]volume=0.3[a1];[a0][a1]amix=inputs=2:duration=first" output.mp4
这样背景音乐的音量就只有原声的30%,不会喧宾夺主。嗯,这个比例我一般根据场景调,如果是纯音乐可以稍微大一点。
aresample 滤镜统一参数。
7.6 音频变速:让声音变快或变慢
音频变速,最常用的滤镜是 atempo。它通过改变音频的播放速度来调整时长,同时保持音调不变(或者变化很小)。
比如把音频速度变成原来的1.5倍:
ffmpeg -i input.mp4 -af "atempo=1.5" output.mp4
-af 是音频滤镜链的入口。 atempo 的取值范围是0.5到2.0,低于0.5或高于2.0会报错。如果你需要更极端的变速,可以串联多个 atempo:
ffmpeg -i input.mp4 -af "atempo=2.0,atempo=2.0" output.mp4
这样就是4倍速。我做过一个音频剪辑工具,用户要求支持0.25倍到4倍速,就是用这种串联方式实现的。
aresample 把采样率提高到48kHz,能改善一些。
7.7 综合实战:一个完整的滤镜链
好了,咱们把上面这些知识点串起来。假设你有一个视频,需要做以下处理:
- 缩放到1280x720
- 裁剪出中心区域
- 旋转90度
- 加上水印
- 混入背景音乐,音量降到30%
- 把整体音频速度变成1.2倍
完整的命令如下:
ffmpeg -i input.mp4 -i logo.png -i bgm.mp3 \
-filter_complex \
"[0:v]scale=1280:720,crop=640:480:(1280-640)/2:(720-480)/2,transpose=1[vid]; \
[0:a]volume=1.0[audio_orig]; \
[2:a]volume=0.3[audio_bgm]; \
[audio_orig][audio_bgm]amix=inputs=2:duration=first[audio_mix]; \
[audio_mix]atempo=1.2[audio_final]" \
-map "[vid]" -map "[audio_final]" output.mp4
这个命令看起来有点长,但拆开看其实很简单:
- 第一行:视频流经过
scale→crop→transpose,输出到[vid] - 第二行:原音频音量设为1.0
- 第三行:背景音乐音量设为0.3
- 第四行:两路音频混音
- 第五行:混音后的音频变速1.2倍
最后用 -map 把处理后的视频和音频流映射到输出文件。
下面这张图展示了整个滤镜链的数据流向:
嗯,看到这里,你应该对FFmpeg滤镜链有了比较完整的认识。说白了,滤镜链就是一条流水线,每个滤镜负责一道工序。你只要把工序排好顺序,FFmpeg就会自动帮你跑完。
我个人建议,刚开始练习时,先拆开每个滤镜单独测试,确认效果没问题了,再拼成完整的滤镜链。这样出错了也容易定位。我曾经有一次把 overlay 和 scale 的顺序搞反了,结果水印被缩放得面目全非,排查了半天才发现。
ffmpeg -filter_complex "你的滤镜链" -f null - 来测试,这样不会生成实际文件,只检查语法。能省不少时间。