一、海思平台OSD理论学习

本部分内容的学习重点参考《HiMPP IPC V2.0 媒体处理软件开发参考.pdf》

链接:https://pan.baidu.com/s/1R5KwdQxVPTXCNJUMCQCFJA 
提取码:onoh 
--来自百度网盘超级会员V5的分享

1、OSD概述

  OSD是on-screen display的简称,指屏幕菜单式调节方式。它通过显示在屏幕上的功能菜单达到调整各项参数的目的,不但调整方便,而且调整的内容也比以上的两种方式多,增加了失真、会聚、色温、消磁等高级调整内容。像以前显示器出现的网纹干扰、屏幕视窗不正、磁化等需要送维修厂商维修的故障,举手之间便可解决。

  另外在OSD选项里还可以调整显示的位置、无动作关闭显示的时间。OSD核心是利用字符发生芯片在显示器的屏幕上显示需要的字符。

  技术方式是:与图像实时同步附加或改变图像中某些像素的颜色,使之组合成人类可以在图像中辨识的数据。以固定或不固定的方式,改变某个特定的OSD控制暂存器,即可达到动态的效果。如:在荧幕上产生由左向右移动的OSD字形,只要将控制左右位置的OSD控制暂存器依序填入由小变大或由大变小的数值,OSD输出字形自然随更改的数值而做左右移动。

2、海思OSD的4种类型

在这里插入图片描述

− Overlay:视频叠加区域,其中区域支持位图的加载、背景色更新等功能。

− OverlayEx:扩展视频叠加区域,功能与 Overlay 类似,支持位图加载、背景色
更新等。

− Cover:视频遮挡区域,其中区域支持纯色块遮挡。

− CoverEx:扩展视频遮挡区域,功能与 Cover 类似,支持纯色块遮挡。

− OverlayEx/ CoverEx,分别相对于 Overlay/Cover,功能上类似,但是会引入额
外的系统带宽, OverlayEx/ CoverEx 由 VGS 叠加到图像上 , OverlayEx/
CoverEx 区域越大,占用 VGS 的性能就越大,当 VGS 性能不足时,会导致帧
率降低。建议只有当 Overlay/Cover 不支持,或者数量无法满足需求时,再使
用。

VGS是视频图形子系统,全称为Video Graphics Sub-System。支持对一幅输入图像进行处理,
如进行缩放、格式转换、解压等处理。

  正常开发过程中,我们是不需要管vgs这个模块的,但是当我们在性能不是很充裕的时候,
为了节约芯片性能,我们需要用vgs来替代vpss的功能。

  具体的做法是将vpss组的属性能设置成flase的都设置成false,那么在vpss性能不够时
就会自动用VGA来替代VPSS

3、4种OSD类型各自支持的模块和功能

在这里插入图片描述
在这里插入图片描述
Argb:透明度、红、绿、蓝
alpha:透明度

4、海思OSD的几个重要概念

(1)区域层次
  区域层次表示区域的叠加级别,层次值越大,表示区域的显示级别越高。当发生重叠时,层次值大的将会覆盖层次值小的。如果是同一级别,则根据区域叠加的,先后顺序,后叠加的将显示在上层。将多个区域叠加到 VPSS 的同一 GROUP时,要求每个区域的层次值不能与叠加到该 GROUP 的其他区域层次值相同,否则区域层次值相同的多个区域中,只能将最后一个叠加的区域叠加到 VPSS 的GROUP 上。将多个层次值相同的区域叠加到同一个 VENC 的通道上时,没有此限制,都能叠加成功。
在这里插入图片描述
(2)位图填充(针对 Overlay 和 OverlayEx 有效)
  位图填充是指将位图的内存值填充到区域内存空间中,位图将会从区域的左上角开始填充

  当位图小于区域时,只能填充一部分内存,剩余部分保持原有值;位
