Skip to main content

Rockchip Developer Guide MPP

:::info 文档信息

页数:38 · 语言:英文

原始文档:Rockchip_Developer_Guide_MPP_CN.pdf

:::

在线预览​

若预览无法显示,请直接下载 PDF 后用本地阅读器打开。


MPP 开发参考

File Status: [ ] Draft [ ] Beta [√] ReleaseProject:MPP
Version:0.5
Author:陈恒明
Date:06/08/2020
RevisionDateDescriptionAuthor
0.104/18/2018初始版本陈恒明
0.205/07/2018增加解码器control 命令说明,编码器部分说明和demo 部分说明陈恒明
0.305/22/2018修正一些笔误和说明错误,重新编排页码陈恒明 谢雄斌(精英智通)
0.411/28/2018更新编码器输入图像的内存排布说明。 修正编码器流程图笔误陈恒明 研果(科通)
0.506/08/2020更新编码器新配置接口 不再支持RK3188陈恒明

第一章 MPP 介绍​

1.1 概述​

瑞芯微提供的媒体处理软件平台(Media Process Platform,简称 MPP)是适用于瑞芯微芯片系列的通用媒体处理软件平台。该平台对应用软件屏蔽了芯片相关的复杂底层处理,其目的是为了屏蔽不同芯片的差异,为使用者提供统一的视频媒体处理接口(Media Process Interface,缩写MPI)。MPP提供的功能包括:视频解码 H.265 / H.264 / H.263 / VP9 / VP8 / MPEG-4 / MPEG-2 / MPEG-1 / VC1 / MJPEG 视频编码 H.264 / VP8 / MJPEG 视频处理视频拷贝,缩放,色彩空间转换,场视频解交织(Deinterlace)本文档描述了MPP 框架以及组成模块,以及供用户使用的MPI 接口。本文档适合于上层应用开发人员以及技术支持人员阅读。

1.2 系统架构​

MPP 平台在系统架构的层次图如下图:

图表 1 MPP 系统框架硬件层Hardware硬件层是瑞芯微系列芯片平台的视频编解码硬件加速模块,包括VPU,rkvdec,rkvenc 等不同类型,不同功能的硬件加速器。

内核驱动层Kernel driver Linux 内核的编码器硬件设备驱动,以及相关的 mmu,内存,时钟,电源管理模块等。支持的平台主要是Linux kernel 3.10 和4.4 两个版本。MPP 库对于内核驱动有依赖。

 MPP 层用户态的MPP 层屏蔽了不同操作系统和不同芯片平台的差异,为上层使用者提供统一的 MPI 接口。MPP 层包括MPI 模块,OSAL 模块,HAL 模块以及视频编解码器(Video Decoder / Video Encoder)和视频处理功能模块(Video Process)。

操作系统层MPP 用户态的运行平台,如 Android 以及Debian 等Linux 发行版

应用层MPP 层通过MPI 对接各种中间件软件,如 OpenMax,ffmpeg 和gstreamer,或者直接对接客户的上层应用。

1.3 平台支持​

1.3.1 软件平台支持​

MPP 支持在各种版本的Android 平台和纯Linux 平台上运行。支持瑞芯微Linux 内核 3.10 和4.4 版本,需要有 vcodec_service 设备驱动支持以及相应的DTS 配置支持。

1.3.2 硬件平台支持​

支持瑞芯微主流的各种系列芯片平台:RK3288 系列,RK3368 系列,RK3399 系列RK30xx 系列,RK312x 系列芯片,RK322x 系列芯片,RK332x 系列RV1109 / RV1126 系列(注:RV1107/RV1108 会逐步退出支持)

1.4 功能支持​

MPP 支持的编解码功能随运行的芯片平台规格不同区别很大,请查询对应芯片的《Multimedia Benchmark》。

1.5 注意事项​

如果想快速了解MPP 的使用和demo,请直接转至——第四章 MPP demo 说明。

如果想快速编译和使用 MPP 代码,请直接转至——第五章 MPP 库编译与使用。

如果想了解MPP 设计细节与设计思路,请参考 MPP 代码根目录的readme.txt,doc 目录下的txt 文档以及头文件的注释说明。

第二章接口设计说明​

本章节描述了用户在使用MPP 过程中会直接接触到的数据结构,以及这些数据结构的使用说明。

由于视频编解码与视频处理过程需要处理大量的数据交互,包括码流数据,图像数据以及内存数据,同时还要处理与上层应用以及内核驱动的交叉关系,所以 MPP 设计了 MPI 接口,用于与上层交互。本章节说明了MPI 接口使用的数据结构,以及设计思路。

2.1 接口结构概述​

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

图表 2 MPI 接口使用的数据结构MppMem 为C 库malloc 内存的封装。MppBuffer 为硬件用的 dmabuf 内存的封装。MppPacket 为一维缓存封装,可以从 MppMem 和 MppBuffer 生成,主要用于表示码流数据。MppFrame 为二维帧数据封装,可以从 MppMem 和 MppBuffer 生成,主要用于表示图像数据。使用 MppPacket 和MppFrame 就可以简单有效的完成一般的视频编解码工作。以视频解码为例,码流输入端把地址和大小赋值给MppPacket,通过 put_packet 接口输入,在输出端通过get_frame 接口得到输入图像 MppFrame,即可完成最简单的视频解码过程。

图表 3 使用简单接口实现视频解码MppMeta 和MppTask 为输入输出用任务的高级组合接口,可以支持指定输入输出方式等复杂使用方式,较少使用。注意:以上这些接口数据结构都是使用 void*句柄来引用使用,其目的是为了方便扩展和前向兼容。本段落中的提到的成员都是通过形如mpp_xxx_set/get_xxx 的接口来访问。

2.1 内存封装MppBuffer​

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

成员名称成员类型描述说明
ptrvoid *表示内存块的起始虚拟地址。
sizesize t _表示内存块的大小。
fdint表示内存块的用户态空间文件句柄。

在解码过程中,解码图像的缓存通常需要在固定的缓存池里进行轮转,为了实现这一点,MPP 在MppBuffer 基础之上又定义了MppBufferGroup,两者的使用方式有两种,如下图:

图表 4 MppBuffer 的常规使用方式其流程伪代码如下图:这种方式可以实现解码器在解码过程中零拷贝输出(解码器的输出帧与解码器内部使用的参考帧用是同一帧),但不容易实现零拷贝显示(解码器的输出帧不一定在显示端可以直接显示),同时要求用户知道需要给解码器开多大的空间进行解码。另一种使用方式是把 MppBufferGroup 完全做为一个缓存的管理器,可以管理外部导入的缓存。其使用方式如下图:

图表 5 MppBuffer 外部导入使用方式其流程伪代码如下图:这种方式可以使得解码器使用外部的缓存,可以对接 OpenMax/ffmpeg/gstreamer 这样的中间件,也方便对接用户己有的上层代码,便于实现零拷贝显示。

2.2 码流封装MppPacket​

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

