基于MPP库进行视频编解码

一、MPP 介绍

以下为 RK 官方 MPP 文档的 GitHub 链接,包含了中文/英文的 MPP 开发指南:

https://github.com/rockchip-linux/mpp/tree/develop/doc

MPP(Media Process Platform)是 Rockchip 提供的一套 硬件视频编解码中间件,用于统一调用芯片上 VPU(Video Processing Unit) 硬件资源,实现高性能视频编码和解码。该平台对应用软件屏蔽了芯片相关的复杂底层处理,其目的是为了屏蔽不同芯片的差异,为使用者提供统一的视频媒体处理接口(Media Process Interface,缩写 MPI)。MPP 提供的功能包括:

视频解码 H.265 / H.264 / H.263 / VP9 / VP8 / MPEG-4 / MPEG-2 / MPEG-1 / VC1 / MJPEG / AV1
视频编码 H.265 / H.264 / VP8 / MJPEG
视频处理 视频拷贝,缩放,色彩空间转换,场视频解交织(Deinterlace)

核心功能:

  • 硬件解码(VDEC):支持 H.264、H.265/HEVC、VP8、VP9、AV1 等格式。
  • 硬件编码(VENC):支持 H.264、H.265、JPEG、AV1 等格式。
  • 统一 API 接口:通过 C API 或 V4L2,简化上层应用调用。
  • 高性能多路处理:支持多路视频流并发编解码,降低 CPU 占用,节省功耗。
  • 跨平台:Linux/Android 平台均可使用。
  • 开源支持:源码开源,可根据项目需求修改和移植。

二、MPP 结构

MPP 在源码层面主要分为以下模块:

模块 功能
OSAL 跨平台底层封装:内存、线程、时间、日志等
MPI / Mpp API 用户调用接口:编码/解码任务提交与获取
Base 核心数据结构:MppBuffer、MppFrame、MppPacket、MppTask
Codec 比特流解析与处理逻辑(Parser + WorkFlow)
HAL 硬件抽象层,将 Codec 命令映射到 VPU 寄存器并触发执行

典型数据流:

1
用户应用 → MPI API → MPP Task → Codec Parser → HAL → VPU → 输出帧/比特流

调用流程简化:

1
2
3
4
5
6
7
8
9
10
11
12
13
应用(如 FFmpeg + libva/VAAPI) 
↓
MPI / MPP API 调用
↓
Base + Codec 解析与任务组织
↓
HAL 生成寄存器命令
↓
Kernel vcodec_service 驱动
↓
SoC VPU 硬件执行
↓
返回 decoded frames / encoded packets

2.1 代码目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
----                             top
|
|----- build CMake out-of-source build directory
| |
| |----- cmake cmake script directory
| |
| |----- android android build directory
| |
| |----- linux linux build directory
| |
| |----- vc10-x86_64 visual studio 2010 on x86_64 build directory
| |
| |----- vc12-x86_64 visual studio 2013 on x86_64 build directory
|
|----- doc design documents of mpp
|
|----- inc header file for external usage, including
| platform header and mpi header
|
|----- mpp Media Process Platform : mpi function private
| | implement and mpp infrastructure (vpu_api
| | private layer)
| |
| |----- base base components including MppBuffer, MppFrame,
| | MppPacket, MppTask, MppMeta, etc.
| |
| |----- common video codec protocol syntax interface for both
| | codec parser and hal
| |
| |----- codec all video codec parser, convert stream to
| | | protocol structure
| | |
| | |----- inc header files provided by codec module for
| | | external usage
| | |
| | |----- dec
| | | |
| | | |----- av1 av1 parser
| | | |
| | | |----- avs avs parser
| | | |
| | | |----- avs2 avs2 parser
| | | |
| | | |----- h263 h263 parser
| | | |
| | | |----- h264 h264 parser
| | | |
| | | |----- h265 h265 parser
| | | |
| | | |----- m2v mpeg2 parser
| | | |
| | | |----- mpg4 mpeg4 parser
| | | |
| | | |----- vp8 vp8 parser
| | | |
| | | |----- vp9 vp9 parser
| | | |
| | | |----- jpeg jpeg parser
| | |
| | |----- enc
| | |
| | |----- dummy encoder controllor work flow sample
| | |
| | |----- h264 h264 encoder
| | |
| | |----- h265 h265 encoder
| | |
| | |----- jpeg jpeg encoder
| | |
| | |----- vp8 vp8 encoder
| |
| |----- hal Hardware Abstract Layer (HAL): modules used in mpi
| | |
| | |----- inc header files provided by hal for external usage
| | |
| | |----- common shared HAL code
| | |
| | |----- rkdec rockchip hardware decoder register generation
| | | |
| | | |----- h264d generate register file from H.264 syntax info
| | | |
| | | |----- h265d generate register file from H.265 syntax info
| | | |
| | | |----- vp9d generate register file from vp9 syntax info
| | | |
| | | |----- av1d generate register file from AV1 syntax info
| | | |
| | | |----- avsd generate register file from AVS syntax info
| | | |
| | | |----- avs2d generate register file from AVS2 syntax info
| | |
| | |----- rkenc rockchip hardware encoder register generation
| | | |
| | | |----- h264e generate register file from H.264 syntax info
| | | |
| | | |----- h265e generate register file from H.265 syntax info
| | | |
| | | |----- jpege generate register file from jpeg syntax info
| | |
| | |----- vpu vpu register generation library
| | |
| | |----- av1d generate register file from AV1 syntax info
| | |
| | |----- h263d generate register file from H.263 syntax info
| | |
| | |----- h264e generate register file from H.264 syntax info
| | |
| | |----- jpegd generate register file from jpeg syntax info
| | |
| | |----- jpege generate register file from jpeg syntax info
| | |
| | |----- m2vd generate register file from mpeg2 syntax info
| | |
| | |----- mpg4d generate register file from mpeg4 syntax info
| | |
| | |----- vp8d generate register file from vp8 syntax info
| | |
| | |----- vp8e generate register file from vp8 syntax info
| |
| |----- vproc video process modules
| | |
| | |----- iep image enhancement processor
| | |
| | |----- iep2 image enhancement processor v2
| | |
| | |----- vdpp video post processor
| |
| |----- legacy generate new libvpu to include old vpuapi path
| | and new mpp path
| |
| |----- test mpp internal video protocol unit test and demo
|
|----- test mpp buffer/packet component unit test and
| mpp/mpi/vpu_api demo
|
|----- out final release binary output directory
| |
| |----- bin executable binary file output directory
| |
| |----- inc header file output directory
| |
| |----- lib library file output directory
|
|----- osal Operation System Abstract Layer: abstract layer
| | for different operation system
| |
| |----- allocator supported allocator including Android ion and
| | Linux drm
| |
| |----- android google's android
| |
| |----- inc osal header file for mpp modules
| |
| |----- linux mainline linux kernel
| |
| |----- windows microsoft's windows
| |
| |----- test OASL unit test
|
|----- tools coding style format tools
|
|----- utils small util functions

