Snapdragon 820上 V4L2_PIX_FMT_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个宏块的连续顺序是:

第一组第一列第一组第二列第二组第一列第二组第一列
MB1MB8MB11MB14
MB7MB2MB13MB12
MB4MB5MB10MB15
MB6MB3MB16MB9

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

偶数组第一列偶数组第二列
MB1MB8
MB7MB2
MB4MB5
MB6MB3
奇数组第一列奇数组第二列
MB3MB6
MB5MB4
MB2MB7
MB8MB1

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

宏块内部的内存布局

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

第一列第二列第三列第四列第五列第六列第七列第八列
SB1SB2SB3SB4SB5SB6SB7SB8
SB9SB10SB11SB12SB13SB14SB15SB16

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

第一列第二列第三列第四列
PX1PX2PX3PX4
PX5PX6PX7PX8
PX9PX10PX11PX12
PX13PX14PX15PX15

实现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
  • 1
    点赞
  • 10
    收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论 2

打赏作者

isoal

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值