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编码入门的主要内容,如果未能解决你的问题,请参考以下文章

RKMPP库快速上手--MPP关键配置

RKMPP库快速上手--RKMPP功能及使用详解

RKMPP API安装使用总结

基于RK3399的MPP库实现的视频编码

海思Hi3519A MPP从入门到精通(一 系统概述)

各位熟悉stm32 的精英们,我想学习stm32单片机,如何快速入门,快速上手,求经验!!!