从FFmpeg源码去解决IJKPlayer直播花屏问题

IJKPlayer是一个非常优秀的播放器,支持rtmp、rtsp、http等协议直播,也支持Android、iOS跨平台使用。我在使用IJKPlayer做rtsp直播时,发现分辨率在达到1080P甚至是4K时,由于数据量比较大,有时出现花屏,影响用户体验。那么,我们需要做的是避开花屏,增大拉流缓冲区防止溢出、丢掉不完整帧、不渲染解码出错帧。

一、增大拉流缓冲区

我们默认是使用udp去拉流,在udp.c文件定义缓冲区大小为UDP_MAX_PKT_SIZE,默认值是65536。在这里,我们把它扩大10倍,改为65536*10。这样能够更大程度保证,在高分辨率时,拉流缓冲区不溢出。

二、丢掉不完整帧

现在主流是h264编解码,所以拉流时会经历rtp拆包、h264解析、h264组帧过程。在这些过程中,如果出现丢包,往往会导致视频帧不完整,FFmpeg也会打印error日志。我们需要在这些出错环节作帧错误标记,用于渲染视频帧时作判断,如果发生帧错误,丢掉整个GOP的帧。下面是出现花屏时,根据FFmpeg打印的日志去寻找对应文件。

1、rtp解析出错

在rtpdec.c文件的rtp_parse_queued_packet方法里,有missed %d packets的日志,在这里把帧错误标记为1。

if (!has_next_packet(s)){
        av_log(s->ic, AV_LOG_WARNING,
               "RTP: missed %d packets\n", s->queue->seq - s->seq - 1);
        //the flag of error frame
        frame_err = 1;
}

2、在h264_parser.c文件的parse_nal_units方法,有non-existing PPS、non-existing SPS、missing pictures日志,前两个是由于没有找到SPS、PPS,后面的是数据包不完整提示,在这里作帧错误标记。

if (!p->ps.pps_list[pps_id]) {
    av_log(avctx, AV_LOG_ERROR,"non-existing PPS %u referenced\n", pps_id);
    goto fail;
}
if (!p->ps.sps_list[p->ps.pps->sps_id]) {
    av_log(avctx, AV_LOG_ERROR,"non-existing SPS %u referenced\n", p->ps.pps->sps_id);
    goto fail;
}
/* didn't find a picture! */
av_log(avctx, AV_LOG_ERROR, "missing picture in access unit with size %d\n", buf_size);

3、在h264_parse.c文件的ff_h264_check_intra4x4_pred_mode方法中,有top block unavailable、left block unavailable日志,在ff_h264_check_intra_pred_mode方法中,有out of range intra chroma等日志,需要作帧错误标记。

if (status < 0) {
    av_log(logctx, AV_LOG_ERROR,"top block unavailable for requested intra mode %d\n",status);
    return AVERROR_INVALIDDATA;
}
if (status < 0) {
    av_log(logctx, AV_LOG_ERROR,"left block unavailable for requested intra4x4 mode %d\n",status);
    return AVERROR_INVALIDDATA;
}
if (mode > 3U) {
    av_log(logctx, AV_LOG_ERROR,"out of range intra chroma pred mode\n");
    return AVERROR_INVALIDDATA;
}

4、在h264_cavlc.c文件的decode_residual方法中,有corrupted macroblock、Invalid level prefix、negative number日志,在ff_h264_decode_mb_cavlc方法中,有mb_type %d in %c slice too large日志,需要作帧错误标记。

if(total_coeff > (unsigned)max_coeff) {
    av_log(h->avctx, AV_LOG_ERROR, "corrupted macroblock %d %d (total_coeff=%d)\n", sl->mb_x, sl->mb_y, total_coeff);
    return -1;
}
if(prefix > 25+3){
    av_log(h->avctx, AV_LOG_ERROR, "Invalid level prefix\n");
    return -1;
}
if(zeros_left<0){
    av_log(h->avctx, AV_LOG_ERROR, "negative number of zero coeffs at %d %d\n", sl->mb_x, sl->mb_y);
    return -1;
}
if(mb_type > 25){
    av_log(h->avctx, AV_LOG_ERROR, "mb_type %d in %c slice too large at %d %d\n", mb_type, av_get_picture_type_char(sl->slice_type), sl->mb_x, sl->mb_y);
    return -1;
}

5、在error_resilience.c文件的ff_er_add_slice方法中,有internal error日志,需要作帧错误标记。

if (start_i > end_i || start_xy > end_xy) {
    av_log(s->avctx, AV_LOG_ERROR,"internal error, slice end before start\n");
    return;
}

6、在h264_slice.c文件的decode_slice方法中,有error while decoding MB错误日志,,在h264_slice_header_parse方法中,有Frame num change from日志,需要作帧错误标记。

if (h->poc.frame_num != sl->frame_num) {
    av_log(h->avctx, AV_LOG_ERROR, "Frame num change from %d to %d\n",h->poc.frame_num, sl->frame_num);
    return AVERROR_INVALIDDATA;
}
if (ret < 0) {
    av_log(h->avctx, AV_LOG_ERROR,"error while decoding MB %d %d\n", sl->mb_x, sl->mb_y);
    //TODO:flag of decode frame error, add by xufulong
    decode_err = 1;
    er_add_slice(sl, sl->resync_mb_x, sl->resync_mb_y, sl->mb_x,sl->mb_y, ER_MB_ERROR);
    return ret;
 }

最后在ff_ffplay.c文件的read_thread方法中,判断有没帧错误,如果出现帧错误就不入队列。

//if frame has occured error, don't enqueue
if(!frame_err){
    packet_queue_put(&is->videoq, pkt);
}else {//release the packet when frame error during total GOP
    av_packet_unref(pkt);
}

三、不渲染解码出错帧

在ffplay_video_thread解码线程里,如果出现解码出错,作对应标记,不渲染错误帧。

       ret = get_video_frame(ffp, frame);

       if (ret == 0){//decode error
            //decode frame error, set the flag
            decode_err = 1;
            continue;
        }else if(ret == -1){//can't get raw frame this time
            continue;
        }else if (ret < -1){//other error
            goto the_end;
        }

        //decode error, don't display
        if(decode_err){
            continue;
        }

通过以上修改,基本上不会有花屏了。当然如果是h265流,需要去修改hevc对应的文件作标记。

发布了63 篇原创文章 · 获赞 179 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/u011686167/article/details/85149808