Snapdragon 820上 V4L2_PIX_FMT_NV12_UBWC格式的像素存储顺序

文章目录

  • 前提和限制
  • 对参考博文的一些补充
  • NV12_UBWC的宏块排列
  • 宏块内部的内存布局
  • 实现HAL层加NV12_UBWC格式水印的代码片段

前提和限制

  1. 本文的排列顺序是经过本人上百次的试验得出的试验性结论,并非来自官方文档。
  2. 所有的试验都基于Intrinsyc Open-Q 820开发平台,没有在其它任何平台上验证过。
  3. 所有的试验都基于Android 6.0,没有在其它任何版本上验证过。
  4. 所有的试验代码都在HAL层实现,不涉及内核和应用层。
  5. 所有的试验都在PREVIEW里实现。
  6. 所用摄像头为SONY IMX214。从数据手册看它输出的是RAW格式。
  7. 本人是Android驱动和高通芯片的初学者,受制于知识体系,文中难免有错。
  8. 本文不介绍YUV420的一般存储格式,它们在网上很容易搜到。
    基于以上前提和限制,请谨慎引用本文的结论。欢迎补充、批评、指正。

对参考博文的一些补充

我的初衷是在骁龙820的CAMERA驱动HAL层加上水印,以验证自己学习Android驱动开发的学习成果。在开始试验的初期,参考了Android视频添加时间水印一文。非常感谢作者给了我最初的方向。因为Android版本和芯片版本的原因,文中有一些地方需要补充和修订:

  1. mWatermarkBuf 在我的版本中没有用到,所以对它的new和delete也是不需要的。
  2. mParameters.getVideoSize()函数在我的版本里没有定义。虽然在试验过程中我添加了此成员函数,但是最终发现它也是不需要的。
  3. 因为1和2,WatermarkProcess_Video函数也是不需要的。
  4. 重新定义了bool WatermarkProcess(mm_camera_buf_def_t *pFrame,QCameraStream * pStream,unsigned char *pOutBuf)函数接口,改成了:
    bool WatermarkProcess(mm_camera_buf_def_t *pFrame,QCameraStream * pStream);
  5. 在我的版本里,YUV数据并非放在frame->buffer[0]最开头,而是先放metadata,再放YUV数据。

NV12_UBWC的宏块排列

和一般的YUV420_NV12格式不同,NV12_UBWC的数据并非是从左到右,从上到下线性排列的。经过反复试验,终于发现它的排列规律。
NV12_UBWC是按宏块顺序排列的,以方便多核、多视频流的处理。不同于H.264的16X16像素的宏块尺寸,NV12_UBWC的宏块尺寸是8X32像素的。一个宏块有8行32列。每16个宏块有一个完整的排列顺序,之后16个开始重复前16个的顺序。可以把这16个宏块成为一个Slice。一个Slice里的宏块可以按8个分为1组,第二组的顺序和第一组完全相反。每一组的尺寸是4X2像素。16个宏块的连续顺序是:

第一组第一列 第一组第二列 第二组第一列 第二组第一列
MB1 MB8 MB11 MB14
MB7 MB2 MB13 MB12
MB4 MB5 MB10 MB15
MB6 MB3 MB16 MB9

如果单独看每一个4X2大小的组,它的排列顺序是这样的:

偶数组第一列 偶数组第二列
MB1 MB8
MB7 MB2
MB4 MB5
MB6 MB3
奇数组第一列 奇数组第二列
MB3 MB6
MB5 MB4
MB2 MB7
MB8 MB1

以上两个表格的奇偶遵循C,C++的一般计数规则,从0开始计数。所以偶数组比奇数组先出现。
对于我所试验的CAMERA PREVIEW场景,其分辨率是640X480,所以整个屏幕包含有15X10个(32X64像素的)Slice,它们是按从上到下,从左到右的顺序在内存中排列的

宏块内部的内存布局

在一个8X32像素的宏块内部,按照4X4像素的尺寸组成2X8=16个子块。宏块内子块与子块之间从左到右,从上到下排列:

第一列 第二列 第三列 第四列 第五列 第六列 第七列 第八列
SB1 SB2 SB3 SB4 SB5 SB6 SB7 SB8
SB9 SB10 SB11 SB12 SB13 SB14 SB15 SB16

每一个子块内部的像素,也是从左到右,从上到下的顺序排列

第一列 第二列 第三列 第四列
PX1 PX2 PX3 PX4
PX5 PX6 PX7 PX8
PX9 PX10 PX11 PX12
PX13 PX14 PX15 PX15