成员名称成员类型描述说明
datavoid *表示缓存空间的起始地址。
sizesize t _表示缓存空间的大小。
posvoid *表示缓存空间内有效数据的起始地址。
lengthsize t _表示缓存空间内有效数据的长度。如果在decode put packet 调用之后 _ _ length 变为0,说明此包码流己消耗完成。

其关系如下图所示:

图表 6 MppPacket 重要参数说明MppPacket 的其他配置参数成员如下:

成员名称成员类型描述说明
ptsRK U64 _表示显示时间戳(Present Time Stamp)。
dtsRK U64 _表示解码时间戳(Decoding Time Stamp)。
eosRK U32 _表示码流结束标志(End Of Stream)。
bufferMppBuffer表示MppPacket 对应的MppBuffer。
flagRK 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 释放处理,如下示例;

如果是拷贝产生的MppPacket,会做free 释放内存,如下示例;如果是MppBuffer 产生的MppPacket,会在生成时对 MppBuffer 加引用,在释放时对 MppPacket 减引用。

2.3 图像封装MppFrame​

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

成员名称成员类型描述说明
widthRK U32 _表示水平方向像素数,单位为像素个数。
heightRK U32 _表示垂直方向像素数,单位为像素个数。
hor stride _RK U32 _表示垂直方向相邻两行之间的距离,单位为byte 数。
ver stride _RK U32 _表示图像分量之间的以行数间隔数,单位为1。

图表 7 MppFrame 重要参数说明MppFrame 的其他配置参数成员如下:

成员名称成员类型描述说明
modeRK U32 _表示图像数据帧场属性:
ptsRK U64 _表示图像的显示时间戳(Present Time Stamp)。
dtsRK U64 _表示图像的解码时间戳(Decoding Time Stamp)。
eosRK U32 _表示图像的结束标志(End Of Stream)。
errinfoRK U32 _表示图像的错误标志,是否图像内有解码错误。
discardRK U32 _表示图像的丢弃标志,如果图像解码时的参考关系不满足要求,则这帧图像 会被标记为需要丢弃,不被显示。
buf size _size t _表示图像需要分配的缓存大小,与图像的格式相关,也与解码数据的格式相 关。
info change _RK U32 _如果为真,表示当前MppFrame 是一个用于标记码流信息变化的描述结构, 说明了新的宽高,stride,以及图像格式。 可能的info change 原因有: _ 1. 图像序列宽高变化。 2. 图像序列格式变化,如8bit 变为10bit。 一旦info change 产生,需要重新分析解码器使用的内存池。 _
fmtMppFrame Format表示图像色彩空间格式以及内存排布方式:
color range _MppFrame ColorRange表示图像数据彩色空间范围: YUV full range:0 ~ 255(8bit)
YUV limit range:16 ~ 235(8bit)
bufferMppBuffer表示MppFrame 对应的MppBuffer。

对于解码器来说,MppFrame 是其输出的信息结构体,码流解码后的信息(包括像素数据与 pts,错误信息等相关信息)都需要带在 MppFrame 结构体给调用者。MppFrame 中的 pts/dts,以及eos 标志,就是继承自对应的输入 MppPacket。同时,一旦发现码流分辨率改变,MppFrame 中的 info_change 标志就会对应置位,向用户通知发生了info_change 事件,需要用户进行缓存池修改处理。

2.4 高级任务封装 MppTask​

当MppPacket 与 MppFrame 组成的接口无法满足需求时,需要使用MppTask 做为一个数据容器,来满足复杂的输入输出需求。MppTask 需要与poll/dequeuer/enqueue 接口来配合使用,对比put_packet/get_frame 等简单流程接口,MppTask 的使用流程复杂,效率低,是为了满足复杂需求的代价。

图表 8 使用 MppTask 来进行输入输出MppTask 是一个通过关键字key 值(MppMetaKey)来进行扩展的结构,可以通过扩展支持的数据类型来支持复杂的高级需求。可以使用通过mpp_task_meta_set/get_xxx 系列接口来对 MppTask 里的不同关键字数据进行访问。图表 9 MppTask 支持的数据类型与关键字类型在实际使用中,需要从 MPP 的输入端口通过 dequeue 接口获取 MppTask,把需要处理的数据通过mpp_task_meta_set_xxx 系列接口配置到MppTask 里,之后enqueue 输出到 MPP 实例进行处理。MPP的输出端口流程类似,需要把 mpp_task_meta_set_xxx 系列接口换成mpp_task_meta_get_xxx 系列接口来从MppTask 里获取数据。目前实用的编码器接口,以及 MJPEG 解码接口有使用 MppTask 进行实现。

2.5 实例上下文封装 MppCtx​

MppCtx 是提供给用户使用的 MPP 实例上下文句柄,用于指代解码器或编码器实例。用户可以通过mpp_create 接口获取MppCtx 实例以及 MppApi 结构体,再通过 mpp_init 来初始化MppCtx 的编解码类型与格式,之后通过decode_xxx/encode_xxx 以及poll/dequeuer/enqueue 接口来进行访问,使用结束时通过mpp_destroy 接口进行销毁。

图表 10 MppCtx 使用过程

2.6 API 封装MppApi(MPI)​

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

成员名称成员类型描述
sizeRK U32 _MppApi 结构体大小。
versionRK 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 命令类型,表示控制命令的不同类型的。 task :Mpi 命令参数,表示控制控制命令的附加参数。 返回值 :0 为正常,有数据可取出,非零为错误码。

第三章 MPI 接口使用说明​

本章节描述了用户使用 MPI 接口的具体过程,以及过程是的一些注意事项。

MPI(Media Process Interface)是MPP 提供给用户的接口,用于提供硬件编解码功能,以及一些必要的相关功能。MPI 是通过C 结构里的函数指针方式提供给用户,用户可以通过 MPP 上下文结构MppCtx 与MPI 接口结构MppApi 组合使用来实现解码器与编码器的功能。

图表 11 MPI 接口范围如上图所示,mpp_create,mpp_init,mpp_destroy 是操作 MppCtx 接口的过程,其中 mpp_create 接口也获取到了MPI 接口结构体 MppApi,真正的编码与解码过程是通过调用 MppApi 结构体里内的函数指针来实现,也就是上图中红框内的部分。红框内的函数调用分为编解码流程接口put/get_packet/frame 和相关的control 和reset 接口。下文先描述编解码器接口,再对编解码器工作中的一些要点进行说明。

3.1 解码器数据流接口​

解码器接口为用户提供了输入码流,输出图像的功能,接口函数为 MppApi 结构体里的decode_put_packet 函数,decode_get_frame 函数和decode 函数。这组函数提供了最简洁的解码功能支持。

3.1.1 decode_put_packet​

接口定义MPP RET decode put packet(MppCtx ctx, MppPacket packet) _ _ _
输入参数ctx :MPP 解码器实例 packet :待输入的码流数据
返回参数运行错误码
功能输入packet 码流包数据给ctx 指定的MPP 解码器实例

