结合项目实战需求,想要在 RV1126 视频 OSD 中实现中文、数字、时间等自定义文字水印,单纯依靠芯片硬件 OSD 模块无法完成文字绘制工作。行业内主流方案是使用 SDL + SDL_TTF 组合库实现矢量文字渲染,再将渲染后的位图数据对接 RKMedia OSD 接口,最终完成视频画面水印叠加。
SDL_TTF 是专门搭配 SDL 使用的字体渲染库,底层依赖 FreeType2 字体引擎解析字体文件,能够高效渲染 TrueType 矢量字体,输出高清文字画面。由于 RV1126 是 ARM 架构嵌入式 Linux 板卡,PC 端原生库无法直接运行,必须在 Ubuntu 编译主机上完成交叉编译,生成适配 ARM 架构的库文件。
本文将参考标准编译流程,依次完成 zlib、FreeType、SDL、SDL_TTF 四层依赖库交叉编译,全程基于 RV1126 官方 SDK 工具链,步骤完整、注释详细,新手也可一步步跟着操作。
一、SDL_TTF 库整体介绍
1.1 库功能说明
SDL_TTF 是一款轻量级、跨平台的 TrueType 字体渲染开源库,必须依赖 SDL 基础图形库运行。它底层调用 FreeType2 内核解析 .ttf、.ttc 等主流字体文件,开发者无需手动编写字体点阵、轮廓绘制逻辑,仅调用简单 API 就能快速渲染出不同字号、颜色的文字。
结合我们的 OSD 水印场景:
该组合方案兼容性强、渲染效果清晰,是嵌入式视频设备实现自定义文字水印的通用方案。
1.2 依赖层级关系(核心重点)
SDL_TTF 并非独立库,存在多层依赖,编译顺序绝对不能颠倒,正确编译链路如下: zlib(压缩库) → FreeType(字体解析内核) → SDL(基础图形库) → SDL_TTF(文字渲染库)
简单理解:
- zlib:为 FreeType 提供文件压缩、解压支持;
- FreeType:真正解析字体文件的核心引擎;
- SDL:提供像素画布、内存管理等基础图形能力;
- SDL_TTF:封装 FreeType,对接 SDL,对外提供简易文字渲染接口。
二、编译环境整体说明
2.1 软硬件环境
- 编译主机:Ubuntu 系统(Ubuntu 16.04/18.04/20.04 均可)
- 目标板卡:瑞芯微 RV1126(ARM32 架构 Linux 系统)
- 交叉工具链:RV1126 SDK 自带 arm-linux-gnueabihf 编译器
- 编译产物:ARM 架构静态 / 动态库,最终部署到 RV1126 开发板使用
2.2 工具链路径(本文统一路径)
RV1126 官方工具链固定路径,下文所有编译命令均以此为准:
/opt/rv1126_rv1109_linux_sdk_v1.8.0_20210224/prebuilts/gcc/linux-x86/arm/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc
2.3 源码与安装目录规划
为方便统一管理库文件,我们约定所有库的源码随意存放,编译产物统一安装到 /opt/ 目录下:
| zlib | zlib-1.2.11 | /opt/arm_zlib |
| FreeType | freetype-2.4.10 | /opt/arm_freetype |
| SDL | SDL-1.2.15 | /opt/arm_sdl |
| SDL_TTF | SDL_TTF 对应版本 | /opt/arm_sdl_ttf_install |
操作前置:将 zlib-1.2.11、freetype-2.4.10、SDL-1.2.15、SDL_TTF 源码包解压到 Ubuntu 主机任意目录。
三、分层交叉编译完整流程
3.1 第一层:zlib 库交叉编译
zlib 是通用压缩解压缩库,FreeType 字体库依赖它完成字体文件读取与解压,是整个编译链路的最底层依赖。
1.进入 zlib 源码根目录
cd $(PWD)/zlib-1.2.11
2.指定交叉编译器并执行配置 直接指定 RV1126 对应的 ARM 交叉编译器,执行配置脚本:
export CC=/opt/rv1126_rv1109_linux_sdk_v1.8.0_20210224/prebuilts/gcc/linux-x86/arm/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc
./configure –prefix=/opt/arm_zlib
- –prefix:指定库文件、头文件的安装目录,此处设置为 /opt/arm_zlib。
3.多线程编译 -j8 代表使用 8 线程编译,根据自己 CPU 核心数调整,加速编译过程:
make -j8
4.安装编译产物 将编译生成的头文件、库文件拷贝到 –prefix 指定目录:
make install
执行完成后,/opt/arm_zlib 目录下会生成 include(头文件)、lib(库文件)两个文件夹,zlib 编译完成。
3.2 第二层:FreeType 字体引擎交叉编译
FreeType 是 SDL_TTF 依赖的字体解析核心,负责解析 .ttf 字体文件、提取字体轮廓,必须在 zlib 编译完成后再操作。
1.进入 FreeType 源码目录
cd $(PWD)/freetype-2.4.10
2.执行交叉编译配置 指定编译器、依赖 zlib 路径、目标架构、安装目录:
./configure CC=/opt/rv1126_rv1109_linux_sdk_v1.8.0_20210224/prebuilts/gcc/linux-x86/arm/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc \\
–with-zlib=/opt/arm_zlib/lib \\
–host=arm-linux \\
–prefix=/opt/arm_freetype
参数说明:
- CC:指定 ARM 交叉编译器;
- –with-zlib:指定上一步编译好的 zlib 库路径,解决依赖关联;
- –host=arm-linux:指定编译目标为 ARM 嵌入式平台;
- –prefix:指定 FreeType 最终安装目录。
3.编译 + 安装
make -j8
make install
安装完成后,/opt/arm_freetype 目录生成对应头文件与库文件,FreeType 编译结束。
3.3 第三层:SDL 基础图形库交叉编译
SDL 是一套跨平台多媒体基础库,提供画布创建、像素管理、内存操作等基础能力,是 SDL_TTF 的直接依赖。
1.进入 SDL 源码目录
cd $(PWD)/SDL-1.2.15
2.交叉编译配置
./configure CC=/opt/rv1126_rv1109_linux_sdk_v1.8.0_20210224/prebuilts/gcc/linux-x86/arm/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc \\
–host=arm-linux-gnueabihf \\
–prefix=/opt/arm_sdl
参数说明:
- –host=arm-linux-gnueabihf:精准匹配 RV1126 的 ARMhf 软浮点架构;
- –prefix:SDL 库安装目录。
3.编译 + 安装
make -j8
make install
执行完毕,/opt/arm_sdl 目录生成 SDL 头文件与库文件。
3.4 第四层:SDL_TTF 文字库交叉编译
前三层依赖全部编译完成后,最后编译我们最终需要的 SDL_TTF 文字渲染库,这也是整个编译链路的目标库。
1.进入 SDL_TTF 源码根目录
2.执行完整配置命令 关联前面所有已编译好的 SDL、FreeType 库,指定编译架构与安装路径:
./configure CC=/opt/rv1126_rv1109_linux_sdk_v1.8.0_20210224/prebuilts/gcc/linux-x86/arm/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc \\
–host=arm-linux-gnueabihf \\
–prefix=/opt/arm_sdl_ttf_install \\
–with-sdl-prefix=/opt/arm_sdl \\
–with-freetype-prefix=/opt/arm_freetype
参数重点解读:
- –with-sdl-prefix:指定已编译完成的 SDL 库路径,让配置脚本自动识别 SDL 依赖;
- –with-freetype-prefix:指定已编译完成的 FreeType 库路径,关联字体解析引擎;
- 其余参数与上层库保持一致,保证架构统一。
3.编译 + 安装
make -j8
make install
四、查看所有编译产物
全部库编译安装完成后,进入 /opt 目录查看所有产物文件夹:
cd /opt
ls
正常会看到以下目录,代表四层库全部编译成功:
- arm_zlib:zlib 压缩库
- arm_freetype:FreeType 字体引擎库
- arm_sdl:SDL 基础图形库
- arm_sdl_ttf_install:SDL_TTF 文字渲染库
每个目录内部都包含 include(头文件,代码编译时引用)和 lib(库文件,链接阶段使用)两个子目录,是后续 OSD 工程开发的核心文件。
五、编译后工程使用说明
5.1 代码引用头文件
在我们的 OSD 水印代码中,需要引入 SDL 和 SDL_TTF 头文件,对应路径:
5.2 Makefile 配置(交叉编译工程必备)
在项目 Makefile 中添加头文件路径、库文件路径,并链接所有依赖库,示例配置:
makefile
# 交叉编译器
CROSS_COMPILE = /opt/rv1126_rv1109_linux_sdk_v1.8.0_20210224/prebuilts/gcc/linux-x86/arm/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-
CC = $(CROSS_COMPILE)gcc
# 头文件路径
INC += -I/opt/arm_zlib/include
INC += -I/opt/arm_freetype/include
INC += -I/opt/arm_sdl/include
INC += -I/opt/arm_sdl_ttf_install/include
# 库文件路径
LIB_PATH += -L/opt/arm_zlib/lib
LIB_PATH += -L/opt/arm_freetype/lib
LIB_PATH += -L/opt/arm_sdl/lib
LIB_PATH += -L/opt/arm_sdl_ttf_install/lib
# 链接依赖库
LIBS += -lz -lfreetype -lSDL -lSDL_ttf -lpthread -lm
5.3 板卡运行注意事项
六、常见编译问题与排查思路
configure 提示 freetype not found 原因:FreeType 未编译成功,或 –with-freetype-prefix 路径填写错误。 解决:重新编译 FreeType,核对路径与配置参数。
configure 提示 sdl not found 原因:SDL 库未安装、–with-sdl-prefix 路径错误。 解决:检查 SDL 编译产物,修正路径参数。
编译过程报大量语法错误 原因:误用 PC 端 gcc 编译器,未正确指定 ARM 交叉编译器。 解决:严格使用文档中指定的 arm-linux-gnueabihf-gcc。
开发板运行程序提示 error while loading shared libraries 原因:使用动态库编译,板卡缺失对应 .so 文件。 解决:将所有依赖动态库拷贝至板卡系统库目录。
七、渲染文字并保存
进行代码实战将sdl_ttf的库渲染出文字库并以bmp的图像的方式保存
7.1 渲染文字流程

