一、librga 的设计哲学
在记 API 之前,先理解 librga 为什么长成现在这样。官方给出了五条设计取向:
第一,接口定义参考 OpenCV / MATLAB 中常用的 2D 图形接口。 目的是减少二次开发的学习成本,所以它叫 IM2D API。
第二,加入 RGA query 查询功能。 目的是消除 RGA 硬件版本差异带来的兼容问题。查询内容包括版本信息、输入输出最大分辨率、图像格式支持情况。
第三,执行图像操作之前,需要对输入输出缓冲区做预处理。 调用 wrapbuffer_T 把图像信息填充到 rga_buffer_t 结构体,结构体里包含分辨率、格式、stride 等信息。
第四,提供 improcess 复合接口。 其他所有 API 都是基于它开发的,当你需要一次调用实现多种功能时用它。
第五,支持把单次无法完成的复合操作绑定为一个 RGA 图像任务,统一提交到驱动内逐个执行。这就是 Job 模式。
二、API 全景地图
2.1 信息查询类
| API | 作用 |
|---|---|
querystring(name) | 返回当前 RGA 硬件能力与版本的字符串 |
imcheckHeader() | 校验头文件版本与 librga 版本差异 |
imcheck() / imcheck_composite() | 校验参数合法性 + 硬件是否支持;仅调试阶段使用 |
2.2 缓冲区预处理类
| API | 作用 |
|---|---|
importbuffer_fd() | 把 dma-buf fd 导入 RGA 驱动,建立映射 |
importbuffer_virtualaddr() | 把虚拟地址导入 RGA 驱动 |
importbuffer_physicaladdr() | 把物理地址导入 RGA 驱动 |
releasebuffer_handle() | 解除映射与绑定,释放驱动内部资源 |
wrapbuffer_handle() | 把参数封装成统一的 rga_buffer_t |
wrapbuffer_virtualaddr() / wrapbuffer_fd() / wrapbuffer_physicaladdr() | 直接封装,内部调 importbuffer |
2.3 任务(Job)类
| API | 作用 |
|---|---|
imbeginJob(flags) | 创建任务,返回 job_handle |
imendJob(job, sync, acq_fd, *rel_fd) | 提交并执行,完成后自动删除任务资源 |
imcancelJob(job) | 取消并删除任务 |
2.4 单次操作类
每个操作都有同步版本、Task 版本,部分有 Array 版本。
| API | 作用 |
|---|---|
imcopy | 快速拷贝 |
imresize / impyramid | 缩放 / 金字塔 |
imcrop | 裁剪 |
imtranslate | 平移 |
imcvtcolor | 格式 / 色域转换 |
imrotate | 旋转 90 / 180 / 270 |
imflip | 镜像翻转 |
imblend / imcomposite | 双 / 三通道合成 |
imcolorkey | 色键(抠图) |
imosd | OSD 字幕叠加 |
imquantize | NN 前处理量化(仅 RV1126/RV1109) |
imrop | 光栅运算 |
imfill / imfillArray | 颜色填充 |
imrectangle / imrectangleArray | 画框 |
immakeBorder | 加边框 |
immosaic / immosaicArray | 马赛克 |
imgaussianBlur | 高斯模糊 |
impalette | 调色板 |
imcfa | CFA 处理 |
2.5 复合与同步类
| API | 作用 |
|---|---|
improcess | 万能复合接口 |
imsync(fence_fd) | 异步模式下等待操作完成 |
imconfig(name, value) | 配置当前线程默认上下文 |
2.6 命名规律
imxxx()—— 同步单次调用imxxxTask()—— 往 job_handle 里追加操作,最后由imendJob()一次性提交imxxxArray()—— 一次对多个矩形区域执行importbuffer_T()/wrapbuffer_T()—— T 是类型占位:_fd/_virtualaddr/_physicaladdr
C 和 C++ 两套写法:im2d.h 是 C 接口(用宏实现可选参数);im2d.hpp 是 C++ 接口(用默认参数实现)。RK3588 上两种都能用,但建议用 C++ ,因为:
importbuffer_fd(fd, size)这个简化版只在 C++ 下可用;C 下必须传im_handle_param_t *- samples 里的 demo 都是
.cpp
三、核心概念一:实宽高 vs 虚宽高
调试日志里会反复看到两组缩写:
| 缩写 | 含义 | 对应字段 |
|---|---|---|
| aw / ah(active) | 实宽实高 = 本次实际操作区域 | im_rect.width / im_rect.height |
| vw / vh(virtual) | 虚宽虚高 = 图像 buffer 本身大小 | rga_buffer_t.width / rga_buffer_t.height |
| xoff / yoff | 操作区域相对 buffer 原点的偏移 | im_rect.x / im_rect.y |
约束是:(x + width) <= wstride 且 (y + height) <= hstride。
违反这条约束,就会看到经典的报错:
err ws[100,1280,1280]
Error srcRect
含义:err ws[x_offset, width, width_stride],X 方向偏移 + 实宽超过了虚宽。
修法:把虚宽改到 1380(= 100 + 1280),或把实宽改到 1180(= 1280 − 100)。
四、核心概念二:内存类型的性能排序
官方明确说明:不同的 buffer 类型调用 RGA 的性能是不同的。性能排序是:
physical address > fd > virtual address
一般推荐使用 fd 作为 buffer 类型。
| 内存类型 | 实际耗时 / 理论耗时 | 评价 |
|---|---|---|
| 物理地址 | 1.1 ~ 1.2 倍 | 最快,但用户态难以拿到 |
| dma_fd(dma-buf) | 1.3 ~ 1.5 倍 | 推荐:效率与易用性的最佳平衡点 |
| 虚拟地址 | 1.8 ~ 2.1 倍,且受 CPU 负载影响大 | 仅适合学习阶段 / 快速验证 |
为什么虚拟地址这么慢? 官方 FAQ 给了两条原因:页表转换由 CPU 算;每帧强制同步 cache。
五、第一个 RGA 程序:标准六步法
这是使用 RGA 的通用流程,所有操作都按这个骨架来。
┌─ 1. 分配 buffer ─── dma_heap / DRM → 拿到 dma-buf fd
│
├─ 2. 导入驱动 ───── importbuffer_fd(fd, size) → rga_buffer_handle_t
│
├─ 3. 封装 ───────── wrapbuffer_handle(hnd, w, h, fmt[, wstride, hstride]) → rga_buffer_t
│
├─ 4. 【可选】校验 ── imcheck(src, dst, srect, drect)
│
├─ 5. 执行 ───────── imcopy / imresize / imcvtcolor / ...
│
└─ 6. 释放 ───────── releasebuffer_handle(hnd) + 释放自己的 buffer
5.1 官方最简 demo:rga_copy_demo.cpp
这是 samples 里最简单的 demo,用 malloc + virtualaddr,适合跑通六步法。
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <linux/stddef.h>
#include "RgaUtils.h"
#include "im2d.hpp"
#include "utils.h"
#define LOCAL_FILE_PATH "/data"
int main() {
int ret = 0;
int src_width = 1280, src_height = 720, src_format = RK_FORMAT_RGBA_8888;
int dst_width = 1280, dst_height = 720, dst_format = RK_FORMAT_RGBA_8888;
char *src_buf, *dst_buf;
int src_buf_size, dst_buf_size;
rga_buffer_t src_img, dst_img;
rga_buffer_handle_t src_handle, dst_handle;
memset(&src_img, 0, sizeof(src_img));
memset(&dst_img, 0, sizeof(dst_img));
src_buf_size = src_width * src_height * get_bpp_from_format(src_format);
dst_buf_size = dst_width * dst_height * get_bpp_from_format(dst_format);
src_buf = (char *)malloc(src_buf_size);
dst_buf = (char *)malloc(dst_buf_size);
/* 填充测试数据 */
if (0 != read_image_from_file(src_buf, LOCAL_FILE_PATH, src_width, src_height, src_format, 0)) {
printf("src image read err\n");
draw_rgba(src_buf, src_width, src_height);
}
memset(dst_buf, 0x80, dst_buf_size);
/* Step 1+2: 导入 RGA 驱动,取得 handle */
src_handle = importbuffer_virtualaddr(src_buf, src_buf_size);
dst_handle = importbuffer_virtualaddr(dst_buf, dst_buf_size);
if (src_handle == 0 || dst_handle == 0) {
printf("importbuffer failed!\n");
goto release_buffer;
}
/* Step 3: 封装成 rga_buffer_t */
src_img = wrapbuffer_handle(src_handle, src_width, src_height, src_format);
dst_img = wrapbuffer_handle(dst_handle, dst_width, dst_height, dst_format);
/* Step 4: 校验 */
ret = imcheck(src_img, dst_img, {}, {});
if (IM_STATUS_NOERROR != ret) {
printf("%d, check error! %s", __LINE__, imStrError((IM_STATUS)ret));
return -1;
}
/* Step 5: 执行 */
ret = imcopy(src_img, dst_img);
if (ret == IM_STATUS_SUCCESS) {
printf("rga_copy_demo running success!\n");
} else {
printf("rga_copy_demo running failed, %s\n", imStrError((IM_STATUS)ret));
goto release_buffer;
}
printf("output [0x%x, 0x%x, 0x%x, 0x%x]\n", dst_buf[0], dst_buf[1], dst_buf[2], dst_buf[3]);
write_image_to_file(dst_buf, LOCAL_FILE_PATH, dst_width, dst_height, dst_format, 0);
release_buffer:
/* Step 6: 释放 */
if (src_handle) releasebuffer_handle(src_handle);
if (dst_handle) releasebuffer_handle(dst_handle);
if (src_buf) free(src_buf);
if (dst_buf) free(dst_buf);
return ret;
}
5.2 官方推荐 demo:rga_allocator_dma_demo.cpp
用 dma_heap 分配非 cache 内存,性能比 virtualaddr 好,是推荐路径。
#include <iostream>
#include <fstream>
#include <sstream>
#include <cstddef>
#include <cmath>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#include "im2d.h"
#include "RgaUtils.h"
#include "utils.h"
#include "dma_alloc.h"
#define LOCAL_FILE_PATH "/data"
int main(void) {
int ret = 0;
int src_width = 1280, src_height = 720, src_format = RK_FORMAT_RGBA_8888;
int dst_width = 1280, dst_height = 720, dst_format = RK_FORMAT_RGBA_8888;
int src_buf_size, dst_buf_size;
char *src_buf, *dst_buf;
int src_dma_fd, dst_dma_fd;
rga_buffer_t src = {}, dst = {};
im_rect src_rect = {}, dst_rect = {};
rga_buffer_handle_t src_handle, dst_handle;
src_buf_size = src_width * src_height * get_bpp_from_format(src_format);
dst_buf_size = dst_width * dst_height * get_bpp_from_format(dst_format);
/* Step 1: 用 dma_heap 分配非 cache 的 dma-buf,返回 fd 和虚拟地址 */
ret = dma_buf_alloc(DMA_HEAP_UNCACHE_PATH, src_buf_size, &src_dma_fd, (void **)&src_buf);
if (ret < 0) { printf("alloc src failed!\n"); return -1; }
ret = dma_buf_alloc(DMA_HEAP_UNCACHE_PATH, dst_buf_size, &dst_dma_fd, (void **)&dst_buf);
if (ret < 0) { printf("alloc dst failed!\n"); dma_buf_free(src_buf_size, &src_dma_fd, src_buf); return -1; }
if (0 != read_image_from_file(src_buf, LOCAL_FILE_PATH, src_width, src_height, src_format, 0)) {
draw_rgba(src_buf, src_width, src_height);
}
memset(dst_buf, 0x33, dst_buf_size);
/* Step 2: 导入 fd */
src_handle = importbuffer_fd(src_dma_fd, src_buf_size);
dst_handle = importbuffer_fd(dst_dma_fd, dst_buf_size);
if (src_handle == 0 || dst_handle == 0) {
printf("import dma_fd error!\n");
ret = -1;
goto free_buf;
}
/* Step 3: 封装 */
src = wrapbuffer_handle(src_handle, src_width, src_height, src_format);
dst = wrapbuffer_handle(dst_handle, dst_width, dst_height, dst_format);
/* Step 4: 校验 */
ret = imcheck(src, dst, src_rect, dst_rect);
if (IM_STATUS_NOERROR != ret) {
printf("%d, check error! %s", __LINE__, imStrError((IM_STATUS)ret));
goto release_buffer;
}
/* Step 5: 执行 */
ret = imcopy(src, dst);
if (ret == IM_STATUS_SUCCESS) {
printf("rga_allocator_dma_demo running success!\n");
} else {
printf("rga_allocator_dma_demo running failed, %s\n", imStrError((IM_STATUS)ret));
goto release_buffer;
}
printf("output [0x%x, 0x%x, 0x%x, 0x%x]\n", dst_buf[0], dst_buf[1], dst_buf[2], dst_buf[3]);
write_image_to_file(dst_buf, LOCAL_FILE_PATH, dst_width, dst_height, dst_format, 0);
release_buffer:
/* Step 6: 释放 */
if (src_handle > 0) releasebuffer_handle(src_handle);
if (dst_handle > 0) releasebuffer_handle(dst_handle);
free_buf:
dma_buf_free(src_buf_size, &src_dma_fd, src_buf);
dma_buf_free(dst_buf_size, &dst_dma_fd, dst_buf);
return 0;
}
dma_alloc.h 里定义的分配器路径:
#define DMA_HEAP_UNCACHE_PATH "/dev/dma_heap/system-uncached"
#define DMA_HEAP_PATH "/dev/dma_heap/system"
#define DMA_HEAP_DMA32_UNCACHED_PATH "/dev/dma_heap/system-uncached-dma32"
#define DMA_HEAP_DMA32_PATH "/dev/dma_heap/system-dma32"
#define CMA_HEAP_UNCACHED_PATH "/dev/dma_heap/cma-uncached"
#define RV1106_CMA_HEAP_PATH "/dev/rk_dma_heap/rk-dma-heap-cma"
四个路径的选择:
| 路径 | 什么时候用 |
|---|---|
system-uncached | 推荐,非 cache,性能最好 |
system | 默认,cacheable,需要手动同步 cache |
system-uncached-dma32 | Color Fill / ROP / colorkey 等 RGA2 特有功能必须用这个(4G 内) |
system-dma32 | 4G 内的 cacheable 内存 |
5.3 关于“缩放 + 换格式”能否一步做完
imresize 要求 dst 的格式与 src 相同。所以“NV12 → RGB888 同时缩放”这类组合需求,官方方案有两个:
两步走:先 imresize 到中间 NV12,再 imcvtcolor 转 RGB888。
用 improcess() 一步完成:它是所有其他 API 的基础。
六、逐个操作:参数与坑位速查
6.1 imcopy —— 拷贝
IM_STATUS imcopy(const rga_buffer_t src, rga_buffer_t dst,
int sync = 1, int *release_fence_fd = NULL);
最简单的操作,用于验证六步法流程是否通。对应 demo:rga_copy_demo.cpp。
6.2 imresize —— 缩放
IM_STATUS imresize(const rga_buffer_t src, rga_buffer_t dst,
double fx = 0, double fy = 0,
int interpolation = IM_INTERP_DEFAULT,
int sync = 1, int *release_fence_fd = NULL);
要点:
- 不传 fx/fy 时,自动按
dst.width / src.width和dst.height / src.height缩放 - 倍率限制:RK3588 上 RGA2 支持 1/16 ~ 16,RGA3 只支持 1/8 ~ 8。超过 8 倍需要走 RGA2
- 坑:用 fx/fy 倍率缩放时,YUV 等有宽高对齐要求的格式会强制向下对齐,可能改变预期缩放效果
对应 demo:
rga_resize_demo.cpp:基础缩放 720p → 1080prga_resize_rect_demo.cpp:用improcess把源图缩放到目标图的四个区域(拼接效果)rga_resize_uv_downsampling_demo.cpp:YUV422 → YUV420 均值降采样
rga_resize_rect_demo.cpp 的用法值得记:
dst_rect[0] = {0, 0, dst_width/2, dst_height/2};
dst_rect[1] = {dst_width/2, 0, dst_width/2, dst_height/2};
dst_rect[2] = {0, dst_height/2, dst_width/2, dst_height/2};
dst_rect[3] = {dst_width/2, dst_height/2, dst_width/2, dst_height/2};
for (int i = 0; i < 4; i++) {
ret = imcheck(src_img, dst_img, {}, dst_rect[i]);
if (IM_STATUS_NOERROR != ret) { /* ... */ }
ret = improcess(src_img, dst_img, {}, {}, dst_rect[i], {}, IM_SYNC);
}
6.3 imcrop —— 裁剪
IM_STATUS imcrop(const rga_buffer_t src, rga_buffer_t dst,
im_rect rect, int sync = 1, int *release_fence_fd = NULL);
rect 指定裁剪区域,x、y、width、height 全部 ≥ 2。
对应 demo:rga_crop_demo.cpp、rga_crop_rect_demo.cpp。
6.4 imtranslate —— 平移
IM_STATUS imtranslate(const rga_buffer_t src, rga_buffer_t dst,
int x, int y, int sync = 1, int *release_fence_fd = NULL);
注意:src 和 dst 宽高必须一致,超出部分会被裁剪。
6.5 imcvtcolor —— 格式与色域转换
IM_STATUS imcvtcolor(rga_buffer_t src, rga_buffer_t dst,
int sfmt, int dfmt,
int mode = IM_COLOR_SPACE_DEFAULT,
int sync = 1, int *release_fence_fd = NULL);
可选的 mode(来自 im2d_type.h):
IM_YUV_TO_RGB_BT601_LIMIT = 1 << 0
IM_YUV_TO_RGB_BT601_FULL = 2 << 0
IM_YUV_TO_RGB_BT709_LIMIT = 3 << 0
IM_RGB_TO_YUV_BT601_FULL = 1 << 2
IM_RGB_TO_YUV_BT601_LIMIT = 2 << 2
IM_RGB_TO_YUV_BT709_LIMIT = 3 << 2
/* 也可以直接用这些: */
IM_RGB_FULL_RANGE = IM_RGB_FULL
IM_YUV_BT601_LIMIT_RANGE
IM_YUV_BT601_FULL_RANGE
IM_YUV_BT709_LIMIT_RANGE
IM_YUV_BT2020_LIMIT_RANGE /* 只有 RGA3 支持 BT.2020 */
IM_COLOR_SPACE_DEFAULT = 0
音视频方向的“头号色偏坑”:
- 新版 librga 默认:RGB2YUV 与 YUV2RGB 都是 BT.601-limit_range
- 旧版 librga 默认:RGB2YUV = BT.601-full_range,YUV2RGB = BT.709-limit_range
- 新版 librga 配了旧驱动 → 严重色偏(偏粉 / 偏绿)
建议:不要依赖默认值,显式传 mode,并与源数据的 range 保持一致。
三种官方 demo 的写法:
写法一:不传 mode,用默认(rga_cvtcolor_demo.cpp)
ret = imcvtcolor(src_img, dst_img, src_format, dst_format);
写法二:用 imsetColorSpace 单独设(rga_cvtcolor_csc_demo.cpp)
imsetColorSpace(&src_img, IM_RGB_FULL_RANGE);
imsetColorSpace(&dst_img, IM_YUV_BT709_LIMIT_RANGE);
ret = imcvtcolor(src_img, dst_img, src_format, dst_format);
写法三:直接传 mode(rga_cvtcolor_gray256_demo.cpp)
/* RGB → Y400 灰度图,必须用 full range,否则 256 阶灰阶会被裁掉 */
ret = imcvtcolor(src_img, dst_img, src_format, dst_format, IM_RGB_TO_YUV_BT601_FULL);
6.6 imrotate / imflip —— 旋转与镜像
IM_STATUS imrotate(const rga_buffer_t src, rga_buffer_t dst,
int rotation, int sync = 1, int *release_fence_fd = NULL);
/* rotation: IM_HAL_TRANSFORM_ROT_90 / _180 / _270 */
IM_STATUS imflip(const rga_buffer_t src, rga_buffer_t dst,
int mode, int sync = 1, int *release_fence_fd = NULL);
/* mode: IM_HAL_TRANSFORM_FLIP_H / _V / _H_V */
旋转 90/270 必须交换宽高,否则图像会被拉伸。rgaImDemo.cpp 里的写法是:
if (IM_HAL_TRANSFORM_ROT_90 == rotate || IM_HAL_TRANSFORM_ROT_270 == rotate) {
dst.width = src.height;
dst.height = src.width;
dst.wstride = src.hstride;
dst.hstride = src.wstride;
}
对应 demo:
rga_transform_rotate_demo.cpp:旋转rga_transform_flip_demo.cpp:镜像rga_transform_rotate_flip_demo.cpp:旋转 + 镜像
6.7 合成:imblend / imcomposite
/* A + B → B:前景 srcA 与背景 dst 混合,结果输出回 dst */
IM_STATUS imblend(const rga_buffer_t fg, rga_buffer_t bg,
int mode = IM_ALPHA_BLEND_SRC_OVER,
int sync = 1, int *release_fence_fd = NULL);
/* A + B → C:前景 srcA + 背景 srcB → 输出 dst */
IM_STATUS imcomposite(const rga_buffer_t srcA, const rga_buffer_t srcB,
rga_buffer_t dst,
int mode = IM_ALPHA_BLEND_SRC_OVER,
int sync = 1, int *release_fence_fd = NULL);
核心公式:
Rc = Sc × Sf + Dc × Df
Ra = Sa × Sf + Da × Df
可选的 mode(来自 im2d_type.h):
IM_ALPHA_BLEND_SRC_OVER = 1 << 6 /* 默认 */
IM_ALPHA_BLEND_SRC = 1 << 7
IM_ALPHA_BLEND_DST = 1 << 8
IM_ALPHA_BLEND_SRC_IN = 1 << 9
IM_ALPHA_BLEND_DST_IN = 1 << 10
IM_ALPHA_BLEND_SRC_OUT = 1 << 11
IM_ALPHA_BLEND_DST_OUT = 1 << 12
IM_ALPHA_BLEND_DST_OVER = 1 << 13
IM_ALPHA_BLEND_SRC_ATOP = 1 << 14
IM_ALPHA_BLEND_DST_ATOP = 1 << 15
IM_ALPHA_BLEND_XOR = 1 << 16
两个最常见的“没效果”问题:
- 叠加看不出效果:检查输入两张图的 alpha 是否都是 0xFF。前景 alpha = 0xFF 时结果就是前景直接覆盖背景
- 前景 alpha = 0x0 却不是全透明:因为颜色值没有预乘 alpha,需要加
IM_ALPHA_BLEND_PRE_MUL标志
官方 demo 里的写法(rga_alpha_demo.cpp):
ret = imblend(fg_img, bg_img,
IM_ALPHA_BLEND_SRC_OVER | IM_ALPHA_BLEND_PRE_MUL);
合成不支持 YUV ↔ YUV:imblend 的 dst 不支持 YUV,imcomposite 的 srcB 不支持 YUV。
对应 demo:
rga_alpha_demo.cpp:双通道合成,RGB + RGBrga_alpha_3channel_demo.cpp:三通道合成,A + B → Crga_alpha_global_alpha_demo.cpp:用imsetOpacity()设置全局 alpharga_alpha_colorkey_demo.cpp:色键抠图rga_alpha_yuv_demo.cpp:YUV 上叠 RGB(重点)rga_alpha_osd_demo.cpp:OSD 字幕叠加(重点)
6.8 叠字幕(OSD):rga_alpha_yuv_demo.cpp
| 输出格式 | 用什么 | 模式 |
|---|---|---|
| RGB | imblend() | src over |
| YUV | imcomposite() | dst over |
为什么不一样? RGA2 有三个图像通道:src、src1/pat、dst。src 支持 YUV2RGB,src1/pat 和 dst 只支持 RGB2YUV。而 RGA 内部的叠加必须在 RGB 域进行。所以 RGB 叠加到 YUV 上,唯一路径是:src(YUV,作背景)→ YUV2RGB → RGB 域叠加 → dst 通道 RGB2YUV → 输出 YUV。
rga_alpha_yuv_demo.cpp 用 improcess 一步完成:
usage = IM_SYNC | IM_ALPHA_BLEND_DST_OVER | IM_ALPHA_BLEND_PRE_MUL;
ret = improcess(fg_img, output_img, bg_img,
fg_rect, output_rect, bg_rect,
-1, NULL, NULL, usage);
6.9 其它常用操作
| API | 要点 | 对应 demo |
|---|---|---|
imcolorkey | 将符合色键过滤条件的像素 alpha 置零后与目标做 alpha 混合。颜色范围排列为 ABGR | rga_alpha_colorkey_demo.cpp |
imrop | IM_ROP_AND / OR / NOT_DST / NOT_SRC / XOR / NOT_XOR | rop_demo |
imfill | color 按 RGBA 排列,由高到低位依次是 A、B、G、R。填充区域 rect 宽高须 ≥ 2 | rga_fill_demo.cpp |
imrectangle | rect 描述边框外径,thickness 为线宽;负数(如 −1)表示画实心矩形 | rga_fill_rectangle_demo.cpp |
imrectangleArray | 一次画多个矩形框 | rga_fill_rectangle_array_demo.cpp |
immakeBorder | IM_BORDER_CONSTANT / REFLECT / WRAP | rga_padding_demo.cpp |
immosaic | IM_MOSAIC_8/16/32/64/128(RK3588 硬件不支持) | mosaic_demo |
imquantize | 仅 RV1126 / RV1109 | — |
imgaussianBlur | 高斯模糊,kernel 宽高必须为正奇数 | — |
impalette | 调色板转换 | — |
imcfa | CFA(Color Filter Array)处理 | — |
七、任务模式(Job):把多个操作打包成一次提交
硬件一次只能顺序工作一个任务。任务模式让你把多个操作攒起来,一次提交到驱动,由硬件连续执行。
官方 demo 写法(rga_fill_rectangle_task_demo.cpp):
im_job_handle_t job_handle;
/* 1. 创建任务 */
job_handle = imbeginJob();
if (job_handle <= 0) {
printf("job begin failed![%d], %s\n", job_handle, imStrError());
goto release_buffer;
}
/* 2. 添加第一个任务:填充绿色实心矩形 */
dst_rect[0] = {0, 0, 300, 200};
ret = imcheck({}, dst, {}, dst_rect[0], IM_COLOR_FILL);
if (IM_STATUS_NOERROR != ret) {
printf("%d, check error! %s", __LINE__, imStrError((IM_STATUS)ret));
imcancelJob(job_handle); /* 失败必须取消 */
goto release_buffer;
}
ret = imrectangleTask(job_handle, dst, dst_rect[0], 0xff00ff00, -1);
if (ret != IM_STATUS_SUCCESS) {
printf("add fill task failed, %s\n", imStrError((IM_STATUS)ret));
imcancelJob(job_handle);
goto release_buffer;
}
/* 3. 添加第二个任务:画红色空心矩形 */
dst_rect[0] = {100, 100, 300, 200};
ret = imcheck({}, dst, {}, dst_rect[0], IM_COLOR_FILL);
if (IM_STATUS_NOERROR != ret) {
imcancelJob(job_handle);
goto release_buffer;
}
ret = imrectangleTask(job_handle, dst, dst_rect[0], 0xffff0000, 4);
if (ret != IM_STATUS_SUCCESS) {
imcancelJob(job_handle);
goto release_buffer;
}
/* 4. 一次性提交并执行(默认 IM_SYNC) */
ret = imendJob(job_handle);
if (ret == IM_STATUS_SUCCESS) {
printf("job[%d] running success!\n", job_handle);
} else {
printf("job[%d] running failed, %s\n", job_handle, imStrError((IM_STATUS)ret));
goto release_buffer;
}
Job 句柄一定要收尾。 官方明确:配置失败后须使用 imcancelJob 释放当前任务句柄,避免内存泄漏。imendJob 成功后会自动删除任务资源;但失败或中途放弃时必须手动 imcancelJob。上面每个 imcheck 失败都调 imcancelJob,就是这个原则。
对应 demo:
rga_copy_splice_task_demo.cpp:用 task 做图像拼接rga_fill_rectangle_task_demo.cpp:用 task 画两个矩形rga_fill_rectangle_task_array_demo.cpp:用 task + array 画两组矩形
八、improcess:万能底层接口
其他所有 API 都是基于 improcess 开发的。当你需要一次调用实现多种功能时,用它。
IM_STATUS improcess(rga_buffer_t src, rga_buffer_t dst, rga_buffer_t pat,
im_rect srect, im_rect drect, im_rect prect,
int acquire_fence_fd, int *release_fence_fd,
im_opt_t *opt_ptr, int usage);
C 接口还有一个简化版(im2d_single.h):
IM_C_API IM_STATUS improcess(rga_buffer_t src, rga_buffer_t dst, rga_buffer_t pat,
im_rect srect, im_rect drect, im_rect prect, int usage);
usage 位域(来自 im2d_type.h):
/* 旋转 */
IM_HAL_TRANSFORM_ROT_90 = 1 << 0
IM_HAL_TRANSFORM_ROT_180 = 1 << 1
IM_HAL_TRANSFORM_ROT_270 = 1 << 2
IM_HAL_TRANSFORM_FLIP_H = 1 << 3
IM_HAL_TRANSFORM_FLIP_V = 1 << 4
IM_HAL_TRANSFORM_FLIP_H_V = 1 << 5
IM_HAL_TRANSFORM_MASK = 0x3f
/* 混合 */
IM_ALPHA_BLEND_SRC_OVER = 1 << 6 /* 默认 */
IM_ALPHA_BLEND_SRC = 1 << 7
IM_ALPHA_BLEND_DST = 1 << 8
IM_ALPHA_BLEND_SRC_IN = 1 << 9
IM_ALPHA_BLEND_DST_IN = 1 << 10
IM_ALPHA_BLEND_SRC_OUT = 1 << 11
IM_ALPHA_BLEND_DST_OUT = 1 << 12
IM_ALPHA_BLEND_DST_OVER = 1 << 13
IM_ALPHA_BLEND_SRC_ATOP = 1 << 14
IM_ALPHA_BLEND_DST_ATOP = 1 << 15
IM_ALPHA_BLEND_XOR = 1 << 16
IM_ALPHA_BLEND_MASK = 0x1ffc0
IM_ALPHA_COLORKEY_NORMAL = 1 << 17
IM_ALPHA_COLORKEY_INVERTED = 1 << 18
IM_ALPHA_COLORKEY_MASK = 0x60000
IM_SYNC = 1 << 19
IM_CROP = 1 << 20 /* Unused */
IM_COLOR_FILL = 1 << 21
IM_COLOR_PALETTE = 1 << 22
IM_NN_QUANTIZE = 1 << 23
IM_ROP = 1 << 24
IM_ALPHA_BLEND_PRE_MUL = 1 << 25
IM_ASYNC = 1 << 26
IM_MOSAIC = 1 << 27
IM_OSD = 1 << 28
IM_PRE_INTR = 1 << 29
IM_ALPHA_BIT_MAP = 1 << 30
IM_GAUSS = 1 << 31
用法建议:先用封装好的 imresize / imblend 等跑通语义,再用 improcess 合并优化。
rga_resize_rect_demo.cpp 用 improcess 做缩放 + 拼接:
ret = improcess(src_img, dst_img, {}, {}, dst_rect[i], {}, IM_SYNC);
rga_alpha_yuv_demo.cpp 用 improcess 做 YUV 上的 dst-over 合成:
usage = IM_SYNC | IM_ALPHA_BLEND_DST_OVER | IM_ALPHA_BLEND_PRE_MUL;
ret = improcess(fg_img, output_img, bg_img,
fg_rect, output_rect, bg_rect,
-1, NULL, NULL, usage);
rga_config_single_core_demo.cpp 用 improcess + im_opt_t 指定核心:
im_opt_t opt = {};
opt.core = IM_SCHEDULER_RGA3_CORE0;
ret = improcess(src_img, dst_img, {}, {}, {}, {}, -1, NULL, &opt, IM_SYNC);
九、异步模式与 fence
把 API 的 sync 形参设为 0 即进入异步调用模式:效果相当于 OpenGL 中的 glFlush;再调用 imsync(release_fence_fd) 则达到 glFinish 的效果。
官方 demo 写法(rga_async_demo.cpp):
int acquire_fence_fd, release_fence_fd;
/* (1) 异步拷贝 src → tmp,拿到 release_fence */
release_fence_fd = -1;
ret = imcopy(src_img, tmp_img, 0, &release_fence_fd);
/* (2) 用 (1) 的 release_fence 作为 (2) 的 acquire_fence */
acquire_fence_fd = release_fence_fd;
release_fence_fd = -1;
ret = improcess(tmp_img, dst_img, {}, {}, {}, {},
acquire_fence_fd, &release_fence_fd, NULL, IM_ASYNC);
/* (3) 等待最后一次操作的 release_fence */
ret = imsync(release_fence_fd);
注意:RK3588 用的是 multicore 驱动,有 fence 机制,可以放心用异步。旧驱动(RGA Device Driver、RGA2 Device Driver)上 imsync() 会等该进程所有异步任务,反而可能更慢。
十、多核调度:指定核心
RK3588 上有三颗 RGA 核(1 颗 RGA2 + 2 颗 RGA3),librga 默认会自动分配。某些场景(比如规避 RGA2/RGA3 缩放算法差异导致的画面抖动)需要指定核心。
10.1 线程级配置
官方 demo(rga_config_thread_core_demo.cpp):
/* 配置当前线程只用 RGA3 core0 或 core1 */
imconfig(IM_CONFIG_SCHEDULER_CORE,
IM_SCHEDULER_RGA3_CORE0 | IM_SCHEDULER_RGA3_CORE1);
ret = imcopy(src_img, dst_img);
可用的核心值(来自 im2d_type.h):
IM_SCHEDULER_RGA3_CORE0 = 1 << 0
IM_SCHEDULER_RGA3_CORE1 = 1 << 1
IM_SCHEDULER_RGA2_CORE0 = 1 << 2
IM_SCHEDULER_RGA2_CORE1 = 1 << 3
IM_SCHEDULER_RGA3_DEFAULT = IM_SCHEDULER_RGA3_CORE0
IM_SCHEDULER_RGA2_DEFAULT = IM_SCHEDULER_RGA2_CORE0
IM_SCHEDULER_MASK = 0xf
IM_SCHEDULER_DEFAULT = 0
10.2 单任务指定
官方 demo(rga_config_single_core_demo.cpp):
im_opt_t opt = {};
opt.core = IM_SCHEDULER_RGA3_CORE0;
ret = improcess(src_img, dst_img, {}, {}, {}, {}, -1, NULL, &opt, IM_SYNC);
使用警告:官方反复强调,priority、core 权限极高,操作不当可能导致系统崩溃或死锁,建议仅用于开发调试阶段,极度不建议在实际产品场景进行配置。唯一的例外是明确必要且经过验证的场景。
十一、RK3588 上哪些 API 受硬件限制
虽然 librga 1.10.0 把所有 API 都实现了,但 RK3588 的硬件对部分 API 有限制:
| API / 功能 | 限制 | demo 里的对策 |
|---|---|---|
imresize 倍率 > 8 | RGA3 只支持 1/8~8,需回退到 RGA2 | — |
imquantize | RK3588 硬件不支持(仅 RV1126/RV1109) | — |
immosaic | RK3588 硬件不支持 | — |
imosd | 官方标注仅部分平台支持 | — |
imcolorkey / imrop / Color Fill | 仅 RGA2 支持。任务被分到 RGA3 会失败 | demo 里用 DMA_HEAP_DMA32_UNCACHED_PATH 分配 4G 内内存 |
imblend 的 dst | 不支持 YUV 格式 | rga_alpha_yuv_demo.cpp 用 imcomposite |
imcomposite 的 srcB | 不支持 YUV 格式 | 同上 |
Color Fill 的场景要特别注意:rga_fill_demo.cpp、rga_fill_rectangle_demo.cpp、rga_fill_rectangle_task_demo.cpp、rga_fill_rectangle_task_array_demo.cpp 这几个 demo 都用了 DMA_HEAP_DMA32_UNCACHED_PATH,也就是专门分配 4G 以内的内存。因为 Color Fill 是 RGA2 特有的功能,而 RGA2 的 IOMMU 只支持 32bit。这个坑第五篇会详细展开。
代码里的注释也写明了这一点:
/*
* Allocate dma_buf within 4G from dma32_heap,
* return dma_fd and virtual address.
* ColorFill can only be used on buffers within 4G.
*/
ret = dma_buf_alloc(DMA_HEAP_DMA32_UNCACHED_PATH, dst_buf_size, &dst_dma_fd, (void **)&dst_buf);
十二、本篇小结
第一,六步法:分配 dma-buf fd → importbuffer_fd → wrapbuffer_handle → imcheck(调试期)→ 执行 → releasebuffer_handle。
第二,实宽高(aw/ah)vs 虚宽高(vw/vh):x+w ≤ wstride、y+h ≤ hstride,YUV 还要 2 对齐。
第三,内存类型性能:phy > fd > va,用 fd;分配时禁用 cache。dma_heap 的四个路径各有用途,Color Fill 必须用 system-uncached-dma32。
第四,旋转 90/270 必须交换宽高;imresize 要求 src/dst 格式一致。
第五,色域显式指定 mode;默认 BT.601 limit range。也可以用 imsetColorSpace() 单独设。
第六,YUV 上叠图用 imcomposite(dst over),RGB 上用 imblend(src over)。
第七,Job 失败必须 imcancelJob。
第八,RK3588 上 Color Fill / ROP / colorkey 只有 RGA2 能做,需要 4G 以内的内存。demo 里用 DMA_HEAP_DMA32_UNCACHED_PATH 就是这个原因。