输入码流的形式:分帧与不分帧MPP 的输入都是没有封装信息的裸码流,裸码流输入有两种形式:一种是已经按帧分段的数据,即每一包输入给 decode_put_packet 函数的 MppPacket 数据都已经包含完整的一帧,不多也不少。在这种情况下,MPP 可以直接按包处理码流,是MPP 的默认运行情况。另一种是按长度读取的数据,这样的数据无法判断一包 MppPacket 数据是否是完整的一帧,需要MPP 内部进行分帧处理。MPP 也可以支持这种形式的输入,但需要在 mpp_init 之前,通过control接口的MPP_DEC_SET_PARSER_SPLIT_MODE 命令,MPP 内的 need_split 标志打开。

这样,调用decode_put_packet 输入的MppPacket 就会被MPP 重新分帧,进入到情况一的处理。如果这两种情况出现了混用,会出现码流解码出错的问题。分帧方式处理效率高,但需要输入码流之前先进行解析与分帧;不分帧方式使用简单,但效率会受影响。在mpi_dec_test 的测试用例中,使用的是方式不分帧的方式。在瑞芯微的Android SDK 中,使用的是分帧的方式。用户可以根据自己的应用场景和平台条件进行选择。

输入码流的消耗输入 MppPacket 的有效数据长度为 length,在送入decode_put_packet 之后,如果输入码流被成功地消耗,函数返回值为零(MPP_OK),同时MppPacket 的length 被清为 0。如果输入码流还没有被处理,会返回非零错误码,MppPacket 的length 保持不变。

函数的工作模式decode_put_packet 函数的功能是输入待解码码流给MPP 实例,但在一些情况下,MPP 实例无法接收更多的数据,这时,工作于非阻塞模式的 decode_put_packet 会报出错误信息并直接返回。用户得到decode_put_packet 返回的错误码之后,需要进行一定时间的等待,再重新送入码流数据,避免额外的频繁cpu 开销。

最大缓冲数据包数量MPP 实例默认可以接收 4 个输入码流包在处理队列中,如果码流送得太快,就会报出错误码要求用户等待后再送。

3.1.2 decode_get_frame​

接口定义MPP RET decode get frame(MppCtx ctx, MppFrame *frame) _ _ _
输入参数ctx :MPP 解码器实例。 frame :用于获取MppFrame 实例的指针。
返回参数运行错误码
功能从ctx 指定的MPP 解码器实例里获取完成解码的frame 描述信息。

MPP 解码输出的图像是通过 MppFrame 结构来描述的,同时MppFrame 结构也是MPP 实例输出信息的管道,图像的错误信息,以及变宽高信息(info change)也是带在MppFrame 结构进行输出的。

输出图像的错误信息图像的错误信息为errinfo,表示图像内容是否有错误,errinfo 不为零则表示码流解码时发生了错误,图像内容是有问题的,可以做丢弃处理。

解码器图像空间需求的确认解码器在解码时,需要为输出图像获取保存像素数据的内存空间,用户需要给解码器提供足够大小,这个空间大小的需求,会在 MPP 解码器内部根据不同的芯片平台以及不同的视频格式需求进行计算,计算后的内存空间需求会通过 MppFrame 的成员变量buf_size 提供给用户。用户需要按buf_size的大小进行内存分配,即可满足解码器的要求。

输出图像的变宽高信息(Info change)当码流的宽高,格式,像素位深等信息发生变化时,需要反馈给用户,用户需要更新解码器使用的内存池,把新的内存更新给解码器。这里涉及到解码内存分配与使用模式,会在 3.3.2 图像内存分配以及交互模式进行说明。

3.1.3 decode​

decode 函数是decode_put_packet 与decode_get_frame 数据的结合,为用户提供了两个函数的复合调用。其内部逻辑为:1. 获取输出图像;2. 如果输出图像获取成功即返回;3. 判断码流己送入成功则返回;4. 送入输入码流;5. 标记码流送入是否成功并循环第一步;在用户看来,decode 函数首先是获取解码图像,有解码图像优先返回解码图像,没有可输出的解码图像的情况下送入码流,最后再尝试一次获取解码图像并退出。

3.2 解码器控制接口​

3.2.1 control​

在定义于rk_mpi_cmd.h 文件的MpiCmd 枚举类型定义了 control 接口命令字,其中与解码器和解码过程相关的命令如下:

从MPP_DEC_CMD_BASE 到 MPP_DEC_CMD_END 之间的命令为解码器的control 接口命令,分别介绍这些命令的功能如下:MPP_DEC_SET_FRAME_INFO命令参数为MppFrame,用于配置解码器的默认宽高信息,返回的 MppFrame 结构会从解码器中带出需要分配的图像缓存大小。命令调用时机一般在 mpp_init 之后,mpi->decode_put_packet 之前。MPP_DEC_SET_EXT_BUF_GROUP命令参数为MppBufferGroup,用于把解码器图像解码所需的MppBufferGroup 配置给解码器。命令调用时机视图像内存分配模式有不同。MPP_DEC_SET_INFO_CHANGE_READY无命令参数,用于标记解码器使用的MppBufferGroup 已经完成Info Change 操作的reset 处理,可以继续解码。命令调用时机视图像内存分配模式有不同。MPP_DEC_SET_PRESENT_TIME_ORDER命令参数为RK_U32*,用于处理异常的码流时间戳。MPP_DEC_SET_PARSER_SPLIT_MODE命令参数为RK_U32*,用于使能MPP 内的协议解析器使用内部分帧处理,默认为码流按帧输入,不开启。命令调用时机是在mpp_init 之前。MPP_DEC_SET_PARSER_FAST_MODE命令参数为RK_U32*,用于使能MPP 内的快速帧解析,提升解码的软硬件并行度,但副作用是会对错误码流的标志有影响,默认关闭。命令调用时机是在 mpp_init 之前。MPP_DEC_GET_STREAM_COUT命令参数为RK_U32*,用于外部应用获取还未处理的码流包数量,历史遗留接口。MPP_DEC_GET_VPUMEM_USED_COUT命令参数为RK_U32*,用于外部应用获取MPP 使用的 MppBuffer 数量,历史遗留接口。MPP_DEC_SET_VC1_EXTRA_DATA暂未实现,历史遗留接口。MPP_DEC_SET_OUTPUT_FORMAT命令参数为MppFrameFormat,用于外部应用配置JPEG 解码器的输出格式,默认不使用。MPP_DEC_SET_DISABLE_ERROR命令参数为RK_U32*,用于关闭MPP 解码器的错误处理。一旦使能,MPP 解码会无视码流的错误情况,输出全部的可解码图像,同时不对输出的 MppFrame 结构里的 errinfo 进行标记。命令调用时机在 decode_put_packet 之前。MPP_DEC_SET_IMMEDIATE_OUT命令参数为RK_U32*,用于使能H.264 解码器的立即输出模式。一旦使能,H.264 解码器会忽略丢帧导致的帧序不连续情况,立即输出解码的图像。命令调用时机在decode_put_packet 之前。