test/ 下的程序不构建库,只产出可执行文件。它们链接 libmpp.so(或静态库)+ utils/ 这个内部静态库,跑在板子的用户态。

1
2
3
4
5
6
7
8
mpp/             - MPP 核心实现(MPI + base + codec + hal)
osal/ - 跨平台抽象层
inc/ - 对外头文件接口(MPI/公共定义)
test/ - MPP 功能测试与示例代码
doc/ - 设计说明文档
utils/ tools/ - 工具与辅助脚本
kmpp/ - 可能的 kernel 侧接口代码
build/ - CMake 构建脚本配置

2.2 模块结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
+------------------------------------------------------+
| 上层应用/多媒体框架(Linux/Android) |
| OpenMax / gstreamer / libva / FFmpeg (hwaccel) |
+-----------------------------|------------------------+
▼
+---------------------------- MPP ----------------------------+
| |
| +--------------------+ +----------------------------+ |
| | MPI / MPP | | OSAL | |
| | Media Process API | | Operation System Abstract | |
| | (Open/Init/Put/Get)| | Layer: memory/thread/log | |
| +--------------------+ +----------------------------+ |
| | |
| ▼ |
| +------------------+ |
| | base | |
| | core components: | |
| | MppBuffer / | |
| | MppFrame / | |
| | MppPacket / | |
| | MppTask / MppMeta | |
| +------------------+ |
| | |
| ▼ |
| +------------------+ |
| | codec | |
| | parser + encoder/ | |
| | decoder workflows | |
| +------------------+ |
| | |
| ▼ |
| +------------------+ |
| | HAL | |
| | Hardware Abstraction| |
| | - register generator | |
| | for VPU codecs | |
| +------------------+ |
+----------------------------|-------------------------------+
▼
+------------------------ kernel ----------------------+
| |
| vcodec_service / v4l2 hardware drivers |
| (stateless frame hw codec kernel interface) |
+------------------------------------------------------+
  1. MPI / MPP 层
  • MPP 的核心对外接口,提供统一的 媒体处理 API(打开/初始化/送入帧/取出结果)。
  • 管理内部任务队列、帧/包队列,负责上层与内部 codec/HAL 的协作。
  • 是上层应用调用硬件编解码的入口。
  1. OSAL(系统抽象层)
  • 屏蔽不同操作系统差异(内存分配、线程、时间、日志等)。
  • 保证 MPP 在 Linux、Android 等平台可跨平台编译和运行。
  1. Base(核心数据结构层)
  • 定义 MPP 的核心数据类型:
    • MppBuffer:统一缓冲区对象(dma-buf / ion / drm 等)。
    • MppFrame:视频帧结构。
    • MppPacket:流数据包结构。
    • MppTask / MppMeta:任务与元数据。
  • 为 codec 层和 HAL 层提供统一数据承载。
  1. Codec(编/解码工作流 + 解析)
  • 对具体视频格式(H.264/H.265/VP8/VP9/JPEG 等)实现解析器、控制流程、逻辑。
  • 分析流数据、组织 codec 工作任务、与 HAL 交互。
  1. HAL(硬件抽象层)
  • 将 codec 解析结果映射为硬件寄存器设置,生成配合内核驱动的配置。
  • 核心作用是与 VPU(Video Processing Unit)硬件协作:
    • 生成给 vcodec_service 内核驱动的寄存器命令。
    • 支持多种编码/解码格式。
  • HAL 是 MPP 与真实硬件的桥梁。
  1. 内核层(kernel)
  • MPP 通过 HAL 与 SoC 的硬件编/解码内核驱动交互:
    • vcodec_service:Stateless API kernel side。
    • v4l2:V4L2 codec interface(部分平台)。
  • 最终由内核驱动调度 VPU 硬件处理。

2.3 命名规则

  • mpi = MPP Interface(rk_mpi.h 那套对外 API)
  • enc / dec = encoder / decoder
  • mt = multi-thread(输入输出分线程,不是多实例)
  • nt = no-thread / no-timeout(关闭 MPP 内部解码线程,调用者一次 decode() 包揽 put+get)
  • multi = multi-instance(多路并发,多实例)
  • rc = rate control(码率控制)
  • rc2 = “RC 测试 v2″,相对于已废弃的 mpi_rc_test

容易搞混的是 mt vs multi:

  • mt(multi-thread):1 个编/解码上下文,2 个线程(一个喂、一个取)
  • multi:N 个上下文,N 个线程,每个互不干扰,用来测吞吐

2.4 接口结构

下图为 MPI 接口使用的主要数据结构:

  • MppMem 为 C 库 malloc 内存的封装。
  • MppBuffer 为硬件用的 dmabuf 内存的封装。
  • MppPacket 为一维缓存封装,可以从 MppMem 和 MppBuffer 生成,主要用于表示码流数据。
  • MppFrame 为二维帧数据封装,可以从 MppMem 和 MppBuffer 生成,主要用于表示图像数据。
  • 使用 MppPacket 和 MppFrame 就可以简单有效的完成一般的视频编解码工作。

2.4.1 MppBuffer

MppBuffer 主要用于描述供硬件使用的内存块(即缓存),提供了内存块的分配、释放和加减引用等功能,目前支持的分配器有:ion、drm 和 dma_heap。MppBuffer 几个重要的参数成员如下:

成员名称 成员类型 描述说明
ptr void * 表示内存块的起始虚拟地址。
size size_t 表示内存块的大小。
fd int 表示内存块的用户态空间文件句柄。

在解码过程中,解码图像的缓存通常需要在固定的缓存池里进行轮转,为了实现这一点,MPP 在 MppBuffer 基础之上又定义了 MppBufferGroup。MppBuffer 的使用方式有两种:常规使用方式和外部导入方式,由于二者的内存分配方式不同,也称为 internal 模式和 external 模式。

在 MppBuffer 常规使用方式下,MppBufferGroup 由 MPP 内部生成和维护。通过 mpp_buffer_get 和 mpp_buffer_put 对内存块进行申请和释放,如下图所示:

其流程大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
MppBufferGroup group = NULL;

// 获取缓冲池并限制其大小和数量
mpp_buffer_group_get_internal(&group, type);
mpp_buffer_group_limit_config(group, size, count);

// 把缓冲池配置给解码器,让解码器从缓冲池中取缓存解码
mpi->control(dec, MPP_DEC_SET_EXT_BUF_GROUP, group);

