ijkplayer-Frame Dropping Strategy Deep Analysis

Keywords: less

1. The test takes a video and finds that playing with ijk player does not feel the same as with system player. Playing with ijk player feels like playing Page Carton is a bit smoother than with system player.

Let's take a look at this problem. The reason for this problem is really simple. Because I set the frame loss value to 5, changing to 1 will make the two players feel similar in experience.(You can see the difference by losing 5 frames of the human eye!).

2. Play a 4k (30fps) video on the machine of Goldman 660, but it can't play properly. The decoded frame in one second is only 20 frames, and the actual play is only 4 frames.Causes picture to get stuck and audio and video to get out of sync.

It was later discovered that because of this video, many frames are decoded slowly on the highway machine, resulting in video being slower than audio. When hard decoding loses frames, it is judged that the video has been slower than audio, resulting in video losing frames all the time, thus appearing as Karton.

Frame Dropping Principle

First, you need to understand where frames need to be dropped and what frames need to be dropped.

Frame dropping can drop the frame before decoding or the frame after decoding.

If you want to lose the I frame, you need to lose the whole gop to p revent the flower screen.

The decoded frames can be discarded without regard to the frame type, since the frames are all decoded data, such as yuv, which directly determines whether the audio and video are discarded out of sync.

1. Frame Dropping Design in ffplay

Let's look at the design of frame loss in ffplay: ffplay loses decoded video frames

In the video_thread decoding thread, we can see that the get_video_frame function is mainly used to decode and get the decoded data avframe, and then to detect and determine frame dropouts.

