完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
相关推荐
2个回答
|
|
MPP编码入门(后附demo代码链接)
mpp编码流程介绍 整个编码分为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)”接口向对象喂图片即可,编码器会自动将图像编码然后存入视频流文件。 编码器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 #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 编码器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 %dn", 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 %dn", 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 %dn", 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 %dn", 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 %dn", 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 %dn", 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 %dn", 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 %dn", 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 failedn"); 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 failedn"); 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 failedn"); return true; } //获得编码后的packet ret = mpp_enc_data.mpi->encode_get_packet(mpp_enc_data.ctx, &packet); if (ret) { printf("mpp encode get packet failedn"); 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 %dn", 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 packetn"); } } 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; } |
|
|
|
你正在撰写答案
如果你是对答案或其他答案精选点评或询问,请使用“评论”功能。
249 浏览 1 评论
rk1126实现BT601输入,输入不带IIC接口的驱动程序
1916 浏览 0 评论
1577 浏览 1 评论
2851 浏览 3 评论
RK3568 Android11让系统ntp校准时间生效,需要设置些什么
3380 浏览 1 评论
浏览过的版块 |
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-8-28 02:08 , Processed in 0.693835 second(s), Total 73, Slave 56 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191