Hi3519DV500-OSD
OSD概念
OSD(On-Screen Display)的字面意思是“屏幕显示”,在视频处理中,它泛指叠加在视频画面上用于显示特定信息的图形或文字,例如通道号、时间戳、设备Logo等。这里的添加是指软件制作好的画布添加到板子端,当视频流到达上位机的时候就带好了这些信息,不是在上位机进行处理添加进去的。
在Hi3519DV500的实现中,这个概念被统一并扩展了:所有叠加在视频上的OSD内容,以及用于遮挡视频的纯色块,都被统称为“区域”(Region)。芯片的REGION模块就是用来统一管理这些区域资源的,负责区域的创建、配置和叠加。
软件制作好的画布这个的含义是什么呢?
硬件只知道“矩形像素图”,不知道文字、字体、字符串长度和中文。也就是说,你可以显示任何信息,只要你软件能画出来,大小合规,硬件只按照像素叠加上去,不管你到底是汉字还是图片还是普通字符串。
region数量
关于区域的数量,在Hi3519DV500的规格中明确指出,芯片支持8个区域的编码前OSD叠加。这意味着你可以同时创建和管理最多8个独立的Region,用于显示不同的信息或实现遮挡功能。
Region作为一个可管理的逻辑单元,包含几个关键的属性定义,决定了它在视频上的表现形式和行为。
区域类型
OVERLAY:可以任意画内容的 Bitmap 图层。可以放任意像素,所以可以显示logo、文字、图标等等。
OVERLAY:扩展版 Overlay,仍然是一张 Bitmap。在 VPSS 等更早阶段做更灵活的图片图层,代价就是消耗更多带宽。
COVER:用纯色遮挡一个区域,实现隐私遮挡等功能。
COVEREX:除了普通矩形,它还能支持更灵活的形状和坐标,例如四边形框。因为工作方式扩展了,所以会经过更灵活的图形处理通路,可能增加带宽。
LINE / LINEEX:本质就是从起点到终点的一条直线。可以调节粗细颜色。它不是 Bitmap,硬件根据参数直接生成,因此比在 Overlay 中画一条线更省内存。
MOSAIC / MOSAICEX:把指定区域变成马赛克。大小支持4x4,8x8,16x16...128x128
CORNER_RECTEX:它不会把整个矩形填满,而是只绘制四个角。
这里有好几个是EX版本,比如COVER和COVEREX这种。其实类似这种就是一个扩展版本,更加灵活,但是更加消耗带宽。
区域层次
当多个区域在视频上发生位置重叠时,需要由区域层次来决定谁显示在最上面。层次值越大,区域的显示级别就越高,会覆盖层次值较小的区域。如果两个区域的层次值相同,则后附加(attach)的区域会覆盖先附加的区域。对于不同类型的区域,则以其内部模块的处理顺序为准。
| 模块 | 类型 | 最大数量 |
|---|---|---|
| VENC | Overlay | 8 |
| VPSS | OverlayEX | 8 |
| VPSS | CoverEX | 8 |
| VPSS | LineEX | 40 |
| VPSS | Mosaic | 12 |
| VPSS | MosaicEX | 8 |
| VPSS | CornerRect | 12 |
| VPSS | CornerRectEX | 32 |
这个表的含义是不同模块支持哪些类型的REGION,以及最多挂几个。VENC 负责把未压缩视频压缩成码流,就是负责编码。VPSS 主要负责一帧图像进入编码器之前的处理,负责缩放、裁剪和多路输出,内部有更多图形处理能力。
SDK中有定义最多可以创建 16 个逻辑 VENC 编码通道。但是不是理论上“16 个 VENC × 每通道 8 个 Overlay”是128个。不能这么理解,全部按最大的乘。要按实际需求来使用。最多 16 个 VPSS Group,一个 Group 总共最多 8 个逻辑输出通道,四个物理通道,可能还有四个逻辑扩展通道。
大家看到这里应该挺蒙的,这么多参数,到底哪个是哪个,我一个REGION应该这么区分?
1. Handle:Region 编号
2. Type:Overlay、Cover、Line 等
3. Target:挂在 VENC 还是 VPSS
4. Group/Channel:挂在哪个通道
5. Layer:多个图层重叠时的层级
就从这五个来进行区分
示例实现
#define OSD_HANDLE 0
#define OSD_WIDTH 640
#define OSD_HEIGHT 72
#define OSD_SCALE 3
#define OSD_TEXT_X 18
#define OSD_TEXT_Y 24
#define OSD_COLOR_BG 0x8000
#define OSD_COLOR_FG 0xffff
#define OSD_COLOR_ACCENT 0xfc00
| 宏 | 含义 |
|---|---|
OSD_HANDLE 0 | 使用 0 号 Region |
OSD_WIDTH 640 | 位图宽度 640 像素 |
OSD_HEIGHT 72 | 位图高度 72 像素 |
OSD_SCALE 3 | 字体放大 3 倍 |
OSD_TEXT_X 18 | 文字在 Bitmap 内的 X 坐标 |
OSD_TEXT_Y 24 | 文字在 Bitmap 内的 Y 坐标 |
OSD_COLOR_BG | 不透明黑色背景 |
OSD_COLOR_FG | 白色文字 |
OSD_COLOR_ACCENT | 红色装饰条 |
Hi3519DV500 的这个 OSD 模式下,不支持半透明渐变,因为ARGB1555 的 Alpha (A)只有 1 位,要么透明要么不透明。
static td_bool g_osd_started = TD_FALSE;
static volatile td_bool g_osd_running = TD_FALSE;
static pthread_t g_osd_thread;
static td_u8 *g_osd_bitmap = TD_NULL;
g_osd_started:防止重复创建 Region,g_osd_running:控制时间刷新线程,g_osd_thread:OSD 更新线程,g_osd_bitmap:保存 640×72 ARGB1555 像素内存
点阵文字
这里绘制的图片,都是要以像素点为单位来进行绘制的,这个就是0
static const td_u8 font_digit_0[7] = {
0x0e, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0e
};


