NV12_UBWC, 一步一步接近真相

缘起

因为工作需要,加上自己的意愿, 从干了多年的电机控制和光伏逆变应用领域回到了自己的老本行——视频应用领域。ToF是个热门的新技术,产品线说需要的本地支持人手已满,唯缺Android驱动程序的技术支持。和当年转去做电机控制一样,知难而进,我没有退路。
操作系统基础还在,Linux编程已经是10多年前的事了。匆匆做了些准备之后,向老板提出在Camera驱动的HAL层加上水印,以向产品线证明我有Android技术支持的最基本能力。

启动

安装Ubuntu 18.04 LTS,搭建Android开发环境,借Intrinsyc Open-Q 820开发平台,下载高通源代码,编译——Jesus, 操作系统太新。只好卸载。安装Ubuntu 16.04 LTS却始终无法自选分区,无数次失败之后只好按默认分区来安装。重新搭建高通编译环境。到第一次成功编译出image的时候,已经一个星期过去了。

隐身

Camera是Sony IMX214,Intrinsyc提供的是Android6.0的支持。grep不到IMX214的任何源码,只发现patch里有DTB的补丁和很多 .so。makefile里搜到了一些使用prebuilt libmm*IMX214**.so的命令。从源代码里看不到V4L2怎样和硬件打交道的。开始的时候居然连IMX214的datasheet也搜不到,不得已充了CSDN的会员,下载了一份。

NV12

没有文档,没有和硬件打交道的源代码,只好从V4L2和HAL里去找视频格式的信息。
#define V4L2_DEFAULT_OUTPUT_COLOR_FMT V4L2_PIX_FMT_NV12_UBWC
default的格式是NV12,我的第一感觉是在HAL里加上一块亮度信号就可以搞定。于是找到了dataProcRoutine,在里面加了一个白色方块。纳尼?出来的是散乱分布的线条!修改几次无效后只好求助网络。

NV12_UBWC并非单纯的NV12

感谢smileorcryps,让我在这泥潭里滚了几圈,总算也前进了一步。至少可以肯定,有人曾经成功过。
Android视频添加时间水印
无数次尝试,水印依然乱序。我怀疑过:

  1. 添加水印的程序位置不对,添加完之后被新数据覆盖了。
  2. UV数据没修改。
  3. 手机缺省旋转了90度,但存储空间扫描方向是原方向。
  4. 执行memset,memcpy时间太长,导致buffer被覆盖。
  5. 偏移位置算错了,width弄错了。
    还真弄错了,博文里的offset是0,而源代码里dumpfile时有这样一句:
    index += (uint32_t)offset.mp[i].meta_len;
    以为就要找到真像了。可欣喜之后是失望。显示位置是变了,而显示内容还是一如既往地what a mess。改width为stride,height为scanline都是徒劳。一开始也怀疑过UBWC是和NV12是不同的格式,不过看到源码里有很多switch case语句里NV12和NV12_UBWC都共享分支,也无法肯定。而上网也搜不到关于UBWC的规范。在所有尝试都失败之后,终于肯定就是UBWC的格式不同。

真相浮出

百度很难有有用的新技术信息。谷歌有墙。辗转问了一个高通的视觉工程师——UBWC,是高通的技术吗?没听说过。
我一直是一个很笨的工程师,解决问题的办法就是不断地试错。这也是我为什么敬畏强电的原因——生命不能试错。感谢上帝,现在我可以随便试,不用再担心电阻击穿、电容冒烟、功率器件爆炸、电脑主板烧坏、硬盘不可逆地损毁,或是逆变器干扰了整个小区的供电系统。
感谢自己在源码里看到了YUV420PackedSemiPlanar32M,感谢文长给我的提示。NV12_UBWC一定是按宏块顺序排列的。我猜想,通用带宽压缩,很可能是把不同的宏块发送到不同的CPU内核或者GPU的MAC。按宏块排序,可以不用像线性排列那样把几行数据同时发往不同的运算单元,或者用很多二维DMA。
于是我尝试显示一个16X16的白色亮度宏块,很好,显示出来是一个8*32的白色长方形。说明至少在我的平台上,一个宏块的大小是8X32。第二个宏块并没有像文长的博文里那样出现在第一个的左边,也就是说它不是按Z和反Z顺序扫描的。经过多次实验,发现每16个宏块有一个完整的排列顺序,之后16个开始重复前16个的顺序。可以叫它们为一个slice吧。每8个宏块又分为1组,4X2的大小。16个宏块的连续顺序是:

第一组第一列第一组第二列第二组第一列第二组第一列
181114
721312
451015
63169

就这16个宏块而言,如果你需要在左上角显示你的目标宏块,那么在内存中它应该排在最开头,如果你要显示目标宏块在第一行第二列的位置,在存储空间中,它应该存放在(8-1)X8X32字节处。对于16个宏块中的任意一个,我们先找到它对应的order,把它存放在内存的第(order - 1)X8X32字节处。

更多细节

当我发现这一规律,便兴奋地告诉妻子:我非常接近真相了!到临睡前,又告诉她:真相好像又离我远去了。虽然显示的区域不再混乱,但是在那个方框内部,依然处于混沌状态。那一晚不停地梦到各种可能,还梦见踩到屎粑粑,很臭很臭。第二天下班时间去买了彩票。白天都在不停地试验输出各种尺寸的黑白相间的方块。其实一早就想到了宏块内部也不是线性排列的,首先就尝试了按4X4大小的子块顺序排列,可是显示在屏幕上的方块并不是我所期待。晚上买了彩票之后,继续观察屏幕上显示的方块,居然在满屏的错误方块中,找到了一块预期的方块。
原来一直4x4大小的字块是正确的,只是因为某种原因,一直没有在屏幕上反应出来。再仔细观察,发现它们在黑白交替的背景前显示得很正常,但是在彩色背景上就模糊了。这次真有理由怀疑是UV数据的原因了。能发现那个显示正常的点,我真是走了狗屎运了。

临时结论

在一个宏块内部,包含有16个4X4的子块,一个子块在存储空间上是连续的,各个子块之间按从左到右,从上到下的顺序排列。下面是我的代码片段。以我的脑子,只能用七层for循环来完成了。

    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

未完,但不继续

显示出的logo
最终显示了一个公司的logo,背景是ubuntu的terminal窗口,我的大救星。
还有很多问题没有处理:UV数据的排列,不同分辨率下的状态,任意offset的支持。但是不能再深究了,必须move on,去学习和解决更多Android驱动的问题。

  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值