Hi3559编译Freetype+SDL+SDL_TTF并实现OSD字符叠加

0. 准备工作

在https://github.com/As772309423/freetype-SDL-SDL_TTL上下载freetype-2.4.10、SDL-1.2.15和SDL_ttf-2.0.11,并在虚拟机上进行解压。

1. 编译Freetype、SDL和SDL_TTF

1.1 Freetype2的编译

解压Freetype的压缩包,进入源码目录进行配置:

./configure CC=aarch64-himix100-linux-gcc --host=arm-hisiv500-linux --enable-shared=no --enable-static=yes

这里的CC是交叉编译工具链的gcc编译器,enable-shared选项是编译动态库(.so),enable-static是编译静态库(.a),host不用管。

这里按照实际情况设置CC为Hi3559的gcc交叉编译工具aarch64-himix100-linux-gcc,编译静态库。

执行完毕后,先make,再make install。

make install命令执行中,可以看到生成的头文件位于/usr/local/include/freetype2/freetype,库文件安装到了/usr/local/lib

1.2 SDL库编译

类似1.1,进入目录后,先用和1.1一样的配置命令配置后,make和make install。

最后可以看到库文件安装到了/usr/local/lib

头文件在/usr/local/include/SDL

1.3 SDL_TTF编译

编译SDL_TTF库之前,在SDL_ttf.c里增加一个动态改变字体大小的接口:

FT_Error TTF_SetFontSize(TTF_Font *font,int ptsize)
{
	FT_Fixed scale;
	FT_Error error;
	FT_Face face;
	
	face = font->face;
	
	/* Make sure that our font face is scalable (global metrics) */
	if ( FT_IS_SCALABLE(face) ) {
 
	  	/* Set the character size and use default DPI (72) */
	  	error = FT_Set_Char_Size( font->face, 0, ptsize * 64, 0, 0 );
			if( error ) {
	    	TTF_SetFTError( "Couldn't set font size", error );
	    	TTF_CloseFont( font );
	    	return error;
	  }
 
	  /* Get the scalable font metrics for this font */
	  scale = face->size->metrics.y_scale;
	  font->ascent  = FT_CEIL(FT_MulFix(face->ascender, scale));
	  font->descent = FT_CEIL(FT_MulFix(face->descender, scale));
	  font->height  = font->ascent - font->descent + /* baseline */ 1;
	  font->lineskip = FT_CEIL(FT_MulFix(face->height, scale));
	  font->underline_offset = FT_FLOOR(FT_MulFix(face->underline_position, scale));
	  font->underline_height = FT_FLOOR(FT_MulFix(face->underline_thickness, scale));
	  
	} else {
		/* Non-scalable font case.  ptsize determines which family
		 * or series of fonts to grab from the non-scalable format.
		 * It is not the point size of the font.
		 * */
		if ( ptsize >= font->face->num_fixed_sizes )
			ptsize = font->face->num_fixed_sizes - 1;
		font->font_size_family = ptsize;
		error = FT_Set_Pixel_Sizes( face,
				face->available_sizes[ptsize].height,
				face->available_sizes[ptsize].width );
	  	/* With non-scalale fonts, Freetype2 likes to fill many of the
		 * font metrics with the value of 0.  The size of the
		 * non-scalable fonts must be determined differently
		 * or sometimes cannot be determined.
		 * */
	  	font->ascent = face->available_sizes[ptsize].height;
	  	font->descent = 0;
	  	font->height = face->available_sizes[ptsize].height;
	  	font->lineskip = FT_CEIL(font->ascent);
	  	font->underline_offset = FT_FLOOR(face->underline_position);
	  	font->underline_height = FT_FLOOR(face->underline_thickness);
	}
 
	if ( font->underline_height < 1 ) {
		font->underline_height = 1;
	}
 
	font->glyph_italics *= font->height;
	
	Flush_Cache(font); 
	
	return 0;
}

SDL_ttf.h增加接口的声明:

extern DECLSPEC int SDLCALL TTF_SetFontSize(TTF_Font *font,int ptsize);

之后参考1.2进行编译。

1.4 Demo代码编译

Demo代码如下。

#include <stdio.h>
#include "SDL.h"
#include "SDL_ttf.h"

