m0_53975054 2024-09-29 21:20 采纳率: 0%
浏览 1
已结题

在使用libyuv库时的一个NV12转RGB24的问题

在使用libyuv库时候出现的一个有关NV12转RGB24的问题,我不知道是什么原因,求解答。

先放流程图,方便理解问题,代码我放到最后面了:

img

首先我定义了一个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;
}
  • 写回答

14条回答 默认 最新

  • 阿里嘎多学长 2024-09-29 21:20
    关注

    AIGC生成:


    NV12转RGB24问题解决方案

    根据你提供的流程图和代码,问题可能出在NV12图像的解码和RGB24图像的编码过程中。Libyuv库提供了多种格式之间的转换函数,我们可以使用I420ToARGB函数将NV12图像转换为ARGB图像,然后使用ARGBToRGB24函数将ARGB图像转换为RGB24图像。

    代码示例:

    #include <libyuv.h>
    
    // ...
    
    // NV12图像
    uint8_t nv12_data[2 * 2 * 3] = {
        0, 1, 2, 3, 4, 5, 6, 7
    };
    
    // ...
    
    // 将NV12图像转换为ARGB图像
    uint8_t argb_data[2 * 2 * 4];
    I420ToARGB(nv12_data, 2, 2, argb_data);
    
    // 将ARGB图像转换为RGB24图像
    uint8_t rgb24_data[2 * 2 * 3];
    ARGBToRGB24(argb_data, 2, 2, rgb24_data);
    
    // ...
    

    在上面的代码中,我们首先将NV12图像转换为ARGB图像,然后将ARGB图像转换为RGB24图像。这样可以解决NV12转RGB24的问题。


    评论

报告相同问题?

问题事件

  • 已结题 (查看结题原因) 9月29日
  • 创建了问题 9月29日

悬赏问题

  • ¥15 UE5.1局部变量对蓝图不可见
  • ¥15 一共有五道问题关于整数幂的运算还有房间号码 还有网络密码的解答?(语言-python)
  • ¥20 sentry如何捕获上传Android ndk 崩溃
  • ¥15 在做logistic回归模型限制性立方条图时候,不能出完整图的困难
  • ¥15 G0系列单片机HAL库中景园gc9307液晶驱动芯片无法使用硬件SPI+DMA驱动,如何解决?
  • ¥15 nasm x86 变量归零
  • ¥65 Tree 树形控件实现单选功能,可以使用element也可以手写一个,实现全选为全选状态
  • ¥60 寻抓云闪付tn组成网页付款链接
  • ¥16 寻字节跳动内部人员帮推简历
  • ¥20 如何通过sentry收集上传Android ndk的崩溃?