VS2019下用ShiftMediaProject编译FFmpeg全流程记录:从环境配置到工程集成
说实话我第一次看到ShiftMediaProject这名字时心里想的是又一个把Linux项目往Windows上硬搬的仓库。直到真的用VS2019把SMP版本的FFmpeg完整编译出来才发现这套工程有多省心。如果你之前用MSYS2折腾过FFmpeg应该懂那种“编到一半发现少了个依赖补完依赖发现版本不对重来之后发现输出的库链接不进VS工程”的绝望。SMP这套方案专门解决的就是这个问题它把FFmpeg以及一堆第三方依赖库全部改造成原生Visual Studio解决方案打开.sln直接Build就完事。这篇博文不是什么官方文档翻译就是我自己从零到一编译SMP版本FFmpeg的完整记录包含环境准备、每一步操作、中间踩过的坑、以及最后怎么把编译好的库接进自己的VS项目。适合三类人看需要基于FFmpeg做二次开发的Windows程序员、想在FFmpeg源码里打断点调试的学习者、以及被MSYS2和MinGW折腾到头疼想找替代方案的朋友。1. 为什么非要自己编译现成包背后是工具链差异与定制需求1.1 你可能不需要自己编译但先看看这几个场景网上现成的FFmpeg Windows编译包一抓一大把BtbN、gyan.dev这些都有每日构建版下载解压就能用。我以前也是这么干的直到某天我需要往FFmpeg里加一个自定义编码器才发现预编译包这条路彻底走不通。下面这几个场景只要命中一条你就应该考虑自己编译了想修改FFmpeg源码做实验。比如改一下avcodec内部的码率控制逻辑或者想在某个特定函数里加日志输出。预编译包对你完全是黑盒。需要在FFmpeg源码里断点调试。这是SMP方案最大的优势后面我会详细说。VS的调试器和FFmpeg源码工程直接对接比在MinGW里用gdb舒服太多。想把FFmpeg作为库接进MSVC工程。预编译包大多是用MinGW编出来的引入自己的Visual Studio项目时运行时库冲突、符号兼容性问题时有发生。用MSVC原生态编出来的库问题会少很多。需要裁剪体积。比如只保留H.264的编解码不要那几十个用不到的编解码器。SMP的VS工程支持通过配置灵活裁剪。需要用开源许可证约束更宽松的构建方式。FFmpeg本身混着LGPL和GPL组件不同编译选项最终产物的协议边界不一样只有自己编译才能精确控制。1.2 SMP版本与官方MSYS2方式的差异FFmpeg官方给的Windows编译教程基本都指向MSYS2环境加MinGW编译器。这套方案成熟可靠但对Windows桌面开发者来说有几个很别扭的点MinGW的工链跟MSVC不互通编译出来的静态库没法直接让MSVC消费调试体验也一般想用Visual Studio看FFmpeg内部变量基本没戏。ShiftMediaProject做的事情简单说就是把FFmpeg的构建系统“翻译”成了一套MSBuild工程。项目里每个核心库avutil、avcodec、avformat、swscale等都是一个独立的.vcxproj彼此有正确的依赖引用关系。你在VS里按一下F7VS会按照依赖顺序自动编译全部模块。我用一个表格直观对比两种方案对比项官方推荐方式ShiftMediaProject方式构建环境MSYS2 MinGWVisual Studio 2019/2022工程形式命令行configure make.sln .vcxproj编译产物MinGW风格的.a/.dllMSVC风格的.lib/.dll源码断点调试可用但体验一般VS原生体验非常顺滑链接进MSVC工程有障碍经常要折腾天然友好依赖库管理手动安装或脚本拉取子模块或仓库内收录上手门槛需要熟悉Linux命令行习惯只需要点鼠标我自己用下来最直观的感受就是“工程思维”和“命令行思维”的区别。SMP把FFmpeg的一切都做成了Visual Studio里的项目属性、宏定义、预编译步骤做Windows桌面开发的人几乎零成本上手。1.3 SMP的适用范围与天然限制SMP不是没有缺点先把它说得明明白白它不是官方项目。ShiftMediaProject是社区维护者的fork虽然维护节奏不错但毕竟不是FFmpeg官方渠道文档和issue覆盖度全凭维护者个人精力。版本更新有滞后。FFmpeg官方release一个版本后SMP需要时间把构建系统适配过去所以往往比官方晚一两个小版本。第三方库收录看心情。不是所有FFmpeg的外部库都有对应的SMP适配平时常用的x264、x265、libvpx、opus这些都有更冷门的需求就得自己造轮子。但这不影响的它的核心价值让Windows开发者在一个熟悉的环境里完完整整地把FFmpeg这一大坨C代码编译成自己想要的形态。接下来就进入正题一步步来。2. 环境准备VS2019组件、YASM路径与Git配置的完整记录2.1 VS2019安装时最容易忽略的组件很多人觉得装VS2019就是下一步下一步的事直到编译SMP工程报了“找不到Windows SDK”或者“MSB8036”才回来补救。这里帮各位把该勾的选项归拢一下。打开Visual Studio Installer找到VS2019版本建议不早于16.7在“使用C的桌面开发”这一项上必须勾选。我建议展开看一下子组件确保包含以下内容MSVC v142 - VS 2019 C x64/x86生成工具Windows 10 SDK任意较新的10.x版本适用于最新v142生成工具的C ATL这个不是必须但有些依赖库的辅助工具会用到之前踩过一个坑只装了Windows 10 SDK的旧版本结果编译时提示找不到ucrt头文件后来在VS安装器里补装了新版SDK才解决。SMP对SDK版本不算挑剔建议直接用VS Installer里推荐的那一个。另外有一点要注意VS2019会自带一个“适用于Windows的C CMake工具”这个组件跟SMP没关系可以不装节省磁盘空间。SMP是纯MSBuild系统不需要额外安装Perl、NASM等一堆命令行工具——当然YASM例外下一个章节就是它。2.2 YASM下载与环境变量不装它编译直接卡在第一步FFmpeg为了让x86平台的汇编优化代码跑得更快编译过程中需要把.asm汇编文件翻译成目标文件这一步靠的是NASM或YASM。SMP工程默认依赖YASM如果不提前装好编译时会报“找不到yasm”或者“yasm不是内部或外部命令”之类的错误而且这个错误出现在第一个项目编译阶段特别劝退。我的做法是下载YASM的Windows 64位版本。去yasm官网找一个yasm-1.3.0-win64.exe这样的文件下载后放到一个干净的目录我习惯建一个专门的工具目录比如C:\tools\yasm把下载佬的exe重命名成yasm.exe。然后关键一步SMP工程在VS里对YASM的引用不一定只靠PATH环境变量。根据工程配置的不同它可能直接查YASM这个环境变量也可能查PATH。为了避免摸不着头脑的情况我建议一步到位系统环境变量里新增一个变量变量名YASM值填C:\tools\yasm\yasm.exe。同时在系统环境变量PATH里追加C:\tools\yasm目录。两个都配好最大程度兼容不同版本的SMP工程配置。配完之后一定记得重启Visual Studio否则VS不会刷新环境变量。2.3 目录规划、Git clone与分支选择SMP仓库本身在GitHub上项目名也叫FFmpeg。第一步是先克隆源码。这里有一个重要建议源码目录绝对不能出现中文、空格、或者过长的路径建议直接放盘符根目录或比较深的纯英文路径。原因很简单FFmpeg的构建系统有很多硬编码路径假设和脚本拼接逻辑路径里一有空格就容易炸。我是这样操作的在某个盘符下建了一个ws目录然后执行git clone --recursive https://github.com/ShiftMediaProject/FFmpeg.git cd FFmpeg注意--recursive这个参数很关键SMP用子模块和子仓库管理第三方依赖不递归克隆的话后面会各种缺文件。不过SMP更像是在编译过程中自动拉取依赖库是否直接递归clone各人情况不同但加了它更稳。分支选择上我要强调一下不要一上来就切master。master对应的是FFmpeg官方的最新主分支代码更新快SMP适配相对滞后很容易遇到编译错误。稳妥做法是选一个稳定的release分支。以VS2019为例我当时选的release/4.4这个分支跟VS2019的编译器兼容性非常好。git checkout release/4.4切分支前建议确认一下子模块已经拉取完整不然issues里常见的“avcodec工程加载失败”很容易出现。3. 一次完整的编译过程从克隆SMP仓库到拿到dll和头文件3.1 从clone到sln项目结构扫一眼代码拉下来之后进到FFmpeg目录你会看到一个FFmpeg.sln解决方案文件这就是SMP给VS准备的入门入口。打开它之前可以先看一下根目录结构大概能对SMP的组织方式有感觉libavcodec、libavformat这些目录和官方源码保持一致是FFmpeg核心库的源码。project或者若干集成文件目录里面是SMP自己加的配置文件和VS工程定义。第三方依赖库往往不在这个仓库根目录下而是在同级目录比如SMP会拉一个../x264之类的依赖仓库到旁边。打开sln之后VS会加载创建好的项目组左侧解决方案资源管理器里能看到一堆项目。核心的几个是avutil、avcodec、avformat、avdevice、avfilter、swscale、swresample最后还有ffmpeg、ffplay、ffprobe这几个应用层可执行文件项目。这里建议先看一眼“解决方案资源管理器”里的项目顺序可以双击项目查看依赖关系。VS的Build依赖关系里这些库之间有明确的引用链比如avcodec依赖avutilavformat依赖avcodec和avutil。第一次看到这么多项目别慌VS全部自动搞定。3.2 平台与生成配置的选择打开sln之后在工具栏上把解决方案配置设为Release解决方案平台设为x64。如果打算在自己机器上只做开发调试可以选Debug但Debug版本性能效率会低很多、输出体积也大一般日常还是用Release。为什么推荐x64因为绝大多数现代FFmpeg应用场景都是64位SMP对x64的支持最成熟遇到的坑最少。x86Win32也不是不能编但某些第三方依赖库可能没有很好的SMP适配反而浪费时间。选好平台和配置之后先别急着Build整个解决方案。我第一次直接F7结果编译到一半因为一个第三方项目拉取不到源码而中断。稳妥做法是在解决方案资源管理器里右键解决方案选择“重新生成解决方案”之前先确认两个东西网络状态正常SMP在编译部分依赖库时可能需要下载或git拉取额外仓库。磁盘空间至少有20GB到30GB空闲因为中间目标文件非常多Release版本的.pdb文件也很占空间。3.3 编译中你会看到的阶段点下生成之后输出窗口就开始刷屏了编译过程大致分下面几个阶段configure配置阶段。虽然SMP是VS工程但它内部还是做了一套类似FFmpeg configure的配置逻辑生成config.h、config_components.h这些关键头文件。这个过程会在输出窗口看到一些Define/Undef的文字判断当前编译开启了哪些功能。汇编编译阶段。YASM开始处理.asm文件生成OBJ文件这个过程输出窗口里会有很多yasm命令。C编译阶段。主力的cl.exe登场每个编译单元编译可能要持续好几分钟主要看CPU核心数。我用的八核十六线程机器整体编译时间大概在三十分钟到一个小时之间。链接阶段。各个库项目生成.lib和.dll最后链接ffmpeg.exe、ffplay.exe、ffprobe.exe。整个过程看起来输出极多但核心信息就两条有没有红色的error以及最后有没有生成三个exe和一堆dll。中间出现黄色warning不用管只要没中断就可以让它跑完。3.4 产物检查与常见误判编译成功后去代码目录下找输出文件夹。SMP的默认输出路径通常在类似output的目录下里面的.dll、.lib、.exe和include头文件基本都会被归拢到几个文件夹里具体叫什么取决于SMP版本。我当时拿到的东西大概是这个形态一堆avcodec-59.dll、avformat-59.dll、avutil-57.dll这样的动态库对应的导入库.lib对应的头文件include目录ffmpeg.exe、ffplay.exe、ffprobe.exe验证是否编译成功可以直接在命令行里切到bin目录执行ffmpeg -version ffprobe -version如果出来的版本信息里带有自定义的配置选项比如你开了某些外部库说明编译确实生效了。这里有个常见的误判双击ffmpeg.exe直接运行结果Windows弹“找不到avcodec-59.dll”于是以为编译失败了。其实exe和dll都在同一个目录时Windows默认也未必能找到更稳妥的方式是在命令行里切换到该目录再执行或者先把dll所在路径加入PATH。4. 编译报错排查链路我在这条路上踩过的几个高频坑4.1 第一个高频坑找不到yasm.exe这个坑我在2.2里提到了但这里要把完整的排查链路写出来供你复现参考。现象是编译刚开始执行到汇编阶段输出窗口飘红The system cannot find the file specified前面往往还有一段yasm相关命令。或者直接报yasm is not recognized as an internal or external command。排查路径是这样的先在命令行里手动敲yasm --version看系统能不能找到。如果提示找不到检查环境变量PATH里有没有YASM所在目录。如果PATH没问题检查环境变量YASM本身有没有配置。确认两个环境变量都配好了但VS还是报错马上重启VS或者干脆注销重新登录系统。有一次我发现自己明明配好了PATH但VS输出窗口还是找不到yasm后来发现是环境变量配在了用户变量里而VS从系统环境启动时没有读取到。建议直接把两个都配在系统变量里省得折腾。4.2 编译中途报“无法打开包含文件stdint.h”这个问题一听名字会觉得莫名其妙微软自己的SDK难道没带stdint.h其实原因不是缺头文件而是项目没有正确选用Windows SDK版本或者是混用了旧版工具集。排查链路如下看看报错的项目是哪个SMP主库项目还是第三方依赖项目。右键该项目的属性找到“常规” - “Windows SDK版本”确认选择的是自己安装的那个版本。再确认“平台工具集”是v142对应VS2019而不是v143VS2022之类的。如果一切正常但问题还在去VS安装器里补装最新Windows 10 SDK然后重启VS重新生成。出现这个问题的场景往往是在旧项目基础上装了新SDK没有同步到项目配置。SMP默认项目属性一般没问题更多是我自己手贱改坏了平台配置。4.3 链接报错LNK1104无法打开“avformat.lib”这是把整套解决方案拆开单独编译某个项目时最容易遇到的坑。比如我只Build了ffmpeg.exe这一个项目没Build依赖库链接时就死活找不到avformat.lib。排查链路比较直接检查解决方案配置确认你要生成的ffmpeg.exe项目引用的依赖项目是否在生成列表中。在VS里右键ffmpeg.exe项目选择“生成依赖项” - “项目依赖项”看是否勾选了avformat、avcodec、avutil等。如果依赖项没勾选或保存异常手动勾上然后再次做一次整个解决方案的重新生成。如果配置没问题但还是找不到lib去接输出目录里有没有avformat.lib可能之前的编译根本没成功过。一般做“重新生成解决方案”可以解决90%的LNK1104因为VS会按依赖顺序完整重建所有库。如果手动勾选了依赖后还是不行直接执行“清理解决方案”再重新生成不要偷懒。4.4 第三方依赖项目编译失败的定位思路SMP最吸引人的一点是预置了很多第三方依赖工程比如x264、x265、opus、libvpx。但IE经常出幺蛾子这些问题往往不是FFmpeg本身的问题而是这些外部库跟你的编译环境兼容性有偏差。常见报错氛围两类找不到外部库源码/子模块未拉取。错误信息里会提示找不到某个.c文件或者某个目录。这时去检查SMP根目录旁边的依赖目录是否存在如果缺失回到git submodule update或者手动clone对应仓库。外部库编译告警升级为错误。有些外部库并不是完全Windows原生适配的在MSVC下会报一些C4996、C4267之类的警告而SMP把警告视作错误/WX导致编译中断。处理方法是右键那个项目进入“C/C” - “常规” - “将警告视为错误”改成“否”。这种情况不算SMP的Bug更像是原本在Unix环境下编写的库第一次移植到Windows的过渡状态。看到报错先不要慌用上面两招基本都能解决。4.5 和MSB编号警告的相处方式编译过程中还会看到很多MSB打头的警告比如MSB8028中间目录共享、MSB4011自定义生成步骤文件没法复制之类的。这些不是错误不影响最终产物。强烈建议第一次编译时把输出窗口的“显示生成日志”级别设成正常或最小只关注error信息。我当时第一次编译看着满屏红色error往下刷紧张到不行后来发现很多是警告被染成红色真正会中断生成的就那么几个。如果看到某个error MSB也不要慌张MSB错误的信息里往往带着具体的项目和文件名直接根据路径去查比在满屏输出里猜效率高得多。5. 按需裁剪与集成把自定义FFmpeg接进自己的VS项目5.1 SMP里的“开关配置”到底是怎么运作的用官方源码时我们是通过./configure --disable-xxx --enable-yyy来控制编译选项的。SMP里没法在命令行敲configure但它把配置能力做到了VS项目的预处理器定义和相关配置文件里。在FFmpeg根目录下会找到类似config.h这样的文件里面一行的形式就是宏定义比如#define CONFIG_AVDEVICE 1、#define CONFIG_H264_DECODER 1。编译后的源代码都会包含它决定哪些模块要不要编译进库。想裁剪功能有个最直接的办法在VS里找到名为configure或者options的项目不同SMP版本名字略有不同点开它的自定义生成步骤看它生成的命令行脚本里有哪些--disable、--enable参数。这个脚本本质上是FFmpeg官方configure的“外壳实现”只不过它会在编译前生成config.h并交给各个库项目使用。如果你只是想快速定制可以直接改这个配置脚本或者修改项目属性的预处理器定义。但这里有个小心得每次修改配置都有太大变化时最好清理解决方案再重新生成。如果只是小改动比如只新增一个编码器宏可能重新生成对应库项目就够了但为了稳妥我建议头几次操作都走完整套流程等摸清楚规律再跳步。5.2 开启GPL库以x264为例要注意的事假设你想把x264的H.264编码器集成进来官方做法是编译时指定--enable-libx264并链接x264库。SMP里就需要额外准备x264的仓库并把它加入解决方案依赖。操作逻辑大概是先把SMP官方提供的x264仓库克隆到指定目录或者在SMP主仓库的配置脚本里把对应的路径填好确保编译时能找到x264的源码。然后在SMP的configure配置里给--enable-libx264和--enable-gpl选项打开。如果你忘了开--enable-gpl后面的--enable-libx264大概率不会生效因为FFmpeg的保护机制要求一旦链接GPL库整个构建必须切入GPL模式。这里还要多说一句GPL相关的现实问题如果你开启了--enable-gpl并链接了x264这样的GPL库那么你基于这个FFmpeg发布的任何程序都要按照GPL协议对外开源。如果只是自用或者公司内部研究这并不影响但产品化前一定要看清楚license边界。不要到了发布环节才发现协议不合到时候再回头重编纯LGPL版本真的是浪费时间又耗精力。5.3 在自己工程中动态链接SMP产物的配置SMP编译完成后最爽的时刻就是把它接进自己的VS项目。假设我现在建一个新的控制台程序想调用FFmpeg打开一个媒体文件拿到时长。步骤如下在VS工程属性的“C/C” - “常规” - “附加包含目录”中加上SMP产物里的include目录。在“链接器” - “常规” - “附加库目录”中加上SMP产物里的lib目录注意x64和x86要对应。在“链接器” - “输入” - “附加依赖项”中填入avformat.lib、avcodec.lib、avutil.lib这些导入库。确保编译生成的exe运行时dll能被找到。最省事的方式的是把dll复制到exe同目录或者把dll所在目录加入系统PATH。写一段最简测试代码#include iostream #include cstdio #pragma comment(lib, avformat.lib) #pragma comment(lib, avcodec.lib) #pragma comment(lib, avutil.lib) extern C { #include libavformat/avformat.h } int main() { avformat_network_init(); AVFormatContext* fmt nullptr; int ret avformat_open_input(fmt, test.mp4, nullptr, nullptr); if (ret 0) { char err[AV_ERROR_MAX_STRING_SIZE] {0}; av_strerror(ret, err, AV_ERROR_MAX_STRING_SIZE); std::cerr open failed: err std::endl; return 1; } std::cout duration fmt-duration ms std::endl; avformat_close_input(fmt); return 0; }跑通之后就能体会到SMP方案的好处鼠标点一下断点在avformat_open_input内部按F5进去整个FFmpeg的源码就在你眼前想干么干什么都行。6. 最后一次编译后的个人体会经过这一轮折腾我最大的感触是SMP不是给不想花时间研究FFmpeg的人准备的恰恰相反它是给那些愿意把FFmpeg当成自己工程的一部分、想在Windows上认真研究它的人准备的。它把Windows编译的碎片化问题收敛到了一个VS解决方案里省掉了一堆命令行时代的环境变量和依赖地狱。如果你打算自己做一次全程编译我最后再分享三个实用建议第一第一次编译时一定走稳定分支不要上来就尝试master或者最新特性分支。我记得当初用release/4.4踩的坑后来切到master又踩了一遍重复的。稳定的版本遇到问题时社区里早就有答案搜索成本低很多。第二YASM的问题提前配好这能帮你节省最先崩溃的时间。很多新手在编译刚开始就被拦在汇编阶段其实只要把这个工具提前放好后面一路顺风。第三编译出exe和dll不是终点把产物接到自己的VS工程里、跑通第一个avformat_open_input调用才算真正完成了闭环。这一步会把你对“编译FFmpeg”的理解从“多了一个文件”升级到“多了一套我可以控制的工具链”。按我自己的经验这套东西配好一次之后不管做转码工具、播放器还是流媒体分析程序都能直接在VS里折腾FFmpeg源码不再需要到处找预编译包也不用在MSYS2里跟那些不友好的命令行较劲。希望你也能顺利编出自己的那一份。