int main(int argc, const char *argv[])
{
    char * pstr = "2019-11-21 15:40:28 Front";  //需要渲染的字符串
    SDL_PixelFormat *fmt;
    TTF_Font *font;  
    SDL_Surface *text, *temp;  
    if (TTF_Init() < 0 ) 
    {  
        fprintf(stderr, "Couldn't initialize TTF: %s\n",SDL_GetError());  
        SDL_Quit();
    }  
    font = TTF_OpenFont("./AGENCYR.TTF", 64);   //字体路径和字体大小
    if ( font == NULL ) 
    {  
        fprintf(stderr, "Couldn't load %d pt font from %s: %s\n", 18, "ptsize", SDL_GetError());  
    }  
    SDL_Color forecol = {  0xff, 0xff, 0xff, 0xff };   //字体颜色
    text = TTF_RenderUTF8_Solid(font, pstr, forecol);

    fmt = (SDL_PixelFormat*)malloc(sizeof(SDL_PixelFormat));
    memset(fmt,0,sizeof(SDL_PixelFormat));
    fmt->BitsPerPixel = 16;
    fmt->BytesPerPixel = 2;
    fmt->colorkey = 0xffffffff;
    fmt->alpha = 0xff;

    temp = SDL_ConvertSurface(text, fmt, 0);
    SDL_SaveBMP(temp, "save.bmp");   //生成的BMP文件名
    SDL_FreeSurface(text);  
    SDL_FreeSurface(temp);
    TTF_CloseFont(font);  
    TTF_Quit();  
    return 0;
}

使用如下命令加上对相关库的依赖进行编译:

aarch64-himix100-linux-gcc test.c -o test2 -I/usr/local/include/SDL -I/usr/local/include/freetype2 -L/usr/local/lib/ -Xlinker --start-group -lfreetype -lSDL -lSDL_ttf -lpthread -Wl,--end-group

带库编译时需要注意库和库之间的关联关系。

使用如下命令

aarch64-himix100-linux-gcc test.c -I/usr/local/include/SDL -I/usr/local/include/freetype2 -L/usr/local/lib/ -lfreetype -lSDL -lSDL_ttf -o test

执行失败,提示undefined reference。原因是SDL库依赖freetype库和pthread库,但是gcc的依赖是从右往左

即:

正确依赖关系:SDL-依赖->freetype & pthread

命令里的依赖关系(错误):freetype-依赖->SDL

解决方法:

  1. 根据实际情况修改编译命令:

    aarch64-himix100-linux-gcc test.c -I/usr/local/include/SDL -I/usr/local/include/freetype2 -L/usr/local/lib/ -lSDL -lSDL_ttf -lfreetype -lpthread -o test
    
  2. 使用-Xlinker:

    aarch64-himix100-linux-gcc test.c -o test2 -I/usr/local/include/SDL -I/usr/local/include/freetype2 -L/usr/local/lib/ -Xlinker "-(" -lfreetype -lSDL -lSDL_ttf -lpthread -Wl,"-)"
    

    aarch64-himix100-linux-gcc test.c -o test2 -I/usr/local/include/SDL -I/usr/local/include/freetype2 -L/usr/local/lib/ -Xlinker --start-group -lfreetype -lSDL -lSDL_ttf -lpthread -Wl,--end-group
    

编译完成后,将程序中的字体放在和生成的文件相同目录下,执行编译后的文件,会看到生成的save.bmp里有渲染出来的文字。

注:

支持中文的方法:

  1. 选择字体时,选择带中文的字体。
  2. 渲染使用的函数和程序文件的编码必须统一。如果使用TTF_RenderUTF8_Solid()进行带中文字符串渲染,则.c文件必须使用UTF8编码格式。否则需要进行转换,对编码格式进行统一。

2. HI3559的OSD叠加

HI3559调用OSD的流程如下:

START
HI_MPI_RGN_Create
创建区域
HI_MPI_RGN_SetBitMap
设置区域位图
HI_MPI_RGN_AttachToChn
将Region叠加到输出Channel上
End

2.1 加载本地BMP图像并叠加

代码的逻辑如上图。

本程序是在VDEC的Sample程序上修改,将BMP叠加到VO的设备0通道0上。

基本代码如下:

HI_S32 SAMPLE_Create_Region_VO(VO_CHN Vo_Chn,RGN_HANDLE R_Handle)
{
    HI_S32 s32Ret;
    MPP_CHN_S stChn;
    RGN_ATTR_S stRgnAttr;
    RGN_CHN_ATTR_S stChnAttr;
    BITMAP_S stBitmap;

	//Region Create First
    stRgnAttr.enType = OVERLAYEX_RGN;
    stRgnAttr.unAttr.stOverlayEx.enPixelFmt = PIXEL_FORMAT_ARGB_1555;  //区域的像素格式设置
    stRgnAttr.unAttr.stOverlayEx.stSize.u32Width  = 496;//128;         //叠加区域的宽高,2对齐
    stRgnAttr.unAttr.stOverlayEx.stSize.u32Height = 76;//128;
    stRgnAttr.unAttr.stOverlayEx.u32BgColor = 0x000003e0;              //叠加区域的背景色
    stRgnAttr.unAttr.stOverlayEx.u32CanvasNum = 2;
 
    s32Ret = HI_MPI_RGN_Create(R_Handle, &stRgnAttr);
    if (s32Ret != HI_SUCCESS)
    {
        printf("region of  chn %d create fail. value=0x%x.", Vo_Chn, s32Ret);
        return s32Ret;
    }
    /*attach region to chn*/
    stChn.enModId = HI_ID_VO;                 //叠加到设备VO上
	stChn.s32DevId = 0;                       //VO的设备ID是0
    stChn.s32ChnId = Vo_Chn;                  //叠加到的设备通道

    stChnAttr.bShow = HI_TRUE;
    stChnAttr.enType = OVERLAYEX_RGN;
    stChnAttr.unChnAttr.stOverlayExChn.stPoint.s32X = 128;//128;       //叠加区域的位置
    stChnAttr.unChnAttr.stOverlayExChn.stPoint.s32Y = 128;//128;
    stChnAttr.unChnAttr.stOverlayExChn.u32BgAlpha   = 0;               //背景前景透明度
    stChnAttr.unChnAttr.stOverlayExChn.u32FgAlpha   = 255;
    stChnAttr.unChnAttr.stOverlayExChn.u32Layer     = 0;
 
    /* load bitmap*/
    s32Ret = SampleVoLoadRgnBmp("save.bmp", &stBitmap, HI_FALSE, 0);    //读取本地BMP图片
    if (s32Ret != HI_SUCCESS)
    {
        return s32Ret;
    }
 
    s32Ret = HI_MPI_RGN_SetBitMap(R_Handle, &stBitmap);                 //BitMap设置
    if (s32Ret != HI_SUCCESS)
    {
        printf("region set bitmap to  Handle %d fail. value=0x%x.", R_Handle, s32Ret);
        free(stBitmap.pData);
        return s32Ret;
    }
    free(stBitmap.pData);                     //BMP设置结束后,释放掉BMP文件的资源
 
    s32Ret = HI_MPI_RGN_AttachToChn(R_Handle, &stChn, &stChnAttr);      //Region叠加到通道上
    if (s32Ret != HI_SUCCESS)
    {
        printf("region handle %d attach to chn %d fail. value=0x%x.", R_Handle,Vo_Chn, s32Ret);
        return s32Ret;
    }
    return HI_SUCCESS;
}

HI_S32 SampleVoLoadRgnBmp(const char *filename, BITMAP_S *pstBitmap, HI_BOOL bFil, HI_U32 u16FilColor)
{
    OSD_SURFACE_S Surface;
    OSD_BITMAPFILEHEADER bmpFileHeader;
    OSD_BITMAPINFO bmpInfo;
 
    if(GetBmpInfo(filename,&bmpFileHeader,&bmpInfo) < 0)
    {
        printf("GetBmpInfo err!\n");
        return HI_FAILURE;
    }
 
    Surface.enColorFmt = OSD_COLOR_FMT_RGB1555;
 
    pstBitmap->pData = malloc(2*(bmpInfo.bmiHeader.biWidth)*(bmpInfo.bmiHeader.biHeight));
 
    if(NULL == pstBitmap->pData)
    {
        printf("malloc osd memroy err!\n");
        return HI_FAILURE;
    }
    CreateSurfaceByBitMap(filename,&Surface,(HI_U8*)(pstBitmap->pData));
 
    pstBitmap->u32Width = Surface.u16Width;
    pstBitmap->u32Height = Surface.u16Height;
    pstBitmap->enPixelFormat = PIXEL_FORMAT_ARGB_1555;
 
    int i,j;
    HI_U16 *pu16Temp;
    pu16Temp = (HI_U16*)pstBitmap->pData;
 
    if (bFil)
    {
        for (i=0; i<pstBitmap->u32Height; i++)
        {
            for (j=0; j<pstBitmap->u32Width; j++)
            {
                if (u16FilColor == *pu16Temp)
                {
                    *pu16Temp &= 0x7FFF;
                }
                pu16Temp++;
            }
        }
    }
    return HI_SUCCESS;
}

注:

这里的SampleVoLoadRgnBmp函数用于读取本地的BMP图片并填充BITMAP_S结构体。

需要sample\common下的bit_map.c和bit_map.h支持。

调用函数的地方在SAMPLE_COMM_VDEC_StartSendStream之后。

