ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

FFmpeg 4.4 shared包音视频转码与二次开发实战

FFmpeg 4.4 shared包音视频转码与二次开发实战

简介:这份资源为 Windows 平台预编译的 FFmpeg 4.4 完整共享库版本,面向需要快速处理音视频、又不愿自行编译源码的开发者与多媒体从业人员。包内共 201 个文件,以 126 个 C 头文件、30 个 HTML 文档页面、8 组 dll/a/def/lib 动态与静态链接库为主体,另有 ffmpeg、ffplay、ffprobe 等 3 个可执行程序以及字体预设、样式文件若干,压缩包约 49.01MB。已有 744 人学习下载。使用者可以直接调用命令行完成格式转换、音频提取、画面裁剪与流媒体推拉,也可以借助 include 下的头文件与导入库把编解码能力集成进自研项目;4.4 版本在 AV1、H.265 支持、HDR 色彩空间处理与多线程优化上均有增强,随包文档便于查阅 API 与参数用法,省去搭建编译环境的时间和硬件成本。

1. ffmpeg-4.4-full_build-shared.zip 不是绿色播放器,而是一套可直接开工的音视频工具链

上周有同事把一段手机拍的 4K 素材拖进剪辑软件,预览卡成幻灯片,问我能不能先压一遍再上时间线。这种活,ffmpeg 一行命令就能处理,前提是手上要有一份能在 Windows 上直接运行的二进制包。ffmpeg-4.4-full_build-shared.zip 就是这种东西:full 表示构建时已编入 x264、x265、libvpx、lame 这类常用第三方库;shared 表示核心库以 dll 形式放在 bin 里,exe 很小,另外还带 include 与 lib,方便接进自己的 C/C++ 工程。它适合三类人:要批量处理音视频的运维和后台开发、要在程序里调用 libav* 接口的工程师,以及不想为了一个转码功能去搭建编译环境的人。

2. 解包先认三件套:bin、lib、include 各自管什么

很多人解压完第一反应是找 exe 双击,结果窗口一闪就没了,然后开始怀疑包坏了。问题不在包,而在于没搞清楚这个压缩包里三个目录的分工:bin 是给命令行用的成品程序,include 是给编译器看的头文件,lib 是给链接器用的导入库。三者版本必须对得上,混用不同来源的文件是后面一切玄学问题的根源。

2.1 bin 目录:三个 exe 配八个 dll,缺一不可

解压后进 bin,一般能看到 ffmpeg.exe、ffprobe.exe、ffplay.exe 三个可执行文件,旁边还有一排 dll。这三个 exe 的分工很明确:

文件职责什么时候用
ffmpeg.exe转码、剪辑、抽帧、推流、滤镜所有实际处理动作
ffprobe.exe只读探测,输出流信息和元数据转码前摸底、转码后验收
ffplay.exe简易播放器,依赖 SDL快速确认文件能不能解

那排 dll 才是 shared 版本的核心。FFmpeg 4.4 这条线的 soname 是固定的,你在 bin 里通常会看到 avcodec-58.dll、avdevice-58.dll、avfilter-7.dll、avformat-58.dll、avutil-56.dll、swresample-3.dll、swscale-5.dll,有的构建还带 postproc-55.dll。数字不是随手写的,它是 ABI 版本号,58 就是 4.4 这一代 libavcodec 的接口代际。这意味着两件事:第一,ffmpeg.exe 启动时必须在工作目录或 PATH 里找到这些 dll,少一个就报“找不到 avcodec-58.dll”;第二,你以后写程序链接的库,主版本号也必须是 58,拿 5.x 的头文件配 4.4 的 dll,编译能过,运行必崩。

