代码链接:https://download.csdn.net/download/qq_39839546/67314289
已设置成了0积分下载,欢迎大家下载分享。
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;
}