RK3399Pro-硬件编解码器MPP库快速上手--(三)MPP编码关键代码讲解

MPP关键配置(讲解在注释中)

代码链接:https://download.csdn.net/download/qq_39839546/67314289
已设置成了0积分下载,欢迎大家下载分享。

为了方便配置功能,我们一般将Mpp配置的关键参数定义为结构体:

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;//图像高
/*Stride:
  stride其实是图像处理中常用的概念,是关于数据对齐的一种概念。
  一行有 11 个像素(Width = 11), 对一个 32 位(每个像素 4 字节)的图像, Stride = 11 * 4 = 44.
  但还有个字节对齐的问题, 譬如:
  一行有 11 个像素(Width = 11), 对一个 24 位(每个像素 3 字节)的图像, Stride = 11 * 3 + 3 = 36.
  为什么不是 Stride = 33? 因为它是按 4 字节对齐的.
  根据上面道理, 我们可以手动计算 Stride 的值:
  1)Stride = 每像素占用的字节数(也就是像素位数/8) * Width;
  2)如果 Stride 不是 4 的倍数, 那么 Stride = Stride + (4 - Stride mod 4);
*/
	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;
	/*在视频编码中,GOP的意思是画面组,指定了intra-和inter-帧的顺序。
	GOP是一个编码视频流中的一组连续的画面。每一个编码的视频流都由连续的GOP组成。
	压缩的视频流中GOP相对独立,解码器解码新的GOP时需要之前的帧来解码后面的帧,
	GOP的存在也可以实现在视频中更快地定位。*/
	int32_t gop = 60;
	//帧率
	int32_t fps = 30;
	//码率
	int32_t bps;
	//输出文件头
	FILE *fp_output;
};

然后是编码的基本配置流程:

#define MPP_ALIGN(x, a)         (((x)+(a)-1)&~((a)-1))
	memset(&mpp_enc_data, 0, sizeof(mpp_enc_data));
	mpp_enc_data.width = width;
	mpp_enc_data.height = height;
	//获取宽高的stride	
	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;
	//不同的图像格式有着不同的数据量大小,
	//比如MPP_FMT_YUV420SP_VU格式的图像数据量大小就是其图像像素量的3/2;
	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+");// 打开输出文件

	//开辟编码所需的内存
	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;
	}

后续的代码按照官方配置来即可,需要改动则按照官方注释按需修改。

图像编码过程

MppPacket : 存放编码数据,例如264、265数据
MppFrame : 存放解码的数据,例如YUV、RGB数据
MppTask : 一次编码或者解码的session

编码就是push MppFrame,输出MppPacket;
解码就是push MppPacket,输出MppFrame;

MPI包含两套接口做编解码:
一套是简易接口, 类似 decode_put_packet / decode_get_frame 这样put/get即可
一套是高级接口, 类似 poll / enqueue/ dequeue 这样的对input output队列进行操作

编码流程:

