一、RGA模块
1.1 RGA模块的定义
RGA = Raster Graphic Acceleration Unit,瑞芯微 2D 硬件图形加速器,RV1126 芯片内置独立硬件图像处理单元,纯硬件运算、不占用 CPU,核心能力: 图像缩放、裁剪、旋转 (0/90/180/270°)、YUV/RGB 格式互转、镜像翻转,是 RKMedia 媒体链路标配模块。
比方说:要把一个原分辨率1920 * 1080的视频压缩成1280 * 720的视频,此时就要用到RGA模块了。比方说下图:
比方说把一个视频用RGA进行旋转,如下图:
比方说把一个视频用RGA进行镜像处理(镜像就是指物体相对于该物体反射出来的虚像,就像一个人照镜子,反射出来的图像),如下图:
要注意的是RGA是直接对VI的图像进行处理,并不是对VENC数据进行处理,这一点要非常注意。
1.2 RGA结构体的定义
1. 区域属性结构体:RGA_INFO_S(输入 / 输出图像描述)(单幅图像信息)
typedef struct rkRGA_INFO_S { IMAGE_TYPE_E imgType; // 图像格式:NV12/NV21/RGB888等 RK_U32 u32X; // 裁剪起始X坐标(左上角) RK_U32 u32Y; // 裁剪起始Y坐标 RK_U32 u32Width; // 有效图像宽 RK_U32 u32Height; // 有效图像高 RK_U32 u32HorStride; // 行跨度(内存宽度,≥u32Width) RK_U32 u32VirStride; // 列跨度(内存高度,≥u32Height) } RGA_INFO_S;2. RGA 通道总属性:RGA_ATTR_S(创建 RGA 通道入参)
typedef struct rkRGA_ATTR_S { RGA_INFO_S stImgIn; // 输入端图像配置(来自VI) RGA_INFO_S stImgOut; // 输出端图像配置(缩放后) RK_U16 u16Rotaion; // 旋转角度:0/90/180/270 RK_BOOL bEnBufPool; // RK_TRUE:开启硬件缓冲池(必须开,你的代码启用) RK_U16 u16BufPoolCnt; // 缓冲池缓存块数量(根源:你之前丢帧就是这个值太小) RGA_FLIP_E enFlip; // 镜像:上下/左右翻转(默认0关闭) } RGA_ATTR_S;3. RGA 镜像翻转枚举rkRGA_FLIP_E
这个枚举用来控制画面镜像翻转,填在RGA_ATTR_S.enFlip参数里,硬件 RGA 直接完成翻转,不占用 CPU。
| 枚举宏 | 含义 | 白话解释 |
|---|---|---|
RGA_FLIP_NULL | 不翻转(默认值) | 原图原样输出,左右上下都不变,你的代码默认填这个 |
RGA_FLIP_H | Horizontal 水平翻转(左右镜像) | 像照镜子,画面左右反过来,摄像头左右反了就开这个 |
RGA_FLIP_V | Vertical 垂直翻转(上下镜像) | 画面上下颠倒,倒立画面用这个修正 |
RGA_FLIP_HV | H+V 同时翻转 | 先左右翻、再上下翻,等效旋转 180° |
代码使用示例
// 1、默认正常画面,不翻转 rga_attr.enFlip = RGA_FLIP_NULL; // 2、开启左右镜像 //rga_attr.enFlip = RGA_FLIP_H; // 3、开启上下镜像 //rga_attr.enFlip = RGA_FLIP_V; // 4、左右+上下一起翻转 //rga_attr.enFlip = RGA_FLIP_HV;1.3 RKMedia RGA 常用 API 定义
// 1. 创建RGA通道(你代码在用) RK_S32 RK_MPI_RGA_CreateChn(RK_S32 s32ChnId, RGA_ATTR_S *pstAttr); // 2. 销毁通道 RK_S32 RK_MPI_RGA_DestroyChn(RK_S32 s32ChnId); // 3. 系统绑定:VI输出 → RGA输入(Bind接口) RK_S32 RK_MPI_SYS_Bind(MPP_CHN_S *pstSrcChn, MPP_CHN_S *pstDstChn); // 4. 取RGA输出缓存(线程取帧接口) MEDIA_BUFFER RK_MPI_SYS_GetMediaBuffer(RK_ID_RGA, RK_S32 s32ChnId, RK_S32 s32TimeoutMs); // 5. 释放缓存(必须调用,归还到RGA缓冲池) RK_VOID RK_MPI_MB_ReleaseBuffer(MEDIA_BUFFER mb);二、获取RGA数据
基于瑞芯微 RV1126 平台 RKMedia 多媒体框架,实现摄像头原图采集→RGA 硬件缩放→裸 NV12 文件本地保存整套链路,全程 RGA 由硬件加速缩放不占用 CPU 资源,拆解从模块初始化到帧数据落地的完整开发流程,适配初学者理解 RKMedia 模块绑定思想。 整体执行流程:VI模块初始化 → RGA模块初始化 → VI与RGA通道绑定 → 启动VI数据流 → 子线程循环拉取RGA输出帧并落盘
2.1 流程
2.1 VI 视频采集模块初始化
VI(Video Input)负责对接 MIPI 摄像头,从 ISP 获取原始 1920×1080 NV12 图像数据,初始化分为参数配置→属性设置→通道使能三步,暂不启动数据流,避免未绑定 RGA 造成数据丢失。
- 参数填充:VI_CHN_ATTR_S该结构体配置摄像头分辨率、图像格式、缓存数量、设备节点名称:
- pcVideoNode:绑定内核 V4L2 设备节点
rkispp_scale0; - u32Width/u32Height:原始采集分辨率 1920×1080;
- enPixFmt:指定输出像素格式 NV12;
- enBufType:内存映射模式 MMAP,RKMedia 标准采集配置;
- u32BufCnt:VI 底层缓存帧数 3,缓存原始摄像头画面。
- pcVideoNode:绑定内核 V4L2 设备节点
- API 配置通道
// 把配置参数写入VI通道 RK_MPI_VI_SetChnAttr(PIPE_ID, VI_CHN_ID, &vi_chn_attr); // 硬件通道使能,打开V4L2设备 RK_MPI_VI_EnableChn(PIPE_ID, VI_CHN_ID);2.2 RGA 硬件缩放模块初始化
RGA(2D 硬件图形加速器),实现原图 1920×1080 → 720P (1280×720) 硬件缩放,初始化核心是填充RGA_ATTR_S通道属性结构体,再创建 RGA 硬件通道。
- RGA_ATTR_S 结构体分层配置结构体由输入图像区域 RGA_INFO_S、输出图像区域 RGA_INFO_S、通道全局配置三部分组成:
- stImgIn:输入画面参数,和 VI 输出保持一致:1920×1080、NV12 格式,HorStride=1920,VirStride=1080;
- stImgOut:缩放后输出画面:1280×720、NV12 格式,HorStride=1280,VirStride=720;
- u16Rotation:画面旋转角度(0/90/180/270),项目填 0 不旋转;
- enFlip:镜像枚举 RGA_FLIP_E,可选无翻转 / 左右 / 上下 / 双向翻转,默认
RGA_FLIP_NULL; - bEnBufPool:
RK_TRUE开启 RGA 硬件输出缓存池; - u16BufPoolCnt:缓存池帧数量,建议 8~10,解决写盘速率不足导致的丢帧告警。
- 创建 RGA 硬件通道 API
调用后内核根据配置申请 RGA 硬件资源与缓存内存,通道就绪等待上游 VI 输入图像。RK_MPI_RGA_CreateChn(RGA_CHN_ID, &rga_attr);
2.3 RK_MPI_SYS_Bind:VI 与 RGA 通道绑定
RKMedia 依靠RK_MPI_SYS_Bind完成模块数据流串联,实现VI 输出缓存自动流转至 RGA 输入,无需 CPU 搬运图像内存,是媒体链路的核心接口。
- MPP_CHN_S 通道描述体分别封装源(VI)、目的(RGA)的模块 ID 与通道号:
// VI源节点 MPP_CHN_S vi_chn = {.enModId = RK_ID_VI, .s32ChnId = VI_CHN_ID}; // RGA目的节点 MPP_CHN_S rga_chn = {.enModId = RK_ID_RGA, .s32ChnId = RGA_CHN_ID}; - 绑定 API 原型与作用
RK_S32 RK_MPI_SYS_Bind(MPP_CHN_S *pSrcChn, MPP_CHN_S *pDstChn);- pSrcChn:上游数据源模块(VI);pDstChn:下游处理模块(RGA);
- 绑定成功后,VI 产生的每一帧图像自动通过 DMA 硬件直送 RGA 输入端,RGA 硬件自动完成缩放处理,处理完成的帧存入 RGA 自身缓存池,等待应用层读取。
反向解绑接口:
RK_MPI_SYS_UnBind,程序退出时调用,释放模块链路。
2.4 启动 VI 数据流 + 子线程采集 RGA 帧数据
2.4.1 绑定完成后开启 VI 推流
RK_MPI_VI_StartStream(PIPE_ID, VI_CHN_ID);VI 正式从摄像头源源不断采集图像,经由绑定链路自动送入 RGA 做缩放。
2.4.2 新建子线程循环取帧落盘
单独开辟采集线程,避免阻塞主线程休眠,核心接口RK_MPI_SYS_GetMediaBuffer从 RGA 缓存池取出缩放完成的 NV12 裸数据:
- 接口说明
MEDIA_BUFFER RK_MPI_SYS_GetMediaBuffer(RK_S32 enModId,RK_S32 s32ChnId,RK_S32 timeout);- enModId:固定
RK_ID_RGA,指定从 RGA 模块取数据; - s32ChnId:RGA 创建时的通道编号 RGA_CHN_ID;
- timeout:-1 代表阻塞等待,无帧时线程休眠,有帧立刻返回。
- enModId:固定
- 线程伪代码(落盘优化要点)
void *get_rga_data_thread(void *args) { // 使用wb二进制打开,避免文本模式篡改NV12二进制数据;/tmp为内存盘,大幅减少磁盘IO丢帧 FILE *fp = fopen("/tmp/vi_rga.nv12","wb"); if(!fp) return NULL; MEDIA_BUFFER mb; while(1) { mb = RK_MPI_SYS_GetMediaBuffer(RK_ID_RGA,RGA_CHN_ID,-1); if(!mb) break; // 写入原始NV12数据 fwrite(RK_MPI_MB_GetPtr(mb),1,RK_MPI_MB_GetSize(mb),fp); // 关键:释放缓存,归还至RGA缓存池,不释放会导致缓存耗尽丢帧 RK_MPI_MB_ReleaseBuffer(mb); } fclose(fp); return NULL; } - 主线程创建采集线程
pthread_t pid; pthread_create(&pid,NULL,get_rga_data_thread,NULL); // 主线程循环休眠,等待Ctrl+C退出 while(1) sleep(1);
2.5 程序退出资源释放规范
按下 Ctrl+C 结束程序时,按停止数据流→解绑通道→销毁模块顺序释放资源,防止 V4L2 设备占用:
RK_MPI_VI_StopStream(PIPE_ID,VI_CHN_ID); RK_MPI_SYS_UnBind(&vi_chn,&rga_chn); RK_MPI_VI_DisableChn(PIPE_ID,VI_CHN_ID); RK_MPI_RGA_DestroyChn(RGA_CHN_ID);三、代码
#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 "rkmedia_api.h" // RKMedia 核心API头文件 // ===================== 宏定义 ===================== #define PIPE_ID 0 // VI 管道ID,固定0 #define VI_CHN_ID 0 // VI 通道号,固定0 #define RGA_CHN_ID 0 // RGA 硬件通道号,固定0 // ===================== 线程函数 ===================== /** * @brief RGA数据采集线程:从RGA硬件通道取帧,保存为NV12裸流文件 * @param args 线程参数(未使用) * @return 线程退出指针 */ void *get_rga_data_thread(void *args) { // 以二进制只写方式打开文件,保存RGA输出的NV12数据 // wb:二进制写入,不破坏原始图像数据,必须用wb不能用w+ FILE *rga_nv12_file = fopen("vi_rga.nv12", "wb"); if (!rga_nv12_file) { // 文件打开失败直接退出线程 return NULL; } MEDIA_BUFFER mb; // 媒体缓存句柄 // 循环持续取RGA输出帧 while (1) { // 从 RGA 模块阻塞式获取一帧处理完成的数据 // RK_ID_RGA:模块ID // RGA_CHN_ID:RGA通道号 // -1:永久阻塞等待,直到有数据 mb = RK_MPI_SYS_GetMediaBuffer(RK_ID_RGA, RGA_CHN_ID, -1); if (!mb) { // 获取缓存失败,退出循环 printf("get_rga_buffer break...\n"); break; } printf("get_rga_buffer succeed...\n"); // 将缓存中的图像数据写入文件 // RK_MPI_MB_GetPtr(mb):获取数据指针 // RK_MPI_MB_GetSize(mb):获取数据长度 fwrite(RK_MPI_MB_GetPtr(mb), 1, RK_MPI_MB_GetSize(mb), rga_nv12_file); // 必须释放缓存!归还给RGA缓冲池,否则会耗尽缓存导致丢帧 RK_MPI_MB_ReleaseBuffer(mb); } fclose(rga_nv12_file); // 关闭文件 return NULL; } // ===================== 主函数 ===================== int main() { int ret; // 1. RKMedia系统初始化,必须第一个调用 RK_MPI_SYS_Init(); // ===================== 2. 初始化VI模块(摄像头采集) ===================== VI_CHN_ATTR_S vi_chn_attr; // VI通道属性结构体 vi_chn_attr.pcVideoNode = "rkispp_scale0"; // 摄像头节点(RV1126默认) vi_chn_attr.u32Width = 1920; // 摄像头输出宽度 1920 vi_chn_attr.u32Height = 1080; // 摄像头输出高度 1080 vi_chn_attr.enPixFmt = IMAGE_TYPE_NV12; // 输出格式 NV12(最常用) vi_chn_attr.enBufType = VI_CHN_BUF_TYPE_MMAP;// 内存映射模式(RK推荐) vi_chn_attr.u32BufCnt = 3; // VI缓存帧数(3帧足够) vi_chn_attr.enWorkMode = VI_WORK_MODE_NORMAL;// 正常工作模式 // 设置VI通道属性 ret = RK_MPI_VI_SetChnAttr(PIPE_ID, VI_CHN_ID, &vi_chn_attr); if (ret) { printf("VI_CHN_ATTR Set Failed...\n"); return -1; } else { printf("VI_CHN_ATTR Set Succeed...\n"); } // 使能VI通道(开启硬件) ret = RK_MPI_VI_EnableChn(PIPE_ID, VI_CHN_ID); if (ret) { printf("Enable_Vi Failed...\n"); return -1; } else { printf("Enable_Vi Succeed...\n"); } // ===================== 3. 初始化RGA模块(硬件缩放) ===================== RGA_ATTR_S rga_attr; // RGA通道配置结构体 // --- RGA输入配置:和VI输出完全一致 1920x1080 NV12 --- rga_attr.stImgIn.u32Width = 1920; rga_attr.stImgIn.u32Height = 1080; rga_attr.stImgIn.imgType = IMAGE_TYPE_NV12; rga_attr.stImgIn.u32X = 0; // 裁剪起始X(不裁剪填0) rga_attr.stImgIn.u32Y = 0; // 裁剪起始Y(不裁剪填0) rga_attr.stImgIn.u32VirStride = 1080; // 垂直跨度 = 高度 rga_attr.stImgIn.u32HorStride = 1920; // 水平跨度 = 宽度 // --- RGA输出配置:缩放到 1280x720 NV12 --- rga_attr.stImgOut.u32Width = 1280; rga_attr.stImgOut.u32Height = 720; rga_attr.stImgOut.imgType = IMAGE_TYPE_NV12; rga_attr.stImgOut.u32X = 0; rga_attr.stImgOut.u32Y = 0; rga_attr.stImgOut.u32VirStride = 720; rga_attr.stImgOut.u32HorStride = 1280; rga_attr.u16Rotaion = 0; // 旋转角度:0不旋转 rga_attr.u16BufPoolCnt = 8; // RGA缓存池数量(8帧,防丢帧) rga_attr.bEnBufPool = RK_TRUE; // 开启RGA硬件缓冲池(必须开启) // 创建RGA硬件通道 ret = RK_MPI_RGA_CreateChn(RGA_CHN_ID, &rga_attr); if (ret) { printf("RK_MPI_RGA_CreateChn Failed...\n"); return -1; } else { printf("RK_MPI_RGA_CreateChn Succeed...\n"); } // ===================== 4. 绑定 VI 和 RGA 模块 ===================== // 定义源模块:VI MPP_CHN_S vi_chn_s; vi_chn_s.enModId = RK_ID_VI; vi_chn_s.s32ChnId = VI_CHN_ID; // 定义目标模块:RGA MPP_CHN_S rga_chn_s; rga_chn_s.enModId = RK_ID_RGA; rga_chn_s.s32ChnId = RGA_CHN_ID; // 执行绑定:VI输出 → RGA输入(自动流转,无需CPU搬运) ret = RK_MPI_SYS_Bind(&vi_chn_s, &rga_chn_s); if (ret) { printf("RK_MPI_SYS_Bind Failed...\n"); return -1; } else { printf("RK_MPI_SYS_Bind Succeed...\n"); } // ===================== 5. 启动VI数据流 ===================== ret = RK_MPI_VI_StartStream(PIPE_ID, VI_CHN_ID); if (ret) { printf("Start Vi Failed...\n"); return -1; } else { printf("Start Vi Succeed...\n"); } // ===================== 6. 创建线程,读取RGA数据 ===================== pthread_t pid; pthread_create(&pid, NULL, get_rga_data_thread, NULL); // 主线程休眠,保持程序运行 while (1) { sleep(2); } // ===================== 7. 资源释放(正常退出时执行) ===================== RK_MPI_VI_DisableChn(PIPE_ID, VI_CHN_ID); // 关闭VI RK_MPI_RGA_DestroyChn(RGA_CHN_ID); // 销毁RGA return 0; }写好后进行编译,移植进板子,详细流程在上篇博客,这里不再赘述。
四、采集数据并播放
4.1 采集数据
移植进板子后进行运行
这便是采集成功了,文件夹里会出现对应的vi_rga.nv12的一个文件,这就是采集到的数据,如果出现采集了几帧数据就开始失败,那有可能是你选则保存问价的文件夹内存不够导致的,输入命令
df -h查看一下各个文件夹的内存,再做选择
4.2 数据播放
将vi_rga.nv12文件下载到ffmpeg的bin文件夹中
至于为什么放到这个文件夹中上篇博客也详细说了
win+r输入cmd,cd 进入这个bin文件夹
配套 PC 端 NV12 播放指令
ffplay -f rawvideo -pix_fmt nv12 -s 1280x720 vi_rga.nv12FFmpeg 直接播放 720P 裸流
播放完了就是这个效果
五、踩坑总结
坑 1:模块启动时序颠倒(高频错误)
错误写法:VI_Enable → StartStream → 再创建 RGA+BindVI 提前开启推流,画面源源不断输出,但下游 RGA 还未初始化绑定,图像无处挂载,内核丢帧,后续GetMediaBuffer频繁空指针。正确顺序:VI 初始化使能→RGA 创建→Bind 绑定→最后 StartStream 开流。
坑 2:文件打开模式错误,w + 存 NV12 裸数据
最初用fopen("xx","w+")文本模式写入 YUV/NV12,Linux 文本 IO 自动修正换行字节,原始裸流被篡改,视频花屏;同时文本 IO 读写效率低,加重磁盘阻塞丢帧。修正:二进制专用wb,优先存/tmp内存盘,大幅降低 IO 耗时。
坑 3:RGA 缓存池数量过小丢帧告警 drop buffer
初始u16BufPoolCnt=3,RGA 只有 3 帧缓存,线程写硬盘速度<摄像头出帧速度,缓存被一直占用无法及时 Release 归还,缓存池被掏空,RGA 硬件无空闲缓存存放新帧,直接丢弃帧并打印 drop。优化:调整 BufPoolCnt=8~10,扩容缓存池,缓解读写速率差。
坑 4:用完 MediaBuffer 忘记 ReleaseBuffer(致命隐患)
RK_MPI_SYS_GetMediaBuffer拿到缓存后,必须调用 ReleaseBuffer 归还缓存至 RGA 缓冲池;遗漏释放会造成内存泄漏、缓存永久占用,短时间池子耗尽程序卡死。
坑 5:程序异常退出资源未释放,V4L2 设备占用
异常 kill 程序、Ctrl+C 直接退出,没有执行UnBind、StopStream、DisableChn、DestroyChn,VI 对应的rkispp_scale0驱动节点被内核占用,再次运行程序提示设备忙、VI 打开失败。规范:上线代码添加信号捕获,退出反向逐级释放资源:停流→解绑→关闭 VI→销毁 RGA。
坑 6:RGA 的 HorStride/VirStride 参数混淆
HorStride = 图像宽度、VirStride = 图像高度,输出尺寸 1280×720 时错填 HorStride=1080,RGA硬件校验图像跨度异常,缩放出错画面异常;修正:输出 Hor=1280,Vir=720。
坑 7:对 Bind 数据流逻辑理解误区
误以为 Bind 是软件拷贝数据,实际 RKMedia Bind 后DMA 硬件直通传输,零 CPU 拷贝;VI 数据自动送入 RGA 做硬件缩放,不用应用层搬运图像。