4. 硬件加速接口与框架

各位同学,欢迎来到第四章。前面我们聊了硬件加速的基本概念,也看了FFmpeg的框架。今天要动真格的了——怎么让代码真正“摸”到硬件设备。

说白了,硬件加速的第一步不是编码解码,而是探测。你得先知道系统里有哪些硬件可用,然后创建对应的设备上下文,最后检查这个设备能不能干你想干的活。这三步走完,才能开始真正的加速流程。

核心三连问:

  • 系统里有哪些硬件加速器?(枚举)
  • 怎么创建它的上下文?(创建)
  • 它能处理我的编码/解码任务吗?(能力检查)

4.1 枚举可用硬件设备:av_hwdevice_iterate_types()

先说说枚举。我记得早期FFmpeg版本里,枚举硬件设备的方式比较笨——你得自己维护一个列表,挨个试。后来社区终于加了个正经的迭代器API。

av_hwdevice_iterate_types() 这个函数,用法很简单:

enum AVHWDeviceType type = AV_HWDEVICE_TYPE_NONE;
while ((type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE) {
    printf("找到硬件设备类型: %s\n", av_hwdevice_get_type_name(type));
}

第一次调用传 AV_HWDEVICE_TYPE_NONE,它会返回第一个可用的设备类型。之后每次传上一次返回的类型,直到返回 AV_HWDEVICE_TYPE_NONE 表示枚举结束。

嗯,这里要注意:这个函数只告诉你FFmpeg编译时支持哪些类型,不代表这些设备真的插在机器上。比如你编译了CUDA支持,但机器没NVIDIA显卡,它照样会返回 AV_HWDEVICE_TYPE_CUDA。真正的可用性检查,得靠后面的创建步骤。

设备类型枚举值 对应硬件 常见平台
AV_HWDEVICE_TYPE_CUDA NVIDIA GPU (CUDA) Windows/Linux
AV_HWDEVICE_TYPE_VAAPI Intel/AMD GPU (VA-API) Linux
AV_HWDEVICE_TYPE_DXVA2 DirectX Video Acceleration 2 Windows
AV_HWDEVICE_TYPE_D3D11VA Direct3D 11 Video Acceleration Windows 8+
AV_HWDEVICE_TYPE_VIDEOTOOLBOX Apple VideoToolbox macOS/iOS
AV_HWDEVICE_TYPE_MEDIACODEC Android MediaCodec Android
AV_HWDEVICE_TYPE_VULKAN Vulkan (跨平台) 多平台

个人经验:我在做跨平台播放器时,枚举结果在不同系统上差异很大。Windows上通常能列出3-4种,Linux上可能只有VAAPI,macOS上就VideoToolbox。建议把枚举结果打印到日志里,方便调试。

4.2 创建硬件设备上下文:av_hwdevice_ctx_create()

枚举只是开胃菜。真正干活的是 av_hwdevice_ctx_create()。这个函数负责初始化硬件设备,返回一个 AVBufferRef 类型的设备上下文。

函数签名长这样:

int av_hwdevice_ctx_create(AVBufferRef **device_ctx,
                           enum AVHWDeviceType type,
                           const char *device,
                           AVDictionary *opts,
                           int flags);

参数一个一个说:

  • device_ctx:输出参数,指向设备上下文的指针。成功后这里会存一个 AVBufferRef。
  • type:设备类型,就是刚才枚举出来的那些。
  • device:设备标识符。传 NULL 表示用默认设备。也可以传具体名称,比如 "0" 表示第一块显卡,或者 "/dev/dri/renderD128" 这种路径。
  • opts:额外选项字典。不同设备有不同选项,比如VAAPI可以传 "driver"="iHD"。
  • flags:保留字段,目前传0就行。

来个完整示例:

AVBufferRef *hw_device_ctx = NULL;
int ret = av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_VAAPI,
                                 NULL, NULL, 0);
if (ret < 0) {
    fprintf(stderr, "创建VAAPI设备失败: %s\n", av_err2str(ret));
    // 这里可以尝试其他设备类型
} else {
    printf("VAAPI设备创建成功!\n");
    // 后续使用 hw_device_ctx
}

