🎬 第七章 FFmpeg解码音视频及流媒体
📄 一句话概括

跟配 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 秒实验

  1. 把附加依赖项里的 swscale.lib 删掉,编译调用 sws_scale 的代码,看报什么错。
  2. 算一下:只做解码不做转格式,最少要链哪几个库?
  3. 判断一下:MSVC 能编过、Linux 编不过,最可能是哪个环节的问题?

常见误区

  1. 不写 extern "C" 包 include。 大多数时候能编过, 但老版本或者只 include 部分头文件时会报「未解析」。
  2. 以为有一个「FFmpeg world 库」。 没有,必须逐个列依赖项。
  3. 库顺序写反。 静态链接时 avutil 必须放最后。gcc 严格按顺序,MSVC 宽容。
  4. 靠 av.get_include() 找头文件。 它只是拼了个路径,目录可能不存在。要自己下 dev 包。
自测三问(点开对答案) 1. FFmpeg 的配置比 OpenCV 多哪个坑? —— `extern "C"`:FFmpeg 是 C 库,C++ 直接 include 会被做名字修饰,链接时找不到符号。 2. 为什么 FFmpeg 的库要按顺序链接? —— 静态链接时前面的库要用后面的库解析符号。`avutil` 是最底层,必须放最后。 3. `av.get_include()` 返回的路径一定存在吗? —— 不一定。它只是「按约定拼一个路径」,PyAV 的 wheel 里根本没有 include 目录。

配置通了,最后一节回到代码:把八步写成能跑的循环。重点是一进多出、以及冲空包。

相关笔记