RV1126-SDK学习之OSD实现原理

RV1126-SDK学习之OSD实现原理

前言

本文简单记录一下我在学习RV1126的SDK当中OSD绘制的原理的过程。

在学习OSD的过程当中,可能需要补充的基础知识:

  1. OSD是什么?

  2. BMP图像文件格式大致组成?

  3. 图像调色(Palette)的原理?

  4. RGA常用接口的使用?

涉及的项目的文件路径:

  1. osd服务:app/mediaserver/src/utils/osd。最关键的应该是osd_server.h/cpp文件。阅读完这个文件的代码,基本就能了解osd大概的实现原理,然后其他的文件都是实现细节相关的。

  2. 编码器的实现,包括如下文件:

    • 编解码器基类头文件:external/rkmedia/include/easymedia/encoder.h和codec.h

    • 编解码器基类源文件:external/rkmedia/src/encoder.cc

    • rkmpp编码器的封装:external/rkmedia/src/rkmpp/mpp_encoder和mpp_final_encoder

  3. OSD绘制:包括使用RGA“手动”绘制OSD和使用瑞芯微提供的底层mpp接口“自动”绘制。

OSD是什么?

首先需要了解OSD是什么?有什么意义?一句话概括:OSD就是给一段原生的实时视频或者录像的每一帧加上一点东西,包括但不限于:logo、时间日期,提示语等。

在IPCamera中,OSD是如何实现的?

在IPCamera中,OSD是作为一个服务来实现。实现于mediaserver层。我这里直接放上一段OSD实现最核心的代码:

// 位于文件:app/mediaserver/src/utils/osd/osd_server.cpp
static void *ServerProcess(void *arg) {
  auto os = reinterpret_cast<OSDServer *>(arg);
  char thread_name[40];
  snprintf(thread_name, sizeof(thread_name), "OSDServer[%d]", os->GetWidth());
  prctl(PR_SET_NAME, thread_name);
  LOG_DEBUG("osd ServerProcess in\n");
  int time_count = 0;
  while (os->status() == kThreadRunning) {
    os->UpdateTimeDate();   // 更新日期
    os->UpdateImage();      // 更新logo
    os->UpdateMask();       // 屏蔽某一区域
    os->UpdateBlink();      // 闪烁
    if (time_count == 30) {
      os->UpdateText();     // 更新文本
      time_count = 0;
    }
    time_count++;
    std::this_thread::sleep_for(std::chrono::milliseconds(os->GetDelayMs()));
  }
  LOG_DEBUG("osd ServerProcess out\n");
  return nullptr;
}

从函数命名就可以很清晰的看出,OSDServer中会起一个线程来运行ServerProcess函数,函数会间隔一定时间去更新最终画到视频帧上的OSD的内容。

在IPCamera当中,每个OSD是和video_encoder_flow绑定的。并且OSD是被video_encoder_flow在编码前绘制到每一帧图像上的。

仔细阅读源码的话,就会发现,因为时间是不断变化的,所以UpdateTimeDate函数能保证每次时间有变化时,都会调用easymedia::video_encoder_set_osd_region,去更新编码器绘制的osd的内容。相反的,因为logo和提示文字因为是几乎不变的,所以只会在系统最开始或者被变更时才会真正调用easymedia::video_encoder_set_osd_region去更新编码器的osd的内容。

那么系统怎样监视一个OSD是否有更新呢?答案就是简单的通过一个整型数组。为1代表该种osd需要被更新到编码器中,为0就无需更新。这个数组对应osd_server头文件中的osds_db_data_change_。

为什么是一个数组呢?没错,结合之前提到过的,一帧图像的OSD可能有好几种,每种OSD负责展示不同的信息。所以数组的存在的很有必要的。从osd_server的实现中能更清楚的理解这一点。

谈到监视更新的变量就不得不提到OSDServer的两个核心成员:osds_db_data_、region_data_,这两成员都是数组,每个元素的类型定义如下:

// osds_db_data_(长度为15的数组)
typedef struct osd_data {
  int type;             // 图像还是文本还是边框
  union {
    const char *image;
    text_data_s text;
    border_data_s border;
  };
  int width;            // osd宽
  int height;           // osd高
  uint8_t *buffer;      // osd内容的buffer
  uint32_t size;        // buffer大小

  int origin_x;         // x
  int origin_y;         // y
  int enable;           // 是否使能?

} osd_data_s;

// region_data_(长度为8的数组)
typedef struct {
  uint8_t *buffer; // Content: ID of palette
  uint32_t pos_x;
  uint32_t pos_y;
  uint32_t width;
  uint32_t height;
  uint32_t inverse;
  uint32_t region_id; // max = 8.
  uint8_t enable;
  REGION_TYPE region_type;
  uint32_t cover_color; // for REGION_TYPE_COVER
} OsdRegionData;

从OSDServer的构造函数中,我们可以看到在一个OSDServer对象创建时会进行一些比较重要的初始化操作,如下:

在这里插入图片描述

  1. 设置调色板。

  2. 初始化region_data_。

  3. 查询数据库对osd的配置,并设置到osds_db_data_数组设置osds_db_data_change_相应元素为1,使后台线程能察觉到osd配置的改变,并同步给编码器。

OSD更新的详细流程

这里梳理一下OSD更新的详细流程:

  1. 用户通过网页对OSD进行设置。

  2. 下位机服务器收到并解析请求。将设置持久化到数据库。(mediaserver那边其实会收到dbus的signal)。

  3. 服务器向mediaserver发送设置osd的dbus请求。

  4. mediaserver收到请求然后调用OSDServer::SetOsdRegion(region_id, map)函数进行osd的设置。(dbus请求的参数会被转换成map然后作为OSDServer::SetOsdRegion参数)。

  5. 根据map设置osds_db_data_[region_id]。并且相应的osds_db_data_change_[region_id]被设置为1。

  6. 后台线程检测到osds_db_data_change_[region_id]为1,根据osds_db_data_[region_id]的配置,生成图像(字体)点阵,将点阵设置到region_data_数组中。

  7. 调用easymedia::video_encoder_set_osd_region函数,将osd配置到编码器中。

如下图:

在这里插入图片描述

图中红色字体表示Dbus发出的请求,黑色字体代表普通网络请求。

上文一直在回避三个个比较重要的细节:什么是点阵?字体和图像是怎么绘制成点阵的?编码器是如何将osd绘制到视频的每一帧上的?下面两节就专门来解释这几个问题。

点阵与调色

点阵也是一个比较有意思的东西,这里推荐可以先去了解一下RGA里面怎么进行调色的。可以参考external/linux-rga/samples/rgaColorPalette下的实例,这里面是使用rga相关的接口对图像进行调色的一个demo,源图像就是一个点阵(每个像素是一Byte),通过一个长度为256的数组(调色板,每个元素4Byte),将点阵映射成RGBA格式的图像(目的图像)。

上面一段粗体其实就已经很好的解释了点阵的作用。总结一下:

  1. 如果你想用点阵来显示一段文字(不管中文还是英文),直接将仿照文字的笔画,将你所需要的字节置为一即可。然后告诉mpp/rga用什么调色板,将绘制好的位图传给它们,它们就能够将点阵钻换成文字显示到一帧帧画面上了。在OSDServer中,FontFactory类就是封装了ft2build库,在点阵中绘制文字。

  2. 如果你想用点阵绘制一幅图片,(参考OSDServer的做法)使用bmp图像,bmp图像具体格式的学习参考链接,bmp里面使用位图数据来表示图像。OSDServer调用BMPReader类的LoadYuvaMapFromFile函数将RGB格式的位图数据通过find_color函数将三通道(rgb)的数据转换成yuva调色板的索引(一通道),这样拿到yuva的调色板后mpp/osd就能将点阵转换成图像。