顺带说一句“full”的含义。FFmpeg 官方只发布源码,你在 Windows 上拿到的二进制包都是第三方按不同配置编出来的。full 通常是打开了 libx264、libx265、libvpx、libmp3lame、libopus、libvorbis 这些外部编码器,所以你能直接-c:v libx264而不用自己编译。但 full 不等于全功能,硬件编码器(nvenc / qsv / amf)支不支持,取决于构建时有没有开对应的头文件依赖,这一点只能靠-encoders查,不能靠猜。

2.2 shared 还是 static:三条判断线

同一个版本一般有两种发布形态,shared 和 static,选错了会在分发环节翻车。

第一条线看分发。如果你只是想把一个转码工具塞进安装包给别人用,static 版本更省事,单文件拷走就能跑,不用管 dll 会不会漏。shared 版本把 exe 单独拷到另一台机器上,百分之百起不来,这是新手最常见的翻车点。

第二条线看二次开发。你要在自己的程序里调 libavformat 解封装、调 libavcodec 解码,shared 版本的优势就出来了:多个程序共享同一份 dll,内存里只映射一次,将来升级 FFmpeg 只要替换 bin 目录下的 dll,自己的程序不用重新编译。static 版本虽然也能链,但每个 exe 都会把几十兆的库代码抄一遍。

第三条线看调试。shared 版本出问题时报错信息更具体,因为符号还在 dll 里;static 版本一旦崩在库内部,堆栈信息会明显更难读。我一般做业务开发用 shared,做小工具对外分发用 static,两套并存,不冲突。

提示:shared 版本的 include/lib 和 bin 必须来自同一个压缩包。网上单独下的头文件配本地 dll,是最典型的“编译通过、运行段错误”来源。

2.3 挂 PATH 并做三条验证命令

不建议把 ffmpeg 装到C:\Program Files下面,路径里带空格会在后续写脚本时反复给你添麻烦。我的习惯是放到D:\tools\ffmpeg-4.4-full_build-shared,然后把 bin 加进 PATH。

临时生效的做法(只影响当前窗口,适合先试):

:: 只在当前 cmd 窗口有效,关掉就没了 set PATH=D:\tools\ffmpeg-4.4-full_build-shared\bin;%PATH% ffmpeg -version

PowerShell 里语法不一样,别混着抄:

# PowerShell 用 $env: 前缀,分号同样是路径分隔符 $env:PATH = "D:\tools\ffmpeg-4.4-full_build-shared\bin;" + $env:PATH ffmpeg -version

-version的输出值得逐行看:第一行是ffmpeg version 4.4...,确认版本没拿错;第二行开始是 libavcodec、libavformat、libavutil 各自的版本号,4.4 这一代对应的是 58、58、56 这一组主版本号,如果你看到的是 59 开头,说明 PATH 里混进了别的 FFmpeg,先把它清掉再继续。这一步看似多余,实际能省掉后面大量“为什么参数不生效”的排查时间。

确认版本后,再看构建配置,这决定了你到底能用哪些编码器:

:: 只看构建参数,-hide_banner 去掉版权横幅,输出干净些 ffmpeg -hide_banner -buildconf :: 看硬件加速方式,输出 cuda / qsv / d3d11va 之类 ffmpeg -hide_banner -hwaccels :: 过滤出含 264 的编码器,Windows 下用 findstr ffmpeg -hide_banner -encoders | findstr /i "264"

-buildconf打印的是--enable-xxx形式的编译开关,你能直接从里面看出 libx264、libx265 有没有编进去。-hwaccels只说明支持哪些硬件解码方式,不代表硬件编码器一定可用,这两件事经常被混淆。最后那条过滤命令在 PowerShell 里要换成Select-String -Pattern "264",findstr是 cmd 的工具,别在 PS 里硬用。

如果输出太长,还可以针对单个编码器查参数,这比翻文档快:

:: 查 libx264 支持的全部选项,包括 preset、profile、crf 相关说明 ffmpeg -hide_banner -h encoder=libx264

3. 命令行四件事:转码、抽帧、切片、硬编

