LINUX学习之OSD模块和SDL_TTF模块及实战

目录

一。理论知识

1.1 OSD概念

1.2 OSD的实现原理

1.2.1 视频信号的处理

1.2.2 OSD图形生成  

1.2.2.1硬件模式

1.2.2.2 软件模式

1.2.3 OSD图像的叠加

1.2.3.1叠加的两种层级

1.2.3.2叠加的核心原理

1.3 RV1126的OSD结构体模块

1.3.1 OSD_REGION_INFO_S:划定舞台的区域经理

1.3.2 BITMAP_S:登台表演的演员

1.4 RK_MPI_VENC_RGN_SetBitMap 函数解析

1.5  SDL_TTF文字库介绍

1.6 RK_MPI_VENC_RGN_Init API 详解

二。实战:SDL_TTF库渲染

2.1 软件框架流程

2.2 TTF_Init初始化

2.3打开字库---fzlth.ttf

2.4 对字符进行渲染---TTF_RenderUTF8_Solid

TTF_RenderUTF8_Solid 函数详解

SDL_Color 结构体

2.5 填充SDL_PixelFormat信息

SDL_PixelFormat - 像素格式描述器

SDL_ConvertSurface - 像素格式转换器

2.6 关闭函数

三。实战---利用ODS和SDL_TTL给视频添加信息

3.1 软件框架图

3.2 第二阶段:osd处理线程

什么是对齐,为何需要对齐?

“掩码” 通俗理解

内存分配计算:

3.3 第三阶段:获取VENC的码流

3.4 疑问:为什么还要将OSD与VENC绑定

3.5 调试过程

3.5.1 第一次调试---按照例程可以正确的显示

3.5.2 对现实内容做出修改---颜色显示不成功+字母显示不成功

3.5.3 第三次调试----为什么英文字母不行


一。理论知识

1.1 OSD概念

中文名称: 屏幕菜单调节显示方式(或简称为“屏幕菜单显示”)

核心定义
OSD是一种通过屏幕上的图形化菜单,来调整和设置显示设备参数的技术,包括:色彩、几何图形等进行调整,从而使得整个显示器得到最佳的状,最常见的OSD调试就是在屏幕上添加水印、LOGO。通过OSD的叠加方法显示出自定义的图层。

1.2 OSD的实现原理

 一半分为三部分:视频信号处理,OSD图形的生成,OSD图形的叠加

1.2.1 视频信号的处理

将模拟的视频信号转换成数字信号,然后通过专门芯片对数字信号进行处理(去噪,锐化等)

1.2.2 OSD图形生成  

当视频数据完成数字信号处理后,即可为其叠加OSD图像(如LOGO、文字、图标)。根据生成方式的不同,主要分为硬件和软件两种模式:

1.2.2.1硬件模式
  • 核心组件:依赖专用的OSD处理芯片

  • 工作方式:通过硬件的图像合成器,直接将OSD图像与视频信号进行叠加合成。

  • 特点

    • 高效低耗:整个过程由专用硬件完成,CPU不参与处理,系统资源占用极低。

    • 性能稳定:延迟低,速度快,适合对实时性要求高的嵌入式设备(如监控摄像头、电视机顶盒)。

1.2.2.2 软件模式
  • 核心组件:依靠CPU图像处理算法

  • 工作方式:通过软件编程生成OSD图像,并由CPU完成与视频帧的叠加计算。

  • 特点

    • 灵活性强:可动态生成复杂图形和特效,易于更新和修改。

    • 依赖CPU:会占用一定的CPU计算资源,在高分辨率或高帧率场景下可能成为性能瓶颈。

    • 常用框架

      • OpenCV:提供强大的图形绘制和图像合成功能,常用于计算机视觉项目。

      • FFmpeg:通过其libavfilter库(如overlay滤镜)实现视频与OSD的叠加,广泛应用于流媒体处理。

1.2.3 OSD图像的叠加

生成OSD图像后,最后一步是将其与原始视频画面合二为一,这个过程就是叠加。本质上,它是将OSD的像素与视频信号的像素进行合成,从而形成一个最终的综合画面。

1.2.3.1叠加的两种层级

根据显示需求,叠加主要分为两种层级:

  1. 上方叠加

    • 描述:将OSD图像(如LOGO、水印)显示在视频内容的顶部

    • 效果:OSD会遮挡住其位置下方的视频内容。

    • 应用:这是最常见的方式,常用于添加台标、标题、自定义水印等。

  2. 下方叠加

    • 描述:将OSD图像显示在视频内容的底部

    • 效果:视频内容会覆盖在OSD之上,OSD仅作为背景或衬底显示。

    • 应用:相对少见,可用于创建特殊的画中画效果或作为动态背景。

1.2.3.2叠加的核心原理

无论采用哪种层级,其核心技术原理都是像素合成。通过特定的算法(如阿尔法混合),将OSD图层与视频图层的对应像素点进行混合。在此过程中,可以灵活调整OSD的多种参数,从而精确控制其在最终画面中的呈现效果:

  • 位置:确定OSD出现在画面的哪个角落或中心。

  • 大小:对OSD进行缩放,以适应不同需求。

  • 透明度:让OSD与视频内容融合得更自然,避免过度遮挡。