对于文字的点阵生成参考mediaserver的osd下的font_factory的实现,对于图像的点阵生成参考mediaserver的osd下的bmp_reader的实现。原理其实都是差不多的,只不过文字只有一个颜色,所以点阵中点亮的值恒为1。而图像则是多彩的,所以索引值有多种。

利用点阵+调色板的方式有两个很好的优势:

  1. 点阵是确定的,但调色板是灵活的,用户可以根据自己的喜好,使用不同的调色板映射调色板。

  2. 典型的点阵每个像素只有1Byte,占用空间小。

最后,我认为点阵源码最重要的是color_table.h文件下的find_color函数。该函数功能是根据rgb三通道反向查找在调色板中最匹配的一个索引。

代码如下:

uint8_t inline find_color(const uint32_t *pal, uint32_t len, uint8_t r,
                          uint8_t g, uint8_t b) {
    uint32_t i = 0;
    uint8_t pixel = 0;
    unsigned int smallest = 0;
    unsigned int distance = 0;
    int rd, gd, bd;
    uint8_t rp, gp, bp;

    smallest = ~0;

    // LOG_DEBUG("find_color rgba_value %8x", (0xFF << 24 | r << 16 | g <<8 | b
    // <<0));

    for (i = 0; i < len; ++i) {
        // rgb : 从低到高
        bp = (pal[i] & 0xff000000) >> 24;
        gp = (pal[i] & 0x00ff0000) >> 16;
        rp = (pal[i] & 0x0000ff00) >> 8;

        rd = rp - r;
        gd = gp - g;
        bd = bp - b;
        // 计算方差
        distance = (rd * rd) + (gd * gd) + (bd * bd);
        if (distance < smallest) {
            pixel = i;

            /* Perfect match! */
            if (distance == 0)
            break;

            smallest = distance;
        }
    }

    // LOG_DEBUG("find_color pixel %d pal[%d][%d] %8x", pixel, pixel/6, pixel%6,
    // pal[pixel]);

    return pixel;
}

这里备忘一个名词,色深:色彩深度又叫色彩位数,即位图中要用多少个二进制位来表示每个点的颜色,是分辨率的一个重要指标。

编码器

IPCamera的OSD的实现是基于编码器的。而在编码器中,OSD有两种绘制方式:让MPP去绘制或者利用RGA进行OSD的绘制。因为RGA的绘制比较直观,而MPP的绘制需要深入到MPP的API中,目前这方面的基础知识尚缺,所以这里主要介绍一下RGA的绘制方式。

OSDServer向编码器设置OSD的函数调用连如下:

  1. easymedia::video_encoder_set_osd_region()

  2. enc_flow->Control(VideoEncoder::kOSDDataChange, pbuff);

  3. enc->RequestChange(request, value);

  4. (request, value)会被挂到VideoEncoder的编码器设置链表上。

++++++++++++++++++++++++++++++++++++++++++++++

当有图片到达VideoEncoderFlow后,里面的回调函数会继续调用:

  1. 进入函数:MPPEncoder::Process(src, dst, extra_dst);

    1. 处理编码器的设置链表:
    while (HasChangeReq()) {
        auto change = PeekChange();
        if (change.first && !CheckConfigChange(change))
        return -1;
    }
    

    对于RGA的OSD,MPPEncoder类中也会有8个类型为RgaOsdData的数组,OSDServer的设置最终通过上面的循环变更到这里。

    1. RgaOsdRegionProcess,该函数负责调用improcess将各个osd绘制到即将编码的图像帧上。

为了方便梳理流程,最后贴一下MPPEncoder::Process(src, dst, extra_dst)中OSD相关的伪代码:


int MPPEncoder::Process(const std::shared_ptr<MediaBuffer> &input,
                        std::shared_ptr<MediaBuffer> &output,
                        std::shared_ptr<MediaBuffer> extra_output) {

    // 处理编码器配置链表
    while (HasChangeReq()) {
        auto change = PeekChange();
        if (change.first && !CheckConfigChange(change))
            return -1;
    }

    // ...
    // 使用RGA绘制所有的OSD
#ifdef RGA_OSD_ENABLE
    if (rga_osd_cnt > 0)
    RgaOsdRegionProcess(hw_buffer);
#endif

    //...
    return 0;
}