3.2.2 reset​

reset 接口用于把解码器恢复为正常初始化后的状态。当用户发送最后一包 MppPacket 码流,并置上 EOS 标记送入解码器,解码器在处理完这最后一包数据之后会进入EOS 的状态,不再接收和处理码流,需要 reset 之后再才能再继续接收新的码流。

3.3 解码器使用要点​

解码器在使用过程中,需要注意的一些重要事项:

3.3.1 解码器单/多线程使用方式​

MPP 解码器的MPI 接口是线程安全的,可以在多线环境下使用。单线程工作模式如 mpi_dec_test 的demo 所示,多线程工作模式如 mpi_dec_mt_test 的 demo 所示。

图表 12 解码器单线程与多线程使用方式

3.3.2 图像内存分配以及交互模式​

解码器在解码图像时,需要获取内存空间以写入数据,在解码完成之后,这块内存空间需要交给用户使用,在用户使用完成之后要释放给解码器,在关闭解码器时要释放全部内存空间。在这种工作方式下,解码器与用户之间才可以形成零拷贝的数据交互。MPP 解码器支持三种内存分配以及与用户交互图像数据的模式:

模式一:纯内部分配模式图像内存直接从MPP 解码器内部分配,内存由解码器直接分配,用户得到解码器输出图像,在使用完成之后直接释放。

图表 13 解码器图像内存纯内部分配模式示意图在这种方式下,用户不需要调用解码器control 接口的MPP_DEC_SET_EXT_BUF_GROUP 命令,只需要在解码器上报info change 时直接调用control 接口的MPP_DEC_SET_INFO_CHANGE_READY命令即可。解码器会自动在内部进行内存分配,用户需要把获取到的每帧数据直接释放。

图表 14 解码器图像内存纯内部分配模式代码流程优点:过程简单,可以快速完成一个可用的demo,评估解码器的性能。缺点:1. 内存都是从解码器内部分配的,如果内存在解码器被销毁时还没有被释放,有可能出现内存泄漏或崩溃问题。2. 无法控制解码器的内存使用量。解码器可以不受限制地使用内存,如果码流输入的速度很快,用户又没有及时释放内存,解码器会很快消耗掉全部的可用内存。3. 实现零拷贝的显示比较困难,因为内存是从解码器内部分配的,不一定和用户的显示系统兼容。

模式二:半内部分配模式这种模式是mpi_dec_test demo 使用的默认模式。用户需要根据get_frame返回的MppFrame的buf_size来创建MppBufferGroup,并通过control 接口的MPP_DEC_SET_EXT_BUF_GROUP 配置给解码器。用户可以通过mpp_buffer_group_limit_config 接口来限制解码器的内存使用量。

图表 15 解码器图像内存半内部分配模式代码流程优点:过程简单,易上手,可以一定程度限制内存的使用。缺点:1. 内存空间的限制并不准确,内存的使用量不是 100%固定的,会有波动。2. 同样难于实现零拷贝的显示。

模式三:纯外部分配模式这种模式通过创建空的 external 模式的MppBufferGroup,从用户那里导入外部分配器分析的内存块文件句柄(一般是dmabuf/ion/drm)。在Android 平台上,Mediaserver 通过 gralloc 从 SurfaceFlinger获取显示用内存,把 gralloc 得到的文件句柄提交(commit)到 MppBufferGroup 里,再把MppBufferGroup 通过control 接口MPP_DEC_SET_EXT_BUF_GROUP 命令配置给解码器,然后MPP解码器将循环使用gralloc 得到的内存空间。

图表 16 解码器图像内存纯外部分配模式示意图图表 17 解码器图像内存纯外部分配模式代码流程优点:直接使用外部显示用的内存,容易实现零拷贝。缺点:1. 理解与使用较困难。2. 需要修改用户程序,一些用户程序的调用方式限制了纯外部分配方式的使用。纯外部分配模式使用时的注意事项:1. 如果图像内存分配是在解码器创建之前,需要有额外的方式来得到图像内存的大小。一般的YUV420 图像内存空间计算方法:图像像素数据:hor_stride * ver_stride * 3 / 2额外附加信息:hor_stride * ver_stride / 2 2. 内存块数需要考虑解码和显示的需求,如果内存块数分配得太少,可能会卡住解码器。H.264/H.265 这类参考帧较多的协议需要 20+内存块能保证一定能解码。其它协议需要 10+内存块能保证一定能解码。3. 如果在码流解码过程中发生了 info change,需要把已有的MppBufferGroup 进行 reset,再commit进新的图像缓存,同时外部的显示也需要相应调整。

3.4 编码器数据流接口​

编码器接口为用户提供了输入图像,输出码流的功能,接口函数为MppApi 结构体里的encode_put_frame 函数, encode_get_packet 函数和 encode 函数。这组函数提供了简洁的编码功能支持。

3.4.1 encode_put_frame​

接口定义MPP RET encode put frame(MppCtx ctx, MppFrame frame) _ _ _
输入参数ctx :MPP 解码器实例 frame :待输入的图像数据
返回参数运行错误码
功能输入frame 图像数据给ctx 指定的MPP 编码器实例

函数的工作模式由于编码器的输入图像一般都比较大,如果进行拷贝,效率会大幅度下降,所以编码器的输入函数需要等待编码器硬件完成对输入图像内存的使用,才能把输入函数返回,把使用后的图像归还给调用者。基于以上的考虑,encode_put_frame 是阻塞式函数,会把调用者阻塞住,直到输入图像使用完成,会一定程度上导致软硬件运行无法并行,效率下降。

拷贝与零拷贝输入编码器的输入不支持 CPU 分配的空间,如果需要支持编码CPU 分配的地址,需要分配 MppBuffer并把数据拷贝进去,这样做会很大程度影响效率。编码器更喜欢dmabuf/ion/drm 内存形式的输入,这样可以实现零拷贝的编码,额外的系统开销最小。

3.4.2 encode_get_packet​

接口定义MPP RET encode get packet(MppCtx ctx, MppPacket *packet) _ _ _
输入参数ctx :MPP 解码器实例 packet :用于获取MppPacket 实例的指针。
返回参数运行错误码
功能从ctx 指定的MPP 编码器实例里获取完成编码的packet 描述信息。

取头信息与图像数据以H.264 编码器为例,编码器的输出数据分为头信息(sps/pps)和图像数据(I/P slice)两部分,头信息需要通过control 接口的MPP_ENC_GET_EXTRA_INFO 命令获取,图像数据则是通过encode_get_packet 接口来获取。获取的时机是在 control 接口的SET_RC_CFG/SET_PREP_CFG/SET_CODEC_CFG 参数配置命令完成之后。在参数配置命令调用时,编码器会进行各个参数的更新,在更新全部完成之后,调用 MPP_ENC_GET_EXTRA_INFO 获取到的头信息才是最新的。