通过这一叠加步骤,静态的OSD元素便与动态的视频流无缝结合,共同构成了我们所看到的完整画面。

1.3 RV1126的OSD结构体模块

在RV1126上进行OSD叠加开发,OSD_REGION_INFO_SBITMAP_S是两个至关重要的结构体。它们的关系可以精辟地概括为:一个负责“搭台”,另一个负责“唱戏”。

1.3.1 OSD_REGION_INFO_S:划定舞台的区域经理

  • 核心作用:在编码图像上划分出一块专用的区域,为OSD图层“预定”空间。你可以把它想象成在视频画布上先框出一个透明的“相框”或“舞台”。

  • 关键属性(通常包括)

    typedef struct rkOSD_REGION_INFO_S {
        OSD_REGION_ID_E enRegionId; // 区域ID
        RK_U32 u32PosX;             // 区域左上角X坐标
        RK_U32 u32PosY;             // 区域左上角Y坐标
        RK_U32 u32Width;            // 区域宽度
        RK_U32 u32Height;           // 区域高度
        RK_U8 u8Inverse;            // 反色显示控制
        RK_U8 u8Enable;             // 区域使能开关
    } OSD_REGION_INFO_S;
    成员变量数据类型说明
    enRegionIdOSD_REGION_ID_E

    区域标识符。用于指定操作的是哪个OSD区域。芯片通常支持多个独立的OSD区域,此ID用于区分它们。

    typedef enum rkOSD_REGION_ID_E {
        REGION_ID_0 = 0,
        REGION_ID_1,
        REGION_ID_2,
        REGION_ID_3,
        REGION_ID_4,
        REGION_ID_5,
        REGION_ID_6,
        REGION_ID_7
    } OSD_REGION_ID_E;

    u32PosXRK_U32区域的X轴起始坐标。定义OSD区域左上角在视频画面中的水平位置(以像素为单位)。
    u32PosYRK_U32区域的Y轴起始坐标。定义OSD区域左上角在视频画面中的垂直位置(以像素为单位)。
    u32WidthRK_U32区域的宽度。定义OSD区域在水平方向上的大小(以像素为单位)。
    u32HeightRK_U32区域的高度。定义OSD区域在垂直方向上的大小(以像素为单位)。
    u8InverseRK_U8反色显示控制。一个布尔标志,当设置为1(或真)时,可能使该区域内的OSD内容以反色(如白底黑字)方式显示,用于实现高对比度效果。
    u8EnableRK_U8区域使能开关。一个布尔标志,用于控制整个OSD区域的显示与否(1/启用,0/禁用)。这是最直接的显示开关。

1.3.2 BITMAP_S:登台表演的演员

  • 核心作用:以位图形式向已划分好的OSD区域填充具体的显示内容。它就是这个“舞台”上登台表演的“演员”。

  • 关键属性(通常包括)

/**
 * @brief 位图结构体,用于定义OSD叠加的具体显示内容。
 * 
 * 该结构体描述了要叠加到视频画面上的图像数据的所有基本信息,
 * 包括图像的格式、尺寸和原始数据存储地址。
 * 它相当于准备了要登台表演的“演员”。
 */
typedef struct rRATTMAP_S {
    OSD_PIXEL_FORMAT_E enPixelFormat; /* 位图的像素格式(例如:ARGB8888, RGB565)。
                                        * 此字段定义了 pData 缓冲区中像素数据的排列和编码方式。
                                        * 为实现透明效果,需使用带Alpha通道的格式(如ARGB)。
                                        */
    RK_U32 u32Width;                  /* 位图的实际宽度,以像素为单位 */
    RK_U32 u32Height;                 /* 位图的实际高度,以像素为单位 */
    RK_VOID *pData;                   /* 指向存储位图像素数据的缓冲区的指针。
                                        * 该缓冲区包含了图像每个像素点的原始数据,
                                        * 数据的解析方式完全由 enPixelFormat 字段指定。
                                        * 开发者需负责分配和维护该内存区域的有效性。
                                        */
} BITMAP_S;
成员变量数据类型说明
enPixelFormatOSD_PIXEL_FORMAT_E位图的像素格式。这是最关键的一个字段,它定义了 pData 缓冲区中每个像素数据的编码方式。例如,它指定了是RGB888、RGB565还是带透明度Alpha通道的ARGB8888等格式。要实现非矩形的、带透明效果的OSD,必须使用支持Alpha通道的格式(如ARGB)。
u32WidthRK_U32位图内容的实际宽度。以像素为单位。这个宽度可以与OSD_REGION_INFO_S中定义的区域宽度不同,硬件或驱动会自动进行缩放以适应区域。
u32HeightRK_U32位图内容的实际高度。以像素为单位。同样,它可以独立于OSD区域的高度。
pDataRK_VOID *位图像素数据的存储地址。这是一个指向存储原始像素数据的内存缓冲区的指针。程序需要保证此缓冲区在OSD显示期间一直有效,并且其数据布局严格符合 enPixelFormat 所指定的格式。