RGA的绘制其实就是按一定比例使用图像blend(混合)的方式进行叠加的。RGA的作用呢包括:对图像缩放、裁剪、混合、调色、拷贝等。它是基于RGA硬件独立于CPU的去做这些操作的,所提供的接口以及用法可以参考external/linux-rga。对于MPP对OSD的绘制,作者暂时还未能深入了解,感兴趣的读者可以自行深入研究。


本章完结

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/908649.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

BFV/BGV全同态加密方案浅析

本文主要为翻译内容&#xff0c;原文地址&#xff1a;Introduction to the BFV encryption scheme、https://www.inferati.com/blog/fhe-schemes-bgv 之前的一篇博客我们翻译了CKKS全同态加密方案的内容&#xff0c;但该篇上下文中有一些知识要点&#xff0c;作者在BFV/BGV中已…

前端小练习——星辰宇宙(JS没有上限!!!)

前言&#xff1a;在刚开始学习前端的时候&#xff0c;我们会学习到前端三件套中的JavaScript&#xff0c;可能那时候读者没有觉得JavaScript这个语言有多么的牛逼&#xff0c;本篇文章将会使用一个炫酷的案例来刷新你对JavaScript这个语言的认知与理解。 ✨✨✨这里是秋刀鱼不做…

JavaScript 生成二维码

我试过了&#xff0c;这一款js库支持中英文混合。 进入网站后&#xff0c;可以直接点击运行哟&#xff5e; https://andi.cn/page/621821.html

Vue全栈开发旅游网项目(6)-接口开发

1.景点详情接口开发 1.设计响应数据结构 文件地址&#xff1a;sight/serializers.py 创建类&#xff1a; class SightDetailSerializers(BaseSerializer):#景点详情def to_dict(self):obj self.objreturn {id: obj.id,name: obj.name,desc: obj.desc,img: obj.banner_img.…

【嵌入式】STM32中的SPI通信

SPI是由摩托罗拉公司开发的一种通用数据总线&#xff0c;其中由四根通信线&#xff0c;支持总线挂载多设备&#xff08;一主多从&#xff09;&#xff0c;是一种同步全双工的协议。主要是实现主控芯片和外挂芯片之间的交流。这样可以使得STM32可以访问并控制各种外部芯片。本文…

微服务系列一:基础拆分实践

目录 前言 一、认识微服务 1.1 单体架构 VS 微服务架构 1.2 微服务的集大成者&#xff1a;SpringCloud 1.3 微服务拆分原则 1.4 微服务拆分方式 二、微服务拆分入门步骤 &#xff1a;以拆分商品模块为例 三、服务注册订阅与远程调用&#xff1a;以拆分购物车为例 3.1 …

密码学简要介绍

密码学是研究编制密码和破译密码的技术科学&#xff0c;它研究密码变化的客观规律&#xff0c;主要包括编码学和破译学两大部分。 一、定义与起源 定义&#xff1a;密码学是研究如何隐密地传递信息的学科&#xff0c;在现代特别指对信息以及其传输的数学性研究&#xff0c;常被…

苄基异喹啉类生物碱的微生物合成-文献精读77

苄基异喹啉类生物碱的微生物合成研究进展及挑战 摘要 微生物发酵是一种经济高效、可持续的生产方式&#xff0c;可替代植物种植和化学合成来生产多种植物来源的药物。苄基异喹啉类生物碱作为植物来源生物碱的典型代表&#xff0c;具有多种重要的生理活性&#xff0c;已成为极具…

Centos安装配置Jenkins

下载安装 注意&#xff1a;推荐的LTS版本对部分插件不适配&#xff0c;直接用最新的版本&#xff0c;jenkins还需要用到git和maven&#xff0c;服务器上已经安装&#xff0c;可查看参考文档[1]、[2]&#xff0c;本次不再演示 访问开始使用 Jenkins 下载jenkins 上传至服务器…

