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)的区域会覆盖先附加的区域。对于不同类型的区域,则以其内部模块的处理顺序为准。

模块类型最大数量
VENCOverlay8
VPSSOverlayEX8
VPSSCoverEX8
VPSSLineEX40
VPSSMosaic12
VPSSMosaicEX8
VPSSCornerRect12
VPSSCornerRectEX32

这个表的含义是不同模块支持哪些类型的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)

处理过程:

  1. 从字符串第一个字符开始。
  2. 找到字符对应的点阵。
  3. 遍历点阵的 7 行、5 列。
  4. 如果该位置是 1,就画一个 3×3 的白色方块。
  5. 绘制完成后,X 坐标增加 18。
  6. 到达 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, &region);

含义:

向 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编码,所以这个没用

Logo

海思 HiSpark 开发者社区是上海海思为开发者搭建的内容和交流平台,依托完备芯片解决方案、专业开发环境、开源/开放的软件资源与社区技术内容,构建完整服务生态开发者的闭环体系。

更多推荐