// 开始解码过程
while (!end_of_deconding) {
{// 括号内为MPP解码器行为,解码器使用缓存数据
MppBuffer buffer_in_mpp_decoder;
// 解码器内部从缓存池取缓存解码
mpp_buffer_get(group, &bufer_in_mpp_decoder);
// 把图像解码到buffer内
...
}
mpi->decoder_get_frame(&frame);
// 输出MppBuffer给外部用户
// 对图像数据做处理
MppBuffer buffer_of_user = mpp_frame_get_buffer(frame);
...
// 用户释放缓存引用和图像引用
mpp_buffer_put(buffer_of_user);
mpp_frame_deinit(frame);
}

// 释放缓冲池
mpp_buffer_group_put(group);

这种方式可以实现解码器在解码过程中零拷贝输出(解码器的输出帧与解码器内部使用的参考帧用是同一帧),但不容易实现零拷贝显示(解码器的输出帧不一定在显示端可以直接显示),同时要求用户知道需要给解码器开多大的空间进行解码。

另一种使用方式是把 MppBufferGroup 完全做为一个缓存的管理器,用于管理外部导入的缓存。其使用方式如下图:

其流程大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
MppBufferGroup group = NULL;
MppBufferlnfo info[16];

// 获取缓存池
mpp_buffer_group_get_external(&group, type);

// 向缓存池中导入外部缓存
mpp_buffer_commit(group, &info[o]);
mpp_buffer_commit(group, &info[1]);
...

//把缓存池配置给解码器,让解码器从缓存池中取缓存解码
mpi->control(dec, MPP_DEC_SET_EXT_BUF_GROUP, group);

//开始解码过程
while (!end_of_decoding) {
{// 括号内为MPP解码器行为,解码器使用缓存数据
MppBuffer buffer_in_mpp_decoder;// 解码器内部从缓存池取缓存解码
mpp_buffer_get(group, &bufer_in_mpp_decoder);
// 把图像解码到buffer内
...
}
mpi->decode_get_frame(&frame);
// 输出MppBuffer给外部用户
MppBuffer buffer_of_user = mpp_frame_get_buffer(frame);
// 对图像数据做处理
...
// 用户释放缓存引用和图像引用
mpp_buffer_put(buffer_of_user);
mpp_frame_deinit(frame);
}

// 释放缓存池
mpp_buffer_group_put(group);

这种方式可以使得解码器使用外部的缓存,可以对接 OpenMax/gstreamer 这样的中间件,也方便对接用户己有的上层代码,便于实现零拷贝显示。

2.4.2 MppPacket

MppPacket 主要用于描述一维码流的相关信息,特别是有效数据的位置与长度。MppPacket 几个重要的参数成员如下:

成员名称 成员类型 描述说明
data void * 表示缓存空间的起始地址。
size size_t 表示缓存空间的大小。
pos void * 表示缓存空间内有效数据的起始地址。
length size_t 表示缓存空间内有效数据的长度。如果在 decode_put_packet 调用之后 length 变为 0,说明此包码流己消耗完成。

其关系如下图所示:

MppPacket的其他配置参数成员如下:

成员名称 成员类型 描述说明
pts RK_U64 表示显示时间戳(Present Time Stamp)。
dts RK_U64 表示解码时间戳(Decoding Time Stamp)。
eos RK_U32 表示码流结束标志(End Of Stream)。
buffer MppBuffer 表示MppPacket对应的MppBuffer。
flag RK_U32 表示MPP内部使用的标志位,包含如下标志:
#define MPP_PACKET_FLAG_EOS (0x00000001)
#define MPP_PACKET_FLAG_EXTRA_DATA (0x00000002)
#define MPP_PACKET_FLAG_INTERNAL (0x00000004)
#define MPP_PACKET_FLAG_INTRA (0x00000008)

MppPacket 作为描述一维内存的结构体,在使用时需要使用 malloc 出来的内存或者使用 MppBuffer 的内存进行初始化。在释放 MppPacket 时有几种情况:

如果是外部 malloc 地址配置到 MppPacket,不会做 free 释放处理,如下示例;

1
2
3
4
5
6
7
void *data = malloc(size);
MppPacket pkt = NULL;

mpp_packet_init(&pkt, data, size);
mpp_packet_deinit(&pkt); // 不会释放data

free(data);

如果是拷贝产生的 MppPacket,会做 free 释放内存,如下示例;

1
2
3
4
5
6
7
8
9
10
11
void *data = malloc(size);
MppPacket pkt = NULL;
MppPacket pkt_copy = NULL;

mpp_packet_init(&pkt, data, size);
mpp_packet_copy_init(&pkt_copy, pkt);

mpp_packet_deinit(&pkt); // 不会释放data
mpp_packet_deinit(&pkt_copy); // 会自动释放分配的内存

free(data);

如果是 MppBuffer 产生的 MppPacket,会在生成时对 MppBuffer 加引用,在释放时对 MppPacket 减引用。

1
2
3
4
5
6
7
8
MppBuffer buffer;
MppPacket pkt = NULL;

mpp_buffer_get(NULL, &buffer, size);

mpp_packet_init_with_buffer(&pkt, buffer); // 自动加引用

mpp_packet_deinit(&pkt); // 自动减引用

2.4.3 MppFrame

MppFrame 主要用于定义二维图像缓存的相关信息,有效数据的位置与长度。MppFrame 几个重要的参数成员如下:

成员名称 成员类型 描述说明
width RK_U32 表示水平方向像素数,单位为像素个数。
height RK_U32 表示垂直方向像素数,单位为像素个数。
hor_stride RK_U32 表示垂直方向相邻两行之间的距离,单位为byte数。
ver_stride RK_U32 表示图像分量之间的以行数间隔数,单位为1。

MppFrame 的其他配置参数成员如下:

成员名称 成员类型 描述说明
mode RK_U32 表示图像数据帧场属性。
pts RK_U64 表示图像的显示时间戳(Present Time Stamp)。
dts RK_U64 表示图像的解码时间戳(Decoding Time Stamp)。
eos RK_U32 表示图像的结束标志(End Of Stream)。
errinfo RK_U32 表示图像的错误标志,是否图像内有解码错误。
discard RK_U32 表示图像的丢弃标志,如果图像解码时的参考关系不满足要求,则这帧图像会被标记为需要丢弃,不被显示。
buf_size size_t 表示图像需要分配的缓存大小,与图像的格式相关,也与解码数据的格式相关。
info_change RK_U32 如果为真,表示当前 MppFrame 是一个用于标记码流信息变化的描述结构,说明了新的宽高,stride,以及图像格式。 可能的 info_change 原因有: 图像序列宽高变化。 图像序列格式变化,如 8bit 变为 10bit。 一旦 info_change 产生,需要重新分析解码器使用的内存池。
fmt MppFrameFormat 表示图像色彩空间格式以及内存排布方式。
color_range MppFrameColorRange 表示图像数据彩色空间范围: YUV full range:0 ~ 255(8bit) YUV limit range:16 ~ 235(8bit)。
buffer MppBuffer 表示 MppFrame 对应的 MppBuffer。

