OSD实时刷新时间

OSD实时刷新时间

源码链接:https://gitee.com/kidwjb/codec_video_audio

海思的OSD(On-Screen Display)部分叫做区域管理,用户一般都需要在视频中叠加OSD用于显示一些特定的信息、(如:通道号、时间戳等),必要时还会填充色块。这些叠加在视频上的OSD和遮挡在视频上的色块统称为区域。

海思mpp使用REGION模块,用于统一管理这些区域资源。

笔者使用的是hi3516,视频流通过VPSS分为两路,一路进入VENC编码,另一路用于屏幕显示。最开始想的是能够在VPSS上使用区域管理从而在编码视频和屏幕上都显示OSD,但是后续查阅海思mpp手册发现只有hi3519支持VPSS使用OVERLAY功能(见下说明)

Snipaste20260204103424png
Snipaste20260204103538png

区域类型

下面首先介绍常见区域类型:

  • OVERLAY:视频叠加区域,其中区域支持位图的加载、背景色更新等功能。
  • OVERLAYEX:扩展视频叠加区域,功能与OVERLAY类似,支持位图加载、背 景色更新等。
  • COVER:视频遮挡区域,其中区域支持纯色块遮挡。
  • COVEREX:扩展视频遮挡区域,功能与COVER类似,支持纯色块遮挡。
  • LINEEX:扩展的线条视频叠加区域,支持颜色、粗细等调节。
  • MOSAIC:马赛克遮挡区域,支持精度调节。
  • MOSAICEX:扩展的马赛克遮挡区域,支持精度调节。
  • CORNER_RECT:角框区域,支持大小,颜色,粗细等调节。
  • CORNER_RECTEX:扩展的角框区域,支持大小,颜色,粗细等调节

对于想要显示时间的情形,需要选择OVERLAYOVERLAYEX

使用流程

使用过程包含以下步骤: ● 用户填充区域属性并创建区域。 ● 将该区域指定到具体通道中(如VENC)。在指定到具体通道时,需要输入通道的显 示属性。 以上步骤完成区域的创建和使用。用户还可以通过以下操作来控制区域属性以及在某 通道的通道显示属性。 ● 通过ss_mpi_rgn_get_attrss_mpi_rgn_set_attr获取和设置区域属性。 ● 通过ss_mpi_rgn_set_bmp(仅针对OVERLAY、OVERLAYEX)设置区域的位图信 息。 ● 通过ss_mpi_rgn_get_chn_display_attrss_mpi_rgn_set_chn_display_attr获 取和设置区域在某通道(如VENC通道)的通道显示属性。 ● 最后用户可以将该区域从通道中撤出(非必须操作),再销毁区域。

具体操作

关于region的具体示例可以参考海思mpp源码中的region文件夹,下面的源码为sample_region.c

1.创建region

hi_s32 sample_comm_region_create(hi_s32 handle_num, hi_rgn_type type)

该接口为海思封装的上层接口,只需要提供区域句柄数量以及区域类型即可

比如创建一个区域,则handle_num为1,创建一个OVERLAY区域则typeHI_RGN_OVERLAY

2.设置区域属性

hi_s32 sample_comm_region_attach(hi_s32 handle_num, hi_rgn_type type, hi_mpp_chn *mpp_chn, region_op_flag op_flag)

该接口为海思封装的上层接口,用于设置区域的属性,其中最重要的参数hi_mpp_chn

,这是用于将区域与某一个模块通道进行绑定的结构体,参考如下:

    chn.mod_id = HI_ID_VENC;
    chn.dev_id = 0;
    chn.chn_id = 0;

3.设置位图

● 位图填充(针对OVERLAY和OVERLAYEX有效) 位图填充是指将位图的内存值填充到区域内存空间中,位图将会从区域的左上角开始填充。当位图小于区域时,只能填充一部分内存,剩余部分保持原有值或使用背景色进行填充;位图大小等于区域时,将刚好全部填充;当位图大于区域时,位图只能将自身和区域一样大小的内存信息填充到区域中。

位图填充支持两种实现方式:

  • 其一、用户通过ss_mpi_rgn_set_bmp接口将位图数据拷贝至内部显示画布;
  • 其二、用户通过ss_mpi_rgn_get_canvas_info获取内部备份显示画布的地 址,直接对该地址数据进行更新,然后调用ss_mpi_rgn_update_canvas接 口将备份显示画布更新为待显示画布,达到实现更新位图数据的目的。

这里笔者选择ss_mpi_rgn_set_bmp接口,更加方便直接

显示图像

该接口也有上层封装,可以支持用户直接输入bmp图片并显示

hi_s32 sample_comm_region_set_bit_map(hi_rgn_handle handle, const hi_char *bmp_path)

通过该接口,可以直接将bmp_path路径的图片显示到region区域上,效果如下图(图片为海思官方mpp示例提供的图片😆)

Snipaste20260204111643png

显示时间

