Hi3531D调试手记(五):获取VI模块的YUV图像数据并保存为BMP

Ubuntu系统版本:Ubuntu 16.04.7 LTS

一、前言

  参考博客 海思多媒体(MPP)开发(8)——获取VI中的YUV数据 以及博客 yuv420 转换成 bmp 实现。实际上 SDK 包的 MPP 中也是提供了相关示例源码的,mpp/tools 下就是一些测试用的小程序,包括从各个模块获取 YUV 图像并存储为 YUV 文件的实现,进目录 make 一下就能得到可执行程序:
在这里插入图片描述

  • 该文件夹下的可执行文件不一定能无条件运行。博主曾将该目录下编译得到的 vi_dump 放到 Hi3531D 的板载 linux 上尝试运行,但是运行失败,原因是错误码 0xA010800E(视频输入缓存为空),而使用时确定是有视频流传输到板子里的。后来检查 vi_dump.c 中的源码发现里面并没有启动 VI 设备直接就在尝试从 VI 通道获取并存储图像帧:
    在这里插入图片描述

PS:这些工具程序应该是要配合 Linux 的程序前后台执行特性来使用的,在后台挂上测试程序,打通数据流转路径,然后在前台执行工具程序,抓取用于分析和调试的图象数据。

  • 实现本文目的的 MPP 核心 API 有四个(前提是 VI 设备已经配置好并且在正常工作),具体说明可以在 HiMPP 开发参考手册中查阅:
    在这里插入图片描述
  • 本文中使用 HI_MPI_VI_GetFrameDepth 接口获取到的图片数据格式为 YUV420SP NV21,也就是 U、V 交替存放,数据结构类似下图的 NV12 结构:
    在这里插入图片描述

PS:关于 YUV 格式的图像格式还是有点东西的,不清楚的话还是建议先移步博客 YUV图解 (YUV444, YUV422, YUV420, YV12, NV12, NV21) 了解相关信息。

  • 实际上咱一开始想做的是把 YUV 封装成 jpg 格式的图片,参考雷神的博客 最简单的基于FFMPEG的图像编码器(YUV编码为JPEG) 写出来却发现调用 avcodec_open2 函数打开编码器一直失败,无法打开。起初以为是 ffmpeg API 版本适配的问题调了大半个礼拜,最后发现是因为自己使用的图片分辨率太大(4k),把图片分辨率改成 1080p 就可以用了 Orz…
  • BMP 的封装相对来说会更简单一些,只需要将 YUV 转成 RGB ,添加一下文件头信息后再把所有像素点的像素值塞进文件里就行,当然代价就是单张图片占用的空间更大了,是 YUV 数据所需存储空间大小的两倍。BMP 文件结构的详细信息参考博客 BMP文件转YUV文件_C语言实现 的第三节和第四节;
  • 即便退而求其次用 BMP 格式封装图像数据也还是出现了问题。板载 linux 的可用内存太小,而 4k 图像占用空间太大,如果要一次性处理整张图片就会因为占用内存过大(需要同时准备至少两个图像的缓存区)而触发 linux 的自我保护机制,强行中断处理程序以保证维持系统运行所必须的空余内存大小…所以要使程序能在板载 linux 上正常运行就需要调整代码,通过循环读取处理来逐步实现整幅图像的格式转换与 BMP 封装。
    在这里插入图片描述

2021.6.9 补充说明:后续验证发现给板载 linux 分配的 os 内存太小了,只有 57M,当然不够内存跑完整的图像格式转换,实际上把内存扩大到 512M 应该就没什么大问题了:《海思中内存分配和优化》

二、流程概览

  关于启动 VI 设备的部分,博主通过配置让系统在上电以后自动开始显示图像,算是预置实现了,因此在拍照功能的代码中没有加入 VI 设备的配置。但为使处理流程看起来更加完整还是将其加入了流程图中。
在这里插入图片描述

三、实现代码

  这里公布的是从 VI 通道获取 YUV 数据以后的两步。获取 YUV 数据参考前面说的博客以及 MPP 自带的 tools 源码相当于直接白给,脑子都不用动的那种,因此没有在这一步的实现上浪费篇幅。
  自认为代码部分的注释已经非常到位了。VIDEO_FRAME_S 是 Hi_MPP 自带的结构体, pfd 是外部接口函数里创建并打开的 bmp 文件的句柄。经过调整,此份代码可以在 Hi3531D 的板载 linux 上顺利完成 4k 图像数据的格式转换与封装为 BMP(当然速度并不是很快)。