图大小等于区域时,将刚好全部填充;当位图大于区域时,位图只能将自身和区
域一样大小的内存信息填充到区域中。

  位图填充支持两种实现方式:其一、用户通过HI_MPI_RGN_SetBitMap 接口将位图数据拷贝至内部显示画布;其二、用户通过 HI_MPI_RGN_GetCanvasInfo 获取内部备份显示画布的地址,直接对该地址数据进行更新,然后调用HI_MPI_RGN_UpdateCanvas 接口将备份显示画布更新为待显示画布,达到实现更新位图数据的目的。

(3)区域公共属性
  用户创建一个区域时,需要设置该属性信息,它包含公共的资源信息。例如,Overlay 包含像素格式,大小和背景色。

(4)通道显示属性
  通道显示属性表明区域在某通道的显示特征。例如, Overlay 的通道显示属性包含显示位置,层次,前景 Alpha,背景 Alpha,还有编码用到的 QP 信息。当通道显示属性中的区域是否显示(bShow)为 TRUE 时,表示显示在该通道中;反之,表示在该通道中存在,但处于隐藏状态。

(5)区域反色
  当区域叠加到视频上显示时,如果视频背景与叠加区域的亮度色度相近,往往会导致背景与区域很难进行区分。区域反色功能即针对这种场景,自适应背景的变化,对区域的亮度色度进行调整,实现区域清晰可见

  区域反色功能支持实现方式如下:通过 VPSS 提供的区域亮度和统计功能。用户可实时获取视频序列中每个待叠加区域背景的亮度统计,然后利用 TDE 的 ROP功能,对区域进行手动的反色处理,最后通过 VPSS 将该反色后的区域叠加到视频上。

(6)区域QP保护
  当区域叠加到视频上进行压缩编码时,为了保证叠加区域的清晰度不因为数据压缩而变模糊,可以单独设定叠加区域部分的压缩特性,即设定 QP 保护功能参数。 QP 保护功能是 Overlay 特有的功能,且仅针对 H.264/H.265 类型编码通道有效,对其它类型无效。

5、海思平台OSD使用方法

(1)用户填充区域属性并创建区域

(2)将该区域指定到具体通道中(如 VENC)

  以上步骤完成区域的创建和使用。用户还可以通过以下操作来控制区域属性以及在某通道的通道显示属性.

(3)通过 HI_MPI_RGN_GetAttr、 HI_MPI_RGN_SetAttr 获取和设置区域属性

(4)通过 HI_MPI_RGN_SetBitMap(仅针对 Overlay)设置区域的位图信息

(5)通过 HI_MPI_RGN_GetDisplayAttr 和 HI_MPI_RGN_SetDisplayAttr 获取和设置区域在某通道(如 VENC 通道)的通道显示属性。

(6)最后用户可以将该区域从通道中撤出(非必须操作),再销毁区域

6、海思平台OSD的API和关键数据结构

区域管理模块主要提供区域资源的控制管理功能,包括区域的创建、销毁,获取与设
置区域属性,获取与设置区域的通道显示属性等。
该功能模块提供以下 MPI:
 HI_MPI_RGN_Create:创建区域。
 HI_MPI_RGN_Destroy:销毁区域。
 HI_MPI_RGN_GetAttr:获取区域属性
 HI_MPI_RGN_SetAttr:设置区域属性。
 HI_MPI_RGN_SetBitMap:设置区域位图。
 HI_MPI_RGN_SetAttachField:设置区域要叠加到的帧/场标志。
 HI_MPI_RGN_GetAttachField:获取区域要叠加到的帧/场标志。
 HI_MPI_RGN_AttachToChn:将区域叠加到通道上。
 HI_MPI_RGN_DetachFromChn:将区域从通道中撤出。
 HI_MPI_RGN_SetDisplayAttr:设置区域的通道显示属性。
 HI_MPI_RGN_GetDisplayAttr:获取区域的通道显示属性。
 HI_MPI_RGN_GetCanvasInfo:获取区域画布信息。
 HI_MPI_RGN_UpdateCanvas:更新区域画布信息。

