在使用libyuv库时候出现的一个有关NV12转RGB24的问题,我不知道是什么原因,求解答。
先放流程图,方便理解问题,代码我放到最后面了:
首先我定义了一个2*2的简单像素的NV12图像(即v_width = 2, v_height = 2),根据NV12的定义,我定义的这个图像在内存中的排布
也就是下面这样:
Y1 Y2
Y3 Y4
U1 V1
总共6字节,其中Y_Stride和UV_Stride都为2
我定义的是
char outputBuffer[] = {0x11,0x11,0x11,0x11,0xFF,0x00};
// 即:
// 0x11 0x11
// 0x11 0x11
// 0xFF 0x00
这时,我将这个图像outputBuffer原封不动输出到/sdcard/source.yuv中。
然后先定义一个用来保存RGB24图像的【宽为2高为2通道数为3】大小的变量rgb_frame[12]
以及rgb_stride显然就是宽*通道数=6
接着就通过调用
libyuv::NV12ToRGB24(src_y, src_stride_y, src_uv, src_stride_uv, rgb_frame, rgb_stride, v_width, v_height);
这个函数通过libyuv库直接将outputBuffer转换成RGB24的格式,保存在rgb_frame这个变量当中。
此时,我们已经得到了通过libyuv转换的来的RGB24图像,我随即将这个linyuv转化来的RGB24图像rgb_frame原封不动输出到/sdcard/libyuv_target.rgb中。
直到现在,看起来一切正常对吧。问题来了。
接着我用ffmpeg来将上面两个文件都转化为RGB24类型的png格式,发现最终得到的两张图片不一致。
# 首先是没有经过libyuv转化的NV12源数据/sdcard/source.yuv,通过以下FFMPEG命令转化成RGB24的png图像:
# 第一步:
ffmpeg -s 2x2 -pix_fmt nv12 -i /sdcard/source.yuv -pix_fmt rgb24 /sdcard/source_ffmpeg_to_rgb.rgb
# 第二步:
ffmpeg -f rawvideo -pixel_format rgb24 -s:v 2x2 -i /sdcard/source_ffmpeg_to_rgb.rgb -c:v png /sdcard/source_ffmpeg_to_rgb.png
这样就得到了源数据对应的正确图像source_ffmpeg_to_rgb.png
# 然后再将libyuv转换的来的RGB24图像/sdcard/libyuv_target.rgb用FFMPEG转化成png格式的图像:
ffmpeg -f rawvideo -pixel_format rgb24 -s:v 2x2 -i /sdcard/libyuv_target.rgb -c:v png /sdcard/libyuv_target.png
这样就得到了libyuv转化得到的RGB24图像的png版本/sdcard/libyuv_target.png
在我的认知里,正常情况下, source_ffmpeg_to_rgb.png和这两个最终的png图像将会是一模一样,尽管可能FFMPEG将NV12转成RGB24的时候会有极其微小的误差,但这点误差根本就是肉眼不可见。
可是事实上,source_ffmpeg_to_rgb.png是正确的版本,对应着正确的色彩表现。
而libyuv_target.png是完全错误的版本,这更像是由NV21转化成RGB24的版本,而不是我代码写的libyuv::NV12ToRGB24();
然后我就特意将libyuv::NV12ToRGB24()切换到libyuv::NV21ToRGB24(),发现一切正常起来了,图像都对了。
我十分不理解这是为什么。
也就是说,我不知道libyuv在哪一步发生了把NV12当成了NV21来处理。
这个实验是我做的小项目发生的问题,并且从里面抽出来的最简化的版本。
代码如下:
void testNV12(){
// 定义一个2*2的NV12像素,Y:{0x11,0x11,0x11,0x11},U:{0XFF},V:{0X00}
uint8_t outputBuffer[] = {0x11,0x11,0x11,0x11,0xFF,0x00};
int _stride = 2;
int v_width = 2;
int v_height = 2;
// 输出源数据
printf("定义的源数据outputBuffer: ");
for(int i =0;i<sizeof(outputBuffer);i++)
{
printf("0x%x, ",outputBuffer[i]);
}
printf("\n");
FILE *fp;
size_t ___count = 0;
char nv12_save_path[] = "/sdcard/source.yuv"; // 源数据直接输出
fp = fopen(nv12_save_path, "wb");
___count = fwrite(outputBuffer, 1, 6, fp);
printf("成功写入%s:%zu字节\n", nv12_save_path, ___count);
fclose(fp);
//////// 处理NV12直接转换成RGB24
uint8_t *nv12_frame = outputBuffer; //获取数据的真正起点,虽然绝大部分情况下offset都是0
printf("outputBuffer: 0x%x, nv12_frame: 0x%x\n", (unsigned int) (uintptr_t) outputBuffer, (unsigned int) (uintptr_t) nv12_frame);
int src_stride_y = _stride; // 从v_info获取,假设没有行填充,Y步长就是宽度v_width
int src_stride_uv = src_stride_y; // 指定UV的步长,事实上和Y分量一致
int rgb_stride = v_width * 3; /// 自己新构建的图像,自己计算指定就行,视频源获取不到
printf("src_stride_y = %d, src_stride_uv = %d, rgb_stride = %d\n", src_stride_y, src_stride_uv, rgb_stride);
const uint8_t* src_y = nv12_frame;
const uint8_t* src_uv = src_y + src_stride_y * v_height; /////// 这一行十分重要,有个bug是如果采用不+1,就会导致输出的色彩是NV21转变成RGB24的色彩,而不是NV12转成RGB24的色彩
printf("src_y: 0x%x, src_uv: 0x%x, src_stride_y * v_height = %d\n",(unsigned int) (uintptr_t) src_y, (unsigned int) (uintptr_t) src_uv, src_stride_y * v_height);
uint8_t *rgb_frame = new uint8_t[v_width * v_height * 3]; // 存放转换后的RGB24数据
if (rgb_frame == NULL)
{
printf("申请rgb_frame内存失败!\n");
}
// 调用转化函数
int _res = libyuv::NV12ToRGB24(src_y, src_stride_y, src_uv, src_stride_uv, rgb_frame, rgb_stride, v_width, v_height);
if (_res != 0) {
printf("转换失败!\n");
printf("src_y = 0x%x, src_stride_y = %d\nsrc_uv = 0x%x, src_stride_uv = %d\nrgb_frame = 0x%x, argb_stride = %d\nv_width = %d, v_height = %d\n",
(unsigned int) (uintptr_t) src_y, src_stride_y,
(unsigned int) (uintptr_t) src_uv, src_stride_uv,
(unsigned int) (uintptr_t) rgb_frame, rgb_stride, v_width, v_height);
}
// 输出转换得到的RGB数据
printf("转换得到的RGB数据rgb_frame: ");
for(int i =0;i<v_width * v_height * 3;i++)
{
printf("0x%x, ",rgb_frame[i]);
}
printf("\n");
FILE *fp1;
size_t ____count = 0;
char nv12_save_path1[] = "/sdcard/libyuv_target.rgb"; //输出从libyuv转化得到的RGB24数据
fp1 = fopen(nv12_save_path1, "wb");
____count = fwrite(rgb_frame, 1, v_width * v_height * 3, fp1);
printf("成功写入%s:%zu字节\n", nv12_save_path1, ____count);
fclose(fp1);
nv12_frame = NULL;
}