但是如果想要显示时间的话肯定不能像上述做法,如果一帧一帧导入图像那样太浪费资源了,于是我们需要手动生成0~9以及特殊字符的ARGB1555格式(这是因为目前笔者使用的接口内部默认初始化handle=0HI_PIXEL_FORMAT_ARGB_1555

Snipaste20260204112228png

生成点阵如下:

// ========================
// 8x16 ASCII 点阵字体(仅支持 0-9, :, -, space)
// ========================
static const unsigned char g_font_8x16_digits[][16] = {
    {0x7C,0x82,0x82,0x82,0x82,0x82,0x82,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // '0'
    {0x08,0x18,0x38,0x08,0x08,0x08,0x08,0x3C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // '1'
    {0x7C,0x82,0x02,0x02,0x7C,0x80,0x80,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // '2'
    {0x7C,0x82,0x02,0x02,0x3C,0x02,0x02,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // '3'
    {0x04,0x0C,0x1C,0x2C,0x4C,0xFE,0x0C,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // '4'
    {0xFE,0x80,0x80,0x80,0xFC,0x02,0x02,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // '5'
    {0x3C,0x42,0x80,0x80,0xFC,0x82,0x82,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // '6'
    {0xFE,0x02,0x02,0x04,0x08,0x10,0x10,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // '7'
    {0x7C,0x82,0x82,0x82,0x7C,0x82,0x82,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // '8'
    {0x7C,0x82,0x82,0x82,0x7E,0x02,0x02,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}  // '9'
};

static const unsigned char g_font_colon[16] = {
    0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00
};

static const unsigned char g_font_dash[16] = {
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0x00,0x00,0x00,0x00,0x00
};

static const unsigned char g_font_space[16] = {
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};

在这之后我们需要将字符串渲染并填充到hi_bmp结构体中,渲染可用笔者如下函数:

// 渲染单个字符到位图缓冲区,支持缩放
static void draw_char_scaled(unsigned short *buffer, int stride, int x, int y, 
                             unsigned char c, unsigned short color, int scale) {
    const unsigned char *font_data = NULL;
    if (c >= '0' && c <= '9') {
        font_data = g_font_8x16_digits[c - '0'];
    } else if (c == ':') {
        font_data = g_font_colon;
    } else if (c == '-') {
        font_data = g_font_dash;
    } else if (c == ' ') {
        font_data = g_font_space;
    } else {
        return;
    }

    // 渲染放大后的字符
    for (int row = 0; row < 16; row++) {
        unsigned char bits = font_data[row];
        for (int col = 0; col < 8; col++) {
            if (bits & (0x80 >> col)) {
                // 每个像素放大 scale 倍
                for (int sy = 0; sy < scale; sy++) {
                    for (int sx = 0; sx < scale; sx++) {
                        int px = x + col * scale + sx;
                        int py = y + row * scale + sy;
                        // 移除 OSD_HEIGHT 的硬编码限制
                        if (px >= 0 && px < stride && py >= 0) {
                            buffer[py * stride + px] = color;
                        }
                    }
                }
            }
        }
    }
}

在函数参数设置一个缩放参数函数,便于调整字体大小

然后填充至hi_bmp结构体并调用刷新接口即可

//  渲染字符串
    int x = start_x;
    for (int i = 0; str[i]; i++) {
        if (x + char_width * scale > region_width) {
            printf("[OSD] Warning: Text truncated at char %d\n", i);
            break;
        }
        draw_char_scaled(argb_buffer, region_width, x, start_y, str[i], color_value, scale);
        x += (char_width + char_spacing) * scale;
    }

    //  设置位图
    hi_bmp bitmap;
    bitmap.data = (hi_void *)argb_buffer;
    bitmap.height = region_height;
    bitmap.width = region_width;
    bitmap.pixel_format = pixel_fmt;

    ret = hi_mpi_rgn_set_bmp(handle, &bitmap);

最终效果如下:

Snipaste20260204113045png

实时刷新

想要实时刷新就需要每隔一秒获取时间戳,那么就需要while循环操作,为了实现OSD模块与主线程的解耦隔离,OSD模块提供一个刷新一次时间的接口,主线程只需要循环调用传入时间戳即可

hi_s32 osdShowTime(hi_s32 handle,hi_char *str,hi_s32 str_len)

region属性修改

对于region属性的修改可以在初始化函数内部修改,在sample_comm_region.c内的

sample_region_create_overlay函数中:

        region.attr.overlay.pixel_format = sample_comm_region_get_pixel_format_by_handle(i - min_handle);
        //设置region的大小
        region.attr.overlay.size.width = sample_region_get_overlay_default_width(region.attr.overlay.pixel_format);
        region.attr.overlay.size.height = sample_region_get_overlay_default_height(region.attr.overlay.pixel_format);
        //设置region的颜色
        //region.attr.overlay.bg_color = sample_region_get_overlay_bg_color(region.attr.overlay.pixel_format);
        region.attr.overlay.bg_color = RGN_COLOR;
        ret = hi_mpi_rgn_create(i, &region);
        if (ret != HI_SUCCESS) {
            sample_print("hi_mpi_rgn_create failed with %#x!\n", ret);
            return HI_FAILURE;
        }
上一篇
下一篇