
如果你的项目跑在RK3568这类瑞芯微板子上刚好又要做实时视频推流、录像或者智能分析那你大概率会遇到跟我一样的问题CPU软编码一开其他任务全被拖垮。我最近在做一个双路1080p低延迟图传最开始图省事用x264直推结果两路编码直接吃掉大半CPU系统响应越来越慢画面一复杂就开始掉帧。后来切换到RKMPP的H.264/H.265硬件编码同样两路1080p30CPU占用降到20%以内编码器本身几乎不占CPU画面也稳住。这篇文章不是API文档的复读而是把我从零接入RKMPP硬件编码的完整过程讲清楚先是整体架构认知再是参数细节然后是代码实现最后是优化手段和排查经验。如果你正准备在RK平台的嵌入式设备上跑视频编码这篇文章应该能帮你少走两三天弯路。1. 为什么我最后选了RKMPP硬件编码1.1 软编码撑不住时换个思路很多团队一上来就直接上x264或者x265软编码原因是现成、好调、交叉编译也简单。但嵌入式平台的CPU资源是有限预算尤其是做多路视频时软编码的钱包很快就见底。我这次的需求是双路1080p30同时编码每路如果用x264的ultrafast档单核基本被吃掉80%两路就直接把四核CPU干到吃紧。系统里还有网络线程、UI线程、日志线程一忙起来全部跟着卡。换成RKMPP硬件编码之后编码这件事从CPU搬到了VEPU单元CPU只负责喂数据和收码流。实测同样两路1080p30 H.264CPU占用直接掉到20%以内而且不会因为画面纹理复杂而产生编码卡顿。这个收益对于长期运行、需要稳定帧率的设备来说是质的区别。所以第一个建议如果你的目标平台是瑞芯微RK3566、RK3568、RK3588、RK3399这类芯片并且需要跑H.264/H.265编码不要犹豫直接走RKMPP。软编码留给那些没有硬件编码器的老平台或者你只是在电脑上做离线转码。1.2 RKMPP到底做了什么RKMPP全称Rockchip Media Process Platform是瑞芯微提供的统一媒体处理中间件。它不只是一个简单的编码库而是把视频编解码、图像处理、内存管理都包了一层。实际编码依赖的是内部的VEPUVideo Encoder Processing Unit硬件模块而你通过MPP接口告诉它“我要编H.264、分辨率多少、码率多少”然后喂NV12帧它给你吐H.264/H.265码流。这里要提到一个容易混淆的点RKMPP不负责色彩空间转换也不负责缩放。如果摄像头输出的是RGB或BGR你需要先用RGA或者软件转成NV12再交给编码器。这跟我第一次用时的预期不一样我以为传个RGB帧进去就能直接编码结果要么报错要么出来是花的。后来查文档才知道MPP编码器的主流输入格式是NV12YUV420SPRGB不是它的主战场。整个数据流大概是这样的采集设备产生原始帧通常是NV12或者需要转换的RGB→ 必要时通过RGA做格式转换/缩放/旋转 → 填入MPP的MppBuffer → 调用编码接口 → VEPU硬件编码 → 取出MppPacket码流 → 封装成H.264/H.265文件或推流。搞清楚这条链路后面所有的参数配置都不容易跑偏。1.3 H.264还是H.265先做选型RKMPP对H.264和H.265的编码支持在API层面几乎是一套写法区别主要在编码类型、profile、level这几个字段上。所以真正要取舍的不是实现难度而是应用场景。对比维度H.264H.265同等画质码率相对偏高通常省30%-50%播放兼容性PC/手机/浏览器普遍支持老设备/部分浏览器不支持硬件解码要求低略高实时性更容易做到低延迟编码/解码链路上延迟略高适合场景实时预览、低延迟图传、兼容性优先存储录像、带宽受限、无线传输我做双路图传时前端是自研的播放器解码能力完全可控所以第一版用了H.264因为低延迟链路更稳。但如果是做NVR录像或者长时间存储H.265会明显省空间。以1080p30为例H.264在CBR模式下通常要给4Mbps才够清晰H.265给2Mbps就能接近同等观感对于存储量很大的设备这个差距非常可观。选型的另一个建议是看你的下游解码端。如果视频要推给浏览器播放H.264的兼容性明显更好如果是在自己的APP或者自研盒子里解码H.265也完全没问题。RKMPP切换编码类型只需要改一个配置项所以建议前期把两条路都跑通后面切换成本几乎为零。2. 跑通编码前必须搞懂的两组关键结构2.1 输入帧NV12、宽高对齐和stride的坑MPP硬件编码的输入帧最常用的是NV12也就是YUV420SP格式。它在一段连续内存里先放Y平面再放UV交叉排列的数据。以1080p为例Y平面是1920×1080字节UV平面是1920×1080/2字节总大小就是1920×1080×3/2。但这里有一个非常容易踩的坑编码器的输入宽高并不一定等于你图像的逻辑宽高。硬件内部是分块处理的宽高通常需要对齐到16的倍数甚至某些平台建议对齐到64。如果你只设置了width1920、height1080而实际内存布局按照1920×1088去排那就要把hor_stride和ver_stride也设置成1920和1088。stride可以简单理解成内存里每一行实际占用的宽度对齐是为了方便硬件按宏块读取。1080本身不是16的倍数1080÷1667.5所以需要补到1088。如果你不设置stride或者设置错了轻则编码出来的画面偏移重则直接花屏。我当时就是width、height、hor_stride、ver_stride四个字段没对齐折腾了整整一天。建议在分配buffer和填写配置时统一用一个对齐函数static int align_to_16(int v) { return (v 15) ~15; }然后用对齐后的值去算buffer大小和填充配置。这样能避免很多莫名其妙的显示问题。另外要记住NV12输入时buffer大小是hor_stride × ver_stride × 3/2而不是width × height × 3/2。2.2 缓冲对象MppBuffer/MppFrame/MppPacket怎么配合新手看RKMPP代码时最容易被MppBuffer、MppFrame、MppPacket这几个对象搞晕。我用自己的话捋一遍MppBuffer底层的内存块可能是ION或DMA分配出来的物理连续内存用来装原始像素数据或者码流数据。MppFrame描述一帧输入画面的结构体里面包含宽高、stride、格式、pts以及一个指向MppBuffer的引用。MppPacket描述一帧输出码流的结构体里面包含码流数据指针、长度和pts。整个流程就是你从BufferGroup里拿一个MppBuffer把一帧NV12数据拷进去然后用MppFrame把这个Buffer包起来设置好宽高和pts调用encode_put_frame送给编码器。编码完成后用encode_get_packet拿到的MppPacket就是编码后的H.264/H.265数据。这里要特别注意BufferGroup的作用。每个编码实例最好有一个自己的MppBufferGroup从组里分配buffer用完之后放回组里重复使用。千万不要每编码一帧就重新创建buffer、用完就销毁那样会引入不必要的内存分配开销长期运行还会造成内存碎片和性能抖动。Packet的释放也别偷懒。取到packet后如果你需要拷贝码流数据就先memcpy出来拷贝完立即调用mpp_packet_deinit释放掉。如果光取不还内部缓冲区很快会被占满延迟越来越大最后编码器直接罢工。2.3 一份标准1080p30 H.264编码参数清单我整理了一份可以直接参考的配置清单单位、推荐值、坑都写在表里。下面这份是H.264、1080p30、CBR 4Mbps的常用配置。配置项推荐值说明与坑prep:width1920图像逻辑宽度prep:height1080图像逻辑高度prep:hor_stride1920对齐后每行宽度建议16的倍数prep:ver_stride1088对齐后行数建议16的倍数prep:formatMPP_FMT_YUV420SP即NV12编码最常用rc:modeMPP_ENC_RC_MODE_CBR恒定码率追求画质可以换AVBRrc:bps4000000单位是bps不是kbps这是重灾区rc:bps_max4000000CBR下和bps保持一致rc:bps_min4000000CBR下和bps保持一致rc:gop6030fps下代表2秒一个IDR帧rc:fps_in_flex00为按固定节拍1为输入灵活rc:fps_in_num30输入帧率分子rc:fps_in_denorm1输入帧率分母rc:fps_out_flex0输出是否跟随输入灵活调整rc:fps_out_num30输出帧率分子rc:fps_out_denorm1输出帧率分母codec:typeMPP_VIDEO_CodingAVCH.264换H.265改成HEVCcodec:profile100100是High Profile低延迟可改66codec:level404.0对应1080p60以内的常见场景我建议你把这个表保存下来每次配置都对照一遍。特别强调一下rc:bps的单位MPP文档里写的是byte per second还是bit per second不同版本有歧义但实际在编码配置里用的是bit per second。我第一次设置成4000000以为单位是kbps结果码率直接失控轻轻松松跑到几十Mbps。后来确认4Mbps就是4000000不要多乘以1024也不要少写两个零。3. 从代码到码流RKMPP编码器实操3.1 初始化编码器并下发配置初始化RKMPP编码器的逻辑按照官方sample的顺序走就行核心是mpp_create、mpp_init、mpi-control三步。完整初始化代码类似下面这样#include mpp_enc.h #include mpp_enc_cfg.h MppCtx ctx; MppApi *mpi; MppEncCfg cfg; MppPollType timeout MPP_POLL_NON_BLOCK; RK_U32 width 1920; RK_U32 height 1080; RK_U32 hor_stride 1920; RK_U32 ver_stride 1088; // 1. 创建上下文 mpp_create(ctx, mpi); // 2. 初始化编码上下文编码类型为H.264 mpp_init(ctx, MPP_CTX_ENC, MPP_VIDEO_CodingAVC); // 3. 使用非阻塞取包模式避免取包时卡死 mpi-control(ctx, MPP_SET_OUTPUT_BLOCK, timeout); // 4. 初始化编码配置结构体 mpp_enc_cfg_init(cfg); // 5. 配置输入格式相关 mpp_enc_cfg_set_s32(cfg, prep:width, width); mpp_enc_cfg_set_s32(cfg, prep:height, height); mpp_enc_cfg_set_s32(cfg, prep:hor_stride, hor_stride); mpp_enc_cfg_set_s32(cfg, prep:ver_stride, ver_stride); mpp_enc_cfg_set_s32(cfg, prep:format, MPP_FMT_YUV420SP); // 6. 配置码控相关 mpp_enc_cfg_set_s32(cfg, rc:mode, MPP_ENC_RC_MODE_CBR); mpp_enc_cfg_set_s32(cfg, rc:bps, 4 * 1000 * 1000); mpp_enc_cfg_set_s32(cfg, rc:bps_max, 4 * 1000 * 1000); mpp_enc_cfg_set_s32(cfg, rc:bps_min, 4 * 1000 * 1000); mpp_enc_cfg_set_s32(cfg, rc:gop, 60); mpp_enc_cfg_set_s32(cfg, rc:fps_in_flex, 0); mpp_enc_cfg_set_s32(cfg, rc:fps_in_num, 30); mpp_enc_cfg_set_s32(cfg, rc:fps_in_denorm, 1); mpp_enc_cfg_set_s32(cfg, rc:fps_out_flex, 0); mpp_enc_cfg_set_s32(cfg, rc:fps_out_num, 30); mpp_enc_cfg_set_s32(cfg, rc:fps_out_denorm, 1); // 7. 配置编码器相关 mpp_enc_cfg_set_s32(cfg, codec:type, MPP_VIDEO_CodingAVC); mpp_enc_cfg_set_s32(cfg, codec:profile, 100); mpp_enc_cfg_set_s32(cfg, codec:level, 40); // 8. 把配置下发到编码器 mpi-control(ctx, MPP_ENC_SET_CFG, cfg); // 9. 让每个IDR帧都携带SPS/PPS方便播放器随时接入 MppEncHeaderMode header_mode MPP_ENC_HEADER_MODE_EACH_IDR; mpi-control(ctx, MPP_ENC_SET_HEADER_MODE, header_mode); // 10. 配置结构体不再需要 mpp_enc_cfg_deinit(cfg);这里值得关注的是非阻塞取包设置。如果你用默认的阻塞模式encode_get_packet在编码器没有输出时可能会一直等下去一旦喂帧节奏出了点问题线程就被挂住整个业务跟着卡。我用MPP_POLL_NON_BLOCK之后取包变成了“有就取没有就下一轮再取”的模式对系统健壮性提升很明显。3.2 编码主循环送帧与取包编码主循环的逻辑比较直接获取一帧原始数据放进MppBuffer包装成MppFrame调用encode_put_frame送进去然后立刻encode_get_packet取码流。取到packet后拷贝数据、释放packet一帧完成。下面是一段可以跑通的核心循环MppBufferGroup group; MppBuffer buf NULL; MppFrame frame; MppPacket packet NULL; RK_S64 pts 0; // 创建buffer group内部使用ION mpp_buffer_group_get_internal(group, MPP_BUFFER_TYPE_ION); // 计算NV12 buffer大小 size_t frame_size hor_stride * ver_stride * 3 / 2; while (running) { // 1. 从group里拿一块buffer mpp_buffer_get(group, buf, frame_size); // 2. 把NV12原始数据拷贝到buffer里 void *dst mpp_buffer_get_ptr(buf); memcpy(dst, nv12_src, frame_size); // 3. 构造MppFrame mpp_frame_init(frame); mpp_frame_set_width(frame, width); mpp_frame_set_height(frame, height); mpp_frame_set_hor_stride(frame, hor_stride); mpp_frame_set_ver_stride(frame, ver_stride); mpp_frame_set_fmt(frame, MPP_FMT_YUV420SP); mpp_frame_set_pts(frame, pts); mpp_frame_set_buffer(frame, buf); // 4. 送入编码器 mpi-encode_put_frame(ctx, frame); // 5. 取码流 mpi-encode_get_packet(ctx, packet); if (packet) { void *out_ptr mpp_packet_get_data(packet); size_t out_len mpp_packet_get_size(packet); if (out_len 0) { fwrite(out_ptr, 1, out_len, fp); // 这里可以拿到文件格式的H.264数据 } mpp_packet_deinit(packet); packet NULL; } // 6. 清理本帧对象归还buffer mpp_frame_deinit(frame); mpp_buffer_put(buf); pts 1000000 / 30; // 按30fps计算pts单位微秒 }这个循环有两个细节值得注意。第一mpp_frame_deinit和mpp_buffer_put的顺序不能反一定要先把frame释放再把buffer归还原组。如果先归还bufferframe内部还引用着这块内存编码器可能还没来得及读取造成花屏或者数据错乱。第二packet取出来之后要尽快处理不要堆积在业务队列里。我遇到过长时间运行后延迟暴涨的情况最后定位出来就是取包后塞到网络发送队列网络慢导致队列积压编码器内部缓存被占满。3.3 H.265到底改动哪几行H.265的切换比想象中简单大部分配置完全复用。真正要改的就三处第一个是mpp_init时的编码类型从MPP_VIDEO_CodingAVC改成MPP_VIDEO_CodingHEVC。第二个是codec:type也改成MPP_VIDEO_CodingHEVC。第三个是profileH.264的100对应H.265通常是Main Profile建议设成1level可以根据分辨率继续用40。mpp_init(ctx, MPP_CTX_ENC, MPP_VIDEO_CodingHEVC); mpp_enc_cfg_set_s32(cfg, codec:type, MPP_VIDEO_CodingHEVC); mpp_enc_cfg_set_s32(cfg, codec:profile, 1); mpp_enc_cfg_set_s32(cfg, codec:level, 40);如果之前H.264的码率是4Mbps切到H.265后可以先按2Mbps试观察画质再微调。实测在嵌入式设备上H.265的编码速度并不会比H.264慢太多因为都是同一块硬件VEPU在处理区别主要在于码流封装和解码端的兼容性。验证编码结果的方法很简单把写入文件的码流拷贝到电脑上用ffprobe查看ffprobe -v error -show_streams -format json output.h264重点看宽高、profile、level和码率是否和你配置一致。如果显示的分辨率不对或者level不对说明配置没完全生效优先检查width和stride有没有配错。也可以抽出几帧用ffplay直接播放确认画面没有花屏偏色。4. 画质、码率和延迟的平衡点码控和GOP优化4.1 四种码率控制模式怎么选RKMPP编码器支持多种码控模式我实际用下来主要有四种CBR、VBR、AVBR、FIXQP。它们各自的定位差异很大选错模式会让你在画质和码率之间反复横跳。CBR恒定码率输出码率最稳定适合实时传输和带宽受限的场景。我图传项目里用的就是CBR因为网络管道带宽固定码率不稳就会卡顿。但CBR的代价是画面复杂时为了控码率不得不提高量化参数画质会有所波动。VBR可变码率允许码率在设定范围内浮动画面复杂时用更多码率简单时减少码率。比CBR省码率画质也更均匀但码率峰值不可控不适合带宽严格受限的场景。AVBR自适应码率是VBR的增强版。它会根据画面运动程度自动调整码率同时尽量维持目标码率附近。这个模式在存储场景很实用实测下来比CBR能省不少空间画质损失又比VBR可控。FIXQP固定量化参数直接指定QP值不去管码率。这个模式适合测试和调试。我之前排查画质问题时就用FIXQP固定成QP28对比不同参数下的主观画质不会被码控策略干扰。模式码率稳定性画质推荐场景CBR最稳复杂画面可能下降实时图传、网络传输VBR波动好录像、存储AVBR相对稳好折中场景FIXQP不可控可精确控制调试、画质对比4.2 GOP结构和B帧对延迟的影响GOP是指两个IDR帧之间的帧组长度intra refresh的节奏。GOP越短IDR帧越多码率越高但播放器可以更快地完成随机接入。GOP越长码率更省但断点续播或者网络抖动后重新同步会变慢。常规场景GOP可以设置成帧率的2倍比如30fps设60相当于2秒一个IDR。低延迟交互场景可以更激进设成30甚至15但预算要有心理准备IDR帧体积通常是普通帧的5到10倍码率会被拉高。真正影响延迟的还有B帧。B帧会引入多帧重排序编码端必须等后面的帧到达才能输出前面帧解码端也面临同样问题。对实时性要求高的场景我通常建议避开B帧。在H.264里最直接的办法是使用Baseline Profile也就是把profile设成66因为Baseline本身不允许B帧。当然High Profile的画质和压缩率更好。如果后端播放器没那么敏感或者你的延迟预算比较宽松用High Profile也能接受。这里要看你自己的选择没有绝对。RKMPP还提供了MPP_ENC_HEADER_MODE_EACH_IDR选项让每个IDR帧都携带SPS/PPS。我强烈建议开启尤其是做RTSP或者FLV等需要随时接入的封装格式时。如果不开启某些播放器在没有SPS/PPS的情况下遇到第一个IDR帧可能还解不出画面黑屏几秒。4.3 画质微调与码率节省从配置到策略除了码控模式和GOP还有几个参数对画质和码率的平衡影响很大。一个是量化参数的范围限制在MPP配置里可以设置最大QP和最小QP。比如CBR模式下为了避免画面极其复杂的瞬间画质崩到没法看可以把最大QP限制在40左右牺牲一点码率上限换取基本画质。另一个是I帧和P帧的质量等级。MPP支持对I帧和P帧分别设置质量区间比如让I帧稍微多给点码率因为I帧质量会影响后面一整个GOP的参考基准。实测把I帧质量拉高之后整个画面组的干净程度会明显提升。码率节省方面除了切H.265还可以考虑在采集端做降分辨率后再编码。比如监控大屏场景不一定要1080p原始分辨率720p就足够清晰这时先在RGA里做一次缩放再送编码器比在1080p上强行压低码率效果更好画质损失也更小。另外如果硬件支持尽量让摄像头直接输出NV12省去RGB转NV12这一步。颜色转换本身不贵但会引入额外耗时和内存带宽消耗对实时性要求高的场景能省则省。4.4 实测数据参考我这边在RK3568平台上做过一组简单对比条件固定为1080p30、H.264、画面内容是一段有运动物体的视频配置平均码率主观画质说明CBR 4Mbps4Mbps清晰稳定但码率偏高CBR 2Mbps2Mbps有可见纹理模糊码率压低后画质下降明显AVBR 2Mbps2.1Mbps接近CBR 3Mbps省码率且画质可接受H.265 AVBR 2Mbps2Mbps接近CBR 4Mbps的H.264同码率下H.265优势明显这组数据不一定能直接复用到你的场景因为画质主观性很强测试内容不同结果差异也大。我只想说明一个趋势如果带宽和存储是瓶颈优先上H.265如果只能在H.264里选AVBR通常比CBR更划算。5. 多路并发与稳定性优化5.1 一路跑不满多路怎么保持稳定RKMPP硬件编码不是只能跑一路你可以在一个进程里同时创建多个MppCtx每个实例独立配置、独立编码。我在RK3568上实际跑过双路1080p30 H.264一路4Mbps一路2Mbps整体稳定。但多路并发有几个注意事项。第一硬件编码器总处理能力有上限和具体芯片型号强相关别指望你买的是高配芯片就一定能跑满N路最好在量产前做压力测试把每路的负载和内存占用都确认清楚。第二每个编码实例建议独立使用一个MppBufferGroup不要在多个ctx之间共享buffer避免内存访问冲突和同步问题。线程模型上我的习惯是一路一个线程线程内部只做“取帧→送编码→取包→投递码流”的动作不做其他重活。有条件的话可以把这个编码线程绑定到特定CPU核心上减少调度抖动。实测开启线程绑核之后帧间隔的波动明显变小对低延迟场景有帮助。5.2 低延迟链路实战低延迟是图传和无人机场景的刚需这里有几个实战技巧可以分享。第一个是喂帧节奏的控制。很多人习惯用usleep(33333)来模拟30fps但usleep的精度受系统调度影响很大实际帧间隔可能忽快忽慢。我建议用pts来驱动而不是sleepstruct timespec now; clock_gettime(CLOCK_MONOTONIC, now); RK_S64 target_pts pts; int64_t current_us now.tv_sec * 1000000 now.tv_nsec / 1000; if (current_us target_pts) { usleep(target_pts - current_us); } pts 1000000 / 30;这样的好处是即使某帧处理慢了下一帧会尽量追回目标时间点整体节奏比固定sleep更平滑。如果有人告诉你sleep最省事那是他没在低延迟场景里吃过亏。第二个是编码器取包模式的设置。在初始化时把输出设为非阻塞取包时如果拿不到packet就继续下一轮循环不要死等。硬件编码虽然快但也有忙不过来的时候死等反而会把问题放大。第三个是编码器配置里fps_in_flex的用法。如果输入帧率是固定的建议fps_in_flex设为0让编码器按固定节奏工作。如果你的输入帧率可能抖动就把fps_in_flex设为1编码器会尽量跟随输入节奏减少额外等待。这个字段不理解时容易乱调我建议先保持0跑出基线数据后再尝试1。5.3 内存回收和缓存控制长时间运行的编码程序最容易出的问题其实是内存和缓冲区的堆积。MppPacket取出来之后必须及时释放如果网络发送队列拥塞packet被积压在业务队列里编码器内部缓存就会持续增长延迟和内存一起飙升。我的做法是限制输出队列深度。每次取到packet后立刻拷贝数据然后马上deinit不持有任何MppPacket对象。业务端如果需要缓冲只缓冲拷贝出来的std::vector或者malloc出来的内存并设置队列上限超过上限就丢旧帧或者直接丢当前帧。这样可以把内存占用控制在一个稳定范围。另外一个值得注意的点是MppBufferGroup的复用。如果每次编码都创建新group、每次取buffer都新建buffer长时间运行后内存分配次数会非常可观。正确的做法是在初始化时创建一个group编码循环中重复使用mpp_buffer_get和mpp_buffer_put让buffer在组内循环复用。5.4 长期运行的稳定性几个建议跑视频编码的程序最怕的不是一开始跑不通而是跑了一天后才崩。我总结几个长期运行的稳定性建议。第一定期检查mpi-control和encode_put_frame的返回值任何一次非MPP_OK的返回都不应该被忽略。硬件编码器在长时间高负载下偶尔会返回错误你至少要打印日志方便事后定位。第二把编码线程和其他业务线程分开不要让网络阻塞直接影响编码节奏。第三对于PTZ、对讲、云台控制这类低时延控制指令一定不要和编码线程共用同一个锁。第四如果项目允许可以做看门狗机制定期检查编码器是否还在正常出帧连续超时后进行重启恢复。这些听起来像老生常谈但每一句都是我踩过坑之后才深刻体会的。尤其是线程锁的问题曾经某次升级后控制指令和编码流程共用了互斥锁结果网络慢的时候控制也卡排查了两天才发现锁被编码线程占住了。6. 常见问题排查与自查清单6.1 首帧迟迟不来或没有SPS/PPS如果调完配置后encode_get_packet一直拿不到packet或者拿到的第一个packet不是SPS/PPS开头先检查三件事header_mode有没有设置成MPP_ENC_HEADER_MODE_EACH_IDR输入帧的width和stride是否一致encode_put_frame之后是否紧接着调用了encode_get_packet。我遇到过的情况是只在初始化时调用了一次encode_get_packet以为能拿到头信息但实际上编码器要等到有帧送进去才会产生输出。正确做法是送完首帧后立即取包这时拿到的才是带SPS/PPS的初始化码流。6.2 花屏、绿屏和条纹花屏是最常见的异常原因通常出在输入数据上。第一个嫌疑是stride没对齐导致硬件按错误的内存布局读取数据。第二个嫌疑是format填错明明是NV12却填了I420或其他格式。第三个嫌疑是buffer数据没有写完整特别是通过Cache写入的内存需要在写完后做Cache刷新。如果你用的是普通malloc内存然后memcpy一般不需要手动刷Cache但如果用了DMA/ION映射的内存写入后可能需要调用相关接口保证数据可见。这个跟具体平台和SDK版本有关遇到花屏时建议先查这三个点。6.3 码率与设置值严重不符码率设置的单位是最容易搞错的地方。rc:bps的单位是bit per second所以4Mbps直接填4000000。如果你填的是4096000或者4都会得到离谱的结果。另一个原因是没有正确设置fps_in和fps_out。如果编码器认为输入帧率是10fps而它内部又按10fps去做码控实际接收30fps时码率就会膨胀到3倍。所以fps_in_num、fps_in_denorm、fps_out_num、fps_out_denorm这些字段一定要和实际帧率一致。6.4 帧率上不去或延迟越来越大帧率上不去首先要看喂帧节奏。如果你在业务线程里做了很多耗时操作再调用encode_put_frame编码器就会等帧输出自然上不去。建议把喂帧和取包放到一个独立线程里并且按pts计算时间而非sleep固定值。延迟越来越大的原因十有八九是输出队列堆积。检查一下是不是取到packet后没有及时处理是不是网络发送阻塞了是不是业务队列没有上限。把packet释放提前到拷贝完成后立刻执行延迟问题通常会缓解。6.5 问题速查表现象可能原因排查方向首帧不来没送帧就取包先encode_put_frame再encode_get_packet无SPS/PPSheader_mode配置不对设置MPP_ENC_HEADER_MODE_EACH_IDR画面花屏stride/format/buffer 错误重点检查NV12布局和hor_stride/ver_stride码率过高单位填错或fps配置错确认rc:bps单位确认fps_in/out延迟增大输出队列堆积及时deinit packet限制队列深度程序崩溃buffer释放顺序错误先mpp_frame_deinit再mpp_buffer_put多路互相干扰共用ctx或buffer group每路独立ctx和group长期内存上涨packet/buffer泄漏检查所有deinit和put是否成对真要说有什么经验我最想留给大家的是拿到新板子先别急着写业务代码去跑一遍官方mpi_enc_test把--type264、--w1920、--h1080、--bps4000000、--rc_modeCBR这样的命令试通确认这条链路在你手里的固件上没问题再开始动应用层。我踩过最大的坑就是以为参数都对结果总线上实际跑的stride和申请buffer对不上画面花了两天最后一张一张对比官方sample才找出来。硬件编码本身不复杂复杂的是你愿不愿意把每个字段确认到底。希望这篇实践能帮你少熬夜。