实现HAL层加NV12_UBWC格式水印的代码片段

    cam_frame_len_offset_t offset;
    memset(&offset, 0, sizeof(cam_frame_len_offset_t)); 
    cam_dimension_t dim; 
    memset(&dim, 0, sizeof(dim));
    pStream->getFrameDimension(dim); 
    pStream->getFrameOffset(offset);
    int width = dim.width; //offset.mp[0].scanline;  //stride;    //dim.width;   
    int height = dim.height; //offset.mp[0].stride;   //scanline;   //dim.height;   
    unsigned char *_ptr; 
    _ptr = (unsigned char*)pFrame->buffer;      
    cam_format_t fmt;
    mParameters.getStreamFormat(CAM_STREAM_TYPE_PREVIEW,fmt);
	QCameraMemory *previewMemObj = (QCameraMemory *)pFrame->mem_info;
	camera_memory_t *preview_mem = NULL;
	if (previewMemObj != NULL) {
        preview_mem = previewMemObj->getMemory(pFrame->buf_idx, false);
    }
    
	LOGI("getStreamFormat = %d, ptr = %u, mem = %u", fmt, _ptr, preview_mem);
	LOGI("Offset.meta_len %d, offset %d, buffer %u",offset.mp[0].meta_len,offset.mp[0].offset, _ptr);
	LOGI("len %u, Offset.stride %d, width %d, height %u",offset.mp[0].len, offset.mp[0].stride,width, height);
	LOGI("meta_stride %d, meta_scanline %d %u",offset.mp[0].meta_stride,offset.mp[0].meta_scanline);
	LOGI("frame.len %d, buf_type %d",pFrame->frame_len, pFrame->buf_type);
	int imageWidth = logo_width;
	int imageHeight = logo_height;
	uint8_t nEvenOrder[4][2] = {1, 8, 7, 2, 4, 5, 6, 3};
	uint8_t nOddOrder[4][2] = {3, 6, 5, 4, 2, 7, 8, 1};
	uint8_t nBlockWidth = 64;
	uint8_t nBlockHeight = 32;
	uint8_t nSubBlockWidth =  32;
	uint8_t nSubBlockHeight = 8;
	uint8_t nImgXBlocks = imageWidth/nBlockWidth;
	uint8_t nImgYBlocks = imageHeight/nBlockHeight;
	uint8_t nImgXRest = imageWidth%nSubBlockWidth;
	uint8_t nImgYRest = imageHeight%nSubBlockHeight;
	uint8_t nInnerBlockWidth =4;
	uint8_t nInnerBlockHeight = 4;
	uint8_t nInnerBlockSize = nInnerBlockWidth * nInnerBlockHeight;
	uint8_t nInnerBlockXNum = 8;
	uint8_t nInnerBlockYNum = 2;
	//My logo size is 236 * 67, so I don't want to calculate imageWidth%nBlockWidth and height rest
	
	_ptr += offset.mp[0].offset + offset.mp[0].meta_len;
	uint8_t aSrcImage[sizeof(gImage_logo)];
	uint32_t nDstIndex;
	uint32_t nSrcIndex;
	uint32_t order;
	uint32_t nBlockStride = nBlockHeight * width; // for preview, width is 640, or 10 times of block width.
	memcpy(aSrcImage, gImage_logo, sizeof(gImage_logo));
	for(int i = 0; i < imageWidth * imageHeight; i++)
		if(aSrcImage[ i ] < 0x80)
			aSrcImage[ i ] = 0;
		else
			aSrcImage[ i ] = 0xFF;
#if 1	
	for(int i = 0; i < nImgYBlocks; i++)
	{
		for(int j = 0; j < nImgXBlocks; j++)
		{
			for(int k = 0; k < 4; k++) //Y count of subblock in a macro block
				for(int m = 0; m < 2; m++) //X count of subblock in a macro block
				{
					if(j%2)
						order = j * 8 + nOddOrder[k][m] - 1;
					else
						order = j * 8 + nEvenOrder[k][m] - 1;
					nDstIndex = i * nBlockStride + order * nSubBlockWidth * nSubBlockHeight;
					nSrcIndex = i * nBlockHeight * imageWidth + k * nSubBlockHeight * imageWidth + j * nBlockWidth + m * nSubBlockWidth;
					//for(int n = 0; n < nSubBlockHeight; n++)
					for(int n = 0; n < nInnerBlockYNum; n++)
					{
						nSrcIndex += n * nInnerBlockHeight * imageWidth;
						for( int p = 0; p < nInnerBlockXNum; p++) //inner block
						{
							//memcpy(_ptr + nDstIndex,  &gImage_logo[nSrcIndex], nSubBlockWidth);
							//nDstIndex += nSubBlockWidth;
							//nSrcIndex += imageWidth;
							uint32_t nSrcOffset = nSrcIndex + p * nInnerBlockWidth;
							for( int q = 0; q < nInnerBlockHeight; q++)
							{
								memcpy(_ptr + nDstIndex,  &aSrcImage[nSrcOffset], nInnerBlockWidth);
								nDstIndex += nInnerBlockWidth;
								nSrcOffset += imageWidth;
							}
						}
					}
				}
		}
	}
	
#else

你可能感兴趣的:(Snapdragon 820上 V4L2_PIX_FMT_NV12_UBWC格式的像素存储顺序)