在嵌入式视频处理领域,OSD(On-Screen Display)字符叠加是一项基础但至关重要的功能。Rockchip RV1126作为一款高性能视觉处理SoC,其OSD功能通过RGN(Region)模块实现,结合SDL_TTF库的文字渲染能力,可以高效完成视频流中的文字信息叠加。本文将深入剖析基于RV1126的OSD实现全流程,从硬件初始化到多线程协同处理,再到H264编码输出。
RV1126的OSD处理流程涉及三个关键硬件模块协同工作:
这三个模块通过Media Process Pipeline(MPP)框架进行数据流转和控制。典型的处理流程如下图所示:
code复制[VI采集] → [RGN叠加OSD] → [VENC编码] → [H264输出]
在开始编码前,需要确保开发环境已正确配置:
交叉编译工具链:
bash复制sudo apt-get install gcc-arm-linux-gnueabihf
RV1126 SDK:需包含MPP媒体处理库和头文件
依赖库:
字体文件:需准备中文字体文件(如文泉驿等)
VI模块负责从摄像头或视频源获取原始图像数据。初始化时需要明确以下参数:
c复制VI_CHN_ATTR_S vi_chn_attr = {
.pcVideoNode = "rkispp_scale0", // 视频设备节点
.u32Width = 1920, // 采集分辨率宽
.u32Height = 1080, // 采集分辨率高
.enPixFmt = IMAGE_TYPE_NV12, // 像素格式(NV12)
.enBufType = VI_CHN_BUF_TYPE_MMAP, // 内存映射方式
.u32BufCnt = 3, // 缓冲区数量
.enWorkMode = VI_WORK_MODE_NORMAL // 工作模式
};
int ret = RK_MPI_VI_SetChnAttr(0, 0, &vi_chn_attr);
if (ret) {
printf("VI Set Attr Failed: %d\n", ret);
return -1;
}
ret = RK_MPI_VI_EnableChn(0, 0);
if (ret) {
printf("VI Enable Failed: %d\n", ret);
return -1;
}
关键参数说明:
u32BufCnt:建议设置为3,形成三缓冲机制,避免帧丢失enPixFmt:NV12格式是视频处理中最常用的YUV格式,具有较好的兼容性enWorkMode:NORMAL模式适用于大多数场景,特殊场景可考虑LOW_LATENCY模式VENC模块负责将处理后的视频帧编码为H.264格式。初始化配置如下:
c复制VENC_CHN_ATTR_S venc_chn_attr = {0};
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; // Main Profile
// CBR码率控制配置
venc_chn_attr.stRcAttr.enRcMode = VENC_RC_MODE_H264CBR;
venc_chn_attr.stRcAttr.stH264Cbr.u32Gop = 25;
venc_chn_attr.stRcAttr.stH264Cbr.u32BitRate = 2000000; // 2Mbps
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);
if (ret) {
printf("VENC Create Failed: %d\n", ret);
return -1;
}
码率控制经验:
RGN模块是OSD功能的核心,负责管理叠加区域和位图数据:
c复制ret = RK_MPI_VENC_RGN_Init(0, NULL);
if (ret) {
printf("RGN Init Failed: %d\n", ret);
return -1;
}
RGN模块特点:
为保持视频处理的实时性,OSD叠加采用多线程架构:
c复制pthread_t osd_thread, enc_thread;
pthread_create(&osd_thread, NULL, osd_handler, NULL);
pthread_create(&enc_thread, NULL, encoder_output, NULL);
线程同步要点:
文字渲染通过SDL2_ttf库实现,核心流程如下:
c复制// 初始化TTF
if (TTF_Init() < 0) {
printf("TTF_Init Failed: %s\n", TTF_GetError());
return NULL;
}
// 加载字体
TTF_Font* font = TTF_OpenFont("fzlth.ttf", 48);
if (!font) {
printf("TTF_OpenFont Failed: %s\n", TTF_GetError());
return NULL;
}
// 设置文字颜色
SDL_Color color = {0, 0, 0, 255}; // 黑色
// 渲染文字
SDL_Surface* text_surface = TTF_RenderText_Solid(font, "2023-01-01 12:00:00", color);
if (!text_surface) {
printf("TTF_RenderText Failed: %s\n", TTF_GetError());
return NULL;
}
字体渲染优化技巧:
SDL渲染的文字表面通常是8位索引色,需要转换为ARGB8888格式:
c复制SDL_PixelFormat* fmt = (SDL_PixelFormat*)malloc(sizeof(SDL_PixelFormat));
fmt->BitsPerPixel = 32;
fmt->BytesPerPixel = 4;
fmt->Amask = 0xFF000000;
fmt->Rmask = 0x00FF0000;
fmt->Gmask = 0x0000FF00;
fmt->Bmask = 0x000000FF;
SDL_Surface* argb_surface = SDL_ConvertSurface(text_surface, fmt, 0);
内存对齐处理:
RV1126硬件要求位图数据16字节对齐,需特殊处理:
c复制int align16(int value, int align) {
return (align == 0) ? value :
((value % align == 0) ? value : ((value / align + 1) * align));
}
BITMAP_S bitmap = {0};
bitmap.u32Width = align16(argb_surface->w, 16);
bitmap.u32Height = align16(argb_surface->h, 16);
bitmap.enPixelFormat = PIXEL_FORMAT_ARGB_8888;
bitmap.pData = malloc(bitmap.u32Width * bitmap.u32Height * 4);
// 行拷贝处理
int src_pitch = argb_surface->pitch;
int dst_pitch = bitmap.u32Width * 4;
int copy_bytes = argb_surface->w * 4;
for (int i = 0; i < argb_surface->h; i++) {
memcpy((uint8_t*)bitmap.pData + i * dst_pitch,
(uint8_t*)argb_surface->pixels + i * src_pitch,
copy_bytes);
}
内存对齐常见问题:
c复制OSD_REGION_INFO_S rgn_info = {0};
rgn_info.enRegionId = REGION_ID_0;
rgn_info.u32Width = bitmap.u32Width;
rgn_info.u32Height = bitmap.u32Height;
rgn_info.u32PosX = 192; // X坐标
rgn_info.u32PosY = 256; // Y坐标
rgn_info.u8Enable = 1; // 使能区域
rgn_info.u8Inverse = 0; // 不反转
ret = RK_MPI_VENC_RGN_SetBitMap(0, &rgn_info, &bitmap);
if (ret) {
printf("SetBitMap Failed: %d\n", ret);
}
坐标系统注意事项:
对于需要多个OSD区域的场景:
c复制void* encoder_output(void* arg) {
FILE* fp = fopen("output.h264", "wb");
if (!fp) {
printf("Open file failed\n");
return NULL;
}
while (1) {
MEDIA_BUFFER mb = RK_MPI_SYS_GetMediaBuffer(RK_ID_VENC, 0, -1);
if (!mb) {
printf("Get buffer failed\n");
break;
}
fwrite(RK_MPI_MB_GetPtr(mb), RK_MPI_MB_GetSize(mb), 1, fp);
RK_MPI_MB_ReleaseBuffer(mb);
}
fclose(fp);
return NULL;
}
编码输出优化:
除本地存储外,还可实现网络推送:
c复制// 伪代码示例
void stream_output(MEDIA_BUFFER mb) {
static rtp_session_t* rtp;
if (!rtp) {
rtp = rtp_session_create("192.168.1.100", 1234);
}
rtp_send_h264(rtp, RK_MPI_MB_GetPtr(mb), RK_MPI_MB_GetSize(mb));
}
OSD不显示:
OSD位置偏移:
OSD闪烁:
文字渲染优化:
内存管理:
多线程优化:
动态OSD内容:
样式丰富化:
智能分析集成:
在实际项目中,我曾遇到一个典型问题:OSD在低光照条件下出现噪点。通过分析发现是YUV转换时未考虑亮度补偿,解决方案是在渲染文字时根据环境光强度动态调整对比度。这个经验告诉我们,OSD实现不仅需要考虑功能正确性,还要适应各种环境条件。