static int get_video_frame(VideoState *is, AVFrame *frame)
{
    int got_picture;
    //Decode to get decoded data avframe
    if ((got_picture = decoder_decode_frame(&is->viddec, frame, NULL)) < 0)
        return -1;

    if (got_picture) {
        double dpts = NAN;
        
        if (frame->pts != AV_NOPTS_VALUE)
            dpts = av_q2d(is->video_st->time_base) * frame->pts;//The pts of the video are converted to ms, which is the current progress time
		//Video aspect ratio
        frame->sample_aspect_ratio = av_guess_sample_aspect_ratio(is->ic, is->video_st, frame);
        //Number of frames dropped is greater than 0 and synchronization is not per video
        if (framedrop>0 || (framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) {
        
            if (frame->pts != AV_NOPTS_VALUE) {
               //frame_last_filter_delay defaults to 0
               //diff less than 0, video slower than audio, frame loss
               //diff greater than 0, video faster than audio, no frame loss
                double diff = dpts - get_master_clock(is);
                // AV_NOSYNC_THRESHOLD: Synchronization threshold.If the error is too large, no correction is made and no frame is lost to synchronize
                if (!isnan(diff) && fabs(diff) < AV_NOSYNC_THRESHOLD &&
                    diff - is->frame_last_filter_delay < 0 &&
                    is->viddec.pkt_serial == is->vidclk.serial &&
                    is->videoq.nb_packets) {
                    is->frame_drops_early++;
                    av_frame_unref(frame);//Frame Drop
                    got_picture = 0;
                }
            }
        }
    }

    return got_picture;
}

2.Frame Dropping Design in ijk

In ijk, frame dropouts are also decoded video frames, which are divided into hard-decoded frame dropouts and soft-decoded frame dropouts.

2.1. Design of frame loss in hard decoding

In the ffpipenode_android_mediacodec_vdec.c file,
The func_run_sync function is mainly used to handle the implementation logic of the entire hard decode.

/**
*Hard Decode Processing Flow
**/
static int func_run_sync(IJKFF_Pipenode *node)
{
    JNIEnv                *env      = NULL;
    IJKFF_Pipenode_Opaque *opaque   = node->opaque;
    FFPlayer              *ffp      = opaque->ffp;
    VideoState            *is       = ffp->is;
    Decoder               *d        = &is->viddec;
    PacketQueue           *q        = d->queue;
    int                    ret      = 0;
    int                    dequeue_count = 0;
    AVFrame               *frame    = NULL;
    int                    got_frame = 0;
    AVRational             tb         = is->video_st->time_base;
    AVRational             frame_rate = av_guess_frame_rate(is->ic, is->video_st, NULL);
    double                 duration;
    double                 pts;

    if (!opaque->acodec) {
        return ffp_video_thread(ffp);
    }

    if (JNI_OK != SDL_JNI_SetupThreadEnv(&env)) {
        ALOGE("%s: SetupThreadEnv failed\n", __func__);
        return -1;
    }

    frame = av_frame_alloc();
    if (!frame)
        goto fail;
	//Create data queue thread enqueue_thread_func
    opaque->enqueue_thread = SDL_CreateThreadEx(&opaque->_enqueue_thread, enqueue_thread_func, node, "amediacodec_input_thread");
    if (!opaque->enqueue_thread) {
        ALOGE("%s: SDL_CreateThreadEx failed\n", __func__);
        ret = -1;
        goto fail;
    }
	//Cyclic Pull Decoded Data
    while (!q->abort_request) {
        int64_t timeUs = opaque->acodec_first_dequeue_output_request ? 0 : AMC_OUTPUT_TIMEOUT_US;
        got_frame = 0;
	    //Hard Unscramble Get frame
        ret = drain_output_buffer(env, node, timeUs, &dequeue_count, frame, &got_frame);
        if (opaque->acodec_first_dequeue_output_request) {
            SDL_LockMutex(opaque->acodec_first_dequeue_output_mutex);
            opaque->acodec_first_dequeue_output_request = false;
            SDL_CondSignal(opaque->acodec_first_dequeue_output_cond);
            SDL_UnlockMutex(opaque->acodec_first_dequeue_output_mutex);
        }
		//Data pull error
        if (ret != 0) {
            ret = -1;
            if (got_frame && frame->opaque) //release buffer false notifies MediaCodec to discard this frame
            {
                SDL_VoutAndroid_releaseBufferProxyP(opaque->weak_vout, (SDL_AMediaCodecBufferProxy **)&frame->opaque, false);
            }
            goto fail;
        }
        if (got_frame) {
            duration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational){frame_rate.den, frame_rate.num}) : 0);
            pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);
		    //Set the frame loss greater than 0 or the number of frames lost is not equal to 0 and the main clock is not a video clock
            if (ffp->framedrop > 0 || (ffp->framedrop && ffp_get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) {
                ffp->stat.decode_frame_count++;//Decoded Frame Count
                if (frame->pts != AV_NOPTS_VALUE) {
                    double dpts = pts;//Such as a frame of video pts
                    double diff = dpts - ffp_get_master_clock(is);//Difference between video and audio frames (if the main clock is audio)
					//frame_last_filter_delay This time is 0, so diff is greater than 0, meaning that video is faster than audio, and no frame dropout is required
					//If diff is less than 0, video is slower than audio and frames need to be dropped
					//Audio is faster than video, and the gap is less than the maximum synchronization value, beyond which no synchronization is done
					if (!isnan(diff) && fabs(diff) < AV_NOSYNC_THRESHOLD &&
                        diff - is->frame_last_filter_delay < 0 &&
                        is->viddec.pkt_serial == is->vidclk.serial &&
                        is->videoq.nb_packets) {//The packet sequence of the video frame in the decoder is equal to the sequence number in the video clock, and the video queue has video frames
                        is->frame_drops_early++;
                        is->continuous_frame_drops_early++;//Initial value is 0
                        if (is->continuous_frame_drops_early > ffp->framedrop) {//Initialize ontinuous_frame_drops_early to 0 if the continuous_frame_drops_early variable is greater than the number of frames dropped
                            is->continuous_frame_drops_early = 0;
                        } else {
                            ffp->stat.drop_frame_count++;//Frame Loss Plus
							//Frame Drop Rate = Number of Frames Dropped/Decoded
                            ffp->stat.drop_frame_rate = (float)(ffp->stat.drop_frame_count) / (float)(ffp->stat.decode_frame_count);
                            if (frame->opaque) {//Notify mediacodec, release, do not show
                                SDL_VoutAndroid_releaseBufferProxyP(opaque->weak_vout, (SDL_AMediaCodecBufferProxy **)&frame->opaque, false);
                            }
                            av_frame_unref(frame);//Release frame
                            continue;
                        }
                    }
                }
            }
			 //Frame queued, placed in decoded video queue, processed in video_refresh
            ret = ffp_queue_picture(ffp, frame, pts, duration, av_frame_get_pkt_pos(frame), is->viddec.pkt_serial);
            if (ret) {//Queuing error, release buffer false tells MediaCodec to discard this frame and not show it
                if (frame->opaque) 
                    SDL_VoutAndroid_releaseBufferProxyP(opaque->weak_vout, (SDL_AMediaCodecBufferProxy **)&frame->opaque, false);
              
            }
            av_frame_unref(frame);
        }
    }