这里0代表不绘制1代表绘制,就可以得到右边这个格式的0,但是这个0是放大三倍后的,这里的放大三倍是指,像素点变为3x3,不是我们手机上的放大的含义。
核心函数
static td_void sample_vio_osd_draw_text(
td_u8 *data,
td_u32 width,
td_u32 height,
td_u32 x,
td_u32 y,
const td_char *text)
处理过程:
- 从字符串第一个字符开始。
- 找到字符对应的点阵。
- 遍历点阵的 7 行、5 列。
- 如果该位置是 1,就画一个
3×3的白色方块。 - 绘制完成后,X 坐标增加 18。
- 到达 Bitmap 右边后停止。
生成整张OSD位图
sample_vio_osd_render()
//清空画布
memset_s(...);
//填充不透明黑色背景
for (i = 0; i < OSD_WIDTH * OSD_HEIGHT; i++) {
((td_u16 *)g_osd_bitmap)[i] = OSD_COLOR_BG;
}
//绘制顶部红色装饰条
for (i = 0; i < OSD_WIDTH * 4; i++) {
((td_u16 *)g_osd_bitmap)[i] = OSD_COLOR_ACCENT;
}
//获取板端本地时间
time_t now = time(TD_NULL);
struct tm local_time;
localtime_r(&now, &local_time);
//格式化字符串
snprintf_s(
text,
sizeof(text),
sizeof(text) - 1,
"IMX385 OSD %04d-%02d-%02d %02d:%02d:%02d",
local_time.tm_year + 1900,
local_time.tm_mon + 1,
local_time.tm_mday,
local_time.tm_hour,
local_time.tm_min,
local_time.tm_sec);
//把文字画进 Bitmap
sample_vio_osd_draw_text(
g_osd_bitmap,
OSD_WIDTH,
OSD_HEIGHT,
OSD_TEXT_X,
OSD_TEXT_Y,
text);
//发送给 RGN 硬件
ot_bmp bmp;
bmp.pixel_format = OT_PIXEL_FORMAT_ARGB_1555;
bmp.width = OSD_WIDTH;
bmp.height = OSD_HEIGHT;
bmp.data = g_osd_bitmap;
ss_mpi_rgn_set_bmp(handle, &bmp);
首先第一步,清空画布,然后把整个REGION填充为黑色,在给一个红色装饰条,然后获取实际格式化字符串,这个就是得到我们要显示的内容,然后把这个文字在写入g_osd_bitmap中,然后把最后的bmp和编号给RGN硬件。到这一步,软件把一张完整 Bitmap 交给底层 RGN。这里注意,比如我们要显示hello,g_osd_bitmap存放的可不是hello,是对应的一个个像素。
创建REGION
ot_rgn_attr region = {0};
region.type = OT_RGN_OVERLAY;
region.attr.overlay.pixel_format = OT_PIXEL_FORMAT_ARGB_1555;
region.attr.overlay.size.width = 640;
region.attr.overlay.size.height = 72;
region.attr.overlay.bg_color = 0x8000;
region.attr.overlay.canvas_num = 2;
ss_mpi_rgn_create(OSD_HANDLE, ®ion);
含义:
向 MPP 申请一个 Region 资源,Handle 使用 0,类型为 Overlay,像素格式为 ARGB1555,尺寸为 640×72,申请 2 个 Canvas 缓冲区
把REGION绑定到VENC
ot_mpp_chn venc_chn;
venc_chn.mod_id = OT_ID_VENC;
venc_chn.dev_id = 0;
venc_chn.chn_id = 0;
ot_rgn_chn_attr chn_attr = {0};
chn_attr.is_show = TD_TRUE;
chn_attr.type = OT_RGN_OVERLAY;
chn_attr.attr.overlay_chn.point.x = 24;
chn_attr.attr.overlay_chn.point.y = 24;
chn_attr.attr.overlay_chn.fg_alpha = 255;
chn_attr.attr.overlay_chn.bg_alpha = 255;
chn_attr.attr.overlay_chn.layer = 0;
ss_mpi_rgn_attach_to_chn(
OSD_HANDLE,
&venc_chn,
&chn_attr);
VENC 设备 0,通道 0,后面为属性,显示 Region,Bitmap 左上角位于视频 (24,24),前景 Alpha 255,背景 Alpha 255,图层为 0
时间刷新线程
while (g_osd_running && g_sig_flag == 0) {
sample_vio_osd_render(handle);
sleep(1);
}
- 秒重新获取时间,重新绘制 Bitmap,调用
ss_mpi_rgn_set_bmp()更新 Region
接到视频启动流程
ret = sample_vio_start_venc_and_vo(...);
if (ret != TD_SUCCESS) {
goto start_venc_and_vo_failed;
}
if (sample_vio_osd_start() != TD_SUCCESS) {
printf("OSD demo failed; video streaming continues.\n");
}
停止流程
sample_vio_osd_stop();
sample_vio_stop_venc_and_vo(...);
g_osd_running = TD_FALSE;
pthread_join(g_osd_thread, TD_NULL);
ss_mpi_rgn_detach_from_chn(OSD_HANDLE, &venc_chn);
ss_mpi_rgn_destroy(OSD_HANDLE);
free(g_osd_bitmap);
g_osd_bitmap = TD_NULL;
补充概念
canvas
它只属于带位图(Bitmap)的 region,也就是overlay和overlayex。取值只能为1/2,1就是没有缓存,2就是有缓存。它的作用在于对刷新频繁的位图,进行刷新,比如现在显示canvas0后台可以更新canvas1,canvas1在下一次被选中。也就是一块正在显示,一块正在被改写。硬件读 canvas 0 ,显示set_bmp() 写 canvas 1,写完后驱动切换,硬件改读 canvas 1,canvas 0 变成后台。(CPU写,硬件读)
typedef struct {
td_phys_addr_t phys_addr; // 物理地址
ot_size size; // 画布宽高
td_u32 stride; // 每行字节数,可能带对齐
ot_pixel_format pixel_format; // 像素格式
td_void *virt_addr; // 用户态虚拟地址
} ot_rgn_canvas_info;
最后要把OSD的内容加到视频中,其实就是把canvas加到了视频中。但是canvas这里还是ARGB1555,不过这些数据放到了驱动内存中,这块内存是映射好的,DMA可以进行读取的。
这里主要有三个类型,region,bmp,和chn_attr
region
region.type = OT_RGN_OVERLAY;
region.attr.overlay.pixel_format = OT_PIXEL_FORMAT_ARGB_1555;
region.attr.overlay.size.width = LOGO_WIDTH;
region.attr.overlay.size.height = LOGO_HEIGHT;
region.attr.overlay.bg_color = 0x0000;
region.attr.overlay.canvas_num = 1;
type这里指定哪种OSD,要创建哪种类型的region,attr.overlay.pixel_format表示创建的canvas是什么来表示的,attr.overlay.size.width/attr.overlay.size.hight,这里表示我这个region的宽和高,这里不是logo的宽和高,但是一般设置为一样,overlay.bg_color表示region的背景颜色为ARGB,overlay.canvas_num表示有几个画布,用于缓存,上面已经讲解。
bmp
bmp.pixel_format = OT_PIXEL_FORMAT_ARGB_1555;
bmp.width = LOGO_WIDTH;
bmp.height = LOGO_HEIGHT;
bmp.data = g_logo_bitmap;
pixel_format表示我传进去的数据是什么格式,最好和region保持一致,width/heigh这个就表示logo的宽和高,data表示用户内存要传入的数据
chn_attr
chn_attr.is_show = TD_TRUE;
chn_attr.type = OT_RGN_OVERLAY;
chn_attr.attr.overlay_chn.point.x = LOGO_X;
chn_attr.attr.overlay_chn.point.y = LOGO_Y;
chn_attr.attr.overlay_chn.fg_alpha = 255;
chn_attr.attr.overlay_chn.bg_alpha = 0;
chn_attr.attr.overlay_chn.layer = 1;
chn_attr.attr.overlay_chn.dst = OT_RGN_ATTACH_JPEG_MAIN;
这个 region 在某个通道上的显示配置:把 handle 这个 region绑定到 venc_chn 这个通道按照 chn_attr 的参数显示
is_show表示是否显示这个区域,type表示按这个类型来解释,和region的type要对上,attr.overlay_chn.point.x/attr.overlay_chn.point.y表示region的位置attr.overlay_chn.fg_alpha/attr.overlay_chn.bg_alpha表示前景和背景,背景就是region设置的背景,前景就是bmp画出来的东西(ARGB中A为1),attr.overlay_chn.layer显示层级,重叠部分优先显示高层内容attr.overlay_chn.dst把OSD叠加到主页面上
typedef enum {
OT_RGN_ATTACH_JPEG_MAIN = 0,
OT_RGN_ATTACH_JPEG_MPF0,
OT_RGN_ATTACH_JPEG_MPF1,
OT_RGN_ATTACH_JPEG_BUTT
} ot_rgn_attach_dst;
OT_RGN_ATTACH_JPEG_MAIN表示把OSD 叠加到 JPEG 主图。OT_RGN_ATTACH_JPEG_MPF0把 OSD 叠加到 MPF 文件中的第 0 张附加图。
OT_RGN_ATTACH_JPEG_MPF1把 OSD 叠加到 MPF 文件中的第 1 张附加图。OT_RGN_ATTACH_JPEG_BUTT只是枚举边界,不是有效值,不能设置。
MPF 是 Multi-Picture Format,可以把多张 JPEG 放在一个文件里,例如主图、附件图、缩略图等。MPF0/MPF1 的具体内容取决于 JPEG/MPF 配置。
但是我们使用的是H.265编码,所以这个没用
更多推荐

所有评论(0)