H.264 编码器输出码流的格式目前硬件固定输出带 00 00 00 01 起始码的码流,所以encode_get_packet 函数获取到码流都是带有 00 00 00 01 起始码。如果需要去掉起始码,可以从起始码之后的地址进行拷贝。

码流数据的零拷贝由于使用encode_put_frame 和 encode_get_packet 接口时没有提供配置输出缓存的方法,所以使用encode_get_packet 时一定会进行一次拷贝。一般来说,编码器的输出码流相对于输入图像不算大,数据拷贝可以接受。如果需要使用零拷贝的接口,需要使用 enqueue/dequeue 接口以及MppTask 结构。

3.4.3 encode​

暂未实现

3.5 编码器控制接口​

编码器与解码器不同,需要用户进行一定的参数配置。编码器需要用户通过 control 接口配置编码器配置信息之后才可以进行编码工作。

3.5.1 control 与 MppEncCfg​

MPP 推荐使用封装后的 MppEncCfg 结构通过control 接口的MPP_ENC_SET_CFG/MPP_ENC_GET_CFG 命令来进行编码器信息配置。

由于编码器可配置的选项与参数繁多,使用固定结构体容易出现接口结构体频繁变化,导致接口二进制兼容性无法得到保证,版本管理复杂,极大增加维护量。

为了缓解这个问题MppEncCfg 使用(void *)作为类型,使用<字符串-值>进行key map 式的配置,函数接口分为s32/u32/s64/u64/ptr,对应的接口函数分为 set 与 get 两组,如下:

MPP_RET mpp_enc_cfg_set_s32(MppEncCfg cfg, const char *name, RK_S32 val);
MPP_RET mpp_enc_cfg_set_s64(MppEncCfg cfg, const char *name, RK_S64 val);
MPP_RET mpp_enc_cfg_set_u64(MppEncCfg cfg, const char *name, RK_U64 val);
MPP_RET mpp_enc_cfg_set_ptr(MppEncCfg cfg, const char *name, void *val);
MPP_RET mpp_enc_cfg_get_s32(MppEncCfg cfg, const char *name, RK_S32 *val);
MPP_RET mpp_enc_cfg_get_u32(MppEncCfg cfg, const char *name, RK_U32 *val);
MPP_RET mpp_enc_cfg_get_s64(MppEncCfg cfg, const char *name, RK_S64 *val);
MPP_RET mpp_enc_cfg_get_u64(MppEncCfg cfg, const char *name, RK_U64 *val);
MPP_RET mpp_enc_cfg_get_ptr(MppEncCfg cfg, const char *name, void **val);

字符串一般用[类型:参数]的方式进行定义,可支持的字符串与参数类型如下:

参数字串接 口实际类型描述说明
rc:modeS32MppEncRcMode表示码率控制模式,目前支持CBR 和VBR 两种: CBR 为Constant Bit Rate,固定码率模式。 在固定码率模式下,目标码率起决定性作用。 VBR 为Variable Bit Rate,可变码率模式。 在可变码率模式下,最大最小码率起决定性作用。 FIX QP 为固定QP 模式,用于调试和性能评估。 _
rc:bps target _S32RK S32 _表示CBR 模式下的目标码率。
rc:bps max _S32RK S32 _表示VBR 模式下的最高码率。
rc:bps min _S32RK S32 _表示VBR 模式下的最低码率。
rc:fps in flex _ _S32RK S32 _表示输入帧率是否可变的标志位,默认为0。 为0 表示输入帧率固定,帧率计算方式为 fps in num/fps in denorm,可以表示分数帧率。 _ _ _ _ 为1 表示输入帧率可变,可变帧率的情况下,帧率不固 定,对应的码率计算与分配的规则变为按实际时间进行 计算。
rc:fps in flex _ _S32RK S32 _表示输入帧率是否可变的标志位,默认为0。 为0 表示输入帧率固定,帧率计算方式为 fps in num/fps in denorm,可以表示分数帧率。 _ _ _ _ 为1 表示输入帧率可变,可变帧率的情况下,帧率不固 定,对应的码率计算与分配的规则变为按实际时间进行 计算。
rc:fps in num _ _S32RK S32 _表示输入帧率分数值的分子部分,如为0 表示默认30fps。
rc:fps in denorm _ _S32RK S32 _表示输入帧率分数值的分母部分。如为0 表示为1
rc:fps out flex _ _S32RK S32 _表示输出帧率是否可变的标志位,默认为0。 为0 表示输出帧率固定,帧率计算方式为 fps out num/fps out denorm,可以表示分数帧率。 _ _ _ _ 为1 表示输出帧率可变,可变帧率的情况下,帧率不固 定,对应的码流输出时间按实际时间进行计算。
rc:fps out num _ _S32RK S32 _表示输出帧率分数值的分子部分,如为0 表示默认30fps。
rc:fps out denorm _ _S32RK S32 _表示输出帧率分数值的分母部分。如为0 表示为1
rc:gopRK S32 _表示Group Of Picture,即两个I 帧之间的间隔,含义如 下。 0 – 表示只有一个I 帧,其他帧均为P 帧 1 – 表示全为I 帧 2 – 表示序列为I P I P I P… 3 – 表示序列为I P P I P P I P P… 一般情况下,gop 选择为输入帧率的整数倍。
rc:max reenc times _ _U32RK U32 _一帧图像最大重编码次数。
prep:widthS32RK S32 _表示输入图像水平方向像素数,单位为像素个数。
prep:heightS32RK S32 _表示输入图像垂直方向像素数,单位为像素个数。
prep:hor stride _S32RK S32 _表示输入图像垂直方向相邻两行之间的距离,单位为 byte 数。
prep:ver stride _S32RK S32 _表示输入图像分量之间的以行数间隔数,单位为1。
prep:formatS32MppFrameFormat表示输入图像色彩空间格式以及内存排布方式。
prep:colorS32MppFrameColorSpace表示输入图像数据彩色空间范围。
prep:rangeS32MppFrameColorRange表示输入图像是full range 还是limit range
prep:rotationS32MppEncRotationCfg表示输入图像旋转属性,默认为0,不旋转。
prep:mirroringS32RK S32 _表示输入图像镜像属性,默认为0,不做镜像。
codec:typeS32MppCodingType表示MppEncCodecCfg 对应的协议类型,需要与MppCtx 初始化函数mpp init 的参数一致。 _
h264:stream type _S32RK S32 _表示输入H.264 的码流格式类型,默认为0。 0 – 表示Annex B 格式,即加入00 00 00 01 的起始码。 1 – 表示没有起始码的格式。 目前内部固定为带00 00 00 01 起始码的格式。
h264:profileS32RK S32 _表示SPS 中的profile idc 参数: _ 66 – 表示Baseline profile。 77 – 表示Main profile。 100 – 表示High profile。
h264:levelS32RK S32 _表示SPS 中的level idc 参数,其中10 表示level 1.0: _ 10/11/12/13 – qcif@15fps / cif@7.5fps / cif@15fps / cif@30fps 20/21/22 – cif@30fps / half-D1@25fps / D1@12.5fps 30/31/32 – D1@25fps / 720p@30fps / 720p@60fps 40/41/42 – 1080p@30fps / 1080p@30fps / 1080p@60fps 50/51/52 – 4K@30fps / 4K@30fps / 4K@60fps 一般配置为level 4.1 即可满足要求。
h264:cabac en _S32RK S32 _表示编码器使用的熵编码格式:
0 – CAVLC,自适应变长编码。 1 – CABAC,自适应算术编码。
h264:cabac idc _S32RK S32 _表示协议语法中的cabac init idc,在cabac en 为1 时有 _ _ _ 效,有效值为0~2。
h264:trans8x8S32RK S32 _表示协议语法中的8x8 变换使能标志。 0 – 为关闭,在Baseline/Main profile 时固定关闭。 1 – 为开启,在High profile 时可选可启。
h264:const intra _S32RK S32 _表示协议语法中constrained intra pred mode 模式使能标 _ _ _ 志。 0 – 为关闭,1 – 为开启。
h264:scaling list _S32RK S32 _表示协议语法中scaling list matrix 模式 _ _ 0 – 为flat matrix,1 – 默认matrix。
h264:cb qp offset _ _S32RK S32 _表示协议语法中chroma cb qp offset 值。 _ _ _ 有效范围为[-12, 12]。
h264:cr qp offset _ _S32RK S32 _表示协议语法中chroma cr qp offset 值。 _ _ _ 有效范围为[-12, 12]。
h264:dblk disable _S32RK S32 _表示协议语法中deblock disable 标志,有效范围为[0, 2]。 _ 0 – deblocking 使能。 1 – deblocking 关闭。 2 – 在slice 边界关闭deblocking。
h264:dblk alpha _S32RK S32 _表示协议语法中deblock offset alpha 值。 _ _ 有效范围为[-6, 6]。
h264:dblk beta _S32RK S32 _表示协议语法中deblock offset beta 值。 _ _ 有效范围为[-6, 6]。
h264:qp init _S32RK S32 _表示初始QP 值,一般情况请勿配置。
h264:qp max _S32RK S32 _表示最大QP 值,一般情况请勿配置。
h264:qp min _S32RK S32 _表示最小QP 值,一般情况请勿配置。
h264:qp max i _ _S32RK S32 _表示最大I 帧QP 值,一般情况请勿配置。
h264:qp min i _ _S32RK S32 _表示最小I 帧QP 值,一般情况请勿配置。
h264:qp step _S32RK S32 _表示相临两帧之间的帧级QP 变化幅度。
h265:profileS32RK S32 _表示VPS 中的profile idc 参数: _ 固定为1,Main profile
h265:levelS32RK S32 _表示VPS 中的level idc 参数 _
h265:scaling list _S32RK S32 _表示协议语法中scaling list matrix 模式 _ _ 0 – 为flat matrix,1 – 默认matrix。
h265:cb qp offset _ _S32RK S32 _表示协议语法中chroma cb qp offset 值。 _ _ _ 有效范围为[-12, 12]。
h265:cr qp offset _ _S32RK S32 _表示协议语法中chroma cr qp offset 值。 _ _ _ 有效范围为[-12, 12]。
h265:dblk disable _S32RK S32 _表示协议语法中deblock disable 标志,有效范围为[0, 2]。 _ 0 – deblocking 使能。 1 – deblocking 关闭。 2 – 在slice 边界关闭deblocking。
h265:dblk alpha _S32RK S32 _表示协议语法中deblock offset alpha 值。 _ _ 有效范围为[-6, 6]。
h265:dblk beta _S32RK S32 _表示协议语法中deblock offset beta 值。 _ _ 有效范围为[-6, 6]。
h265:qp init _S32RK S32 _表示初始QP 值,一般情况请勿配置。
h265:qp max _S32RK S32 _表示最大QP 值,一般情况请勿配置。
h265:qp min _S32RK S32 _表示最小QP 值,一般情况请勿配置。
h265:qp max i _ _S32RK S32 _表示最大I 帧QP 值,一般情况请勿配置。
h265:qp min i _ _S32RK S32 _表示最小I 帧QP 值,一般情况请勿配置。
h265:qp step _S32RK S32 _表示相临两帧之间的帧级QP 变化幅度。
h265:qp delta ip _ _S32RK S32 _表示I 帧与前一个P 帧的QP 差值。
jpeg: quantS32RK S32 _表示JPEG 编码器使用的量化参数等级,编码器一共内置 了11 级量化系数表格,从0 到10,图像质量从差到好。
split:modeU32MppEncSplitMode表示H.264/H.265 协议的slice 切分模式 0– 不切分。 1– BY BYTE 切分slice 根据slice 大小。 _ 2– BY CTU 切分slice 根据宏块或CTU 个数。 _
split:argU32RK U32 _Slice 切分参数: 在BY BYTE 模式下,参数表示每个slice 的最大大小。 _ 在BY CTU 模式下,参数表示每个slice 包含的宏块或 _ CTU 个数。

其他的字符串与参数会进行后续扩展。

3.5.2 control 其他命令​

在定义于rk_mpi_cmd.h 文件的MpiCmd 枚举类型定义了 control 接口命令字,其中与编码器和编码过程相关的命令如下:

从MPP_ENC_CMD_BASE 到 MPP_ENC_CMD_END 之间的命令为编码器的 control 接口命令,其中配置命令的MPP_ENC_SET/GET_ CFG 已经做为基本的配置命令在3.5.1 进行了介绍。剩下的命令在下面进行简要的介绍,其中的命令与编码器硬件相关,只有部分硬件支持。

目前 MPP 支持的编码器硬件分为 vepu 系列和 rkvenc 系列,vepu 系列支持 H.264 编码,vp8 编码和jpeg 编码,配备于绝大多数RK 芯片中。rkvenc 系列只支持H.264 编码,目前只配备于RV1109/RV1126系统芯片,其支持的编码功能相对于vepu 系统会更多更强。

部分CMD 命令简要说明:MPP_ENC_SET_PREP_CFG/ MPP_ENC_GET_PREP_CFG MPP_ENC_SET_RC_CFG/ MPP_ENC_GET_RC_CFG MPP_ENC_SET_CODEC_CFG/ MPP_ENC_GET_CODEC_CFG废弃命令,为了前向兼容保留,不要使用

MPP_ENC_SET_IDR_FRAME无命令参数,用于向编码器请求 I 帖,编码器收到请求之后,会将待编码的下一帧编码为 IDR 帧。全部硬件都支持。

MPP_ENC_SET_OSD_LEGACY_0 MPP_ENC_SET_OSD_LEGACY_1 MPP_ENC_SET_OSD_LEGACY_2废弃命令,前向兼容用保留,不要使用