对于解码器来说,MppFrame 是其输出的信息结构体,码流解码后的信息(包括像素数据与 pts,错误信息等相关信息)都需要带在 MppFrame 结构体给调用者。MppFrame 中的 pts/dts,以及 eos 标志,就是继承自对应的输入 MppPacket。

同时,一旦发现码流分辨率改变,MppFrame 中的 info_change 标志就会对应置位,向用户通知发生了 info_change 事件,需要用户进行缓存池修改处理。

FBC 与 Tile 格式

MppFrameFormat 的高位标志位支持 FBC(Frame Buffer Compression)和 Tile 两种特殊的内存排布方式。

FBC 是一种帧缓存压缩格式,可以减少图像数据在内存和总线中的占用带宽。MppFrameFormat 中定义了以下 FBC 类型:

FBC类型 标志位 说明
无FBC 0x0 默认情况,不使用帧缓存压缩
AFBC_V1 0x00100000 用于 ISP 输出,具有基于宽高自动计算的 payload offset
AFBC_V2 0x00200000 用于视频解码器输出,payload offset 存储在 header 首地址的前 32 位中,始终为 0
RKFBC 0x00400000 用于视频解码器输出和编码器输入,具有基于宽度自动计算的 header stride

FBC 格式与 Tile 格式不能同时存在。

Tile 格式通过 bit 25 标志位表示,启用后图像数据按 Tile 排布存储,而非按行连续存储。Tile 排布可以提升内存访问效率,在某些硬件上可以获得更好的性能。

对于使用 FBC 或 Tile 格式的 MppFrame,图像缓存的计算方式与普通格式不同,需要预留额外的 header 空间。解码器在使用 FBC 输出时,会在 info_change 事件中通过 fmt 字段通知用户当前的输出格式,用户需要根据格式类型正确计算所需的缓存大小。

如需配置解码器输出为 FBC 或 Tile 格式,可通过 control 接口的 MPP_DEC_SET_OUTPUT_FORMAT 或 MPP_DEC_SET_FRAME_INFO 命令来实现。MPP_DEC_SET_OUTPUT_FORMAT 用于配置解码器的输出图像格式,MPP_DEC_SET_FRAME_INFO 用于配置解码器的默认宽高及格式信息。两个命令的调用时机均在 mpp_init 之后、正式解码之前。具体配置值通过在 MppFrameFormat 的基础色域格式上叠加 FBC 或 Tile 标志位来设定,例如将 NV12 格式叠加 AFBC_V2 标志位即表示输出 AFBC_V2 压缩的 NV12 图像。

2.4.4 MppCtx

MppCtx 是提供给用户使用的 MPP 实例上下文句柄,用于指代解码器或编码器实例。

用户可以通过 mpp_create 接口获取 MppCtx 实例以及 MppApi 结构体,再通过 mpp_init 来初始化 MppCtx 的编解码类型与格式,之后通过 decode_xxx/encode_xxx 以及 poll/dequeuer/enqueue 接口来进行访问,使用结束时通过 mpp_destroy 接口进行销毁。

如上图所示,mpp_create,mpp_init,mpp_destroy 是操作 MppCtx 接口的过程,其中 mpp_create 接口也获取到了 MPI 接口结构体 MppApi,真正的编码与解码过程是通过调用 MppApi 结构体里内的函数指针来实现,也就是上图中红框内的部分。红框内的函数调用分为编解码流程接口 put/get_packet/frame 和相关的 control 和 reset 接口。

2.4.5 MppApi(MPI)

MppApi 结构体封装了 MPP 的对外 API 接口,用户通过使用 MppApi 结构中提供的函数指针实现视频编解码功能,其结构如下:

成员名称 成员类型 描述
size RK_U32 MppApi结构体大小。
version RK_U32 MppApi结构体版本。
decode 函数指针 MPP_RET (*decode)(MppCtx ctx, MppPacket packet, MppFrame *frame)
视频解码接口,同时进行输入与输出,单独使用。
ctx :MPP实例上下文。
packet :输入码流。
frame :输出图像。
返回值 :0为正常,非零为错误码。
decode_put_packet 函数指针 MPP_RET (*decode_put_packet)(MppCtx ctx, MppPacket packet)
视频解码输入接口,与decode_get_frame配合使用。
ctx :MPP实例上下文。
packet :输入码流。
返回值 :0为正常,表示码流己被MPP处理;非零为出现错误,码流未被处理,需要把码流重新输入。
decode_get_frame 函数指针 MPP_RET (*decode_get_frame)(MppCtx ctx, MppFrame *frame)
视频解码输出接口,与decode_put_packet配合使用。
ctx :MPP实例上下文。
frame :输出图像。
返回值 :0为正常,表示获取输出过程正常,需要判断是否有得到有值的frame指针;非零为出现错误。
encode 函数指针 MPP_RET (*encode)(MppCtx ctx, MppFrame frame, MppPacket *packet)
视频编码接口,同时进行输入与输出,单独使用。
ctx :MPP实例上下文。
frame :输入图像。
packet :输出码流。
返回值 :0为正常,非零为错误码。
encode_put_frame 函数指针 MPP_RET (*encode_put_frame)(MppCtx ctx, MppFrame frame)
视频编码输入接口,与encode_get_packet配合使用。
ctx :MPP实例上下文。
frame :输入图像。
返回值 :0为正常,非零为错误码。
encode_get_packet 函数指针 MPP_RET (*encode_get_packet)(MppCtx ctx, MppPacket *packet)
视频编码输出接口,与encode_put_frame配合使用。
ctx :MPP实例上下文。
packet :输出码流。
返回值 :0为正常,非零为错误码。
poll 函数指针 MPP_RET (*poll)(MppCtx ctx, MppPortType type, MppPollType timeout)
端口查询接口,用于查询端口是否有数据可供dequeue。
ctx :MPP实例上下文。
type :端口类型,分为输入端口与输出端口。
timeout :查询超时参数,-1为阻塞查询,0为非阻塞查询,正值为超时毫秒数。
返回值 :0为正常,有数据可取出,非零为错误码。
dequeue 函数指针 MPP_RET (*dequeue)(MppCtx ctx, MppPortType type, MppTask *task)
端口出队列接口,用于从端口中取出MppTask结构。
ctx :MPP实例上下文。
type :端口类型,分为输入端口与输出端口。
task :MppTask。
返回值 :0为正常,有数据可取出,非零为错误码。
enqueue 函数指针 MPP_RET (*enqueue)(MppCtx ctx, MppPortType type, MppTask task)
端口入队列接口,用于往端口送入MppTask结构。
ctx :MPP实例上下文。
type :端口类型,分为输入端口与输出端口。
task :MppTask。
返回值 :0为正常,有数据可取出,非零为错误码。
reset 函数指针 MPP_RET (*reset)(MppCtx ctx)
复位接口,用于对MppCtx的内部状态进行复位,回到可用的初始化状态。需要注意的是,reset接口是阻塞的同步接口。
ctx :MPP实例上下文。
返回值 :0为正常,有数据可取出,非零为错误码。
control 函数指针 MPP_RET (*control)(MppCtx ctx, MpiCmd cmd, MppParam param)
控制接口,用于向MPP实例进行额外控制操作的接口。
ctx :MPP实例上下文。
cmd :Mpi命令类型,表示控制命令的不同类型的。
param :Mpi命令参数,表示控制控制命令的附加参数。
返回值 :0为正常,有数据可取出,非零为错误码。