bin 目录通顺之后,剩下 90% 的工作都能用四条命令解决。这里有个顺序问题:很多人是先搜到一条命令就照着跑,跑完发现画质糊了或者体积翻倍,再回头找原因。合理的做法是先查能力,再写参数,最后小样本试跑。下面每条命令都按这个顺序拆开讲。

3.1 先查能力再写命令,别拿教程当真理

-encoders的输出有三列:编码器标志、名称、描述。标志位里 V 表示视频、A 表示音频、S 表示字幕,后面跟的F表示支持帧级多线程,X表示实验性。真正要记的是名称那一列,libx264、libx265、libvpx-vp9、aac、libmp3lame都是可以直接写进-c:v/-c:a的值。硬件编码器的命名规则是<编码>_<厂商>,比如h264_nvenc、hevc_qsv,能不能用取决于-encoders里有没有这一行。

同理,封装格式用-muxers查,滤镜用-filters查。这三个命令构成了一套自查体系:报Unknown encoder就去-encoders里找正确名字,报Requested output format is not known就去-muxers里找,报No such filter就去-filters里确认滤镜名和参数顺序。养成这个习惯,比在搜索引擎里翻十页帖子快得多。

3.2 一条能当模板的转码命令,逐段拆参数

下面这条命令处理的是最常见需求:把一个来源不明的 mov 转成兼容性好的 mp4,分辨率限制到 1080p,音频统一成 AAC。

:: Windows cmd 的续行符是 ^,每行结尾不要有空格 ffmpeg -hide_banner -y -i "input.mov" ^ -c:v libx264 -preset medium -crf 20 -pix_fmt yuv420p ^ -vf "scale=-2:1080" ^ -c:a aac -b:a 128k -ac 2 -ar 44100 ^ -movflags +faststart ^ "output.mp4"

PowerShell 和 bash 里续行是反引号或反斜杠,别把^抄过去。

参数逐个说清楚:-y表示输出已存在时直接覆盖,不加会卡在交互式询问上,脚本里必加。-i放在所有输入相关参数之后、输出相关参数之前,位置错了会被当成输出文件。-c:v libx264指定视频编码器,-preset medium是编码速度与压缩率的平衡档,从 ultrafast 到 veryslow 共九档,日常用 medium 或 fast,赶时间就 veryfast。-crf 20是恒定质量模式,数值越小质量越高体积越大,18 到 23 是常用区间,动画类素材可以再低两档。

-pix_fmt yuv420p这一项经常被忽略,但它是播放器兼容性的关键。不少手机或录屏工具产出的是 yuv444p 或 yuvj420p,本地播放器能放,浏览器和移动端直接黑屏。强制转成 yuv420p 是最省事的解法。

-vf "scale=-2:1080"里的-2是个约定写法:宽度按比例自动算,并且保证结果是 2 的倍数。为什么要保证偶数,下一章的踩坑记录里会细说。

音频部分-c:a aac -b:a 128k -ac 2 -ar 44100,分别是编码器、码率、声道数、采样率。源文件是 5.1 声道时-ac 2会做下混,避免某些播放器只出人声不出环境音。-movflags +faststart把 moov box 挪到文件头,网页上边下边播必须加,本地播放加了也无害。

3.3 抽帧与切片:两个 %0Nd 占位符的坑

抽封面图和批量缩略图是内容侧的高频需求,命令本身很短:

:: 精确定位到第 10 秒,只取一帧,-q:v 2 是 JPEG 质量(2 最好,31 最差) ffmpeg -hide_banner -y -ss 00:00:10 -i "input.mp4" -frames:v 1 -q:v 2 "cover.jpg" :: 每 5 秒抽一帧,宽度缩到 320,高度按比例自动算 ffmpeg -hide_banner -y -i "input.mp4" -vf "fps=1/5,scale=320:-2" -q:v 3 "thumb_%04d.jpg"