fail:
    av_frame_free(&frame);
    opaque->abort = true;
    SDL_WaitThread(opaque->enqueue_thread, NULL);
    SDL_AMediaCodecFake_abort(opaque->acodec);
    if (opaque->n_buf_out) {
        free(opaque->amc_buf_out);
        opaque->n_buf_out = 0;
        opaque->amc_buf_out = NULL;
        opaque->off_buf_out = 0;
        opaque->last_queued_pts = AV_NOPTS_VALUE;
    }
    if (opaque->acodec) {
        SDL_VoutAndroid_invalidateAllBuffers(opaque->weak_vout);
        SDL_LockMutex(opaque->acodec_mutex);
        SDL_UnlockMutex(opaque->acodec_mutex);
    }
    SDL_AMediaCodec_stop(opaque->acodec);
    SDL_AMediaCodec_decreaseReferenceP(&opaque->acodec);
    ALOGI("MediaCodec: %s: exit: %d", __func__, ret);
    return ret;
#if 0 // Hard Solve Error, Soft Solve
fallback_to_ffplay:
    ALOGW("fallback to ffplay decoder\n");
    return ffp_video_thread(opaque->ffp);
#endif
}

2.2 Soft Decode Frame Loss Design

static int get_video_frame(FFPlayer *ffp, AVFrame *frame)
{
    VideoState *is = ffp->is;
    int got_picture;
	//Video Stream buffer Load Cache Statistics
    ffp_video_statistic_l(ffp);
	//Soft Dissolution Time-consuming Test
	//int64_t starttime  = av_gettime_relative();
	//Decode to get decoded data avframe
    if ((got_picture = decoder_decode_frame(ffp, &is->viddec, frame, NULL)) < 0)
        return -1;
	/*
	if(frame->key_frame) {//Keyframe Soft Decomposition Time-consuming Test
		int64_t endtime  = av_gettime_relative();
		int usetime = endtime - starttime;
		ALOGE("zmlruan>>>>>>usetime:%d",usetime);
	}*/
    if (got_picture) {//Decode successfully, get decoded data
        double dpts = NAN;

        if (frame->pts != AV_NOPTS_VALUE)
            dpts = av_q2d(is->video_st->time_base) * frame->pts;//The pts of the video are converted to ms, which is the current progress time
        //Video aspect ratio
        frame->sample_aspect_ratio = av_guess_sample_aspect_ratio(is->ic, is->video_st, frame);
        //Number of frames dropped is greater than 0 and synchronization is not per video
        if (ffp->framedrop>0 || (ffp->framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) {
            ffp->stat.decode_frame_count++;//Decode Number
            if (frame->pts != AV_NOPTS_VALUE) {//diff = Video timestamp minus the master clock timestamp (see audio timestamp here)
                double diff = dpts - get_master_clock(is); // AV_NOSYNC_THRESHOLD: Synchronization threshold.If the error is too large, no correction will be made
                if (!isnan(diff) && fabs(diff) < AV_NOSYNC_THRESHOLD &&
                    diff - is->frame_last_filter_delay < 0 &&
                    is->viddec.pkt_serial == is->vidclk.serial &&
                    is->videoq.nb_packets) {
                    is->frame_drops_early++;
                    is->continuous_frame_drops_early++;
                    if (is->continuous_frame_drops_early > ffp->framedrop) {
                        is->continuous_frame_drops_early = 0;
                    } else {
                        ffp->stat.drop_frame_count++;//Frame Loss Plus
                        //Frame Drop Rate = Number of Frames Dropped/Decoded
                        ffp->stat.drop_frame_rate = (float)(ffp->stat.drop_frame_count) / (float)(ffp->stat.decode_frame_count);
                        av_frame_unref(frame);//Frame Drop
                        got_picture = 0;//Modify the return parameter to indicate that no video frame was captured and lost
                    }
                }
            }
        }
    }

    return got_picture;
}

In this way, we can see the implementation logic of frame dropping in ijk.

Posted by sword on Fri, 17 May 2019 09:08:16 -0700