以上API具体参数及使用方法和一些关键数据结构参考:《HiMPP IPC V2.0 媒体处理软件开发参考.pdf》

在这里插入图片描述
在这里插入图片描述

二、OSD实验演示和代码框架分析

1、OSD实验演示

在这里插入图片描述
在画面中添加了四个水印。

2、OSD代码框架分析

使用SourceInsight软件创建工程,分析源码。源码链接如下:

链接:https://pan.baidu.com/s/1fV8TqpLtbH6YljoNKPRV3Q 
提取码:67pc 
--来自百度网盘超级会员V5的分享

以sample_venc.c文件为重点进行分心,main主函数也在该文件。

main()
	SAMPLE_VENC_720P_CLASSIC()
		SAMPLE_RGN_CreateVideoRegion();
		HH_OSD_Init(); //实现静态内容
		HH_OSD_All_Refresh();//实现动态内容

在这里插入图片描述
  只要绑定好,剩下的工作都是自动的,因为海思的mpp框架都已经实现好了,内部开启了多个线程进行工作。

三、OSD代码实现分析

1、SAMPLE_RGN_CreateVideoRegion()所做的事

执行该函数所产生的效果:
在这里插入图片描述
(1)函数调用关系

SAMPLE_RGN_CreateVideoRegion
	SAMPLE_RGN_CreateOverlayForVenc
		HI_MPI_RGN_Create 创建区域
		HI_MPI_RGN_AttachToChn 将区域叠加到通道上
	SAMPLE_RGN_Add
	SAMPLE_RGN_CreateOverlayForVenc
	pthread_create(&osd_ThreadId, NULL, SAMPLE_RGN_AddVideoTimestamp, NULL);
		SAMPLE_RGN_AddVideoTimestamp
HI_S32 SAMPLE_RGN_CreateVideoRegion(HI_VOID)
{
	HI_U32 u32ChnId 		= 0;
	RGN_HANDLE Handle 	= 0;

	//下面定义的四个变量均与OSD有关
	HI_U32 coordinate_x 	= 0;//描述坐标的变量
	HI_U32 coordinate_y 	= 0;
	HI_U32 region_with 	= 0;//区域的宽
	HI_U32 region_height = 0;//区域的高

	u32ChnId 	= VENC_RECORD_CHNID;//使用的通道的id,venc的通道0

	region_with		= 480;// 320 *3 / 2,3表示使用图像的bpp,显存是argb1555,2个字节,故除二
	region_height 	= 320;// 320
	coordinate_x 	= 0;
	coordinate_y 	= 720 - 320;
	
	Handle 			= VENC_RECORD_LOGO_OSD_HANDLE;

	//RGN:表示region,即区域
	SAMPLE_RGN_CreateOverlayForVenc(u32ChnId, Handle, coordinate_x, coordinate_y, region_with, region_height);
	SAMPLE_RGN_Add( Handle, VENC_RECORD_LOGO_OSD_HANDLE);

	//上边执行的两个函数实现了一个左下角的区域静态画面


	//下边的内容实现了一个右下角的动态画面
	Handle 		= VENC_RECORD_TIME_OSD_HANDLE;//区域句柄号。必须是未使用的 Handle 号
											  //取值范围: [0, RGN_HANDLE_MAX)。

	//24bit的bmp图片,一个像素点就是三字节。一个字节8bit
	region_with		= 544;  //18个字体14*32+4*16+16=528, 预留16像素空间 
	region_height 	= 24;  

	coordinate_x 	= 1280-16-260;//根据需求调整位置
	coordinate_y 	= 720 - 24-12;//下面预留12像素空间

	SAMPLE_RGN_CreateOverlayForVenc(u32ChnId, Handle, coordinate_x, coordinate_y, region_with, region_height);

	pthread_t osd_ThreadId = 0;
	pthread_create(&osd_ThreadId, NULL, SAMPLE_RGN_AddVideoTimestamp, NULL);//因为在SAMPLE_RGN_AddVideoTimestamp
					//中有while(1),如果不开启一个线程,程序将会卡在这里
	return HI_SUCCESS;
}

