ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

RK3588多路视频拼接实战:RGA与GPU协同加速优化指南

RK3588多路视频拼接实战:RGA与GPU协同加速优化指南 1. 多路视频拼接的底层逻辑与方案选型1.1 为什么要在RK3588上做视频拼接多路视频拼接这件事放在几年前基本是x86工控机加独立显卡的活儿。四路1080P输入先解码再缩放、裁剪、旋转最后合成一路大画面输出整套流程跑下来CPU占用率轻松飙到百分之七八十风扇呼呼转功耗直奔60W以上。而RK3588这颗芯片的出现让整个方案的成本和功耗结构发生了根本性变化。RK3588是瑞芯微的一款旗舰级SoC8核CPU4个A76大核加4个A55小核内置Mali-G610 MP4 GPU、独立的NPU6TOPS算力、VPU视频编解码单元以及一个很多人容易忽略但极其关键的模块——RGARaster Graphic Acceleration。RGA是一块专门做2D图形加速的硬件支持缩放、裁剪、旋转、格式转换、Alpha混合、色彩空间转换等操作。它和GPU的分工非常明确RGA干的是搬砖的活GPU干的是画画的活。视频拼接的本质是什么把多路视频帧经过缩放、裁剪、定位后贴到一张大画布的不同区域上。这个操作在数学上就是2D仿射变换加像素拷贝恰好是RGA最擅长的事情。如果你用GPU去做这件事当然也能做但GPU的渲染管线是为3D图形和通用计算设计的用它来干2D贴图相当于开卡车送快递——能送到但油耗高、启动慢。我实测过一组数据四路1080P30fps输入拼接成一路4K30fps输出纯CPU方案用OpenCV的resize和copyToCPU占用约65%纯GPU方案OpenGL ES做纹理映射CPU占用约20%但GPU占用约45%而RGAGPU协同方案CPU占用不到8%GPU占用约12%RGA占用约35%。这个差距在嵌入式场景下是决定性的因为功耗和散热直接决定了产品能不能做成无风扇的紧凑形态。1.2 RGA和GPU到底怎么分工很多人第一次接触RK3588的媒体处理管线时会搞不清楚RGA和GPU各自该干什么。我用一个生活化的类比来解释假设你要布置一面照片墙RGA就是那个帮你把每张照片裁剪成合适大小、旋转到正确角度、然后贴到墙上指定位置的助手GPU则是那个负责在照片上画装饰、做渐变过渡、渲染OSD文字和图标的美工。两个人各干各的效率最高。具体到技术层面RGA负责的操作包括缩放把不同分辨率的输入源统一缩放到目标尺寸支持最近邻、双线性、双三次等多种插值算法裁剪从原始帧中截取感兴趣区域ROI旋转支持0/90/180/270度旋转以及任意角度的仿射变换格式转换比如把摄像头输出的NV12转换成RGB888或者把RGB转成YUV用于编码Alpha混合多图层叠加时的透明度处理色彩空间转换BT.601到BT.709之类的转换GPU负责的操作包括OSD叠加在拼接画面上渲染文字、图标、时间戳等过渡特效画面切换时的淡入淡出、滑动等效果几何校正如果做投影融合需要GPU做非线性的几何形变最终合成把RGA处理好的各个图层合成为最终输出帧这个分工的核心原则是能用RGA做的就不要用GPUGPU只做RGA做不了的事情。因为RGA是固定功能硬件执行效率极高功耗极低GPU是通用计算单元灵活但开销大。1.3 数据流的整体架构设计一个典型的多路视频拼接系统的数据流是这样的Camera/HDMI输入 → MIPI CSI/HDMI RX → VPU解码 → DDR帧缓冲 → RGA缩放/裁剪/旋转 → DDR中间帧 → GPU合成/OSD → DDR输出帧 → VPU编码 → HDMI TX/网络推流这里面有几个关键的设计决策点。第一个是零拷贝理想情况下VPU解码后的帧不应该被CPU读出来再写回去而是通过DMA-BUF直接传递给RGA和GPU。RK3588的媒体框架Rockchip Media Process Platform简称MPP原生支持DMA-BUF这是必须用上的。第二个是流水线并行四路输入的解码和RGA处理应该并行执行而不是串行否则帧率上不去。第三个是缓冲管理每一级之间需要维护帧缓冲队列队列深度太浅会导致丢帧太深会增加延迟。我在实际项目中的经验是四路1080P拼接场景下每一级之间的缓冲队列深度设为3到4比较合适。太少了容易因为调度抖动导致丢帧太多了端到端延迟会明显增加。如果你做的是实时监控场景延迟要求高那就设2如果是录制或推流场景可以设4到5。2. RGA硬件加速的核心细节与实操要点2.1 RGA的驱动接口与调用方式RK3588上的RGA使用最底层的方式是直接调用librga库。这个库提供了C语言接口核心API包括im2d系列的函数。我先把最常用的几个接口列出来// 创建RGA缓冲区 rga_buffer_t src wrapbuffer_virtualaddr(src_vaddr, src_w, src_h, src_format); rga_buffer_t dst wrapbuffer_virtualaddr(dst_vaddr, dst_w, dst_h, dst_format); // 执行缩放裁剪旋转 im_rect src_rect {src_x, src_y, src_w, src_h}; im_rect dst_rect {dst_x, dst_y, dst_w, dst_h}; imrectangle_t rect {src_rect, dst_rect}; imresize_t resize {src_w, src_h, dst_w, dst_h}; improcess(src, dst, rect, resize, 0, IM_SYNC);这段代码看起来简单但里面有几个坑。第一个坑是格式匹配源和目标的像素格式必须明确指定NV12、RGB888、RGBA8888等格式在RGA内部的处理路径不同性能差异很大。第二个坑是对齐要求RGA对缓冲区的宽高有对齐要求通常是16字节对齐如果你的图像宽度不是16的倍数需要手动padding。第三个坑是同步模式IM_SYNC是同步调用会阻塞当前线程直到RGA完成IM_ASYNC是异步调用需要配合imsync()使用。在流水线场景下异步模式能显著提升吞吐量。注意RGA的异步模式虽然效率高但缓冲区管理会复杂很多。如果你在异步模式下复用了正在被RGA读取的缓冲区会出现画面撕裂或花屏。建议在项目初期先用同步模式跑通确认功能正确后再切换到异步模式优化性能。2.2 多路拼接中的RGA参数计算假设我们要把四路1080P1920x1080的视频拼接成一路4K3840x2160输出采用2x2的网格布局。每一路视频需要缩放到1920x1080然后分别放置在四个象限。这里有一个关键问题缩放比例的计算。如果输入是1920x1080目标是1920x1080那缩放比例是1:1RGA只需要做拷贝速度最快。但如果输入是1280x720的摄像头要放到1920x1080的格子里就需要放大1.5倍。RGA的缩放能力是有上限的官方文档给出的建议是单次缩放比例不超过8倍超过这个范围画质会明显下降。在实际项目中我建议的缩放策略是输入分辨率目标格子尺寸缩放比例建议插值算法RGA耗时微秒1920x10801920x10801.0x最近邻约8001280x7201920x10801.5x双线性约1200640x4801920x10803.0x双线性约15003840x21601920x10800.5x双三次约2000这个表格里的耗时数据是我在RK3588开发板上实测的结果具体数值会因DDR带宽和RGA频率而有所波动。可以看到缩放比例越大耗时越长。如果四路都是3倍放大四路加起来就是6000微秒也就是6毫秒在30fps33毫秒一帧的预算里占了将近20%。这个开销是可以接受的但如果你要做60fps预算只有16.6毫秒就需要仔细规划了。2.3 格式转换的性能陷阱摄像头输出的格式通常是NV12YUV420SP而GPU渲染和屏幕显示通常需要RGB格式。这个格式转换如果交给CPU做四路1080P30fps的转换量是相当大的。RGA支持硬件格式转换但这里有一个性能陷阱NV12到RGB888的转换比NV12到RGB565慢将近一倍因为RGB888每个像素需要写3个字节而RGB565只需要2个字节。如果你的显示终端支持RGB565很多LCD屏都支持那就优先用RGB565能省不少带宽。如果必须用RGB888那就要注意DDR带宽的占用。四路1080P30fps的RGB888数据量是1920x1080x3x30x4 746MB/s这个带宽对RK3588的DDR来说不算什么RK3588支持LPDDR4x/LPDDR5带宽几十GB/s但加上读写双向实际占用会翻倍。还有一个容易被忽略的点RGA的格式转换和缩放可以一次完成。也就是说你不需要先把NV12转成RGB再做缩放而是可以在一次RGA调用中同时完成格式转换和缩放。这个操作在librga里通过设置源和目标的格式参数自动实现不需要额外的步骤。我见过有开发者先调一次RGA做格式转换再调一次RGA做缩放这是完全没必要的白白浪费了一倍的RGA时间。3. GPU协同加速的实操过程与核心环节3.1 GPU在拼接管线中的角色定位前面说了RGA负责2D变换GPU负责合成和特效。但在实际项目中GPU的角色可能比你想的更重。原因在于RGA虽然能做Alpha混合但它的混合模式比较有限只支持简单的全局Alpha和预乘Alpha。如果你需要做逐像素的Alpha混合比如不规则形状的OSD叠加或者需要做颜色校正比如不同摄像头之间的白平衡统一那就必须用GPU。RK3588的Mali-G610 MP4支持OpenGL ES 3.2和Vulkan 1.2。在视频拼接场景下我推荐用OpenGL ES因为它的生态更成熟调试工具更多。核心思路是把RGA处理好的每一路视频帧作为纹理上传到GPU然后在Fragment Shader里做混合和特效最后渲染到FBOFrame Buffer Object再交给VPU编码或直接送显示。这里有一个关键的性能优化点纹理上传的方式。如果你用glTexImage2D每帧重新上传纹理开销会很大。正确做法是用glTexSubImage2D更新纹理内容或者更好的是用EGL Image配合DMA-BUF实现零拷贝纹理上传。EGL Image的方式在RK3588上是支持的但配置起来比较麻烦需要先创建eglImageKHR再绑定到GL纹理。// 从DMA-BUF创建EGL Image EGLint attrs[] { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE }; EGLImageKHR egl_image eglCreateImageKHR( display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, attrs); // 绑定到GL纹理 glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, egl_image);这段代码看起来简单但实际调试时可能会遇到各种问题比如DMA-BUF的格式不被EGL支持、stride对齐不对、或者驱动版本不匹配。我的建议是先用glTexSubImage2D的方式跑通功能确认拼接逻辑正确后再切换到EGL Image做性能优化。3.2 拼接画面的几何布局与坐标计算四路视频拼接成2x2网格听起来简单但坐标计算容易出错。假设输出画布是3840x2160每个格子的尺寸是1920x1080。四个格子的左上角坐标分别是左上(0, 0)右上(1920, 0)左下(0, 1080)右下(1920, 1080)在OpenGL ES中纹理坐标的原点在左下角而视频帧的原点在左上角所以需要做Y轴翻转。这个翻转可以在顶点着色器里做也可以在RGA阶段就做好。我推荐在RGA阶段做因为RGA做翻转不额外消耗时间而GPU做翻转需要额外的矩阵运算。还有一个细节格子之间的缝隙。如果你希望四个画面之间有一条细线分隔那每个格子的实际渲染区域要缩小几个像素。比如缝隙宽度设为4像素那每个格子的渲染区域就是1916x1076左上角坐标也要相应偏移。这个偏移量需要在RGA的裁剪参数和GPU的顶点坐标中同步设置否则会出现画面错位。3.3 帧率同步与流水线调度多路视频拼接最头疼的问题之一是帧率同步。四路摄像头的帧率可能不完全一致有的30fps有的25fps有的甚至因为光线变化导致自动曝光调整而出现帧率波动。如果不对齐拼接出来的画面会出现撕裂或者不同步。我的解决方案是以最慢的一路为基准做帧率归一化。具体做法是维护一个帧计数器每路视频到达时检查当前计数如果某一路的帧还没到就用上一帧填充。这个逻辑在RGA之前做确保送给RGA的每一路帧都是对齐的。流水线调度方面我推荐用多线程生产者消费者模型。每一路视频的解码和RGA处理放在独立的线程里处理完的帧放入一个共享的帧队列。GPU渲染线程从队列里取帧做合成和渲染。这样能最大化利用RK3588的8核CPU和各个硬件加速单元。// 简化的流水线伪代码 void* camera_thread(void* arg) { while (running) { // 1. 从VPU获取解码帧 frame vpu_dequeue_frame(); // 2. RGA处理缩放裁剪格式转换 rga_process(frame, processed_frame); // 3. 放入共享队列 queue_push(shared_queue, processed_frame); } } void* render_thread(void* arg) { while (running) { // 1. 从队列取四路处理好的帧 frames queue_pop_n(shared_queue, 4); // 2. GPU合成 gpu_composite(frames); // 3. 输出 output_frame(); } }这个架构的关键是队列的线程安全性和缓冲区的生命周期管理。我踩过的坑是RGA处理完的帧放入队列后如果GPU还没取走RGA线程不能复用这个缓冲区。解决方案是维护一个缓冲区池用引用计数来管理取走时计数减一归零时归还到池子里。4. 常见问题与排查技巧实录4.1 RGA相关问题的排查思路RGA用起来虽然简单但出问题的时候往往让人摸不着头脑。我整理了几个最常见的RGA问题问题一RGA调用返回失败错误码-1。这个通常是因为缓冲区地址没有对齐或者格式不支持。排查方法是先用rga_check()函数检查参数是否合法然后确认缓冲区的物理地址是否连续。RGA要求缓冲区在物理内存上是连续的如果你用的是malloc分配的内存很可能不连续。解决方案是用dma_heap分配DMA缓冲区。问题二RGA处理后的画面颜色不对。这个九成是色彩空间的问题。NV12格式有BT.601和BT.709两种色彩空间如果源和目标的色彩空间不一致颜色就会偏。RGA默认使用BT.601如果你的视频源是BT.709需要在调用时显式指定。问题三RGA处理速度比预期慢很多。先检查是不是用了同步模式同步模式下每次调用都要等RGA完成吞吐量上不去。然后检查缩放比例如果缩放比例超过4倍RGA会自动切换到多pass模式速度会明显下降。最后检查DDR带宽如果同时有大量其他内存操作比如VPU编码RGA的带宽会被挤占。实操心得RGA的性能对DDR带宽非常敏感。我在一个项目里发现RGA处理四路1080P的耗时突然从3毫秒涨到了8毫秒排查了半天才发现是另一个线程在做大文件读写把DDR带宽占满了。后来把文件读写改到空闲时段RGA性能就恢复了。所以在RK3588上做多媒体应用一定要关注DDR带宽的分配。4.2 GPU合成中的典型故障GPU合成的问题通常比RGA更复杂因为涉及OpenGL ES的状态机和驱动。我遇到过的典型问题包括问题一纹理上传后画面是黑的。最常见的原因是纹理坐标不对或者纹理没有正确绑定。排查方法是先用一个纯色纹理测试确认渲染管线是通的然后再换成实际视频帧。另外注意如果视频帧的stride和宽度不一致比如1920宽但stride是2048需要在glPixelStorei里设置GL_UNPACK_ROW_LENGTH。问题二合成画面出现撕裂。这个通常是垂直同步没开或者帧缓冲交换的时机不对。在RK3588上可以通过eglSwapInterval设置垂直同步。另外如果用的是FBO离屏渲染要确保在glFinish之后再读取像素否则可能读到不完整的帧。问题三GPU占用率过高。如果GPU占用超过50%说明合成逻辑太重了。优化方向包括减少Fragment Shader的复杂度、降低纹理过滤质量从三线性降到双线性、关闭不必要的混合操作。还有一个容易被忽略的点纹理的格式。RGBA8888纹理比RGB565纹理的带宽占用高一倍如果显示终端支持RGB565就用RGB565。4.3 端到端延迟的优化技巧多路视频拼接的端到端延迟是很多应用场景的核心指标。从摄像头采集到最终显示延迟来源包括采集延迟、解码延迟、RGA处理延迟、GPU合成延迟、编码延迟、显示延迟。每一级都有优化空间。我的经验是最大的延迟来源往往是缓冲队列。如果你每一级都设了4帧的缓冲那端到端延迟至少是4帧的时间。在30fps下就是133毫秒这个延迟在监控场景下可能可以接受但在交互场景下就太长了。优化策略是在保证不丢帧的前提下尽可能减少缓冲深度。具体做法是采集端用V4L2_MEMORY_MMAP模式减少一次拷贝解码端设置MPP_DEC_SET_OUTPUT_FORMAT为NV12避免解码器做额外的格式转换RGA处理完的帧直接送GPU中间不设队列用双缓冲交替GPU渲染完直接送显示用eglSwapBuffers的垂直同步机制这样下来端到端延迟可以控制在2到3帧以内也就是60到100毫秒。如果还需要更低那就只能牺牲帧率或者降低分辨率了。4.4 常见问题速查表现象可能原因排查方法解决方案RGA调用失败缓冲区地址不对齐检查物理地址是否16字节对齐用dma_heap分配画面颜色偏色色彩空间不匹配确认源和目标色彩空间显式指定BT.601/709RGA速度慢同步模式或缩放比例大检查调用模式和缩放比例改异步模式分步缩放GPU纹理黑屏纹理坐标或绑定错误用纯色纹理测试检查纹理坐标和绑定画面撕裂垂直同步未开检查eglSwapInterval开启垂直同步端到端延迟高缓冲队列太深检查各级队列深度减少队列深度帧率不稳定某路视频帧率波动检查各路帧率做帧率归一化内存泄漏缓冲区未释放检查引用计数用缓冲区池管理这个表格里的每一个问题我都实际遇到过解决方案也是经过验证的。特别是内存泄漏那个在长时间运行的场景下非常致命。我建议在开发阶段就加上缓冲区的引用计数检查每次分配和释放都打日志这样出问题的时候能快速定位。5. 性能调优与实战经验总结5.1 从30fps到60fps的优化路径很多项目一开始只要求30fps但后来业务需求变了要上60fps。这时候你会发现简单的参数调整根本达不到需要做系统性的优化。我分享一下我从30fps优化到60fps的实战经验。第一步是确认瓶颈在哪里。用perf工具或者RK3588自带的性能监控工具看CPU、GPU、RGA、VPU、DDR各自的占用率。如果RGA占用超过70%那瓶颈在RGA如果GPU占用超过60%瓶颈在GPU如果DDR带宽占用超过80%瓶颈在内存带宽。第二步是针对性优化。如果是RGA瓶颈可以尝试降低缩放比例用更接近目标尺寸的输入源、减少格式转换用RGB565代替RGB888、开启RGA的异步模式。如果是GPU瓶颈可以尝试简化Shader、降低纹理精度、减少混合层数。如果是DDR瓶颈可以尝试用更紧凑的像素格式、减少中间帧的拷贝次数、优化缓冲区的对齐方式。第三步是流水线重构。30fps的时候串行处理可能就够了60fps的时候必须做并行。我的做法是把四路视频的处理拆成四个独立的流水线每个流水线有自己的解码线程、RGA线程然后共享一个GPU渲染线程。这样能最大化利用RK3588的多核能力。5.2 不同应用场景的方案调整多路视频拼接的应用场景很多不同场景对方案的要求不一样。我列几个典型场景和对应的方案调整安防监控场景四路1080P输入拼接成4K输出要求7x24小时稳定运行。这个场景下稳定性比性能更重要。我建议用同步模式的RGA虽然吞吐量低一点但不容易出问题。缓冲队列设深一点避免因为偶尔的调度抖动导致丢帧。另外要加看门狗监控各个线程的状态出问题自动重启。视频会议场景多路摄像头输入拼接后推流。这个场景对延迟要求高对画质要求中等。我建议用异步模式的RGA缓冲队列设浅一点2帧用RGB565格式减少带宽。GPU合成的时候可以加一些简单的过渡特效提升用户体验。工业检测场景多路工业相机输入拼接后做分析。这个场景对画质要求极高对延迟要求中等。我建议用双三次插值的RGA保持原始色彩空间不做转换GPU合成的时候不做任何压缩。如果相机输出是RAW格式还需要在RGA之前加一步去马赛克处理。数字标牌场景多路视频源拼接用于广告展示。这个场景对画质和延迟要求都不高但对成本敏感。我建议用最低端的配置RGA用最近邻插值GPU用最简单的Shader能省则省。5.3 我踩过的那些坑最后分享几个我在RK3588多路视频拼接项目中踩过的坑希望能帮你少走弯路。坑一RGA的异步模式不是万能的。我一开始听说异步模式性能好就把所有RGA调用都改成了异步。结果发现在四路并发的情况下异步模式的性能反而比同步模式差。原因是异步模式下RGA的硬件队列会满后来的请求要排队等待反而增加了延迟。后来我改成了两路同步、两路异步的混合模式性能才达到最优。坑二GPU的垂直同步会限制帧率。我一开始开了垂直同步发现帧率被锁在了显示器的刷新率上。如果你的显示器是60Hz那拼接输出最多也就60fps。后来我关掉了垂直同步用glFinish来手动同步帧率就上去了。但关掉垂直同步会引入撕裂需要根据场景权衡。坑三DDR带宽是隐形瓶颈。我前面提过RGA对DDR带宽很敏感。但很多人不知道的是VPU编码也吃带宽GPU渲染也吃带宽CPU访问内存也吃带宽。在四路1080P60fps的场景下DDR带宽的占用可能超过50%。如果你的应用还有其他内存密集型操作很容易撞到带宽墙。解决方案是尽量用DMA-BUF减少拷贝用紧凑的像素格式以及合理安排各个硬件单元的工作时序。坑四驱动版本很关键。RK3588的RGA和GPU驱动在不断的更新中不同版本之间的性能和稳定性差异很大。我遇到过某个版本的RGA驱动在特定分辨率下会花屏升级驱动后就解决了。所以建议用官方推荐的最新稳定版驱动不要用太老的版本。坑五温度会影响性能。RK3588在满载运行的时候温度会升到70度以上。如果散热不好芯片会降频RGA和GPU的性能都会下降。我在一个无风扇的项目里夏天的时候帧率从60fps掉到了45fps后来加了散热片才解决。所以如果你做的是紧凑型产品一定要考虑散热设计。这些经验都是我在实际项目中一点一点积累的有些是花了很长时间才排查出来的。多路视频拼接这个方向技术门槛不算特别高但细节特别多每一个细节都可能影响最终的性能和稳定性。希望这些分享能帮你更快地上手少踩一些坑。
返回列表