🎬 第十二章 各种开发语言调用FFmpeg命令行
📄 一句话概括

MinGW 编出来的 exe,缺的从来不是头文件和库——是运行时那三个 DLL。 编译通过只是第一步,能双击打开才算完。

📝 本节和前面几节的关系

前三节讲的是「怎么起进程」,这一节往回退一步: 你写的那个调用 ffmpeg 的程序,自己是怎么被编出来的。 用的是 MinGW(g++),跟 VS 那套不是一回事。

一、MinGW 与 MSVC 的两处不同

第一处:头文件与库的名字不一样。 MSVC 链接的是 .lib 导入库,MinGW 既可以直接链 .dll,也可以用 .a/.dll.a。 所以从 VS 项目抄过来的「附加依赖项」,在 MinGW 里要换个写法。

第二处:MinGW 自带一套 GCC 运行库,而这套运行库是动态的。 也就是说,你编出来的 exe 依赖 libgcc_s_seh-1.dll 与 libstdc++-6.dll。 这两样在 MinGW 的 bin 目录里,不在你的 exe 旁边, 所以「在我机器上能跑,拷给别人就报缺模块」。

VS 那边没这个问题,因为它默认静态链接运行库(/MT)。 这也是很多人「VS 里好好的,换 MinGW 就一堆缺 DLL」的真正原因—— 不是代码变了,是运行库的链接方式变了。

二、编译命令的四个部分

一条能用的 MinGW 编译命令,拆开就是四段:

这一段 管什么


-IC:/ffmpeg/include 找得到头文件 -LC:/ffmpeg/lib 找得到库文件 -lavformat -lavcodec -lavutil 链哪几个库 -static-libgcc -static-libstdc++ 运行库塞进 exe

四段缺一不可,而且缺哪一段的报错完全不一样: 缺 -I 报「找不到头文件」,缺 -L 报「找不到 -lavformat」, 缺 -l 报「undefined reference」,缺最后一段什么都不报—— 编得出来,跑起来才报缺 DLL。 最后这种最难查,因为编译阶段一路绿灯。

三、链接顺序不是小事

GNU 的链接器是从左到右扫描的:遇到一个库,就把当前还没解析的符号拿去里面找, 找完了就丢。所以被依赖的库必须写在依赖者的右边。

libavformat 依赖 libavcodec,libavcodec 依赖 libavutil, 正确的顺序就是 -lavformat -lavcodec -lavutil。 写反了,报的是一屏 undefined reference to av_..., 看着像代码写错、像库没链上,其实是顺序错。

这个坑有个很省事的自查方法:把所有 -l 参数按「谁依赖谁」从大到小排一遍。 排不出来就把最底层的 -lavutil 复制一份放到最后—— 重复写同一个库不会报错,这是 GNU 链接器允许的。

四、运行时的三个 DLL

MinGW 编出来的程序,运行时可能要找这三个:

DLL 谁带来的 什么情况下必须


libgcc_s_seh-1.dll GCC 运行库 默认就要 libstdc++-6.dll C++ 标准库 用了 STL 就要 libwinpthread-1.dll 线程模型 静态链接也躲不掉

第三个最容易漏:很多人加了 -static-libgcc -static-libstdc++ 就以为万事大吉, 结果换了台机器还是报缺 libwinpthread-1.dll—— 因为它属于 win32 线程模型那一层,得另加 -static 或 -Wl,-Bstatic。

另外一个更省事的办法:直接用 -static 全静态。 代价是 exe 变大(几 MB 起),好处是拷到任何一台 Windows 上都能双击打开。 做工具类程序,这个交换通常是划算的。

五、报错长什么样,先认下来

「缺 DLL」在程序里的样子,跟「找不到命令」完全不是一回事。 前者是系统在加载阶段失败,报的是「找不到模块」; 后者是你调 CreateProcess 时路径不对,报的是「找不到文件」。

这两种错经常被混为一谈,因为中文提示都很含糊。 记住一条分界线: 错误里提到具体 DLL 名字的,是运行库问题;提到 exe 路径的,是调用问题。

六、一句总结

MinGW 的三个门槛依次是:找得到头文件、链得上库、跑得起来。 前两个报错清楚,第三个最阴—— 因为它在编译阶段一点征兆都没有,只在你把 exe 拷到别人机器上时爆发。 所以 MinGW 项目的发布清单里,永远要有「目标机器上双击能不能打开」这一条。

七、动手 Demo:编译环境体检:从「有没有编译器」查起

本机没有装 MinGW,也没有 gcc/cl,所以这一节只能把「查」这件事做实。 查的顺序就是排查的顺序:先看有没有,再看缺什么。

import ctypes, shutil, subprocess