(2)RGN(region,区域)画布尺寸计算:以像素为单位。原始图像是bpp24的,每个像素3字节。而画布的图像是ARGB1555的,所以每个像素是2字节。所以画布每一行的像素数是图像宽度*3/2 。

(3)整个图像的坐标系是左上角是(0,0)点,宽度方向是x,高度方向是y
在这里插入图片描述

2、前景和背景透明

(1)所谓前景foreground,就是图片中显示的内容部分;所谓背景background,就是图片中没有内容的部分。

(2)前景和背景的透明度范围都是0-128,其中0代表全透明,128代表全不透明

(3)前景和背景透明度可以同时设置,各自起作用,互不影响。

(4)stRgnAttr.unAttr.stOverlay.u32BgColor是RGN的画布(canvas,等同于LCD显示时的显存fb)的背景颜色。也就是画布中没有被填充的部分默认显示的颜色。

3、RGN(region)通道属性分析

HI_S32 SAMPLE_RGN_CreateOverlayForVenc(HI_U32 u32ChnId, RGN_HANDLE Handle, HI_U32 u32X, HI_U32 u32Y, HI_U32 u32Width, HI_U32 u32Height)
{
	//printf("u32X  = %d u32Y = %d \n",u32X,u32Y);
	HI_S32 s32Ret = 0;
	MPP_CHN_S stChn;
	RGN_ATTR_S stRgnAttr;
	RGN_CHN_ATTR_S stChnAttr;

	/* Add cover to vpss group */
	stChn.enModId  = HI_ID_VENC;
	stChn.s32DevId = 0;
	stChn.s32ChnId = u32ChnId/*0*/;

	stRgnAttr.enType = OVERLAY_RGN;
	stRgnAttr.unAttr.stOverlay.enPixelFmt       = PIXEL_FORMAT_RGB_1555;//像素格式
	stRgnAttr.unAttr.stOverlay.stSize.u32Width  = u32Width; //16x, value=bmp's Widthx3/2
	stRgnAttr.unAttr.stOverlay.stSize.u32Height = u32Height; //16x
	//stRgnAttr.unAttr.stOverlay.u32CanvasNum = 4; //Max:6
	stRgnAttr.unAttr.stOverlay.u32BgColor       = 0xffffffff;//背景色

	s32Ret = HI_MPI_RGN_Create(Handle, &stRgnAttr);
	if (s32Ret != HI_SUCCESS)
	{
		printf("--@@_1 HI_MPI_RGN_Create failed! s32Ret: 0x%x.\n", s32Ret);
		return s32Ret;
	}

	//	以下的内容是进行通道属性的设置
	stChnAttr.bShow  = HI_TRUE ; //region是否显示
	stChnAttr.enType = OVERLAY_RGN;//OSD类型
	stChnAttr.unChnAttr.stOverlayChn.stPoint.s32X = u32X; //区域的X坐标
	stChnAttr.unChnAttr.stOverlayChn.stPoint.s32Y = u32Y; //区域的y坐标
	stChnAttr.unChnAttr.stOverlayChn.u32BgAlpha   = 0; //背景透明度,透明度范围0-128
	stChnAttr.unChnAttr.stOverlayChn.u32FgAlpha   = 128;//前景透明度
	stChnAttr.unChnAttr.stOverlayChn.u32Layer     = 4 /*7 Handle*/;//区域层次

	// below para must keep
	stChnAttr.unChnAttr.stOverlayChn.stQpInfo.bAbsQp 		= HI_FALSE ;//是否开启qp区域保护
	stChnAttr.unChnAttr.stOverlayChn.stQpInfo.s32Qp  		= 0;
	stChnAttr.unChnAttr.stOverlayChn.stQpInfo.bQpDisable 	= HI_FALSE ;

	//	区域反色功能相关的
	stChnAttr.unChnAttr.stOverlayChn.stInvertColor.stInvColArea.u32Height = 48/*16 * (Handle % 2 + 1)*/;
	stChnAttr.unChnAttr.stOverlayChn.stInvertColor.stInvColArea.u32Width  = 48/*16 * (Handle % 2 + 1)*/;
	stChnAttr.unChnAttr.stOverlayChn.stInvertColor.u32LumThresh = 128;
	stChnAttr.unChnAttr.stOverlayChn.stInvertColor.enChgMod	= LESSTHAN_LUM_THRESH;
	stChnAttr.unChnAttr.stOverlayChn.stInvertColor.bInvColEn	= HI_FALSE ;


	s32Ret = HI_MPI_RGN_AttachToChn(Handle, &stChn, &stChnAttr);//将区域叠加到通道上
	if (s32Ret != HI_SUCCESS)
	{
		printf("HI_MPI_RGN_AttachToChn failed! s32Ret: 0x%x.\n", s32Ret);
		return s32Ret;
	}

	return HI_SUCCESS;
}

