以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。
一、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字节。