1.4 RK_MPI_VENC_RGN_SetBitMap 函数解析

这个函数是RV1126平台媒体处理接口(MPI)中,用于将位图设置到视频编码器的指定叠加区域的核心函数。它完成了OSD叠加的“最后一公里”,即将准备好的内容和区域进行绑定。

通过这个函数将生成的矢量图与VENC连接,实现在视频上叠加信息

RK_S32 RK_MPI_VENC_RGN_SetBitMap(
    VENC_CHN VeChn,
    const OSD_REGION_INFO_S *pstRgnInfo,
    const BITMAP_S *pstBitmap
);
参数类型输入/输出说明
VeChnVENC_CHN输入视频编码通道号。指定此OSD要叠加到哪个编码通道的视频流上。在有多路编码的场景下,通过此参数区分不同的视频流。
pstRgnInfoconst OSD_REGION_INFO_S *输入指向OSD区域信息结构体的指针。该结构体定义了OSD的“舞台”——即显示的位置、大小和区域ID。函数将根据此指针指向的信息,找到对应的区域。
pstBitmapconst BITMAP_S *输入指向位图结构体的指针。该结构体定义了“演员”——即要显示的具体图像内容(像素数据、格式、尺寸)。

1.5  SDL_TTF文字库介绍

SDL_TTF库是一个TrueType的字体渲染库,SDL_TTF库基本上和SDL库一起进行使用,它依赖于freeType2来处理字体数据。并允许程序员使用多种TrueType字体、这些字体不需要程序员自己来编写例程,SDL_TTF库可以使用轮廓字体这种强大功能,让开发者轻松地获得高质量的文本输出。下面是用SDL_TTF库输出的文本

1.6 RK_MPI_VENC_RGN_Init API 详解

专门用于初始化视频编码器(VENC)的区域(RGN)叠加功能。它为VENC模块使用OSD功能做好基础准备。

RGN模块是VENC编码器内部的一个前端功能单元。

RK_S32 RK_MPI_RGN_Create(RGN_HANDLE Handle, const RGN_ATTR_S *pstRegion);
参数类型输入/输出说明
s32VencChnRK_S32输入视频编码通道号。指定要为哪个编码通道初始化RGN功能。例如,0 表示VENC通道0。
pstRgnAttrconst VENC_RGN_ATTR_S *输入VENC区域属性参数指针。用于传递更详细的初始化参数。如果不需要特殊配置,通常可以传入 NULL,表示使用默认属性。

二。实战:SDL_TTF库渲染

2.1 软件框架流程

2.2 TTF_Init初始化

#include "SDL.h"
#include "SDL_ttf.h"
  
    TTF_Font *font;

  //初始化SDL_TTF的全局参数
    if (TTF_Init() < 0)
    {
        fprintf(stderr, "Couldn't initialize TTF: %s\n", SDL_GetError());
        SDL_Quit();
    }

两个头文件不能缺少:SDL.h和SDL_ttf.h

2.3打开字库---fzlth.ttf

    //使用了fzlth.ttf字库(方正字库),字体大小最合适设置为48
    font = TTF_OpenFont("./fzlth.ttf", 48);
    if (font == NULL)
    {
        fprintf(stderr, "Couldn't load %d pt font from %s: %s\n", 48, "ptsize", SDL_GetError());
    }
TTF_Font* TTF_OpenFont(const char *file, int ptsize);

从磁盘加载一个字体文件,并创建一个可供后续渲染操作使用的字体对象

参数类型说明
fileconst char *字体文件的路径
指向一个字符串,指定要打开的 TrueType (.ttf) 或 OpenType (.otf) 字体文件在文件系统中的位置。可以是相对路径(如 "./fonts/simhei.ttf")或绝对路径。
ptsizeint字体的点大小
这个参数决定了字体加载后的初始物理大小。点是一种印刷单位,大致等于 1/72 英寸。该值直接影响文本渲染到图像上的最终尺寸。

在加载到RV1126板卡时,需要将使用到的字库和生成的执行文件放置在同一个目录下,不然无法调用字库

2.4 对字符进行渲染---TTF_RenderUTF8_Solid

SDL_Surface *text, *temp;
    
////字体颜色是黑色,0xff=255, 0xff=255,0xff=255,rgb={255,255,255}
    SDL_Color forecol = {0xff, 0xff, 0xff, 0xff};
    //SDL_Color forecol = {0x00, 0x00, 0x00, 0xff};
    text = TTF_RenderUTF8_Solid(font, pstr, forecol); //对输入的字符串进行渲染

TTF_RenderUTF8_Solid 函数详解

将 UTF-8 编码的文本字符串快速渲染成一个具有实心颜色且背景透明的图像表面。

