Linux_19:RV1126的OSD模块和SDL_TTF结合输出H264文件

1.RV1126多线程处理输出OSD字符叠加图层的流程

用RV1126多线程输出OSD叠加需要经过上面几个重要步骤,分别是VI模块初始化、VENC模块初始化、RGN模块初始化、多线程进行OSD字库的叠加(里面主要是进行字库的渲染和RV1126的OSD

2.代码

cs 复制代码
#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)
{
    if (align == 0)
        return input_value;

    if (input_value % align == 0)
        return input_value;

    return (input_value / align + 1) * align;

}

void * bitmap_osd_handle_thread(void * args)
{
    pthread_detach(pthread_self());
    int ret ;
    TTF_Font * ttf_font;
    char *pstr = "2026-2-11 19:46:29";
    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); ////渲染文字

    if (!text_surface)
    {
        printf("TTF_RenderText_Solid error: %s\n", TTF_GetError());
        return NULL;
    }

    printf("text w=%d h=%d\n",
           text_surface->w,
           text_surface->h);


    //ARGB_8888
    pixel_format = (SDL_PixelFormat *)malloc(sizeof(SDL_PixelFormat));
    pixel_format->BitsPerPixel = 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);

    printf("surface pitch=%d\n", convert_text_surface->pitch);
    printf("bitmap stride=%d\n", bitmap.u32Width * 4);////bitmap的data的分配大小

    //memcpy(bitmap.pData, convert_text_surface->pixels, (convert_text_surface->w) * (convert_text_surface->h) * pixel_format->BytesPerPixel); ////bitmap的data赋值

    memset(bitmap.pData, 0,bitmap.u32Width * bitmap.u32Height * 4);

    int src_pitch = convert_text_surface->pitch;  // 2044
    int dst_pitch = bitmap.u32Width * 4;          // 2048
    int copy_bytes = convert_text_surface->w * 4; // 2044

    for (int i = 0; i < convert_text_surface->h; i++)
    {
        memcpy(
            (uint8_t *)bitmap.pData + i * dst_pitch,
            (uint8_t *)convert_text_surface->pixels + i * src_pitch,
            copy_bytes);
    }

    printf("text w=%d h=%d\n",
           convert_text_surface->w,
           convert_text_surface->h);

    printf("align w=%d h=%d\n",
           bitmap.u32Width,
           bitmap.u32Height);

    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 = 192; //Osd的X轴方向
    rgn_info.u32PosY = 256; //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;
}

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");
        fwrite(RK_MPI_MB_GetPtr(mb), RK_MPI_MB_GetSize(mb), 1, h264_file);
        RK_MPI_MB_ReleaseBuffer(mb);
    }
    
    return NULL;
}


int main()
{
    setvbuf(stdout, NULL, _IONBF, 0);

    int ret;
    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获取类型
    vi_chn_attr.u32BufCnt = 3;                    //设置缓冲数量
    vi_chn_attr.enWorkMode = VI_WORK_MODE_NORMAL; //设置VI工作类型
    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;
    memset(&venc_chn_attr, 0, sizeof(venc_chn_attr));
    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;//设置H264的CBR码率控制模式
    venc_chn_attr.stRcAttr.stH264Cbr.u32Gop = 25;
    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(0, &venc_chn_attr);  //VENC模块的初始化
    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);//RGN模块的初始化
    printf("Create RGN ret=%d\n", ret);

    if(ret)
    {
        printf("Create VENC_RGN Failed .....\n");
        return 0;
    }
    else
    {
        printf("Create VENC_RGN Success .....\n");
    }
    
    MPP_CHN_S vi_chn_s;
    MPP_CHN_S venc_chn_s;
    vi_chn_s.enModId = RK_ID_VI;
    vi_chn_s.s32ChnId = 0;

    venc_chn_s.enModId = RK_ID_VENC;
    venc_chn_s.s32ChnId = 0;

    ret = RK_MPI_SYS_Bind(&vi_chn_s, &venc_chn_s); ////VI模块节点和VENC节点绑定
    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); //创建OSD叠加线程
    pthread_create(&venc_pid, NULL, get_h264_data_thread, NULL);   //获取H264码流线程

    while (1)
    {
        sleep(2);
    }

    RK_MPI_SYS_UnBind(&vi_chn_s, &venc_chn_s);
    RK_MPI_VENC_DestroyChn(VENC_CHN);
    RK_MPI_VI_DisableChn(CAMERA_ID, CAMERA_CHN);

    return 0;
}

3.详解

1.初始化RGN 模块:

RGN是RV1126图层区域功能管理的功能,比方说OSD图层、Bitmap位图都属于RGN模块。所以开发者要用到OSD功能都需要初始化RGN,具体的API描述如下:

第一个传参数:VENC编码通道号

第二个传参数:VENC_COLOR_TBL_S结构体指针,默认是NULL

RK_MPI_VENC_RGN_Init(0, NULL);

2.setvbuf(stdout, NULL, _IONBF, 0);

关闭 stdout 的缓冲,让 printf 立即输出 = 在printf 后边加fflush(stdout);

3.对齐函数

static int get_align16_value(int input_value, int align)

{

if (align == 0)

return input_value;

if (input_value % align == 0)

return input_value;

return (input_value / align + 1) * align;

}

4.创建osd叠加线程

cs 复制代码
void * bitmap_osd_handle_thread(void * args)
{
    pthread_detach(pthread_self());
    int ret ;
    TTF_Font * ttf_font;
    char *pstr = "2026-2-11 19:46:29";
    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); ////渲染文字

    if (!text_surface)
    {
        printf("TTF_RenderText_Solid error: %s\n", TTF_GetError());
        return NULL;
    }

    printf("text w=%d h=%d\n",
           text_surface->w,
           text_surface->h);


    //ARGB_8888
    pixel_format = (SDL_PixelFormat *)malloc(sizeof(SDL_PixelFormat));
    pixel_format->BitsPerPixel = 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);

    printf("surface pitch=%d\n", convert_text_surface->pitch);
    printf("bitmap stride=%d\n", bitmap.u32Width * 4);////bitmap的data的分配大小

    //memcpy(bitmap.pData, convert_text_surface->pixels, (convert_text_surface->w) * (convert_text_surface->h) * pixel_format->BytesPerPixel); ////bitmap的data赋值

    memset(bitmap.pData, 0,bitmap.u32Width * bitmap.u32Height * 4);

    int src_pitch = convert_text_surface->pitch;  
    int dst_pitch = bitmap.u32Width * 4;          
    int copy_bytes = convert_text_surface->w * 4; 

    for (int i = 0; i < convert_text_surface->h; i++)
    {
        memcpy(
            (uint8_t *)bitmap.pData + i * dst_pitch,
            (uint8_t *)convert_text_surface->pixels + i * src_pitch,
            copy_bytes);
    }

    printf("text w=%d h=%d\n",convert_text_surface->w,convert_text_surface->h);

    printf("align w=%d h=%d\n",bitmap.u32Width,bitmap.u32Height);

    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 = 192; //Osd的X轴方向
    rgn_info.u32PosY = 256; //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;
}

1.text_surface = TTF_RenderText_Solid(ttf_font, pstr, sdl_color);

它干了两件事:

  1. 用字体把字符串画成"位图"
  2. 生成一个 SDL_Surface,是8 位索引色(调色板),不是 ARGB8888。

它内部其实是:

  • 0 = 透明
  • 1 = 文字颜色

Solid 的特点

  • 无抗锯齿
  • 边缘锐利
  • 速度最快

2.convert_text_surface = SDL_ConvertSurface(text_surface, pixel_format, 0);

把原来的 8 位 surface 转换成 32 位 ARGB8888 surface

  1. 按你给的 pixel_format 创建新 surface
  2. 遍历原 surface 每个像素
  3. 根据 mask 生成新的 32 位像素
  4. 处理 alpha

3.pixel_format->Amask

告诉 SDL:哪 8 位是 Alpha 通道

5.VENC 的 RGN 和 bitmap

在 RK VENC RGN 里:

bitmap 相对于 region 是没有"位置偏移"的。

bitmap 永远是从 region 的左上角 (0,0) 开始贴。

如果 bitmap 小于 region :

  • 剩余区域保持透明
  • 或者保留原值(看是否清零)
  • 但不会自动居中。

如果 bitmap 大于 region ,直接失败。

因为:bitmap 不能超过 region 尺寸

6.surface 的 pitch 和 bitmap 的 stride 不一致(重点!)

1.text w=482 h=56

这是字体真实渲染出来的尺寸。

真实有效区域 = 482 × 56

2.align w=496 h=64

宽度:

482 → 496 (16 对齐)

482 % 16 = 2

高度:

56 → 64

56 不是 16 的倍数
最终 Region = 496 × 64

3.surface pitch = 1928 最重要的参数一

每一行 SDL surface 实际占用的字节数

计算验证:

482 × 4 = 1928

说明SDL surface:

  • 没做额外对齐
  • 一行就是 width × 4

4.bitmap stride = 1984 最重要的参数二