SAMPLE_COMM_VDEC_StartSendStream(u32VdecChnNum, &stVdecSend[0], &VdecThread[0]);
//ADD START
printf("Start OSD At R_HNDL 0\n");
const RGN_HANDLE R_Hndl = 0;
SAMPLE_Create_Region_VO(0,R_Hndl);
//ADD END
SAMPLE_COMM_VDEC_CmdCtrl(u32VdecChnNum, &stVdecSend[0], &VdecThread[0]);
//ADD START
SAMPLE_Destroy_VO_OSD(0,R_Hndl);
//ADD END
SAMPLE_COMM_VDEC_StopSendStream(u32VdecChnNum, &stVdecSend[0], &VdecThread[0]);

2.2 调用Freetype&SDL&SDL_TTF进行字符叠加

使用SDL_TTL进行字符叠加的流程和静态显示BMP类似。

不同之处:

  1. 对字符串进行渲染之前,需要先进行SDL库的初始化和字体的加载。
  2. HI_MPI_RGN_SetBitMap函数里用到的Bitmap不再来自文件,而是将SDL_TTF库的渲染结果填入BITMAP_S结构体。
2.2.1 SDL初始化和Font加载

初始化和加载代码如下:

TTF_Font* InitFonts(const char *ttfFilename,int size)
{
	TTF_Font* ret = NULL;
	CHK_ISNULL(ttfFilename, NULL);
	if(TTF_Init() < 0)
	{
		printf("Couldn't initialize TTF: %s\n",SDL_GetError());
		//SDL_Quit();
		return ret;
	}
	ret = TTF_OpenFont(ttfFilename, size);
	if(ret == NULL)
	{
		printf("Couldn't Open TTF: %s\n",SDL_GetError());
		TTF_Quit();
	}
	return ret;
}

首先调用TTF_Init()函数初始化SDL_TTF库。

初始化成功后,使用TTF_OpenFont()打开字体并返回一个TTF_Font类型的指针。如果打开失败返回NULL。

程序执行完后使用TTF_CloseFont()TTF_Quit()两个函数退出:

void Font_Quit(TTF_Font* font)
{
    TTF_CloseFont(font);  
    TTF_Quit();  
}
2.2.2 渲染和叠加

相关代码如下,关键函数已经做了注释。

其中的重点函数是Sample_SurfaceWord_ToBMP(),实现了Surface转换成BITMAP_S的转换。