SDL_Surface* TTF_RenderUTF8_Solid(TTF_Font *font, const char *text, SDL_Color fg);
参数类型说明
fontTTF_Font *字体对象指针。这是一个通过 TTF_OpenFont() 函数成功加载后返回的指针,它决定了文本的字体样式和大小。
textconst char *要渲染的文本字符串。此文本必须是 UTF-8 编码格式,这对于正确显示中文等非ASCII字符至关重要。
fgSDL_Color文本的前景颜色。即字体本身的颜色。
SDL_Color 结构体

RGB 颜色的简单结构体:

typedef struct SDL_Color {
    Uint8 r; // 红色分量 (0 - 255)
    Uint8 g; // 绿色分量 (0 - 255)  
    Uint8 b; // 蓝色分量 (0 - 255)
    Uint8 a; // Alpha/透明度分量 (0 - 255),但在本函数中通常未使用
} SDL_Color;

2.5 填充SDL_PixelFormat信息

  SDL_PixelFormat *fmt;   

     fmt = (SDL_PixelFormat *)malloc(sizeof(SDL_PixelFormat));
    memset(fmt, 0, sizeof(SDL_PixelFormat));
    fmt->BitsPerPixel = 16; //SDL的每个像素位
    fmt->BytesPerPixel = 2; //SDL的每个像素占的字节数
    temp = SDL_ConvertSurface(text, fmt, 0); //SDL_ConvertSurface对渲染的字体进行像素转换,转换成位图
    SDL_SaveBMP(temp, "save.bmp");//保存位图
SDL_PixelFormat - 像素格式描述器

SDL_PixelFormat 结构体用于描述一个表面(Surface)的像素数据是如何组织和存储的。

参数类型说明
BitsPerPixelUint8每个像素占用的位数
例如:32位色深 = 32,24位色深 = 24,16位色深 = 16。
BytesPerPixelUint8每个像素占用的字节数
这是 BitsPerPixel / 8 的计算结果。例如:32位色深 = 4字节,24位色深 = 3字节。
其他重要字段
Rmask, Gmask, Bmask, AmaskUint32RGBA分量的位掩码。这些掩码定义了每个颜色分量在像素值中的位置,是确保颜色正确解析的关键。
Rloss, Gloss, Bloss, AlossUint8颜色精度损失。表示从8位颜色转换时的精度损失。
Rshift, Gshift, Bshift, AshiftUint8RGBA分量的位偏移。定义每个颜色分量在像素值中的偏移量。
SDL_ConvertSurface - 像素格式转换器

这个函数用于将一个 SDL_Surface 从当前的像素格式转换为另一个指定的格式。

SDL_Surface* SDL_ConvertSurface(SDL_Surface *src, const SDL_PixelFormat *fmt, Uint32 flags);
参数类型说明
srcSDL_Surface *源表面指针。指向通过 TTF_RenderUTF8_Solid() 等函数渲染得到的原始文本表面。
fmtSDL_PixelFormat *目标像素格式描述。指向一个定义了目标格式的 SDL_PixelFormat 结构体。这个格式必须与RV1126 OSD硬件要求的格式(如 OSD_PIXEL_FORMAT_ARGB8888)相匹配。
flagsUint32转换标志。目前未使用,应设置为 0

2.6 关闭函数

    SDL_FreeSurface(text);
    SDL_FreeSurface(temp);
    TTF_CloseFont(font);
    TTF_Quit();

2.7注意事项:

需要把libSDL_ttf-2.0.so.0,libSDL-1.2.so.0放入RV1126的/usr/lib库中,否则RV1126在执行时无法调用相关的库函数

三。实战---利用ODS和SDL_TTL给视频添加信息

3.1 软件框架图

第一阶段:VI,VENC初始化,RGN模块初始化,VI和VENC绑定

RGN初始化:

ret = RK_MPI_VENC_RGN_Init(VENC_CHN, NULL);//RGN模块的初始化

其余三个前面文章都介绍过很多次了,就不做过大的说明

整体思路:

3.2 第二阶段:osd处理线程

