衡山派MPP多媒体框架设计说明:解码器、编码器与图形加速模块架构与接口详解 📅 发布时间:2026/7/4 17:50:59 👁️ 浏览次数: 衡山派MPP多媒体框架设计说明解码器、编码器与图形加速模块架构与接口详解大家好我是老张一个在嵌入式多媒体领域摸爬滚打多年的工程师。最近在衡山派平台上做音视频项目深入研究了它的MPPMedia Process Platform多媒体框架发现这个框架设计得挺有意思特别是它的解码器、编码器和图形加速模块。今天我就结合自己的使用经验给大家详细拆解一下这个框架的设计思路和接口用法希望能帮到正在使用或打算使用衡山派MPP的开发者朋友们。1. MPP框架整体架构三层设计清晰明了咱们先来看看MPP的整体架构。打开源码目录结构是这样的aic-mpp/ ├── base/ // 公共模块内存分配、链表等基础功能 ├── ge/ // 2D图形加速模块 ├── ve/ // 编解码器模块 │ ├── include/ // ve模块头文件 │ ├── common/ // 编解码器公共组件 │ ├── decoder/ // 解码器 │ │ ├── h264/ // H.264解码模块 │ │ ├── jpeg/ // JPEG解码模块 │ │ └── png/ // PNG解码模块 ├── vin/ // 视频输入模块 ├── include/ // MPP对外头文件 └── mpp_test/ // MPP测试用例从软件框图来看MPP采用了经典的三层架构应用层基于Luban-Lite系统提供了MPP播放器、MPP测试程序、LVGL Demo等应用你也可以基于此开发自己的App。MPP中间件层这是核心分为四大功能模块MPP Decoder支持H.264、JPEG、PNG等格式的解码MPP Encoder目前支持JPEG编码MPP GE2D图形加速功能MPP VIN视频输入采集功能驱动层MPP底层依赖的硬件驱动包括VE视频编解码引擎、GE图形引擎、DVP数字视频端口、Camera驱动等。这种分层设计的好处是各司其职应用层不用关心底层硬件细节中间件层负责业务逻辑驱动层直接操作硬件分工明确维护起来也方便。2. MPP解码器Decoder数据流转的艺术解码器是MPP中使用最频繁的模块它的设计核心是数据管理。MPP Decoder由三个主要模块组成解码模块负责实际的解码工作支持H.264、JPEG、PNG等格式。Packet Manager管理输入的码流数据。Frame Manager管理解码后的图像数据。2.1 Packet管理机制码流数据的高效流转Packet Manager负责管理输入的码流数据。初始化时它会申请一块物理连续的内存大小可以配置专门用来存放视频或图片的码流数据。这里有个关键概念packet。一个packet代表一笔码流数据可以是一整帧也可以不是比如H.264的NALU单元。每个packet都对应物理内存中的一块数据记录了这块内存的起始地址、结束地址、偏移量、虚拟地址、数据长度等信息。Packet通过两个链表来管理empty list存放空闲的packetready list存放已经填充了码流数据、等待解码的packet数据流转过程很简单送数据时从empty list拿一个空闲packet → 填充码流数据 → 放入ready list解码时解码器从ready list取一个packet → 解码 → 用完后放回empty list这样就形成了一个循环避免了频繁的内存申请和释放。2.2 Frame管理机制图像缓冲区的状态管理Frame Manager管理解码后的图像缓冲区。它内部也有两个链表empty list存放可以给解码器输出使用的空闲图像bufferrender list存放已经解码完成但还没显示的图像buffer正在显示的图像buffer和用作参考帧的buffer可能不在这两个链表中。每个图像buffer有4种状态IDLE空闲状态Decoding正在被解码器使用输出或作为参考帧wait_render在render list中等待显示Rendering正在被显示占用状态转移过程我画个流程图大家就明白了初始化 → 所有buffer在empty list状态为IDLE ↓ 解码器从empty list取buffer → 状态变为Decoding ↓ 解码完成 ├─ 如果当前帧还未显示 → 加入render list状态变为wait_render └─ 如果不再用作参考帧且已显示完成 → 加入empty list状态变回IDLE ↓ 显示模块从render list取帧 → 状态变为Rendering ↓ 显示完成 ├─ 如果不用于参考 → 加入empty list状态变回IDLE └─ 如果用于参考 → 状态变为Decoding等待解码器归还这里有个重要区别JPEG/PNG没有参考帧概念解码后直接送render listH.264有参考帧解码后的帧可能既被显示占用也被用作参考帧而且由于双向参考帧的存在视频帧需要重排序后才能显示。H.264解码库内部有个delay list专门做这个重排序。2.3 内存使用H.264解码的内存规划H.264解码需要申请多块物理连续内存具体如下内存占用模块计算方式说明输入码流大小由应用层配置存放待解码的H.264码流输出帧width × height × 3/2 × frame_numframe_num至少需要参考帧个数1个显示占用个数可通过extra_frame_num配置帧内预测帧格式width × 2MBAFFwidth × 4需要上一行数据进行预测宏块信息固定12K存放宏块相关信息dblk模块帧格式width × 8MBAFFwidth × 16存放上一个宏块行最后4行数据co-located信息固定68K运动向量相关信息每一帧co-located数据缓存(width/16) × (height/16) × 32 × frame_num每帧的co-located数据注意co-located有两个bufferI帧和P帧解码时会往buffer里写数据B帧解码时从buffer读数据。即使码流中没有B帧这两块内存也需要申请。2.4 解码器调用流程三线程模型为了保证解码效率官方建议创建3个线程// 1. 送数据线程send data thread // 负责把码流数据送到packet管理模块 mpp_decoder_get_packet(); // 获取空packet // 填充码流数据... mpp_decoder_put_packet(); // 归还填充好的packet // 2. 解码线程decode thread // 控制解码过程 mpp_decoder_decode(); // 解码一笔数据 // 3. 显示线程render thread // 从frame管理模块获取视频帧并显示 mpp_decoder_get_frame(); // 获取解码后的帧 // 显示该帧... mpp_decoder_put_frame(); // 归还帧这三个线程各司其职通过packet和frame两个管理器解耦实现了流水线操作提高了整体效率。2.5 关键数据结构解析struct decode_config解码器配置struct decode_config { enum mpp_pixel_format pix_fmt; // 输出像素格式 int bitstream_buffer_size; // packet manager中码流缓存的总长度 int packet_count; // packet manager中packet的最大个数 int extra_frame_num; // frame manager中额外分配的帧个数 };pix_fmt解码输出的颜色格式比如ARGB8888、NV12等bitstream_buffer_size存放输入码流的缓存总大小建议根据视频分辨率估算packet_countpacket的个数一般1-3个就够了extra_frame_num额外分配的帧个数主要用于缓存显示帧以保证显示平滑struct mpp_packet码流数据结构struct mpp_packet { void *data; // 码流数据存放的起始地址 int size; // 该笔码流数据长度 long long pts; // 时间戳 unsigned int flag; // 标记位目前仅用于标识是否为最后一笔码流 };struct mpp_frame视频帧数据结构struct mpp_frame { struct mpp_buf buf; // buffer信息 long long pts; // 时间戳 unsigned int id; // 唯一标识 unsigned int flags; // 标志位 };其中mpp_buf结构体包含了buffer的详细信息struct mpp_buf { enum mpp_buf_type buf_type; // buffer类型MPP_DMA_BUF_FD或MPP_PHY_ADDR union { int fd[3]; // 三个分量的fdDMA buffer方式 unsigned int phy_addr[3]; // 三个分量的物理地址 }; unsigned int stride[3]; // 三个分量的stride步长 struct mpp_size size; // buffer的宽高 unsigned int crop_en; // 是否需要裁剪 struct mpp_rect crop; // 裁剪信息 enum mpp_pixel_format format; // 颜色格式 };2.6 解码器接口详解MPP Decoder提供了一套完整的API下面我挑几个核心的讲讲创建和初始化// 创建解码器对象 struct mpp_decoder* dec mpp_decoder_create(MPP_CODEC_VIDEO_DECODER_H264); // 配置解码器 struct decode_config config; config.pix_fmt MPP_FMT_ARGB_8888; config.bitstream_buffer_size (file_len 1023) (~1023); // 按1K对齐 config.packet_count 1; config.extra_frame_num 0; // 初始化解码器 mpp_decoder_init(dec, config);数据流转接口// 获取一个空的packet用于填充码流数据 struct mpp_packet packet; memset(packet, 0, sizeof(struct mpp_packet)); mpp_decoder_get_packet(dec, packet, file_len); // 填充数据后归还 fread(packet.data, 1, file_len, fp); packet.size file_len; packet.flag PACKET_FLAG_EOS; // 标识这是最后一笔数据 mpp_decoder_put_packet(dec, packet); // 解码 mpp_decoder_decode(dec); // 获取解码后的帧 struct mpp_frame frame; memset(frame, 0, sizeof(struct mpp_frame)); mpp_decoder_get_frame(dec, frame); // 显示后归还 mpp_decoder_put_frame(dec, frame);错误码说明MPP Decoder定义了一套错误码调试时很有用enum mpp_dec_errno { DEC_ERR_NOT_SUPPORT 0x90000001, // 不支持的码流格式 DEC_ERR_NO_EMPTY_PACKET 0x90000002, // empty list中没有空闲packet DEC_ERR_NO_READY_PACKET 0x90000003, // ready list中没有待解码packet DEC_ERR_NO_EMPTY_FRAME 0x90000004, // 没有空闲frame DEC_ERR_NO_RENDER_FRAME 0x90000005, // 没有待显示的frame DEC_ERR_NULL_PTR 0x90000006, // 空指针错误 DEC_ERR_FM_NOT_CREATE 0x90000007, // frame manager未创建 };遇到这些错误时通常需要调整线程同步或buffer数量。3. MPP编码器EncoderJPEG编码实战MPP Encoder目前只支持JPEG编码接口相对简单int mpp_encode_jpeg(struct mpp_frame* frame, // 待编码的原始YUV数据 int quality, // 编码质量1~100100最好 int dma_buf_fd, // 输出JPEG图片存放的dma-buf fd int buf_len, // 输出buffer的长度 int* len); // 输出JPEG图片的实际大小小技巧输出JPEG的buffer需要预先申请较大的内存因为编码前不知道实际大小。一般按width × height × 4/5 × quality/100估算quality是质量参数。使用示例// 1. 获取dma-buf device句柄 int dma_fd dmabuf_device_open(); // 2. 设置输入YUV数据 struct mpp_frame frame; // ... 填充frame数据 ... // 3. 申请编码输出buffer int len 0; int buf_len width * height * 4/5 * quality / 100; // 估算大小 int jpeg_data_fd dmabuf_alloc(dma_fd, buf_len); // 4. 编码JPEG图片 mpp_encode_jpeg(frame, quality, jpeg_data_fd, buf_len, len); // 5. 保存编码后的JPEG unsigned char* jpeg_vir_addr dmabuf_mmap(jpeg_data_fd, buf_len); FILE* fp_save fopen(/save.jpg, wb); fwrite(jpeg_vir_addr, 1, len, fp_save); fclose(fp_save); // 6. 释放资源 dmabuf_munmap(jpeg_vir_addr, buf_len); dmabuf_free(jpeg_data_fd); dmabuf_device_close(dma_fd);4. MPP图形加速GE2D图形处理的利器GE模块提供了硬件加速的2D图形处理功能支持矩形填充、位块搬移、旋转等操作。驱动支持两种模式普通模式非命令队列和命令队列模式。MPP接口对这两种模式做了封装保持了统一的API建议大家都用MPP中间层API。4.1 基本操作流程// 1. 打开GE设备 struct mpp_ge *ge mpp_ge_open(); // 2. 获取GE模式 enum ge_mode mode mpp_ge_get_mode(ge); // 3. 执行图形操作如矩形填充 struct ge_fillrect fillrect; // ... 配置fillrect参数 ... mpp_ge_fillrect(ge, fillrect); // 4. 发送命令命令队列模式需要 mpp_ge_emit(ge); // 5. 等待任务完成命令队列模式需要 mpp_ge_sync(ge); // 6. 关闭GE设备 mpp_ge_close(ge);4.2 核心功能接口矩形填充mpp_ge_fillrectint mpp_ge_fillrect(struct mpp_ge *ge, struct ge_fillrect *fillrect);填充颜色格式只能是ARGB8888不支持缩放、旋转和镜像但支持alpha blending和color key。位块搬移mpp_ge_bitbltint mpp_ge_bitblt(struct mpp_ge *ge, struct ge_bitblt *blt);支持两种情况原图矩形区域搬移到目标图矩形区域不进行缩放原图矩形区域搬移到目标图矩形区域同时进行放大或缩小支持alpha blending、color key、90/180/270度旋转和镜像。任意角度旋转mpp_ge_rotateint mpp_ge_rotate(struct mpp_ge *ge, struct ge_rotation *rot);支持任意角度旋转可以指定原图和目标图的旋转中心支持alpha blending。原图和目标图都只支持RGB格式。旋转角度传给驱动的是sin和cos值为2.12定点数小数部分12位。计算方式#include math.h #define PI 3.14159265 #define SIN(x) (sin(x * PI / 180.0)) #define COS(x) (cos(x * PI / 180.0)) double degree 30.0; // 旋转角度0~360度 int angle_sin (int)(SIN(degree) * 4096); // 乘以4096转为定点数 int angle_cos (int)(COS(degree) * 4096);实际项目中可以预先生成常用角度的sin/cos值表通过查表减少计算量。4.3 命令队列模式说明在命令队列模式下任务会先缓存在用户的cmd buffer中当用户缓存buffer足够时仅把命令缓存在用户空间当用户缓存空间不够时会通过write接口把缓存的命令全部写入内核的ring buffermpp_ge_emit()用于发送命令mpp_ge_sync()用于等待所有任务完成普通模式下这两个函数是空操作不产生任何作用。5. MPP视频输入VIN摄像头数据采集VIN模块主要做两件事对上封装DVP、Camera驱动的ioctl接口尽量保持和Linux MPP VIN一致管理视频Buffer队列实现DVP应用和驱动之间的Buffer轮转5.1 Buffer队列管理VIN的队列管理参考了Linux的V4L2框架通过struct vb_queue中的两个队列来管理queued_list空闲Buffer队列等待Sensor数据到来done_list已填充视频数据的Buffer队列等待用户处理Buffer流转过程DVP驱动从queued_list取一个空闲BufferSensor数据填充到这个BufferBuffer放入done_list等待用户处理用户处理完后通过QBUF命令将Buffer还给queued_list重要运行期间DVP需要一个BufferAPP需要一个BufferQBUF还需要一个Buffer在等待否则DVP的done中断来了发现没有等待的QBUF会丢帧所以至少需要3个Buffer。5.2 内存计算对于每一帧图像DVP输出有两个planeY和UV。针对不同的输出格式PlaneYUV422_COMBINED_NV16YUV420_COMBINED_NV12Plane YWidth × heightWidth × heightPlane UVWidth × heightWidth × height / 2根据前面的分析我们需要分配的内存空间至少是3个Buffer × 每个Buffer 2个Plane。以NV12格式、640×480分辨率为例Plane Y大小640 × 480 307200字节Plane UV大小640 × 480 / 2 153600字节每个Buffer大小307200 153600 460800字节3个Buffer总大小460800 × 3 1382400字节 ≈ 1.32MB5.3 核心接口// 初始化VIN模块 int mpp_vin_init(char *camera); // camera是摄像头设备名称 // 释放VIN模块资源 int mpp_vin_deinit(void); // DVP设备的ioctl接口 int mpp_dvp_ioctl(int cmd, void *arg);5.4 使用示例MPP提供了一个完整的test_dvp示例实现了Sensor → DVP → DE的数据通路。核心流程// 1. 初始化VIN mpp_vin_init(CAMERA_DEV_NAME); // 2. 获取Sensor格式 sensor_get_fmt(); // 3. 设置DVP子设备格式 dvp_subdev_set_fmt(); // 4. 配置DVP输出格式 dvp_cfg(width, height, format); // 5. 申请Buffer dvp_request_buf(vbuf); // 6. 将所有Buffer加入队列 for (i 0; i VID_BUF_NUM; i) { dvp_queue_buf(i); } // 7. 启动流 dvp_start(); // 8. 循环处理帧 while (running) { // 从队列取出一个已填充的Buffer dvp_dequeue_buf(index); // 处理这一帧显示、旋转等 // ... // 将Buffer重新加入队列 dvp_queue_buf(index); } // 9. 停止流 dvp_stop(); // 10. 释放资源 dvp_release_buf(num_buffers); mpp_vin_deinit();这个示例还支持通过GE旋转后再显示具体实现可以参考test_dvp.c源码。6. 实际使用中的几个坑点最后分享几个我在使用MPP时踩过的坑H.264解码内存不足H.264解码需要多块物理连续内存如果申请失败会导致解码异常。特别是co-located buffer即使码流中没有B帧也要申请。Buffer数量配置VIN模块至少需要3个Buffer如果只配置2个在高帧率下容易丢帧。实际项目中我一般配置4-5个留点余量。命令队列模式同步使用GE的命令队列模式时一定要记得调用mpp_ge_sync()等待任务完成否则可能出现资源竞争或内存泄漏。JPEG编码buffer估算mpp_encode_jpeg()的输出buffer需要预先申请如果申请太小会导致编码失败。我一般按width × height × 3/4来估算对于高质量图片再适当加大。线程同步解码器的三线程模型虽然高效但要注意线程间的同步。特别是mpp_decoder_get_frame()和mpp_decoder_put_frame()的调用时机过早归还frame可能导致显示异常。MPP框架设计得比较完善掌握了这些核心模块的设计思路和接口用法在衡山派平台上开发多媒体应用就会顺畅很多。建议大家多看看mpp_test目录下的示例代码里面有很多实用的参考实现。
百度网盘提取码智能检索工具:从技术原理到实战应用 百度网盘提取码智能检索工具:从技术原理到实战应用 【免费下载链接】baidupankey 项目地址: https://gitcode.com/gh_mirrors/ba/baidupankey 问题引入:当分享链接遇见提取码壁垒 你是否经历过这样的场景:急需下载的学习资料、重要的… 2026/7/2 22:33:02
MusePublic圣光艺苑一文详解:Noto Serif SC字体嵌入与衬线美学实践 MusePublic圣光艺苑一文详解:Noto Serif SC字体嵌入与衬线美学实践 1. 引言:当古典排版遇见AI艺术创作 在数字艺术创作领域,视觉体验的每一个细节都至关重要。MusePublic圣光艺苑作为一个专为AI大模型打造的沉浸式艺术创作空间,… 2026/5/17 11:40:57
DeepSeek-R1-Distill-Qwen-1.5B频繁重启?资源限制配置优化实战 DeepSeek-R1-Distill-Qwen-1.5B频繁重启?资源限制配置优化实战 内容安全声明:本文仅讨论技术实现方案,所有内容均基于公开技术文档和最佳实践,不涉及任何敏感信息或违规操作。 1. 问题背景:为什么模型服务会频繁重启&a… 2026/5/17 11:40:56
从零构建食物分类系统:CNN模型设计与实战优化 1. 项目概述食物分类是计算机视觉领域一个经典且实用的应用场景。不同于通用物体识别,食物图像往往具有更高的类内差异和更模糊的类间边界——同一道菜可能呈现完全不同的摆盘形态,而不同菜品可能使用相似的食材。这个项目将带您从零构建一个完整的食物分… 2026/7/4 17:49:09
ICM-42605与PIC18F26K22的6轴IMU系统设计与姿态解算 1. 项目背景与核心组件解析 在工业自动化、无人机导航和虚拟现实等领域,精确追踪物体在三维空间中的运动状态一直是个关键挑战。ICM-42605这款6轴惯性测量单元(IMU)与PIC18F26K22微控制器的组合,为解决这个问题提供了高性价比的硬件方案。 ICM-42605是T… 2026/7/4 17:49:09
CMS漏洞扫描工具实战指南:从资产识别到深度验证的10款工具评测 1. 项目概述:为什么你需要一份CMS漏洞扫描工具实战指南?如果你是一名网站管理员、安全工程师,或者正在负责维护一个或多个基于WordPress、Joomla、Drupal、ThinkPHP等常见内容管理系统(CMS)的网站,那么“安… 2026/7/4 17:45:07
大电流FOC驱动设计:从硬件选型到算法优化 1. 项目背景与核心挑战在工业自动化、机器人关节驱动和高端家电领域,无刷直流电机(BLDC)因其高效率、长寿命和低噪音特性已成为主流选择。传统六步换向控制虽然实现简单,但在低速平稳性和能效方面存在明显短板。我们这次要实现的磁… 2026/7/4 17:43:07
学术写作效率突破!2026智能AI论文平台深度解析 2026 年 AI 论文写作工具已进入全流程闭环 学术合规时代,千笔 AI(综合评分 99 分)中文学术场景标杆;Grammarly Academic与Elicit为英文论文写作首选;按需求匹配度 - 数据可信度 - 成本承受力三维模型选型,… 2026/7/4 17:43:07
DeepSeek、ChatGPT、豆包三模型实战选型指南 1. 这不是“选哪个更好”的问题,而是“你手里的活儿需要什么工具”最近在好几个技术群、产品讨论组和内容创作社群里,反复看到这个问题:“deepseek,chatGPT,豆包,这三个你们觉得哪个更强或者更好用… 2026/7/4 17:39:05
STM32F745VG与MC6470 IMU的高性能姿态控制系统设计 1. MC6470与STM32F745VG的黄金组合解析在工业自动化和机器人控制领域,传感器与微控制器的协同工作能力直接决定了系统的响应速度和定位精度。MC6470作为一款6自由度惯性测量单元(6DOF IMU),与STM32F745VG这款基于ARM Cortex-M7内核的高性能微控制器组合&… 2026/7/4 0:00:28
Playwright自动化测试实战:从零搭建现代Web测试框架 1. 项目概述:为什么是 Playwright?如果你正在为现代 Web 应用的自动化测试头疼,尤其是面对那些充斥着动态加载、复杂交互的单页应用(SPA),那么 Playwright 的出现,很可能就是你的解药。我接触过… 2026/7/4 0:00:28
终极指南:如何将JSXBIN二进制文件转换为可读JSX源代码 终极指南:如何将JSXBIN二进制文件转换为可读JSX源代码 【免费下载链接】jsxbin-to-jsx-converter JSXBin to JSX Converter written in C# 项目地址: https://gitcode.com/gh_mirrors/js/jsxbin-to-jsx-converter 你是否曾经面对过Adobe产品的JSXBIN文件感到… 2026/7/4 0:02:28