//OVERLAYEX_MAX_NUM_VO = 1,每个VO最大叠加1个OverlayEx
HI_S32 Sample_String_OSD_Disp(MPP_CHN_S *dstchn,RGN_HANDLE *getHandle,POINT_S pos,const char* str,HI_U32 RgnLayer,const TTF_Font *fnt,SDL_Color color)
{
	HI_S32 s32Ret;
	//ARG CHECK.
	CHK_ISNULL(getHandle,HI_FAILURE);
	CHK_ISNULL(str,HI_FAILURE);
	CHK_ISNULL(fnt,HI_FAILURE);

	//Render Text First
	SDL_Surface *text,*temp;
	text = TTF_RenderUTF8_Solid(fnt,str,color);   //渲染UTF8字符串,需要显示中文要保证这里的str是UTF8编码
	CHK_ISNULL(text,HI_FAILURE);               //NULL则渲染失败

	//Convert PixelFmt
	SDL_PixelFormat *fmt;
    fmt = (SDL_PixelFormat*)malloc(sizeof(SDL_PixelFormat));
	CHK_ISNULL(fmt,HI_FAILURE);
    memset(fmt,0,sizeof(SDL_PixelFormat));   //SDL_PixelFormat结构体放置要转换的目的像素格式
    fmt->BitsPerPixel = 16;                  //每个像素16Bit
    fmt->BytesPerPixel = 2;                  //每个像素2Byte
    fmt->colorkey = 0xffffffff;              //不需要修改
    fmt->alpha = 0xff;                       //不需要修改

	temp = SDL_ConvertSurface(text, fmt, 0); //将渲染结果转换成SDL_PixelFormat描述的格式(默认RGB565)
	
	SDL_FreeSurface(text);             //释放不适用的Surface

	//Create Region if not existed...
	if(*getHandle > RGN_HANDLE_MAX)
	{
		//UNEXIST HANDLE,NOW CREATE ONE.
		RGN_ATTR_S stRgnAttr;
		memset(&stRgnAttr,0,sizeof(RGN_ATTR_S));
		stRgnAttr.enType = OVERLAYEX_RGN;
	    stRgnAttr.unAttr.stOverlayEx.enPixelFmt = PIXEL_FORMAT_ARGB_1555;
	    stRgnAttr.unAttr.stOverlayEx.stSize.u32Width = ALIGN_2(temp->w);  //使用渲染后的宽高作为区域的宽高,2对齐
	    stRgnAttr.unAttr.stOverlayEx.stSize.u32Height = ALIGN_2(temp->h);
	    stRgnAttr.unAttr.stOverlayEx.u32BgColor = 0x000003e0;
	    stRgnAttr.unAttr.stOverlayEx.u32CanvasNum = 2;

		RGN_HANDLE i = Sample_RGN_AllocAvaliable(&stRgnAttr);  //循环申请Handle,就是从Handle = 0开始申请
        													//使用HI_MPI_RGN_Create(Handle,*attr)
		                                                       //申请失败检查错误码是否为HI_ERR_RGN_EXIST
        													//是则Handle+1继续直到RGN_HANDLE_MAX
														    //返回其他错误返回错误码
        													//申请成功返回成功的Handle
		if(i > RGN_HANDLE_MAX)
		{
			SDL_FreeSurface(temp);
			return HI_FAILURE;
		}
		*getHandle = i;
	}

	RGN_CHN_ATTR_S stChnAttr;
    stChnAttr.bShow = HI_TRUE;
    stChnAttr.enType = OVERLAYEX_RGN;               //Region类型是OVERLAYEX
    stChnAttr.unChnAttr.stOverlayExChn.stPoint.s32X = pos.s32X;     //显示OSD的位置
    stChnAttr.unChnAttr.stOverlayExChn.stPoint.s32Y = pos.s32Y;
    stChnAttr.unChnAttr.stOverlayExChn.u32BgAlpha   = 0;            //ALPHA=1的前景色透明度(ARGB1555)
    stChnAttr.unChnAttr.stOverlayExChn.u32FgAlpha   = 255;          //ALPHA=0的背景色透明度(ARGB1555)
    stChnAttr.unChnAttr.stOverlayExChn.u32Layer     = RgnLayer;     //区域层次

	BITMAP_S stBitmap;
	Sample_SurfaceWord_ToBMP(temp,&stBitmap,color);  //将渲染后的Surface转换成Bitmap

	s32Ret = HI_MPI_RGN_SetBitMap(*getHandle, &stBitmap);
	if (s32Ret != HI_SUCCESS)
	{
		printf("HI_MPI_RGN_SetBitMap failed with %#x!\n", s32Ret);
		HI_MPI_RGN_Destroy(*getHandle);
		SDL_FreeSurface(temp);
		return s32Ret;
	}
    s32Ret = HI_MPI_RGN_AttachToChn(*getHandle, dstchn, &stChnAttr);
    if (s32Ret != HI_SUCCESS)
    {
        printf("region handle %d attach fail. value=0x%x.", *getHandle, s32Ret);
		HI_MPI_RGN_Destroy(*getHandle);
		SDL_FreeSurface(temp);
        return s32Ret;
    }
	SDL_FreeSurface(temp);
	return HI_SUCCESS;
}
2.2.3 Surface转BITMAP_S

这个函数主要是Surface结构体和BITMAP_S结构体的转换。

主要实现下列功能:

  1. BITMAP_S结构体的初始化,包括申请内存空间
  2. 将SDL_TTF的RGB565转换成OSD叠加的ARGB1555像素格式
  3. 对图像宽度为奇数的Surface进行处理,使之能在宽为偶数的Region上正常显示

代码如下:

HI_VOID Sample_SurfaceWord_ToBMP(SDL_Surface *surface,BITMAP_S *stBitmap,SDL_Color fntcol)
{
	HI_U16 wrd_color = ((fntcol.r >> 3) << 11) + ((fntcol.g >> 2) << 5) + ((fntcol.b >> 3) << 0); //RGB888=>RGB565
	HI_U16 bg_color = 0xffff - wrd_color;  //RGB565下背景色的计算
	stBitmap->u32Height = ALIGN_2(surface->h); //BITMAP 的宽高向上2对齐
	stBitmap->u32Width = ALIGN_2(surface->w);
	stBitmap->pData = malloc(2*(stBitmap->u32Height)*(stBitmap->u32Width)); //申请空间,RGB1555=>2Byte/Pixel,总大小为2*w*h
	memset(stBitmap->pData,0,2*(stBitmap->u32Height)*(stBitmap->u32Width));
	int i,j;
	int w = surface->w;
	int h = surface->h;
	for (i = 0; i < h; ++i)
	{
		HI_U16 *p_dst = (HI_U16*)stBitmap->pData;
		HI_U16 *p_src = (HI_U16*)surface->pixels;
	    int dis_pos = 0;
	    if(w % 2 != 0)
	    	dis_pos = i;     //处理w为奇数的情况
		for(j=0;j<w;j++)
		{
			int a,r, g , b;
			r = (p_src[i*w+dis_pos+j] & 0xF800) >> 11;   //原图像是RGB565,RGB各分量提取
			g = (p_src[i*w+dis_pos+j] & 0x07e0) >> 5;
			b = (p_src[i*w+dis_pos+j] & 0x001f);
			a = (bg_color==p_src[i*w+dis_pos+j])?0:(1<<15);  //A分量,计算当前颜色和背景色是否一致
            											   //一致则A位设置为0,透明

			p_dst[i*stBitmap->u32Width+j] = (r<<10)+((g>>1)<<5)+b+a;  //转换成RGB1555
			//剩下一个问题,转换为1555后H265输出颜色有点不正常。转换逻辑没问题,应该是内部显示的效果问题
		}
	}
	stBitmap->enPixelFormat = PIXEL_FORMAT_ARGB_1555;
}
  1. 申请空间:

    目标像素格式RGB1555,每个像素2字节。因此需要申请的内存大小为:

    size = 2*w*h

  2. 源图像宽度为奇数时,由于目标图像宽度向上2对齐,直接复制可能会出现斜体字体的情况。

    因此在复制+转换时需要增加一个偏移量。

    参考源代码中的dis_pos变量。