上面是SDL_TTF输出文字图层的几个重点步骤,分别是ttf_init初始化、TTF_OpenFont打开字符库、TTF_RenderUTF8_Solid渲染字体、填充SDL_PixelFormat信息、SDL_ConvertSurface进行像素转换
7.2 SDL_TTF库每个步骤的讲解
下面我们对每一个步骤的操作进行讲解
1. TTF_Init初始化TTF的全局环境
它的作用是初始化TTF的库,是做SDL_TTF渲染的第一步

2. TTF_OpenFont打开字符库

它的作用是打开字体库,同时设置字体的大小,我们来看看它的传参
TTF_Font * SDLCALL TTF_OpenFont(const char *file, int ptsize);
第一个参数:需要加载的字符库的名称,我们这里选择的是方正字库fzlth.ttf。
第二个参数:设置字体的大小,方正字库48效果最好。
3. TTF_RenderUTF8_Solid
对输入的字符串进行渲染

SDL_Surface * SDLCALL TTF_RenderUTF8_Solid(TTF_Font *font, const char *text, SDL_Color fg);
第一个参数:TTF_Font结构体指针
第二个参数:需要渲染的字符串
第三个参数:字体颜色的设置,用SDL_Color进行设置,这个结构体分别是r、g、b三种颜色进行设置,这里的rgb都是用16进制表示。在本实例中我用三个0xff代表的是三个255,也就是字体是白色。
4. 填充SDL_PixelFormat信息