void * bitmap_osd_handle_thread(void * args)
{
    pthread_detach(pthread_self());
    int ret ;
    TTF_Font * ttf_font;
    char *pstr = "2025-11-20 22:48:11";
    SDL_Surface * text_surface;  //渲染字符串
    SDL_Surface * convert_text_surface;// 位图转换
    SDL_PixelFormat * pixel_format;  //像素的设置

    //TTF模块的初始化
    ret = TTF_Init();
    if(ret < 0)
    {
        printf("TTF_Init Failed...\n");
    }

    //打开TTF的字库
    ttf_font = TTF_OpenFont("./fzlth.ttf", 48);
    if(ttf_font == NULL)
    {
        printf("TTF_OpenFont Failed...\n");
    }

    //SDL_COLOR黑色,RGB(0,0,0)
    SDL_Color sdl_color;
    sdl_color.r = 0;
    sdl_color.g = 0;
    sdl_color.b = 0;
    text_surface = TTF_RenderText_Solid(ttf_font, pstr, sdl_color); ////渲染文字 

    //ARGB_8888  镜像位图的转换
    pixel_format = (SDL_PixelFormat *)malloc(sizeof(SDL_PixelFormat));

    pixel_format->BitsPerPixel = 32;  //每个像素所占的比特位数 4*8=32
    pixel_format->BytesPerPixel = 4; //每个像素所占的字节数 

    pixel_format->Amask = 0XFF000000;//ARGB的A掩码,A位0xff  当前为有用,其他为没有用
    pixel_format->Rmask = 0X00FF0000;//ARGB的R掩码,R位0xff
    pixel_format->Gmask = 0X0000FF00;//ARGB的G掩码,G位0xff
    pixel_format->Bmask = 0X000000FF;//ARGB的B掩码,B位0xff
    //转换
    convert_text_surface = SDL_ConvertSurface(text_surface, pixel_format, 0);
    if(convert_text_surface == NULL)
    {
        printf("convert_text_surface failed...\n");
    }

    BITMAP_S bitmap;//Bitmap位图结构体
    bitmap.u32Width = get_align16_value(convert_text_surface->w, 16);//Bitmap的宽度
    bitmap.u32Height = get_align16_value(convert_text_surface->h, 16);//Bitmap的高度
    bitmap.enPixelFormat = PIXEL_FORMAT_ARGB_8888;////像素格式ARGB8888
    bitmap.pData = malloc((bitmap.u32Width) * (bitmap.u32Height) * pixel_format->BytesPerPixel); ////bitmap的data的分配大小
    memcpy(bitmap.pData, convert_text_surface->pixels, (convert_text_surface->w) * (convert_text_surface->h) *pixel_format->BytesPerPixel);////bitmap的data赋值
    //OSD位置
    OSD_REGION_INFO_S rgn_info; //OSD_RGN_INFO结构体
    rgn_info.enRegionId = REGION_ID_0; //rgn的区域ID
    rgn_info.u32Width = bitmap.u32Width ; //osd的长度
    rgn_info.u32Height = bitmap.u32Height ; //osd的高度
    rgn_info.u32PosX = 128; //Osd的X轴方向
    rgn_info.u32PosY = 128; //Osd的Y轴方向
    rgn_info.u8Enable = 1; ////使能OSD模块,1是使能,0为禁止。
    rgn_info.u8Inverse = 0; //禁止翻转

    ret = RK_MPI_VENC_RGN_SetBitMap(VENC_CHN, &rgn_info, &bitmap);  //设置OSD位图
    if(ret)
    {
        printf("RK_MPI_VENC_RGN_SetBitMap failed...\n");
    }
    else
    {
        printf("RK_MPI_VENC_RGN_SetBitMap Success...\n");
    }

    return NULL;
}
什么是对齐,为何需要对齐?
//对某个数值进行对齐
static int get_align16_value(int input_value, int align)
{
    int handle_value = 0;
    if (align && (input_value % align))
        handle_value = (input_value/ align + 1) * align;
    return handle_value;
}
  • 对齐: 指一个数值(如宽度或高度)是某个特定基数(这里是16)的整数倍。

  • 为何需要: 这是为了满足硬件处理器的高效数据存取要求。许多图像处理硬件(如DMA、编码器)都以16x16的宏块为单位处理数据,对齐可以确保内存地址边界正确,从而提升处理效率并避免硬件错误。

“掩码” 通俗理解

想象一个像素的32位数据就像一个 32个格子的长条储物柜,每个格子只能放一个0或1。ARGB四种成分的杂物混合放在这个柜子里。

掩码就像是一把特制的钥匙板

  • 钥匙板上有些位置是实的(1),可以捅到柜子深处。

  • 有些位置是空的(0),被挡住了。

当你想单独取出红色成分时,你就用 红色掩码(Rmask) 这把钥匙板(0x00FF0000)去对应这个储物柜。这把钥匙板只能接触到代表红色成分的那8个格子,其他的格子都被钥匙板挡住了。这样,你就能屏蔽掉其他不相关的A、G、B成分,只把红色成分的值“提取”出来。

内存分配计算:

根据对齐后的尺寸计算内存,大小 = 宽 * 高 * 每像素字节数

  • ARGB8888 格式使用 32位 (4字节)来表示一个像素

  • 这32位被分为4个通道,每个通道8位:

    • A (Alpha/透明度) = 8位

    • R (Red/红色) = 8位

    • G (Green/绿色) = 8位

    • B (Blue/蓝色) = 8位

3.3 第三阶段:获取VENC的码流

void * get_h264_data_thread(void * args)
{
    pthread_detach(pthread_self());
    FILE *h264_file = fopen("test_osd_venc.h264", "w+");
    MEDIA_BUFFER mb ;

    while (1)
    {
        mb = RK_MPI_SYS_GetMediaBuffer(RK_ID_VENC, VENC_CHN, -1);
        if(!mb)
        {
            printf("RK_MPI_SYS_GetMediaBuffer failed...\n");
            break;
        }

        printf("Get osd buffer success.....\n");
        //获取指针,获取mb的大小,写入h264_file文件中
        fwrite(RK_MPI_MB_GetPtr(mb), RK_MPI_MB_GetSize(mb), 1, h264_file);
        RK_MPI_MB_ReleaseBuffer(mb);
    }
    
    return NULL;
}