-ss放在-i前面是快速定位,解码器会先跳到最近关键帧再修正,速度比放在后面快一个数量级。-frames:v 1限制只输出一帧,不加这个参数会一直解到文件结束,白白浪费 CPU。fps=1/5表示每 5 秒一帧,写成fps=1就是每秒一帧,一部十分钟的片子会吐出六百张图,磁盘瞬间见底。

%04d是序号占位符,位数不够会补零。位数要按预期最大数量选:只切十几个片段用%02d就够,抽几千张图必须%04d,否则排序会变成 1、10、11、2 这种。如果从中间开始抽,加-start_number 100指定起始序号。

切片用 segment 分离器,速度极快,因为不重新编码:

:: 每 60 秒切一段,-reset_timestamps 让每段从 0 开始计时 ffmpeg -hide_banner -y -i "input.mp4" -c copy -f segment -segment_time 60 -reset_timestamps 1 "part_%03d.mp4"

这里必须提醒:-c copy模式下切片点只能落在关键帧上,所以-segment_time 60切出来的片段可能是 58 秒也可能是 63 秒,不是精确切割。要精确到帧,就得去掉-c copy重新编码,或者加-force_key_frames "expr:gte(t,n_forced*60)"强制插入关键帧,代价是编码时间变长。选哪个取决于你的业务能不能接受几百毫秒的偏差。

3.4 硬件编码:先造一个测试源验证能不能用

很多人直接拿业务文件试硬件编码,失败后看到一堆驱动报错,不知道是参数问题还是显卡问题。更稳的做法是用 lavfi 造一个纯净的测试源,只验证编码器能不能初始化:

:: 造 3 秒 720p 测试画面,编码后直接丢弃,只看编码器能否启动 ffmpeg -hide_banner -f lavfi -i testsrc2=size=1280x720:rate=30 -t 3 -c:v h264_nvenc -f null -

-f lavfi是虚拟输入设备,testsrc2是内置的彩色测试图案,-f null -表示输出到空设备,不写磁盘。这条命令跑通,说明编码器可用,再把-c:v libx264换成-c:v h264_nvenc就能提速;跑不通,报错信息会明确告诉你卡在哪一步,通常是驱动版本太老或者显卡不支持该编码格式。

硬件编码的画质在同码率下一般不如 libx264,追求体积的场景建议还是软编。我通常的做法是脚本里先探测一次,能用就用硬编做粗剪和预览,最终成片仍然走 libx264。

4. 二次开发:把 include 和 lib 接进工程

命令行能覆盖大部分场景,但一旦要做自定义封装、需要在解码过程中拿到每一帧的 YUV 数据、或者要做低延迟的流式处理,就绕不开直接调库。shared 版本在这里的价值才真正体现出来:头文件和导入库都现成,不用自己搭交叉编译环境。

4.1 头文件版本与 ABI:soname 就是那道门槛

include 目录下是 libavcodec、libavformat、libavutil、libswscale、libswresample 这几个子目录的 .h 文件。每个头文件里都定义了LIBAVCODEC_VERSION_MAJOR这类宏,这个值必须和运行时 dll 的 soname 数字一致。4.4 对应的是 58,如果你哪天从别处拷了一份新版头文件,编译时用 59 的声明,链接时找 58 的 dll,最常见的表现是函数参数错位导致的崩溃,堆栈信息完全对不上,这种问题查一天也查不出来。

所以我一般在工程里做的第一件事,是写一个十行的版本打印程序,确认头文件和 dll 是同一套。别看这个动作很土,它能在项目初期把一半的环境问题挡掉。

4.2 最小验证程序:只调版本函数

