(一)基本概念理解
(1)Overlay疊加
視頻疊加區域,其中區域支持位圖的加載、背景色更新等功能,簡單理解就是可以設置透明度,也就是下面的Alpha值
(2)Cover遮擋
視頻遮擋區域,其中區域支持純色塊遮擋,與Overlay疊加不同的是它不能加載圖片,不能設置透明度
(3)Alpha通道
如果圖形卡具有32位總線,附加的8位信號就被用來保存不可見的透明度信號以方便處理用,這就是Alpha通道。白色的alpha象素用以定義不透明的彩色象素,而黑色的alpha象素用以定義透明象素,黑白之間的灰階用來定義半透明象素。
- VPSS OVERLAY時,Alpha取值範圍爲[0, 255]。取值越小,越透明。
- VPSS VENC 時,Alpha取值範圍爲[0, 127]。取值越小,越透明。
(4)Stride圖像跨距
Image Stride(內存圖像行跨度) 當視頻圖像存儲在內存時,圖像的每一行末尾也許包含一些擴展的內容,這些擴展的內容隻影響圖像如何存儲在內存中,但是不影響圖像如何顯示出來;Stride 就是這些擴展內容的名稱,Stride 也被稱作 Pitch,如果圖像的每一行像素末尾擁有擴展內容,Stride 的值一定大於圖像的寬度值,就像下圖所示:
兩個緩衝區包含同樣大小(寬度和高度)的視頻幀,卻不一定擁有同樣的 Stride 值,如果你處理一個視頻幀,你必須在計算的時候把 Stride 考慮進去;
在做OSD水印的時候,疊加圖片的stride值大於region畫布的寬度時,該圖像添加到畫布會失敗。
(二)像素格式:
- OVERLAY VENC類型支持:Argb1555,Argb4444
- OVERLAY VPSS類型支持:Argb1555,Argb4444,Argb8888
ARGB---Alpha,Red,Green,Blue.一種色彩模式,也就是RGB色彩模式附加上Alpha(透明度)通道,常見於32位位圖的存儲結構。
(a)格式類型
ALPHA_8:數字爲8,圖形參數應該由一個字節來表示,應該是一種8位的位圖,常見的顏色格式:
- ARGB_4444:4+4+4+4=16,圖形的參數應該由兩個字節來表示,應該是一種16位的位圖.
- ARGB_8888:8+8+8+8=32,圖形的參數應該由四個字節來表示,應該是一種32位的位圖.
- RGB_565:5+6+5=16,圖形的參數應該由兩個字節來表示,應該是一種16位的位圖.
Argb1555 也就是15位表示透明度和分別使用5位表示R,G,B,構成一個32位的位圖,其中有兩個位沒有使用到。(b)顏色格式:
顏色對照表可以查看:https://tool.oschina.net/commons?type=3
同樣是RGB顏色,但是顏色格式卻有很多種,所以查表得到的顏色與顯示的顏色是不能對應的,需要轉換或是直接對應格式查找。
(c)顏色轉換
三種RGB格式表示方式:
- RGB555: R-5bit,G-5bit,B-5bit
- RGB565: R-5bit,G-6bit,B-5bit
- RGB888: R-8bit,G-8bit,B-8bit
RGB888轉RGB555
RGB888 : R7 R6 R5 R4 R3 R2 R1 R0 G7 G6 G5 G4 G3 G2 G1 G0 B7 B6 B5 B4 B3 B2 B1 B0
RGB555 :0 R7 R6 R5 R4 R3 G7 G6 G5 G4 G3 B7 B6 B5 B4 B3
其它格式於此類似。
(三)實時刷新OSD圖像
在海思官方提供的region sample中,它們使用的是現成的圖片,也就是直接拿現成的圖片加載到視頻流中去,這樣有一個問題,就是如果我需要實時改變OSD的內容,這個就不好處理了。 比如在視頻中添加時間水印。
以時間水印爲例,要實現將時間水印添加到視頻流流中去,大的流程只有兩個:
- 生成帶帶時間的圖像
- 將時間圖像加載到預設置的region畫布中去
(1)生成時間位圖:
這裏需要使用到freetype、SDL、SDl_ttf這三個庫。
- FreeType2是一個簡單的跨平臺的字體繪製引擎
- SDL(Simple DirectMedia Layer)是一套開放源代碼的跨平臺多媒體開發庫,使用C語言寫成。SDL提供了數種控制圖像、聲音、輸出入的函數,讓開發者只要用相同或是相似的代碼就可以開發出跨多個平臺(Linux、Windows、Mac OS X等)的應用軟件。
- SDL_ttf是TrueType字體渲染庫,使用SDL庫,幾乎一樣的便攜。這取決於FreeType2處理TrueType字體數據。它允許程序員使用多個TrueType字體無需代碼的字體渲染程序本身。隨着輪廓字體和反走樣的力量,高質量的文本輸出可以毫不費力的獲得。
需要將這三個庫移植到海思設備中去,交叉編譯移植過程這裏不介紹,網上有很多介紹。
這裏提供一個簡單的測試程序:
/************************************************************
*Copyright (C),lcb0281at163.com lcb0281atgmail.com
*BlogAddr: caibiao-lee.blog.csdn.net
*FileName: debug_font_osd.c
*Description:測試生成帶時間字符的圖像
*Date: 2020-02-03
*Author: Caibiao Lee
*Version: V1.0
*Others:
*History:
***********************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "SDL/SDL.h"
#include "SDL/SDL_ttf.h"
#define FONT_PATH "./font/hisi_osd.ttf"
int string_to_bmp(char *pu8Str)
{
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(FONT_PATH, 80);
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, pu8Str, 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");
SDL_FreeSurface(text);
SDL_FreeSurface(temp);
TTF_CloseFont(font);
TTF_Quit();
return 0;
}
工程中包含下面些內容:
biao@ubuntu:~/test/github/hisi_sdk_develop/freetype_SDL_Dl_ttf_debug$ tree -L 2
.
├── bin
│ └── objs
├── debug_font_osd.c
├── debug_font_osd.h
├── font
│ ├── hisi_osd.ttf
│ └── hisi_osd.ttf_df
├── inc
│ ├── freetype2
│ ├── ft2build.h
│ └── SDL
├── lib
│ ├── libfreetype.a
│ ├── libfreetype.so
│ ├── libfreetype.so.6
│ ├── libSDL-1.2.so.0
│ ├── libSDL.a
│ ├── libSDLmain.a
│ ├── libSDL.so
│ ├── libSDL_ttf-2.0.so.0
│ ├── libSDL_ttf.a
│ ├── libSDL_ttf.so
│ └── pkgconfig
├── Makefile
├── save.bmp
└── test
8 directories, 18 files
biao@ubuntu:~/test/github/hisi_sdk_develop/freetype_SDL_Dl_ttf_debug$
生成保存的save.bmp圖像如下:
(2)將字符水印添加到視頻流中:
這裏是根據官方sample修改而來,主要流程是:
- 將解碼器與編碼器綁定,區域通道與編碼通道綁定,從h264文件中讀取數據流,輸入到解碼器中,由解碼器中流向編碼器,最後將編碼器產生的數據存成文件。
編碼之後的圖像帶有區域圖像的水印,這裏可以根據實際的分辨率設置VENC和VDEC。
/*************************************************
Function: BIAO_RGN_AddOsdToVenc
Description: 將視頻文件添加時間水印
Input: none
OutPut: none
Return: 0: success,none 0:error
Others: 解碼器輸入的分辨率與編碼器的輸出分辨率可以不相同,
比如將1080P圖像解碼後,可以再編碼成720P圖像。
Author: Caibiao Lee
Date: 2020-03-08
*************************************************/
HI_S32 BIAO_RGN_AddOsdToVenc(HI_VOID)
{
HI_S32 s32Ret = HI_SUCCESS;
RGN_HANDLE OverlayHandle;
HI_S32 u32OverlayRgnNum;
MPP_CHN_S stSrcChn, stDesChn;
RGN_ATTR_S stRgnAttrSet;
RGN_CANVAS_INFO_S stCanvasInfo;
BITMAP_S stBitmap;
VENC_CHN VencChn;
VDEC_CHN VdecChn;
VDEC_SENDPARAM_S stVdesSendPram;
VENC_PTHREAD_INFO_S stVencGetPram;
SIZE_S stSize;
FILE * pastream = NULL;
HI_U32 i;
int l_s32CanvasHandle = 0;
/**分配緩存**/
s32Ret = BIAO_RGN_SYS_Init();
if(HI_SUCCESS != s32Ret)
{
printf("SAMPLE_RGN_SYS_Init failed! s32Ret: 0x%x.\n", s32Ret);
goto END_O_VENC0;
}
/**創建區域,並將它添加到編碼通道**/
OverlayHandle = 0;
u32OverlayRgnNum = 1;
s32Ret = BIAO_RGN_CreateOverlayForVenc(OverlayHandle, u32OverlayRgnNum);
if(HI_SUCCESS != s32Ret)
{
printf("SAMPLE_RGN_CreateOverlayForVenc failed! s32Ret: 0x%x.\n", s32Ret);
goto END_O_VENC1;
}
/**開啓解碼通道**/
VdecChn = 0;
s32Ret = BIAO_RGN_StartVdec(VdecChn);
if(HI_SUCCESS != s32Ret)
{
printf("SAMPLE_RGN_StartVdec failed! s32Ret: 0x%x.\n", s32Ret);
goto END_O_VENC2;
}
/**開啓編碼通道**/
VencChn = 0;
s32Ret = BIAO_RGN_StartVenc(VencChn);
if(HI_SUCCESS != s32Ret)
{
printf("SAMPLE_RGN_StartVenc failed! s32Ret: 0x%x.\n", s32Ret);
goto END_O_VENC3;
}
/**將解碼通道綁定到編碼通道**/
stSrcChn.enModId = HI_ID_VDEC;
stSrcChn.s32DevId = 0;
stSrcChn.s32ChnId = 0;
stDesChn.enModId = HI_ID_VENC;
stDesChn.s32DevId = 0;
stDesChn.s32ChnId = 0;
s32Ret = HI_MPI_SYS_Bind(&stSrcChn, &stDesChn);
if(HI_SUCCESS != s32Ret)
{
printf("HI_MPI_SYS_Bind failed! s32Ret: 0x%x.\n", s32Ret);
goto END_O_VENC4;
}
/**創建一個線程用來從h264文件中讀取數據,模擬h264數據流**/
stSize.u32Width = DECODE_VIDEO_W;
stSize.u32Height = DECODE_VIDEO_H;
stVdesSendPram.bRun = HI_TRUE;
stVdesSendPram.VdChn = VdecChn;
stVdesSendPram.enPayload = PT_H264;
stVdesSendPram.enVideoMode = VIDEO_MODE_FRAME;
stVdesSendPram.s32MinBufSize = stSize.u32Height * stSize.u32Width / 2;
pthread_create(&g_stVdecThread, NULL, BIAO_RGN_VdecSendStream, (HI_VOID*)&stVdesSendPram);
/**更新OSD內容**/
l_s32CanvasHandle = 0;
pthread_create(&g_stRgnOsdThread, NULL, BIAO_UpdateCanvas, (HI_VOID*)&l_s32CanvasHandle);
/**創建一個線程,將編碼器輸出的數據存成文件**/
char pfilename[64];
sprintf(pfilename, ENCODE_H264_FILE);
pastream = fopen(pfilename, "wb");
HI_ASSERT( NULL != pastream);
stVencGetPram.pstream = pastream;
stVencGetPram.VeChnId = VencChn;
stVencGetPram.s32FrmCnt = 0;
pthread_create(&g_stVencThread, 0, BIAO_RGN_VencGetStream, (HI_VOID *)&stVencGetPram);
printf("\n#############Sample start ok! Press Enter to switch!#############\n");
/*************************************************
step 8: stop thread and release all the resource
*************************************************/
/**延時之後推出編解碼**/
sleep(10);
bExit = HI_TRUE;
pthread_join(g_stVdecThread, 0);
pthread_join(g_stVencThread, 0);
pthread_join(g_stRgnOsdThread, 0);
bExit = HI_FALSE;
END_O_VENC4:
HI_MPI_SYS_UnBind(&stSrcChn, &stDesChn);
END_O_VENC3:
BIAO_RGN_StopVenc(VencChn);
END_O_VENC2:
BIAO_RGN_StopVdec(VdecChn);
END_O_VENC1:
BIAO_RGN_DestroyRegion(OverlayHandle, u32OverlayRgnNum);
END_O_VENC0:
SAMPLE_COMM_SYS_Exit();
return s32Ret;
}
將海思官方視頻添加水印之後的視頻效果如下:
第一個生成時間圖像的工程可以從下面獲取:
GitHub: freetype_SDL_Dl_ttf_debug
CSDN : freetype_SDL_Dl_ttf_debug.tar.gz
第二個將水印疊加到視頻測工程可以從「目錄與序言」提供的地址去獲取
本專欄第一篇文章「目錄與序言」列出了專欄的完整目錄,按目錄順序閱讀,有助於你的理解。