
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功能(见下说明)


区域类型
下面首先介绍常见区域类型:
- OVERLAY:视频叠加区域,其中区域支持位图的加载、背景色更新等功能。
- OVERLAYEX:扩展视频叠加区域,功能与OVERLAY类似,支持位图加载、背 景色更新等。
- COVER:视频遮挡区域,其中区域支持纯色块遮挡。
- COVEREX:扩展视频遮挡区域,功能与COVER类似,支持纯色块遮挡。
- LINEEX:扩展的线条视频叠加区域,支持颜色、粗细等调节。
- MOSAIC:马赛克遮挡区域,支持精度调节。
- MOSAICEX:扩展的马赛克遮挡区域,支持精度调节。
- CORNER_RECT:角框区域,支持大小,颜色,粗细等调节。
- CORNER_RECTEX:扩展的角框区域,支持大小,颜色,粗细等调节
对于想要显示时间的情形,需要选择OVERLAY或OVERLAYEX
使用流程
使用过程包含以下步骤: ● 用户填充区域属性并创建区域。 ● 将该区域指定到具体通道中(如VENC)。在指定到具体通道时,需要输入通道的显 示属性。 以上步骤完成区域的创建和使用。用户还可以通过以下操作来控制区域属性以及在某 通道的通道显示属性。 ● 通过ss_mpi_rgn_get_attr、ss_mpi_rgn_set_attr获取和设置区域属性。 ● 通过ss_mpi_rgn_set_bmp(仅针对OVERLAY、OVERLAYEX)设置区域的位图信 息。 ● 通过ss_mpi_rgn_get_chn_display_attr和ss_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区域则type为HI_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示例提供的图片😆)

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

生成点阵如下:
// ========================
// 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);
最终效果如下:

实时刷新
想要实时刷新就需要每隔一秒获取时间戳,那么就需要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, ®ion);
if (ret != HI_SUCCESS) {
sample_print("hi_mpi_rgn_create failed with %#x!\n", ret);
return HI_FAILURE;
}