【进度猫-注册/登录安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

【前端基础】盒子模型

目标&#xff1a;掌握盒子模型组成部分&#xff0c;使用盒子模型布局网页区域 01-选择器 结构伪类选择器 基本使用 作用&#xff1a;根据元素的结构关系查找元素。 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8">…

Webserver(2.8)守护进程

目录 守护进程案例 守护进程案例 每隔2s获取系统时间&#xff0c;将这个时间写入到磁盘文件中 #include<stdio.h> #include<sys/stat.h> #include<sys/types.h> #include<unistd.h> #include<fcntl.h> #include<sys/time.h> #include<…

基于SpringBoot+微信小程序+协同过滤算法+二维码订单位置跟踪的农产品销售平台-新

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&#xff1a; “农产品商城”小程序…

Windows 10 安装使用Docker踩过的坑和解决-31/10/2024

目录 环境版本 一、Docker Desktop双击启动没反应&#xff0c;open //./pipe/dockerDesktopLinuxEngine: The system cannot find the file specified. 二、Docker Desktop运行run命令时显示错误HTTP code 500 并且错误大意是服务器拒绝访问 三、Docker Engine stopped/启动…

AG32( MCU + FPGA)实现多串口(15个UART)的应用

AG32 的引脚定义 AG32只有模拟相关的IO是固定的&#xff0c;其它数字IO接口可以任意分配。 QFN-32Pin nameAG32VFxxxKAGRV2KQ321PIN_1IO/RTCIO_GB2PIN_2IO/OSC_INIO3PIN_3IO/OSC_OUTIO4NRSTNRSTNRST5PIN_5IO_ADC_IN12IO6VDDA33VDDA33VDDA337PIN_7IO_WKUP_ADC_IN0_CMP_PA0IO8PI…

CSS基础知识六(浮动的高度塌陷问题及解决方案)

目录 1.浮动高度塌陷概念 2.下面是几种解决高度塌陷的几种方案&#xff1a; 解决方案一&#xff1a; 解决方案二&#xff1a; 解决方案三&#xff1a; 1.浮动高度塌陷概念 在CSS中&#xff0c;高度塌陷问题指的是父元素没有正确地根据其内部的浮动元素或绝对定位元素来计…

014:无人机遥控器操作

摘要&#xff1a;本文详细介绍了无人机遥控器及其相关操作。首先&#xff0c;解释了油门、升降舵、方向舵和副翼的概念、功能及操作方式&#xff0c;这些是控制无人机飞行姿态的关键部件。其次&#xff0c;介绍了美国手、日本手和中国手三种不同的操作模式&#xff0c;阐述了遥…

GitHub | 发布到GitHub仓库并联文件夹的方式

推送到Github 推送步骤如果你只想更新单个文件&#xff0c;只需在第 4 步中指定该文件的路径即可。可能问题一 效果 推送步骤 更新 GitHub 仓库中的文件通常涉及以下步骤&#xff1a; 克隆仓库&#xff1a; 首先&#xff0c;你需要将 GitHub 上的仓库克隆到本地。使用 git …

qt QCloseEvent详解

1、概述 QCloseEvent 是 Qt 框架中用于处理窗口关闭事件的一个类。当用户尝试关闭一个窗口&#xff08;例如&#xff0c;通过点击窗口的关闭按钮&#xff0c;或者通过调用窗口的 close() 方法&#xff09;时&#xff0c;Qt 会生成一个 QCloseEvent 对象&#xff0c;并将其发送…

《JVM第5课》虚拟机栈

无痛快速学习入门JVM&#xff0c;欢迎订阅本免费专栏 Java虚拟机栈&#xff08;Java Virtual Machine Stack&#xff0c;简称JVM栈&#xff0c;又称Java方法栈&#xff09;是 JVM 运行时数据区的一部分&#xff0c;主要用于支持Java方法的执行。每当一个新线程被创建时&#xf…