84、FFmpeg:集成FFmpeg音视频处理库
说实话,FFmpeg 这个库,做音视频开发的基本绕不开。我最早接触它是在做一个视频剪辑工具的时候,那时候被各种编解码参数搞得头大。后来用 CMake 集成 FFmpeg,踩了不少坑,今天我把这些经验整理出来,希望能帮你少走弯路。
FFmpeg 是什么?
FFmpeg 是一个跨平台的音视频处理库。它支持几乎所有的音视频格式,能解码、编码、转码、推流、拉流。说白了,你看到的视频播放器、直播软件、视频编辑工具,底层大概率都用了 FFmpeg。
它的核心组件包括:
- libavformat:封装格式处理,比如 MP4、FLV、TS 这些容器
- libavcodec:编解码器,H.264、H.265、AAC 都归它管
- libavutil:工具函数库,内存管理、数学运算这些基础能力
- libswscale:图像缩放和色彩空间转换
- libswresample:音频重采样
核心要点:FFmpeg 的 API 设计比较底层,你需要手动管理内存、处理数据包。但这也意味着它的灵活性极高,几乎能实现你想要的任何音视频功能。
CMake 集成 FFmpeg 的两种方式
我在项目中用过两种方式集成 FFmpeg,各有优劣。
方式一:系统级查找(find_package)
如果你的系统已经安装了 FFmpeg,可以用 CMake 的 find_package 来找它。我个人习惯用 pkg-config 的方式,因为 FFmpeg 的库比较多,手动写 find_library 容易漏掉。
find_package(PkgConfig REQUIRED)
pkg_check_modules(AVCODEC REQUIRED libavcodec)
pkg_check_modules(AVFORMAT REQUIRED libavformat)
pkg_check_modules(AVUTIL REQUIRED libavutil)
pkg_check_modules(SWSCALE REQUIRED libswscale)
pkg_check_modules(SWRESAMPLE REQUIRED libswresample)
target_include_directories(my_app PRIVATE
${AVCODEC_INCLUDE_DIRS}
${AVFORMAT_INCLUDE_DIRS}
${AVUTIL_INCLUDE_DIRS}
${SWSCALE_INCLUDE_DIRS}
${SWRESAMPLE_INCLUDE_DIRS}
)
target_link_libraries(my_app PRIVATE
${AVCODEC_LIBRARIES}
${AVFORMAT_LIBRARIES}
${AVUTIL_LIBRARIES}
${SWSCALE_LIBRARIES}
${SWRESAMPLE_LIBRARIES}
)
这种方式的好处是简单,但有个坑——不同 Linux 发行版上 FFmpeg 的版本和库名可能不一样。我曾经在 Ubuntu 20.04 上编译通过,换到 CentOS 7 就报找不到库。嗯,这时候就需要第二种方式了。
方式二:源码编译 + 自定义 FindFFmpeg.cmake
如果你需要跨平台,或者要控制 FFmpeg 的编译选项(比如开启硬件加速),我建议自己编译 FFmpeg 源码,然后写一个 FindFFmpeg.cmake 模块。
这是我常用的一个模板:
# FindFFmpeg.cmake
set(FFMPEG_ROOT "/path/to/ffmpeg/install" CACHE PATH "FFmpeg安装路径")
find_path(AVCODEC_INCLUDE_DIR libavcodec/avcodec.h
PATHS ${FFMPEG_ROOT}/include
NO_DEFAULT_PATH)
find_library(AVCODEC_LIBRARY avcodec
PATHS ${FFMPEG_ROOT}/lib
NO_DEFAULT_PATH)
# 其他库类似...
if(AVCODEC_INCLUDE_DIR AND AVCODEC_LIBRARY)
set(FFMPEG_FOUND TRUE)
message(STATUS "Found FFmpeg: ${FFMPEG_ROOT}")
else()
message(FATAL_ERROR "FFmpeg not found!")
endif()
小技巧:编译 FFmpeg 时记得加上 --enable-shared 和 --enable-pic,否则链接时会报位置无关代码的错误。我刚开始编译时忘了加 --enable-pic,折腾了半天才发现问题。
一个完整的集成示例
下面是一个实际可用的 CMakeLists.txt,它集成了 FFmpeg 并实现了一个简单的视频解码功能。
cmake_minimum_required(VERSION 3.16)
project(ffmpeg_demo C)
set(CMAKE_C_STANDARD 11)
# 查找FFmpeg
find_package(PkgConfig REQUIRED)
pkg_check_modules(AVCODEC REQUIRED libavcodec)
pkg_check_modules(AVFORMAT REQUIRED libavformat)
pkg_check_modules(AVUTIL REQUIRED libavutil)
pkg_check_modules(SWSCALE REQUIRED libswscale)
add_executable(video_decoder
src/main.c
src/decoder.c
)
target_include_directories(video_decoder PRIVATE
${AVCODEC_INCLUDE_DIRS}
${AVFORMAT_INCLUDE_DIRS}
${AVUTIL_INCLUDE_DIRS}
${SWSCALE_INCLUDE_DIRS}
src
)
target_link_libraries(video_decoder PRIVATE
${AVCODEC_LIBRARIES}
${AVFORMAT_LIBRARIES}
${AVUTIL_LIBRARIES}
${SWSCALE_LIBRARIES}
)
# 添加编译选项
target_compile_options(video_decoder PRIVATE -Wall -Wextra)
对应的 decoder.c 核心代码片段:
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
int open_video_file(const char *filename) {
AVFormatContext *fmt_ctx = NULL;
// 打开输入文件
if (avformat_open_input(&fmt_ctx, filename, NULL, NULL) != 0) {
fprintf(stderr, "无法打开文件: %s\n", filename);
return -1;
}
// 查找流信息
if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
fprintf(stderr, "无法获取流信息\n");
return -1;
}
// 找到视频流
int video_stream_idx = -1;
for (int i = 0; i < fmt_ctx->nb_streams; i++) {
if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
video_stream_idx = i;
break;
}
}
// ... 后续解码逻辑
avformat_close_input(&fmt_ctx);
return 0;
}
注意事项:FFmpeg 4.0 之后,AVStream->codec 已经被废弃,改用 AVStream->codecpar。如果你用的是旧版教程,记得更新 API 调用。我曾经因为这个问题,在升级 FFmpeg 版本后编译报错,排查了半天才发现是 API 变了。
FFmpeg 集成架构图
下面这张图展示了 FFmpeg 各模块之间的关系,以及 CMake 如何把它们链接到你的应用中。
常见问题与避坑指南
集成 FFmpeg 时,有几个问题我几乎每次都会遇到:
- 链接顺序问题:FFmpeg 库之间有依赖关系,链接顺序不对会报 undefined reference。正确的顺序是:
avformat→avcodec→avutil→swscale→swresample。我曾经因为顺序写反,排查了整整一个下午。 - 动态库 vs 静态库:如果你用静态库,记得加上
-ldl -lpthread -lm这些系统库。动态库则要注意运行时能否找到.so文件,可以用LD_LIBRARY_PATH或者rpath来解决。 - 版本兼容性:FFmpeg 的 API 变化比较频繁。我建议在 CMake 中检查版本号,避免编译时才发现不兼容。
# 检查FFmpeg版本
if(AVCODEC_VERSION VERSION_LESS "4.0")
message(FATAL_ERROR "需要FFmpeg 4.0或更高版本")
endif()
我的习惯:在项目根目录放一个 third_party/ffmpeg/ 文件夹,里面放编译好的 FFmpeg 库。这样团队成员拉下代码就能直接编译,不用每个人都装一遍 FFmpeg。配合 CMake 的 find_path 和 find_library,指定路径即可。
总结
集成 FFmpeg 其实不复杂,核心就是三件事:找到头文件、找到库文件、正确链接。用 CMake 的 pkg_check_modules 或者自定义 FindFFmpeg.cmake 都能搞定。我个人更推荐后者,因为可控性更强,跨平台也更方便。
记住一点:FFmpeg 的 API 文档虽然不算特别好,但它的示例代码质量很高。遇到问题先去 doc/examples 目录下翻一翻,往往能找到答案。