三、实例

3.1 解码器

官方提供多个基本的解码器 demo 实现。解码器 demo 为 mpi_dec_test 系列程序,包括使用 decode_put_packet 和 decode_get_frame 接口的单线程 mpi_dec_test、多线程的 mpi_dec_mt_test、多实例的 mpi_dec_multi_test 以及使用 no_thread 模式直接调用 decode 接口的 mpi_dec_nt_test。

下面以 mpi_dec_test 为例,学习下 MPP 解码器的使用。首先运行测试用例 mpi_dec_test,查看运行效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ ./mpi_dec_test
mpp[62376]: mpi_dec_utils: usage: ./mpi_dec_test [options]
mpp[62376]: mpi_dec_utils: -i input_file input bitstream file
mpp[62376]: mpi_dec_utils: -o output_file output decoded frame file
mpp[62376]: mpi_dec_utils: -w width the width of input bitstream
mpp[62376]: mpi_dec_utils: -h height the height of input bitstream
mpp[62376]: mpi_dec_utils: -t type input stream coding type
mpp[62376]: mpi_dec_utils: -f format output frame format type
mpp[62376]: mpi_dec_utils: -n frame_number max output frame number
mpp[62376]: mpi_dec_utils: -s instance_nb number of instances
mpp[62376]: mpi_dec_utils: -v trace option q - quiet f - show fps
mpp[62376]: mpi_dec_utils: -slt slt file slt verify data file
mpp[62376]: mpi_dec_utils: -help help show help
mpp[62376]: mpi_dec_utils: -bufmode buffer mode hi - half internal (default) i -internal e - external
mpp[62376]: mpi: mpp coding type support list:
mpp[62376]: mpi: type: dec id 0 coding: mpeg2 id 2
mpp[62376]: mpi: type: dec id 0 coding: mpeg4 id 4
mpp[62376]: mpi: type: dec id 0 coding: h.263 id 3
mpp[62376]: mpi: type: dec id 0 coding: h.264/AVC id 7
mpp[62376]: mpi: type: dec id 0 coding: h.265/HEVC id 16777220
mpp[62376]: mpi: type: dec id 0 coding: vp8 id 9
mpp[62376]: mpi: type: dec id 0 coding: VP9 id 10
mpp[62376]: mpi: type: dec id 0 coding: avs id 16777222
mpp[62376]: mpi: type: dec id 0 coding: avs+ id 16777221
mpp[62376]: mpi: type: dec id 0 coding: avs2 id 16777223
mpp[62376]: mpi: type: dec id 0 coding: jpeg id 8
mpp[62376]: mpi: type: dec id 0 coding: av1 id 16777224
mpp[62376]: mpi: type: enc id 1 coding: h.264/AVC id 7
mpp[62376]: mpi: type: enc id 1 coding: jpeg id 8
mpp[62376]: mpi: type: enc id 1 coding: h265 id 16777220
mpp[62376]: mpi: type: enc id 1 coding: vp8 id 9

帮助文档可以分为两部分:一是 mpi_dec_test 的命令参数说明;二是码流文件的协议类型说明。

命令参数的描述说明如下:

命令参数 描述说明
-i 输入的码流文件。
-o 输出的图像文件。
-w 图像宽度,单位为像素。
-h 图像高度,单位为像素。
-t 码流文件的协议类型。
-f 图像色彩空间格式以及内存排布方式,默认为 NV12。
-n 最大解码帧数。测试时若码流较长,可仅输出前 n 帧。
-s MPP 实例数,默认为 1。
-v 日志选项:q 为静默标志;f 为 fps 显示标志。
-slt 输出帧对应的校验文件。
-bufmode 缓冲模式:hi – 半内部分配模式(默认);i – 纯内部分配模式;e – 纯外部分配模式。
-help 打开帮助文档。

mpi_dec_test 的命令参数中,输入文件(i)和码流类型(t)为强制要求配置的参数,其他参数如输出文件(o)、图像宽度(w)、图像高度(h)和解码帧数(n)等为可选参数,可以根据不同的测试需求进行配置。

mpi_dec_test 的命令参数中,输出帧对应的校验文件(slt)将输出帧数据转换为对应的循环冗余校验码(具体逻辑见utils/utils.c)。校验文件的大小往往只有几 KB,在芯片的 slt 测试中,将输出帧文件的对比转换成校验文件的对比,可以显著缩短测试周期。

MPP 库支持的输入文件的编码格式(t)为 MPEG2/4、H.263/4/5、VP8/9 和 JPEG 等,id 后的数字为不同编码格式对应的参数值。参数值来源于 OMX 的定义,值得注意的是,HEVC 和 AVS 格式的参数值与其他格式的有显著区别。

下面先实际运行下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$ ./mpi_dec_test -i /mnt/nfs/orangepi5pro/media-sdk-test/test.h264   -o /mnt/nfs/orangepi5pro/media-sdk-test/build-tree-out.yuv   -t 7 -w 640 -h 480 -n 10 -v f
mpp[96767]: mpi_dec_utils: input file /mnt/nfs/orangepi5pro/media-sdk-test/test.h264 size 310407
mpp[96767]: mpi_dec_utils: cmd parse result:
mpp[96767]: mpi_dec_utils: input file name: /mnt/nfs/orangepi5pro/media-sdk-test/test.h264
mpp[96767]: mpi_dec_utils: output file name: /mnt/nfs/orangepi5pro/media-sdk-test/build-tree-out.yuv
mpp[96767]: mpi_dec_utils: width : 640
mpp[96767]: mpi_dec_utils: height : 480
mpp[96767]: mpi_dec_utils: type : 7
mpp[96767]: mpi_dec_utils: max frames : 10
mpp[96767]: mpi_dec_test: mpi_dec_test start
mpp[96767]: mpp_info: mpp version: unknown mpp version for missing VCS info
mpp[96767]: mpi_dec_test: 0x5597471180 mpi_dec_test decoder test start w 640 h 480 type 7
mpp[96767]: mpi_dec_test: 0x5597471180 decode_get_frame get info changed found
mpp[96767]: mpi_dec_test: 0x5597471180 decoder require buffer w:h [640:480] stride [640:480] buf_size 614400
mpp[96767]: mpi_dec_test: 0x5597471180 decode get frame 0
mpp[96767]: mpi_dec_test: 0x5597471180 decode get frame 1
mpp[96767]: mpi_dec_test: 0x5597471180 decode get frame 2
mpp[96767]: mpi_dec_test: 0x5597471180 decode get frame 3
mpp[96767]: mpi_dec_test: 0x5597471180 decode get frame 4
mpp[96767]: mpi_dec_test: 0x5597471180 decode get frame 5
mpp[96767]: mpi_dec_test: 0x5597471180 decode get frame 6
mpp[96767]: mpi_dec_test: 0x5597471180 decode get frame 7
mpp[96767]: mpi_dec_test: 0x5597471180 decode get frame 8
mpp[96767]: mpi_dec_test: 0x5597471180 decode get frame 9
mpp[96767]: mpi_dec_test: decode 10 frames time 73 ms delay 5 ms fps 135.51
mpp[96767]: mpi_dec_test: test success max memory 1.76 MB

