如何修正从Cairo表面向FFmpeg AVFrame填充数据时的RGB颜色异常问题
问题原因及修复方案
核心错误点
你当前的代码存在3个直接导致颜色异常的问题:
- Cairo图像表面默认格式为
CAIRO_FORMAT_ARGB32,小端模式下每个4字节像素的存储顺序为B(偏移0)、G(偏移1)、R(偏移2)、A(偏移3),你直接将B值赋值给Y通道、G值赋值给U通道,还注释掉了R值对应的V通道赋值,红色分量完全丢失,自然红色图形显示异常 - 如果你使用的是FFmpeg默认的YUV420P格式AVFrame,U、V通道的宽高均为Y通道的1/2,你逐像素填充U、V通道会直接造成内存越界,还会导致采样完全错误
- 没有做RGB到YUV的色彩空间转换,直接把RGB值硬塞到YUV通道,本身就会产生严重的色偏
修复方案
方案1:使用匹配的像素格式(推荐,性能最优)
直接将AVFrame的像素格式设置为和Cairo surface匹配的AV_PIX_FMT_BGRA,无需色彩转换,直接按行拷贝即可,绝大多数主流视频编码器都支持该输入格式:
/* Image info and pixel data */ int width = cairo_image_surface_get_width( surface ); int height = cairo_image_surface_get_height( surface ); int stride = cairo_image_surface_get_stride( surface ); uint8_t* pix = cairo_image_surface_get_data( surface ); // 分配AVFrame时指定格式为AV_PIX_FMT_BGRA,宽高和surface一致 for( int row = 0; row < height; row++ ) { // 直接按行拷贝,不需要逐像素操作,效率更高 memcpy( img->video_frame->data[0] + row * img->video_frame->linesize[0], pix + row * stride, width * 4 // 每个像素占4字节 ); } img->video_frame->pts++;
方案2:转换为YUV420P格式(适配要求YUV输入的场景)
如果你的编码器强制要求YUV420P输入,使用FFmpeg内置的sws_scale工具做色彩空间转换,不需要手动写转换公式避免踩坑:
- 初始化转换上下文(全局初始化一次即可,不要每次编码都重复创建)
struct SwsContext* sws_ctx = sws_getContext( width, height, AV_PIX_FMT_BGRA, // 输入格式:Cairo的BGRA width, height, AV_PIX_FMT_YUV420P, // 输出格式:YUV420P SWS_BILINEAR, NULL, NULL, NULL );
- 每次编码前做格式转换
uint8_t* src_data[4] = {pix, NULL, NULL, NULL}; int src_linesize[4] = {stride, 0, 0, 0}; sws_scale(sws_ctx, src_data, src_linesize, 0, height, img->video_frame->data, img->video_frame->linesize);
- 程序退出前释放上下文
sws_freeContext(sws_ctx);
额外注意事项
- 如果确认你的Cairo surface使用的是
CAIRO_FORMAT_RGB24,对应FFmpeg像素格式改为AV_PIX_FMT_BGR24即可 - 若转换后仍有轻微色偏,检查AVFrame的
color_range参数,根据需要设置为全范围(AVCOL_RANGE_JPEG,0255)或有限范围(`AVCOL_RANGE_MPEG`,16235)即可修复
内容的提问来源于stack exchange,提问作者GiuTor
相关产品推荐
相关产品推荐

