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 集成架构图 你的应用 CMake 构建系统 libavformat libavcodec libavutil libswscale 操作系统(Linux / Windows / macOS) 硬件加速(VAAPI / NVENC / VideoToolbox) CMake 通过 find_package 或 pkg-config 将 FFmpeg 库链接到应用

常见问题与避坑指南

集成 FFmpeg 时,有几个问题我几乎每次都会遇到:

  • 链接顺序问题:FFmpeg 库之间有依赖关系,链接顺序不对会报 undefined reference。正确的顺序是:avformatavcodecavutilswscaleswresample。我曾经因为顺序写反,排查了整整一个下午。
  • 动态库 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_pathfind_library,指定路径即可。

总结

集成 FFmpeg 其实不复杂,核心就是三件事:找到头文件、找到库文件、正确链接。用 CMake 的 pkg_check_modules 或者自定义 FindFFmpeg.cmake 都能搞定。我个人更推荐后者,因为可控性更强,跨平台也更方便。

记住一点:FFmpeg 的 API 文档虽然不算特别好,但它的示例代码质量很高。遇到问题先去 doc/examples 目录下翻一翻,往往能找到答案。

公众号:蓝海资料掘金营,微信 deep3321