unsigned char header[54] = {
	// 位图文件头
	0x42, 0x4d,       	//WORD  bfType----------- [0,1]   			说明文件类型
	0, 0, 0, 0,      	//DWORD bfSize----------- [2,3,4,5]   		说明文件大小
	0, 0,            	//WORD  bfReserved1------ [6,7]   			保留,设置为 0
	0, 0,            	//WORD  bfReserved2------ [8,9]   			保留,设置为 0
	54, 0, 0, 0,      	//WORD  bfOffBits-------- [10,11,12,13]   	说明从位图文件头开始到实际的图像数据之间的字节偏移量

	// 位图信息头
	40, 0, 0, 0,      	//DWORD biSize----------- [14,15,16,17]		说明位图信息头需要的字节数
	0, 0, 0, 0,      	//LONG  biWidth---------- [18,19,20,21]		以像素为单位说明图像宽度
	0, 0, 0, 0,      	//LONG  biHeight--------- [22,23,24,25]    	以像素为单位说明图像高度
	1, 0,            	//WORD  biplanes--------- [26,27]   		说明位面数,必须为 1
	24, 0,            	//WORD  biCount---------- [28,29]   		说明位数/像素,1、2、4、8、24
	0, 0, 0, 0,      	//DWORD biCompression---- [30,31,32,33]   	说明图像是否是压缩及压缩类型 BI_RGB,BI_RLE8,BI_RLE4,BI_BITFIELDS
	0, 0, 0, 0,      	//DWORD biSizeImage------ [34,35,36,37]   	以字节为单位说明图像大小,必须是 4 的整数倍
	0, 0, 0, 0,      	//LONG  biXPelsPerMeter-- [38,39,40,41]   	目标设备的水平分辨率,像素/米
	0, 0, 0, 0,      	//LONG  biYPelsPerMeter-- [42,43,44,45]    	目标设备的垂直分辨率,像素/米
	0, 0, 0, 0,      	//DWORD biClrUsed-------- [46,47,48,49]    	说明图像实际用到的颜色数,如果为 0 则颜色数为 2 的 biBitCount 次方
	0, 0, 0, 0       	//DWORD biClrImportant--- [50,51,52,53]   	说明对图像显示有重要影响的颜色索引的数目,如果为 0 则表示都重要
};
float YUV2RGB_CONVERT_MATRIX[3][3] = { { 1, 0, 1.4022 }, { 1, -0.3456, -0.7145 }, { 1, 1.771, 0 } };
/* sp420 */
HI_S32 vi_dump_save_one_frame(VIDEO_FRAME_S* pVBuf, FILE* pfd)
{
    int i, j, k, l, x;
	int width = pVBuf->u32Width;
	int height = pVBuf->u32Height;
    long len = height * width;								// 一整幅幅图像的长度
	int two_rows_len = 2 * width;							// 两行图像的长度
	long int bytePerLine = width * 3;						// RGB24 一行的长度
    HI_U32 phy_addr, size;
    HI_U8* pUserPageAddr[3] = {NULL, NULL, NULL};			// 0:原图像,1:2行 RGB24图像缓存,2:2行 写入bmp文件数据缓存
    PIXEL_FORMAT_E  enPixelFormat = pVBuf->enPixelFormat;

	HI_U8* pVBufVirt_Y = NULL;								// YUV420sp Y 分量首地址
    HI_U8* pVBufVirt_C = NULL;								// YUV420sp VU 分量首地址
	HI_U8* pVBufVirt_R = NULL;								// RGB24 R 分量首地址
	HI_U8* pVBufVirt_G = NULL;								// RGB24 G 分量首地址
	HI_U8* pVBufVirt_B = NULL;								// RGB24 B 分量首地址
	HI_U8* data = NULL;

	int R[4], G[4], B[4];
    int Y[4], U, V;
    int y0_Idx, y1_Idx, uIdx, vIdx;

	// 图片格式检查
    if (width > MAX_FRM_WIDTH)
    {
        printf("Over max frame width: %d, can't support.\n", MAX_FRM_WIDTH);
        return HI_FAILURE;
    }
    if (PIXEL_FORMAT_YUV_SEMIPLANAR_420 == enPixelFormat)
        size = (pVBuf->u32Stride[0]) * height * 3 / 2;
    else if (PIXEL_FORMAT_YUV_SEMIPLANAR_422 == enPixelFormat)
        size = (pVBuf->u32Stride[0]) * height * 2;
    else if (PIXEL_FORMAT_YUV_400 == enPixelFormat)
        size = (pVBuf->u32Stride[0]) * height;
    else
    {
        printf("not support pixelformat: %d\n", enPixelFormat);
        return HI_FAILURE;
    }

	// 获取图像的物理地址,并将其映射为虚拟地址
    phy_addr = pVBuf->u32PhyAddr[0];
    pUserPageAddr[0] = (HI_U8*)HI_MPI_SYS_Mmap(phy_addr, size);
    if (NULL == pUserPageAddr[0])
		return HI_FAILURE;
    printf("stride: %d,%d\n", pVBuf->u32Stride[0], pVBuf->u32Stride[1]);

	// 申请图像处理缓存空间
	pUserPageAddr[1] = (HI_U8*)malloc(two_rows_len * 3);		// 每次循环只处理两行数据
	pUserPageAddr[2] = (HI_U8*)malloc(bytePerLine * 2);
	if ((NULL == pUserPageAddr[1]) || (NULL == pUserPageAddr[2]))
		goto END;

	// 获取图像中 Y 分量和 VU 分量的首地址
    pVBufVirt_Y = pUserPageAddr[0];
    pVBufVirt_C = pVBufVirt_Y + len;

	// 写 BMP 文件头
	long file_size = (long)(width) * (long)(height) * 3 + 54;
	header[2] = (unsigned char)(file_size & 0x000000ff);
	header[3] = (file_size >> 8) & 0x000000ff;
	header[4] = (file_size >> 16) & 0x000000ff;
	header[5] = (file_size >> 24) & 0x000000ff;

	header[18] = (width) & 0x000000ff;
	header[19] = (width >> 8) & 0x000000ff;
	header[20] = (width >> 16) & 0x000000ff;
	header[21] = (width >> 24) & 0x000000ff;

	header[22] = (height) & 0x000000ff;
	header[23] = (height >> 8) & 0x000000ff;
	header[24] = (height >> 16) & 0x000000ff;
	header[25] = (height >> 24) & 0x000000ff;
	fwrite(header, sizeof(unsigned char), 54, pfd);
	fflush(pfd);

	// 写 BMP 图片数据
	pVBufVirt_R = pUserPageAddr[1];
	pVBufVirt_G = pVBufVirt_R + two_rows_len;
	pVBufVirt_B = pVBufVirt_G + two_rows_len;
	data = pUserPageAddr[2];

	for (i = height - 2; i >= 0; i = i - 2)		// 为便于在循环中增加数据写入,从最后两行开始倒序转换
    {
		// 两行数据格式转换: NV21 -> RGB24
        for (j = 0; j <= width - 2; j = j + 2)
        {
            y0_Idx = i * width + j;
            y1_Idx = (i + 1) * width + j;

			// Y[0]、Y[1]、Y[2]、Y[3]分别代表 Y00、Y01、Y10、Y11
            Y[0] = pVBufVirt_Y[y0_Idx];
            Y[1] = pVBufVirt_Y[y0_Idx + 1];
            Y[2] = pVBufVirt_Y[y1_Idx];
            Y[3] = pVBufVirt_Y[y1_Idx + 1];

            vIdx = (i / 2) * width + j;
            uIdx = vIdx + 1;
			
			V = pVBufVirt_C[vIdx];
            U = pVBufVirt_C[uIdx];

            R[0] = Y[0] + (V - 128) * YUV2RGB_CONVERT_MATRIX[0][2];
            G[0] = Y[0] + (U - 128) * YUV2RGB_CONVERT_MATRIX[1][1] + (V - 128) * YUV2RGB_CONVERT_MATRIX[1][2];
            B[0] = Y[0] + (U - 128) * YUV2RGB_CONVERT_MATRIX[2][1];

            R[1] = Y[1] + (V - 128) * YUV2RGB_CONVERT_MATRIX[0][2];
            G[1] = Y[1] + (U - 128) * YUV2RGB_CONVERT_MATRIX[1][1] + (V - 128) * YUV2RGB_CONVERT_MATRIX[1][2];
            B[1] = Y[1] + (U - 128) * YUV2RGB_CONVERT_MATRIX[2][1];

            R[2] = Y[2] + (V - 128) * YUV2RGB_CONVERT_MATRIX[0][2];
            G[2] = Y[2] + (U - 128) * YUV2RGB_CONVERT_MATRIX[1][1] + (V - 128) * YUV2RGB_CONVERT_MATRIX[1][2];
            B[2] = Y[2] + (U - 128) * YUV2RGB_CONVERT_MATRIX[2][1];

            R[3] = Y[3] + (V - 128) * YUV2RGB_CONVERT_MATRIX[0][2];
            G[3] = Y[3] + (U - 128) * YUV2RGB_CONVERT_MATRIX[1][1] + (V - 128) * YUV2RGB_CONVERT_MATRIX[1][2];
            B[3] = Y[3] + (U - 128) * YUV2RGB_CONVERT_MATRIX[2][1];

            *(pVBufVirt_R + j) = R[0] < 0 ? 0 : (R[0] > 255 ? 255 : R[0]);
            *(pVBufVirt_G + j) = G[0] < 0 ? 0 : (G[0] > 255 ? 255 : G[0]);
            *(pVBufVirt_B + j) = B[0] < 0 ? 0 : (B[0] > 255 ? 255 : B[0]);

            *(pVBufVirt_R + j + 1) = R[1] < 0 ? 0 : (R[1] > 255 ? 255 : R[1]);
            *(pVBufVirt_G + j + 1) = G[1] < 0 ? 0 : (G[1] > 255 ? 255 : G[1]);
            *(pVBufVirt_B + j + 1) = B[1] < 0 ? 0 : (B[1] > 255 ? 255 : B[1]);

            *(pVBufVirt_R + width + j) = R[2] < 0 ? 0 : (R[2] > 255 ? 255 : R[2]);
            *(pVBufVirt_G + width + j) = G[2] < 0 ? 0 : (G[2] > 255 ? 255 : G[2]);
            *(pVBufVirt_B + width + j) = B[2] < 0 ? 0 : (B[2] > 255 ? 255 : B[2]);

            *(pVBufVirt_R + width + j + 1) = R[3] < 0 ? 0 : (R[3] > 255 ? 255 : R[3]);
            *(pVBufVirt_G + width + j + 1) = G[3] < 0 ? 0 : (G[3] > 255 ? 255 : G[3]);
            *(pVBufVirt_B + width + j + 1) = B[3] < 0 ? 0 : (B[3] > 255 ? 255 : B[3]);
        }

		// 两行数据写入 BMP 文件: 以 B、G、R 顺序存储,且 BMP 图像的高度与 RGB24 相反
		for(x = 0, k = 0, l = 0; x < width; x++)
		{
			// B
			data[k++] = pVBufVirt_B[width + x];
			data[bytePerLine + l++] = pVBufVirt_B[x];
			// G
			data[k++] = pVBufVirt_G[width + x];
			data[bytePerLine + l++] = pVBufVirt_G[x];
			// R
			data[k++] = pVBufVirt_R[width + x];
			data[bytePerLine + l++] = pVBufVirt_R[x];
		}
		fwrite(data, sizeof(unsigned char), two_rows_len * 3, pfd);
    }
	fflush(pfd);

	printf("done %d!\n", pVBuf->u32TimeRef);

END:
	if(pUserPageAddr[2] != NULL)
	{
		data = NULL;
		free(pUserPageAddr[2]);
		pUserPageAddr[2] = NULL;
	}
	if(pUserPageAddr[1] != NULL)
	{
		pVBufVirt_R = NULL;
		pVBufVirt_G = NULL;
		pVBufVirt_B = NULL;
		free(pUserPageAddr[1]);
		pUserPageAddr[1] = NULL;
	}
	// 解除映射
	pVBufVirt_Y = NULL;
	pVBufVirt_C = NULL;
    HI_MPI_SYS_Munmap(pUserPageAddr[0], size);
	pUserPageAddr[0] = NULL;

    return HI_SUCCESS;
}

