直接开工,不整虚的。网上搜FFmpeg教程,十篇有八篇在互相抄,抄来抄去连滤镜参数都写错。我这些年用FFmpeg处理过上万条视频,从监控录像到短视频素材都碰过,这次就把水印和分辨率这两块最常用的操作一次性讲透——查看分辨率、指定分辨率、加文字水印、加图片水印、控制水印位置,全都有现成命令和实测参数,照着抄就能用。
1. 动手之前:先把FFmpeg的命令骨架搞明白
1.1 FFmpeg不是一条命令,而是一组工具链
很多人第一次接触FFmpeg,以为它就是个能转格式的命令行工具,其实它是一整套多媒体处理套件。日常用得最多的是三个成员:ffmpeg负责转码和处理,ffprobe负责查看媒体文件信息,ffplay负责快速预览播放。查看分辨率和媒体信息,优先用ffprobe,它输出的信息干净、结构化,好解析;ffmpeg也能看,但信息混在转码日志里,不如ffprobe清爽。
安装这一步反而最容易被忽略。Windows用户别去官网下那个几百MB的完整版,直接搜gyan.dev的release构建,下载ffmpeg-release-full.7z解压,把bin目录加进系统PATH就行。macOS用户用Homebrew跑brew install ffmpeg,一条命令搞定。Linux用户一般apt install ffmpeg或yum install ffmpeg,但有些发行版的源里版本太老,转码性能和新滤镜支持都跟不上,建议用官方静态构建。版本这事别马虎,FFmpeg每年都在加新滤镜新参数,网上很多老教程的命令在新版本上跑不通,就是因为版本太旧。
1.2 命令的通用骨架:输入、滤镜、输出
FFmpeg命令看着长,拆开就三部分:输入参数、滤镜链、输出参数。拿水印命令举例:
ffmpeg -i input.mp4 -vf "drawtext=text='HELLO':fontsize=48" output.mp4-i input.mp4是输入,-vf后面跟的是视频滤镜链,output.mp4是输出。所有复杂的视频处理,比如缩放、裁剪、加水印、调色,本质上都是在-vf这根滤镜链上做文章。滤镜之间用逗号串联,按顺序执行,前一个滤镜的输出是后一个滤镜的输入。
音频处理则是-af参数,比如调音量、降噪、变速不变调。录音场景里经常要同时处理视频和音频,可以分开写两条滤镜链。理解了这个骨架,后面所有命令都只是往里面填不同的滤镜参数而已。我见过不少新手把滤镜参数写到-i前面,结果FFmpeg完全忽略,输出文件没任何变化,这就是没搞清楚输入输出顺序的典型错误。
2. 查看分辨率:别猜,直接用ffprobe拿准确数据
2.1 ffprobe一条命令看全部媒体信息
想知道一个视频是多大分辨率、什么编码、多少帧率,最靠谱的方式就是ffprobe:
ffprobe -show_streams -show_format input.mp4输出内容会比较长,包含视频流、音频流、封装格式三类信息。实际操作中一般不用看完整输出,加个-select_streams v只选视频流:
ffprobe -v error -select_streams v:0 -show_entries stream=width,height,avg_frame_rate,codec_name,bit_rate -of default=noprint_wrappers=1 input.mp4返回结果大概是:
codec_name=h264 width=1920 height=1080 avg_frame_rate=30000/1001 bit_rate=N/A这里有个细节容易看懵:avg_frame_rate返回的是分数形式,30000/1001不是乱码,是NTSC标准的29.97fps。直接除一下就知道真实帧率。bit_rate显示N/A也别慌,很多封装格式里码率信息不在流级别里,要到容器级别查。想看容器信息就用-show_entries format=bit_rate,duration。
2.2 从ffmpeg输出日志里辨认分辨率
有些时候你只有ffmpeg -i的日志输出,比如在一台没装ffprobe的机器上临时排查。FFmpeg在转码前会输出媒体文件的基础信息,里面有一行长这样:
Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(progressive), 1920x1080 [SAR 1:1 DAR 16:9], 30000/1001 fps, 30000/1001 tbr, 12800 tbn中间的1920x1080就是分辨率,括号里的DAR 16:9是显示宽高比。这里有个小坑:有些视频的存储分辨率是1920x1080,但实际显示宽高比不是16:9,比如某些DV录像或老视频会带像素宽高比(SAR)修正。后续做缩放处理时,这个SAR信息很关键,处理不对画面就会变形。
2.3 批量查看多文件分辨率的实用脚本
单个文件用命令看没问题,几十个文件排着队就麻烦了。我习惯写个小循环脚本,几行代码把文件夹里所有视频的分辨率一次列出来:
for f in *.mp4; do dims=$(ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=s=x:p=0 "$f") echo "$f: $dims" done-of csv=s=x:p=0的意思是输出格式为CSV,分隔符用x,不打印条目名,所以结果就是1920x1080这种直接的字符串。Windows用户没有bash循环,可以写个批处理或者直接用PowerShell的ForEach-Object,逻辑一样。批量场景里还有一个更高效的办法:用ffprobe输出JSON格式,然后交给Python或jq解析,数据量大时这样更好处理。
3. 指定分辨率:缩放滤镜的进阶用法
3.1 scale滤镜基础:直接写死宽高
最朴素的指定分辨率方式:
ffmpeg -i input.mp4 -vf "scale=1280:720" output.mp4也可以不写数字,让FFmpeg自己算一边:
ffmpeg -i input.mp4 -vf "scale=1280:-1" output.mp4-1表示按原视频宽高比自动计算另一边。比如源文件是1920x1080,scale=1280:-1会算出高度720,成片就是1280x720。这个参数很常用,但也容易出问题:如果源视频是竖屏1080x1920,scale=1280:-1算出的是1280x2276,跑出来分辨率奇奇怪怪,不是你想的效果。所以动脑子想想再决定哪边写死哪边自适应。
另一个常见需求是固定倍数缩放,两倍放大用:
ffmpeg -i input.mp4 -vf "scale=iw*2:ih*2" output.mp4iw是输入宽度,ih是输入高度,这是滤镜里的变量。缩到50%就是scale=iw/2:ih/2。这里的变量体系是整个滤镜链的通用能力,不只在scale里用,overlay、drawtext这些滤镜也能引用,后面讲水印位置时会反复用到。
3.2 保持宽高比与防止画面变形
新手最常犯的错误是直接写两个固定值,把画面拉伸变形。比如把1920x1080压到640x480,视频流分辨率确实变成640x480了,但画面内容被横向压扁,人脸拉宽,字幕变色,观感极差。
真正稳妥的做法有三条路:
- 用
-1自动计算一边:
ffmpeg -i input.mp4 -vf "scale=640:-1" output.mp4- 先裁剪补边再缩放,适合不同宽高比转换的场景:
ffmpeg -i input.mp4 -vf "scale=640:480:force_original_aspect_ratio=decrease,pad=640:480:(ow-iw)/2:(oh-ih)/2" output.mp4- 只改分辨率,宽高比交给播放器自适应:
ffmpeg -i input.mp4 -vf "scale=640:480:force_original_aspect_ratio=decrease" output.mp4最后这个命令输出会是640x360(对16:9源),但容器里记录了640x480的目标显示尺寸,部分播放器能正确处理,但很多播放器会糊。做后期的话千万别用这种方案,压片的人拿到你这文件得骂街。
force_original_aspect_ratio=decrease这个参数要重点说:它的作用是让画面保持原始宽高比缩放,不足的部分用背景填充,配合pad滤镜补边。我在短视频平台的封面制作里经常用这招,把横屏视频转成竖屏封面,主体内容不变形,上下黑边自然填充。
3.3 录音录像场景:采集时直接指定采集分辨率
用FFmpeg做屏幕录制或摄像头采集时,指定分辨率的方式和转码略有不同。屏幕录制命令:
ffmpeg -f gdigrab -framerate 30 -video_size 1920x1080 -i desktop output.mp4这里-video_size是采集参数,不是滤镜参数,写在-i前面。摄像头采集(Linux下UVC设备常见):
ffmpeg -f v4l2 -input_format mjpeg -video_size 1280x720 -framerate 30 -i /dev/video0 output.mp4macOS下摄像头采集用的是-f avfoundation,Windows用的是-f dshow:
# Windows,设备名要先列出来 ffmpeg -f dshow -list_devices true -i dummy # 然后按设备名采集 ffmpeg -f dshow -video_size 1280x720 -framerate 30 -i "video=USB Camera" output.mp4这种"推流前先查分辨率"的思路和后面讲水印时的"先查视频参数再定水印位置"是一脉相承的。采集分辨率这个东西,高了浪费CPU(因为采集完可能还要缩放),低了糊,最好是按最终输出需求来。我实测过,采集1920x1080再缩到1280x720,画质比直接采1280x720好不了多少,但CPU占用会高8%~12%,实时场景里完全不值得。
录音也有类似的逻辑。指定采样率:
ffmpeg -f pulse -i default -ar 44100 -ac 2 output.wav-ar是音频采样率,-ac是声道数。录像时要同步指定音视频参数,我常用的实战命令:
ffmpeg -f gdigrab -framerate 30 -video_size 1920x1080 -i desktop -f dshow -i audio="Microphone" -c:v libx264 -preset ultrafast -crf 28 -c:a aac -b:a 128k output.mp43.4 高效缩放的性能考量与参数微调
缩放不是免费的,1280x720升级到4K对性能要求不低。FFmpeg默认的swscale(软件缩放器)虽然兼容性好,但速度一般。有两个办法提速:
- 指定更快的缩放算法:
ffmpeg -i input.mp4 -vf "scale=1920:1080:flags=fast_bilinear" output.mp4flags可以选fast_bilinear(速度快)、bicubic(质量好)、lanczos(质量最好但慢)。日常处理用bicubic就够了,做放大这类质量敏感的操作用lanczos。
- 硬件缩放。Intel平台的QSV、NVIDIA的CUDA都能参与缩放,命令写法不同,但思路都是在scale前指定硬件上下文。比如NVIDIA硬解硬编的例子:
ffmpeg -hwaccel cuda -hwaccel_output_format cuda -i input.mp4 -vf "scale_cuda=1920:1080" -c:v h264_nvenc output.mp4硬件加速这条路能聊一整篇,这里先提个醒:RTX 30系以后显卡的NVENC编码器质量已经很好了,但NVIDIA的缩放滤镜scale_cuda在部分老驱动或老FFmpeg版本上兼容性一般,遇到黑屏或花屏先检查驱动和FFmpeg版本。CPU多核性能够用的话,优先软件方案,显卡留给渲染或者游戏,稳字当头。
4. 添加水印:文字、图片、位置控制一次搞定
4.1 文字水印:drawtext滤镜初体验
文字水印用drawtext滤镜,最简单的命令:
ffmpeg -i input.mp4 -vf "drawtext=text='Hello World':fontsize=48:fontcolor=white" output.mp4默认文字出现在左上角,白字黑边(实际上默认带一点阴影)。真实场景里一般要指定字体和位置:
ffmpeg -i input.mp4 -vf "drawtext=fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf:text='HELLO':fontsize=48:fontcolor=white:x=50:y=50" output.mp4Windows用户注意,fontfile路径里的冒号和反斜杠都要转义,这个坑我踩得刻骨铭心。Windows下写路径,要用正斜杠或者双反斜杠。macOS下常见字体路径是/System/Library/Fonts/Helvetica.ttc。
x和y是文字左上角的坐标。但实际控制位置时几乎不会用死值,因为水印要跟着视频尺寸走,比如居中、靠右下角留出边距,这些都需要用到动态变量。
4.2 图片水印:overlay滤镜实战
图片水印用overlay滤镜,逻辑是把一张PNG叠在视频上面:
ffmpeg -i input.mp4 -i logo.png -filter_complex "overlay=W-w-10:10" output.mp4注意这里用的是-filter_complex而不是-vf,因为涉及两个输入(视频和图片),必须用复杂滤镜图。W是视频宽度,w是图片宽度,W-w-10就是图片离视频右边界10像素。初始坐标是图片左上角的落点。
多个水印叠加就写多个overlay滤镜,用逗号串联:
ffmpeg -i input.mp4 -i logo1.png -i logo2.png -filter_complex "overlay=W-w-10:10,overlay=10:H-h-10" output.mp4每个overlay作用的对象是前一步的结果,所以第二个overlay里的W和H已经是第一个overlay处理过的视频尺寸了。这个逻辑初学者容易绕晕,我打个比方:滤镜链就是流水线,每个工位处理完的东西会传给下一个工位,所以后面工位看到的"视频宽高"已经是加工过的了。想确认某个滤镜需要的变量值,可以加-debug=verbose看日志里的滤波器图信息。
4.3 水印位置控制:坐标变量的进阶理解
水印定位是滤镜里最灵活也最容易出错的点。最基本的定位需求就五种:左上、右上、左下、右下、居中。
| 位置 | x坐标 | y坐标 | 说明 |
|---|---|---|---|
| 左上角 | 10 | 10 | 直接写死就行 |
| 右上角 | W-w-10 | 10 | 靠右留边 |
| 左下角 | 10 | H-h-10 | 靠下留边 |
| 右下角 | W-w-10 | H-h-10 | 最常用 |
| 居中 | (W-w)/2 | (H-h)/2 | 注意除法结果自动截断取整 |
这几个公式里,W、H是视频宽高,w、h是水印图片宽高。做图片水印时,如果你不确定PNG的实际尺寸,可以先跑一下ffprobe logo.png看宽高,免得算出来的位置偏得离谱。
上面这些公式在drawtext里同样适用,只是变量含义略有区别:drawtext里没有w和h(那是文字尺寸),文字宽度要用text_w表示,文字高度用text_h。所以文字水印右下角定位是:
drawtext=...:x=W-tw-20:y=H-th-20tw是文本宽度,th是文本高度。这个变量名很多人不知道,翻官方文档也只写了text_w和text_h的别名tw、th。我在脚本里习惯全用短变量,命令能短一截是一截。
4.4 多行多列水印:平铺马赛克式水印的做法
"多行多列文字水印"这个需求最近很热,很多人想给自己的素材打满屏水印防搬运。做法不复杂,思路是用多个drawtext滤镜平铺,或者用一个drawtext加换行符。先看多行文字:
ffmpeg -i input.mp4 -vf "drawtext=fontfile=...:text='版权声明 请勿搬运':x=10:y=10,drawtext=fontfile=...:text='2025-03-01':x=10:y=80" output.mp4多列就继续加坐标,手动写了N个drawtext也行,但我觉得丑且难维护。更好的方案是生成一张文字水印PNG,然后用overlay平铺。生成PNG可以用FFmpeg自己来做(下面这行命令会生成一个透明背景的PNG,但中文字体支持很麻烦,我一般用ImageMagick或Photoshop搞定),不过更干净的办法是准备好一张水印图,然后用overlay铺:
ffmpeg -i input.mp4 -i watermark.png -filter_complex "overlay=W-w-10:H-h-10,overlay=W-w-260:H-h-10,overlay=W-w-10:H-h-180" output.mp4手动铺三个已经有点嫌弃了,真正平铺整屏的做法是配合tile滤镜。先复制输入帧然后做多宫格拼贴,这个命令稍微反直觉,我这里给一个实用的平铺方案:
ffmpeg -i input.mp4 -i wm.png -filter_complex "[0:v]scale=1920:1080[bg];[1:v]scale=200:-1[wm];[bg][wm]overlay=W-w-20:H-h-20[o1];[o1][wm]overlay=20:H-h-20[o2];[o2][wm]overlay=W-w-20:20[o3];[o3][wm]overlay=20:20" output.mp4这个命令先把视频缩到统一尺寸,然后把水印图缩到200宽,四个角落各放一个。做"九宫格水印""满屏网格水印"的核心也就是这个思路的扩展。
4.5 半透明水印与动态水印
半透明水印的做法是用format=rgba,colorchannelmixer=aa=0.4先把PNG转成半透明,再overlay。不过更常见的做法是PNG素材本身就是半透明的,像很多设计网站出的透明底logo,透明度在PS里已经控制好了,滤镜端不用动。如果硬要在FFmpeg里调透明度:
ffmpeg -i input.mp4 -i logo.png -filter_complex "[1:v]format=rgba,colorchannelmixer=aa=0.5[logo];[0:v][logo]overlay=W-w-10:H-h-10" output.mp4动态水印,比如跑马灯效果,就是让x或y坐标随时间变化。dx和dy参数可以让水印移动:
ffmpeg -i input.mp4 -i logo.png -filter_complex "overlay=x='mod(t*50, W)':y=10" output.mp4这个效果是水印从左边进入,从右边消失。t是当前时间秒数,t*50控制速度,mod是取模运算,保证x坐标不会超过视频宽度。
5. 水印实战中的坑与解决思路
5.1 中文字体缺失与编码
drawtext默认字体往往不支持中文,加上fontfile参数指定中文字体是必须的。Windows下一般指定C盘字体目录,比如C\:/Windows/Fonts/msyh.ttc(微软雅黑)。Linux下常见的中文字体路径是/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc。
另外中文字符串建议单独抽出来先用文本编辑器确认编码,然后直接贴在参数里。如果你在Windows的命令行里敲中文参数,编码经常不是UTF-8,FFmpeg会报错或者显示乱码。稳妥的办法是把文字内容先写入一个文本文件,然后让drawtext读取:
ffmpeg -i input.mp4 -vf "drawtext=fontfile=...:textfile=title.txt:fontsize=48" output.mp45.2 水印清晰度与视频压缩的关系
水印被压糊的根本原因是编码码率不够。加了水印的视频,尤其是文字水印区域,细节变多,编码器会分配更多码率,如果总码率固定,画质必然下降。两个应对办法:
- 用CRF模式而不是固定码率:
ffmpeg -i input.mp4 -vf "drawtext=..." -c:v libx264 -crf 23 output.mp4CRF(恒定质量因子)的数值越小质量越高,23是Web发布的推荐值,18以下属于无损级,做存档用。
- 水印做成半透明,减少高频细节。
5.3 批量给整个目录加图片水印
文本处理和批处理是shell的拿手戏。给一个文件夹里所有MP4加同一个水印:
for f in *.mp4; do ffmpeg -i "$f" -i logo.png -filter_complex "overlay=W-w-10:H-h-10" -c:a copy "watermarked_$f" done这个命令有一个不起眼但很重要的参数:-c:a copy,表示音频直接复制不重新编码,能够让处理速度快很多。视频流做了滤镜处理必须重新编码,但音频流如果不需要滤镜操作,直接copy能省下不少CPU。
6. 常见问题排查与命令速查
6.1 滤镜未找到或参数报错
[Parsed_drawtext_0 @ 0x...] Error: Invalid text or expression这类问题八成是字体文件路径有问题,或者text参数里的特殊字符没转义。排查方法:先把命令拆到只保留drawtext,其他滤镜全部去掉,然后逐步加参数,定位是哪个参数导致的报错。
[AVFilterGraph @ 0x...] No such filter: 'drawtext'这是FFmpeg编译时没启用libfreetype,静态构建一般不会出这问题,但某些精简版FFmpeg会。解决办法就是去下载完整版。
6.2 分辨率指定了但没生效
最典型的情况是把滤镜参数放在了输出文件名后面:
# 错误示例 ffmpeg -i input.mp4 output.mp4 -vf "scale=1280:720"FFmpeg对参数顺序有严格要求,-vf必须出现在输出文件之前。第二个坑是把滤镜写到了-i前面某些场景下会被解释成输入选项,然后被忽略。
6.3 水印位置参数速查表
| 需求 | overlay (图片) | drawtext (文字) |
|---|---|---|
| 右下角留边20px | x=W-w-20:y=H-h-20 | x=W-tw-20:y=H-th-20 |
| 右上角留边20px | x=W-w-20:y=20 | x=W-tw-20:y=20 |
| 水平居中垂直顶部 | x=(W-w)/2:y=20 | x=(W-tw)/2:y=20 |
| 水平居中垂直居中 | x=(W-w)/2:y=(H-h)/2 | x=(W-tw)/2:y=(H-th)/2 |
6.4 去水印的正确姿势与边界
看到"去水印"需求,我的态度很明确:只聊技术原理,不鼓励盗用他人素材。视频去水印在FFmpeg里没有魔法,只有delogo滤镜,做法是在水印区域模糊或插值:
ffmpeg -i input.mp4 -vf "delogo=x=100:y=100:w=200:h=80" output.mp4delogo的原理是取水印区域周围的像素做内插,把水印盖住。实效上,静态背景上效果好,复杂运动场景会有明显涂抹感。如果你要处理的是自己拍的视频不小心录入了厂商logo,比如摄像头时间戳、相机水印,delogo是能救急的;处理别人的视频素材用于商用,这就不只是技术问题了,是版权问题,我不给任何建议。真要给自己的素材做"去水印",更优雅的做法是用crop直接裁掉水印区域,比如去掉底部时间条:
ffmpeg -i input.mp4 -vf "crop=in_w:in_h-100:0:0" output.mp4这是把画面底部100像素高的区域整个裁掉,效果干净利落,没有任何涂抹感。
7. 我自己攒的一套水印+分辨率工作流
最后分享一个我长期在生产环境里用的脚本框架,本质上是把分辨率检测、缩放、加水印三个环节串成了一个流程。这个脚本做的事情是:读取输入视频的真实分辨率,检查是否超过目标尺寸,超标就缩放,然后把水印加上,最后统一输出。
#!/bin/bash # usage: process_video.sh input.mp4 output.mp4 input="$1" output="$2" width=$(ffprobe -v error -select_streams v:0 -show_entries stream=width -of csv=p=0 "$input") height=$(ffprobe -v error -select_streams v:0 -show_entries stream=height -of csv=p=0 "$input") echo "Source: ${width}x${height}" # 超过1920宽就按比例缩 if [ "$width" -gt 1920 ]; then VF="-vf scale=1920:-2" else VF="" fi # 加右下角图片水印,如果VF已有内容就追加 VF="$VF -i logo.png -filter_complex overlay=W-w-20:H-h-20" ffmpeg -i "$input" $VF -c:a copy "$output"这里有个容易忽略的细节:scale=1920:-2中的-2和-1略有区别,-1可能生成奇数宽高,某些编码器不兼容,-2保证是偶数,所以限制只能2的倍数且正负号表示保持宽高比时偏向放大/缩小。编码时高度必须是偶数,不然硬编会报错,这是个很小但很真实的坑——我在第一次接触时完全没想到。
这套流程我用了很久,实际效果:在有水印的情况下,1080p视频用-crf 23输出,码率会涨8%到15%,这是水印内容增加细节导致的正常现象。如果你是给平台批量传视频,建议统一CRF,别锁死码率。
在水印尺寸上,我的经验是:水印宽度一般控制在视频宽度的1/4以内。比如1920宽的1080p视频,水印图片宽度在300到480像素之间观感最合适,再大就喧宾夺主了。文字水印的字体大小也类似,1080p下fontsize=36是基础档位,封面或推广视频可以放到48到64。
最后再分享一个小技巧:加水印时如果视频很长,几十GB的那种,建议先截取10秒片段测试命令,确认水印位置、大小、透明度都满意了,再跑全片。截取命令:
ffmpeg -i input.mp4 -t 10 -c copy sample.mp4这10秒钟的等待能帮你省下几个小时的重复转码,我在处理批量素材时永远先跑这个流程。