第8季2:OSD实验演示与代码分析

以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。

一、OSD实验演示

(1)将原来的/home/xjh/iot/hisi_development/Hi3518E_SDK/Hi3518E_SDK_V1.0.3.0/mpp目录改名为mpp_backup,然后将提供的mpp文件夹拷贝到Hi3518E_SDK_V1.0.3.0目录下。该sample基于rtsp协议进行实时传输,只不过添加了OSD相关的内容。主要不同点,在于多了OSD文件夹,venc目录下的文件内容不同,common目录下的文件内容不同等等。

root@ubuntu:/home/xjh/iot/hisi_development/Hi3518E_SDK/Hi3518E_SDK_V1.0.3.0/mpp# ls
component  extdrv  include  ko  lib  Makefile.param  sample  tools
root@ubuntu:/home/xjh/iot/hisi_development/Hi3518E_SDK/Hi3518E_SDK_V1.0.3.0/mpp# cd sample/
root@ubuntu:/home/xjh/iot/hisi_development/Hi3518E_SDK/Hi3518E_SDK_V1.0.3.0/mpp/sample# ls
audio  common  hifb  ive  Makefile  Makefile.param  osd  readme  region  scene_auto  tde  venc  vio   #这里多了osd目录
root@ubuntu:/home/xjh/iot/hisi_development/Hi3518E_SDK/Hi3518E_SDK_V1.0.3.0/mpp/sample# cd venc
root@ubuntu:/home/xjh/iot/hisi_development/Hi3518E_SDK/Hi3518E_SDK_V1.0.3.0/mpp/sample/venc# ls
Makefile  sample_venc.c
root@ubuntu:/home/xjh/iot/hisi_development/Hi3518E_SDK/Hi3518E_SDK_V1.0.3.0/mpp/sample/venc# 

(2)在mpp/sample/venc/下执行make,将生成的sample_venc文件拷贝到板载系统的/mnt目录。

(3)将提供的res文件夹拷贝到板载系统/mnt目录中(与生成的sample_venc位于同一目录)。如果忘记该步骤,提示错误:

/mnt # ./sample_venc 
MPP Ver  HI_VERSION=Hi3518EV200_MPP_V1.0.3.0 B040 Release
RTSP:-----Init Rtsp server
s32ChnNum = 1
=============SAMPLE_COMM_VI_SetMipiAttr enWDRMode: 0
linear mode
Aptina AR0130 sensor 720P30fps init success!
Open file faild:res/logo_1080p.bmp!
OSD_LoadBMP error!
load bmp error!
Segmentation fault
/mnt #

(4)在板载系统/mnt目录下执行“./sample_venc”,显示内容如下。

(5)在PC端运行VLC软件,选择“媒体>>打开网络串流”,输入“rtsp://192.168.1.11:554/mnt/stream_chn0.h264”(注意这里的IP是板载系统的IP),然后点击播放。

(6)实时视频画面如下,可见确实添加了一些区域信息。

二、OSD代码分析

1、代码框架分析

(1)利用上面提供的mpp目录建立SI工程,相比于第6季2:基于RTSP协议的实时视频流传输,不同之处在于下图框出的部分。

(2)其中SAMPLE_RGN_CreateVideoRegion函数完成了左下角、右下角的区域显示,而HH_OSD_Init函数、while循环和HH_OSD_ALL_Refresh函数完成了右上角、左上角的区域显示。

(3)这三个函数所调用的函数位于sample_venc.c文件中、OSD目录中。

2、分析SAMPLE_RGN_CreateVideoRegion()函数

(1)该函数的调用关系与简单分析如下。

SAMPLE_RGN_CreateVideoRegion
    //左下角的区域内容
    SAMPLE_RGN_CreateOverlayForVenc//创建叠加(即OVERLAY类型的)区域
        HI_MPI_RGN_Create //创建region
        HI_MPI_RGN_AttachToChn //将region和venc通道进行绑定
    SAMPLE_RGN_Add
        HI_MPI_RGN_GetAttr//获取区域的一些属性
        HI_MPI_RGN_GetCanvasInfo//获取画布的一些信息
        SAMPLE_RGN_UpdateCanvas//将要显示的内容更新到备份画布中
            SAMPLE_RGN_CreateSurfaceByCanvas
                SAMPLE_RGN_LoadCanvasEx
                    SAMPLE_RGN_LoadBMPCanvas_Logo
                        GetBmpInfo//读取bmp的文件头
                        malloc//申请一个临时的buff
                        fseek//跳过bmp的文件头
                        fread//读取bmp真正的内容到malloc申请的临时buff中
                        OSD_MAKECOLOR_U16//完成888到1555的转换              
        HI_MPI_RGN_UpdateCanvas//将备份画布的内容更新到真正的画布中

    //右下角的区域内容
    SAMPLE_RGN_CreateOverlayForVenc
    SAMPLE_RGN_AddVideoTimestamp
