4. 硬解码像素格式转换

任务目标

FFmpeg硬解码器(如VAAPI、CUVID、QSV)输出的像素格式通常为NV12等硬件友好格式,而显示或OpenGL纹理上传、图像处理等场景往往需要RGB24/BGR0。本案例实现一个最小可复用的转换模块:从AVFrame(NV12)出发,利用libswscale完成到RGB24的转换,并输出转换后的AVFrame供后续使用。

核心实现

#include <libavutil/frame.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>

/**
 * 将任意像素格式的AVFrame转换为RGB24
 * @param src 输入帧(如硬解码输出的NV12)
 * @return 新分配的RGB24帧,使用后需调用av_frame_free释放
 */
AVFrame *convert_frame_to_rgb24(AVFrame *src) {
    if (!src || !src->data[0]) return NULL;

    // 1. 创建目标帧
    AVFrame *dst = av_frame_alloc();
    if (!dst) return NULL;

    dst->format = AV_PIX_FMT_RGB24;
    dst->width  = src->width;
    dst->height = src->height;

    // 2. 分配目标帧缓冲区
    if (av_frame_get_buffer(dst, 32) < 0) {
        av_frame_free(&dst);
        return NULL;
    }

    // 3. 初始化SwsContext(每次转换创建,简单可靠;性能敏感时可缓存复用)
    struct SwsContext *sws_ctx = sws_getContext(
        src->width, src->height, (enum AVPixelFormat)src->format,
        dst->width, dst->height, AV_PIX_FMT_RGB24,
        SWS_BILINEAR, NULL, NULL, NULL);
    if (!sws_ctx) {
        av_frame_free(&dst);
        return NULL;
    }

    // 4. 执行转换
    sws_scale(sws_ctx,
              (const uint8_t *const *)src->data, src->linesize,
              0, src->height,
              dst->data, dst->linesize);

    sws_freeContext(sws_ctx);
    return dst;
}

调用示例

// 假设decoded_frame是从硬解码器拿到的AVFrame(NV12格式)
AVFrame *decoded_frame = ...; // 来自avcodec_receive_frame

AVFrame *rgb_frame = convert_frame_to_rgb24(decoded_frame);
if (!rgb_frame) {
    // 处理转换失败
}

// 此时rgb_frame->data[0]即为RGB24像素数据
// 使用完毕后释放
av_frame_free(&rgb_frame);

编译与链接

# 依赖FFmpeg的libavutil、libswscale
gcc -o converter converter.c $(pkg-config --cflags --libs libavutil libswscale)

验证方法

  1. 使用硬解码器(如VAAPI)解码一段H.264视频,获取NV12格式的AVFrame。
  2. 调用convert_frame_to_rgb24得到RGB24帧。
  3. 检查返回值非NULL,且rgb_frame->format == AV_PIX_FMT_RGB24。
  4. 将rgb_frame->data[0]写入BMP文件(BMP格式为BGR顺序,需注意字节序),或直接使用av_image_write_to_file辅助函数输出PPM文件验证:
// 输出PPM验证(PPM为RGB顺序,可直接验证)
FILE *f = fopen("output.ppm", "wb");
fprintf(f, "P6\n%d %d\n255\n", rgb_frame->width, rgb_frame->height);
for (int y = 0; y < rgb_frame->height; y++) {
    fwrite(rgb_frame->data[0] + y * rgb_frame->linesize[0], 1, rgb_frame->width * 3, f);
}
fclose(f);

用图片查看器打开output.ppm,画面内容与源视频一致且颜色正常,即判定转换通过。

关键说明

  • 硬解码帧可能带有对齐填充(linesize大于width*像素字节数),转换时sws_scale已通过linesize参数正确处理,无需手动拷贝。
  • 若需转换到BGR0(如OpenGL纹理常用格式),将AV_PIX_FMT_RGB24替换为AV_PIX_FMT_BGR0即可,其余逻辑不变。
  • 性能敏感场景下,SwsContext应创建一次并跨帧复用,避免每帧重复初始化。