/* ver_check.c —— 只依赖 libavutil/libavcodec/libavformat 的版本接口 */ #include <stdio.h> #include <libavutil/avutil.h> #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> int main(void) { /* av_version_info 返回构建时的版本字符串,最直观 */ printf("av_version_info : %s\n", av_version_info()); /* 这几个返回的是打包成整数的版本号,需要自己拆 */ unsigned int v = avcodec_version(); printf("avcodec_version : %u (major=%u minor=%u micro=%u)\n", v, v >> 16, (v >> 8) & 0xFF, v & 0xFF); printf("avformat_version: %u\n", avformat_version()); printf("avutil_version : %u\n", avutil_version()); return 0; }

这段代码的作用只有一个:确认编译期头文件和运行期 dll 对得上。av_version_info()返回的是字符串,直接看就知道是不是 4.4;avcodec_version()返回的是整数,按 16 位主版本、8 位次版本、8 位微版本的规则拆开,主版本应该是 58。如果打印出来是 59 或者直接崩在 printf 之前,说明 PATH 里混了别的 FFmpeg,或者 dll 放错了位置。

MinGW 下编译:

# -I 指向 include,-L 指向 lib,链接顺序按依赖从上层到下层 gcc ver_check.c -I"D:/tools/ffmpeg-4.4-full_build-shared/include" \ -L"D:/tools/ffmpeg-4.4-full_build-shared/lib" \ -lavformat -lavcodec -lavutil -o ver_check.exe

MSVC 的命令行是另一套写法:

:: cl 用 /I 和 /LIBPATH,导入库直接写文件名 cl /nologo /I"D:\tools\ffmpeg-4.4-full_build-shared\include" ver_check.c ^ /link /LIBPATH:"D:\tools\ffmpeg-4.4-full_build-shared\lib" ^ avformat.lib avcodec.lib avutil.lib

链接顺序这件事值得单独提一句:-lavformat依赖-lavcodec,-lavcodec依赖-lavutil,所以写的时候必须把上层放前面,反过来会报一堆 undefined reference。这个规则在 Linux 上人人都知道,到了 Windows 下用 MinGW 一样适用,但很多教程直接省略了,导致新手反复踩。

运行之前还要处理 dll。最省事的办法是把 bin 目录下所有 dll 拷到 exe 同目录,或者干脆把 exe 输出到 bin 目录里。把 bin 加进 PATH 也可以,但那意味着换台机器就得重配环境,交付时容易漏。

注意:lib 目录里导入库的命名随工具链变化,MinGW 生成的通常是 libavcodec.dll.a 这种形式,MSVC 用的可能是 avcodec.lib。以你解压后实际看到的文件名为准,别照抄别人命令里的名字。

4.3 不碰 C 也能做工程化:Python 侧调 exe

大部分业务场景其实不需要链库,用 Python 拼参数列表调用 ffmpeg.exe 就够了,关键是别用字符串拼接命令行。

import subprocess from pathlib import Path # 用原始字符串,避免 Windows 路径里的反斜杠被当成转义 FFMPEG = r"D:\tools\ffmpeg-4.4-full_build-shared\bin\ffmpeg.exe" def transcode(src: Path, dst: Path, crf: int = 20, preset: str = "medium") -> None: # 参数用列表传,不经过 shell,路径带空格也不用自己加引号 cmd = [ FFMPEG, "-hide_banner", "-y", "-i", str(src), "-c:v", "libx264", "-preset", preset, "-crf", str(crf), "-pix_fmt", "yuv420p", "-c:a", "aac", "-b:a", "128k", "-ac", "2", "-movflags", "+faststart", str(dst), ] # Windows 控制台默认不是 UTF-8,显式指定编码并容错,避免中文路径报错 proc = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="replace") if proc.returncode != 0: # 只保留 stderr 尾部,FFmpeg 的报错一般在最后几十行 raise RuntimeError(f"转码失败: {src.name}\n{proc.stderr[-2000:]}") if __name__ == "__main__": transcode(Path("input.mov"), Path("output.mp4"), crf=22)

