RV的时间戳水印 2

RV的时间戳水印 2 Marc 2023-08-14 08:41:00 1940

上回书我们说到, 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

声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
Marc
红包 10 6 评论 打赏
评论
8个
内容存在敏感词
手气红包
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
Marc
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区