简介:ffmpeg-4.2.1-win32-shared.zip 是面向 32 位 Windows 平台的多媒体处理工具包,适合需要在本地完成音视频转码、剪辑、合并与流传输的开发者及运维人员使用。该版本为共享库构建,核心组件涵盖命令行工具 ffmpeg、ffplay、ffprobe,以及 libavcodec、libavformat、libavfilter、libswscale、libswresample、libavdevice 等动态链接库,可满足编解码、容器封装、滤镜处理、色彩空间转换与音频重采样等需求。压缩包共 52 个文件,以 8 个 dll 动态库、3 个 exe 可执行程序、30 个 html 离线文档及 5 个 ffpreset 预设为主,整体约 22.8MB,目录包含 bin、lib、include、doc 等,结构清晰,便于按模块查阅。目前已有 704 人学习下载。借助随附的离线手册与预设文件,读者可快速定位各组件用法,在 C/C++ 项目中链接头文件与库,或将库路径加入 PATH 后直接调用命令行完成多媒体处理任务。
1. 拿到 ffmpeg-4.2.1-win32-shared.zip 之后:为什么老项目还在用它
如果你手上有一个ffmpeg-4.2.1-win32-shared.zip,大概率不是你自己主动去官网翻出来的,而是被某个老项目、某份遗留文档或者某个只在特定环境下跑得起来的工具链“绑”过来的。这个包的本质是 Windows 32 位平台的 FFmpeg 4.2.1 动态链接库版本,解压后你会看到bin、lib、include三个目录,bin里是ffmpeg.exe、ffprobe.exe、ffplay.exe以及一堆avcodec-58.dll、avformat-58.dll这样的运行时库。它解决的核心问题是:你需要在 32 位 Windows 进程里调用 FFmpeg 做转码、抽帧、封装格式转换,但又不想自己从源码编译一遍。适合谁?适合维护老桌面软件、做工业相机采集回放、给 32 位宿主程序做音视频插件的那批人。32 位这个限制在今天听起来有点“复古”,但很多工控机、老版采集卡驱动、甚至某些嵌入式 Windows 环境就是只认 32 位,你绕不过去。
2. 解压之后先别急着写代码:目录结构与链接方式选型
2.1 shared 版和 static 版的区别,以及为什么你只能选 shared
FFmpeg 的 Windows 构建通常分 static、shared、dev 三种包。ffmpeg-4.2.1-win32-shared.zip里的bin目录同时包含可执行文件和 DLL,lib目录里是导入库(.lib),include里是头文件。shared 意味着你的程序在运行时必须能加载到那些av*.dll,否则一启动就报“找不到 avcodec-58.dll”。static 版会把所有代码编进你的 exe,体积大但部署简单。你拿到的是 shared,所以部署时 DLL 必须跟着 exe 走,或者放进系统能搜索到的目录。
选型理由很直接:如果你要在一个 32 位宿主里做插件,宿主本身可能已经加载了不同版本的 FFmpeg DLL,这时候用 shared 版反而容易冲突;但如果你是从零写一个 32 位小工具,shared 版编译快、链接快,改一行代码重新链接不用等静态库那漫长的编译。我一般会先确认宿主进程的位数,32 位宿主绝对不能链接 64 位库,这是血泪经验,链接阶段可能不报错,运行到avformat_open_input直接崩。
2.2 把 bin 目录加进 PATH 还是拷到 exe 旁边
部署时有两个选择:把bin目录加到系统 PATH,或者把需要的 DLL 拷到你的 exe 同目录。前者适合开发机,后者适合交付。交付时不要偷懒只拷ffmpeg.exe,你的程序调的是avcodec-58.dll这些,不是 exe。一个稳妥的做法是写个批处理,把bin下所有av*.dll和sw*.dll拷到输出目录。
# 假设解压到了 D:\libs\ffmpeg-4.2.1-win32-shared # 把运行时 DLL 拷到你的程序输出目录 set FFMPEG_BIN=D:\libs\ffmpeg-4.2.1-win32-shared\bin set OUT_DIR=D:\my_project\build\Release copy "%FFMPEG_BIN%\av*.dll" "%OUT_DIR%" copy "%FFMPEG_BIN%\sw*.dll" "%OUT_DIR%" copy "%FFMPEG_BIN%\ffmpeg.exe" "%OUT_DIR%"这段脚本的逻辑是:av*.dll覆盖avcodec、avformat、avutil、avfilter、avdevice,sw*.dll覆盖swscale、swresample。ffmpeg.exe拷过去是为了让你能在命令行里快速验证,不是程序运行必需。参数上注意OUT_DIR要指向你实际的构建输出目录,Debug 和 Release 别搞混,否则你会遇到“明明拷了 DLL 还是找不到”的玄学问题。
2.3 用 dumpbin 确认 DLL 的位数和依赖
在链接之前,先确认你拿到的 DLL 确实是 32 位。用 Visual Studio 自带的dumpbin工具:
dumpbin /headers D:\libs\ffmpeg-4.2.1-win32-shared\bin\avcodec-58.dll | findstr machine如果输出里有machine (x86),说明是 32 位;如果是x64,那这个包就不是你标题里那个 win32 版本,或者你下错了。这一步能帮你排除掉一半的“链接不上”问题。另外dumpbin /dependents可以看 DLL 还依赖哪些系统库,4.2.1 这个版本在 Windows 上通常只依赖kernel32、user32、ws2_32这些,不会有什么奇怪的第三方依赖。
3. 在 32 位 C++ 工程里链接 FFmpeg 4.2.1 的最小闭环
3.1 配置 include 和 lib 路径
假设你用 Visual Studio 建了一个 Win32 Console Application。在项目属性里,C/C++ → 常规 → 附加包含目录,加上D:\libs\ffmpeg-4.2.1-win32-shared\include。链接器 → 常规 → 附加库目录,加上D:\libs\ffmpeg-4.2.1-win32-shared\lib。然后在链接器 → 输入 → 附加依赖项里填:
avcodec.lib avformat.lib avutil.lib swscale.lib swresample.lib注意顺序,avformat依赖avcodec,avcodec依赖avutil,虽然 MSVC 的链接器对顺序不如 GCC 那么敏感,但把基础库放后面是个好习惯。如果你还用了avfilter,就再加avfilter.lib。
3.2 一个能跑通的最小转码调用
下面这段代码不追求功能完整,只验证链接和运行时加载是否正常。它打开一个输入文件,打印流信息,然后关闭。
extern "C" { #include <libavformat/avformat.h> #include <libavcodec/avcodec.h> } #include <iostream> int main(int argc, char* argv[]) { if (argc < 2) { std::cerr << "usage: probe <input>" << std::endl; return -1; } // 4.2.1 里 av_register_all 还存在,但已经标记为废弃 // 为了兼容老代码,这里仍然调用,新代码可以省略 av_register_all(); AVFormatContext* fmt_ctx = nullptr; int ret = avformat_open_input(&fmt_ctx, argv[1], nullptr, nullptr); if (ret < 0) { char errbuf[AV_ERROR_MAX_STRING_SIZE] = {0}; av_strerror(ret, errbuf, sizeof(errbuf)); std::cerr << "open input failed: " << errbuf << std::endl; return -1; } ret = avformat_find_stream_info(fmt_ctx, nullptr); if (ret < 0) { std::cerr << "find stream info failed" << std::endl; avformat_close_input(&fmt_ctx); return -1; } std::cout << "format: " << fmt_ctx->iformat->name << std::endl; std::cout << "duration: " << fmt_ctx->duration / AV_TIME_BASE << " s" << std::endl; std::cout << "streams: " << fmt_ctx->nb_streams << std::endl; for (unsigned int i = 0; i < fmt_ctx->nb_streams; i++) { AVStream* st = fmt_ctx->streams[i]; if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { std::cout << "video stream " << i << " " << st->codecpar->width << "x" << st->codecpar->height << " codec_id=" << st->codecpar->codec_id << std::endl; } } avformat_close_input(&fmt_ctx); return 0; }逻辑说明:av_register_all在 4.2.1 里还能用,但如果你写新代码,直接调avformat_open_input就行,4.0 之后大部分注册已经自动化了。avformat_open_input的第三个参数是输入格式,传nullptr让 FFmpeg 自己探测。avformat_find_stream_info会读一小段数据来填充流参数,对某些裸流格式(比如 h264)必须调,否则codecpar里宽高可能是 0。错误处理用av_strerror把负数错误码转成可读字符串,比直接打印ret有用得多。
参数上,AV_TIME_BASE是 1000000,duration单位是微秒,除以它得到秒。codecpar是 4.0 之后替代codec字段的结构,老代码里常见的st->codec->width在 4.2.1 里还能编译,但会有废弃警告,建议直接用codecpar。
3.3 编译时常见的 LNK2019 和运行时 0xc000007b
LNK2019 通常是库没链上或者位数不对。先检查附加依赖项有没有拼错,再确认lib目录下的.lib是不是 32 位。运行时0xc000007b是典型的“64 位进程加载了 32 位 DLL”或者反过来,用dumpbin确认你的 exe 和 DLL 位数一致。还有一个坑:如果你把ffmpeg.exe和你的程序放在同一目录,而ffmpeg.exe依赖的 DLL 版本和你的程序依赖的不一致,Windows 会优先加载 exe 同目录的 DLL,导致你的程序行为异常。交付时要么只拷 DLL 不拷 exe,要么确保版本完全一致。
4. 用 ffprobe 和 ffmpeg.exe 做快速验证与批量抽帧
4.1 先用 ffprobe 确认文件能读
在写代码之前,用包里的ffprobe.exe验证一下你的测试文件:
D:\libs\ffmpeg-4.2.1-win32-shared\bin\ffprobe.exe -v error -show_entries stream=index,codec_name,width,height -of csv input.mp4-v error只打印错误,-show_entries控制输出字段,-of csv让输出适合脚本解析。如果这一步就报错,说明文件本身有问题或者这个 4.2.1 版本不支持该编码,不用往下写代码了。
4.2 批量抽帧的命令行写法
很多场景下你不需要写 C++,直接用ffmpeg.exe抽帧更快。比如每秒抽一帧:
D:\libs\ffmpeg-4.2.1-win32-shared\bin\ffmpeg.exe -i input.mp4 -vf fps=1 -q:v 2 frame_%04d.jpg-vf fps=1表示每秒输出一帧,-q:v 2控制 JPEG 质量,数字越小质量越高。frame_%04d.jpg里的%04d会生成frame_0001.jpg这样的文件名。如果你要抽指定时间点的一帧:
D:\libs\ffmpeg-4.2.1-win32-shared\bin\ffmpeg.exe -ss 00:01:30 -i input.mp4 -frames:v 1 output.jpg-ss放在-i前面是快速定位,放在后面是精确但慢。4.2.1 对这两种写法的支持都正常,但快速定位在关键帧间隔大的文件上可能偏几秒,精确场景要把-ss放后面。
4.3 用 ffmpeg.exe 转封装而不是转码
如果你只是想把mkv转成mp4,不要重新编码,用-c copy:
D:\libs\ffmpeg-4.2.1-win32-shared\bin\ffmpeg.exe -i input.mkv -c copy output.mp4-c copy表示所有流直接复制,速度极快,但前提是目标容器支持源编码。比如把flac音频塞进mp4就会失败,因为 mp4 不认 flac。这时候要么换容器,要么用-c:a aac重新编码音频。这个命令在批量处理时特别有用,写个 for 循环就能把一堆 mkv 转成 mp4。
5. 避坑:32 位 FFmpeg 4.2.1 在 Windows 上的五个翻车现场
5.1 现象:程序在开发机跑得好,到客户机器就报“找不到 avcodec-58.dll”
原因:开发机 PATH 里有 FFmpeg 的 bin 目录,客户机没有。解决:交付时把av*.dll和sw*.dll拷到 exe 同目录,或者用安装包写注册表加 PATH。不要指望客户会自己配环境变量。
5.2 现象:链接时报 LNK2019,说avformat_open_input无法解析
原因:头文件里声明了,但.lib没链上,或者链接的是 64 位的.lib。解决:检查附加依赖项,用dumpbin /headers确认.lib的机器类型是 x86。还有一个隐蔽情况:C++ 工程里忘了加extern "C",导致名字修饰不匹配,报错信息里会有一长串修饰名。
5.3 现象:调用avcodec_send_packet返回AVERROR(EAGAIN)
原因:4.2.1 用的是新的 send/receive API,解码器内部有缓冲,不是每次 send 都能立刻 receive。解决:这是正常行为,不是错误。循环 send 直到返回AVERROR_EOF,每次 send 后循环 receive 直到返回EAGAIN。老代码里avcodec_decode_video2的写法在 4.2.1 里还能用,但已经废弃,新代码建议直接上 send/receive。
5.4 现象:抽帧出来的图片颜色发绿或者发灰
原因:像素格式转换没做,直接把AV_PIX_FMT_YUV420P的数据当 RGB 写文件。解决:用sws_scale把解码后的帧转成AV_PIX_FMT_BGR24或AV_PIX_FMT_RGB24,再写图片。sws_getContext的参数里源宽高和目标宽高要一致,SWS_BILINEAR就够用,追求质量可以上SWS_LANCZOS,但速度会慢。
5.5 现象:处理大文件时内存一直涨,最后崩掉
原因:AVPacket和AVFrame没有及时av_packet_unref和av_frame_unref。4.2.1 里av_packet_unref会把 packet 重置到初始状态,但不会释放 packet 结构本身。解决:在每次循环末尾调用 unref,AVFrame用av_frame_free释放。如果用了av_frame_alloc分配 frame,记得在循环外只分配一次,循环内 unref 后复用,不要每次循环都 alloc/free,那样碎片太多。
6. 进阶:把 4.2.1 的 DLL 封装成稳定的转码服务
6.1 用 C 接口包一层,避免 C++ 异常穿过 FFmpeg 边界
FFmpeg 是纯 C 库,内部不会抛 C++ 异常。如果你的 C++ 代码在回调里抛异常,穿过 FFmpeg 的栈帧会导致未定义行为。稳妥做法是写一层纯 C 的包装函数,所有 FFmpeg 调用都在 C 函数里完成,C++ 侧只负责调用和错误码转换。
// ffmpeg_wrapper.h #ifdef __cplusplus extern "C" { #endif int probe_video(const char* path, int* width, int* height, double* duration); #ifdef __cplusplus } #endif// ffmpeg_wrapper.c #include <libavformat/avformat.h> int probe_video(const char* path, int* width, int* height, double* duration) { AVFormatContext* ctx = NULL; int ret = avformat_open_input(&ctx, path, NULL, NULL); if (ret < 0) return ret; ret = avformat_find_stream_info(ctx, NULL); if (ret < 0) { avformat_close_input(&ctx); return ret; } *width = 0; *height = 0; *duration = (double)ctx->duration / AV_TIME_BASE; for (unsigned i = 0; i < ctx->nb_streams; i++) { if (ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { *width = ctx->streams[i]->codecpar->width; *height = ctx->streams[i]->codecpar->height; break; } } avformat_close_input(&ctx); return 0; }这样 C++ 侧只需要#include "ffmpeg_wrapper.h",不直接包含 FFmpeg 头文件,编译依赖也干净。参数上duration用double返回秒,比返回微秒整数更直观。错误码直接返回 FFmpeg 的负数错误码,C++ 侧用av_strerror转文字。
6.2 用 ffprobe 的输出做自动化校验
在 CI 或者批量处理脚本里,不要只依赖程序返回值,用ffprobe的输出做二次校验。比如转码完成后检查输出文件是否有视频流:
D:\libs\ffmpeg-4.2.1-win32-shared\bin\ffprobe.exe -v error -select_streams v:0 -show_entries stream=codec_name -of csv=p=0 output.mp4如果输出为空,说明没有视频流,转码失败。-select_streams v:0只选第一个视频流,-of csv=p=0去掉字段名只留值。这个命令的退出码在 4.2.1 里是可靠的,可以直接在脚本里判断。
6.3 一个我常用的验证习惯
每次拿到一个新的 FFmpeg 包,不管是不是 4.2.1,我都会先做三件事:用dumpbin确认位数,用ffprobe读一个已知正常的文件,用ffmpeg -c copy转一次封装。这三步能在十分钟内排除掉九成的环境问题。4.2.1 这个版本虽然老,但它的 API 在 4.x 系列里是稳定的,你在这上面写的代码,迁移到 4.4 或者 5.x 大部分能直接编译,只有少数废弃接口需要改。我一般会把av_register_all这种废弃调用集中在一个兼容层里,将来升级时只改一个文件。希望帮到你。
本文还有配套的精品资源,点击获取