尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

RK3588硬解H264转BGRA:RKMPP与RGA全流程实战与排雷指南

RK3588硬解H264转BGRA:RKMPP与RGA全流程实战与排雷指南 这块RK3588板子在我的工位上服役已经大半年了第一批需求里最磨人的不是模型推理而是把一路RTSP里的H264拉下来硬解再转成BGRA8888喂给上位机做画面显示和算法输入。CPU还得留着跑调度、跑业务逻辑所以软解这条路从立项第一天就被否掉了。Rockchip平台的常规答案就是RKMPP加RGA的组合拳RKMPP专门负责H264硬解码RGA这块2D硬件加速单元负责把解码出来的NV12帧转换成BGRA8888。两条硬件流水线串起来CPU基本只做搬运和调度负载非常舒服。这套方案看起来简单真做起来细节非常多。解码器的初始化参数、H264码流的格式协商、解码帧的拿取时机、RGA转换时的stride对齐、内存的cache一致性每一个环节都埋着坑。我这次把从零到跑通的全过程以及后来在代码评审和稳定性测试里踩过的几个雷一并整理出来。无论是刚接触Rockchip平台的嵌入式开发还是已经在用MPP但被RGA转换折腾过的人这篇都能当一份排错手册用。1. 为什么说RKMPP和RGA是Rockchip平台上绕不开的组合1.1 软解被直接否掉的原因先说结论H264 1080p30的软解在一颗Cortex-A76核上大概能跑但CPU占用率会冲到百分之三四十甚至更高这还是在没有任何业务负载的前提下。如果还有RTSP拉流、图像预处理、网络发送、UI渲染这些活CPU直接就被吃满。RK3588虽然八核但总核心数架不住多路视频流。嵌入式项目里CPU是所有业务的总调度池把最耗费算力的视频解码扔给专用硬件单元是性价比最高的做法。RKMPP是Rockchip自研的Media Process Platform内部通过VEPU/VPU等硬件模块完成编解码对外暴露一套统一API。H264解码只是它能力的一部分H265、VP9、JPEG也都在它的管辖范围内。1.2 解码和格式转换的分工解码器解出来的帧默认是以NV12格式输出的也就是YUV420SPY平面和UV交错平面分开存放。这个格式在视频编码领域很常见但直接拿去做显示、送给深度学习前处理或者Qt渲染往往不是最优解。上位机那边的接口通常会要BGRA8888每个像素四个字节内存按B-G-R-A顺序排列这也是很多GUI框架和图像库通用的像素格式。RKMPP管解码RGA管像素搬运和格式转换两者各管一段。RGA全称Raster Graphic Acceleration本质是一块2D图形加速硬件能做事先定义好的格式转换、缩放、旋转、裁剪、颜色空间转换。用RGA做NV12到BGRA8888的转换一个核心设定就是让工作量远离CPU毕竟这种逐像素的格式转换如果交给CPU做memcpy级的循环带宽和算力都吃不消还容易引起缓存抖动。1.3 这套方案适合谁我的判断是以下几类项目可以直接参考这条路线需要在Rockchip平台上解码多路H264/H265视频流的设备比如NVR、多路RTSP网关。解码后图像要送往算法模块做检测、识别而算法输入要求BGRA/RGB的成套系统。嵌入式GUI需要视频画面显示又不想引入额外CPU开销的场景。正在评估RKMPP和RGA性能边界想知道这两块硬件到底能顶多少路流的开发者。后面整个过程以RK3588为主部分经验兼容RK3568、RK3399等平台遇到平台差异我会单独标出来。2. 开工前的硬准备MPP环境、解码器初始化与输入缓冲管理2.1 环境准备与库的编译链接RKMPP的源码可以直接从Rockchip官方仓库拉取一般是一套rockchip-mpp工程。交叉编译时最省事的方式是直接用buildroot或yocto工具链按官方文档把mpp编成动态库。如果你只是在自己开发板上跑也可以直接装系统里自带的librkmpb-dev或者通过apt源装rockchip-mpp-dev但开发机上我还是建议源码编译因为能拿到完整的头文件和调试信息。链接的时候需要关注这几个库target_link_libraries(your_target rockchip_mpp rockchip_mpp_rkai rga pthread dl )RGA库分两个时代。老版本接口是librga提供的rga_info_t和rga_blit新版本接口是librga的im2d函数族头文件是im2d.h和rga.h。新的librga实际上是一套更现代的统一API推荐新项目直接用im2d接口老接口虽然还能用但建议尽快迁移。我这里后面的代码实例会以新接口为主老接口会给出对应的关键差异。2.2 解码器创建与初始化参数先用标准的mpp_create和mpp_init创建解码器实例#include rk_mpi.h #include mpp_common.h #include mpp_buffer.h MppCtx ctx NULL; MppApi *mpi NULL; MppDecCfg dec_cfg NULL; // 创建上下文 mpp_create(ctx, mpi); // 初始化解码器类型这里指定H264 mpp_init(ctx, MPP_CTX_DEC, MPP_VIDEO_CodingAVC);这段代码所有RKMPP项目都一样真正的差异在control阶段。解码器启动前我强烈建议显式处理H264码流是否需要split模式。如果你的输入是纯裸流也就是直接从流媒体服务器或者摄像头那边一路读下来的字节流包边界和帧边界不一定对齐就需要把模式设置为按起始码拆分RK_U32 need_split 1; mpi-control(ctx, MPP_DEC_SET_PARSER_SPLIT_MODE, need_split);反过来如果输入是FFmpeg解封装后输出的AVPacket这个数据已经是完整帧不需要做start code扫描可以直接把need_split设为0效率会高一些。很多初学者在最开始没注意这个开关结果MPP解析出来的帧要么只有半个要么画面花屏排查半天。还有一个容易被忽略的参数是解码帧的格式输出配置。RKMPP默认会输出MPP_FMT_YUV420SP即NV12但在某些平台上解码器可能会输出MPP_FMT_YUV420SP_10BIT如果码流是10bit H264那后面接RGA时格式就要小心NV12 10bit在内存排布上和8bit完全不同。如果确认业务只需要8bit可以在解码前用MPP_DEC_SET_OUTPUT_FORMAT去限定MppEncCodecCfg codec_cfg; MPP_RET ret mpi-control(ctx, MPP_DEC_SET_OUTPUT_FORMAT, fmt);更稳妥的做法还是等解码器真正出帧后用mpp_frame_get_fmt读一下实际格式按实际格式去配RGA不要先入为主。2.3 输入缓冲和码流送交方式H264裸流通常是一大块连续数据直接整块塞给解码器并不合适。工程上建议用环形缓冲或队列管理输入码流一帧一帧地交给MPP。每次送包时用mpp_packet_init创建packet然后把数据指针和大小填进去MppPacket packet NULL; mpp_packet_init(packet, data_ptr, data_len); mpi-decode_put_packet(ctx, packet); // 送完后记得释放packetMPP内部会拷贝需要的数据 mpp_packet_deinit(packet);注意一个细节mpp_packet_init的data_ptr在decode_put_packet调用完成后MPP并不保证还在使用它所以可以立即释放。但如果你设置了MPP_DEC_SET_PARSER_SPLIT_MODE为0那么MPP会假设packet内就是完整的一帧且它可能需要引用数据到解码完成此时不要复用data_ptr内存直到该帧解码完成为止。为了避免这种隐式耦合我一般倾向统一用split1模式凡是需要长缓存的数据都拷贝一份虽然多了一次内存拷贝但生命周期清晰不容易出悬垂指针。2.4 给码流加热身期SPS/PPS和GOPH264解码不是拿到第一帧就能出图。解码器必须等到SPS/PPS以及第一个IDR帧才能开始真正输出图像。网络流通常会在开始时带好SPS/PPS但RTSP或GOP间断流不一定每次都带。遇到长时间不出帧第一反应应该是看码流里有没有SPS/PPS而不是怀疑解码器配置错了。MPP解码过程中如果输入是纯裸流内部会自己解析SPS/PPS所以并不需要单独把SPS/PPS提取出来构建AVCDecoderConfigurationRecord只要把含有SPS/PPS的字节流送进去即可。但split模式下MPP会按0x00000001起始码识别NALU所以数据里必须有正确的起始码。还有一个帧率控制问题。H264解码器的输出帧率由码流里的VUI信息控制但MPP实际输出速度跟硬件能力和输入节奏有关。RTSP拉流场景下经常是网络线程收到一个包就扔给解码器解码器可能还没凑齐一帧这样会导致decode_get_frame一直拿不到输出帧。我的做法是维护一个小队列攒满一整个完整帧的NAL再送packet这样解码器不会因为半截数据频繁状态切换。3. 解码主循环从MppPacket进到MppFrame出的完整链路3.1 解码器主循环骨架MPP的模型非常简单一边往解码器里用decode_put_packet送压缩数据一边用decode_get_frame把解出来的帧取出来。取不到帧不是错误可能是数据还不足以解码出完整图形也可能是参考帧还没凑齐。标准循环如下while (running) { // 1. 从输入队列取一包H264数据 // 2. 封装成MppPacket并送交解码器 MppPacket packet NULL; mpp_packet_init(packet, input_data, input_size); mpi-decode_put_packet(ctx, packet); // 3. 尝试拿解码帧注意要循环拿直到拿不到为止 MppFrame frame NULL; MPP_RET ret mpi-decode_get_frame(ctx, frame); if (ret MPP_OK frame) { MppBuffer buffer mpp_frame_get_buffer(frame); RK_U32 width mpp_frame_get_width(frame); RK_U32 height mpp_frame_get_height(frame); RK_U32 hor_stride mpp_frame_get_hor_stride(frame); RK_U32 ver_stride mpp_frame_get_ver_stride(frame); RK_U32 eos mpp_frame_get_eos(frame); // 交给RGA做格式转换 convert_to_bgra(buffer, width, height, hor_stride, ver_stride); // 用完必须deinit否则会内存泄漏 mpp_frame_deinit(frame); } mpp_packet_deinit(packet); }很多刚从FFmpeg转过来的同学会习惯性去找decode_frame这种阻塞接口MPP不是这个逻辑。MPP的decode_put_packet基本上只要缓冲区里有空间就立刻返回decode_get_frame也只是查询当前有没有输出帧是非阻塞的。整个解码动作发生在硬件内部驱动通过中断等机制完成帧输出而应用层拿到的是已经完成解码的帧。3.2 从解码帧提取关键信息拿到MppFrame之后不要急着去拷贝数据先把下面这些元信息读准width/height实际显示尺寸就是H264里的图像宽高。hor_stride/ver_stride解码器内部缓冲区的行间距这个值通常大于或等于width因为硬件为了对齐会在行尾填补字节。mpp_frame_get_fmt实际输出格式可能是MPP_FMT_YUV420SP也可能是MPP_FMT_YUV420SP_10BIT甚至有可能是MPP_FMT_YUV422SP。mpp_frame_get_buffer指向帧数据的内存缓冲区类型是MppBuffer。我最想强调的是hor_stride。很多RGA转换花屏/偏色的案例根因都出在这里——从MPP拿到的NV12数据行宽不是width而是hor_stride如果你直接用width当作行宽去读Y数据和UV数据后面UV平面的位置就会整体偏移画面就花了。正确做法是把它传给RGA的wstride字段让硬件按实际stride去读取。3.3 帧释放时机与参考帧管理H264解码依赖参考帧MPP内部会维护一个DPBDecoded Picture Buffer列表。应用层拿到的MppFrame并不完全独立它在内部可能被引用为后续帧的参考帧。所以释放时机很重要。正确用法是处理完当前帧的所有数据后立即调用mpp_frame_deinit(frame)释放的只是应用层持有这个MppFrame对象MPP内部对帧缓冲的引用计数由它自己控制。如果你强行把帧数据保存在自己构造的另一种结构体里那就要确保在deinit之前把需要的数据复制走或者对该帧的MppBuffer额外做一次引用。我在项目里踩过一个很低级的错误为了把解码帧和后续的显示控制线程共享我把MppFrame指针直接塞进了队列想等显示线程用完再释放。结果MPP需要这个帧做参考帧时内部引用计数已经乱掉后续画面出现周期性的花屏和卡顿。后来改成“解码线程内完成复制或立即转格式队列只传转换后的BGRA数据”问题立刻消失。3.4 错误恢复机制网络流难免丢包。H264码流如果丢了一个关键NALU解码器会进入错误状态输出几帧花屏或直接停止输出。MPP内部有错误恢复机制但前提是应用层要正确处理检测到decode_get_frame在长时间内持续拿不到帧并且解码器报MPP_ERR_INVALID_PARAM这类错误码时要把解码器reset。恢复手段有两种一是调用mpi-control(ctx, MPP_DEC_RESET, NULL)复位解码器二是直接把解码器销毁重建。我的经验是短时的丢包可以不处理等到下一个IDR帧到来时解码器会自动恢复同步。如果丢包严重导致长期无输出就主动请求关键帧。在RTSP场景里可以发PLAY方法里带SETPARAM请求或使用RTSP的ClientCommand让对端强制重新发送关键帧。这个思路比暴力reset解码器更优雅适合对实时性敏感的场景。4. RGA做NV12到BGRA8888转换配置上最容易翻车的几个点4.1 新旧librga API怎么选RGA的开发接口现在有两套。老接口是rga_info_t配合rga_blit使用起来直接、流程短但很多细节参数需要手填写错就是花屏新接口是im2d函数族由im2d自动处理很多对齐和格式细节推荐新项目使用。老接口核心代码示例#include rga.h #include RgaApi.h static int rga_bgra_convert(void *src_vir, int src_fd, int width, int height, int hor_stride, int ver_stride, void *dst_vir, int dst_fd) { rga_info_t src {0}; rga_info_t dst {0}; src.fd src_fd; src.virAddr src_vir; src.mmuEnabled 1; src.format RK_FORMAT_YCbCr_420_SP; src.width width; src.height height; src.hor_stride hor_stride; src.ver_stride ver_stride; rga_set_rect(src.rect, 0, 0, width, height, hor_stride, ver_stride, RK_FORMAT_YCbCr_420_SP); dst.fd dst_fd; dst.virAddr dst_vir; dst.mmuEnabled 1; dst.format RK_FORMAT_BGRA_8888; dst.width width; dst.height height; dst.hor_stride width * 4; dst.ver_stride height; rga_set_rect(dst.rect, 0, 0, width, height, width * 4, height, RK_FORMAT_BGRA_8888); return rga_blit(src, dst, NULL); }新接口用起来简洁得多#include im2d.h #include RgaApi.h static int im2d_bgra_convert(void *src_ptr, int src_fd, int width, int height, int hor_stride, int ver_stride, void *dst_ptr, int dst_fd) { int ret 0; // 输入图像信息 im_rect src_rect {0, 0, width, height}; im_rect dst_rect {0, 0, width, height}; ret improcess(src_ptr, src_fd, hor_stride, ver_stride, RK_FORMAT_YCbCr_420_SP, dst_ptr, dst_fd, width * 4, height, RK_FORMAT_BGRA_8888, src_rect, dst_rect, IM_SYNC); return ret; }两种接口都支持传入fd或者虚拟地址。能传fd尽量传fd尤其是解码帧的MppBuffer它本身可以拿到fd走dma-buf路径可以让RGA直接通过硬件访问缓冲省掉CPU cache同步的开销。4.2 宽高、stride、偏移量这些参数是怎么影响出图的RGA做格式转换最核心的是三个概念width、height、wstrideRGA里也叫hor_stride。width是真实图像宽度wstride是缓冲区每行实际占用的像素数。对NV12来说每行存储的Y数据是按wstride对齐的而不是按width对齐。如果解码器输出hor_stride是1920而实际图像宽度是1900RGA如果按1920当作显示宽度处理转换结果就会多出20个像素的余量反过来如果RGA按1900去读数据行尾又会对不齐整个画面都会斜掉。BGRA8888目标同样有对齐要求。RGB排列每像素4字节理论上wstride width * 4就够了。但RGA内部对wstride有对齐要求通常是16字节对齐某些平台要求64字节对齐所以建议直接用(width * 4 15) ~15计算int dst_wstride (width * 4 15) ~15; int dst_ver_stride (height 15) ~15;我第一次写这段代码时偷懒直接用width * 4跑出来的画面在特定分辨率下右边缘会出现半列的颜色错位。后来改成16字节对齐后问题消失。再补一个可靠的经验MPP的解码帧stride是硬件给的不要自己算直接通过mpp_frame_get_hor_stride和mpp_frame_get_ver_stride读取RGA的目标stride必须自己基于width推算并对齐。4.3 内存来源与cache一致性问题RGA输入的source buffer来源多种多样MPP解码帧的MppBuffer、普通malloc内存、DMA内存池。这几种在RGA的使用上体验完全不同。最推荐的是直接传递MPP buffer对应的fd。MppBuffer内部就是dma-buf通过mpp_buffer_get_fd拿到的fd可以直接传给RGA的fd字段。RGA识别到fd就会走真正的硬件DMA路径不经过CPU也不会有cache不一致问题。如果只能用虚拟地址malloc出来的内存也可以给RGA但因为RGA硬件不经过CPU缓存需要做cache同步。新接口im2d在IM_SYNC模式下会自动处理老接口rga_blit不一定每次都做稳妥起见你自己cache_flush一下。项目上线后在RK3568上遇到过雪花点排查半天其实就是malloc内存段cache没刷干净。RGA读到的是CPU缓存里的旧数据结果转换出来的画面随机出现花点。改成从mpp_buffer池子里统一取内存后这个现象再没出现过。4.4 色彩空间与颜色范围H264解码出来的NV12其色彩空间定义通常来自SPS里的VUI信息。大多数监控摄像头和视频文件的色彩范围是BT.601 limited range但也有不少是BT.709 full range。RGA转换时如果色彩空间配置错了画面看起来会发灰或者发白就好比用错误的调色公式去套每个像素整体观感一片陈旧灰蒙。新接口im2d支持在improcess的第四个参数设置输出格式后再通过im_draw或imconfig去调整色彩空间矩阵。由于色彩空间配置在不同librga版本里API差异大我建议你找一个简单的办法验证解码出来后把NV12原始帧直接保存成文件放到PC上用YUV播放器看颜色对不对。如果原始颜色正常而转换后偏色就是RGA色彩空间配置的问题如果原始NV12就偏色就要先检查解码器输出格式设置。我的项目里需要统一转成BGRA给算法用算法期望的是full range的RGB所以我在RGA端配置了色彩空间扩展参数从limited range转full range。这一步不做的话算法框出来的目标框位置是正确的但分类置信度会整体偏低很迷惑人。5. 实测拷问花屏、偏移、卡顿三条线索的完整排查链路5.1 画面整体偏移或右下角斜线几乎全是stride问题现象很典型画面能出来但整体向右下方倾斜或者右侧有一条绿色/黑色的竖条宽度不确定。排查链路打印mpp_frame_get_hor_stride和mpp_frame_get_ver_stride看是否等于width/height。我见过很多驱动版本返回的hor_stride都带了32像素或64像素的对齐余量。检查传给RGA的源图src.hor_stride必须等于这个值而不是width。如果代码里写死了src.hor_stride width立刻改过来。检查目标buffer的dst_hor_stride是否等于(width * 4 15) ~15。再做一次自查把转换出的BGRA数据在本地保存成.bmp文件或直接送到显示器如果显示倾斜就说明配置仍有问题如果显示正常但上位机仍然花说明是上位机读取BGRA时也犯了同样的stride错误。这个链条占了RGA问题的大头90%的概率都是这里。5.2 颜色整体发绿或发紫格式与色彩空间不匹配现象画面结构正常但整体色调偏向绿色、紫色或者颜色非常淡。排查链路确认MPP实际输出格式。打印mpp_frame_get_fmt如果是MPP_FMT_YUV420SP_10BIT而你在RGA里按8bit的RK_FORMAT_YCbCr_420_SP处理颜色一定不对。10bit NV12是4个字节存两个像素内存排布完全不一样。确认RK_FORMAT枚举值与实际内存排布一致。NV12是RK_FORMAT_YCbCr_420_SPNV21是RK_FORMAT_YCbCr_420_SP的反转实际UV顺序不同千万别搞混。我看到过一个项目里把NV12写成NV21整体紫绿互换一开始还以为是算法问题。确认色彩空间。BT.601和BT.709的转换矩阵不同RGA默认使用BT.601如果码流是BT.709就需要设置色彩空间参数。保存原始NV12帧是排查颜色问题最快的路子没有之一。把解码帧的buffer地址dump到文件用电脑上YUV播放器打开就能快速区分是解码端颜色就不对还是RGA转换端颜色不对。5.3 长时间运行卡顿、内存缓慢上涨帧泄漏与cache刷写现象程序刚启动很流畅跑了一两个小时后开始掉帧内存占用缓慢爬升系统负载升高。排查链路先检查帧是否泄漏。mpp_frame_deinit漏掉的每帧都会有内存变化。统计每1万帧处理前后的/proc/meminfo或者/proc/self/status里的VmRSS如果有持续增长那基本可以断定是某些分支路径上没有释放MppFrame或MppPacket。检查rga_blit的调用频率。如果每次转换前都新建rga_info_t不做复用栈没问题但堆对象如果用了new也要确保释放。检查是否在虚拟地址模式下频繁做cache flush。这一项通常不会造成内存泄漏但会造成性能劣化。虚拟地址模式下每次flush都有不小的开销高频调用会明显拖慢主循环。看是否因为目标BGRA buffer反复申请释放导致内存碎片。这种场景建议用内存池提前申请block循环利用。我这边曾经有个“灵异现象”运行5小时之后内存涨了差不多200MB最后定位到是一个异常分支里调用mpp_frame_deinit之前直接continue跳过了释放逻辑。这种错误很隐蔽因为正常流程下帧都能释放只有异常分支会泄漏排查起来非常困难。从那之后我给所有解码循环都加了一层RAII式的封装保证无论从哪个分支退出都会释放帧。5.4 RTSP场景下的周期性卡顿现象画面每几秒就卡顿一次时间点不固定但频率和I帧间隔高度相关。这类问题大多不在解码本身而在拉流线程和数据投递节奏。H264解码时I帧数据量大网络抖动容易导致I帧的数据没有在预期时间内到齐解码器就会一直等数据直到完整I帧组装完成后才突然输出一批后续帧。看起来就是卡一笔然后猛地快进。解决办法是给输入加缓冲让解码器有足够的数据持续消化。我用的策略是把输入缓冲控制在3到5帧的码流数据量当缓冲低于阈值时暂停解码输出让网络追上来高于阈值时再继续。这样虽然增加了一点延迟但整体画面流畅度明显提升。实时性要求非常高的项目不建议这么做但大多数显示类项目能接受100到200ms的额外延迟。6. 性能实测与下一步优化方向6.1 RK3588单路1080p30的实测数据在我的RK3588平台上用RKMPP解一路1080p30 H264CPU占用率在解码线程内大约是百分之五到百分之八这其中包括了码流读取、packet构造和RGA调用。RGA做一次1080p NV12到BGRA8888转换硬件时间在1到2毫秒量级整条流水线从解码器输出帧到拿到BGRA数据延迟在5毫秒以内。这个数据对于显示、录制、算法预处理都是足够友好的。如果用CPU软解加CPU转格式同样的1080p30流畅度只能勉强做到CPU占用率会接近百分之百而且遇到复杂画面时还会掉帧。两块硬件的差距不是一个量级。在RK3588这种平台上继续往上叠路数我测过4路1080p30解码加4路RGA转换CPU总占用仍然保持在百分之二十以内主要是零碎开销。RGA是共享硬件单元多路转换时会产生排队但只要每路间隔分配好还是能保持整体实时的。如果到8路以上就需要关注RGA的带宽瓶颈。6.2 多通道场景的调度策略多路视频流并发时解码任务天然是多线程的。每个视频流一个解码线程、一个RGA转换上下文线程之间互不干扰。RGA调用本身是同步的但也可以放到一个独立的转换线程池里做异步化解码线程只管取帧转换线程负责把NV12转成BGRA。这样做的好处是解码线程不会被RGA的排队时间阻塞整体的帧间隔会更稳定。内存方面多路场景尤其要注意buffer复用。每路维护一个BGRA buffer池子转换完成的帧在显示或算法消费后立即回收到池子里。我在4路项目里给每个池子预分配了5帧BGRA空间换算下来内存占用约4路乘以1080p乘4字节乘5大约80MB完全可接受。6.3 零拷贝的进一步尝试RGA转换最大的成本不在计算而在内存搬运。如果能做到解出来的帧不被复制直接送往显示层或算法层性能还能再上一个台阶。Rockchip平台上的零拷贝思路主要有两条解码帧的MppBuffer直接作为RGA源RGA输出到dma-buf然后再通过drm/wayland直接显示全程CPU只拿到fd和地址不碰像素数据。算法模块如果能直接消费NV12数据最好不要转BGRA这样连RGA这一层转换都能省掉前提是模型输入支持YUV。如果你的算法只能吃RGBA那么RGA这层转换基本省不掉能优化的是把转换结果输出到复用缓冲区避免分配释放抖动。我最终在项目里保留了一个环形的BGRA buffer队列解码线程和算法线程通过fd索引传递性能比最初版本好了不少。6.4 最后再分享一个排查经验整个项目跑下来我最大的体会是这种硬件加速链路出问题时一定要先分层定位。**先确认解码帧格式对不对再把解码帧dump出来看图像对不对最后才去调RGA的参数。**很多人一上来就改RGA配置来回折腾半天浪费大量时间。把每一层的输入输出截图或存文件用工具验证再往下走几次下来就能形成自己的排错套路。如果你的项目里也要做RKMPP硬解加RGA转BGRA照着前面这几步走大部分坑都能避开。真遇到奇怪的现象按照第5章的排查链路一步步做很少会有解决不了的问题。
返回列表