yuv转换之I420转NV12

yuv

含义

Y: 表示明亮度(Luminance或Luma)
U和V: 色度(Chrominance或者Chroma)

YUV420

yuv420是YUV像素格式的一种。

大小:
对于分辨率为 W×H 的图像:

  • Y 分量大小:W × H 字节(每个像素 1 字节)。

  • U 分量大小:(W/2) × (H/2) = W×H/4 字节。

  • V 分量大小:(W/2) × (H/2) = W×H/4 字节。

yuv420p => I420
yuv420 planar代表内存分布是有y u v三个平面,代码理解就是有yuv三个不同指针,分别记录存放着对应分量数据的地址。

存放为图片分量数据排布 在这里插入图片描述

YUV420SP =>nv12/nv21为 semi-planar 模式。代码理解是有两个不同指针,记录着两块地址,一款放y分量数据,一路放UV数据,UV交错放在一起。

存放为图片分量数据排布 在这里插入图片描述

问题

将内存中一帧数据(格式是yuv420p,有stride,w按512字节对齐),保存为yuv格式图片。

思路:获取y数据写入,然后获取u数据写入,然后获取v数据写入文件。
1.获取y数据,大小为width * height
2.获取u数据,大小为width/2 * height/2
3.获取v数据,大小为width/2 * height/2

疑问:这里是不是直接对应分量地址去取对于大小的数据即可?
答:并不是,有stride,这个是步长,意思是内存中有字节对齐,w按512字节对齐就是每一行的数据是512的倍数,不足的会自动填充数据的。

代码理解就是例如1920 * 1080的图像数据。y分量数据大小是1920 * 1080字节,1920就是它的一行,从y地址从一次取一行数据,然后取1080次。但是有步长,正常你第一个从ptr_y地址取第一行数据,第二次是从ptr_y+1920取数据是吧,但是有步长,这个就变为ptr_y+2048取数据。 uv分量同理。

2048就是1920向上512倍数最近的值。一般帧结构体中会给出计算好对于分量的stride。

示例代码如下:

void save_yuv420_frame(VIDEO_FRAME_INFO_S *stFrameInfom, const char* filename) {
    FILE* pfd = fopen(filename, "wb");
    if (!pfd) {
        perror("Failed to open file");
        return;
    }
    VIDEO_FRAME_S *pVBuf = &(stFrameInfom->stVFrame);
    U32 u32ChromaPicW = 0;
    U32 u32ChromaPicH = 0;
    U8 *pVirAddr_Y = (U8 *) pVBuf->u64VirAddr[0];
    U8 *pVirAddr_U = (U8 *) pVBuf->u64VirAddr[1];
    U8 *pVirAddr_V = (U8 *) pVBuf->u64VirAddr[2];
    u32ChromaPicW = pVBuf->u32Width/2;
    u32ChromaPicH = pVBuf->u32Height/2;
    
    fprintf(stderr, "saving......Y......\n");
    fflush(stderr);
    for (int i = 0; i < pVBuf->u32Height; i++) {
        fwrite(pVirAddr_Y, pVBuf->u32Width, 1, pfd);
        pVirAddr_Y += pVBuf->u32Stride[0];
    }
    fflush(pfd);

    fprintf(stderr, "saving......U......\n");
    fflush(stderr);
    for (int i = 0; i < u32ChromaPicH; i++) {
        fwrite(pVirAddr_U, u32ChromaPicW, 1, pfd);
        pVirAddr_U += pVBuf->u32Stride[1];
    }
    fflush(pfd);

    fprintf(stderr, "saving......V......\n");
    fflush(stderr);
    for (int i = 0; i < u32ChromaPicH; i++) {
        fwrite(pVirAddr_V, u32ChromaPicW, 1, pfd);
        pVirAddr_V += pVBuf->u32Stride[2];
    }
    fflush(pfd);
    fprintf(stderr, "save %dth yuv data done !\n", 10);
    fflush(stderr);
    fclose(pfd);
    return 0;   
}

将内存中一帧数据(格式是yuv420p,有stride,w按512字节对齐),保存为nv12的yuv格式图片
思路:
NV12 y分量数据和I420一样 ,uv数据量和I420也是一样,只是交错放在一起。
1.先存放y数据
2.交错存放UV数据

示例代码如下。

int save_nv12_frame(VIDEO_FRAME_INFO_S *stFrameInfom, const char* filename) {
    FILE* pfd = fopen(filename, "wb");
    if (!pfd) {
        perror("Failed to open file");
        return -1;
    }
    
    VIDEO_FRAME_S *pVBuf = &(stFrameInfom->stVFrame);
    U32 width = pVBuf->u32Width;
    U32 height = pVBuf->u32Height;
    U32 chromaWidth = width/2;
    U32 chromaHeight = height/2;
    
    U8 *pY = (AR_U8 *)pVBuf->u64VirAddr[0];
    U8 *pU = (AR_U8 *)pVBuf->u64VirAddr[1];
    U8 *pV = (AR_U8 *)pVBuf->u64VirAddr[2];
    
    U32 yStride = pVBuf->u32Stride[0];
    U32 uStride = pVBuf->u32Stride[1];
    U32 vStride = pVBuf->u32Stride[2];
    
    // 写入Y平面数据
    fprintf(stderr, "Writing Y plane...\n");
    for (int i = 0; i < height; i++) {
        fwrite(pY, width, 1, pfd);
        pY += yStride;
    }
    
    // 分配临时缓冲区存储UV交错数据
    U32 uvSize = chromaWidth * chromaHeight * 2;
    U8 *uvBuffer = (U8 *)malloc(uvSize);
    if (!uvBuffer) {
        fprintf(stderr, "Memory allocation failed\n");
        fclose(pfd);
        return -1;
    }
    
    // 生成NV12格式的UV数据 (U和V交替存储)
    fprintf(stderr, "Generating NV12 UV data...\n");
    U8 *uvPtr = uvBuffer;
    for (int i = 0; i < chromaHeight; i++) {
        for (int j = 0; j < chromaWidth; j++) {
            *uvPtr++ = *pU++;  // U分量
            *uvPtr++ = *pV++;  // V分量
        }
        pU += uStride - chromaWidth;
        pV += vStride - chromaWidth;
    }
    
    // 写入UV数据
    fprintf(stderr, "Writing UV data...\n");
    fwrite(uvBuffer, uvSize, 1, pfd);
    
    // 释放资源
    free(uvBuffer);
    fclose(pfd);
    
    fprintf(stderr, "NV12 frame saved successfully\n");
    return 0;
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值