PS:从整份代码中应该可以看出对指针的处理格外重视,因为咱在调试期间被指针引起的内存泄露问题(Segment fault 的原因之一)折腾了很久,都快出现 PTSD 了QAQ…

四、附录

  以备不时之需,这里附上直接将整张图进行数据格式转换的函数以及 Win10 上的完整测试代码。

4.1 YUV420p I420/YV12 转 RGB

  这里的是 I420 转 RGB 的代码,但是 I420 和 YV12 的区别只在于 U 分量和 V 分量的存放位置是相反的,因此如果需要 YV12 转 RGB,仅需将代码中 U 分量和 V 分量的首地址互换一下即可。

typedef unsigned char byte;
double YUV2RGB_CONVERT_MATRIX[3][3] = { { 1, 0, 1.4022 }, { 1, -0.3456, -0.7145 }, { 1, 1.771, 0 } };
void ConvertYUV2RGB(unsigned char *yuvFrame, unsigned char *rgbFrame, int width, int height)
{
	int uIndex = width * height;
	int vIndex = uIndex + ((width * height) >> 2);
	int gIndex = width * height;
	int bIndex = gIndex * 2;
	int temp, x, y;

	for (y = 0; y < height; y++)
	{
		for (x = 0; x < width; x++)
		{
			// R分量        
			temp = (int)(yuvFrame[y * width + x] + (yuvFrame[vIndex + (y / 2) * (width / 2) + x / 2] - 128) * YUV2RGB_CONVERT_MATRIX[0][2]);
			rgbFrame[y * width + x] = (byte)(temp < 0 ? 0 : (temp > 255 ? 255 : temp));
			// G分量       
			temp = (int)(yuvFrame[y * width + x] + (yuvFrame[uIndex + (y / 2) * (width / 2) + x / 2] - 128) * YUV2RGB_CONVERT_MATRIX[1][1] + (yuvFrame[vIndex + (y / 2) * (width / 2) + x / 2] - 128) * YUV2RGB_CONVERT_MATRIX[1][2]);
			rgbFrame[gIndex + y * width + x] = (byte)(temp < 0 ? 0 : (temp > 255 ? 255 : temp));
			// B分量             
			temp = (int)(yuvFrame[y * width + x] + (yuvFrame[uIndex + (y / 2) * (width / 2) + x / 2] - 128) * YUV2RGB_CONVERT_MATRIX[2][1]);
			rgbFrame[bIndex + y * width + x] = (byte)(temp < 0 ? 0 : (temp > 255 ? 255 : temp));
		}
	}
}