在这里插入图片描述

2.2.4 遇到问题
  1. VO无法叠加1个以上的Region,在同一个通道叠加一个以上的region会报错0xA0038009,查看/dev/logmpp会看到类似如下报错:

    <3>[region] [Func]:rgn_attach_to_chn [Line]:4344 [Info]:chn(mod:15,dev:0,chn:0)'s region_num is too much!
    

    原因是OVERLAYEX在VO上只能叠加1个Region。

    参见

    mpp\out\linux\multi-core\include\hi_defines.h
    
    #define OVERLAYEX_MAX_NUM_VO            1
    

在这里插入图片描述

  1. 图像格式的转换(RGB565 to ARGB1555)

    图像格式的转换可以在SDL_ConvertSurface时实现。参考代码如下:

    //Convert PixelFmt
    SDL_PixelFormat *fmt;
    fmt = (SDL_PixelFormat*)malloc(sizeof(SDL_PixelFormat));
    CHK_ISNULL(fmt,HI_FAILURE);
    memset(fmt,0,sizeof(SDL_PixelFormat));
    fmt->BitsPerPixel = 16;
    fmt->BytesPerPixel = 2;
    fmt->colorkey = 0xffffffff;
    fmt->alpha = 0xff;
    
    fmt->Amask=0x8000; //Alpha位掩码,即16Bit最高位1000 0000 0000 0000
    //fmt->Ashift=15;    
    //fmt->Aloss = 7;
    fmt->Rmask=0x7C00; //RED 位掩码,0111 1100 0000 0000
    //fmt->Rshift=10;
    //fmt->Rloss = 3;
    fmt->Gmask=0x03E0; //Green位掩码,0000 0011 1110 0000
    //fmt->Gshift= 5;
    //fmt->Gloss = 3;
    fmt->Bmask=0x001F; //Blue位掩码,0000 0000 0001 1111
    //fmt->Bshift= 0;
    //fmt->Bloss = 3;
    
    temp = SDL_ConvertSurface(text, fmt, 0);
    

    这样转换出来的Surface就是RGB1555格式的数据了。

2.3 直接更新画布

按照SDK文档,除了使用HI_MPI_RGN_SetBitMap()将BMP图像叠加之外,还可以通过HI_MPI_RGN_GetCanvasInfo()直接获取画布的内存地址并直接对画布的内存数据进行修改,修改完毕后再使用HI_MPI_RGN_UpdateCanvas()刷新画布数据。

该方法的效率比调用HI_MPI_RGN_SetBitMap()高,可以省掉一次内存拷贝。

代码如下:

