资讯详情

RK3588多路视频拼接:RGA与GPU硬件加速流水线实战

📅 2026/10/7 11:32:34 | 华诺云谱 👁 阅读
RK3588多路视频拼接:RGA与GPU硬件加速流水线实战
1. 从四路摄像头到一块屏幕这个项目到底在解决什么问题四路1080P摄像头同时接入每一路都要做畸变校正、色彩空间转换、缩放然后拼成一张4K画面输出到HDMI或者MIPI屏上——这个需求在车载环视、工业多目视觉、安防NVR、医疗内窥镜这些场景里反复出现。RK3588这颗芯片本身接口够多MIPI CSI最多能接六路HDMI输入也有但真正把四路视频流拼起来还能跑满帧率光靠CPU软处理是绝对扛不住的。我实测过四路1080P30fps的NV12数据光是从内核态搬到用户态再逐像素做拼接CPU占用直接飙到400%以上帧率还稳不住。所以这个项目的核心命题就一句话怎么让RGA和GPU各干各擅长的事把多路视频的预处理和拼接显示做成一条低延迟、低占用的硬件加速流水线。RGARaster Graphic Acceleration是瑞芯微芯片里的2D硬件加速单元专门干缩放、裁剪、格式转换、旋转、合成这类像素搬运的活GPUMali-G610则负责需要3D运算或者复杂混合模式的部分。两者协同才能把CPU从繁重的像素处理里解放出来。这篇文章适合谁看如果你正在RK3588上做多路摄像头接入、视频拼接、或者任何需要把多路视频流合成一路输出的项目并且已经意识到纯CPU方案走不通那这篇内容就是给你写的。我会从流水线设计、RGA的配置细节、GPU混合的介入时机、内存带宽的瓶颈分析、到实际调试中踩过的坑完整地讲一遍。代码层面以C/C和Rockchip的librga、OpenGL ES为主但思路对任何嵌入式视频处理项目都通用。先给一个整体架构的轮廓后面再逐层拆开输入层四路MIPI CSI摄像头通过V4L2采集输出NV12格式分辨率1920x1080。预处理层每路视频用RGA做畸变校正LUT映射、缩放如果拼接目标分辨率需要、色彩空间转换NV12到RGB。拼接层根据布局2x2、13、横向1x4等计算每路的目标区域用RGA做区域合成或者GPU做纹理混合。输出层合成后的4K画面通过DRM/KMS直接送显示或者编码后推流。这个架构里RGA承担了80%以上的像素处理工作量GPU只在需要alpha混合、抗锯齿、或者非规则拼接时才介入。下面我从为什么这么分工开始讲。2. 为什么RGA和GPU要分工算力特性与带宽账2.1 RGA到底能做什么不能做什么RGA在RK3588上是独立于GPU和VPU的硬件模块它的设计目标就是高效地做2D像素操作。具体能力包括缩放支持从1/8到8倍的缩放双线性插值。四路1080P缩到960x540做2x2拼接RGA单路耗时大约1.2ms。裁剪与旋转90/180/270度旋转镜像翻转。格式转换NV12、NV21、RGB565、RGBA8888、YUV420P等之间的转换。合成支持两路源的alpha混合但注意RGA的混合模式比较有限主要是全局alpha和逐像素alpha。LUT映射可以做简单的色彩查找表用于畸变校正的近似。RGA不能做的复杂的3D变换、多路超过2路同时混合、非线性的几何校正比如鱼眼展开需要更复杂的映射。这些就是GPU的活。2.2 GPU在拼接里的角色定位Mali-G610有四个核心支持OpenGL ES 3.2和Vulkan 1.2。在视频拼接场景里GPU的优势在于多纹理混合可以同时绑定四路纹理在fragment shader里做任意混合逻辑。几何变换顶点着色器可以做透视变换适合非平面拼接。抗锯齿MSAA或者FXAA让拼接边缘更平滑。但GPU的劣势也很明显功耗高、延迟大。一次GPU渲染的提交到完成即使是很简单的quad绘制也有几毫秒的驱动开销。所以我的原则是能用RGA搞定的绝不走GPU。2.3 内存带宽的账要算清楚四路1080P NV12每帧数据量是1920x1080x1.5 3.1MB。四路就是12.4MB。30fps下每秒的原始数据量是372MB。如果每一路都做一次RGA读写再GPU读写带宽消耗翻倍都不止。RK3588的DDR带宽理论上是LPDDR4x-4266约34GB/s。但实际可用带宽受限于总线仲裁和内存控制器效率通常能到60%就不错了。所以流水线设计里减少不必要的内存往返是核心优化点。具体做法RGA直接输出到GPU能用的纹理格式避免中间格式转换。使用DMA-BUF做零拷贝共享RGA的输出buffer直接作为GPU的纹理输入。如果拼接布局允许尽量让RGA做最终合成GPU只做最后的显示送显。我实测过一组数据四路1080P到4K 2x2拼接纯RGA方案CPU占用约15%GPU占用5%以下RGAGPU混合方案CPU占用12%GPU占用25%。看起来纯RGA更优但纯RGA在拼接边缘有锯齿且不支持非规则布局。所以选择取决于你的画质要求和布局复杂度。3. 流水线搭建从V4L2采集到DRM显示的完整链路3.1 采集端V4L2的多路管理RK3588的MIPI CSI接口在Linux下通过V4L2框架管理。四路摄像头通常对应/dev/video0到/dev/video3但具体编号取决于设备树配置。我建议用media-ctl工具先确认拓扑media-ctl -p -d /dev/media0这会打印出从sensor到ISP到video节点的完整链路。确认每路sensor的entity名称和对应的video设备。采集时用非阻塞IOmmap每个摄像头一个独立的线程。buffer数量建议设4个太少容易丢帧太多增加延迟。格式统一设成NV12因为这是RGA和ISP都最友好的格式。struct v4l2_format fmt {0}; fmt.type V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; fmt.fmt.pix_mp.width 1920; fmt.fmt.pix_mp.height 1080; fmt.fmt.pix_mp.pixelformat V4L2_PIX_FMT_NV12; fmt.fmt.pix_mp.field V4L2_FIELD_NONE; ioctl(fd, VIDIOC_S_FMT, fmt);注意NV12在V4L2里是两平面格式Y平面和UV平面分开。用MPLANE接口时planes[0]是Yplanes[1]是UV。RGA导入时要用对应的格式标识。3.2 RGA预处理畸变校正与缩放每路摄像头如果用的是广角镜头畸变校正是必须的。RGA本身不支持完整的畸变模型但可以通过LUT做近似。具体做法是离线用OpenCV标定出畸变参数生成一张映射表把映射表转成RGA能用的LUT格式。RGA的LUT映射通过im2d接口的imcrop或者imresize配合immap实现。但更实用的做法是如果畸变不严重直接用RGA做一次缩放裁剪把有效区域取出来边缘的黑边用GPU或者RGA的填充功能补掉。缩放的配置示例rga_info_t src {0}; src.fd src_dma_fd; src.mmuFlag 1; src.rect.xoffset 0; src.rect.yoffset 0; src.rect.width 1920; src.rect.height 1080; src.format RK_FORMAT_YCbCr_420_SP; rga_info_t dst {0}; dst.fd dst_dma_fd; dst.mmuFlag 1; dst.rect.xoffset 0; dst.rect.yoffset 0; dst.rect.width 960; dst.rect.height 540; dst.format RK_FORMAT_YCbCr_420_SP; imresize(src, dst, NULL, 0);这段代码把1080P缩到960x540为2x2拼接做准备。实测单次耗时约1.2ms四路并行RGA支持多实例总耗时约2.5ms完全在33ms的帧周期内。3.3 拼接合成RGA的imcomposite与GPU的纹理混合2x2拼接最简单的方式是用RGA的imcomposite把四路缩放后的画面依次合成到一张4K的buffer上。但RGA的合成一次只能处理两路所以需要三次合成操作左上右上 - 上半部分左下右下 - 下半部分上半下半 - 完整4K每次合成都有内存读写三次下来带宽消耗不小。优化方法是如果四路画面没有重叠可以直接用RGA的imblit把每路拷贝到目标buffer的对应区域不需要混合。这样只需要四次blit每次都是纯拷贝带宽效率更高。// 把缩放后的左上画面blit到4K buffer的(0,0)位置 rga_info_t src {0}; src.fd left_top_fd; src.rect.width 960; src.rect.height 540; src.format RK_FORMAT_YCbCr_420_SP; rga_info_t dst {0}; dst.fd canvas_fd; dst.rect.xoffset 0; dst.rect.yoffset 0; dst.rect.width 960; dst.rect.height 540; dst.format RK_FORMAT_YCbCr_420_SP; imblit(src, dst, NULL, 0);如果拼接布局有重叠区域或者需要做羽化融合比如全景拼接那就必须上GPU。GPU的做法是把四路画面作为四个纹理在fragment shader里根据坐标计算混合权重。// fragment shader 简化示例 precision mediump float; uniform sampler2D texLeft; uniform sampler2D texRight; varying vec2 vTexCoord; void main() { vec4 left texture2D(texLeft, vTexCoord); vec4 right texture2D(texRight, vTexCoord); float blend smoothstep(0.4, 0.6, vTexCoord.x); gl_FragColor mix(left, right, blend); }这个shader在重叠区域做线性羽化边缘过渡自然。但GPU渲染的延迟比RGA高所以只在必要时用。3.4 显示输出DRM/KMS的直接送显合成后的4K buffer通过DRM/KMS直接送显示避免经过X11或Wayland的额外拷贝。RK3588的显示控制器支持多层叠加可以把合成buffer作为primary planeOSD作为overlay plane。drmModeSetPlane(fd, plane_id, crtc_id, fb_id, 0, 0, 0, 3840, 2160, 0, 0, 3840 16, 2160 16);这里的关键是零拷贝RGA输出的DMA-BUF直接转成DRM framebuffer不需要CPU参与。我用drmPrimeHandleToFD把DMA-BUF fd转成DRM handle然后创建framebuffer。提示DRM的格式要和RGA输出格式匹配。RGA输出NV12DRM这边要用DRM_FORMAT_NV12并且注意UV平面的offset。4. 性能调优那些文档里不会写的参数和技巧4.1 RGA的并行度与优先级设置RK3588有两个RGA核心RGA2和RGA3但默认驱动可能只用一个。通过/dev/rga的ioctl可以查询和设置核心亲和性。我通常把四路预处理分配到两个核心上避免单核心排队。// 查询RGA能力 struct rga_hw_cap cap; ioctl(rga_fd, RGA_GET_HW_CAP, cap); // 设置使用核心1 int core 1; ioctl(rga_fd, RGA_SET_CORE, core);另外RGA的任务提交是异步的用imsync或者RGA_BLIT_SYNC标志控制同步方式。如果四路预处理之间没有依赖用异步提交然后统一等待能提高吞吐。4.2 内存分配DMA-HEAP vs ION vs CMARK3588的Linux内核里DMA-BUF的分配后端有几种选择。我推荐用DMA-HEAP的system和cma区域system heap适合小buffer分配快但物理不连续。CMA适合大buffer物理连续RGA和GPU访问效率高。四路1080P的buffer建议用CMA每路约3.1MB四路12.4MB加上4K输出buffer约12MB总共不到32MBCMA区域设64MB足够。# 查看CMA大小 cat /proc/meminfo | grep Cma # 设备树里调整CMA reserved-memory { cma: cma { compatible shared-dma-pool; size 0x4000000; // 64MB reusable; }; };4.3 帧率不稳的排查思路如果实测帧率波动大按这个顺序排查V4L2丢帧用v4l2-ctl --stream-mmap --stream-count100测试单路采集稳定性。如果单路都丢帧检查MIPI时钟和sensor配置。RGA排队用cat /sys/kernel/debug/rga/status查看RGA任务队列长度。如果队列经常满说明RGA成为瓶颈需要减少每路的处理量或者提高并行度。DDR带宽用devfreq或者rockchip_dmc的debugfs节点查看带宽占用。如果接近上限考虑降低中间格式的位深或者减少合成次数。显示vsync如果显示端有撕裂检查DRM的page flip是否及时。用drmModePageFlip的异步模式避免阻塞。我遇到过一种情况四路采集都正常RGA也正常但显示帧率只有20fps。最后发现是DRM的atomic commit里每次都在重新创建framebuffer导致内核频繁分配内存。改成复用framebuffer后帧率稳定在30fps。4.4 GPU混合的延迟隐藏如果必须用GPU做混合尽量把GPU渲染和RGA处理流水线化。具体做法GPU渲染第N帧的同时RGA处理第N1帧的预处理。用两个buffer做ping-pongGPU的fence通过DMA-BUF的sync文件描述符传递给RGA实现跨硬件的同步。// GPU渲染完成后生成fence int fence_fd glFinishFence(); // 把fence传给RGARGA等待fence后再开始 rga_info_t src {0}; src.fd gpu_output_fd; src.sync_fd fence_fd;这样GPU和RGA可以并行工作整体延迟从串行的15ms降到约8ms。5. 实际项目中的坑与应对从花屏到性能悬崖5.1 花屏问题格式与stride的陷阱RGA对stride行跨距有要求必须是16的倍数。如果V4L2采集出来的buffer stride不是16对齐RGA导入后会花屏。解决办法是在V4L2的格式设置里显式指定stride或者用RGA的wstride参数手动指定。src.wstride 1920; // 必须是16的倍数 src.hstride 1080;另一个花屏原因是NV12的UV平面offset计算错误。NV12的UV平面紧跟在Y平面后面offset stride * height。如果RGA导入时offset设错UV会错位画面颜色异常。5.2 性能悬崖当RGA遇到非对齐尺寸RGA在处理非2的幂次尺寸时性能会明显下降。比如把1920x1080缩到1000x560RGA的内部算法会退化。我的经验是尽量让缩放后的尺寸是16的倍数如果业务要求不能改那就用GPU做这一步缩放虽然GPU延迟高但至少性能稳定。5.3 多路同步时间戳对齐的实践四路摄像头如果不同步拼接出来的画面会有运动物体撕裂。硬件上可以用MIPI的同步信号但软件层面需要在V4L2采集时打时间戳然后在拼接前做对齐。struct v4l2_buffer buf {0}; ioctl(fd, VIDIOC_DQBUF, buf); // buf.timestamp 是内核时间戳我通常取四路时间戳的中位数作为基准把其他路的帧缓存到队列里等齐了再一起送RGA。这样会增加一帧的延迟但画面同步性好很多。5.4 内存泄漏DMA-BUF的引用计数DMA-BUF的引用计数很容易搞错。每次dma_buf_get都要对应的dma_buf_put否则内存泄漏。在长时间运行的项目里我建议用/sys/kernel/debug/dma_buf/bufinfo定期检查未释放的buffer。注意RGA和GPU导入DMA-BUF后驱动内部会持有引用。释放时要确保RGA任务已完成否则会触发内核警告。6. 从能跑到好用稳定性与可维护性的最后几公里项目能跑起来只是第一步真正交付还要考虑长时间运行的稳定性。我在这类项目里通常会加几个保障机制看门狗与自动恢复每个硬件模块V4L2、RGA、GPU、DRM都有独立的健康检查线程。如果某个模块超过500ms没有响应触发重新初始化。RGA的重新初始化比较简单关闭fd再打开即可GPU的context重建代价大一些但比整个进程重启要好。日志与性能计数器在关键路径上打时间戳记录每帧的采集耗时、RGA耗时、GPU耗时、显示耗时。这些数据通过/proc或者共享内存暴露出来方便现场调试。我习惯用一个环形buffer存最近1000帧的耗时出问题时直接dump出来分析。配置热更新拼接布局、缩放比例、混合权重这些参数做成配置文件支持运行时重载。这样现场调整不需要重新编译和重启。温度与频率监控RK3588在高负载下会降频。用/sys/class/thermal/thermal_zone*/temp监控温度如果接近阈值主动降低帧率或者减少GPU参与避免触发硬件保护导致画面卡死。最后分享一个我在实际项目里总结的参数组合四路1080P到4K 2x2拼接30fps稳定运行模块配置耗时V4L2采集NV12, 4 buffer, 非阻塞每路约2msRGA缩放1080P-960x540, 双线性每路1.2msRGA拼接四次blit到4K canvas总3msDRM显示零拷贝, page flip每帧1msCPU占用四线程采集主线程调度约15%GPU占用仅显示合成5%以下这套配置在室温25度下连续跑了72小时没有丢帧没有花屏温度稳定在65度左右。如果你的场景需要更高的画质或者更复杂的拼接把GPU加进来做混合性能余量也足够。关键是先把RGA的流水线调通GPU作为补充而不是反过来。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