整个编码分为mpp_create,mpp_init,
再通过mpp的接口mpi->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)”接口向对象喂图片即可,编码器会自动将图像编码然后存入视频流文件。
其中结构体“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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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
构造函数:
//输入文件名、图像宽高、输入图像格式、输出视频流类型、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 / [email protected] / cif@15fps / cif@30fps
* 20 / 21 / 22 - cif@30fps / half-D1@@25fps / [email protected]
* 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");
}
用户接口:图像编码函数
/****************************************************************************
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;
//获得开辟的内存的首地址
void *buf = mpp_buffer_get_ptr(mpp_enc_data.frm_buf);
//TODO: improve performance here?
//从输入图像的首地址开始读取图像数据,但是读取时会考虑16位对齐,即读取的长和宽都是16的整数倍。
//若图像一行或者一列不满16整数倍,则会用空数据补齐行和列
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;
}
//获得编码后的packet
ret = mpp_enc_data.mpi->encode_get_packet(mpp_enc_data.ctx, &packet);
if (ret)
{
printf("mpp encode get packet failed\n");
return true;
}
//获得编码后的数据长度和首地址,将其写入文件
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");
}
}
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;
}
从内存读取YUV图像函数
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;
//YUV格式的图像都是将YUV三个通道分为三部分存取,YYYYYY*****UUUUU*****VVVVV,所以在将其按照16位对齐copy时先将YUV三个通道的起始地址指定好。
uint8_t *buf_y = buf;
uint8_t *buf_u = buf_y + hor_stride * ver_stride; // NOTE: diff from gen_yuv_image
uint8_t *buf_v = buf_u + hor_stride * ver_stride / 4; // NOTE: diff from gen_yuv_image
uint32_t ptr = 0;
//然后按照不同的格式,按照16位对齐copy图像数据到buf下,不同格式读取方式不同。
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 : {
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/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;
}
总代码链接:https://download.csdn.net/download/qq_39839546/67314289
已设置成了0积分下载,欢迎大家下载分享。
后面的博客会讲解图像格式和存储方式、mpp程序编译、关于16位对齐方式等内容。