ffmpeg库视频硬解码使用流程

FFmpeg 的硬解码(Hardware Decoding)通过调用 GPU 或专用硬件的编解码能力实现,能显著降低 CPU 占用率。

一、FFmpeg 支持的硬件解码类型

FFmpeg 原生支持多种硬件加速类型,具体由 AVHWDeviceType 定义,包括:

  • NVIDIA CUDA/NVDEC‌:基于 NVIDIA 显卡的解码‌。

  • ‌**Intel Quick Sync Video (QSV)**‌:Intel 集成显卡的硬件加速‌。

  • VAAPI‌:适用于 Intel/AMD 硬件的通用视频加速 API‌。

  • VideoToolbox‌:macOS/iOS 平台的硬解码‌。

  • MediaCodec ‌:Android 平台的硬解码(需 FFmpeg 编译时启用)‌。

    复制代码
    enum AVHWDeviceType {
        AV_HWDEVICE_TYPE_NONE,
        AV_HWDEVICE_TYPE_VDPAU,
        AV_HWDEVICE_TYPE_CUDA,
        AV_HWDEVICE_TYPE_VAAPI,
        AV_HWDEVICE_TYPE_DXVA2,
        AV_HWDEVICE_TYPE_QSV,
        AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
        AV_HWDEVICE_TYPE_D3D11VA,
        AV_HWDEVICE_TYPE_DRM,
        AV_HWDEVICE_TYPE_OPENCL,
        AV_HWDEVICE_TYPE_MEDIACODEC,
    };

av_hwdevice_find_type_by_name:根据名称查找对应的AVHWDeviceType。支持的名称如下所示。

复制代码
static const char *const hw_type_names[] = {
    [AV_HWDEVICE_TYPE_CUDA]   = "cuda",
    [AV_HWDEVICE_TYPE_DRM]    = "drm",
    [AV_HWDEVICE_TYPE_DXVA2]  = "dxva2",
    [AV_HWDEVICE_TYPE_D3D11VA] = "d3d11va",
    [AV_HWDEVICE_TYPE_OPENCL] = "opencl",
    [AV_HWDEVICE_TYPE_QSV]    = "qsv",
    [AV_HWDEVICE_TYPE_VAAPI]  = "vaapi",
    [AV_HWDEVICE_TYPE_VDPAU]  = "vdpau",
    [AV_HWDEVICE_TYPE_VIDEOTOOLBOX] = "videotoolbox",
    [AV_HWDEVICE_TYPE_MEDIACODEC] = "mediacodec",
};

与硬解相关的函数:

avcodec_get_hw_config:用于获取编解码器支持的硬件配置AVCodecHWConfig。这里用于获取硬件支持的像素格式。

av_hwdevice_ctx_create:av_hwdevice_ctx_create创建硬件设备相关的上下文信息AVHWDeviceContext和对硬件设备进行初始化。

decoder_ctx->get_format = get_hw_format ,get_hw_format是向AVCodecContext注册的一个函数,用于协商支持的像素格式。

av_hwframe_transfer_data:拷贝数据到一个硬件的surface,或者从一个硬件surface拷贝数据,也就是GPU和CPU之间数据拷贝。这里用于GPU拷贝到CPU。GPU解码后数据格式默认类型是从硬件读取,CUDA可能是AV_PIX_FMT_NV12;而CPU解码后的数据一般是YUV数据,比如AV_PIX_FMT_YUV420P。

av_find_best_stream:查找最佳媒体流(如视频、音频、字幕等)的函数。

复制代码
enum AVPixelFormat hw_pix_fmt;
enum AVHWDeviceType type = AV_HWDEVICE_TYPE_CUDA;
AVCodec *decoder = NULL;

/* open the input file */
    if (avformat_open_input(&input_ctx, argv[2], NULL, NULL) != 0) {
        fprintf(stderr, "Cannot open input file '%s'\n", argv[2]);
        return -1;
    }

    if (avformat_find_stream_info(input_ctx, NULL) < 0) {
        fprintf(stderr, "Cannot find input stream information.\n");
        return -1;
    }

    /* find the video stream information */
    ret = av_find_best_stream(input_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &decoder, 0);
    if (ret < 0) {
        fprintf(stderr, "Cannot find a video stream in the input file\n");
        return -1;
    }

for (i = 0;; i++) {
        const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i);
        if (!config) {
            fprintf(stderr, "Decoder %s does not support device type %s.\n",
                    decoder->name, av_hwdevice_get_type_name(type));
            return -1;
        }
        if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX &&
            config->device_type == type) {
            hw_pix_fmt = config->pix_fmt;
            break;
        }
    }

