跟配 OpenCV 一样是三处:包含目录、库目录、附加依赖项。
多出来一个坑:extern "C",以及库的链接顺序。
上一节把骨架立起来了,这一节解决「能不能编过」。 配置这件事本身没有技术含量,但它拦住了很多人。 跟 05.01 的 OpenCV 配置对照着看,会发现套路完全一样——只有 C 与 C++ 混用这一层是 FFmpeg 特有的。
一、三处配置,跟 OpenCV 一个套路
| 配在哪 | 填什么 |
|---|---|
| C/C++ → 附加包含目录 | ...\include |
| 链接器 → 附加库目录 | ...\lib |
| 链接器 → 附加依赖项 | avformat.lib、avcodec.lib、avutil.lib、swscale.lib、swresample.lib |
第三行比 OpenCV 啰嗦:OpenCV 有一个 opencv_world 把全部打成一个库,FFmpeg 没有。
所以你必须按用到的功能逐个列:解码要 avformat + avcodec + avutil,
转格式再加 swscale,音频重采样再加 swresample,滤镜再加 avfilter 和 avdevice。
漏了会出现什么:LNK2019 未解析外部符号,而符号名里带着库的痕迹(av_、sws_、
swr_),所以从错误信息能反推「该加哪个库」——这一点比 OpenCV 友好。
二、extern C:C 库给 C++ 用
FFmpeg 是 C 库。C++ 编译器会对函数名做修饰(name mangling), 所以声明成「C++ 函数」再去链接「C 的实现」,名字对不上。
报错长这样:
「无法解析的外部符号 avformat_open_input」——函数名明明拼对了,但还是找不到。
关键的线索是:报错里的名字「没有那一串乱码后缀」。
如果修饰过头,报错里会带 @@YA 之类的东西。
标准解法是包一层:
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
}
为什么库自己不处理:因为它要在纯 C 里也能用,而 extern "C" 是 C++ 语法。
正经库的写法是 #ifdef __cplusplus extern "C" { #endif,
FFmpeg 的头文件其实也这么干了——所以大多数情况下你不包也能编过。
那为什么还要讲:两种情况会翻车:一是老版本的头文件漏了某些文件; 二是你只 include 了部分头文件,而那个文件恰好没做保护。 所以「自己包一层」是个成本极低、收益确定的习惯。
三、库顺序与位数
静态链接时,库的顺序是有意义的:后面的库要能被前面的库解析。 所以顺序应该是「上层在前」:
| 顺序 | 库 | 依赖谁 |
|---|---|---|
| 1 | avdevice |
avfilter、avformat |
| 2 | avfilter |
avformat、avcodec、avutil |
| 3 | avformat |
avcodec、avutil |
| 4 | avcodec |
avutil |
| 5 | swscale / swresample |
avutil |
| 6 | avutil |
不依赖 |
写反了会报「未解析」,而 MSVC 对此比较宽容(它会自动重试),gcc 严格按顺序。 所以「MSVC 能编、Linux 编不过」的现象,九成就是库顺序问题。
位数那条跟 OpenCV 完全一样:x64 项目要 x64 的 lib 与 dll,
混了就是 LNK2038 或者启动报 0xc000007b。
这个错误码在第六章已经认过一次,这里不再展开。
最后说本机这个特殊之处:PyAV 把 FFmpeg 静态编进了 .pyd,所以没有头文件也没有 .lib。
Demo 里打印了 av.get_include() 的返回值,那个目录根本不存在——说明这个 API 只是「按约定拼了个路径」。
所以做 C++ 开发不能靠它,得自己下 dev 包。
四、动手 Demo:C++ 里配 FFmpeg,三处加一个 extern
这段查 av.get_include() 返回的路径是否真的存在,再把三处配置与 extern "C" 的写法列出来。
那个「路径给了但目录不存在」的结果,是这一节最诚实的一条证据:
API 给你的东西,不一定是真的。
import av, os
inc = av.get_include()
print(inc, os.path.isdir(inc))
# C++ 侧要这样写:
# extern "C" {
# #include <libavformat/avformat.h>
# }
实跑输出:
--------------------------------------------------------------------
C++ 里配 FFmpeg,三处加一个 extern
--------------------------------------------------------------------
**先说本机这个特殊之处**:**我们用的是 PyAV,它把 FFmpeg 静态编进了 `.pyd` 里**,
**所以没有头文件、也没有 `.lib`。**
`av.get_include()` 尾部 = **...lt\Lib\site-packages\av\..\include**
这个目录存在吗:**不存在**
**这个「路径给了但目录不存在」的现象很有意思**:**说明这个 API 只是「按约定拼一个路径」**,
**并不保证文件真的在那儿。**
**做 C++ 开发不能靠它,得自己下 dev 包。**
**C++ 里配 FFmpeg 跟配 OpenCV 一样是三处,但多一个坑**:
配在哪 填什么
--------------------------------------------------------------------------------
附加包含目录 `...\include`
附加库目录 `...\lib`
附加依赖项 `avformat.lib`、`avcodec.lib`、`avutil.lib`、`swscale.lib`…
**多出来的那个坑是 `extern "C"`**:**FFmpeg 是 C 库,C++ 编译器会做名字修饰(name mangling)**,
**不声明 `extern "C"` 就会链接失败**,
**报的错是「未解析的外部符号 avformat_open_input」**——**而函数名字明明拼对了。**
**标准写法是把 include 包起来**:
`extern "C" {`
`#include <libavformat/avformat.h>`
`}`
**为什么头文件自己不加**:**因为 FFmpeg 要在纯 C 里也能用**,**而 `extern "C"` 是 C++ 的语法。**
**大多数正经库会用 `#ifdef __cplusplus` 自己处理**,**FFmpeg 的头文件其实也处理了一部分**,
**但老版本、或者只 include 部分头文件时仍会出问题**,**所以「自己包一层」是最稳的习惯。**
**还有一个库顺序的坑**:**静态链接时,`avformat` 要用 `avcodec`,`avcodec` 要用 `avutil`**,
**顺序写反了会报「未解析」**。
**MSVC 对此比较宽容,gcc 会严格按顺序解析**,
**所以跨平台项目里经常看到「MSVC 能编、Linux 编不过」,就是这个原因。**
False 那个结果是本节的核心:路径拼出来了,目录不存在。
因为 PyAV 的 wheel 里根本没有 include 目录,它不需要——编译时头文件已经用完了。
这件事的普遍意义:「API 返回了一个值」不等于「这个值有效」。
所有「按约定拼路径」的函数都这样,所以判断之前先 isdir / isfile 一次,是个便宜的习惯。
三处配置那张表里,唯一跟 OpenCV 不同的是第三行:FFmpeg 没有 world 库,得逐个列。
好处是「按需链接」,编出来的 exe 小;坏处是「漏一个就 LNK2019」。
最后提醒一句部署:FFmpeg 的 dll 有一堆(avcodec-60.dll、avformat-60.dll…),
而且版本号写在文件名里。
所以「换了个版本」这件事在 FFmpeg 上特别容易发生事故——exe 找不到那一串精确的文件名,直接启动失败。
最稳的做法是「把用到的 dll 全拷到 exe 旁边」,别依赖 PATH。
30 秒实验
- 把附加依赖项里的
swscale.lib删掉,编译调用sws_scale的代码,看报什么错。 - 算一下:只做解码不做转格式,最少要链哪几个库?
- 判断一下:MSVC 能编过、Linux 编不过,最可能是哪个环节的问题?
常见误区
- 不写
extern "C"包 include。 大多数时候能编过, 但老版本或者只 include 部分头文件时会报「未解析」。 - 以为有一个「FFmpeg world 库」。 没有,必须逐个列依赖项。
- 库顺序写反。 静态链接时
avutil必须放最后。gcc 严格按顺序,MSVC 宽容。 - 靠
av.get_include()找头文件。 它只是拼了个路径,目录可能不存在。要自己下 dev 包。
自测三问(点开对答案)
1. FFmpeg 的配置比 OpenCV 多哪个坑? —— `extern "C"`:FFmpeg 是 C 库,C++ 直接 include 会被做名字修饰,链接时找不到符号。 2. 为什么 FFmpeg 的库要按顺序链接? —— 静态链接时前面的库要用后面的库解析符号。`avutil` 是最底层,必须放最后。 3. `av.get_include()` 返回的路径一定存在吗? —— 不一定。它只是「按约定拼一个路径」,PyAV 的 wheel 里根本没有 include 目录。配置通了,最后一节回到代码:把八步写成能跑的循环。重点是一进多出、以及冲空包。
- ☐ (本节完,下一节 07.03.03 FFmpeg解码流程与案例实战)