4.2 YUV420sp NV21/NV12 转 RGB

  这里的是 NV21 转 RGB 的代码,NV12 和 NV21 的区别同第一小节 I420 和 YV12 二者的区别,需要 NV12 转 RGB 只要将 U 分量和 V 分量的索引赋值互换即可。

double YUV2RGB_CONVERT_MATRIX[3][3] = { { 1, 0, 1.4022 }, { 1, -0.3456, -0.7145 }, { 1, 1.771, 0 } };
void ConvertYUV2RGB(unsigned char *yuvFrame, unsigned char *rgbFrame, int width, int height)
{
	const long len = height * width;
    
    // Y与UV数据地址
    unsigned char *yData = yuvFrame;
    unsigned char *vuData = yData + len;

	// 	R、G、B数据地址
    unsigned char *rData = rgbFrame;
    unsigned char *gData = rData + len;
    unsigned char *bData = gData + len;

    int R[4], G[4], B[4];
    int Y[4], U, V;
    int y0_Idx, y1_Idx, uIdx, vIdx;

    for (int i = 0; i < height; i = i + 2)
    {
        for (int j = 0; j < width; j = j + 2)
        {
            y0_Idx = i * width + j;
            y1_Idx = (i + 1) * width + j;

			// Y[0]、Y[1]、Y[2]、Y[3]分别代表 Y00、Y01、Y10、Y11
            Y[0] = yData[y0_Idx];
            Y[1] = yData[y0_Idx + 1];
            Y[2] = yData[y1_Idx];
            Y[3] = yData[y1_Idx + 1];

            vIdx = (i / 2) * width + j;
            uIdx = vIdx + 1;
			
			V = vuData[vIdx];
            U = vuData[uIdx];

            R[0] = Y[0] + (V - 128) * YUV2RGB_CONVERT_MATRIX[0][2];
            G[0] = Y[0] + (U - 128) * YUV2RGB_CONVERT_MATRIX[1][1] + (V - 128) * YUV2RGB_CONVERT_MATRIX[1][2];
            B[0] = Y[0] + (U - 128) * YUV2RGB_CONVERT_MATRIX[2][1];

            R[1] = Y[1] + (V - 128) * YUV2RGB_CONVERT_MATRIX[0][2];
            G[1] = Y[1] + (U - 128) * YUV2RGB_CONVERT_MATRIX[1][1] + (V - 128) * YUV2RGB_CONVERT_MATRIX[1][2];
            B[1] = Y[1] + (U - 128) * YUV2RGB_CONVERT_MATRIX[2][1];

            R[2] = Y[2] + (V - 128) * YUV2RGB_CONVERT_MATRIX[0][2];
            G[2] = Y[2] + (U - 128) * YUV2RGB_CONVERT_MATRIX[1][1] + (V - 128) * YUV2RGB_CONVERT_MATRIX[1][2];
            B[2] = Y[2] + (U - 128) * YUV2RGB_CONVERT_MATRIX[2][1];

            R[3] = Y[3] + (V - 128) * YUV2RGB_CONVERT_MATRIX[0][2];
            G[3] = Y[3] + (U - 128) * YUV2RGB_CONVERT_MATRIX[1][1] + (V - 128) * YUV2RGB_CONVERT_MATRIX[1][2];
            B[3] = Y[3] + (U - 128) * YUV2RGB_CONVERT_MATRIX[2][1];

            *(rData + y0_Idx) = R[0] < 0 ? 0 : (R[0] > 255 ? 255 : R[0]);
            *(gData + y0_Idx) = G[0] < 0 ? 0 : (G[0] > 255 ? 255 : G[0]);
            *(bData + y0_Idx) = B[0] < 0 ? 0 : (B[0] > 255 ? 255 : B[0]);

            *(rData + y0_Idx + 1) = R[1] < 0 ? 0 : (R[1] > 255 ? 255 : R[1]);
            *(gData + y0_Idx + 1) = G[1] < 0 ? 0 : (G[1] > 255 ? 255 : G[1]);
            *(bData + y0_Idx + 1) = B[1] < 0 ? 0 : (B[1] > 255 ? 255 : B[1]);

            *(rData + y1_Idx) = R[2] < 0 ? 0 : (R[2] > 255 ? 255 : R[2]);
            *(gData + y1_Idx) = G[2] < 0 ? 0 : (G[2] > 255 ? 255 : G[2]);
            *(bData + y1_Idx) = B[2] < 0 ? 0 : (B[2] > 255 ? 255 : B[2]);

            *(rData + y1_Idx + 1) = R[3] < 0 ? 0 : (R[3] > 255 ? 255 : R[3]);
            *(gData + y1_Idx + 1) = G[3] < 0 ? 0 : (G[3] > 255 ? 255 : G[3]);
            *(bData + y1_Idx + 1) = B[3] < 0 ? 0 : (B[3] > 255 ? 255 : B[3]);
        }
    }
}