这段代码有三个关键点。第一,cmd是列表而不是字符串,shell 不参与解析,路径里的空格、括号、中文都不会出问题,这比手写引号可靠得多。第二,capture_output=True把 FFmpeg 的 stderr 抓下来,失败时截取尾部打印,因为 FFmpeg 的报错信息就在最后几行,前面全是进度输出。第三,encoding="utf-8", errors="replace"是针对 Windows 控制台的,FFmpeg 输出里混着中文路径时,不指定编码可能直接抛 UnicodeDecodeError,把真正的错误掩盖掉。

需要进度反馈的话,可以加-progress pipe:1 -nostats,把进度以 key=value 形式输出到 stdout,边读边解析。这个用法在批量任务里很有用,但不是必需,先跑通再说。

5. 避坑排查:五个真实翻车现场

环境搭起来、命令能跑通,不代表不会出事。下面这五条是我在实际项目里反复遇到的,每一条都按“现象 → 原因 → 解决”写清楚,遇到类似问题可以直接对照。

5.1 双击没反应,拷到服务器报找不到 avcodec-58.dll

现象:在本地 cmd 里ffmpeg -version正常,把 ffmpeg.exe 单独拷到另一台机器或者塞进安装包,运行时报“无法启动此程序,因为计算机中丢失 avcodec-58.dll”,或者报 0xc000007b。

原因:shared 版本的 exe 只是个壳,真正的代码在 dll 里。单独拷 exe 等于只拷了入口,依赖全丢了。0xc000007b 更典型,是 32 位和 64 位混用,比如 64 位 exe 配了 32 位 dll,或者反过来。

解决:分发时把 bin 目录整个打包,不要只拿 exe。如果必须精简,至少保证八个核心 dll 齐全,并且位数一致。用dumpbin /headers ffmpeg.exe | findstr machine能看到架构,x64 和 x86 一对比就清楚了。我现在的习惯是交付前在一台干净虚拟机上解压跑一遍,比在开发机上测十遍都管用。

5.2 照抄新教程,报 Unrecognized option 'fps_mode'

现象:从网上抄了一条带-fps_mode cfr的命令,4.4 直接报Unrecognized option 'fps_mode',命令根本没开始执行。

原因:-fps_mode是较新版本才引入的参数,用来替代老的-vsync。4.4 这条线上还没有它,只有-vsync。

解决:把-fps_mode cfr改成-vsync cfr,-fps_mode vfr对应-vsync vfr,-fps_mode passthrough对应-vsync passthrough。这个坑的本质是版本和文档不匹配,搜到的东西要先看它对应的版本号。同理,一些新滤镜和新编码器参数在 4.4 上也不存在,报 Unknown option 时先查-h输出,别急着怀疑环境。

5.3 libx264 报 height not divisible by 2

现象:转码到一半中断,报[libx264] height not divisible by 2,前面已经编码的帧全废。

原因:yuv420p 是色彩二次采样格式,色度平面的宽高都是亮度平面的一半,所以宽高都必须是偶数。源文件是 1081x1921 这种奇数尺寸时,直接编码就会失败。

解决:在-vf里加缩放,scale=-2:1080或scale=1920:-2。这里的-2表示按比例计算并取最近的偶数,-1表示按比例计算但不保证偶数(可能仍然报错),-2才是安全写法。如果要保持原分辨率不变,用scale=trunc(iw/2)*2:trunc(ih/2)*2,这是老办法,效果一样但可读性差一些。这个坑在手机竖屏视频和截图录屏上特别常见。

5.4 -c copy 截取后开头花屏、音画不同步

现象:用-ss 30 -i in.mp4 -c copy out.mp4截出来的片段,开头几秒画面是花的或者黑屏,声音提前或延后,播放器拖进度条后更明显。

原因:-c copy不重新编码,只能从关键帧开始切。-ss指定的时间点如果不是关键帧,FFmpeg 会从上一个关键帧开始拷贝,但时间戳仍然按 30 秒算,于是开头多出一段负时间戳的数据,播放器处理方式各不相同,表现就是花屏或者不同步。

