使用MPP硬解码,同时适用解码H264和H265
在之前关于MPP的使用过程中,发现了一个非常让人费解的现象,同一套解码流程,即使在MPP初始化的时候配置了mpp_init的MppCodingType参数为对应类型,H265能正常解码,但是H264却只能解码关键帧,非关键帧解码出来以后画面是绿色的。通过多次尝试后问题得到了解决:
- 使用ffmpeg拉取rtsp流,讲读取到的AVPacket转换成MppPacket送入mpp解码器解码
- 初始化mpp解码器:
MppCodingType type = MPP_VIDEO_CodingAVC; //h264
//MppCodingType type = MPP_VIDEO_CodingHEVC; //h265
MPP_RET ret = MPP_OK;
ret = mpp_create(&ctx, &mpi);
if (MPP_OK != ret) {
qDebug() << "mpp_create failed!";
deInit(ctx, mpi);
}
ret = mpp_init(ctx, MPP_CTX_DEC, type);
if (MPP_OK != ret) {
qDebug() << "mpp_init failed!";
deInit(ctx, mpi);
}
- mpp解码核心:
RK_U32 pkt_done = 0; //是否送包成功
RK_U32 err_info = 0; //错误信息
RK_U32 discard_info = 0; //标志此帧是否该被丢弃,非0为该丢弃
MPP_RET ret = MPP_OK;
MppPacket MppPacket = NULL;
MppFrame frame = NULL;
RK_U32 nPutErrNum = 0; //送包出现错误次数
//将AVPacket转换成MppPacket
ret = mpp_packet_init(&MppPacket, av_packet->data, av_packet->size);
if(ret < 0){
char errstrs[32] = {0};
av_strerror(ret, errstrs, 32);
qDebug() << "mpp_packet_init error: " << errstrs;
av_packet_unref(av_packet);
av_packet_free(&av_packet);
return -1;
}
mpp_packet_set_pts(MppPacket, av_packet->pts);
do{
//送包入解码器
if (!pkt_done) {
ret = mpi->decode_put_packet(ctx, MppPacket);
if (MPP_OK != ret){
nPutErrNum ++;
char errstr[32] = {0};
av_strerror(ret, errstr, 32);
qDebug() << "decode_put_packet error: " << errstr;
}else{
pkt_done = 1;
}
}
RK_S32 times = 5;
do{
mpp_frame_init(&frame);
try_again:
//从解码器中获取MppFrame
ret = data.mpi->decode_get_frame(data.ctx, &frame);
if(MPP_OK != ret){
char errstr[32] = {0};
av_strerror(ret, errstr, 32);
qDebug() << "decode_get_frame error: " << errstr;
//获取帧超时则重试,超过5次则认为失败
if(MPP_ERR_TIMEOUT == ret){
if(times > 0){
times--;
msleep(2);
goto try_again;
}
mpp_err("decode_get_frame failed too much time\n");
mpp_frame_deinit(&frame);
break;
}
}
if (frame) //如果获取的帧有效
{
//只会在首次解码以及宽高变换时候产生一次
if (mpp_frame_get_info_change(frame)) {
RK_U32 width = mpp_frame_get_width(frame);
RK_U32 height = mpp_frame_get_height(frame);
RK_U32 hor_stride = mpp_frame_get_hor_stride(frame);
RK_U32 ver_stride = mpp_frame_get_ver_stride(frame);
RK_U32 buf_size = mpp_frame_get_buf_size(frame);
qDebug() << "decode_get_frame get info changed found!";
qDebug() << QString("decoder require buffer w:h [%1:%2], stride [%3:%4], buf_size %5")
.arg(width).arg(height).arg(hor_stride).arg(ver_stride).arg(buf_size);
ret = mpp_buffer_group_get_internal(&data.frm_grp, MPP_BUFFER_TYPE_DRM);
if (ret) {
mpp_err("get mpp buffer group failed ret %d\n", ret);
break;
}
//为mpp解码器设置外部缓冲组
mpi->control(ctx, MPP_DEC_SET_EXT_BUF_GROUP, data.frm_grp);
//通知解码器可以开始解码
mpi->control(ctx, MPP_DEC_SET_INFO_CHANGE_READY, NULL);
}else{
//mpp_frame_get_discard判断当前帧是否应该被丢弃
//非零,则表示该帧应该被丢弃;如果返回值为零,则表示该帧应该被保留
err_info = mpp_frame_get_errinfo(frame);
discard_info = mpp_frame_get_discard(frame);
if(err_info) {
char errstr[32] = {0};
av_strerror(mpp_frame_get_errinfo(frame), errstr, 32);
qDebug() << "mpp_frame_get_errinfo: " << errstr;
}
if(discard_info) {
char errstr[32] = {0};
av_strerror(mpp_frame_get_discard(frame), errstr, 32);
qDebug() << "mpp_frame_get_discard: " << errstr;
mpp_frame_deinit(&frame);
break;
}
//-0.5到-4倍速,每个GOP的每一帧都解码,将一个GOP解码得到的MppFrame暂存到一个QList,再从后到先取出显示即是倒放
if(m_pPlaybackThread->getRate() < 0 and m_pPlaybackThread->getRate() > -8.0f){
appendTempFrame(frame);
break;
}else{
//0.5 to 16倍速本来就是正序
// -8 to -16只解关键帧,且ffmpeg读包的时候就是反向seek关键帧,所以送进解码器解码的时候已经是倒序
m_lstBackFrames.append(frame);
lastframePts = mpp_frame_get_pts(frame);
break;
}
}
} else {
mpp_frame_deinit(&frame);
usleep(3000);
break;
}
//这个while非常重要,如果记录此while的循环次数,
//会发现如果没有上面的usleep(3000),会循环非常多次才能获取有效的MppFrame
}while(1);
if (pkt_done)
break;
if(nPutErrNum > 5) //最多送包5次,避免死循环
break;
usleep(3000); // 取到的帧无效或者送包失败,过3毫秒再进行下一次取帧
}while(1);
if(av_packet) {
av_packet_unref(av_packet);
av_packet_free(&av_packet);
}
if(MppPacket) {
mpp_packet_deinit(&MppPacket);
}
return ret;
- 在我出现文章开始描述的那种情况的时候,是因为我解码的时候,没有使用两个while循环,不管是送包还是从解码器取帧都只尝试了一次,失败了就不进行下一步。然而这样操作,解码H265的时候行得通,解码H264的时候就只能解关键帧。谨以此文记录这个困扰我许久让我百思不得其解的问题的解决方式。