二、FFmpeg 硬件解码器名称及对应编码格式

FFmpeg 支持的硬件解码器名称与编码格式关联紧密,需根据具体硬件平台(如 NVIDIA、Intel、AMD)及接口协议(如 CUDA、VAAPI、QSV)选择适配方案。以下是主流编码格式对应的硬件解码器名称示例:

2.1、视频编码格式与硬件解码器
  1. H.264/AVC

    • h264_cuvid(NVIDIA CUDA加速)
    • h264_qsv(Intel Quick Sync Video)
    • h264_vaapi(跨平台开源接口)
    • h264_amf(AMD Advanced Media Framework)‌
  2. H.265/HEVC

    • hevc_cuvid(NVIDIA)
    • hevc_qsv(Intel)
    • hevc_vaapi(通用接口)
    • hevc_amf(AMD)‌
  3. VP8/VP9

    • vp8_cuvid(NVIDIA)
    • vp9_vaapi(通用接口)
    • vp9_qsv(Intel)‌
  4. AV1

    • av1_qsv(Intel)
    • av1_vaapi(通用接口)‌
2.2、音频编码格式与硬件解码器
  1. AAC

    • 硬件解码依赖平台驱动支持(如 Intel HD Audio),FFmpeg 中通常通过系统接口调用,无独立硬解名称‌。
  2. MP3/Opus

    • 硬解支持较少,多采用软件解码‌。

‌三**、硬解码实现流程**‌

1. 初始化硬件设备
  • 获取硬件设备类型
    通过 av_hwdevice_find_type_by_name 或枚举类型确定目标硬解码设备‌。
  • 创建硬件设备上下文
    使用 av_hwdevice_ctx_create 初始化硬件设备上下文(hw_device_ctx)‌。
2. 配置解码器
  • 查找支持硬解码的编解码器
    例如 H.264 硬解需查找 h264_cuvid(NVIDIA)或 h264_mediacodec(Android)等解码器‌。
  • 设置解码器参数
    AVCodecContext 中指定 hw_device_ctx,关联硬件设备上下文‌。
3. 解码数据
  • 发送数据包
    调用 avcodec_send_packet 将压缩数据送入解码器。
  • 接收解码帧
    通过 avcodec_receive_frame 获取解码后的帧数据,硬件解码的帧通常存储在 GPU 内存中‌。
4. 处理解码数据
  • 内存映射与格式转换
    若需 CPU 访问解码数据,需使用 av_hwframe_transfer_data 将帧从 GPU 内存复制到 CPU 内存‌。

‌四**、代码示例**

3.1 实现硬件解码(以 ‌NVIDIA CUDA/NVDEC‌ 为例)的完整示例代码。

复制代码
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/hwcontext.h>

int main(int argc, char *argv[]) {
    AVFormatContext *fmt_ctx = NULL;
    AVCodecContext *codec_ctx = NULL;
    const AVCodec *codec = NULL;
    AVBufferRef *hw_device_ctx = NULL;
    AVPacket *pkt = NULL;
    AVFrame *hw_frame = NULL, *sw_frame = NULL;
    int video_stream_idx = -1;

    // 1. 初始化 FFmpeg
    avformat_network_init();

    // 2. 打开输入文件
    if (avformat_open_input(&fmt_ctx, "input.mp4", NULL, NULL) < 0) {
        fprintf(stderr, "无法打开输入文件\n");
        return -1;
    }

    // 3. 查找视频流索引
    if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
        fprintf(stderr, "无法获取流信息\n");
        goto cleanup;
    }
    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;
        }
    }
    if (video_stream_idx == -1) {
        fprintf(stderr, "未找到视频流\n");
        goto cleanup;
    }

    // 4. 初始化硬件设备 (CUDA)
    if (av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_CUDA, NULL, NULL, 0) < 0) {
        fprintf(stderr, "无法创建 CUDA 硬件设备\n");
        goto cleanup;
    }

    // 5. 配置硬件解码器
    codec = avcodec_find_decoder_by_name("h264_cuvid"); // NVIDIA 硬解解码器
    if (!codec) {
        fprintf(stderr, "未找到支持的硬解解码器\n");
        goto cleanup;
    }
    codec_ctx = avcodec_alloc_context3(codec);
    avcodec_parameters_to_context(codec_ctx, fmt_ctx->streams[video_stream_idx]->codecpar);
    codec_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx); // 关联硬件设备

    // 6. 打开解码器
    if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
        fprintf(stderr, "无法打开硬解解码器\n");
        goto cleanup;
    }

    // 7. 初始化数据包和帧
    pkt = av_packet_alloc();
    hw_frame = av_frame_alloc();
    sw_frame = av_frame_alloc();

    // 8. 解码循环
    while (av_read_frame(fmt_ctx, pkt) >= 0) {
        if (pkt->stream_index == video_stream_idx) {
            // 发送数据包到解码器
            if (avcodec_send_packet(codec_ctx, pkt) < 0) {
                fprintf(stderr, "发送数据包失败\n");
                continue;
            }

            // 接收解码后的帧
            while (avcodec_receive_frame(codec_ctx, hw_frame) == 0) {
                // 检查是否为硬件帧
                if (hw_frame->format == AV_PIX_FMT_CUDA) {
                    // 将 GPU 内存数据复制到 CPU 内存
                    if (av_hwframe_transfer_data(sw_frame, hw_frame, 0) < 0) {
                        fprintf(stderr, "GPU→CPU 内存拷贝失败\n");
                        continue;
                    }

                    // 在此处理 sw_frame(YUV420 数据)
                    // 例如:保存到文件、渲染、转码等
                    printf("解码一帧:宽度=%d, 高度=%d\n", sw_frame->width, sw_frame->height);
                }
                av_frame_unref(hw_frame);
                av_frame_unref(sw_frame);
            }
        }
        av_packet_unref(pkt);
    }