3.4 疑问:为什么还要将OSD与VENC绑定

OSD是先将信息加载到原视频上,再通过VENC输出获得码流。这样子的话,为什么还要通过ret = RK_MPI_VENC_RGN_SetBitMap(VENC_CHN, &rgn_info, &bitmap);  将OSD数据与VENC_CHN通道关联上?

RK_MPI_VENC_RGN_SetBitMap 这个调用真正的含义是:

“VENC通道0,请你从现在开始,在对你输入的每一帧视频进行编码之前,先使用这个位图(&bitmap)和这个区域配置(&rgn_info),在你的内部流水线上完成叠加操作。”

不是把OSD数据“交给”原始视频,而是为编码器设定一条加工规则。这条规则会持续生效,直到你再次更改它。这正是硬件加速OSD的精妙和高效之处。

3.5 调试过程

3.5.1 第一次调试---按照例程可以正确的显示

3.5.2 对现实内容做出修改---颜色显示不成功+字母显示不成功

    pthread_detach(pthread_self());
    int ret;
    TTF_Font * ttf_font;
   char *pstr = "2019-11-21 15:40:29";
   //修改成 darren-20251120
    char *pstr = "2019-11-21 15:40:29";
    SDL_Surface * text_surface;
    SDL_Surface * convert_text_surface;
    SDL_PixelFormat * pixel_format;

对字符串内容修改到Darren-20251120后,显示出来的信息是乱码

取掉英文只保留数字,显示是正常的

在程序中加入SDL位图保存代码,同时注释掉掩码

但是掩码的像素注释掉后,视频不显示出来,但是save.bmp是有显示出来的,但是颜色和设置的对不上

    //ARGB-8888
    pixel_format = (SDL_PixelFormat *)malloc(sizeof(SDL_PixelFormat));

    pixel_format->BitsPerPixel = 32;
    pixel_format->BytesPerPixel = 4;
/*
    pixel_format->Amask = 0Xff000000;
    pixel_format->Rmask = 0x00ff0000;
    pixel_format->Gmask = 0x0000ff00;
    pixel_format->Bmask = 0x000000ff;
*/
    convert_text_surface = SDL_ConvertSurface(text_surface,pixel_format,0);
    SDL_SaveBMP(convert_text_surface, "save.bmp");//保存位图

先说说为什么视频中没有显示信息:

因为我把ARGB掩码设置为0,或者注销掉了,这说明没有正确的调用到颜色,上面我们解释了掩码的了解,掩码好比抽屉的钥匙,只负责打开抽屉,但抽屉里面具体有什么则不是掩码负责的。全部都是0,说明全部都没打开。同时注释掉的话,也没显示,说明掩码默认值是0,关闭状态

    pixel_format->Amask = 0X00000000;
    pixel_format->Rmask = 0x00000000;
    pixel_format->Gmask = 0x00000000;
    pixel_format->Bmask = 0x00000000;

经常几次尝试终于发现问题所在:

RGA三个分量,在代码中我只输入了r和g两个分量,把b给漏掉了

    //SDL_COLOR黑色,RGB(0,0,0)
    SDL_Color sdl_color;
    sdl_color.r = 0xff;
    sdl_color.g = 0x00;
    sdl_color.r = 0x00;
    //修改后
    sdl_color.b = 0x00;
    text_surface = TTF_RenderText_Solid(ttf_font,pstr,sdl_color);

3.5.3 第三次调试----为什么英文字母不行

   char *pstr = "Darren-20251121";

能正常生成位图,但是视频显示乱码,位图能正常保存,说明SDL配置是正常的,主要是OSD配置问题

后续接着更新,暂时没有找到问题,希望路过的大佬能解答一下原因,刚学习还有许多不懂的地方

四。代码

#include <assert.h>
#include <fcntl.h>
#include <getopt.h>
#include <pthread.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

// #include "common/sample_common.h"
#include "rkmedia_api.h"

#include <stdio.h>
#include "SDL.h"
#include "SDL_ttf.h"
#include <time.h>

#define CAMERA_PATH "rkispp_scale0"
#define CAMERA_ID 0
#define CAMERA_CHN 0
#define VENC_CHN 0



//对某个数值进行对齐
static int get_align16_value(int input_value, int align)
{
    int handle_value = 0;
    if (align && (input_value % align))
        handle_value = (input_value/ align + 1) * align;
    return handle_value;
}