HI_S32 SAMPLE_RGN_CreateVideoRegion(HI_VOID)
{
	HI_U32 u32ChnId 	= 0; //通道id
	RGN_HANDLE Handle 	= 0;

	HI_U32 coordinate_x  = 0; //以下四个变量定义了一个区域
	HI_U32 coordinate_y  = 0;
	HI_U32 region_with 	 = 0;
	HI_U32 region_height = 0;

	u32ChnId = VENC_RECORD_CHNID;//venc模块只有一个通道,其id这里为0

    // 这里的设置要根据实际区域内容来决定
    // “深创客”图标的分辨率是320像素*320像素
    // 换成画布的话,原有320*3个字节,而画布一个像素两个字节,则宽是320*3/2个像素
	region_with		= 480;  // 320 *3 / 2  
	region_height 	= 320;  // 这里的高不用转换?
	
	coordinate_x 	= 0;
	coordinate_y 	= 720 - 320;
	
	Handle = VENC_RECORD_LOGO_OSD_HANDLE;

	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;

	//24bit的bmp图片,每个像素点就是3字节
	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);

	return HI_SUCCESS;
}

(2)其中region_with、region_height,是在计算出区域画布的尺寸有多大(以像素为单位的)。我们知道原始图像是bpp24的,所以每个像素是3个字节,而区域画布的图像是ARGB1555的,所以每个像素是2字节。因此区域画布每一行的像素数是图像宽度*3/2,即先计算出原始图像的一行有多少个字节,即图像宽度*3,然后再计算出区域画布的一行多少个像素,即图像宽度*3 / 2。 

(3)整个屏幕坐标系是这样的:左上角是(0,0),宽度方向是x,高度方向是y。而代码中的coordinate_x、coordinate_y,是在计算区域画布的位置,如上图所示。

(4)我们来分析SAMPLE_RGN_CreateOverlayForVenc函数。

其内部首先填充了区域属性变量stRgnAttr,然后调用HI_MPI_RGN_Create来创建区域,接着填充区域通道属性变量stChnAttr,然后调用HI_MPI_RGN_AttachToChn将区域绑定到venc通道中。

代码中的区域属性变量stRgnAttr有以下成员,它表示画布的背景色。

stRgnAttr.unAttr.stOverlay.u32BgColor = 0x00000000;

代码中的区域通道属性变量stChnAttr有以下两个成员,它们分别表示背景透明度、前景透明度。

stChnAttr.unChnAttr.stOverlayChn.u32BgAlpha   = 0;
stChnAttr.unChnAttr.stOverlayChn.u32FgAlpha   = 128;

接下来通过实验来形象地理解 “画布背景色” “前景透明度” “背景透明度” 这三个概念。简单起见,我们将HH_OSD_Init、while循环注释掉,则此时显示左下角、右下角的区域内容。

	SAMPLE_RGN_CreateVideoRegion();
/*	
    HH_OSD_Init(); 
	while(1){
		HH_OSD_All_Refresh();//hsx
		sleep(1);
	}
*/
	getchar();
	getchar();

1)修改前景透明度

将前景透明度作如下的修改,即保持背景透明度为0(全透明)不变 ,将前景透明度从128(不透明)改为50(半透明)。

stChnAttr.unChnAttr.stOverlayChn.u32BgAlpha   = 0;//保持不变
stChnAttr.unChnAttr.stOverlayChn.u32FgAlpha   = 50;//从128改为50

修改后的效果如下所示,和上图相比,可见“深创客”蓝色字体部分已经变得半透明。

2)修改背景透明度

在1)的修改基础上,将背景透明度从0(全透明)改为50(半透明)。

stChnAttr.unChnAttr.stOverlayChn.u32BgAlpha   = 50;//从0改为50,即半透明
stChnAttr.unChnAttr.stOverlayChn.u32FgAlpha   = 50;

修改后的效果如下所示,和上图相比,可见“深创客”的背景已经由全透明变为半透明。另外,注意到“深创客”图片并没有填满整个画布,因为画布是480*320,而“深创客”图片是320*320。

3)修改画布背景色

上图中箭头所指的区域的颜色就是画布的背景色(这里为0x00000000)。我们在2)的修改基础上逐次修改背景色值如下:

//原来的
stRgnAttr.unAttr.stOverlay.u32BgColor = 0x00000000;
//修改实验1
stRgnAttr.unAttr.stOverlay.u32BgColor = 0xabcdabcd;
//修改实验2
stRgnAttr.unAttr.stOverlay.u32BgColor = 0xffffffff;