OSD buffer 每一行占用的字节数

计算验证:

496 × 4 = 1984

5.memcpy拷贝

cs 复制代码
    int src_pitch = convert_text_surface->pitch;  
    int dst_pitch = bitmap.u32Width * 4;          
    int copy_bytes = convert_text_surface->w * 4; 

    for (int i = 0; i < convert_text_surface->h; i++)
    {
        memcpy(
            (uint8_t *)bitmap.pData + i * dst_pitch,
            (uint8_t *)convert_text_surface->pixels + i * src_pitch,
            copy_bytes);
    }

for (int i = 0; i < convert_text_surface->h; i++) {

当前是第 i 行

1.计算 source 第 i 行地址

(uint8_t*)convert_text_surface->pixels + i * src_pitch

意思:

从首地址开始 跳 i 行

2.计算 destination 第 i 行地址

(uint8_t*)bitmap.pData + i * dst_pitch

3.只拷贝有效像素数据

memcpy(dst_row, src_row, copy_bytes);

例子:

1.图像不是连续紧密排列的

surface pitch = 2044 bitmap stride = 2048

source(SDL surface)内存结构

宽 511,ARGB8888

511 × 4 = 2044 字节

SDL 没对齐,所以:

第1行:2044字节 第2行:2044字节 第3行:2044字节 ...

内存排布:

|----2044----|----2044----|----2044----| ...

destination(bitmap)内存结构

你对齐后宽度变 512:

512 × 4 = 2048 字节

所以 bitmap 内存是:

|----2048----|----2048----|----2048----| ...

注意:

每一行比 source 多 4 字节空位

如果直接 memcpy 会发生什么?

memcpy(dst, src, 2044 * 56);

一条直线复制

实际发生的情况:

第1行:

dst[0 ~ 2043] ← src[0 ~ 2043] 正确

但 dst 第1行真实应该占 2048 字节!

所以:

dst[2044 ~ 2047] 这4字节没有填

第2行应该从:

dst[2048] 开始

但你现在继续写的是:

dst[2044]

于是

整行向左错4字节

下一行再错4字节

结果就是:

每行逐渐错位 → 倾斜

7.关于显示的问题

cs 复制代码
    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 = 192; //Osd的X轴方向
    rgn_info.u32PosY = 256; //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");
    }

VENC OSD 的坐标系是:

编码器内部帧缓存坐标

不是显示窗口坐标。

VENC OSD 不是在"显示画面"上叠加

它是:

在编码器内部帧缓存上叠加

而这个帧缓存:

很可能不是 1920×1080

而是:对齐后的尺寸

注:

编码器内部坐标原点 (0,0) 是:

内部对齐帧的左上角

但你实际显示的视频:

可能经过:

  • 裁剪
  • 缩放
  • ISP crop
  • VI crop

所以你看到的画面左上角,

不一定等于编码器内部 (0,0)。

常见:如果你的视频源来自:

VI -> VENC

很多 RK 平台:

VI 输出的有效区域

并不是整个 buffer

例如:

Sensor 实际输出 1944×1096

但你裁成 1920×1080

内部 buffer 仍然可能保留 padding 区。

这时:

VENC 的 (0,0)

可能对应的是:

padding 区

不是可见画面左上角。

相关推荐
小白同学_C6 小时前
Lab4-Lab: traps && MIT6.1810操作系统工程【持续更新】 _
linux·c/c++·操作系统os
今天只学一颗糖6 小时前
1、《深入理解计算机系统》--计算机系统介绍
linux·笔记·学习·系统架构
2601_949146536 小时前
Shell语音通知接口使用指南:运维自动化中的语音告警集成方案
运维·自动化
儒雅的晴天6 小时前
大模型幻觉问题
运维·服务器
Gofarlic_OMS7 小时前
科学计算领域MATLAB许可证管理工具对比推荐
运维·开发语言·算法·matlab·自动化
通信大师7 小时前
深度解析PCC策略计费控制:核心网产品与应用价值
运维·服务器·网络·5g
dixiuapp7 小时前
智能工单系统如何选,实现自动化与预测性维护
运维·自动化
不做无法实现的梦~7 小时前
ros2实现路径规划---nav2部分
linux·stm32·嵌入式硬件·机器人·自动驾驶
Elastic 中国社区官方博客7 小时前
如何防御你的 RAG 系统免受上下文投毒攻击
大数据·运维·人工智能·elasticsearch·搜索引擎·ai·全文检索
小锋学长生活大爆炸7 小时前
【教程】免Root在Termux上安装Docker
运维·docker·容器