void *get_h264_data_thread(void* args)
{
    pthread_detach(pthread_self());

    FILE *h264_file = fopen("test_osd_venc.h264","w+");
    //MEDIA_BUFFER 是 Rockchip 媒体框架中对视频/音频数据缓冲区的抽象封装,你应该通过 SDK 提供的 API 函数来操作它,而不是直接访问其内部结构。
    MEDIA_BUFFER mb;

    while (1)
    {
        mb = RK_MPI_SYS_GetMediaBuffer(RK_ID_VENC,VENC_CHN,-1);
        if(!mb)
        if(!mb)
        {
            printf("RK_MPI_SYS_GetMediaBuffer failed...\n");
            break;
        }

        printf("Get osd buffer success.....\n");
        /*
        RK_MPI_MB_GetPtr(mb) - 获取缓冲区的数据指针(H.264 编码数据)
        RK_MPI_MB_GetSize(mb) - 获取缓冲区数据的大小(字节数)
        1 - 写入的元素个数
        h264_file - 目标文件指针
    */
        fwrite(RK_MPI_MB_GetPtr(mb),RK_MPI_MB_GetSize(mb),1,h264_file);
        RK_MPI_MB_ReleaseBuffer(mb);
    }
}

void * bitmap_osd_handle_thread(void * args)
{
    pthread_detach(pthread_self());
    int ret;
    TTF_Font * ttf_font;
   char *pstr = "D";
    SDL_Surface * text_surface;
    SDL_Surface * convert_text_surface;
    SDL_PixelFormat * pixel_format;

    //TTF模块的初始化
    ret = TTF_Init();
    if(ret < 0)
    {
        printf("TTF_Init Failed...\n");
    }

    //打开TTF的字库 48
    ttf_font = TTF_OpenFont("./fzlth.ttf", 48);
    if(ttf_font == NULL)
    {
        printf("TTF_OpenFont Failed...\n");
    }

    //SDL_COLOR黑色,RGB(0,0,0)
    SDL_Color sdl_color;
    sdl_color.r = 0xff;
    sdl_color.g = 0x00;
    sdl_color.b = 0x00;
    text_surface = TTF_RenderText_Solid(ttf_font,pstr,sdl_color);

    //ARGB-8888
    pixel_format = (SDL_PixelFormat *)malloc(sizeof(SDL_PixelFormat));

    pixel_format->BitsPerPixel = 32;
    pixel_format->BytesPerPixel = 4;

    pixel_format->Amask = 0Xff000000;
    pixel_format->Rmask = 0x00ff0000;
    pixel_format->Gmask = 0x0000ff00;
    pixel_format->Bmask = 0x000000ff;

    convert_text_surface = SDL_ConvertSurface(text_surface,pixel_format,0);
    SDL_SaveBMP(convert_text_surface, "save.bmp");//保存位图
    if(convert_text_surface == NULL)
    {
        printf("convert_text_surface failed...\n");
    }

    BITMAP_S bitmap;//Bitmap位图结构体
    bitmap.u32Width = get_align16_value(convert_text_surface->w, 16);//Bitmap的宽度
    bitmap.u32Height = get_align16_value(convert_text_surface->h, 16);//Bitmap的高度
    bitmap.enPixelFormat = PIXEL_FORMAT_ARGB_8888;////像素格式ARGB8888
    bitmap.pData = malloc((bitmap.u32Width) * (bitmap.u32Height) * pixel_format->BytesPerPixel); ////bitmap的data的分配大小
    memcpy(bitmap.pData, convert_text_surface->pixels, (convert_text_surface->w) * (convert_text_surface->h) *pixel_format->BytesPerPixel);////bitmap的data赋值
    
    if (!bitmap.pData)
    {
            printf("ERROR: no mem left for argb8888(%d)!\n", (bitmap.u32Width) * (bitmap.u32Height) * pixel_format->BytesPerPixel);
            return 0 ;
    }
     printf(" left for argb8888(%d)!\n", (bitmap.u32Width) * (bitmap.u32Height) * pixel_format->BytesPerPixel);
    //OSD位置
    OSD_REGION_INFO_S rgn_info; //OSD_RGN_INFO结构体
    rgn_info.enRegionId = REGION_ID_1; //rgn的区域ID
    rgn_info.u32Width = bitmap.u32Width ; //osd的长度
    rgn_info.u32Height = bitmap.u32Height ; //osd的高度
    rgn_info.u32PosX = 128; //Osd的X轴方向
    rgn_info.u32PosY = 128; //Osd的Y轴方向
    rgn_info.u8Enable = 1; ////使能OSD模块,1是使能,0为禁止。
    rgn_info.u8Inverse = 0; //禁止翻转

    ret = RK_MPI_VENC_RGN_SetBitMap(VENC_CHN, &rgn_info, &bitmap);  //设置OSD位图
    if(ret)
    {
        printf("RK_MPI_VENC_RGN_SetBitMap failed...\n");
    }
    else
    {
        printf("RK_MPI_VENC_RGN_SetBitMap Success...\n");
    }
    // 调试信息
printf("=== BITMAP调试信息 ===\n");
printf("原始表面尺寸: %dx%d\n", convert_text_surface->w, convert_text_surface->h);
printf("对齐后尺寸: %dx%d\n", bitmap.u32Width, bitmap.u32Height);
printf("内存分配大小: %d bytes\n", bitmap.u32Width * bitmap.u32Height * 4);
printf("实际拷贝大小: %d bytes\n", convert_text_surface->w * convert_text_surface->h * 4);

// 检查前几个像素值
if (bitmap.pData && convert_text_surface->pixels) {
    RK_U32* src_pixels = (RK_U32*)convert_text_surface->pixels;
    RK_U32* dst_pixels = (RK_U32*)bitmap.pData;
    
    printf("源表面前3个像素: 0x%08X, 0x%08X, 0x%08X\n", 
           src_pixels[0], src_pixels[1], src_pixels[2]);
    printf("目标表面前3个像素: 0x%08X, 0x%08X, 0x%08X\n", 
           dst_pixels[0], dst_pixels[1], dst_pixels[2]);
}

    return NULL;
    

}