for exe in ("g++", "gcc", "cl", "clang++"):
    print("%-8s %s" % (exe, shutil.which(exe) or "没有"))

try:
    subprocess.run(["g++", "--version"], capture_output=True)
except FileNotFoundError as e:
    print("直接调用 g++ 会抛:", type(e).__name__, e.errno)

try:
    ctypes.CDLL("libgcc_s_seh-1.dll")
except OSError as e:
    print("缺 DLL 时报的是:", str(e).split("(or")[0].strip())

实跑输出:

--------------------------------------------------------------------
12.1.4 MinGW 调用 FFmpeg 命令行
--------------------------------------------------------------------
**① 先体检:这台机器上到底有没有编译器**

  找的是            找到了吗
  ----------------------------------------------------------
  g++               没有
  gcc               没有
  cl                没有
  clang++           没有
  mingw32-make      没有

**PATH 上一共 36 个目录,其中 18 个里面真有 exe——**
**一个编译器都没有。**
**所以这一节只能讲机制。但机制比命令重要,**
**因为 MinGW 的麻烦九成不在编译,而在【编译完了跑不起来】。**

**② 「不是内部或外部命令」在程序里长什么样**

  你做的事                  系统给你的答复
  ------------------------------------------------------------------
  直接调 g++                FileNotFoundError [WinError 2]
  在命令行窗口敲 g++        'g++' 不是内部或外部命令

**同一件事,两个说法:命令行窗口说「不是内部或外部命令」,**
**代码里抛 `FileNotFoundError`。**
**所以看到这个异常,第一反应不该是「代码写错了」,而是「PATH 里没有它」。**

**③ MinGW 编出来的 exe,旁边必须有三个 DLL**

  DLL                       谁带来的                少了会怎样
  ----------------------------------------------------------------------------
  libgcc_s_seh-1.dll        GCC 运行库              启动就报缺模块
  libstdc++-6.dll           C++ 标准库              同上,且报错不提 C++
  libwinpthread-1.dll       线程模型                同上,`-static` 也躲不掉

**「缺 DLL」的报错在程序里是这个样子:**

  加载的东西              系统的答复
  ----------------------------------------------------------------------
  libgcc_s_seh-1.dll      Could not find module 'libgcc_s_seh-1.dll'

**注意它说的是「找不到这个模块【或者它的某个依赖】」——**
**这句话把排查方向直接指歪了:你以为缺的是 libgcc,**
**其实常常是它的依赖(比如 vcruntime)不在。**

**④ 一条能用的编译命令,四个部分**

    g++ main.cpp -o main.exe ^
        -IC:/ffmpeg/include ^                 REM 头文件在哪
        -LC:/ffmpeg/lib -lavformat -lavcodec -lavutil ^   REM 库在哪、链接谁
        -static-libgcc -static-libstdc++       REM 把两个运行库塞进 exe

**四个部分缺一不可:`-I` 管「找得到头文件」,`-L` 管「找得到库文件」,**
**`-l` 管「链哪一个」,最后那行管「运行时别再找 DLL」。**
**链接顺序也有讲究:`-lavformat` 要写在 `-lavcodec` 前面,**
**因为前者依赖后者——顺序反了,报的是一堆「undefined reference」,**
**看着像代码写错,其实是命令行顺序错。**

四行「没有」,说明这台机器压根没有编译器——所以后面全是机制,不是实测。 第二段说明「不是内部或外部命令」在代码里长什么样:FileNotFoundError。 第三段是缺 DLL 的报错原话:Could not find module ...。

两种错长得完全不一样,这就是分界线的实证。

30 秒实验

  1. 把 shutil.which 换成 where g++ 的输出:看命令行与程序两条路的说法差别。
  2. 用 dumpbin /dependents(VS)或 objdump -p(MinGW)看一个 exe 依赖哪些 DLL:练一次就记住了。
  3. 给 MinGW 加 -static 重编一次:看 exe 从几百 KB 变成几 MB。

常见误区

  1. 编过了就当完事。运行库缺失只在跑的时候才暴露。
  2. 把 -l 顺序当无关紧要。GNU 链接器从左到右扫,顺序错了就是一屏 undefined reference。
  3. 只加 -static-libgcc -static-libstdc++ 就以为全静态。libwinpthread-1.dll 还在。
自测三问(点开对答案) 1. MinGW 编的 exe 拷到别的机器为什么可能打不开? —— 缺 libgcc_s_seh-1.dll / libstdc++-6.dll / libwinpthread-1.dll。 2. `-lavformat` 为什么必须写在 `-lavcodec` 前面? —— GNU 链接器从左到右扫描,被依赖的库要放在右边。 3. 「找不到模块」与「找不到文件」分别指什么? —— 前者是 DLL 加载失败,后者是 exe 路径不对。

相关笔记