4、RGN(region)内容填充

HI_S32 SAMPLE_RGN_Add(unsigned int Handle,int Type)
{
    	//printf("-------------------%s add rgn %d --------------------\n",__func__,Type);

	HI_S32 s32Ret = HI_SUCCESS;
	RGN_ATTR_S stRgnAttrSet;
	RGN_CANVAS_INFO_S stCanvasInfo;
	BITMAP_S stBitmap;
	SIZE_S stSize;

	/* Photo logo */
	s32Ret = HI_MPI_RGN_GetAttr(Handle/*VencOsdHandle*/, &stRgnAttrSet);//获取区域属性
	if (HI_SUCCESS != s32Ret)
	{
		printf("HI_MPI_RGN_GetAttr failed! s32Ret: 0x%x.\n", s32Ret);
		return s32Ret;
	}

	s32Ret = HI_MPI_RGN_GetCanvasInfo(Handle/*VencOsdHandle*/, &stCanvasInfo);//获取区域画布信息
	if (HI_SUCCESS != s32Ret)
	{
		printf("HI_MPI_RGN_GetCanvasInfo failed! s32Ret: 0x%x.\n", s32Ret);
		return s32Ret;
	}

	stBitmap.pData   = (void*)stCanvasInfo.u32VirtAddr;//位图数据
	stSize.u32Width  = stCanvasInfo.stSize.u32Width;//位图宽度
	stSize.u32Height = stCanvasInfo.stSize.u32Height;//位图高度
	s32Ret = SAMPLE_RGN_UpdateCanvas(Type, &stBitmap, HI_TRUE , 0x0000, &stSize, stCanvasInfo.u32Stride,stRgnAttrSet.unAttr.stOverlayEx.enPixelFmt);
	if (HI_SUCCESS != s32Ret)//将要显示的内容丢到备份的内存中,等待使用
	{
		printf("SAMPLE_RGN_UpdateCanvas failed! s32Ret: 0x%x.\n", s32Ret);
		return s32Ret;
	}

	s32Ret = HI_MPI_RGN_UpdateCanvas(Handle/*VencOsdHandle*/);//更新区域画布信息
	if (HI_SUCCESS != s32Ret)//将备份内存中的数据直接刷到画布去显示
	{
		printf("HI_MPI_RGN_UpdateCanvas failed! s32Ret: 0x%x.\n", s32Ret);
		return s32Ret;
	}

	return HI_SUCCESS;
}