我曾经踩过的坑:在Linux服务器上,VAAPI设备创建失败最常见的原因是权限问题。用户没有 video 组权限,或者 /dev/dri/* 的访问权限不够。还有一次是因为Intel驱动没装全,返回了 AVERROR(ENOSYS)。建议创建失败时打印详细错误码,别只看“失败”两个字。

创建成功后,hw_device_ctx 就可以传给编码器或解码器了。具体怎么传,后面章节会细讲。

4.3 检查设备能力:AVCodecHWConfig

设备创建好了,但能不能用来编解码?这得看 AVCodecHWConfig。

每个编解码器(AVCodec)都可能关联多个硬件配置。每个配置描述了一种硬件加速方式。你需要遍历这些配置,找到匹配的。

获取配置的API是 avcodec_get_hw_config():

const AVCodecHWConfig *config;
for (int i = 0;; i++) {
    config = avcodec_get_hw_config(codec, i);
    if (!config) break;  // 没有更多配置了
    if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) {
        printf("编解码器 %s 支持设备类型: %s\n",
               codec->name,
               av_hwdevice_get_type_name(config->device_type));
    }
}

AVCodecHWConfig 结构体里几个关键字段:

字段 含义 常用值
pix_fmt 硬件帧的像素格式 AV_PIX_FMT_VAAPI, AV_PIX_FMT_CUDA
methods 硬件加速方法标志 AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX
device_type 需要的设备类型 AV_HWDEVICE_TYPE_VAAPI

关键判断逻辑:

检查 methods 是否包含 AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX。这个标志表示该编解码器支持通过外部传入的设备上下文进行硬件加速。如果不包含,说明它可能用其他方式(比如内部自动选择),或者根本不支持硬件加速。

4.4 完整探测流程

把上面三步串起来,就是一个完整的硬件探测流程。我画了张图,方便你理解:

硬件设备探测完整流程 硬件设备探测完整流程 步骤1:枚举设备类型 av_hwdevice_iterate_types() 步骤2:创建设备上下文 av_hwdevice_ctx_create() 创建成功? 尝试下一种类型 步骤3:检查能力 avcodec_get_hw_config() 匹配成功 → 使用该设备 避坑提醒 设备类型枚举成功 ≠ 设备可用 必须经过创建验证

实际项目中,我一般会写一个工具函数,自动遍历所有设备类型,找到第一个能成功创建的。代码大概这样:

AVBufferRef *find_first_working_hw_device(enum AVHWDeviceType preferred) {
    AVBufferRef *hw_ctx = NULL;
    
    // 先试首选类型
    if (preferred != AV_HWDEVICE_TYPE_NONE) {
        int ret = av_hwdevice_ctx_create(&hw_ctx, preferred, NULL, NULL, 0);
        if (ret == 0) return hw_ctx;
    }
    
    // 不行就遍历所有类型
    enum AVHWDeviceType type = AV_HWDEVICE_TYPE_NONE;
    while ((type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE) {
        if (type == preferred) continue;  // 已经试过了
        int ret = av_hwdevice_ctx_create(&hw_ctx, type, NULL, NULL, 0);
        if (ret == 0) {
            printf("回退到设备类型: %s\n", av_hwdevice_get_type_name(type));
            return hw_ctx;
        }
    }
    
    return NULL;  // 所有设备都失败
}

我的习惯:在正式产品里,我不会用这种“暴力遍历”的方式。而是先读配置文件或注册表,确定用户期望的硬件类型。遍历只作为兜底策略。你想想看,如果用户明确指定了“用NVIDIA显卡”,结果你自动选了Intel的,那体验多糟糕。

4.5 常见问题与避坑

最后聊几个我实际踩过的坑:

  • 设备创建成功,但编解码失败:这通常是因为设备类型和编解码器不匹配。比如你创建了VAAPI设备,但解码器是 h264_cuvid(NVIDIA的)。检查 AVCodecHWConfig 里的 device_type 是否一致。
  • 多显卡环境下的设备选择:av_hwdevice_ctx_create 的 device 参数可以传 "0"、"1" 等索引。但不同平台索引规则不一样。Windows上D3D11VA的索引和CUDA的索引可能不同。我建议先枚举所有设备,让用户选。
  • 内存泄漏:av_hwdevice_ctx_create 返回的 AVBufferRef 需要用 av_buffer_unref() 释放。别忘记。

我曾经遇到一个诡异的bug:在某个Linux发行版上,VAAPI设备创建总是返回成功,但解码出来的画面全是绿的。查了两天才发现是驱动版本太老,虽然设备上下文创建成功了,但实际硬件编码功能缺失。所以,创建成功不代表一切正常——最好再做一个简短的编解码测试,验证设备真的能用。