代码首先进行参数初始化:

1
2
mpi_dec_test_cmd_init(cmd, argc, argv);
mpi_dec_test_cmd_options(cmd);

然后就进入到核心:

1
ret = dec_decode(cmd);

dec_decode() 可以分作五个阶段:

1
2
3
4
5
6
1. 打开输出文件
2. 初始化 buffer manager
3. 根据类型初始化 packet 或 frame
4. 创建并配置 MPP decoder
5. 创建 thread_decode 线程,进入解码循环
6. 解码结束后 reset、deinit、destroy

第一二部分不过多介绍,第三部分会根据输入类型进行区分:

1
2
3
4
5
6
7
8
9
10
cmd->simple = (cmd->type != MPP_VIDEO_CodingMJPEG) ? 1 : 0;

if (cmd->simple) {
mpp_packet_init(&packet, NULL, 0);
...
}
else {
mpp_frame_init(&frame); // MJPEG 需要预分配输出 frame
...
}
  • H.264/H.265 等普通视频流 -> dec_simple()
  • MJPEG -> dec_advanced()

原因是 MJPEG 高级路径要求预先告诉解码器输出 frame 和 buffer,而普通 H.264/H.265 是先动态解析,等 info change 后再建 output buffer。

然后创建和初始化解码器:

1
2
mpp_create(&ctx, &mpi);
mpp_init(ctx, MPP_CTX_DEC, type);

接下来读取默认解码配置:

1
2
3
4
mpp_dec_cfg_init(&cfg);
mpi->control(ctx, MPP_DEC_GET_CFG, cfg);
mpp_dec_cfg_set_u32(cfg, "base:split_parse", need_split);
mpi->control(ctx, MPP_DEC_SET_CFG, cfg);

这里的 need_split = 1 很重要。H.264/H.265 文件不是“一个 packet 就是一帧”,可能是:

1
2
3
4
SPS/PPS
I 帧
P 帧
分片 NAL

need_split 让 MPP 内部 parser 自己找帧边界,应用只需要把文件块不断喂进去。

初始化完后,dec_decode() 创建了一个线程:

1
pthread_create(&thd, &attr, thread_decode, &data);

这个线程不是多线程流水线,它只是把实际解码循环放在线程里,方便 frame_num < 0 时主线程可以等用户按 Enter 来停止。

thread_decode 的基本逻辑是:

1
2
3
4
5
if (cmd->simple)
while (!data->loop_end && MPP_OK == dec_simple(data)); // H.264/H.265解码
else
while (!data->loop_end)
dec_advanced(data); // MJPEG解码

下面看 dec_simple:

先读一块文件数据:

1
reader_read(cmd->reader, &slot);

这个 reader 来自 mpi_dec_utils.c,内部有后台线程预读文件,把它切成 FileBufSlot。每个 slot 里有:

1
2
3
slot->data
slot->size
slot->eos

如果已经 EOS,但还没达到目标帧数,就:

1
reader_rewind(cmd->reader);

也就是重新从文件头读,用于无限循环或补齐帧数。

然后把 slot 设置到 packet:

1
2
3
4
mpp_packet_set_data(packet, slot->data);
mpp_packet_set_size(packet, slot->size);
mpp_packet_set_pos(packet, slot->data);
mpp_packet_set_length(packet, slot->size);

如果是最后一包:

1
mpp_packet_set_eos(packet);

接下来是两层循环。外层负责送 packet,内层负责取 frame。

送 packet:

1
ret = mpi->decode_put_packet(ctx, packet);

decode_put_packet() 成功,只代表 MPP 接收了这段压缩数据。它不代表马上解码出一帧。而且如果 MPP 输入队列满了,decode_put_packet() 可能失败,所以代码会 sleep 后重试:

1
msleep(1);

然后取 frame:

1
ret = mpi->decode_get_frame(ctx, &frame);

如果返回 MPP_ERR_TIMEOUT,说明当前还没有 frame,它最多重试 30 次,每次 sleep 1 ms:

1
2
3
times--;
msleep(1);
goto try_again;

拿到 frame 后,第一件要判断的是:

1
if (mpp_frame_get_info_change(frame))

这个 frame 不是像素帧,而是解码器通知你:

  • 我已经解析出视频参数了
  • 宽高是多少
  • stride 是多少
  • 每个 buffer 需要多大

然后创建输出 buffer group:

1
grp = dec_buf_mgr_setup(data->buf_mgr, buf_size, 24, cmd->buf_mode);

这里的 24 是 buffer 个数。H.264/H.265 需要参考帧和多帧输出,所以不能只给一两个 buffer。

然后告诉 MPP:

1
2
mpi->control(ctx, MPP_DEC_SET_EXT_BUF_GROUP, grp);  // 以后输出 frame 用这个 buffer group
mpi->control(ctx, MPP_DEC_SET_INFO_CHANGE_READY, NULL); // 已经准备好了,继续解码

如果不处理 info change,后面通常拿不到正常图像帧。

如果不是 info change,那就是实际解码帧。代码会读取错误和丢弃信息:

1
2
err_info = mpp_frame_get_errinfo(frame);
discard = mpp_frame_get_discard(frame);

如果没有错误,并且配置了输出文件,就写文件:

1
dump_mpp_frame_to_file(frame, data->fp_output);

dump_mpp_frame_to_file() 在 utils 里,会根据 frame 的格式把 buffer 写成原始 YUV/NV12/RGB 文件。它不是显示,也不是转码,只是导出原始帧。

然后统计:

1
2
data->frame_count++;
fps_calc_inc(cmd->fps);

最后必须释放 frame:

1
mpp_frame_deinit(&frame);  // 如果不 deinit,就会泄漏或者让 buffer 池耗尽