修改为 0xffffffff 后的效果如下:

 修改为 0xabcdabcd 后的效果如下:

从以上实验中可以看到:

a、所谓前景(foreground)就是图片中显示的内容部分,所谓背景(background)就是图片中没有内容的部分。比如下图中的白色部分就是背景,蓝色部分就是前景。

b、前景的透明度、背景的透明度范围都是0-128,其中0代表全透明,128代表全不透明。

c、前景的透明度、背景的透明度可以同时设置,各自起作用,互不影响。

d、画布(canvas,相当于LCD的显存)背景色,即画布中没有被填充的部分默认显示的颜色。

(6)上面的操作保留了左下角的logo区域、右下角的时间区域。如果要去除右下角的时间区域,可以将其注释掉,如下图所示。这也说明logo区域和时间区域是相互独立的两个区域。

(7)我们接着分析sample_RGN_Add函数。

该函数内部的流程如下:

SAMPLE_RGN_Add
    HI_MPI_RGN_GetAttr//获取区域的一些属性
    HI_MPI_RGN_GetCanvasInfo//获取画布的一些信息
    SAMPLE_RGN_UpdateCanvas//将要显示的内容更新到备份画布中
        SAMPLE_RGN_CreateSurfaceByCanvas
            SAMPLE_RGN_LoadCanvasEx
                SAMPLE_RGN_LoadBMPCanvas_Logo
                    GetBmpInfo//读取bmp的文件头
                    malloc//申请一个临时的buff
                    fseek//跳过bmp的文件头
                    fread//读取bmp真正的内容到malloc申请的临时buff中
                    OSD_MAKECOLOR_U16//完成888到1555的转换              
    HI_MPI_RGN_UpdateCanvas//将备份画布的内容更新到真正的画布中

该函数的内容如下:

重点理解一下变量stBitmap、stCanvasInfo。

typedef struct hiBITMAP_S
{
    PIXEL_FORMAT_E enPixelFormat;  /* Bitmap's pixel format */
    HI_U32 u32Width;               /* Bitmap's width */
    HI_U32 u32Height;              /* Bitmap's height */
    HI_VOID* pData;                /* Address of Bitmap's data */
} BITMAP_S;

typedef struct hiRGN_CANVAS_INFO_S
{
    HI_U32         u32PhyAddr;
    HI_U32         u32VirtAddr;
    SIZE_S         stSize;              
    HI_U32         u32Stride;
    PIXEL_FORMAT_E enPixelFmt;  
} RGN_CANVAS_INFO_S;

BITMAP_S stBitmap;
RGN_CANVAS_INFO_S stCanvasInfo;

//指针指向了画布对应的显存地址(因为基于linux系统,这里是虚拟地址)
stBitmap.pData = (void*)stCanvasInfo.u32VirtAddr;

另外,BMP图片中存储图像的像素顺序和区域的画布里的像素顺序是不同的,因此需要转换。这一步的工作在函数SAMPLE_RGN_LoadBMPCanvas_Logo中完成。

(8)接着分析右下角的时间区域,即(6)中被注释的部分,我们修改为“#if 1”则去掉注释。

#if 1
	Handle 		= VENC_RECORD_TIME_OSD_HANDLE;

	//24bit的bmp图片,一个点就是3字节。一个字节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);
#endif

a、该代码创建了一个线程,线程函数是SAMPLE_RGN_AddVideoTimestamp,内容如下。

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)
	{
        //获取当前linux内核存储的时间(依靠本地RTC),但实际上我们一般通过服务器获取时间
		time(&timep);
		
        pLocalTime = localtime(&timep);
		if (seconds == pLocalTime->tm_sec){
			usleep(150*1000);//150ms?
			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;
}

b、通过分析得知,右下角的时间区域是多个BMP图片的组合,使用while循环进行动态刷新。而左下角的logo区域是单张BMP图片,是静态的显示。

c、下面的代码的赋值是如何计算出来的呢?

	//24bit的bmp图片,一个点就是3字节。一个字节8bit
	region_with		= 544;  //14*32+4*16+32=544
	region_height 	= 24;  

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

首先右下角时间区域包括14个数字、2个短横2个冒号、1个空格,如下所示:

其中,14个数字都是相同的分辨率,16像素*24像素。因为每个像素两个字节,因此每行(即宽)共有16*2=32个字节,那么14个数字共有14*32个字节。

而2个短横与2个冒号都是相同的分辨率,8像素*24像素。因为每个像素两个字节,因此每行(即宽)共有8*2=16个字节,那么2个短横与2个冒号共有4*16个字节。

而1个空格的分辨率是16像素*24像素,因为每个像素两个字节,因此对应着16*2=32字节。


  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天糊土

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

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

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

打赏作者

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

抵扣说明:

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

余额充值