MPP_ENC_GET_HDR_SYNC/ MPP_ENC_GET_EXTRA_INFO用于单独获取码流头数据的命令,其中MPP_ENC_GET_EXTRA_INFO 为旧命令,不推荐使用。MPP_ENC_GET_HDR_SYNC 输入参数为MppPacket,需要外部用户分配好空间并封装为MppPacket再control 到编码器,control 接口调用返回时就完成了数据拷贝,线程安全。调用时机在编码器基本配置完成之后。需要用户手动释放之前分配的 MppPacket。MPP_ENC_GET_EXTRA_INFO 输入参数为MppPacket*,会获取编码器的内部MppPacket 来进行访问。调用时机在编码器基本配置完成之后。需要注意的是,这里得到的 MppPacket 是 MPP 的内部空间,不需要用户释放。由于在多线程情况下,MPP_ENC_GET_EXTRA_INFO 命令获取的MppPacket 有可能在读取时被其他control 修改,所以这个命令并不是线程安全的,仅做为旧vpu_api 的兼容用,不要再使用。

MPP_ENC_SET_SEI_CFG/MPP_ENC_GET_SEI_DATA废弃命令,前向兼容用保留,不要使用

MPP_ENC_PRE_ALLOC_BUFF/ MPP_ENC_SET_QP_RANGE/ MPP_ENC_SET_ROI_CFG/ MPP_ENC_SET_CTU_QP废弃命令,前向兼容用保留,不要使用

MPP_ENC_GET_RC_API_ALL获取 MPP 目前支持的码率控制策略API 信息的接口,输入 RcApiQueryAll*指针,在返回时填充好结构体内容

MPP_ENC_GET_RC_API_BY_TYPE获取指定MppCodingType 类型的所有码率控制策略API 信息,输入RcApiQueryType*指针并指定MppCodingType,在返回时会填充好结构体内容。

MPP_ENC_SET_RC_API_CFG注册外部码率控制策略API,输入RcImplApi*指针,该结构中的函数指针定义了码率控制策略插件的行为,注册之后的码率控制策略才可以被查询和激活使用。MPP_ENC_GET_RC_API_CURRENT返回当前使用的码率控制策略API 信息,输入 RcApiBrief*指针,在返回时会填充好结构体内容。

MPP_ENC_SET_RC_API_CURRENT激活指定名字的码率控制策略API,输入RcApiBrief*指针,编码器会搜索到RcApiBrief 中指定字符串名字的码率控制策略API 并激活为当前码率控制策略。

MPP_ENC_SET_HEADER_MODE/MPP_ENC_GET_HEADER_MODE配置和获取H.264/H.265 编码器的SEI 调试信息输出方式,调试用开关,以后会被环境变量取代,不要使用。

MPP_ENC_SET_SPLIT/ MPP_ENC_GET_SPLIT配置和获取H.264/H265 编码器的slice 切分配置信息,己被 MppEncCfg 中的 split:mode 和 split:arg取代,不要使用

MPP_ENC_SET_REF_CFG配置编码器高级参考帧模式,默认不需要配置,在需要配置长期参考帧,短期参考帧参考关系模式时使用,用于配置特殊的参考关系模式。高级接口,文档待完善。

MPP_ENC_SET_OSD_PLT_CFG用于配置rkvenc 系列硬件的 OSD 调色板,命令参数为 MppEncOSDPlt。一般只在编码开始时配置一次,全编码过程使用统一的调色板。仅RV1109/RV1126 系列支持。

MPP_ENC_GET_OSD_PLT_CFG用于获取rkvenc 系列硬件的 OSD 调色板,命令参数为 MppEncOSDPlt*。一般不使用

MPP_ENC_SET_OSD_DATA_CFG用于配置rkvenc 系列硬件的 OSD 数据,命令参数为 MppEncOSDData。需要每帧进行配置,每编码一帧之后需要重新配置。本命令被MppFrame 带的 MppMeta 中的KEY_OSD_DATA 进行替代,不再使用。

3.6 编码器使用要点​

3.6.1 输入图像的宽高与 stride​

编码器的输入图像宽高配置需要与图像数据在内存中的排布一致。以 1920x1080 大小的YUV420 图像编码为例,参考图表 7 MppFrame 重要参数说明的内容,假设有两种情况如下:

图表 18 编码器输入帧内存排布左图情况:亮度分量的宽度为 1920,高度为1080,亮度数据与色度数据不直接相接,中间有8 行的空行。这种情况下,水平stride 为1920,垂直stride 为 1088,应用需要以1920*1088*3/2 的大小分配空间并写入数据,使用宽 1920,高1080,水平stride 1920,垂直stride 1088 的配置即可以正常进行编码。

右图情况:亮度分量的宽度为 1920,高度为 1080,亮度数据与色度数据直接相接,中间没有空行。这种情况下,水平stride 为 1920,垂直stride 为 1080,但由于编码器对数据的访问是 16 对齐的,在读取亮度下边缘数据时会读取到色度部分,读取色度下边缘数据时会读取到色度数据之外的部分,需要用户开出额外的空间,这里的空间为 1920*1080*3/2+1920*4 的填充,才能保证编码器不出现访问未分配空间的情况。

3.6.2 编码器控制信息输入方式以及扩展​

编码器控制信息的输入方式分为两种:一种是全局性控制信息,如码率配置,宽高配置等,作用于整个编码器和编码过程;另一种是临时性控制信息,如每帧的OSD 配置信息,用户数据信息等,只作用于单帧编码过程。第一类控制信息主要通过 control 接口来进行配置,第二类控制信息主要是通过MppFrame 所带的MppMeta 接口来进行配置。今后对控制信息的扩展也会遵循这两种规则来进行扩展。

3.6.3 编码器输入输出流程​

目前编码器默认输入接口仅支持阻塞式调用,输出接口支持非阻塞式和阻塞式调用,默认为非阻塞式调用,有可能出现获取数据失败的情况,需要在使用中注意。

3.6.4 插件式自定义码率控制策略机制​

MPP 支持用户自己定义码率控制策略,码率控制策略接口 RcImplApi 定义了几个编码处理流程上的钩子函数,用于在指定环节插入用户自定义的处理方法。具体使用方法可以参考默认的 H.264/H.265码控策略实现(default_h264e/default_h265e 结构)。码控插件机制在MPP 中有预留,接口与流程都不算稳定,可以预见将来还会有不少调整,只建议有能力阅读理解代码,以及持续维护更新的用户使用这个机制,一般用户不建议使用。

第四章 MPP demo 说明​

MPP 的demo 程序变化比较快,以下说明仅供参考,具体情况以实际运行结果为准。Demo 的运行环境均以Android 32bit 平台为准。

4.1 解码器 demo​

解码器demo 为mpi_dec_test 系列程序,包括使用 decode_put_packet 和decode_get_frame 接口的单线程mpi_dec_test,多线程的 mpi_dec_mt_test 以及多实例的 mpi_dec_multi_test。

以下以Android 平台上的mpi_dec_test 为例进行使用说明。首先直接运行 mpi_dec_test,输入输出如下图:

mpi_dec_test 的命令参数中,输入文件(i),码流类型(t)为强制要求的参数,其他参数如输出文件(o),图像宽度(w)图像高度(h),解码帧数(n)等为可选参数,影响不大。后面的打印显示了MPP 库支持的coding 格式,支持MPEG2/4,H.263/4/5,VP8/9 的解码,id 后的数字为格式对应的-t 项后的参数值。参数值来源于OMX 的定义,HEVC 和AVS 的格式参数值与其他的格式参数值区别较大,需要留意。

然后以/sdcard/下的tennis200.h264 解码10 帧为例,对demo 和输出进行说明。运行的命令为:mpi_dec_test -t 7 -i /sdcard/tennis200.h264 -n 10 -t 7 表示是H.264 码流,-i 表示输入文件,-n 10 表示解码10 帧,如果一切正常,会得到如下的结果:打印的信息里包含了 MPP 库的版本信息:mpp version: aeb361f author: Herman Chen [cmake]: Remove static library VISIBILITY setting

mpp_rt 的内核分配器检测信息:I/mpp_rt ( 1249): found ion allocator I/mpp_rt ( 1249): NOT found drm allocator I/mpp_ion ( 1249): vpu_service iommu_enabled 1 I/mpp_ion ( 1249): using ion heap ION_HEAP_TYPE_SYSTEM表示找到了ion 分配器,没有找到 drm 分配器,内核设备的 iommu 使能,使用 ion 的系统heap。

I/mpi_dec_test( 1249): decode_get_frame get info changed found为mpi_dec_test 本身的打印,表示发现 MPP 解码器上报了 info change 事件。

I/mpi_dec_test( 1249): decoder require buffer w:h [1920:1080] stride [1920:1088]为mpi_dec_test 本身的打印,表示 MPP 解码器请求的图像内存情况。

I/mpi_dec_test( 1249): decode_get_frame get frame 0为mpi_dec_test 本身打印,表示解码器在正常解码和输出图像。

I/mpi_dec_test( 1249): test success为mpi_dec_test 本身打印,表示解码器完成了解码10 帧的功能。

解码器的demo 具体代码参见 test/mpi_dec_test.c。

4.2 编码器 demo​

编码器demo 为mpi_enc_test 系列程序,包括单线程的 mpi_enc_test,及多实例的 mpi_enc_multi_test。

以下以Android 平台上的mpi_enc_test 为例进行使用说明。首先直接运行 mpi_enc_test,输出如下图:

mpi_enc_test 的命令参数中,图像宽度(w)图像高度(h),码流类型(t)为强制要求参数,其他参数如输入文件(i),输出文件(o),编码帧数(n)等为可选参数。如果没有指定输入文件,mpi_enc_test会生成默认彩条图像进行编码。

以编码/sdcard 下的soccer_720x480_30fps.yuv 文件 10 帧为例,对demo 和输出进行说明。运行的命令为:mpi_enc_test -w 720 -h 480 -t 7 -i /sdcard/soccer_720x480_30fps.yuv -o /sdcard/out.h264 -n 10然后使用ls –l 查看输出的码流文件。

库以及环境相关的log 同解码器demo。

I/h264e_api( 1284): h264e_config MPP_ENC_SET_RC_CFG bps 1296000 [1215000 : 1377000]编码器的码率控制参数配置,目标比特率为1.3Mbps。

I/mpi_enc_test( 1284): test_mpp_run encoded frame 0 size 63711编码器运行编码一帧,以及输出的一帧码流大小情况。

I/mpi_enc_test( 1284): mpi_enc_test success total frame 10 bps 2941200编码器完成了10 帧的编码,这 10 帧的码率为 2.9Mbps。注,这里编码不足 30 帧,码率有偏差,如果编码30 帧,实际码率为 1.3Mbps。

编码器的demo 具体代码参见 test/mpi_enc_test.c,但目前编码器demo 使用的是enqueue/dequeue 接口模式,后续会进行修改。

4.3 实用工具​

MPP 提供了一些单元测试用的工具程序,这种程序可以对软硬件平台以及 MPP 库本身进行测试mpp_info_test用于读取和打印MPP 库的版本信息,在反馈问题时,可以把打印出来信息附上。mpp_buffer_test用于测试内核的内存分配器是否正常。mpp_mem_test用于测试C 库的内存分配器是否正常。mpp_runtime_test用于测试一些软硬件运行时环境是否正常。mpp_platform_test用于读取和测试芯片平台信息是否正常。

第五章 MPP 库编译与使用​

5.1 下载源代码​

MPP 源代码发布官方地址:https://github.com/rockchip-linux/mpp发布分支为release 分支,开发分支为develop 分支,默认为开发分支。下载命令:git clone https://github.com/rockchip-linux/mpp.git

5.2 编译​

MPP 源代码编译脚本为cmake,需要依赖2.8.12 以上的版本,建议使用2.8.12 版,使用高版本的cmake工具可能会产生较多的 warning。

5.2.1 Android 平台交叉编译​

编译Android 库需要使用 ndk 环境,默认脚本使用 android-ndk-r10d 进行编译。r10d ndk 的下载路径可以在源代码目录下的 build/android/ndk_links.md 文件里查找。

把下载好的ndk 解压到/home/pub/ndk/android-ndk-r10d,或者手动修改 build/android/目录下env_setup.sh 脚本的ANDROID_NDK 变量路径。

进入 build/android/arm/目录,运行 make-Android.bash 脚本生成编译用 Makefile,运行make –j16 进行编译。

5.2.2 Unix/Linux 平台编译​

先配置build/linux/arm/目录下 arm.linux.cross.cmake 文件里的工具链,再运行make-Makefiles.bash 脚本通过cmake 生成Makefile,最后运行make –j16 进行编译。MPP 也支持直接在开发板运行的 Debian 上编译。

第六章 常见问题 FAQ​

Q:aarch64 编译出错,报错为undefined reference to `__system_property_get'。A:这是google 64bit ndk 的问题,其libc.so 中缺少一些符号定义,问题情况参见:http://stackoverflow.com/questions/28413530/api-to-get-android-system-properties-is-removed-in-arm64-p latforms解决方法:MPP 中已经把对应的 libc.so 放到 build/android/aarch64/fix/目录下,把库拷贝到path_to_ndk/platforms/android-21/arch-arm64/usr/lib/路径下,重新编译即正常。

Q:运行时会打印如下这样的内核 log,是不是有问题?vpu_service_ioctl:1844: error: unknow vpu service ioctl cmd 40086c01 A:没有问题,mpp 对内核驱动有一些依赖,内核驱动有不同版本的接口,mpp 会进行多次尝试。如果遇到尝试失败会进行另外一种接口的尝试。这个打印是尝试失败时打印出来的,只会打印一次,可以忽略这个打印。

Q:MPP 运行异常如何分析?A:首先先分析错误日志,如果出现打开内核设备失败的log,需要先分析内核平台的视频编解码器硬件设备配置文件是否有便能,然后提交问题到 redmine 上,分析运行环境问题之后,再来分析MPP运行的内部问题。