而 dec_advanced() 和 dec_simple() 不一样。JPEG 是完整独立帧,所以它用了更直接的流程:

1
2
3
4
5
6
reader_index_read(cmd->reader, 0, &slot);
mpp_packet_init_with_buffer(&packet, slot->buf);
mpp_meta_set_frame(meta, KEY_OUTPUT_FRAME, frame);
mpi->decode_put_packet(ctx, packet);
mpi->decode_get_frame(ctx, &frame_ret);
dump_mpp_frame_to_file(frame_ret, data->fp_output);

这也是为什么 MJPEG 需要提前 mpp_frame_init() 并设置 buffer,而 H.264/H.265 是等 info change 后再创建 buffer group。

线程结束后,dec_decode() 做:

1
mpi->reset(ctx);

这个很重要,提前停止、达到帧数停止、出错退出,都应该 reset,否则 MPP 可能打印 leaked buffer。

然后统一释放:

1
2
3
4
5
6
7
mpp_packet_deinit(&data.packet);
mpp_frame_deinit(&frame);
mpp_destroy(ctx);
mpp_buffer_put(frm_buf);
dec_buf_mgr_deinit(data.buf_mgr);
fclose(data.fp_output);
mpp_dec_cfg_deinit(cfg);

总结一下,实现 MPP 解码器,大致按照如下步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
1. 打开输入文件,创建 reader
2. 打开输出文件
3. mpp_create() 创建 ctx
4. mpp_init(ctx, MPP_CTX_DEC, coding) 初始化解码器
5. MPP_DEC_GET_CFG -> 设置 split_parse -> MPP_DEC_SET_CFG
6. 循环读输入 packet
7. decode_put_packet(),失败就等待重试
8. decode_get_frame(),没帧就等待重试
9. 如果 info_change:设置 buffer group,再 SET_INFO_CHANGE_READY
10. 如果是普通 frame:处理、写文件、frame_count++、mpp_frame_deinit()
11. EOS 或达到帧数后退出
12. mpi->reset()
13. mpp_packet_deinit / mpp_frame_deinit / mpp_destroy / release buffer group

3.2 编码器

下面以 mpi_enc_test.c 为例,看下编码器的实现。

先看执行效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ mpi_dec_test \
-i /mnt/nfs/orangepi5pro/media-sdk-test/enc-out.h264 \
-o /mnt/nfs/orangepi5pro/media-sdk-test/enc-dec.yuv \
-t 7 -w 640 -h 480 -n 10 -v f
mpp[102659]: mpi_dec_utils: input file /mnt/nfs/orangepi5pro/media-sdk-test/enc-out.h264 size 23136
mpp[102659]: mpi_dec_utils: cmd parse result:
mpp[102659]: mpi_dec_utils: input file name: /mnt/nfs/orangepi5pro/media-sdk-test/enc-out.h264
mpp[102659]: mpi_dec_utils: output file name: /mnt/nfs/orangepi5pro/media-sdk-test/enc-dec.yuv
mpp[102659]: mpi_dec_utils: width : 640
mpp[102659]: mpi_dec_utils: height : 480
mpp[102659]: mpi_dec_utils: type : 7
mpp[102659]: mpi_dec_utils: max frames : 10
mpp[102659]: mpi_dec_test: mpi_dec_test start
mpp[102659]: mpp_info: mpp version: unknown mpp version for missing VCS info
mpp[102659]: mpi_dec_test: 0x558fc6b180 mpi_dec_test decoder test start w 640 h 480 type 7
mpp[102659]: mpi_dec_test: 0x558fc6b180 loop again
mpp[102659]: mpi_dec_test: 0x558fc6b180 decode_get_frame get info changed found
mpp[102659]: mpi_dec_test: 0x558fc6b180 decoder require buffer w:h [640:480] stride [640:480] buf_size 614400
mpp[102659]: mpi_dec_test: 0x558fc6b180 decode get frame 0
mpp[102659]: mpi_dec_test: 0x558fc6b180 decode get frame 1
mpp[102659]: mpi_dec_test: 0x558fc6b180 decode get frame 2
mpp[102659]: mpi_dec_test: 0x558fc6b180 decode get frame 3
mpp[102659]: mpi_dec_test: 0x558fc6b180 decode get frame 4
mpp[102659]: mpi_dec_test: 0x558fc6b180 decode get frame 5
mpp[102659]: mpi_dec_test: 0x558fc6b180 decode get frame 6
mpp[102659]: mpi_dec_test: 0x558fc6b180 decode get frame 7
mpp[102659]: mpi_dec_test: 0x558fc6b180 decode get frame 8
mpp[102659]: mpi_dec_test: 0x558fc6b180 loop again
mpp[102659]: mpi_dec_test: 0x558fc6b180 decode get frame 9
mpp[102659]: mpi_dec_test: decode 10 frames time 24 ms delay 4 ms fps 413.62
mpp[102659]: mpi_dec_test: test success max memory 4.69 MB

解码器是“压缩码流进,YUV 帧出”。编码器正好相反:

1
2
解码:MppPacket -> decode_put_packet -> MppFrame -> decode_get_frame
编码:MppFrame -> encode_put_frame -> MppPacket -> encode_get_packet

mpi_enc_test 的生命周期是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
main
-> 解析参数
-> enc_test_multi
-> 创建 enc_test 线程
-> mpi_enc_ctx_init
-> 创建 buffer group
-> 分配输入 frame buffer
-> 分配输出 packet buffer
-> mpp_create
-> mpp_init(MPP_CTX_ENC)
-> MPP_ENC_GET_CFG
-> mpi_enc_cfg_setup
-> MPP_ENC_SET_CFG
-> test_mpp_run
-> 写 SPS/PPS header
-> 逐帧编码
-> mpi->reset
-> destroy / release

大致可以把它理解成四个阶段:

  • 准备阶段 :初始化输入、输出、buffer
  • 配置阶段 :告诉 encoder 要怎么编码
  • 运行阶段 :frame 进,packet 出
  • 清理阶段 :reset、destroy、释放资源

参数解析的部分不过多介绍,下面直接看核心 enc_test_multi():

支持多实例编码。

如果 -s 1,它就创建一个编码线程:

1
pthread_create(&ctxs[i].thd, NULL, enc_test, &ctxs[i]);

如果 -s 4,它就创建 4 个 enc_test() 线程,每个线程有自己的:

  • MppCtx
  • MppBufferGroup
  • 输入 frame buffer
  • 输出 packet buffer
  • 输出文件

注意,这里的“多实例”不是把一个编码器拆成多线程,而是同时创建多个独立编码器。默认 -s 1。

如果 -n -1,主线程会等你按 Enter:

1
**** Press Enter to stop loop encoding ****

然后设置 loop_end = 1,让编码线程退出。

在 enc_test_multi() 中创建了一个线程:

1
ret = pthread_create(&ctxs[i].thd, NULL, enc_test, &ctxs[i]);

enc_test 是重点。它的骨架可以简化成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
mpi_enc_ctx_init(p, cmd, chn);

mpp_buffer_group_get_internal(&p->buf_grp, ...);
mpp_buffer_get(p->buf_grp, &priv->frm_buf, p->frame_size + p->header_size);
mpp_buffer_get(p->buf_grp, &priv->pkt_buf, p->frame_size);
mpp_buffer_get(p->buf_grp, &priv->md_info, mdinfo_size);

mpp_create(&p->ctx, &p->mpi);
mpi->control(p->ctx, MPP_SET_OUTPUT_TIMEOUT, &timeout);
mpp_init(p->ctx, MPP_CTX_ENC, p->type);

mpp_enc_cfg_create(&p->cfg, cmd->kmpp_mode);
mpi->control(p->ctx, MPP_ENC_GET_CFG, p->cfg);
mpi_enc_cfg_setup(p, cmd, obj_set->cfg_obj);

test_mpp_run(info);

mpi->reset(p->ctx);

mpp_destroy(p->ctx);
mpp_enc_cfg_deinit(p->cfg);
mpp_buffer_put(priv->frm_buf);
mpp_buffer_put(priv->pkt_buf);
mpp_buffer_put(priv->md_info);
mpp_buffer_group_put(p->buf_grp);

enc_test() 里分配了三个关键 buffer:

1
2
3
priv->frm_buf
priv->pkt_buf
priv->md_info

含义:

buffer 作用
frm_buf 存放输入原始帧,比如 NV12
pkt_buf 存放编码输出码流
md_info motion info,某些编码模式需要

使用流程是:

1
2
3
4
5
CPU 把 YUV 写进 frm_buf
mpp_buffer_sync_end() 刷 cache
VPU 从 frm_buf 读原始帧
VPU 把编码结果写进 pkt_buf
CPU 从 pkt_buf 读出 packet 并写文件

实际写输入图像时,代码会先:

1
2
3
mpp_buffer_sync_begin(priv->frm_buf);
read_image(buf, ...);
mpp_buffer_sync_end(priv->frm_buf);

sync_begin/sync_end 是为了 cache 一致性。CPU 写完数据后,要确保 VPU 能看到。

编码 H.264/H.265 时,码流前面需要 header:

1
2
H.264: SPS/PPS
H.265: VPS/SPS/PPS

这些不是图像帧,但解码器必须靠它们知道分辨率、profile、level、帧率等信息。

所以 test_mpp_run() 一开头做:

1
2
3
4
mpp_packet_init_with_buffer(&packet, priv->pkt_buf);
mpp_packet_set_length(packet, 0);
mpi->control(ctx, MPP_ENC_GET_HDR_SYNC, packet);
fwrite(ptr, 1, len, p->fp_output[chn]);

含义是:

  • 向编码器要一次 header
  • 把 SPS/PPS 写进 .h264 或 .h265 文件
  • 后面再写普通帧的编码数据

如果少了这一步,输出文件可能无法被解码器正常读取。

test_mpp_run() 是最核心函数。它的循环可以写成伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
while 没有 pkt_eos:
准备一帧原始图像到 frm_buf
创建 MppFrame
设置宽高、stride、格式、buffer
创建 MppPacket
把 MppPacket 挂到 frame meta: KEY_OUTPUT_PACKET
encode_put_frame(frame)
mpp_frame_deinit(frame)

while 还有 packet:
encode_get_packet(packet)
写 packet 到输出文件
mpp_packet_deinit(packet)

判断是否达到 frame_num 或 EOS

准备好原始图像后,代码每帧创建一个 MppFrame:

1
2
3
4
5
6
7
8
9
10
mpp_frame_init(&frame);

mpp_frame_set_width(frame, p->width);
mpp_frame_set_height(frame, p->height);
mpp_frame_set_hor_stride(frame, p->hor_stride);
mpp_frame_set_ver_stride(frame, p->ver_stride);
mpp_frame_set_fmt(frame, p->fmt);
mpp_frame_set_eos(frame, p->frm_eos);

mpp_frame_set_buffer(frame, priv->frm_buf);

这相当于告诉编码器:

  • 这是一帧 640x480 NV12
  • 每行内存 stride 是 640
  • 像素在 priv->frm_buf 里面
  • 这是不是最后一帧

MppFrame 本身不存像素,它只是描述这帧图像并引用 MppBuffer。

真正送入编码器:

1
ret = mpi->encode_put_frame(ctx, frame);

成功后释放输入 frame:

1
mpp_frame_deinit(&frame);

这里释放的是 MppFrame 描述对象,不是释放 priv->frm_buf。priv->frm_buf 会继续复用于下一帧。

然后取输出:

1
2
3
4
5
6
7
8
9
10
do {
ret = mpi->encode_get_packet(ctx, &packet);

ptr = mpp_packet_get_pos(packet);
len = mpp_packet_get_length(packet);

fwrite(ptr, 1, len, p->fp_output[chn]);

mpp_packet_deinit(&packet);
} while (!eoi);

编码完成后:

1
p->mpi->reset(p->ctx);

然后释放:

1
2
3
4
5
6
mpp_destroy(p->ctx);
mpp_enc_cfg_deinit(p->cfg);
mpp_buffer_put(priv->frm_buf);
mpp_buffer_put(priv->pkt_buf);
mpp_buffer_put(priv->md_info);
mpp_buffer_group_put(p->buf_grp);

顺序可以理解成:

1
2
3
4
5
6
1. reset encoder,结束编码会话
2. destroy MppCtx
3. 释放 cfg
4. 释放 frame/packet/motion buffer
5. 释放 buffer group
6. 关闭文件

MPP 是异步硬件资源,必须按这个方向清理,否则容易 leaked buffer。

总结一下,实现编码器的大致思路:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1. 初始化输入输出
2. 创建 buffer group
3. 分配 frm_buf、pkt_buf、md_info
4. mpp_create()
5. mpp_init(MPP_CTX_ENC, type)
6. MPP_ENC_GET_CFG
7. 设置 prep/rc/codec 参数
8. MPP_ENC_SET_CFG
9. H.264/H.265 先取 header 写 SPS/PPS
10. while 帧循环:
准备 YUV 到 frm_buf
mpp_frame_init
设置 width/height/stride/format/buffer
mpp_packet_init_with_buffer
mpp_meta_set_packet(KEY_OUTPUT_PACKET)
encode_put_frame
mpp_frame_deinit
while packet:
encode_get_packet
写文件
mpp_packet_deinit
11. reset
12. destroy、释放 buffer/cfg/文件

基于MPP库进行视频编解码
http://example.com/2026/08/18/基于MPP库进行视频编解码/
作者
Yu xin
发布于
2026年8月18日
许可协议