RKMPP库快速上手--MPP编码入门
Posted Geek.Fan
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RKMPP库快速上手--MPP编码入门相关的知识,希望对你有一定的参考价值。
首先,了解MPP编码之前,先了解一下MPP的MPI接口。
1、MPI接口结构
MPP设计的MPI接口,下面的图都来自于官方参考文档:
MppMem:C库malloc内存的封装;
MppBuffer:dmabuf内存的封装;
MppPacket:一维缓存封装,可以从MppMem、MppBuffer生成,用来表示码流数据;
MppFrame:二维帧数据封装,可以从MppMem、MppBuffer生成,用来表示图像数据;
MppMeta、MppTask:输入输出用任务的高级组合接口,可以指定输入输出方式;
使用MppPacket和MppFrame就可以完成一般的编解码工作。
以视频编码为例,图像输入端把图像数据和大小交给MppFrame,通过encode_put_frame输入,在输出端通过encode_get_packet得到编码后的码流MppPacket,就完成了一帧数据的编码。
2、MPI接口使用
MPI是MPP提供给用户的接口,通过C结构里的函数指针方式提供给用户,用户可以通过MPP上下文结构MppCtx与MPI接口结构MppApi组合实现编解码器。
如上图所示,mpp_create,mpp_init,mpp_destory操纵MppCtx接口,红框内是实现编码与解码的过程。红框内的函数调用分为编解码流程接口put/get_packet/frame和相关的control和reset接口。
3、MPP编码流程介绍
control编码配置命令:编码开始之前要进行一定的参数配置,参数设置通过control接口。一般需要配置三类信息:
码率控制方式(MPPEncRcCfg):通过命令MPP_ENC_RC_CFG配置;
输入控制配置(MppEncPrepCfg):通过命令MPP_ENC_SET_PREP_CFG配置;
协议控制配置(MppEncCodecCfg):通过命令MPP_ENC_SET_CODEC_CFG配置;
基本流程:
配置完mpp以后就可以通过mpi->encode_put_frame向编码器输入图片(注:图像的格式和数据对其有着严格要求,具体细则会在后续给出),再通过mpi->encode_get_packet获取编码好的包,再写入相应文件即可。
为了方便理解和使用,我将mpp的编码封装成了一个class,只需要在定义对象时给出编码器初始化的数据(包括输入图像的首地址、数据格式、宽高,输出视频流的编码格式、FPS以及输出文件路径),后续的使用则只需要通过“process_image(uint8_t *p)”接口向对象喂图片即可,编码器会自动将图像编码然后存入视频流文件。
编码器class定义头文件
其中结构体“MPP_ENC_DATA ”为编码器create和init时所需要的数据,class中定义了它的一个变量,这样整个编码器的配置都可以由这个MPP_ENC_DATA变量给出。
编码器在定义时需要向构造函数提供:输出文件路径,输入图像的首地址、宽高、数据格式,输出视频流的编码格式、FPS以及gop等配置信息。
init_mpp():
用于初始化mpp编码器,它会在构造函数中调用;
destroy_mpp():
用于销毁mpp编码器;
process_image(uint8_t *p):
为用户使用接口,输出图像的首地址,编码图像并存入文件;
read_yuv_image():
用于读取输入图像的内存中的数据,并且将其对齐成mpp所需要的按16位对齐的格式。
代码实现如下:
#ifndef ENCODER_H
#define ENCODER_H
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
#include <string.h>
#include <errno.h>
#include <rk_mpi.h>
#include "mpp_err.h"
#include "opencv2/opencv.hpp"
using namespace cv;
using namespace std;
#define MPP_ALIGN(x, a) (((x)+(a)-1)&~((a)-1))
struct MPP_ENC_DATA //编码所需要的数据
// global flow control flag
uint32_t frm_eos;
uint32_t pkt_eos;
uint32_t frame_count;
uint64_t stream_size;
// base flow context
MppCtx ctx;
MppApi *mpi;
MppEncPrepCfg prep_cfg;
MppEncRcCfg rc_cfg;
MppEncCodecCfg codec_cfg;
// input / output
MppBuffer frm_buf;
MppEncSeiMode sei_mode;
uint32_t width;
uint32_t height;
uint32_t hor_stride;
uint32_t ver_stride;
MppFrameFormat fmt = MPP_FMT_YUV422_YUYV;
MppCodingType type = MPP_VIDEO_CodingAVC;
uint32_t num_frames;
// resources
size_t frame_size;
int32_t gop = 60;
int32_t fps = 30;
int32_t bps;
FILE *fp_output;
;
class Encoder
public:
Encoder(char* outPutFileName, uint32_t width,uint32_t height,
MppFrameFormat fmt = MPP_FMT_YUV422_YUYV,
MppCodingType type = MPP_VIDEO_CodingAVC,
uint32_t fps = 30, uint32_t gop = 60);
~Encoder();
bool process_image(uint8_t *p);
private:
MPP_ENC_DATA mpp_enc_data;
MPP_RET read_yuv_image(uint8_t *buf, uint8_t *image, uint32_t width, uint32_t height,
uint32_t hor_stride, uint32_t ver_stride, MppFrameFormat fmt);
void init_mpp();
void destroy_mpp();
;
#endif // ENCODER_H
编码器class代码解读
构造函数:
//输入文件名、图像宽高、输入图像格式、输出视频流类型、fps、gop
Encoder::Encoder(char* outPutFileName, uint32_t width, uint32_t height,
MppFrameFormat fmt, MppCodingType type,
uint32_t fps, uint32_t gop)
memset(&mpp_enc_data, 0, sizeof(mpp_enc_data));
mpp_enc_data.width = width;
mpp_enc_data.height = height;
//mpp编码图像的行和列都是按16位对齐的,如果输出的行列不是16的整数,则需要在编码时将数据按照16位对齐。
//此函数就是为了得到行列补齐16整除的数据,比如行是30,通过MPP_ALIGN(30,16);的输出就是32;
mpp_enc_data.hor_stride = MPP_ALIGN(mpp_enc_data.width, 16);
mpp_enc_data.ver_stride = MPP_ALIGN(mpp_enc_data.height, 16);
mpp_enc_data.fmt = fmt;//MPP_FMT_BGR565;//MPP_FMT_YUV422_YUYV;
mpp_enc_data.type = type;
mpp_enc_data.fps = fps;
mpp_enc_data.gop = gop;
//不同的图像格式所占的内存大小和其长宽的关系是不同的
//所以要根据不同的输入图像格式为编码器编码开辟不同的内存大小,
******详情请见我的博客-“图像格式和编码方式整理”******
if (mpp_enc_data.fmt <= MPP_FMT_YUV420SP_VU)
mpp_enc_data.frame_size = mpp_enc_data.hor_stride * mpp_enc_data.ver_stride * 3/2;
else if (mpp_enc_data.fmt <= MPP_FMT_YUV422_UYVY)
mpp_enc_data.hor_stride *= 2;
mpp_enc_data.frame_size = mpp_enc_data.hor_stride * mpp_enc_data.ver_stride;
else
mpp_enc_data.frame_size = mpp_enc_data.hor_stride * mpp_enc_data.ver_stride * 4;
mpp_enc_data.fp_output = fopen(outPutFileName, "wb+");// 打开输出文件
init_mpp();//使用输入的配置初始化编码器
编码器初始化函数:
void Encoder::init_mpp()
MPP_RET ret = MPP_OK;
//开辟编码时需要的内存
ret = mpp_buffer_get(NULL, &mpp_enc_data.frm_buf, mpp_enc_data.frame_size);
if (ret)
printf("failed to get buffer for input frame ret %d\\n", ret);
goto MPP_INIT_OUT;
//创建 MPP context 和 MPP api 接口
ret = mpp_create(&mpp_enc_data.ctx, &mpp_enc_data.mpi);
if (ret)
printf("mpp_create failed ret %d\\n", ret);
goto MPP_INIT_OUT;
/*初始化编码还是解码,以及编解码的格式
MPP_CTX_DEC : 解码
MPP_CTX_ENC : 编码
MPP_VIDEO_CodingAVC : H.264
MPP_VIDEO_CodingHEVC : H.265
MPP_VIDEO_CodingVP8 : VP8
MPP_VIDEO_CodingVP9 : VP9
MPP_VIDEO_CodingMJPEG : MJPEG*/
ret = mpp_init(mpp_enc_data.ctx, MPP_CTX_ENC, mpp_enc_data.type);
if (ret)
printf("mpp_init failed ret %d\\n", ret);
goto MPP_INIT_OUT;
/*设置编码参数:宽高、对齐后宽高等参数*/
mpp_enc_data.bps = mpp_enc_data.width * mpp_enc_data.height / 8 * mpp_enc_data.fps;
//mpp_enc_data.bps = 4096*1024;
mpp_enc_data.prep_cfg.change = MPP_ENC_PREP_CFG_CHANGE_INPUT |
MPP_ENC_PREP_CFG_CHANGE_ROTATION |
MPP_ENC_PREP_CFG_CHANGE_FORMAT;
mpp_enc_data.prep_cfg.width = mpp_enc_data.width;
mpp_enc_data.prep_cfg.height = mpp_enc_data.height;
mpp_enc_data.prep_cfg.hor_stride = mpp_enc_data.hor_stride;
mpp_enc_data.prep_cfg.ver_stride = mpp_enc_data.ver_stride;
mpp_enc_data.prep_cfg.format = mpp_enc_data.fmt;
mpp_enc_data.prep_cfg.rotation = MPP_ENC_ROT_0;
ret = mpp_enc_data.mpi->control(mpp_enc_data.ctx, MPP_ENC_SET_PREP_CFG, &mpp_enc_data.prep_cfg);
if (ret)
printf("mpi control enc set prep cfg failed ret %d\\n", ret);
goto MPP_INIT_OUT;
/*设置编码码率、质量、定码率变码率*/
mpp_enc_data.rc_cfg.change = MPP_ENC_RC_CFG_CHANGE_ALL;
mpp_enc_data.rc_cfg.rc_mode = MPP_ENC_RC_MODE_VBR;
mpp_enc_data.rc_cfg.quality = MPP_ENC_RC_QUALITY_MEDIUM;
if (mpp_enc_data.rc_cfg.rc_mode == MPP_ENC_RC_MODE_CBR)
/* constant bitrate has very small bps range of 1/16 bps */
mpp_enc_data.rc_cfg.bps_target = mpp_enc_data.bps;
mpp_enc_data.rc_cfg.bps_max = mpp_enc_data.bps * 17 / 16;
mpp_enc_data.rc_cfg.bps_min = mpp_enc_data.bps * 15 / 16;
else if (mpp_enc_data.rc_cfg.rc_mode == MPP_ENC_RC_MODE_VBR)
if (mpp_enc_data.rc_cfg.quality == MPP_ENC_RC_QUALITY_CQP)
/* constant QP does not have bps */
mpp_enc_data.rc_cfg.bps_target = -1;
mpp_enc_data.rc_cfg.bps_max = -1;
mpp_enc_data.rc_cfg.bps_min = -1;
else
/* variable bitrate has large bps range */
mpp_enc_data.rc_cfg.bps_target = mpp_enc_data.bps;
mpp_enc_data.rc_cfg.bps_max = mpp_enc_data.bps * 17 / 16;
mpp_enc_data.rc_cfg.bps_min = mpp_enc_data.bps * 1 / 16;
/* fix input / output frame rate */
mpp_enc_data.rc_cfg.fps_in_flex = 0;
mpp_enc_data.rc_cfg.fps_in_num = mpp_enc_data.fps;
mpp_enc_data.rc_cfg.fps_in_denorm = 1;
mpp_enc_data.rc_cfg.fps_out_flex = 0;
mpp_enc_data.rc_cfg.fps_out_num = mpp_enc_data.fps;
mpp_enc_data.rc_cfg.fps_out_denorm = 1;
mpp_enc_data.rc_cfg.gop = mpp_enc_data.gop;
mpp_enc_data.rc_cfg.skip_cnt = 0;
ret = mpp_enc_data.mpi->control(mpp_enc_data.ctx, MPP_ENC_SET_RC_CFG, &mpp_enc_data.rc_cfg);
if (ret)
printf("mpi control enc set rc cfg failed ret %d\\n", ret);
goto MPP_INIT_OUT;
/*设置264相关的其他编码参数*/
mpp_enc_data.codec_cfg.coding = mpp_enc_data.type;
switch (mpp_enc_data.codec_cfg.coding)
case MPP_VIDEO_CodingAVC :
mpp_enc_data.codec_cfg.h264.change = MPP_ENC_H264_CFG_CHANGE_PROFILE |
MPP_ENC_H264_CFG_CHANGE_ENTROPY |
MPP_ENC_H264_CFG_CHANGE_TRANS_8x8;
/*
* H.264 profile_idc parameter
* 66 - Baseline profile
* 77 - Main profile
* 100 - High profile
*/
mpp_enc_data.codec_cfg.h264.profile = 77;
/*
* H.264 level_idc parameter
* 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
*/
mpp_enc_data.codec_cfg.h264.level = 40;
mpp_enc_data.codec_cfg.h264.entropy_coding_mode = 1;
mpp_enc_data.codec_cfg.h264.cabac_init_idc = 0;
mpp_enc_data.codec_cfg.h264.transform8x8_mode = 1;
break;
case MPP_VIDEO_CodingMJPEG :
mpp_enc_data.codec_cfg.jpeg.change = MPP_ENC_JPEG_CFG_CHANGE_QP;
mpp_enc_data.codec_cfg.jpeg.quant = 10;
break;
case MPP_VIDEO_CodingVP8 :
case MPP_VIDEO_CodingHEVC :
default :
printf("support encoder coding type %d\\n", mpp_enc_data.codec_cfg.coding);
break;
ret = mpp_enc_data.mpi->control(mpp_enc_data.ctx, MPP_ENC_SET_CODEC_CFG, &mpp_enc_data.codec_cfg);
if (ret)
printf("mpi control enc set codec cfg failed ret %d\\n", ret);
goto MPP_INIT_OUT;
/* optional */
mpp_enc_data.sei_mode = MPP_ENC_SEI_MODE_ONE_FRAME;
ret = mpp_enc_data.mpi->control(mpp_enc_data.ctx, MPP_ENC_SET_SEI_CFG, &mpp_enc_data.sei_mode);
if (ret)
printf("mpi control enc set sei cfg failed ret %d\\n", ret);
goto MPP_INIT_OUT;
if (mpp_enc_data.type == MPP_VIDEO_CodingAVC)
MppPacket packet = NULL;
ret = mpp_enc_data.mpi->control(mpp_enc_data.ctx, MPP_ENC_GET_EXTRA_INFO, &packet);
if (ret)
printf("mpi control enc get extra info failed\\n");
goto MPP_INIT_OUT;
/* get and write sps/pps for H.264 */
if (packet)
void *ptr = mpp_packet_get_pos(packet);
size_t len = mpp_packet_get_length(packet);
if (mpp_enc_data.fp_output)
fwrite(ptr, 1, len, mpp_enc_data.fp_output);
packet = NULL;
return;
MPP_INIT_OUT:
if (mpp_enc_data.ctx)
mpp_destroy(mpp_enc_data.ctx);
mpp_enc_data.ctx = NULL;
if (mpp_enc_data.frm_buf)
mpp_buffer_put(mpp_enc_data.frm_buf);
mpp_enc_data.frm_buf = NULL;
printf("init mpp failed!\\n");
以上是关于RKMPP库快速上手--MPP编码入门的主要内容,如果未能解决你的问题,请参考以下文章