(1)程序中会有一个RGB888向ARGB1555转的过程。
(2)BMP图片中存储图像的像素顺序,和RGN的canvas里像素顺序是不同的。
在这里插入图片描述
这部分内容涉及到了bmp图片的解析以及如何显示,若是之前未了解过该方面的知识,可参考我的《BMP图片显示原理

5、动态刷新BMP

在这里插入图片描述
  将上图中这些带有数字的图片不断切换显示,形成数字变化的效果。
在这里插入图片描述

HI_VOID* SAMPLE_RGN_AddVideoTimestamp(HI_VOID* p)//真正网络摄像机一般会从服务器获取时间而非本地
{
	HI_S32 s32Ret = HI_SUCCESS;
	time_t timep;
	struct tm *pLocalTime;
	HI_U8 seconds = 80;

	RGN_HANDLE Handle;
	Handle = VENC_RECORD_TIME_OSD_HANDLE;

	while (1)
	{
		time(&timep);
		pLocalTime = localtime(&timep);//转换成当前时间
		if (seconds == pLocalTime->tm_sec){
			usleep(150*1000);//增加循环的时间,屏幕上的内容是每过一秒刷新一次的
			continue;
		} else {
			seconds = pLocalTime->tm_sec;
		}

		s32Ret = SAMPLE_RGN_Add( Handle, VENC_RECORD_TIME_OSD_HANDLE);//关键在这
		if (HI_SUCCESS != s32Ret)
		{
			printf("SAMPLE_RGN_Add line %d  failed! s32Ret: 0x%x.\n",__LINE__, s32Ret);
			break;
		}
	}

	pthread_detach(pthread_self());//分离线程

	return 0;
}

总结:左下角的就是单bmp图片OSD,静态的;右下角是多BMP图片组合,且动态刷新显示。

6、小结——整体函数调用层次

SAMPLE_RGN_CreateVideoRegion
	SAMPLE_RGN_CreateOverlayForVenc
		HI_MPI_RGN_Create //创建一个区域
		HI_MPI_RGN_AttachToChn //将区域叠加到通道上
	SAMPLE_RGN_Add
		HI_MPI_RGN_GetAttr //获取区域属性
		HI_MPI_RGN_GetCanvasInfo //获取区域画布信息
		SAMPLE_RGN_UpdateCanvas //将要显示的内容丢到备份的内存中,等待使用
			SAMPLE_RGN_CreateSurfaceByCanvas //解析BMP数据,创建要显示的内容
				SAMPLE_RGN_LoadCanvasEx 加载数据
					SAMPLE_RGN_LoadBMPCanvas_Logo//获取要显示的logo数据
						GetBmpInfo //得到bmp图片的信息
						malloc
						fseek
						fread
						OSD_MAKECOLOR_U16 //RGB888向ARGB1555转的过程				
		HI_MPI_RGN_UpdateCanvas //更新区域画布信息
			
	SAMPLE_RGN_CreateOverlayForVenc
	pthread_create(&osd_ThreadId, NULL, SAMPLE_RGN_AddVideoTimestamp, NULL);
		SAMPLE_RGN_AddVideoTimestamp
			SAMPLE_RGN_Add
				HI_MPI_RGN_GetAttr //获取区域属性
				HI_MPI_RGN_GetCanvasInfo //获取区域画布信息
				SAMPLE_RGN_UpdateCanvas //将要显示的内容丢到备份的内存中,等待使用
					SAMPLE_RGN_CreateSurfaceByCanvas //解析BMP数据,创建要显示的内容
						SAMPLE_RGN_LoadCanvasEx //加载数据
							SAMPLE_RGN_LoadBMPCanvas_TimeSmap//通过动态刷新bmp图片显示时间

注:本文章参考了《朱老师物联网大讲堂》课程笔记,并结合了自己的实际开发经历以及网上他人的技术文章,综合整理得到。如有侵权,联系删除!水平有限,欢迎各位在评论区交流。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