int main()
{
    int ret;
    RK_MPI_SYS_Init();

    // vi set
    VI_CHN_ATTR_S vi_chn_attr;
    vi_chn_attr.pcVideoNode = CAMERA_PATH;
    vi_chn_attr.u32Width = 1920;
    vi_chn_attr.u32Height = 1080;
    vi_chn_attr.enPixFmt = IMAGE_TYPE_NV12;
    vi_chn_attr.enBufType = VI_CHN_BUF_TYPE_MMAP;
    vi_chn_attr.u32BufCnt = 3;
    vi_chn_attr.enWorkMode = VI_WORK_MODE_NORMAL;

    ret = RK_MPI_VI_SetChnAttr(CAMERA_ID,CAMERA_CHN,&vi_chn_attr);
    if(ret)
    {
        printf("vi set attr Failed.....\n");
        return 0;
    }else{
        printf("vi set attr Success....\n");
    }

    ret = RK_MPI_VI_EnableChn(CAMERA_ID,CAMERA_CHN);
    if(ret)
    {
        printf("vi Enable Attr Failed....\n");
        return 0;
    }else {
        printf("vi Enable Attr Success...\n");
    }

    VENC_CHN_ATTR_S venc_chn_attr;//diff   venc_attr_s
    memset(&venc_chn_attr,0,sizeof(venc_chn_attr));  
    //stVencAttr - 编码器基础属性
    venc_chn_attr.stVencAttr.enType = RK_CODEC_TYPE_H264;
    venc_chn_attr. stVencAttr.imageType = IMAGE_TYPE_NV12;
    venc_chn_attr.stVencAttr.u32PicWidth = 1920;
    venc_chn_attr.stVencAttr.u32PicHeight = 1080;
    venc_chn_attr.stVencAttr.u32VirWidth = 1920;
    venc_chn_attr.stVencAttr.u32VirHeight = 1080;
    venc_chn_attr.stVencAttr.u32Profile = 77;

    //编码码率控制属性结构体
    venc_chn_attr.stRcAttr.enRcMode = VENC_RC_MODE_H264CBR;
    venc_chn_attr.stRcAttr.stH264Cbr.u32Gop = 20;
    venc_chn_attr.stRcAttr.stH264Cbr.u32BitRate  = 2000000;//2MB
    venc_chn_attr.stRcAttr.stH264Cbr.fr32DstFrameRateDen = 1;
    venc_chn_attr.stRcAttr.stH264Cbr.fr32DstFrameRateNum = 25;
    venc_chn_attr.stRcAttr.stH264Cbr.u32SrcFrameRateDen = 1;
    venc_chn_attr.stRcAttr.stH264Cbr.u32SrcFrameRateNum = 25;

    ret = RK_MPI_VENC_CreateChn(VENC_CHN,&venc_chn_attr);
    if (ret)
    {
        printf("ERROR: create VENC[0] error! ret=%d\n", ret);
        return 0;
    }
    else
    {
        printf("VENC SUCCESS\n");
    }

    ret = RK_MPI_VENC_RGN_Init(VENC_CHN,NULL);
    if(ret)
    {
        printf("Create VENC_RGN Failed .....\n");
        return 0;
    }
    else
    {
        printf("Create VENC_RGN Success .....\n");
    }

    MPP_CHN_S vi_chns;
    vi_chns.enModId = RK_ID_VI;
    vi_chns.s32ChnId = 0;

    MPP_CHN_S venc_chns;
    venc_chns.enModId = RK_ID_VENC;
    venc_chns.s32ChnId = VENC_CHN;

    ret = RK_MPI_SYS_Bind(&vi_chns,&venc_chns);
        if(ret)
    {
        printf("RK_MPI_SYS_Bind Failed .....\n");
    }
    else
    {
        printf("RK_MPI_SYS_Bind Success .....\n");
    }

    pthread_t bitmap_pid,venc_pid;

    pthread_create(&bitmap_pid,NULL,bitmap_osd_handle_thread,NULL);
    pthread_create(&venc_pid,NULL,get_h264_data_thread,NULL);

    while(1)
    {
        sleep(2);
    }

    RK_MPI_SYS_UnBind(&vi_chns,&venc_chns);
    RK_MPI_VENC_DestroyChn(VENC_CHN);
    RK_MPI_VI_DisableChn(CAMERA_ID,CAMERA_CHN);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值