bool Encoder::process_image(uint8_t *p)
{
	MPP_RET ret = MPP_OK;
    MppFrame frame = NULL;
    MppPacket packet = NULL;
    //获取编码器buffer的头指针
    void *buf = mpp_buffer_get_ptr(mpp_enc_data.frm_buf);
	
    //将已有图像数据按16位对齐拷贝至编码器buffer
	read_yuv_image((uint8_t *)buf,  p,  mpp_enc_data.width, mpp_enc_data.height,
                       mpp_enc_data.hor_stride, mpp_enc_data.ver_stride, mpp_enc_data.fmt);
    ret = mpp_frame_init(&frame);
    if (ret)
    {
    	printf("mpp_frame_init failed\n");
    	return true;
    }

    mpp_frame_set_width(frame, mpp_enc_data.width);
    mpp_frame_set_height(frame, mpp_enc_data.height);
    mpp_frame_set_hor_stride(frame, mpp_enc_data.hor_stride);
    mpp_frame_set_ver_stride(frame, mpp_enc_data.ver_stride);
    mpp_frame_set_fmt(frame, mpp_enc_data.fmt);
    mpp_frame_set_buffer(frame, mpp_enc_data.frm_buf);
    mpp_frame_set_eos(frame, mpp_enc_data.frm_eos);
	//输入图像
    ret = mpp_enc_data.mpi->encode_put_frame(mpp_enc_data.ctx, frame);
    if (ret)
    {
    	printf("mpp encode put frame failed\n");
    	return true;
    }
	//获得编码后的数据包
	//注意:有些时候编码尚未完成取包会得到空包,但是却不会报错
	//这时需要在此处建立一个循环等待编码完成再取包
	//若直接跳过,下次再调用mpp_enc_data.mpi->encode_put_frame(mpp_enc_data.ctx, frame)时
	//会因为上一帧数据没有取走而卡住。
    ret = mpp_enc_data.mpi->encode_get_packet(mpp_enc_data.ctx, &packet);
    if (ret)
    {
    	printf("mpp encode get packet failed\n");
    	return true;
    }

REGET:
    if (packet)
    {
    	// write packet to file here
    	void *ptr   = mpp_packet_get_pos(packet);
    	size_t len  = mpp_packet_get_length(packet);

    	mpp_enc_data.pkt_eos = mpp_packet_get_eos(packet);

    	if (mpp_enc_data.fp_output)
    		fwrite(ptr, 1, len, mpp_enc_data.fp_output);
    	mpp_packet_deinit(&packet);

    	//printf("encoded frame %d size %d\n", mpp_enc_data.frame_count, len);
    	mpp_enc_data.stream_size += len;
    	mpp_enc_data.frame_count++;

    	if (mpp_enc_data.pkt_eos)
    	{
    		printf("found last packet\n");
    	}
    }
    else 
    	goto REGET;//避免上述的编码阻塞情况

    if (mpp_enc_data.num_frames && mpp_enc_data.frame_count >= mpp_enc_data.num_frames)
    {
    	printf("encode max %d frames", mpp_enc_data.frame_count);
    	return false;
    }

    if (mpp_enc_data.frm_eos && mpp_enc_data.pkt_eos)
    	return false;

    return true;
}

图像拷贝函数:

在mpp中,编码器输入图像是必须严格按照16位对齐的,所以如果我们自己的图像宽高所占的数据位不是16的整数。就需要对图像数据重新整理,按行将图像拷贝到我们在mpp初始化时申请的内存中,每拷贝一行后在当前行后增加空数据,使行的数据长度为16的整数。在拷贝完成所有行后还需要再末尾增加空行,使得行数满足16的整数,这就是mpp的16位对齐方法。

MPP_RET Encoder::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)
{
    MPP_RET ret = MPP_OK;
    uint32_t read_size;
    uint32_t row = 0;
    //获得即将获得数据的buffer中,Y分量存储的首地址
    uint8_t *buf_y = buf;
    //U分量存储的首地址
    uint8_t *buf_u = buf_y + hor_stride * ver_stride; // NOTE: diff from gen_yuv_image
    //V分量存储的首地址
    uint8_t *buf_v = buf_u + hor_stride * ver_stride / 4; // NOTE: diff from gen_yuv_image
    switch (fmt) {
    case MPP_FMT_YUV420SP : {
        for (row = 0; row < height; row++) {
			memcpy(buf_y + row * hor_stride, image,width);
			image += width;
        }
        for (row = 0; row < height / 2; row++) {
			memcpy(buf_u + row * hor_stride, image, width);
			image += width;           
        }
    } break;
    以此图像格式为例,其他图像格式的情况类似。
    case MPP_FMT_YUV420P : {
    	//在已有图像中,按行拷贝至buffer
        for (row = 0; row < height; row++) {
        	//buffer每次拷贝进width个数据,但是指针却要前进hor_stride个位置,这就是对齐的过程
			memcpy(buf_y + row * hor_stride, image, width);
			//每拷贝一行,就要按照让数据指针增加width,也就是到下一行的首地址。
			image += width;
        }
        for (row = 0; row < height / 2; row++) {
            memcpy(buf_u + row * hor_stride/2, image, width/2);
			image += width/2;
        }
        for (row = 0; row < height / 2; row++) {
			memcpy(buf_v + row * hor_stride/2, image, width/2);
			image += width/2;
        }
    } break;
    case MPP_FMT_ARGB8888 : {
        for (row = 0; row < height; row++) {
			memcpy(buf_y + row * hor_stride*4, image, width*4);
			image += width*4;
        }
    } break;
    case MPP_FMT_YUV422_YUYV :
    case MPP_FMT_YUV422_UYVY : {
        for (row = 0; row < height; row++) {
			memcpy(buf_y + row * hor_stride, image, width*2);
			image += width*2;
        }
    } break;
    default : {
        cout << "read image do not support fmt "<< endl;
        ret = MPP_ERR_VALUE;
    } break;
    }

err:
    return ret;
}

你可能感兴趣的:(RK3399入门,深度学习,计算机视觉,图像处理)