SDL_PixelFormat主要是填充一些像素信息,其中重点设置的参数就上面那几个。
- BitsPerPixel:SDL的每个像素位
- BytesPerPixel:SDL的每个像素占的字节数
5. SDL_ConvertSurface
SDL_ConvertSurface对渲染的字体进行像素转换,转换成位图
SDL_Surface * SDLCALL SDL_ConvertSurface (SDL_Surface *src, SDL_PixelFormat *fmt, Uint32 flags);
第一个参数:SDL_Surface结构体指针
第二个参数:SDL_PixelFormat结构体指针
第三个参数:标识符,默认为0
7.3 代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "SDL.h"
#include "SDL_ttf.h"
#include <time.h>
int main()
{
// ————————– 1. 变量定义 ————————–
// 待渲染的文字内容(这里是固定时间,可改为动态时间)
char *pstr = "2019-11-21 15:40:29";
// 自定义像素格式(适配 OSD 需求的 32 位 BGRA 格式)
SDL_PixelFormat *fmt = NULL;
// 字体文件句柄
TTF_Font *font = NULL;
// 渲染后的原始文字表面、格式转换后的目标表面
SDL_Surface *text_surface = NULL, *convert_surface = NULL;
// ————————– 2. 初始化 SDL 与 SDL_ttf ————————–
// 注意:SDL_ttf 依赖 SDL,必须先初始化 SDL
if (SDL_Init(SDL_INIT_VIDEO) < 0)
{
fprintf(stderr, "SDL 初始化失败: %s\\n", SDL_GetError());
return -1;
}
// 初始化 SDL_ttf 库
if (TTF_Init() < 0)
{
fprintf(stderr, "SDL_ttf 初始化失败: %s\\n", SDL_GetError());
SDL_Quit();
return -1;
}
// ————————– 3. 加载字体文件 ————————–
// 加载指定路径的 TTF 字体文件,设置字体大小为 48
font = TTF_OpenFont("./fzlth.ttf", 48);
if (font == NULL)
{
fprintf(stderr, "加载字体文件失败: %s\\n", SDL_GetError());
TTF_Quit();
SDL_Quit();
return -1;
}
// ————————– 4. 定义文字颜色并渲染 ————————–
// SDL_Color 格式:{R, G, B, A}(红、绿、蓝、透明度)
// 这里设置为白色不透明:R=255, G=255, B=255, A=255
SDL_Color text_color = {0xFF, 0xFF, 0xFF, 0xFF};
// 背景颜色(仅 TTF_RenderText_Shaded 需要,设置为黑色)
SDL_Color bg_color = {0x00, 0x00, 0x00, 0xFF};
// 方式1:Solid 模式(透明背景,适合叠加)
text_surface = TTF_RenderUTF8_Solid(font, pstr, text_color);
// 方式2:Shaded 模式(带背景色,适合需要黑底白字的场景)
// text_surface = TTF_RenderUTF8_Shaded(font, pstr, text_color, bg_color);
if (text_surface == NULL)
{
fprintf(stderr, "文字渲染失败: %s\\n", SDL_GetError());
TTF_CloseFont(font);
TTF_Quit();
SDL_Quit();
return -1;
}
// ————————– 5. 自定义 32 位 BGRA 像素格式(适配 OSD) ————————–
// 为了后续对接 RV1126 OSD,必须使用 32 位 BGRA 格式(B-G-R-A 顺序)
fmt = (SDL_PixelFormat *)malloc(sizeof(SDL_PixelFormat));
if (fmt == NULL)
{
fprintf(stderr, "内存分配失败\\n");
SDL_FreeSurface(text_surface);
TTF_CloseFont(font);
TTF_Quit();
SDL_Quit();
return -1;
}
memset(fmt, 0, sizeof(SDL_PixelFormat));
// 32 位像素格式配置
fmt->BitsPerPixel = 32; // 每个像素 32 位
fmt->BytesPerPixel = 4; // 每个像素占 4 字节
// 颜色掩码(定义每个颜色分量在 32 位中的位置)
fmt->Rmask = 0x00FF0000; // 红色分量:第 16-23 位
fmt->Gmask = 0x0000FF00; // 绿色分量:第 8-15 位
fmt->Bmask = 0x000000FF; // 蓝色分量:第 0-7 位
fmt->Amask = 0xFF000000; // 透明分量:第 24-31 位
// 位移量(每个颜色分量的起始偏移)
fmt->Rshift = 16;
fmt->Gshift = 8;
fmt->Bshift = 0;
fmt->Ashift = 24;
// ————————– 6. 格式转换 ————————–
// 将原始渲染表面转换为自定义的 32 位格式
convert_surface = SDL_ConvertSurface(text_surface, fmt, 0);
if (convert_surface == NULL)
{
fprintf(stderr, "格式转换失败: %s\\n", SDL_GetError());
free(fmt);
SDL_FreeSurface(text_surface);
TTF_CloseFont(font);
TTF_Quit();
SDL_Quit();
return -1;
}
// ————————– 7. 保存 BMP 调试(可选) ————————–
// 保存转换后的表面为 BMP 图片,方便查看渲染效果
SDL_SaveBMP(convert_surface, "text.bmp");
printf("文字渲染成功,已保存为 text.bmp\\n");
// ————————– 8. 资源释放 ————————–
free(fmt); // 释放自定义格式结构体
SDL_FreeSurface(text_surface); // 释放原始渲染表面
SDL_FreeSurface(convert_surface); // 释放转换后的表面
TTF_CloseFont(font); // 关闭字体文件句柄
TTF_Quit(); // 退出 SDL_ttf
SDL_Quit(); // 退出 SDL
return 0;
}
1. 头文件引入
#include <stdio.h>
#include "SDL.h"
#include "SDL_ttf.h"
#include <time.h>
- stdio.h:标准输入输出,用于 printf/fprintf。
- SDL.h:SDL 基础图形库头文件,提供 SDL_Surface、SDL_PixelFormat 等核心类型。
- SDL_ttf.h:SDL_ttf 字体渲染库头文件,提供 TTF_Init、TTF_RenderText 等接口。
- time.h:代码中未实际使用,推测是后续动态时间渲染的预留头文件。
2. 主函数与变量定义
int main()
{
char *pstr = "2019-11-21 15:40:29";
SDL_PixelFormat *fmt;
TTF_Font *font;
SDL_Surface *text, *temp;
- pstr:待渲染的文字字符串,这里是固定时间文本。
- fmt:自定义像素格式结构体,用于指定渲染后的位图格式。
- font:字体文件句柄,用于后续加载 .ttf 字体。
- text:SDL 渲染生成的原始文字表面(Surface)。
- temp:转换格式后的目标表面,用于适配后续 OSD 需求。
3. SDL_ttf 初始化
//初始化SDL_TTF的全局参数
if (TTF_Init() < 0)
{
fprintf(stderr, "Couldn't initialize TTF: %s\\n", SDL_GetError());
}
SDL_Quit();
- TTF_Init():初始化 SDL_ttf 库,返回 0 表示成功,-1 表示失败。
- SDL_Quit():这里存在严重错误:如果初始化失败,直接调用 SDL_Quit(),但 SDL 本身还未初始化;如果初始化成功,也会直接调用 SDL_Quit(),导致后续代码完全无法执行。
4. 加载字体文件
//使用了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_OpenFont(const char *file, int ptsize):加载指定路径的 .ttf 字体文件,并设置字体大小为 48。
- 错误处理:如果字体加载失败(文件不存在 / 权限不足),打印错误信息。
- 问题:printf 中的 %s 格式符错误地使用了 "ptsize" 字符串,会导致日志信息混乱。
5. 定义文字颜色并渲染
//字体颜色是黑色,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); //对输入的字符串进行渲染
- SDL_Color:定义文字颜色,格式为 {R, G, B, A}(红、绿、蓝、透明度)。
- 当前代码中 forecol = {0xff, 0xff, 0xff, 0xff} 表示白色不透明。
- 注释掉的 forecol = {0x00, 0x00, 0x00, 0xff} 表示黑色不透明。
- TTF_RenderUTF8_Solid(TTF_Font *font, const char *text, SDL_Color fg):以实心模式渲染文字,生成 SDL_Surface 位图,背景为透明(0x00000000)。
6. 自定义像素格式与格式转换
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对渲染的字体进行像素转换,转换成位图
- 手动 malloc 并初始化 SDL_PixelFormat,目标格式为 16 位色(BitsPerPixel=16,BytesPerPixel=2)。
- SDL_ConvertSurface(SDL_Surface *src, SDL_PixelFormat *fmt, Uint32 flags):将原始 text 表面转换为指定格式的 temp 表面。
- 问题:
- 16 位色格式不完整,缺少 Rmask/Gmask/Bmask/Amask 等关键掩码定义,转换后数据会乱序。
- 没有对 malloc 结果做 NULL 检查,可能导致空指针崩溃。
- 16 位色不支持透明通道,不适合后续 OSD 叠加(需要 32 位 BGRA 格式)。
7. 保存位图与资源释放
SDL_SaveBMP(temp, "save.bmp");//保存位图
SDL_FreeSurface(text);
SDL_FreeSurface(temp);
TTF_CloseFont(font);
TTF_Quit();
return 0;
}
- SDL_SaveBMP(SDL_Surface *surface, const char *file):将 temp 表面保存为 BMP 图片,方便调试查看渲染效果。
- SDL_FreeSurface():释放 SDL_Surface 占用的内存。
- TTF_CloseFont(TTF_Font *font):关闭字体文件句柄。
- TTF_Quit():退出 SDL_ttf 库。
编译与运行说明
arm-linux-gnueabihf-gcc text_render.c -o text_render -lSDL -lSDL_ttf
7.4 运行效果

上面是这个程序运行的效果,保存了一张bmp的位图。这个位图就是2019-11-21 15:40:29分的字符串。
7.5 运行时报的错误
![]()
上面这个错误是缺少了libSDL_ttf-2.0.so.0的库,此时要把libSDL_ttf-2.0.so.0放在/usr/lib下面
![]()
上面这个错误是缺少了libSDL-1.2.so.0的库,此时要把libSDL-1.2.so.0放在/usr/lib下面
八、本章总结
至此,SDL 文字渲染环境全部搭建完成。下一章我们将结合前文讲解的 OSD 结构体、API,使用 SDL_TTF 渲染文字,完成文字水印 + 硬件 OSD 叠加的完整代码实战。





