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)
验证方法
- 使用硬解码器(如VAAPI)解码一段H.264视频,获取NV12格式的AVFrame。
- 调用
convert_frame_to_rgb24得到RGB24帧。 - 检查返回值非NULL,且
rgb_frame->format == AV_PIX_FMT_RGB24。 - 将
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应创建一次并跨帧复用,避免每帧重复初始化。