4.3 PC端测试完整代码

  留一份 PC 端的终版作为备份。

#include <stdio.h>
#include <stdlib.h>

double YUV2RGB_CONVERT_MATRIX[3][3] = { { 1, 0, 1.4022 }, { 1, -0.3456, -0.7145 }, { 1, 1.771, 0 } };
int main()
{
	char InputFileName[] = "./test.yuv";
    char OutputFileName[] = "./test.bmp";

	int i, j, k, l, x;
    int width = 3840, height = 2160;							// 图像分辨率
	const long len = height * width;							// 一整幅幅图像的长度
	const int two_rows_len = 2 * width;							// 两行图像的长度
	long int bytePerLine = width * 3;							// RGB24 
	unsigned char *data;
	unsigned char *image;
	unsigned char *image_bmp;

	unsigned char *Yaddr;										// YUV420sp Y 分量首地址
	unsigned char *VUaddr;										// YUV420sp VU 分量首地址
	unsigned char *Raddr;										// RGB24 R 分量首地址
	unsigned char *Gaddr;										// RGB24 G 分量首地址
	unsigned char *Baddr;										// RGB24 B 分量首地址

	// 申请空间
	image = (unsigned char *)malloc(len * 3 / 2);
	image_bmp = (unsigned char *)malloc(two_rows_len * 3);		// 每次循环只处理两行数据
	data = (unsigned char*)malloc(bytePerLine * 2);
	if ((NULL == image) || (image_bmp == NULL) || (data == NULL))
	{
		printf("faied to malloc the image\n");
		return -1;
	}

	/******** 读取yuv 文件 ***********/
	FILE *fp_r;
	fp_r = fopen(InputFileName, "rb"); // 打开yuv 文件
	if (NULL == fp_r)
	{
		printf("failed to open the fp_r\n");
		return -1;
	}
	fread(image, sizeof(unsigned char), width * height * 3 / 2, fp_r);
	fclose(fp_r);

	Yaddr = image;
	VUaddr = image + len;

	/******** 写 bmp 图片 ***********/
	unsigned char header[54] = {
		// 位图文件头
		0x42, 0x4d,       	//WORD  bfType----------- [0,1]   			说明文件类型
		0, 0, 0, 0,      	//DWORD bfSize----------- [2,3,4,5]   		说明文件大小
		0, 0,            	//WORD  bfReserved1------ [6,7]   			保留,设置为 0
		0, 0,            	//WORD  bfReserved2------ [8,9]   			保留,设置为 0
		54, 0, 0, 0,      	//WORD  bfOffBits-------- [10,11,12,13]   	说明从位图文件头开始到实际的图像数据之间的字节偏移量

		// 位图信息头
		40, 0, 0, 0,      	//DWORD biSize----------- [14,15,16,17]		说明位图信息头需要的字节数
		0, 0, 0, 0,      	//LONG  biWidth---------- [18,19,20,21]		以像素为单位说明图像宽度
		0, 0, 0, 0,      	//LONG  biHeight--------- [22,23,24,25]    	以像素为单位说明图像高度
		1, 0,            	//WORD  biplanes--------- [26,27]   		说明位面数,必须为 1
		24, 0,            	//WORD  biCount---------- [28,29]   		说明位数/像素,1、2、4、8、24
		0, 0, 0, 0,      	//DWORD biCompression---- [30,31,32,33]   	说明图像是否是压缩及压缩类型 BI_RGB,BI_RLE8,BI_RLE4,BI_BITFIELDS
		0, 0, 0, 0,      	//DWORD biSizeImage------ [34,35,36,37]   	以字节为单位说明图像大小,必须是 4 的整数倍
		0, 0, 0, 0,      	//LONG  biXPelsPerMeter-- [38,39,40,41]   	目标设备的水平分辨率,像素/米
		0, 0, 0, 0,      	//LONG  biYPelsPerMeter-- [42,43,44,45]    	目标设备的垂直分辨率,像素/米
		0, 0, 0, 0,      	//DWORD biClrUsed-------- [46,47,48,49]    	说明图像实际用到的颜色数,如果为 0 则颜色数为 2 的 biBitCount 次方
		0, 0, 0, 0       	//DWORD biClrImportant--- [50,51,52,53]   	说明对图像显示有重要影响的颜色索引的数目,如果为 0 则表示都重要
	};
	long file_size = (long)width * (long)height * 3 + 54;
	header[2] = (unsigned char)(file_size & 0x000000ff);
	header[3] = (file_size >> 8) & 0x000000ff;
	header[4] = (file_size >> 16) & 0x000000ff;
	header[5] = (file_size >> 24) & 0x000000ff;

	header[18] = width & 0x000000ff;
	header[19] = (width >> 8) & 0x000000ff;
	header[20] = (width >> 16) & 0x000000ff;
	header[21] = (width >> 24) & 0x000000ff;

	header[22] = height & 0x000000ff;
	header[23] = (height >> 8) & 0x000000ff;
	header[24] = (height >> 16) & 0x000000ff;
	header[25] = (height >> 24) & 0x000000ff;

	FILE *fp_w;
	fp_w = fopen(OutputFileName, "wb");
	if (fp_w == NULL)
	{
		printf("failed to open the fp_w\n");
		return -1;
	}
	fwrite(header, sizeof(unsigned char), 54, fp_w);

	Raddr = image_bmp;
	Gaddr = Raddr + two_rows_len;
	Baddr = Gaddr + two_rows_len;

	int R[4], G[4], B[4];
    int Y[4], U, V;
    int y0_Idx, y1_Idx, uIdx, vIdx;

	for (i = height - 2; i >= 0; i = i - 2)							// 为便于在循环中增加数据写入,从最后两行开始倒序转换
    {
		// 两行数据格式转换: NV21 -> RGB24
        for (j = 0; j < width; j = j + 2)
        {
            y0_Idx = i * width + j;
            y1_Idx = (i + 1) * width + j;

			// Y[0]、Y[1]、Y[2]、Y[3]分别代表 Y00、Y01、Y10、Y11
            Y[0] = Yaddr[y0_Idx];
            Y[1] = Yaddr[y0_Idx + 1];
            Y[2] = Yaddr[y1_Idx];
            Y[3] = Yaddr[y1_Idx + 1];

            vIdx = (i / 2) * width + j;
            uIdx = vIdx + 1;
			
			V = VUaddr[vIdx];
            U = VUaddr[uIdx];

            R[0] = Y[0] + (V - 128) * YUV2RGB_CONVERT_MATRIX[0][2];
            G[0] = Y[0] + (U - 128) * YUV2RGB_CONVERT_MATRIX[1][1] + (V - 128) * YUV2RGB_CONVERT_MATRIX[1][2];
            B[0] = Y[0] + (U - 128) * YUV2RGB_CONVERT_MATRIX[2][1];

            R[1] = Y[1] + (V - 128) * YUV2RGB_CONVERT_MATRIX[0][2];
            G[1] = Y[1] + (U - 128) * YUV2RGB_CONVERT_MATRIX[1][1] + (V - 128) * YUV2RGB_CONVERT_MATRIX[1][2];
            B[1] = Y[1] + (U - 128) * YUV2RGB_CONVERT_MATRIX[2][1];

            R[2] = Y[2] + (V - 128) * YUV2RGB_CONVERT_MATRIX[0][2];
            G[2] = Y[2] + (U - 128) * YUV2RGB_CONVERT_MATRIX[1][1] + (V - 128) * YUV2RGB_CONVERT_MATRIX[1][2];
            B[2] = Y[2] + (U - 128) * YUV2RGB_CONVERT_MATRIX[2][1];

            R[3] = Y[3] + (V - 128) * YUV2RGB_CONVERT_MATRIX[0][2];
            G[3] = Y[3] + (U - 128) * YUV2RGB_CONVERT_MATRIX[1][1] + (V - 128) * YUV2RGB_CONVERT_MATRIX[1][2];
            B[3] = Y[3] + (U - 128) * YUV2RGB_CONVERT_MATRIX[2][1];

            *(Raddr + j) = R[0] < 0 ? 0 : (R[0] > 255 ? 255 : R[0]);
            *(Gaddr + j) = G[0] < 0 ? 0 : (G[0] > 255 ? 255 : G[0]);
            *(Baddr + j) = B[0] < 0 ? 0 : (B[0] > 255 ? 255 : B[0]);

            *(Raddr + j + 1) = R[1] < 0 ? 0 : (R[1] > 255 ? 255 : R[1]);
            *(Gaddr + j + 1) = G[1] < 0 ? 0 : (G[1] > 255 ? 255 : G[1]);
            *(Baddr + j + 1) = B[1] < 0 ? 0 : (B[1] > 255 ? 255 : B[1]);

            *(Raddr + width + j) = R[2] < 0 ? 0 : (R[2] > 255 ? 255 : R[2]);
            *(Gaddr + width + j) = G[2] < 0 ? 0 : (G[2] > 255 ? 255 : G[2]);
            *(Baddr + width + j) = B[2] < 0 ? 0 : (B[2] > 255 ? 255 : B[2]);

            *(Raddr + width + j + 1) = R[3] < 0 ? 0 : (R[3] > 255 ? 255 : R[3]);
            *(Gaddr + width + j + 1) = G[3] < 0 ? 0 : (G[3] > 255 ? 255 : G[3]);
            *(Baddr + width + j + 1) = B[3] < 0 ? 0 : (B[3] > 255 ? 255 : B[3]);
        }

		// 两行数据写入 BMP 文件: 以 B、G、R 顺序存储,且 BMP 图像的高度与 RGB24 相反
		for(x = 0, k = 0, l = 0; x < width; x++)
		{
			// B
			data[k++] = Baddr[width + x];
			data[bytePerLine + l++] = Baddr[x];
			// G
			data[k++] = Gaddr[width + x];
			data[bytePerLine + l++] = Gaddr[x];
			// R
			data[k++] = Raddr[width + x];
			data[bytePerLine + l++] = Raddr[x];
		}
		fwrite(data, sizeof(unsigned char), two_rows_len * 3, fp_w);
    }
	fclose(fp_w);

	free(data);
	free(image_bmp);
	free(image);
	
	printf("done.\n");
	return 0;
}
  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值