HI_S32 Sample_SurfaceToCanvas(SDL_Surface *surface,RGN_HANDLE hndl,SDL_Color fntcol)
{
	HI_S32 s32Ret = 0;
	HI_U16 wrd_color = ((fntcol.r >> 3) << 11) + ((fntcol.g >> 2) << 5) + ((fntcol.b >> 3) << 0); //RGB888=>RGB565
	HI_U16 bg_color = 0xffff - wrd_color;
	int dstH = ALIGN_2(surface->h);
	int dstW = ALIGN_2(surface->w);

	int i,j;
	int w = surface->w;
	int h = surface->h;

	RGN_CANVAS_INFO_S cvinfo;
	
	s32Ret = HI_MPI_RGN_GetCanvasInfo(hndl,&cvinfo);  //获取画布信息
	if(s32Ret != HI_SUCCESS)
	{
		printf("HI_MPI_RGN_GetCanvasInfo Failed with 0x%08X\n",s32Ret);
		return s32Ret;
	}
	HI_U16 *p_dst = (HI_U16*)cvinfo.u64VirtAddr;
	HI_U16 *p_src = (HI_U16*)surface->pixels;
	for (i = 0; i < h; ++i)
	{
	    int dis_pos = 0;
	    if(w % 2 != 0)
	    	dis_pos = i;
		for(j=0;j<w;j++)
		{
			int a,r, g , b;
             r = (p_src[i*w+dis_pos+j] & 0xF800) >> 11;
             g = (p_src[i*w+dis_pos+j] & 0x07e0) >> 5;
             b = (p_src[i*w+dis_pos+j] & 0x001f);
             a = (bg_color==p_src[i*w+dis_pos+j])?0:(1<<15);

			p_dst[i*(cvinfo.u32Stride/sizeof(HI_U16))+j] = (r<<10)+((g>>1)<<5)+b+a;  
			/*cvinfo.u32Stride/sizeof(HI_U16):
			* cvinfo.u32Stride是每行首地址之间的字节数
			* 由于p_dst是16Bit的指针,需要把字节数转换成数组元素个数,即cvinfo.u32Stride/sizeof(HI_U16)
			* 图像宽度和stride关系如下:
			*   --------------------
			*   |              |...|
			*   |     PIC      |...|
			*   |              |...|
			*   --------------------
			*   |<-Img.Width-->|
			*   |<---Img.Stride--->|
			*/
		}
	}
	s32Ret = HI_MPI_RGN_UpdateCanvas(hndl);  //更新内存数据到屏幕
	if(s32Ret != HI_SUCCESS)
	{
		printf("HI_MPI_RGN_UpdateCanvas Failed with 0x%08X\n",s32Ret);
		return s32Ret;
	}
	return HI_SUCCESS;
}

代码和2.2.3中代码基本接近,区别在于2.2.3中Surface是先转换成BITMAP_S,再使用HI_MPI_RGN_SetBitMap()将数据叠加到区域。而这里是直接对画布的内存数据进行操作。

这里需要注意的是向画布内存区域写入像素数据的代码:

HI_U16 *p_dst = (HI_U16*)cvinfo.u64VirtAddr;
...
p_dst[i*(cvinfo.u32Stride/sizeof(HI_U16))+j] = (r<<10)+((g>>1)<<5)+b+a;  

u32Stride是各行之间首地址的偏差字节。由于我们是以2Byte的格式访问(p_dst是16位指针),因此需要将字节数转换成数组下标,即cvinfo.u32Stride/sizeof(HI_U16)=cvinfo.u32Stride/2

Image Stride(内存图像行跨度)相关资料节选如下

在这里插入图片描述

参考链接:https://blog.csdn.net/jsn_ze/article/details/55190374

2.4 对OSD内容的动态刷新

动态刷新OSD有两种方式,一个是使用SetBmp更新,一个是获取画布进行更新。

获取画布进行更新的方法比较方便,和2.3的代码一样,通过RGN_HANDLE获取到画布的虚拟地址后直接填充,最后在进行UpdateCanvas操作。

2.5 释放和关闭

结束业务后,需要释放相关资源。

使用HI_MPI_RGN_DetachFromChn()断开region和Channel的链接,再使用HI_MPI_RGN_Destroy()释放Region的内存。

参考代码:

HI_S32 SAMPLE_Destroy_VO_OSD(VO_CHN Vo_Chn, RGN_HANDLE R_Handle)
{
	MPP_CHN_S stChn;
    stChn.enModId = HI_ID_VO;  
    stChn.s32ChnId = Vo_Chn;
	stChn.s32DevId = 0;

	CHECK_RET(HI_MPI_RGN_DetachFromChn(R_Handle,&stChn),"HI_MPI_RGN_DetachFromChn");
	CHECK_RET(HI_MPI_RGN_Destroy(R_Handle),"HI_MPI_RGN_Destroy");

	return HI_SUCCESS;
}

3. 在Region任意位置叠加OSD

由于Region的总数是固定的,因此需要在一块区域内显示多块内容。

这种情况下需要申请一块较大的Region并使用更新Canvas的方式对OSD进行更新。

将ARGB1555的OSD叠加到Region上任意位置的代码如下:

//这里的XY是相对于REGION的左上角
//imgdata可以是转成ARGB1555的Surface.pixels,也可以是转换后的BMP.pdata
//imgW/imgH是图像的实际宽高
//startXY是起始点
HI_S32 Sample_Attach1555ImageToExistedCanvas_XY(RGN_HANDLE hndl,void *imgdata,HI_U32 imgW,
                                                HI_U32 imgH,POINT_S startXY,HI_BOOL bUpdateCanvas)
{
	HI_S32 s32Ret = 0;
	CHK_ISNULL(imgdata,HI_FAILURE);
	HI_U16 *posSrc,*posDes;

	RGN_CANVAS_INFO_S cvinfo;
	s32Ret = HI_MPI_RGN_GetCanvasInfo(hndl,&cvinfo);
	if(s32Ret != HI_SUCCESS)
	{
		printf("HI_MPI_RGN_GetCanvasInfo Failed with 0x%08X\n",s32Ret);
		return s32Ret;
	}

	//检查输入的起始位置是否合法
	if(startXY.s32X > cvinfo.stSize.u32Width || startXY.s32Y > cvinfo.stSize.u32Height)
	{
		HI_MPI_RGN_UpdateCanvas(hndl);   //必须进行Update操作,否则该Canvas在执行其他操作时会报错
		printf("StartXY Exceed. STARTXY(%d,%d),Canvas Size(W %d,H %d)\n",
               startXY.s32X,startXY.s32Y,cvinfo.stSize.u32Width,cvinfo.stSize.u32Height);
		return HI_FAILURE;
	}
	//要复制的图像长宽,如果起始位置合法但是叠加后超出画布范围,则只保留一部分
	HI_U32 copyH = (startXY.s32Y+imgH) < cvinfo.stSize.u32Height ? \
        imgH:cvinfo.stSize.u32Height-startXY.s32Y; 
	HI_U32 copyW = (startXY.s32X+imgW) < cvinfo.stSize.u32Width ? \
        imgW:cvinfo.stSize.u32Height-startXY.s32X;

	//开始复制OSD内容到画布上
	int i,j;
	posSrc = (HI_U16*)imgdata;
	posDes = (HI_U16*)cvinfo.u64VirtAddr;
	for(i = startXY.s32Y;i < startXY.s32Y + copyH;i++)
	{
		memcpy(&(posDes[(cvinfo.u32Stride/2)*i+startXY.s32X]),
                &(posSrc[(i-startXY.s32Y)*(imgW+copyW%2)]),
               2*copyW);
	}
	
	//如果需要更新画布则更新
	if(bUpdateCanvas == HI_TRUE)
	{
		s32Ret = HI_MPI_RGN_UpdateCanvas(hndl);
		if(s32Ret != HI_SUCCESS)
		{
			printf("HI_MPI_RGN_UpdateCanvas Failed with 0x%08X\n",s32Ret);
			return s32Ret;
		}
	}
	
	return HI_SUCCESS;
}

关键步骤代码:

  1. 计算要叠加显示的图像的实际大小:

    //要复制的图像长宽,如果起始位置合法但是叠加后超出画布范围,则只保留一部分
    HI_U32 copyH = (startXY.s32Y+imgH) < cvinfo.stSize.u32Height ? \
        imgH:cvinfo.stSize.u32Height-startXY.s32Y; 
    HI_U32 copyW = (startXY.s32X+imgW) < cvinfo.stSize.u32Width ? \
        imgW:cvinfo.stSize.u32Height-startXY.s32X;
    

    获取到画布大小之后,计算叠加后的图像是否超出画布范围。

    如果超出范围,则只复制(0,0)~(cvinfo.stSize.u32Height-startXY.s32X,cvinfo.stSize.u32Height-startXY.s32Y)的图像。

    不超出范围,复制完整图像。

  2. 将图像叠加到起始位置XY处:

    int i,j;
    posSrc = (HI_U16*)imgdata;
    posDes = (HI_U16*)cvinfo.u64VirtAddr;
    for(i = startXY.s32Y;i < startXY.s32Y + copyH;i++)
    {
        /*复制图解:
    		* ----------------------
    		* |                |...|
    		* |      PIC       |...| P->Padding,存放图像像素之外的数据
    		* |        x---    |.P.| x->起始点,坐标(startXY.s32X,startXY.s32Y),宽高imgW,imgH
    		* |        |**|    |...| 
    		* |        ----    |...|
    		* ----------------------
    		* |<--Img.Width--->|
    		* |<----Img.Stride---->|
    		*
    		* 如图所示,要把图像内容叠加到Canvas的(startXY.s32X,startXY.s32Y)
    		* 就要把图像逐行填充,从startXY.s32X~(startXY.s32X + imgW),填充行数为imgH
    		* 即内存中
    		* (cvinfo.u32Stride/2)*(startXY.s32Y+LineNum)+startXY.s32X 到
    		* (cvinfo.u32Stride/2)*(startXY.s32Y+LineNum)+startXY.s32X + imgW
    		* 其中LineNum是复制的行数
    		*/
        memcpy(&(posDes[(cvinfo.u32Stride/2)*i+startXY.s32X]),
               &(posSrc[(i-startXY.s32Y)*(imgW+copyW%2)]),
               2*copyW);
    }
    
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