解决:三个方向。要么去掉-c copy重新编码,时间精确但慢;要么加-avoid_negative_ts make_zero,把负时间戳归零,能缓解大部分花屏;要么把-ss放到-i后面做精确解码定位,代价是速度慢。我一般对时长要求不严的切片用第二种,对精度有要求的直接重编码,别在这上面省时间。

5.5 路径带空格或中文,脚本报 No such file or directory

现象:手动在 cmd 里跑没问题,写到脚本里就报找不到文件,或者输出文件名变成了一串乱码。

原因:三种情况混在一起。一是路径里有空格没加引号,被拆成了多个参数;二是输出文件名没有扩展名,FFmpeg 猜不出封装格式;三是脚本文件本身的编码不是 UTF-8,中文路径写进去就已经坏了。

解决:路径统一加双引号,Python 里用列表传参彻底回避引号问题;输出文件必须带扩展名,不确定时显式加-f mp4指定封装格式;脚本文件保存为 UTF-8 无 BOM,cmd 里先执行chcp 65001切到 UTF-8 代码页。这一条看起来琐碎,但它是批处理任务失败率最高的原因,没有之一。

6. 批量流水线:让“转完了”等于“转对了”

单文件跑通之后,真正的活是几百上千个文件。我现在的做法是分两步:先批量转码,再用 ffprobe 做一次机器验收。这两步分开写,是因为转码成功不等于结果正确,编码器返回 0 只代表进程没崩,不代表分辨率、帧率、时长符合预期。

批量部分用一个很短的脚本收口,重点是幂等——已经处理过的文件跳过,中断后能续跑。

from pathlib import Path import subprocess, json FFMPEG = r"D:\tools\ffmpeg-4.4-full_build-shared\bin\ffmpeg.exe" FFPROBE = r"D:\tools\ffmpeg-4.4-full_build-shared\bin\ffprobe.exe" def probe(path: Path) -> dict: # 只取第一个视频流和容器信息,输出 JSON 方便程序判断 cmd = [FFPROBE, "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=codec_name,width,height,r_frame_rate,pix_fmt", "-show_entries", "format=duration,bit_rate", "-of", "json", str(path)] out = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="replace") return json.loads(out.stdout or "{}") def batch(src_dir: Path, dst_dir: Path): dst_dir.mkdir(parents=True, exist_ok=True) for src in sorted(src_dir.glob("*.mov")): dst = dst_dir / (src.stem + ".mp4") if dst.exists() and dst.stat().st_size > 1024: continue # 已存在且非空,直接跳过,保证可续跑 subprocess.run([FFMPEG, "-hide_banner", "-y", "-i", str(src), "-c:v", "libx264", "-preset", "fast", "-crf", "22", "-pix_fmt", "yuv420p", "-vf", "scale=-2:1080", "-c:a", "aac", "-b:a", "128k", "-ac", "2", str(dst)], check=True) info = probe(dst) print(src.name, "->", info.get("format", {}).get("duration"), "s")

验收环节我固定看几个字段,写在表格里方便对照:

字段期望值不符合说明什么
codec_nameh264编码器参数没生效或被覆盖
pix_fmtyuv420p兼容性风险,浏览器可能黑屏
width / height均为偶数,高度 1080缩放表达式写错
r_frame_rate与源一致或 25/30帧率被误改,常见于 -r 滥用
duration与源相差小于 1 秒截断或时间戳异常

这套流程跑下来,最大的收获不是脚本本身,而是把“转码”从一个动作变成了一个有输入校验、有输出验收的环节。我早年图省事,几百个文件转完直接上传,用户反馈有十几个是黑屏的,回头一个个排查,发现全是 pix_fmt 不是 yuv420p。从那以后每次批量处理,我都强制先跑十个样本,用 ffprobe 逐条对一遍字段再放开跑,宁可多花五分钟,也不愿意再返工一遍。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进