简介:面向32位Windows平台的多媒体处理工具包,适合需要在本地完成音视频转码、剪辑、流传输的开发者与运维人员使用。压缩包为FFmpeg 4.2.1共享库版本,共52个文件、约22.8MB,包含8个dll动态链接库、3个exe可执行程序、5个ffpreset编码预设,以及30个html离线文档和少量css、txt说明文件,bin、lib、include、doc目录划分清晰,便于按用途取用。其中dll对应avcodec、avformat、avfilter等核心组件,exe提供命令行入口,html文档覆盖编解码、滤镜、协议等专题,可离线查阅。已有705人学习下载。读者可据此在32位Windows环境中配置PATH后直接调用ffmpeg、ffplay、ffprobe,完成格式转换、音频提取、视频裁剪与设备采集等任务,也可借助头文件与库文件在C/C++项目中集成编解码能力,省去自行编译的时间成本。
1. 拿到 ffmpeg-4.2.1-win32-shared.zip 先别急着解压:它到底解决什么问题
上周有个做桌面录屏工具的同行发来一段报错,说程序在自己机器上跑得好好的,拷到客户那台老 Win7 上就提示找不到 avcodec-58.dll。他一开始以为是杀毒软件误删,折腾半天才发现是打包时把 ffmpeg 的静态库和动态库搞混了。这类问题在 Windows 上做音视频开发的人几乎都撞过,而ffmpeg-4.2.1-win32-shared.zip这个包,恰好就是冲着「动态链接、32 位、Windows 原生」这三个关键词来的。ffmpeg 本身不用多介绍,转码、抽帧、推流、滤镜、封装解封装,命令行和库两条路都能走;但真正让人翻车的往往不是 API 怎么调,而是拿到手的二进制包结构对不对、DLL 放没放对位置、32 位和 64 位有没有串。这个 shared 包的价值就在于:它把 ffmpeg 4.2.1 这一版的动态库、可执行文件和头文件按 Windows 32 位的方式组织好了,适合需要在自己程序里动态调用 libav* 系列、又不想从源码编译的从业者。如果你只是偶尔用命令行转个格式,用静态版或系统里现成的也行;但只要涉及把 ffmpeg 嵌进 32 位宿主程序、或者要控制 DLL 的分发,这个包就是省掉一整天编译折腾的起点。下面按「包里有什么、怎么接进工程、怎么排错、怎么验证」的顺序拆。
2. 拆开 shared 包:目录结构、DLL 依赖与 32 位选型理由
2.1 shared 与 static 的差别,以及为什么偏偏选 win32
ffmpeg 的 Windows 预编译包通常分 static、shared 两种。static 版把 libavcodec、libavformat 这些全部静态链进 exe,好处是单文件分发、不依赖外部 DLL,坏处是体积大、多个程序各带一份、升级要重新链接。shared 版则把核心库编成avcodec-58.dll、avformat-58.dll、avutil-56.dll、swscale-5.dll、swresample-3.dll这类动态库,exe 只保留导入表,运行时按名字去找 DLL。选 shared 的典型场景是:宿主程序本身已经是 32 位(比如某些老工业软件、老版本宿主、32 位插件体系),或者你希望多个模块共用同一份 ffmpeg 运行时、只换 DLL 不重编主程序。
这里必须把「win32」三个字说透。win32 在 ffmpeg 预编译命名里指 32 位 Windows 目标,不是指 Win32 API 那层含义。32 位进程只能加载 32 位 DLL,64 位进程只能加载 64 位 DLL,混用会直接报「不是有效的 Win32 应用程序」或「模块与进程位数不匹配」。所以拿到这个包,第一件事是确认你的宿主进程是 32 位。用任务管理器看进程后面有没有「(32 位)」,或者在自己的程序里打印指针宽度:
#include <stdio.h> int main(void) { // 指针 4 字节即 32 位进程,8 字节即 64 位 printf("pointer size = %zu\n", sizeof(void *)); return 0; }这段代码只做一件事:用指针宽度判断当前编译目标位数。sizeof(void *)在 32 位下是 4,64 位下是 8。如果你编译出来是 8,却硬要加载这个 win32 包里的 DLL,运行期一定失败,别在这上面浪费时间。常见做法是工程属性里把平台固定为 Win32/x86,再链接对应的导入库。
2.2 包内典型布局与各目录职责
shared 包解压后一般能看到三类东西:bin/放 exe 和 DLL,lib/放导入库(.lib)和静态库,include/放头文件。不同打包者的命名略有差异,但职责固定。下面这张表是我按常见布局整理的对照,拿到包先按它核对一遍,缺哪个目录后面就会卡在哪一步。
| 路径 | 内容 | 用途 |
|---|---|---|
| bin/ | ffmpeg.exe、ffprobe.exe、ffplay.exe 及 av*.dll | 命令行工具与运行时动态库 |
| bin/ 下 DLL | avcodec-58、avformat-58、avutil-56、swscale-5、swresample-3 等 | 程序运行时按名加载 |
| lib/ | avcodec.lib、avformat.lib 等导入库 | 链接期解析符号 |
| include/ | libavcodec/、libavformat/、libavutil/ 等头文件 | 编译期声明 |
需要提醒的是,4.2.1 这一代的主版本号是 avcodec 58、avformat 58、avutil 56、swscale 5、swresample 3,DLL 文件名里带的就是这些数字。你链接的.lib和运行时加载的.dll必须来自同一份包,主版本号对不上会在加载阶段直接报「找不到指定的模块」或符号解析失败。我见过有人头文件用一套、DLL 用另一套,编译能过、一跑就崩,血泪经验就是版本号必须整包对齐。
2.3 用 dumpbin 和 ffmpeg -version 做开箱体检
解压后别急着写代码,先做两项体检。第一项看 DLL 的位数和导出符号,第二项看命令行工具自报的配置。用 Visual Studio 自带的 dumpbin:
# 查看 DLL 是 32 位还是 64 位,看 machine 一行 dumpbin /headers avcodec-58.dll | findstr machine # 查看导出的关键函数是否存在 dumpbin /exports avcodec-58.dll | findstr avcodec_send_packet/headers输出里 machine 显示 x86 即 32 位,x64 即 64 位;/exports用来确认你要调的符号确实被导出。第二项直接跑命令行:
ffmpeg -version ffprobe -version输出里会带版本号和编译配置(configuration 一行),能看出是否启用了你要的编码器、协议。如果ffmpeg -version都跑不起来,先查 PATH 和 DLL 是否同目录,别往下走。这两步加起来不到两分钟,却能挡掉后面大半的「玄学」问题。
3. 把 shared 包接进 32 位工程:头文件、导入库与运行时 DLL 三步走
3.1 编译期:include 路径与 lib 路径怎么配
以常见的 C/C++ 工程为例,编译期要做的是让编译器找到头文件、让链接器找到导入库。假设包解压到D:\sdk\ffmpeg-4.2.1-win32-shared,工程里加两条路径即可。用命令行编译(MinGW 或 cl 都类似)可以这样写:
# 以 MinGW-w64 32 位工具链为例 gcc main.c -o demo.exe \ -ID:/sdk/ffmpeg-4.2.1-win32-shared/include \ -LD:/sdk/ffmpeg-4.2.1-win32-shared/lib \ -lavformat -lavcodec -lavutil -lswscale -lswresample-I指定头文件根目录,-L指定导入库目录,-l按依赖顺序列出库名。顺序有讲究:调用方在前、被依赖方在后,一般 avformat 依赖 avcodec,avcodec 依赖 avutil,所以 avformat 写在最前、avutil 放最后。顺序写反会出现大量 undefined reference。用 Visual Studio 的话,等价操作是在「附加包含目录」填 include、「附加库目录」填 lib、「附加依赖项」填avformat.lib;avcodec.lib;avutil.lib;swscale.lib;swresample.lib。参数含义和命令行一致,只是换了界面。
3.2 链接期:导入库不是静态库,别重复链接
这里有个高频误解:lib/下的.lib在 shared 包里是导入库(import library),它只记录「这个符号在哪个 DLL 里」,本身不含实现代码。所以它体积很小,链接时也不会把 ffmpeg 代码塞进你的 exe。有人看到.lib小就以为下错了包,又去找静态库,结果把 static 版的.lib链进来,和 shared 的 DLL 混用,运行期出现重复符号或堆损坏。判断方法很简单:导入库通常几十到几百 KB,静态库动辄几十 MB。链接成功后,你的 exe 导入表里会出现avcodec-58.dll这类条目,用 dumpbin 一看便知:
dumpbin /dependents demo.exe输出里列出 exe 运行时要找的 DLL。如果这里出现的是 avcodec-58.dll 而不是一堆静态符号,说明链接方式对了。这一步是区分「真 shared」和「假 shared」的关键,别跳过。
3.3 运行期:DLL 搜索顺序与部署位置
链接通过只是编译期的事,程序一启动,Windows 按自己的顺序找 DLL:exe 所在目录 → 系统目录 → PATH 里的目录。最稳的做法是把bin/下的 av*.dll 和你的 exe 放同一目录,或者放一个子目录再在启动时SetDllDirectory指过去。不要指望开发机上能跑就等于客户机能跑——开发机往往因为装过别的软件、PATH 里有旧版 ffmpeg 而「碰巧」加载成功,这就是最坑的玄学现场。部署时把需要的 DLL 列个清单,和 exe 一起打包:
# 把运行时 DLL 拷到输出目录(示例,按实际包内文件名调整) copy D:\sdk\ffmpeg-4.2.1-win32-shared\bin\av*.dll .\dist\ copy D:\sdk\ffmpeg-4.2.1-win32-shared\bin\sw*.dll .\dist\拷完用dumpbin /dependents对照一遍,确保 exe 导入表里的每个 av*/sw* DLL 都在 dist 目录里。少一个,客户机上就是一句冷冰冰的「找不到指定的模块」。我一般还会在程序启动时主动探测一次版本,把av_version_info()打出来,出问题时一眼能看出加载的是哪份运行时。
4. 最小可运行示例:用 libavformat 打开一个文件并打印流信息
4.1 代码骨架与关键调用
光配好环境不算落地,得跑通一个最小闭环。下面这段用 libavformat 打开媒体文件、遍历流、打印编解码器信息,覆盖了注册、打开、读流、释放四个环节,是验证 shared 包是否接对的常用手段:
#include <stdio.h> #include <libavformat/avformat.h> int main(int argc, char *argv[]) { if (argc < 2) { printf("usage: %s <file>\n", argv[0]); return 1; } // 4.x 起 av_register_all 已废弃,打开时自动初始化,这里不再调用 AVFormatContext *fmt = NULL; int ret = avformat_open_input(&fmt, argv[1], NULL, NULL); if (ret < 0) { char err[128]; av_strerror(ret, err, sizeof(err)); printf("open failed: %s\n", err); return 1; } ret = avformat_find_stream_info(fmt, NULL); if (ret < 0) { printf("find_stream_info failed\n"); avformat_close_input(&fmt); return 1; } printf("format=%s duration=%lld us\n", fmt->iformat->name, (long long)fmt->duration); for (unsigned i = 0; i < fmt->nb_streams; i++) { AVStream *st = fmt->streams[i]; // 4.x 用 codecpar 取参数,codec 上下文需单独分配 printf("stream %u type=%d codec_id=%d\n", i, st->codecpar->codec_type, st->codecpar->codec_id); } avformat_close_input(&fmt); return 0; }逻辑上分四段:avformat_open_input打开容器并探测格式,avformat_find_stream_info补全流参数(有些容器头部信息不全,必须读一段数据才能拿到真实参数),循环里通过codecpar读编解码参数,最后avformat_close_input释放。4.2.1 属于 4.x,av_register_all已经不需要显式调用,网上很多老教程还在写它,照抄会编译告警甚至链接失败,这是版本差异带来的典型坑。
4.2 参数与返回值怎么读
avformat_open_input的第三个参数可以强制指定输入格式,传 NULL 表示自动探测;第四个参数是 AVDictionary,用来传超时、缓冲区大小等选项,比如设rtsp_transport或probesize。返回值小于 0 表示失败,务必用av_strerror转成可读文本,否则你只能看到一个负数,排错全靠猜。avformat_find_stream_info的第二个参数同样是选项字典,网络流场景下可以调大analyzeduration和probesize来提高探测成功率。fmt->duration单位是微秒(AV_TIME_BASE),打印时别当成毫秒。这些参数在 4.2.1 上行为稳定,换到更高主版本时字段和函数可能有变动,所以整包版本对齐这件事在示例里同样成立。
4.3 编译运行与预期输出
把上面的代码存成probe.c,用 3.1 节的命令编译,运行时把 DLL 放同目录,然后拿一个 mp4 试:
demo.exe sample.mp4正常输出类似format=mov,mp4,m4a,3gp,3g2,mj2 duration=...,下面跟着若干stream 0 type=0 codec_id=...。如果打开就失败,先看错误文本是「No such file」还是「Invalid data」,前者查路径,后者查文件是否完整或格式是否被这个包支持。如果编译期报找不到avformat.h,回头查-I路径是不是指到了 include 的上一层;如果链接报 undefined reference,查-l顺序和库名拼写。这个最小示例跑通,说明头文件、导入库、运行时 DLL 三层都对齐了,后面再上转码、滤镜才有意义。
5. 避坑与排查:shared 包在 Windows 上最容易翻车的五件事
5.1 现象:程序在开发机能跑,拷到别的机器报「找不到指定的模块」
原因几乎总是运行时 DLL 没跟着走,或者目标机 PATH 里有一份旧版 ffmpeg 抢先被加载。Windows 的 DLL 搜索顺序里 exe 目录优先,但如果你把 DLL 放在子目录又没设搜索路径,就会落到系统目录或 PATH 去找,找到的可能是别的软件带的旧版本。解决办法是把 av*.dll 和 exe 放同一目录,部署清单里逐个核对dumpbin /dependents的输出;需要放子目录时,启动早期调用SetDllDirectory指向该目录,别依赖当前工作目录。
5.2 现象:编译通过,一运行就崩溃在 avcodec 相关调用
原因多半是头文件、导入库、DLL 三者版本不一致。比如头文件是 4.2.1、DLL 却是别的 4.x 小版本甚至 5.x,结构体布局变了,传参就踩内存。另一种情况是把 static 版的.lib和 shared 版 DLL 混链,出现重复符号或堆不一致。解决方式是整包使用,头文件、lib、dll 全部来自同一个 zip;升级时三样一起换,不要只换 DLL。用av_version_info()在启动日志里打出运行时版本,和编译期LIBAVCODEC_VERSION_INT对照,能快速定位。
5.3 现象:链接报一堆 undefined reference 或 LNK2019
原因通常是库顺序不对或漏库。MinGW 下-l顺序要从调用方到被依赖方,avformat 在前、avutil 在后;MSVC 下附加依赖项里漏了 swscale 或 swresample,而代码里又调了缩放或重采样。还有一种隐蔽情况:工程是 C++ 编译,头文件没加extern "C",导致符号名被 C++ 修饰,链接器找不到 C 符号。ffmpeg 头文件本身一般带extern "C"保护,但如果你自己包了一层头,记得补上。解决就是按依赖顺序列全库,并确认编译语言与符号约定一致。
5.4 现象:32 位程序加载 DLL 报「不是有效的 Win32 应用程序」
原因就是位数不匹配,拿 64 位 DLL 喂给 32 位进程,或者反过来。这个包是 win32,只能给 32 位进程用。排查用dumpbin /headers看 machine 字段,x86 才是 32 位。另一个容易忽略的点是工具链本身:用 64 位编译器编出来的 exe 去链 32 位导入库,链接期就可能报错,或者勉强过了运行期也加载不了。解决是把工程平台固定为 Win32/x86,工具链也用对应的 32 位版本,别在位数上省事。
5.5 现象:命令行 ffmpeg.exe 能转码,自己程序里同样的参数却失败
原因往往在选项传递方式。命令行里-probesize、-analyzeduration是字符串解析,程序里要通过 AVDictionary 以键值对传入,键名和命令行一致但值要转成字符串;另外命令行默认会做很多隐式处理(比如自动选编码器、自动重采样),程序里这些都要自己补。还有一种情况是命令行工具和 DLL 不是同一份包,行为自然对不上。解决办法是先用 ffprobe 确认输入参数,再在代码里显式设置字典项,逐项对照命令行行为,别假设「命令行能跑代码就能跑」。
6. 进阶验证:用 av_version_info 与依赖清单把「能跑」变成「可交付」
把示例跑通只是自测,真正交付前我习惯做两件事:版本自证和依赖固化。版本自证是在程序启动时打印编译期与运行期版本,二者不一致就早失败早暴露:
#include <libavutil/avutil.h> #include <libavcodec/avcodec.h> void log_ffmpeg_version(void) { // 运行期实际加载的版本 printf("runtime ffmpeg: %s\n", av_version_info()); // 编译期头文件版本,用于对照 printf("compile avcodec: %d.%d.%d\n", LIBAVCODEC_VERSION_MAJOR, LIBAVCODEC_VERSION_MINOR, LIBAVCODEC_VERSION_MICRO); }av_version_info()返回运行期加载的库版本字符串,LIBAVCODEC_VERSION_*是编译时头文件里的宏。两者主版本号对不上,说明头文件和 DLL 不是一套,直接判定环境有问题。这一步在客户现场排障时特别省事,日志里一眼就能看出加载的是哪份运行时,不用远程猜。
依赖固化则是把dumpbin /dependents的输出整理成部署清单,和安装包一起维护。下面这张表是我常用的核对项,交付前逐行打勾:
| 核对项 | 方法 | 通过标准 |
|---|---|---|
| exe 位数 | dumpbin /headers demo.exe | machine 为 x86 |
| 运行时 DLL 齐全 | dumpbin /dependents demo.exe | 每个 av*/sw* 都在 dist |
| DLL 位数一致 | dumpbin /headers avcodec-58.dll | machine 为 x86 |
| 版本一致 | 启动日志 av_version_info | 与编译期主版本一致 |
| 符号可解析 | 运行最小示例 | 打开文件并打印流信息成功 |
这套流程看着笨,但能挡掉绝大多数「开发机能跑、交付就崩」的翻车。我早年图省事,只在自己机器上验证就打包发出去,结果客户机因为 PATH 里有一份旧版 ffmpeg 抢先加载,排查了两天才定位到 DLL 搜索顺序上。从那以后我每次交付前都强制走一遍版本自证加依赖清单,哪怕只是换了个小版本也照做。希望帮到你。
本文还有配套的精品资源,点击获取