cleanup:
    // 9. 释放资源
    av_frame_free(&hw_frame);
    av_frame_free(&sw_frame);
    av_packet_free(&pkt);
    avcodec_free_context(&codec_ctx);
    av_buffer_unref(&hw_device_ctx);
    avformat_close_input(&fmt_ctx);
    avformat_network_deinit();
    return 0;
}

说明:

AV_PIX_FMT_CUDA等像素格式对比。

格式 存储位置 典型用途 性能优势
AV_PIX_FMT_CUDA GPU 显存 硬解码、全流程 GPU 处理 零拷贝、低延迟‌
AV_PIX_FMT_NV12 CPU 内存 软解码、跨设备处理 兼容性强,但需拷贝‌
AV_PIX_FMT_RGB24 CPU 内存 图像显示、算法输入 通用性强,但带宽占用高‌

‌五**、注意事项**‌

  1. 编译配置
    启用硬解码需在 FFmpeg 编译时添加对应选项(如 --enable-cuda --enable-cuvid --enable-nonfree)‌。
  2. 平台差异
    • Windows:常用 DXVA2 或 NVIDIA CUDA‌6。
    • Android:需启用 --enable-mediacodec 并关联 MediaCodec API‌。
  3. 兼容性回退
    硬解码失败时需切换至软解(如 h264 解码器)‌。
  4. 硬件类型选择
    若需使用其他硬件(如 Intel QSV 或 VAAPI):
    1)解码器名称改为 h264_qsv 或 h264_vaapi。
    2)修改 AV_HWDEVICE_TYPE_CUDA 为 AV_HWDEVICE_TYPE_QSV 或 AV_HWDEVICE_TYPE_VAAPI。
  5. 滤镜处理 ‌:通过 libavfilter 实现缩放、裁剪、水印等操作‌。
  6. 封装格式转换 ‌:使用 avformat_write_header()av_write_frame() 实现转封装(如 MP4 转 TS)‌。

‌六**、性能优化**‌

  • 减少内存拷贝‌:直接在 GPU 内存中处理数据(如 OpenGL 渲染)‌。
  • 帧格式限制 ‌:硬解码输出格式通常为 NV12YUV420P,需适配后续处理流程‌。
相关推荐
byxdaz4 小时前
利用ffmpeg库实现音频Opus编解码
ffmpeg·音视频·opus
byxdaz9 小时前
利用ffmpeg库实现音频AAC编解码
ffmpeg·音视频·aac
Yeauty1 天前
三分钟掌握视频分辨率修改 | 在 Rust 中优雅地使用 FFmpeg
rust·ffmpeg·音视频
zooKevin1 天前
腾讯云宝塔安装ffmpeg
ffmpeg·node.js·腾讯云
小gpt&2 天前
03 介绍ffmpeg 视频解码流程
c++·qt·ffmpeg·音视频
AJi2 天前
Android音视频框架探索(一):多媒体系统服务MediaServer
android·ffmpeg·音视频开发
年轮不改3 天前
Ubuntu 配置 ffmpeg 开发环境
linux·ubuntu·ffmpeg
却道天凉_好个秋3 天前
linux(centos8)下编译ffmpeg
linux·运维·ffmpeg