RV的时间戳水印 2
上回书我们说到, rgn相当于一个透明层, 然后attach到venc的某个通道上, 就可以实现水印效果, 这类水印有几种用途,有的跟CCTV电视台台标一样,搞个logo上去, 属于不会动的, 从头到尾在一个固定位置,就行了, 那个很简单, 弄张bmp格式的图片即可, 有一种就是大家经常看到的时间戳, 每秒会刷新, 我这里所做的方法, 比较土鳖, 理论上有更新, 跟优秀的方法, 这里只是抛砖引玉哈.
首先, 思路是, 准备所有的数字跟几个特殊标点符号的bmp图片, 例如从0到9, “-“, “:”等等, 最终用这些东西, 可以组成一个日期的格式, 例如”2023-08-01 15:23”, 或者你喜欢还可以加入中文的星期”一”到”七”, 总之把所有要显示的文字, 都处理为bmp图片, 然后起一个线程, 根据当前的时间, 每秒刷新, 根据组合, 选出需要的图片, 把数据插入到rgn的内存中即可.
首先, 线程:
RK_VOID *add_ts_thread(RK_VOID *p)
{
RK_S32 s32Ret = RK_SUCCESS;
time_t timep;
struct tm *pLocalTime;
RK_U8 seconds = 80; // just for make difference for first load
// RGN_HANDLE Handle;
// Handle = VENC_RECORD_TIME_OSD_HANDLE;
// s32Ret = rgn_add(Handle, VENC_RECORD_TIME_OSD_HANDLE);
while (!quitApp)
{
time(&timep);
pLocalTime = localtime(&timep);
if (seconds == pLocalTime->tm_sec)
{
usleep(100 * 1000);
// usleep(150 * 1000);
continue;
}
else
{
seconds = pLocalTime->tm_sec;
}
s32Ret = rgn_add(VENC_RECORD_TIME_OSD_HANDLE);
if (RK_SUCCESS != s32Ret)
{
printf("RGN_Add line %d failed! s32Ret: 0x%x.\n", __LINE__, s32Ret);
break;
}
}
// pthread_detach(pthread_self());
return NULL;
}
核心的函数就是rgn_add
这个函数其实很简单, 一共分几步:
HI_S32 rgn_add(unsigned int Handle)
{
// printf("-------------------%s add rgn %d --------------------\n",__func__,Type);
HI_S32 s32Ret = HI_SUCCESS;
RGN_ATTR_S stRgnAttrSet;
RGN_CANVAS_INFO_S stCanvasInfo;
memset(&stRgnAttrSet, 0, sizeof(RGN_ATTR_S));
BITMAP_S stBitmap;
memset(&stBitmap, 0, sizeof(BITMAP_S));
SIZE_S stSize;
/* Photo logo */
s32Ret = RK_MPI_RGN_GetAttr(Handle /*VencOsdHandle*/, &stRgnAttrSet);
if (HI_SUCCESS != s32Ret)
{
printf("HI_MPI_RGN_GetAttr failed! s32Ret: 0x%x.\n", s32Ret);
return s32Ret;
}
s32Ret = RK_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 = reinterpret_cast<void *>(stCanvasInfo.u64VirAddr);
// stBitmap.pData = (void *)stCanvasInfo.u64VirAddr; // u64VirAddr
stSize.u32Width = stCanvasInfo.stSize.u32Width;
stSize.u32Height = stCanvasInfo.stSize.u32Height;
RK_U32 canvasHeight = stCanvasInfo.stSize.u32Height;
RK_U32 canvasWidth = stCanvasInfo.stSize.u32Width;
// printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>> canvas.u32Width:%d canvas.u32Height:%d \n", stSize.u32Width, stSize.u32Height);
s32Ret = update_canvas(&stBitmap, 0x0000, canvasHeight, canvasWidth);
// s32Ret = update_canvas(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 = RK_MPI_RGN_UpdateCanvas(Handle /*VencOsdHandle*/);
if (HI_SUCCESS != s32Ret)
{
printf("RK_MPI_RGN_UpdateCanvas failed! s32Ret: 0x%x.\n", s32Ret);
return s32Ret;
}
return HI_SUCCESS;
}
根据上回书写的使用步骤(截图自mpi的文档):
RK_MPI_RGN_GetAttr 拿到画布的配置属性, 如果你知道画布的信息, 这一步压根没用, 因为无非是画布的长款格式, 这些都是你之前自己设置好的.
RK_MPI_RGN_GetCanvasInfo 获取画布的信息, 这一步是拿到画布在内存中的(虚拟)地址, 这一步很重要, 因为你需要把数据写到这个地址中去, 才能显示出来.
RK_MPI_RGN_UpdateCanvas 即刷新画布, 就是显示.
关键是中间这个填充数据的函数update_canvas(&stBitmap, 0x0000, canvasHeight, canvasWidth);
static HI_S32 update_canvas(BITMAP_S *pstBitmap, HI_U32 u16FilColor, RK_U32 canvasHeight, RK_U32 canvasWidth)
参数分别是画布的虚拟起始地址, 填充的颜色, 直接就是全0, 画布的高跟宽.
static HI_S32 update_canvas(BITMAP_S *pstBitmap, HI_U32 u16FilColor, RK_U32 canvasHeight, RK_U32 canvasWidth)
{
HI_S32 nRet = 0;
OSD_SURFACE_S surface;
OSD_LOGO_T stLogo;
if (NULL == pstBitmap->pData)
{
printf("malloc osd memroy err!\n");
return HI_FAILURE;
}
memset(&stLogo, 0, sizeof(OSD_LOGO_T));
stLogo.pRGBBuffer = (RK_U8 *)pstBitmap->pData;
stLogo.width = canvasWidth;
stLogo.height = canvasHeight;
// BRGBA8888每像素字节数为4
stLogo.stride = canvasWidth * 4;
这里指的注意的就是, stride, 其实是画布每一行的字节数, 例如如果你宽度为200的画布, 因为是RGBA8888色彩空间, 所以每个像素是4个字节, 所以rgn每一行的数据为宽度x4.
nRet = RGN_LoadBMPCanvas_TimeStamp(&stLogo);
if (nRet != HI_SUCCESS)
{
printf("OSD_LoadBMP error!\n");
return -1;
}
接着使用RGN_LoadBMPCanvas_TimeStamp 函数填入数据.
通过一个函数拿到系统的时间:
pLocalTime = localtime(&timep);
// 图片尺寸16*24 因为有18个字符, 所以总宽度是16*18=288
for (charIdx = 0; charIdx < TIME_STAMP_WATER_MARK_STR_LEN; charIdx++)
{
首先, 遍历这个时间戳的字符串, 例如我最终要的是2023-08-01 15:23, 这种格式, 那么第一个数字, 就是年份中最大的那个数字, 拍第二的就是年份中第二的数字, 以此类推, 然后通过数字, 找到对应的图片名字, 然后读取图片:
准备好类似上面的若干图片, 内容就是数字或者一些符号.
filename = get_filename_by_idx(charIdx, pLocalTime);
if (NULL == filename)
{
printf("OSD_LoadBMP: filename=NULL\n");
return -1;
}
// printf("bmp filename: is %s \n", filename);
// 获取bmp图片信息, bmp文件头和bmp信息头
if (get_bmp_info(filename, &bmpFileHeader, &bmpInfo) < 0)
{
printf("get_bmp_info error!\n");
return -1;
}
之所谓获取bmp文件的头, 是因为bmp文件并不是一个纯粹的数据就完了, bmp有个文件头, 里面保存了很多图片相关的信息, 真正的数据是从比如56个字节的offset位置开始的, 而且行排列时从下往上的, 具体的可以wiki上看看bmp文件格式.
bmpWidth = (HI_U16)bmpInfo.bmiHeader.biWidth;
bmpHeight = (HI_U16)((bmpInfo.bmiHeader.biHeight > 0) ? bmpInfo.bmiHeader.biHeight : (-bmpInfo.bmiHeader.biHeight));
bmpStride = get_bmp_stride(bmpWidth, bytesPerPixel);
// printf("filename: %s stride: %d w: %d, h: %d \n", filename, stride, w, h);
RK_U32 bmpPicSize = bmpHeight * bmpStride;
/* RGB8888 or RGB1555 */
// 申请内存用于保存每个bmp原始图像数据, 即4*长宽高
pOrigBMPBuf = (HI_U8 *)malloc(bmpPicSize);
if (NULL == pOrigBMPBuf)
{
printf("not enough memory to malloc!\n");
fclose(pFile);
return -1;
}
你自己肯定知道图片的大小跟宽高, 不过要注意的是, 图片并不是都一样宽高的, 有些字符会窄一点,比如”:”比”-“就要窄,
fseek(pFile, bmpFileHeader.bfOffBits, 0);
// 将每张图片数据读到pOrigBMPBuf里面
if (fread(pOrigBMPBuf, 1, bmpPicSize, pFile) != (bmpPicSize))
{
perror("fread error!");
goto _FAILED;
}
调过偏移部分, 读取文件内容
void fill_bitmap_buf(HI_U8 *pRGBBuf, HI_U16 bytesPerPixel, HI_U32 bmpWidth, HI_U32 bmpHeight, HI_U8 *pStart, HI_U16 *pDst,
OSD_LOGO_T *pVideoLogo, HI_U8 *pOrigBMPBuf, HI_U32 stride)
{
const static HI_U8 spa = 16;
HI_U16 i, j;
// HI_U8 r, g, b;
// printf("fill_bitmap_buf >>>>>>>>>>>>>>>>>>>>>> bmpWidth:%d, bmpHeight:%d, stride:%d\n", bmpWidth, bmpHeight, stride);
// printf("pRGBBuf: %p, pOrigBMPBuf: %p\n", pRGBBuf, pOrigBMPBuf);
for (i = 0; i < bmpHeight; i++) // Bpp:3,enFmt:4
{
// printf("k:%d,i:%d,j:%d,h:%d,w:%d \n", k, i, j, h, w);
for (j = 0; j < bmpWidth; j++)
{
memcpy(pRGBBuf + i * pVideoLogo->stride + j * 4, pOrigBMPBuf + ((bmpHeight - 1) - i) * stride + j * bytesPerPixel, bytesPerPixel);
*(pRGBBuf + i * pVideoLogo->stride + j * 4 + 3) = WATER_MARK_ALPHA; /*alpha*/
// 如果是白色, 就改成透明色
if (0xff == *(pRGBBuf + i * pVideoLogo->stride + j * 4) && 0xff == *(pRGBBuf + i * pVideoLogo->stride + j * 4 + 1) && 0xff == *(pRGBBuf + i * pVideoLogo->stride + j * 4 + 2))
{
*(pRGBBuf + i * pVideoLogo->stride + j * 4 + 3) = 0x00; /*alpha*/
}
}
}
}
挨个字符的调用, 填充内存.
fill_bitmap_buf(pRGBBuf, bytesPerPixel, bmpWidth, bmpHeight, pStart, pDst, pVideoLogo, pOrigBMPBuf, bmpStride);
pRGBBuf += bmpWidth * 4;
用一张图来说明一下rgn内存中图片填充过程:
理解一下这几个概念, 就是, 首先rgn的画布必须要足够大, 不然就会无法显示全部的字符(这就是废话), 大了没事, 但是不能小.
然后就是rgn的内存, 肯定是绿色框中的部分, 每一行的大小, 就是rgn的宽度x4, 因为是RGBA8888, 所以是4字节.
接着, 你是一个一个字符的填入数据的, 而你的每个字符的行宽, 都肯定小于rgn的行宽, 好像又是废话, 所以这里你肯定是, 写一段数据, 然后跳过后面的部分, 再写入, 再跳过.
所以你把读出来的bmp图像(RGB格式24)读出来之后, 第一要加一个Alpha通道的值, 即透明度, 这个会影响你整个字符的透明度, 一个就是要及时换行, 这里其实就要把rgn的行宽代入进去, 不然你是没法这样一行行填入数据的.
就像我想在这个绿色框中, 填入红色的一块:
我只能一行行的填入, 先填一行
然后跳过红色后面的绿色框的内存字节数量, 再写入第二行
就这样完成一个字符, 然后再从第二个字符的内存位置, 第二个字符
就这么一点点的难点, 其他都不是太大问题.
代码已经上传到github, 是基于1106的.
https://github.com/MontaukLaw/rv1106
- 分享
- 举报
-
dmmonstr 2024-09-12 14:10:27回复 举报学到了
-
眼里有星河ღ᭄࿐ 2023-09-06 13:10:27回复 举报学习学习
-
automan 2023-09-05 15:47:43回复 举报只是为了抢红包的
-
tianshanyi 2023-09-05 15:43:26回复 举报不错不错
-
易百纳技术社区 2023-09-05 15:33:3910.00元回复 举报优秀的文章必须支持一下
-
么得钱钱 2023-08-16 13:44:16回复 举报感谢分享
-
david 2023-08-15 19:55:33回复 举报感谢分享
-
冬了个冬 2023-08-14 09:00:49回复 举报感谢分享
-
浏览量:1471次2023-07-30 08:53:58
-
浏览量:4585次2019-09-01 11:15:39
-
浏览量:1252次2023-12-05 13:41:24
-
浏览量:7051次2021-05-20 16:37:42
-
浏览量:638次2023-09-01 18:13:05
-
浏览量:1292次2023-01-31 10:06:39
-
浏览量:974次2024-01-19 17:52:40
-
浏览量:690次2024-01-11 15:05:58
-
浏览量:1387次2023-04-14 11:48:45
-
浏览量:1064次2023-04-14 11:21:17
-
浏览量:1210次2024-01-19 17:57:59
-
浏览量:1537次2023-12-29 17:51:55
-
浏览量:10406次2020-11-24 23:10:14
-
浏览量:7607次2020-12-06 00:56:08
-
2023-04-07 15:12:43
-
浏览量:2530次2022-03-05 09:18:25
-
浏览量:1747次2023-02-25 23:00:00
-
浏览量:766次2023-11-23 13:38:54
-
2024-01-22 16:35:19
-
广告/SPAM
-
恶意灌水
-
违规内容
-
文不对题
-
重复发帖
Marc
感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~
举报类型
- 内容涉黄/赌/毒
- 内容侵权/抄袭
- 政治相关
- 涉嫌广告
- 侮辱谩骂
- 其他
详细说明