3. 硬解码器参数配置

任务目标

在FFmpeg中,硬解码器(如h264_cuvid、hevc_qsv)的参数配置与软解码器不同,需通过avcodec_parameters_to_context后的hw_device_ctx关联及AVCodecContext字段设置完成。本案例以NVIDIA CUVID为例,演示如何配置解码输出的分辨率限制、线程数及码率相关选项,并对比不同配置下的解码帧率。

环境依赖

  • FFmpeg 4.x+(启用--enable-cuda-nvcc或--enable-cuvid)
  • NVIDIA显卡驱动及CUDA工具包
  • 输入测试视频:H.264编码,分辨率1920x1080,码率约8Mbps

关键完整代码

// harddec_config.c
#include <stdio.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/hwcontext.h>

static int decode_video(const char *filename, int thread_count, int resize_w, int resize_h) {
    AVFormatContext *fmt_ctx = NULL;
    AVCodecContext *dec_ctx = NULL;
    AVCodec *decoder = NULL;
    AVBufferRef *hw_device_ctx = NULL;
    AVPacket *pkt = NULL;
    AVFrame *frame = NULL;
    int ret, frame_count = 0;
    int64_t start_time, end_time;

    if (avformat_open_input(&fmt_ctx, filename, NULL, NULL) < 0) return -1;
    if (avformat_find_stream_info(fmt_ctx, NULL) < 0) return -1;

    int video_stream = -1;
    for (int i = 0; i < fmt_ctx->nb_streams; i++) {
        if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            video_stream = i;
            break;
        }
    }
    if (video_stream == -1) return -1;

    // 选择硬解码器
    decoder = avcodec_find_decoder_by_name("h264_cuvid");
    if (!decoder) {
        fprintf(stderr, "h264_cuvid not found\n");
        return -1;
    }

    dec_ctx = avcodec_alloc_context3(decoder);
    avcodec_parameters_to_context(dec_ctx, fmt_ctx->streams[video_stream]->codecpar);

    // 创建硬件设备上下文
    ret = av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_CUDA, NULL, NULL, 0);
    if (ret < 0) {
        fprintf(stderr, "Failed to create CUDA device\n");
        return -1;
    }
    dec_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx);

    // === 参数配置核心 ===
    // 1. 线程数(硬解码通常建议1,多线程由驱动管理)
    dec_ctx->thread_count = thread_count;
    // 2. 输出分辨率限制(0表示不限制)
    if (resize_w > 0 && resize_h > 0) {
        dec_ctx->width = resize_w;
        dec_ctx->height = resize_h;
    }
    // 3. 码率相关:硬解码器通常忽略bit_rate,但可设置低延迟模式
    dec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
    // 4. 通过AVOption设置CUVID专用参数
    av_opt_set_int(dec_ctx->priv_data, "resize", (resize_w << 16) | resize_h, 0);
    av_opt_set_int(dec_ctx->priv_data, "surfaces", 8, 0);  // 解码表面数
    // ========================

    if (avcodec_open2(dec_ctx, decoder, NULL) < 0) {
        fprintf(stderr, "Failed to open decoder\n");
        return -1;
    }

    pkt = av_packet_alloc();
    frame = av_frame_alloc();
    start_time = av_gettime();

    while (av_read_frame(fmt_ctx, pkt) >= 0) {
        if (pkt->stream_index == video_stream) {
            ret = avcodec_send_packet(dec_ctx, pkt);
            if (ret < 0) break;
            while (ret >= 0) {
                ret = avcodec_receive_frame(dec_ctx, frame);
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break;
                if (ret < 0) break;
                frame_count++;
                av_frame_unref(frame);
            }
        }
        av_packet_unref(pkt);
    }

    end_time = av_gettime();
    double duration = (end_time - start_time) / 1000000.0;
    printf("Threads=%d Resize=%dx%d Frames=%d Time=%.2fs FPS=%.1f\n",
           thread_count, resize_w, resize_h, frame_count, duration, frame_count / duration);

    av_frame_free(&frame);
    av_packet_free(&pkt);
    avcodec_free_context(&dec_ctx);
    av_buffer_unref(&hw_device_ctx);
    avformat_close_input(&fmt_ctx);
    return 0;
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        fprintf(stderr, "Usage: %s <input.h264>\n", argv[0]);
        return 1;
    }
    // 测试不同参数组合
    decode_video(argv[1], 1, 0, 0);       // 默认
    decode_video(argv[1], 4, 0, 0);       // 多线程
    decode_video(argv[1], 1, 1280, 720);  // 缩放输出
    return 0;
}

编译与运行

# 编译(需pkg-config配置FFmpeg路径)
gcc -o harddec_config harddec_config.c $(pkg-config --cflags --libs libavcodec libavformat libavutil)

# 运行
./harddec_config input.h264

验证方法与判定标准

  • 功能验证:程序能正常解码并输出帧数统计,无报错退出。
  • 参数生效验证:观察输出中不同thread_count和resize参数下的FPS差异。若线程数从1改为4时FPS无明显提升(硬解码器通常由驱动管理并行度),说明线程参数对CUVID不敏感;若设置resize=1280x720后解码帧率提升,说明分辨率限制生效。
  • 预期输出示例(非实测值,仅示意格式):
    Threads=1 Resize=0x0 Frames=1500 Time=3.20s FPS=468.8
    Threads=4 Resize=0x0 Frames=1500 Time=3.18s FPS=471.7
    Threads=1 Resize=1280x720 Frames=1500 Time=2.45s FPS=612.2
  • 判定通过条件:程序能完成解码循环,输出三行统计信息,且不同参数配置下FPS数值存在可观察差异(尤其resize参数应带来明显变化)。

常见问题

  • h264_cuvid未找到:确认FFmpeg编译时启用了--enable-cuvid和--enable-nvenc。
  • CUDA设备创建失败:检查驱动版本及nvidia-smi是否正常。
  • 码率参数无效:硬解码器不直接使用bit_rate字段,码率控制由编码器负责;解码端可通过AV_CODEC_FLAG_LOW_DELAY降低延迟。