
1. 项目背景与整体思路做嵌入式Linux视频开发的朋友多半都绕不开Rockchip MPP这套解码库。MPP全称是Media Process Platform瑞芯微提供的这套多媒体处理框架专门用来在自家芯片上做视频编解码、图像处理这类重活。我这次要分享的是最基础也最常用的一条链路读取一个H264文件通过MPP硬解码把每一帧的视频数据输出成YUV裸流保存到本地文件。整个过程不涉及显示也不做编码就是把“文件到YUV”这条最核心的通路跑通。这个实战适合谁如果你刚接触Rockchip平台对MPP的API一头雾水或者你能用ffmpeg软解H264但想换成硬解、把CPU占用降下来那这篇文章正好对口。哪怕你之前没写过MPP代码只要懂一点C语言、知道线程和内存的基本概念跟着我下面的步骤走就能把流程跑起来。等这条路通了再去接显示、接RTSP、接Qt渲染就都有了底子。为什么我强调从文件到YUV因为YUV是解码器输出的最终裸数据它不依赖任何封装格式后面你想转RGB、送显示器、做AI推理都是从这一步开始的。H264文件本身只是一堆压缩码流解码器的任务就是把它还原成一幅幅图像。理解了这个过程就不会被MPP的包结构、帧结构绕晕。本文我会先讲清楚H264编码和YUV格式的基础再介绍MPP的API设计思路最后给出一份可以直接编译运行的完整代码并把我在调试中踩过的坑一并列出来。整篇文章的核心就一句话搞清楚MPP的输入是压缩包输出是图像帧中间的顺序不能乱。2. 核心原理解析H264编码与YUV格式2.1 H264编码原理与码流结构做解码之前最起码要知道H264码流里装了什么。H264编码的本质是去除视频帧之间的空间冗余和时间冗余。空间冗余靠帧内预测、变换、量化来处理时间冗余靠帧间运动估计和补偿来处理。这就是为什么码流里会有I帧、P帧、B帧的区别I帧是关键帧可以独立解码P帧参考前面的帧B帧参考前后的帧。MPP在解码时会自动处理这些参考关系我们只需要把完整的码流喂给它就行。在实际的H264码流文件里数据是以NALUNetwork Abstraction Layer Unit网络抽象层单元为单位组织的。每个NALU由起始码和NAL头组成常见起始码是00 00 00 01或00 00 01。NAL头里最重要的字段是nal_unit_type它决定了这个NALU的类型。比如类型1是片数据类型7是SPS序列参数集类型8是PPS图像参数集。SPS和PPS包含了分辨率、帧率、参考帧数量等关键信息解码器必须拿到它们才能开始正常解码。如果是裸H264文件SPS和PPS通常出现在文件头部或I帧前面。如果你的码流是RTP打包过来的SPS和PPS可能会带外传输需要自己拼进码流再送解码器。理解码流结构对MPP开发非常重要。MPP的输入就是一个一个的MppPacket每个Packet可以是完整的帧数据也可以是分片的码流。通常我们把一个NALU或者一组连续NALU封装成一个Packet带着长度信息送给解码器。解码器内部会做拆包、组帧、解析SPS/PPS这些动作。所以你不需要自己解析出每一帧的边界但必须保证码流数据是完整的、连续的不能丢数据不能乱序。2.2 YUV格式与存储布局解码器输出的是YUV裸数据这是视频领域的通用中间格式。Y代表亮度U和V代表色度。人眼对亮度更敏感所以YUV可以让色度采样率低于亮度采样率从而压缩数据量。常见的采样格式有4:4:4、4:2:2、4:2:0视频会议和流媒体里绝大多数是4:2:0。MPP在Rockchip平台上最常见的输出格式是NV12属于YUV 4:2:0的一种内存布局。它的排列方式很简单前面是整帧的Y平面数据后面交替存储U和V平面。具体来说Y分量占width*height字节U和V合计占width*height/2字节总大小是width*height*3/2。内存里先Y后UVUV又是交错存放所以叫NV12。另一种常见格式I420则是U平面和V平面分开存放MPP也支持输出。调试时最直观的验证方法就是把YUV数据保存下来用YUV播放器打开如果画面正常说明整条链路没问题。这里必须提醒一个问题对齐。解码器输出帧的宽高不一定是原始视频的宽高而是按照MPP内部的要求做了对齐一般是16字节对齐。比如1920x1080的视频解码器输出的实际尺寸可能是1920x1088或者1920x1080取决于对齐策略。如果你直接拿MppFrame返回的宽高去分配显存或文件写入没问题但如果你想按原始尺寸保存就得自己裁剪。我后面代码里会演示怎么用MppFrame的宽高信息避免踩这个坑。3. 环境准备与MPP开发基础知识3.1 软硬件环境与交叉编译我这次用的平台是RK3588板子跑的是Ubuntu系统但代码用的是通用的MPP API换到RK3399、RK3568等平台也一样。MPP源码可以直接去Rockchip的官方仓库拉取在Linux主机上交叉编译生成librockchip_mpp.so和头文件。如果你用的SDK自带MPP那更简单直接链接就行。编译MPP本身不复杂核心步骤就是CMake。先确认交叉编译工具链已经安装然后在源码根目录执行mkdir build cd build cmake .. -DCMAKE_TOOLCHAIN_FILE../cmake/toolchains/aarch64.linux.cmake make -j8如果是直接在ARM板子上本地编译直接cmake .. make就行。编译完成后会把头文件和库输出到rockchip_mpp/inc和rockchip_mpp/lib目录。我习惯把整个目录拷到板子上编译应用的时候直接指定头文件和库路径。链接的时候除了rockchip_mpp还可能需要链接它的依赖库比如rockchip_mpp_rk3588。最保险的方法是-lrockchip_mpp然后通过pkg-config或者手动指定库路径。具体看我下面的编译示例aarch64-linux-gnu-gcc main.c -I./rockchip_mpp/inc -L./rockchip_mpp/lib -lrockchip_mpp -lpthread -o decode_h2643.2 MPP核心API与调用模型MPP的API设计其实不算复杂核心对象就几个MppCtx是解码器上下文MppBuffer是经过内存池管理的缓冲区MppPacket表示输入码流包MppFrame表示解码输出帧。整个解码过程可以看作一条流水线先创建解码器然后循环往里送Packet再从输出端口拿Frame。创建解码器的流程很固定。我先说一下代码结构后面实战部分再展开。MppCtx mpp_ctx NULL; MppApi *mpi NULL; mpp_create(mpp_ctx, mpi); mpp_init(mpp_ctx, MPP_CTX_DEC, MPP_VIDEO_CodingAVC);mpp_create会把上下文和API句柄一起返回mpp_init指定解码类型。H264对应的编码类型是MPP_VIDEO_CodingAVC。如果是H265换成MPP_VIDEO_CodingHEVC。初始化完之后还需要设置一些解码参数比如mpi-control(mpp_ctx, MPP_DEC_SET_OUTPUT_FORMAT, format)把输出格式设为NV12。这个格式设置也是后面容易踩坑的地方如果解码器内部已经根据SPS确定了格式你再去改可能不生效需要放在合适的时机。MPP的解码模型支持很多高级特性比如编解码组、参考帧管理、自适应分辨率等。想深入理解可以去看它的头文件mpp_rc.h、mpp_buffer.h。不过对于“文件到YUV”这个场景只需要关心Packet和Frame的递交、回收流程就行。4. 完整实战从H264文件到YUV文件4.1 读取H264文件并封装成MppPacket先把文件读进内存。这里有两种做法一种是一次性把整个文件读入内存然后按长度切分作为一个大Packet送给解码器另一种是每次读若干字节组成Packet。我推荐第一种简单直接文件够大也不会出问题MPP内部会自己边读边解。整个文件作为一组数据流送进去解码器会自动拆帧。uint8_t *file_data NULL; size_t file_size 0; FILE *fp fopen(input.h264, rb); if (!fp) { perror(fopen); return -1; } fseek(fp, 0, SEEK_END); file_size ftell(fp); fseek(fp, 0, SEEK_SET); file_data malloc(file_size); fread(file_data, 1, file_size, fp); fclose(fp);读进来之后直接把这整块数据封装成MPP的输入包。MPP的mpp_packet_init可以指定数据和大小。MppPacket packet NULL; mpp_packet_init(packet, file_data, file_size);需要注意的是mpp_packet_init得到的数据指针是外部内存MPP在解码过程中不会帮你释放它需要自己管好生命周期。等解码器确认这个Packet的所有数据都被消费掉之后才能释放或复用这块内存。在文件解码场景里我一般就等全部解码完成后再释放文件数据省心。另一种做法是循环读取文件块每次init一个Packet解码完再释放但那样需要处理Packet的引用计数和缓冲复用新手容易出错。4.2 初始化解码器并配置输出格式这部分是关键。我按实际使用的顺序写一遍并用注释说明每步的作用。MppCtx mpp_ctx NULL; MppApi *mpi NULL; MppBufferGroup frm_grp NULL; uint32_t need_split 1; MppFrameFormat fmt MPP_FMT_YUV420SP; mpp_create(mpp_ctx, mpi); mpp_init(mpp_ctx, MPP_CTX_DEC, MPP_VIDEO_CodingAVC); mpi-control(mpp_ctx, MPP_DEC_SET_PARSER_SPLIT_MODE, need_split); mpi-control(mpp_ctx, MPP_DEC_SET_OUTPUT_FORMAT, fmt); mpp_buffer_group_get_internal(frm_grp, MPP_BUFFER_TYPE_ION); mpi-control(mpp_ctx, MPP_DEC_SET_EXT_BUF_GROUP, frm_grp);need_split 1告诉MPP内部自己拆分码流不需要我们手动分帧。这个参数很重要如果设成0MPP期望输入Packet本身就是一帧完整数据设成1它可以在Packet内部做NALU解析和帧重组。调试裸H264文件我强烈建议开启split模式省掉很多边界处理的麻烦。MPP_DEC_SET_OUTPUT_FORMAT设置在解码初始化阶段指定输出NV12。MPP内部支持很多格式比如YUV420P、YUV422、RGB但最常用的还是NV12也就是MPP_FMT_YUV420SP。MPP_DEC_SET_EXT_BUF_GROUP不是必须的但建议设置。屏幕录制或相机类应用通常需要把解码输出帧直接拿去显示或做后处理这时候需要自己管理buffer。但对于我们这种保存YUV文件的场景直接用MPP内部默认的buffer管理就够了。所以如果你只想跑通流程上面这行可以省略。不过写上它有一个好处你可以通过mpp_buffer_group_get_internal获取一个buffer group后续如果想要复制帧数据可以统一用这个group分配内存。4.3 按序送包与取帧解码主循环是最核心的部分。大体逻辑是先送一个Packet给解码器然后循环从输出端拿Frame拿到多少帧就保存多少帧。MPP的接口模型是异步的但对用户来说只要按照“发包、收帧”的顺序调用就行。MPP_RET ret MPP_OK; size_t packet_size mpp_packet_get_size(packet); MppPacket pkt NULL; mpp_packet_copy(pkt, packet); mpp_packet_set_pts(pkt, 0); ret mpi-decode_put_packet(mpp_ctx, pkt); if (ret ! MPP_OK) { mpp_log(decode_put_packet failed\n); return ret; } MppFrame frame NULL; while (1) { ret mpi-decode_get_frame(mpp_ctx, frame); if (ret ! MPP_OK) { mpp_log(decode_get_frame failed\n); break; } if (frame NULL) { mpp_log(frame is null, try again\n); break; } // 处理一帧YUV write_yuv_frame(frame); mpp_frame_deinit(frame); }这里有个细节mpp_packet_copy并不是非得用如果你能保证整个文件Packet在解码完成前有效就不需要copy。我有时候直接传原始packet。但要注意decode_put_packet之后MPP不会立即处理完Packet的内存必须在外层一直有效。如果文件是一次性读入的那没问题如果是边读边解还放在临时buf里就可能出问题。decode_get_frame返回可能为NULL这不一定是你解码出错也可能只是码流没解码完需要再给解码器喂数据。所以我的循环设计是单次送入整个文件然后不断decode_get_frame拿到NULL就结束。这种粗暴方式对文件解码完全够用但对实时流或长时间解码不适用因为decode_put_packet可能因为内部buffer不够而返回错误需要边送边取。你可以在自己的实现里用“送包后一直取帧直到取到NULL再送下一个包”的更稳健模型。4.4 保存YUV帧并验证结果从MppFrame里拿YUV数据有两个关键APImpp_frame_get_buf获取MppBuffer再通过mpp_buffer_get_ptr拿到内存地址mpp_frame_get_width和mpp_frame_get_height拿到宽度和高度mpp_frame_get_hor_stride和mpp_frame_get_ver_stride拿到stride对齐后的维度。保存时必须按stride去读取每行Y数据的实际长度是hor_stride而不是width。static void write_yuv_frame(MppFrame frame) { int width mpp_frame_get_width(frame); int height mpp_frame_get_height(frame); int h_stride mpp_frame_get_hor_stride(frame); int v_stride mpp_frame_get_ver_stride(frame); MppBuffer buf mpp_frame_get_buf(frame); uint8_t *ptr mpp_buffer_get_ptr(buf); FILE *yuv fopen(output.yuv, ab); if (!yuv) return; uint8_t *y_plane ptr; for (int i 0; i height; i) { fwrite(y_plane, 1, width, yuv); y_plane h_stride; } // NV12: U和V交错存放 uint8_t *uv_plane ptr h_stride * v_stride; int uv_height height / 2; int uv_width width; // NV12中UV占比偶数行的宽度 for (int i 0; i uv_height; i) { fwrite(uv_plane, 1, uv_width, yuv); uv_plane h_stride; } fclose(yuv); }看到这里你应该能理解为什么刚才强调对齐了。如果直接写ptr width * height作为UV起始地址解码器输出的内存布局是按stride对齐的用原始width计算就可能偏移。如果你保存的YUV文件播放出来有绿边或者颜色错位八成就是stride没对上。验证结果很简单用支持YUV播放的软件打开output.yuv输入分辨率填原始分辨率比如1920x1080像素格式选NV12即可。如果画面正常说明整条链路没问题。到这里最基本的功能就跑通了。4.5 完整代码结构参考我把整个流程串成一个单文件示例方便你直接理解调用顺序。实际工程中建议按模块拆分。#include stdio.h #include stdlib.h #include string.h #include rk_mpi.h #include mpp_packet.h #include mpp_frame.h #include mpp_buffer.h static void save_yuv_frame(MppFrame frame) { /* 见上文实现 */ } int main(int argc, char **argv) { if (argc 3) { printf(usage: %s input.h264 output.yuv\n, argv[0]); return -1; } FILE *in fopen(argv[1], rb); fseek(in, 0, SEEK_END); long len ftell(in); fseek(in, 0, SEEK_SET); uint8_t *data malloc(len); fread(data, 1, len, in); fclose(in); MppCtx ctx; MppApi *mpi; mpp_create(ctx, mpi); mpp_init(ctx, MPP_CTX_DEC, MPP_VIDEO_CodingAVC); uint32_t split 1; MppFrameFormat fmt MPP_FMT_YUV420SP; mpi-control(ctx, MPP_DEC_SET_PARSER_SPLIT_MODE, split); mpi-control(ctx, MPP_DEC_SET_OUTPUT_FORMAT, fmt); MppPacket packet; mpp_packet_init(packet, data, len); mpi-decode_put_packet(ctx, packet); MppFrame frame NULL; int frame_count 0; while (mpi-decode_get_frame(ctx, frame) MPP_OK frame) { frame_count; save_yuv_frame(frame); mpp_frame_deinit(frame); } printf(decoded frames: %d\n, frame_count); mpp_packet_deinit(packet); free(data); mpp_destroy(ctx); return 0; }上面这个示例有一个问题decode_put_packet和decode_get_frame的循环不够严谨对某些文件可能只能解出前几帧。建议在decode_get_frame返回NULL时再尝试多次或者使用发送和接收交替模型。为了稳妥我后面会给出一个更健壮的循环思路这也是实战中真正能稳定跑的写法。5. 常见问题与排查技巧实录5.1 输出花屏或绿屏这是新手最容易遇到的问题。花屏往往不是MPP解错码而是你读数据时没有按stride对齐。检查一下保存YUV时是否每一行都跳过了h_stride - width的间隔。还有UV平面的起始地址可能是ptr h_stride * v_stride而不是ptr width * height。如果这两个地方都对了画面一般就正常了。还有一种情况是分辨率发生变化。H264码流里可能出现中途切换分辨率的场景比如流媒体码流自适应。此时MPP会动态调整输出帧尺寸你的保存逻辑必须每次都从mpp_frame_get_width取最新值不能写死。有的老代码用全局变量存宽高换分辨率后就会保存出错。这也是我建议每次处理帧时都重新取宽高的原因。5.2 解码只出前几帧或者直接卡死我最早调试时也遇到过解码器返回前几帧后就不再出帧了。问题出在Packet发送和Frame接收的平衡上。MPP内部buffer有限如果你一口气把大文件作为一个Packet塞进去解码器可能来不及处理后续decode_put_packet就会返回错误。更稳的做法是循环发送和接收每次发一个Packet然后不断取帧取到NULL再发下一个Packet。对于整个文件作为一个大Packet的场景你可能需要在decode_get_frame返回NULL后延时等待一会儿再继续取。更推荐的方式是把文件按NALU或者固定长度例如4096字节切成多个小Packet分批次发送。这样解码器不会积压太多数据内存占用也更平滑。我提供一个比较健壮的解码循环模板size_t remaining len; size_t offset 0; uint32_t packet_size_limit 65536; while (remaining 0) { size_t send_len remaining packet_size_limit ? packet_size_limit : remaining; MppPacket pkt; mpp_packet_init(pkt, data offset, send_len); mpi-decode_put_packet(ctx, pkt); offset send_len; remaining - send_len; MppFrame frame NULL; while (1) { ret mpi-decode_get_frame(ctx, frame); if (ret ! MPP_OK) break; if (frame NULL) break; save_yuv_frame(frame); mpp_frame_deinit(frame); } mpp_packet_deinit(pkt); }这种方式把一个大文件拆成了64K一个的小包边送边取基本不会卡死。每个Packet对应多少字节的数据其实没有严格要求MPP会在内部拼帧。只要不把一帧数据拆成半个NALU一般都能正确解码。但是为了安全你可以按NALU边界切分或者继续用split模式让MPP自己处理。5.3 内存释放与资源泄漏MPP的内存管理比较绕尤其是MppFrame和MppBuffer。decode_get_frame拿到的一帧数据内部buffer的生命周期由MPP管理。当你调用mpp_frame_deinit(frame)时框架会释放这个frame对象但底层buffer不一定会立即回收而是回到buffer group复用。所以你如果保存了YUV数据必须在deinit之前拷贝完或者直接保存到文件。不要试图把MppBuffer的指针保存到其他地方等下次循环后还用那是不安全的。还有一个细节是mpp_packet_init和mpp_packet_deinit的配对。如果你调用mpp_packet_copy拷贝了一个packet记得deinit原packet和拷贝后的packet。如果只deinit一个另一个就会泄漏。文件不大还好长时间运行的内存泄漏会让你崩溃。我通常在函数内部临时创建packet用完立即deinit不外传。5.4 在Linux Qt5.15环境中显示H264视频的经验很多朋友做完YUV保存之后下一步就是想显示到界面上。我在Qt5.15下也走过这条路。MPP解码输出NV12Qt的QImage原生不认NV12通常要转成RGB再显示。转换可以用软件做也可以在Rockchip平台用RGA硬件加速。如果是软转最常见的方式是libyuv性能在ARM平台上也不错。如果你只是做预览可以把NV12转成I420再交给QVideoFrame处理但Qt5.15里的多媒体框架对裸YUV支持也不算友好。我自己的做法是解码线程得到NV12帧后用libyuv的NV12ToI420转成I420然后通过QVideoFrame封装成QAbstractVideoSurface的帧推给QVideoWidget显示。这样能获得原生视频控件的缩放和渲染能力。不过这个流程涉及Qt线程和MPP线程的同步需要用信号槽把帧数据从解码线程发到UI线程不能直接在解码线程里操作UI对象。如果你不想引入复杂的Qt多媒体框架也可以直接用QWidget的paintEvent里把YUV转RGB后绘制。转RGB可以用下面简单粗暴的方式// YUV转RGB的简化版本非最佳性能但能跑 for (int y 0; y height; y) { for (int x 0; x width; x) { int Y yuv[y * stride x]; int V uvy[y / 2 * stride x / 2 * 2]; int U uvy[y / 2 * stride x / 2 * 2 1]; int R (int)(Y 1.402 * (V - 128)); int G (int)(Y - 0.344 * (U - 128) - 0.714 * (V - 128)); int B (int)(Y 1.772 * (U - 128)); // clamp to 0-255 } }这段代码只适合理解原理实际项目务必用libyuv或RGA否则性能会很差。在Qt5.15的工程里加libyuv非常简单直接加源码编译或者用系统包管理安装都行。整体方案就是“解码线程送YUVUI线程转RGB”只要保证线程安全显示基本稳定。这也是我强烈建议先把文件到YUV流程跑通的原因因为不管后面接什么上层框架底层VPP和MPP的接口都不会变。5.5 可能遇到的其他坑MPP库版本不同API也有细微差异。我用的这份代码在RK3588和较新的MPP版本上没问题但如果你SDK里的MPP比较老MPP_DEC_SET_PARSER_SPLIT_MODE这个控制接口可能不存在需要换成MPP_DEC_SET_ENABLE_DEINTERLACE之类的接口来匹配旧版本。遇到编译不过的时候多翻翻头文件看看rk_mpi.h或mpp_rc.h里的枚举定义别死磕老代码。另外裸H264文件有时会出现码流不完整的情况比如只有P帧没有I帧。此时解码器无法解出第一帧输出端会一直返回NULL。建议用专门的码流分析工具看一眼或者先用ffmpeg生成一个标准的测试H264文件再跑MPP。验证环境正常后再去调试你自己的码流来源。注意如果你使用的是RTSP或网络流不要直接把网络收包长度当成NALU长度交给MPP。RTP打包H264时一个NALU会被拆成多个RTP包你需要先做RTP解包组帧再交给MPP。否则会出现大量解码报错。6. 性能优化与扩展思路跑通基本流程后很多朋友会关心性能。MPP硬解码本身基本不占用CPU瓶颈往往在读取文件、保存YUV、内存拷贝和磁盘IO上。如果你用于实时处理建议使用内存映射或异步IO不要让fwrite成为瓶颈。保存YUV到磁盘的操作非常慢通常只能用于调试实际应用里应该直接把YUV帧送入后续处理单元。MPP也支持零拷贝。解码输出帧的MppBuffer可能来自ION或DMA-BUF你可以把这个buffer直接送进RGA做缩放或格式转换再送往显示或编码器整个过程不需要把数据搬到CPU。这才是Rockchip平台的正确用法。我在做多路解码时就是让MPP输出帧直接走RGA避免了大内存拷贝内存占用和延迟都下降明显。如果你后续需要把解码后的YUV再编码成H264MPP也可以复用同一套上下文只要初始化时改为MPP_CTX_ENC即可。注意编码器输入格式要匹配解码输出格式通常是NV12。来回转一圈就实现了转码功能。这样你就不需要再依赖ffmpeg软转了。另外MPP的解码线程不是线程安全的。尽量不要在多个线程并发调用同一个MppCtx的decode_put_packet。如果你要解码多路视频建议一个线程对应一路解码上下文。多线程处理时可以用一个单独的Packet池来复用内存减少malloc/free的频率这个优化在长时间运行时效果非常明显。我还想再提醒一下pts的处理。文件解码场景里pts可以设成0但如果你要做音视频同步pts必须从码流里解析出来经过packet的mpp_packet_set_pts传给解码器。解码后的frame可以通过mpp_frame_get_pts取回对应的pts。很多播放器画面卡顿、音画不同步问题往往就出在pts没传递。这块我打算下次单独写一篇本次先点到为止。7. 我的最后建议如果让我总结这次实战中最值得记住的经验那就是先不看MPP的复杂特性先把“Packet进、Frame出”这条主流程跑通再逐步添加各种控制。你不需要一开始就搞懂所有调优参数只要能把文件变成YUV就已经掌握了MPP解码的骨架。调试时建议用ffmpeg生成一段标准的1080p测试视频保证源文件没有问题。比如ffmpeg -f lavfi -i testsrcduration10:size1920x1080:rate30 -c:v libx264 -pix_fmt yuv420p test.h264这样生成的H264文件带SPS/PPS结构标准适合验证代码逻辑。等你确认解码流程没问题了再去处理各种异常码流。我在实际项目中踩过最大的坑就是stride对齐栽了整整一下午。后来把解码器输出的hor_stride打印出来才发现和视频宽不一样。从那以后我只要看到YUV文件花屏第一反应就是去查stride而不是怀疑解码器坏了。另外记得每次拿到frame后处理完一定要deinit否则长时间运行内存涨得飞快。最后补充一个小技巧在调试阶段可以在每次decode_get_frame后打印一次width、height、hor_stride、ver_stride和frame格式。这些信息能帮你快速定位很多问题。等你跑通了再删掉这些打印也不迟。Rockchip MPP的坑虽然不少但一旦摸清它的脾气这套硬解方案在性能和稳定性上都很能打。希望这篇实战记录能让你少走一些弯路。