
上个月在RK3588平台上做视频采集和显示项目采集端出来的是1080p裸流显示和算法输入要的都是小分辨率CPU直接做缩放把整个流水线拖到卡顿被逼着去啃Linux下的RGA硬件加速。RGA全称Raster Graphic Acceleration是瑞芯微SoC里的2D图形加速引擎专门干图像缩放、格式转换、旋转、裁剪这类活用好了能省下一大截CPU和延迟。这篇文章就从我在RK3588上的真实经历出发把RGA从库编译到图像缩放跑通的完整过程记录下来顺便把这一路踩过的编译排错坑都列清楚。文章适合正在做RK3588视觉方案、NPU前处理、视频编码合成或者显示合成的朋友尤其是刚接触RGA、准备在Linux端调用硬件的同学。你看完不一定能成为RGA专家但至少能把环境搭起来、把第一个缩放demo跑起来遇到问题也知道往哪个方向查。1. 项目概述与需求拆解1.1 为什么在RK3588上必须用RGA先算一笔账。假设输入是1920x1080的RGBA图像要缩放到640x640。一个像素4字节一份原始数据就是8.3MB左右。如果纯用CPU做双线性插值缩放就算A76核心跑满单帧也要几十毫秒。放到实时视频流里这就是灾难。有人会问不是还有GPU吗RK3588的GPU确实可以干这事但GPU做一次渲染要涉及纹理上传、绘制调用、帧缓冲回读对于每隔几毫秒就要做一次的小尺寸缩放来说开销太大还会跟显示合成抢资源。RGA不一样它是一个独立的2D硬件模块专门为图像搬运和变换设计调用接口非常轻量一次缩放操作在毫秒级甚至更低。所以在RK3588上做视觉前处理、视频拼接、画中画、低延迟视频流这些场景用RGA几乎是绕不开的最优解。从应用侧看它就是一个“硬件搬运工”你把输入buffer、输出buffer、格式和矩形区域告诉它它内部通过MMU完成地址映射和DMA搬运CPU基本不参与像素级计算。1.2 RGA硬件能力与常见误区RGA能做的事情不少图像缩放、格式转换RGB、BGR、YUV各格式互转、旋转0/90/180/270、镜像、裁剪、letterbox填充、alpha混合。在RK3588上驱动会注册RGA相关的设备节点新版librga会自动调度硬件实例我们在应用层通常不需要关心具体是哪个RGA单元在工作。很多刚接触的人有几个误区RGA只能做整数倍缩放不对它支持任意比例缩放。1920x1080缩到640x640这种非等比缩放完全没问题。RGA只是格式转换器不对缩放和旋转同样是它的核心能力。随便用malloc分配的buffer就行大多数场景能跑但高带宽、多路并发时强烈建议用dma_fd后面会细说。RGA缩放质量比CPU实现好不一定。RGA内部是硬件插值器通常是双线性或类似算法质量在视觉上够用但如果你追求极致质量还是得看具体算法需求。2. 编译环境与排错实战2.1 获取librga源码与交叉编译工具链RGA的用户空间库叫librga代码在Rockchip官方仓库里能找到。你可以从RK3588的SDK里直接拿也可以从GitHub上拉最新分支两者选一个就行。建议优先用你板子对应SDK里携带的版本因为SDK里的librga和内核驱动版本往往是配套验证过的。交叉编译环境需要注意的是RK3588是aarch64架构工具链要选64位的不要用armv7那套。我用的命令大概是这样sudo apt install gcc-aarch64-linux-gnu g-aarch64-linux-gnu cmakelibrga的构建方式不同版本不太一样。老版本是CMake新版逐渐往meson迁移。如果你拿到的是CMake工程mkdir -p build cd build cmake ../ -DCMAKE_TOOLCHAIN_FILE../toolchain/aarch64-linux-gnu.cmake make -j$(nproc)如果你拿到的是meson工程则需要先准备交叉文件。不过无论哪种方式编译产物最终会生成librga.so和几个头文件我们做应用开发只需要关心这三个东西。2.2 编译排错实录我踩过的5个坑这里把我在编译和链接阶段遇到的典型问题列一下基本都是网上FAQ里不太容易搜全的。坑1头文件找不到rk_common.h不存在老代码或者网上教程里经常写#include rk_common.h但新版librga头文件已经重新组织了常见的是#include im2d.h #include rga.h #include RgaUtils.h如果你还在用rk_common.h大概率是代码和库版本不匹配。解决办法是切换到当前头文件对应的API别硬找一个老头文件凑。坑2链接阶段undefined reference to c_RkRgaBlit这个错误非常经典。老接口的符号名和新接口不一样新版librga里核心提交函数可能叫rga_blit或者经过改造后的c_RkRgaBlit。你如果按老文章抄代码然后又链接了新库就会出现这个错误。我当时的处理方式是放弃老API改用新版im2d接口省心很多。如果你必须用老接口要确认头文件和库文件来自同一个版本别混搭。坑3编译器架构不匹配这个错误很蠢但很容易犯。有人拿着老项目的Makefile直接编译里面写死了-mfloat-abisoftfp或者arm-linux-gnueabihf-这在aarch64平台下会直接报错unrecognized command-line option -mfloat-abisoftfp解决办法是搜索Makefile里的工具链前缀和浮点参数改成aarch64-linux-gnu-再把float相关flag去掉。坑4依赖libdrm头文件缺失新版librga在某些版本会引用DRM的四字节格式定义比如drm_fourcc.h。如果你系统里没装libdrm-dev编译会报找不到头文件。在Ubuntu交叉环境里可以装一下sudo apt install libdrm-dev如果是纯SDK环境确保CMake的include路径里包含了内核或者libdrm的头文件目录。坑5静态库和动态库混用librga有时候编译生成libggi不是是libRGA.so同时也会生成.a。我一开始链接了静态库后来换动态库结果运行时报符号冲突。建议统一使用动态库编译时加g main.cpp -o rga_demo -lrga -I/usr/include/librga然后运行前把librga.so放到板子的/usr/lib或者通过LD_LIBRARY_PATH指定。2.3 确认驱动和设备节点是否正常编译通过只是第一步板子上如果没有RGA设备节点应用层做得再漂亮也没用。连接板子后先检查ls -l /dev/rga正常情况下能看到一个/dev/rga字符设备。如果是Ubuntu移植环境可能节点没有自动创建此时要检查内核配置里RGA驱动有没有打开。常见配置项有CONFIG_ROCKCHIP_RGA、CONFIG_ROCKCHIP_RGA2、CONFIG_ROCKCHIP_RGA3至少要保证对应平台的驱动被编译进内核。再看驱动运行日志dmesg | grep -i rga如果能看到类似rga3: module initialized之类的信息说明驱动已经加载。此时建议先跑一下librga自带的测试程序比如rga_test确认硬件通路没毛病再开始写自己的缩放逻辑。3. 图像缩放应用实现细节3.1 新版librga还是老接口librga的接口演进跨度挺大。老版本API核心是rga_info_t结构体加rga_blit函数所有参数都靠结构体填充用起来繁琐但直白。新版本是im2d接口直接把缩放、旋转、格式转换封装成了语义明确的函数。我的建议是除非你的项目被锁死在老SDK上否则一律用新版im2d。原因是im2d代码可读性好头文件里对每个函数都有说明排错效率高。后面代码示例也以新版为主。3.2 完整缩放代码示例我写的demo功能是把一张1920x1080的RGBA图像缩放到640x640。这里解释一下RGBA是裸数据不含文件头直接从采集buffer或者解码器输出里拿到的就是这种格式。#include cstdio #include cstdlib #include cstring #include im2d.h #include rga.h #include RgaUtils.h int main() { const uint32_t srcW 1920, srcH 1080; const uint32_t dstW 640, dstH 640; const uint32_t srcSize srcW * srcH * 4; const uint32_t dstSize dstW * dstH * 4; int ret rga_open(); if (ret 0) { printf(rga_open failed: %d\n, ret); return -1; } void *srcPtr malloc(srcSize); void *dstPtr malloc(dstSize); if (!srcPtr || !dstPtr) { printf(malloc failed\n); rga_close(); return -1; } memset(srcPtr, 0x80, srcSize); memset(dstPtr, 0, dstSize); rga_buffer_t src wrapbuffer_virtualaddr(srcPtr, srcW, srcH, srcW, srcH, RK_FORMAT_RGBA_8888); rga_buffer_t dst wrapbuffer_virtualaddr(dstPtr, dstW, dstH, dstW, dstH, RK_FORMAT_RGBA_8888); IM_STATUS status imresize(src, dst); if (status ! IM_STATUS_SUCCESS) { printf(imresize failed: %s\n, imStrError(status)); rga_close(); free(srcPtr); free(dstPtr); return -1; } printf(rga resize ok\n); rga_close(); free(srcPtr); free(dstPtr); return 0; }这段代码有几个地方要注意。rga_open是打开设备节点返回负数说明设备不可用。wrapbuffer_virtualaddr是把用户态虚拟地址交给RGA中间参数依次是宽、高、wstride行宽对齐后的值、hstride、格式。这里为了演示方便直接用原始宽高做stride实际项目中要按对齐规则计算。编译命令aarch64-linux-gnu-g rga_demo.cpp -o rga_demo -lrga -I/usr/include/librga如果是在板子上本地编译把工具链前缀去掉即可。3.3 对齐规则与参数计算RGA硬件对图像的行宽和高度有对齐要求这是新手最容易踩坑的地方。简单讲硬件在处理二维图像时需要按一定的粒度把行数据切成块如果宽高不满足对齐要求轻则花屏重则直接返回失败。我惯用的对齐方式是宽按16像素对齐高按2像素对齐。对于RGB系列和常见的YUV格式这个规则在我测试过的RK3588驱动上都能稳定跑。写一个通用的对齐函数static inline uint32_t align_up(uint32_t value, uint32_t align) { return (value align - 1) ~(align - 1); }计算strideuint32_t srcWstride align_up(srcW, 16); uint32_t srcHstride align_up(srcH, 2); uint32_t dstWstride align_up(dstW, 16); uint32_t dstHstride align_up(dstH, 2);然后传给wrapbuffer_virtualaddr的第二个和第三个参数指buffer接口中代表实际宽高的参数还是原始宽高wstride和hstride传对齐后的值。这样RGA就能正确定位每一行的起始地址。不同RGA版本的对齐要求可能有差异体现在某些YUV格式上高度甚至要按8或16对齐。如果你在官方demo里看到类似RGA_ALIGN的宏那基本就是对硬件约束的兜底。最稳妥的办法是先跑通一个测试分辨率再逐步改到你实际需要的分辨率上确认不花屏再往整条链路上集成。3.4 缩放效果的验证方法缩放完不能只打印一个ok就完事必须验证输出数据对不对。最简单的验证方法是把输出保存成可查看的图片格式。如果板子上有OpenCV直接imwrite最方便没有的话可以手写一个BMP文件头把RGBA数据塞进去。BMP文件头结构不复杂核心是14字节文件头加40字节信息头然后跟像素数据。注意BMP默认是从下往上排列通常还要做一次垂直翻转否则看到的图像是倒着的。验证这一步很重要因为很多硬件缩放问题不是缩放本身出错而是stride填错导致图像行错位检查一下输出图就能一眼看出来。4. 性能实测与常见问题排查4.1 耗时测试方法性能测试我建议用clock_gettime(CLOCK_MONOTONIC)这个接口在Linux下是单调时钟不受系统时间调整影响。先预热几次让驱动和数据通路都热起来再连续跑几百次统计平均值。struct timespec start, end; clock_gettime(CLOCK_MONOTONIC, start); for (int i 0; i 1000; i) { imresize(src, dst); } clock_gettime(CLOCK_MONOTONIC, end); double elapsed (end.tv_sec - start.tv_sec) * 1000.0 (end.tv_nsec - start.tv_nsec) / 1000000.0; printf(avg cost: %.3f ms\n, elapsed / 1000);我在实际板子上测下来1920x1080 RGBA缩到640x640单次耗时在1~2ms量级同样分辨率NV12转NV12加缩放速度还会更快一些。作为对比纯CPU双线性插值处理同样操作需要30ms以上差距非常明显。需要注意测试时要尽量让CPU和DDR频率稳定否则数字会有波动。如果板子有性能调频可以先固定性能模式再测如果没有多跑几个批次取中位数不要被单次峰值带偏。4.2 常见问题速查表现象可能原因解决方法输出花屏、条纹stride没有按16对齐或格式描述与真实数据不符按对齐规则重新设置wstride/hstride颜色错乱、偏色RGB/BGR顺序填错或YUV色彩范围不对检查format枚举YUV转换时设置color_spaceimresize返回失败宽高超过硬件限制或格式不受支持看dmesg尾部RGA错误日志查询支持格式列表旋转90度后尺寸不对目标宽高没有跟着交换旋转操作时把dstW和dstH互换多线程同时调用崩溃librga上下文被多线程混用自己加锁或每个线程独立初始化用vaddr偶尔崩、不稳定驱动MMU没能持续映射同一块用户态内存优先使用dma_fd并保证buffer生命周期内不释放调试RGA代码最实用的一招是看内核日志。每次调用失败后立刻执行dmesg | tail -n 50很多时候驱动会把失败原因写得很清楚比如格式不支持、地址校验失败、宽高超出限制。这个信息比你猜变量强一百倍。4.3 多路并发与内存复用建议如果你的项目里同时要缩放多路视频流我的建议是不要为每一帧都去malloc和释放内存。RGA调用本身很快内存分配和地址映射反而可能成为瓶颈。最好是在初始化阶段就分配好一组可复用的buffer然后轮转使用。多线程方面librga内部虽然有一定的线程安全措施但我不建议在高并发场景下毫无约束地共享同一个上下文。可以自己设计一个简单的互斥锁或者用生产者消费者队列把请求串行化。实测下来RGA单次操作极快串行化带来的延迟远小于锁竞争和调度抖动。内存分配方式上能走dma_fd就走dma_fd。常见做法是从v4l2采集buffer或者解码器输出的dmabuf直接拿fd传给RGA省掉一次CPU拷贝。如果是自己分配的buffer可以考虑从内核的dma-heap里分配路径一般是/dev/dma_heap/system-uncached通过DMA_HEAP_IOCTL_ALLOC拿到fd再映射成虚拟地址用于填充数据。这种内存天然适合硬件DMA访问稳定性比普通malloc高不少。最后再分享一个经验调试RGA初期不要急着把RGA塞进完整视频链路先用一张固定测试图把缩放、格式转换、旋转这几个基本动作单独验一遍。确认每一步的输出都正确再往采集、编码、显示这种复杂链条里集成。我把这步省掉了结果后面排查花屏问题浪费了整整一下午最后还是回头用测试图定位到是stride填错。这个顺序别搞反。