ijkplayer源码分析 start流程和buffering缓冲策略

本系列如下:

视频渲染流程
音频播放流程
read线程流程
音频解码流程
视频解码流程
视频向音频同步
start流程和buffering缓冲策略

本文是分析ijkPlayer中的start流程和buffering机制,放在一块分析是因为两部分代码都在对播放状态进行操作,暂停或恢复播放。其中buffering机制也是ijk的核心,是卡顿和延时的核心。

关键bool值

ffp->render_wait_start:
等到start时候再渲染音频和数据;为0表示不用等start调用,为1表示必须等start调用;
用于控制渲染流程的;

ffp->start_on_prepared:
prepared之后自动开始读数据流程,不用等start;为0表示必须等start,为1表示prepared之后自动向下进行,不用调用start方法即可播放。
用于控制read_thread流程;和ffp->render_wait_start是互斥条件。

is->pause_req:
是否是暂定状态,暂定则循环Delay,等到恢复;
在stream_open中赋值:is->pause_req = !ffp->start_on_prepared;

ffp->packet_buffering:
是否开启缓冲机制

buffering_on:
是否正在缓冲

start流程

start流程描述:
start调用到toggle_pause(ffp, 0),将pause状态取消,开始进行播放;

需要注意:
如果render_wait_start为0,start_on_prepared也设置为0的话,则必须在播放器状态为MEDIA_PREPARED之后调用start才有效,否则无效。无效具体代码是在start流程中,preparing中调用start,状态不对,跳出start流程;
所以要在封装层要注意这一点。

// ijkPlayer.c
int ffp_start_l(FFPlayer *ffp) {
    toggle_pause(ffp, 0); // 0,表示不暂停了,恢复正常
}

// ff_ffplay.c
static int read_thread(void *arg) {
    // 都是0则调用先暂停,等待start修改pause_req跳出等待循环
    if (!ffp->render_wait_start && !ffp->start_on_prepared) {
        toggle_pause(ffp, 1);
    }
    ffp->prepared = true;
    if (!ffp->render_wait_start && !ffp->start_on_prepared) {
        // 等待start调用,修改pause_req为0
        while (is->pause_req && !is->abort_request) {
            SDL_Delay(20);
        }
    }
}

static void toggle_pause(FFPlayer *ffp, int pause_on) {
    av_log(ffp, AV_LOG_DEBUG, "toggle_pause method called");

    SDL_LockMutex(ffp->is->play_mutex);
    toggle_pause_l(ffp, pause_on);
    SDL_UnlockMutex(ffp->is->play_mutex);
}

static void toggle_pause_l(FFPlayer *ffp, int pause_on) {
    VideoState *is = ffp->is;
    if (is->pause_req && !pause_on) {
        set_clock(&is->vidclk, get_clock(&is->vidclk), is->vidclk.serial);
        set_clock(&is->audclk, get_clock(&is->audclk), is->audclk.serial);
    }
    is->pause_req = pause_on; // 修改pause_req,start中修改为0
    ffp->auto_resume = !pause_on;
    stream_update_pause_l(ffp);
    is->step = 0;
}

static void stream_update_pause_l(FFPlayer *ffp) {
    VideoState *is = ffp->is;
    if (!is->step && (is->pause_req || is->buffering_on)) {
        stream_toggle_pause_l(ffp, 1);
    } else {
        stream_toggle_pause_l(ffp, 0);
    }
}

/* pause or resume the video */
static void stream_toggle_pause_l(FFPlayer *ffp, int pause_on) {
    VideoState *is = ffp->is;

    if (is->paused && !pause_on) {
    	/*
    	 * 当前是暂停状态,要去播放,把frame_timer加一下
    	 * 把clock重置一波
    	 */
	    is->frame_timer += av_gettime_relative() / 1000000.0 - is->vidclk.last_updated;
        set_clock(&is->vidclk, get_clock(&is->vidclk), is->vidclk.serial);
        set_clock(&is->audclk, get_clock(&is->audclk), is->audclk.serial);
    } else {
    }
    set_clock(&is->extclk, get_clock(&is->extclk), is->extclk.serial);

    if (is->step && (is->pause_req || is->buffering_on)) {
        is->paused = is->vidclk.paused = is->extclk.paused = pause_on;
    } else {
        is->paused = is->audclk.paused = is->vidclk.paused = is->extclk.paused = pause_on;
        SDL_AoutPauseAudio(ffp->aout, pause_on);
    }
}

CSDN站内私信我,领取最新最全C++音视频学习提升资料,内容包括(C/C++Linux 服务器开发,FFmpeg webRTC rtmp hls rtsp ffplay srs

buffering缓冲策略

buffering机制描述:
在read_thread的for(;;)循环中首帧未播放时每50ms/首帧播放后每500ms进行检查是否可以恢复播放;
当检查到能满足播放时,就升级时间梯度,进行更严格的检查,让队列中缓存尽可能多的数据,以避免卡顿;同样,当触发卡顿时,也必须得等到满足时间梯度才能进入播放。
在解码前取包时,若发现取不到包了,则暂停播放,触发缓冲,并置is->paused为1;此时依然要等循环调用ffp_check_buffering_l,填满队列,满足播放条件后才能恢复播放状态;
是以牺牲延迟来保障流畅。

满足播放条件
能播放时长已经足够播放hwm_in_ms了 || 能播放的数据量已经大于256K了

时间梯度默认初始值
high_water_mark_in_bytes = DEFAULT_HIGH_WATER_MARK_IN_BYTES; 256K,始终不变
first_high_water_mark_in_ms = DEFAULT_FIRST_HIGH_WATER_MARK_IN_MS; 100
next_high_water_mark_in_ms = DEFAULT_NEXT_HIGH_WATER_MARK_IN_MS; 1000
last_high_water_mark_in_ms = DEFAULT_LAST_HIGH_WATER_MARK_IN_MS; 5000
current_high_water_mark_in_ms = DEFAULT_FIRST_HIGH_WATER_MARK_IN_MS; 100

buffer缓冲时长
100ms -> 1s -> 2s -> 4s -> 5s,最大递增到5s;但满足256K即可放行;

代码如下:

static int read_thread(void *arg) {
    for  (;;) {
        /*
         * 开启缓冲机制
         * 在for循环中,每读到一个包,都会检查是否进行缓冲
         */
	    if (ffp->packet_buffering) {
            io_tick_counter = SDL_GetTickHR();

            if ((!ffp->first_video_frame_rendered && is->video_st) ||
                (!ffp->first_audio_frame_rendered && is->audio_st)) {
                // 首帧未显示前,50ms检测一次
                if (abs((int) (io_tick_counter - prev_io_tick_counter)) > FAST_BUFFERING_CHECK_PER_MILLISECONDS) {
                    prev_io_tick_counter = io_tick_counter;
                    ffp->dcc.current_high_water_mark_in_ms = ffp->dcc.first_high_water_mark_in_ms;
                    ffp_check_buffering_l(ffp);
                }
            } else {
                if (abs((int) (io_tick_counter - prev_io_tick_counter)) > BUFFERING_CHECK_PER_MILLISECONDS) {
                	// 首帧显示后,500ms检测一次
                    prev_io_tick_counter = io_tick_counter;
                    ffp_check_buffering_l(ffp);
                }
            }
        }      
    }
}

/*
  * 循环检查是否缓冲够了,够了就去播放吧,取消缓冲状态,取消暂停,恢复播放
  */
void ffp_check_buffering_l(FFPlayer *ffp) {
    // 阶梯递增,最大DEFAULT_LAST_HIGH_WATER_MARK_IN_MS,5s
    int hwm_in_ms = ffp->dcc.current_high_water_mark_in_ms;
    int hwm_in_bytes = ffp->dcc.high_water_mark_in_bytes;

    // 队列里缓存的能播放的音视频播放时长
    int64_t audio_cached_duration = ffp->stat.audio_cache.duration;
    int64_t video_cached_duration = ffp->stat.video_cache.duration;
    int cached_duration_in_ms = min((video_cached_duration, audio_cached_duration);
    
    /*
      *  计算当前能播放的时长超过了多少hwm_in_ms
      *  我理解这块是个四舍五入,然后放大一百倍,cached_duration_in_ms * 100.5 / hwm_in_ms
      *  后面的算法,实际上表示cached_duration_in_ms>hwm_in_ms即可进行播放了
      */
    int  buf_time_percent = (int) av_rescale(cached_duration_in_ms, 1005, hwm_in_ms * 10);

    // 队列里缓存的音视频总大小
    int cached_size = is->audioq.size + is->videoq.size;
    // 计算缓存数据大小超过了多少hwm_in_bytes
    int buf_size_percent = (int) av_rescale(cached_size, 1005, hwm_in_bytes * 10);

    /*
      *  能播放时长已经足够播放hwm_in_ms了  ||
      *  能播放的数据量已经足够播放hwm_in_bytes了,
      *  就解除缓冲状态/暂停状态,设置为播放状态
      */ 
    int need_start_buffering = 0;
    if  (buf_time_percent >= 100 || buf_size_percent >= 100) {
        need_start_buffering = 1;
    }

    if (need_start_buffering) {
        if (hwm_in_ms < ffp->dcc.next_high_water_mark_in_ms) {
            hwm_in_ms = ffp->dcc.next_high_water_mark_in_ms;
        } else {
            hwm_in_ms *= 2;
        }

        if (hwm_in_ms > ffp->dcc.last_high_water_mark_in_ms)
            hwm_in_ms = ffp->dcc.last_high_water_mark_in_ms;

        ffp->dcc.current_high_water_mark_in_ms = hwm_in_ms;

        if (is->buffer_indicator_queue && is->buffer_indicator_queue->nb_packets > 0) {
            if ((is->audioq.nb_packets >= MIN_MIN_FRAMES || is->audio_stream < 0 || is->audioq.abort_request)
                && (is->videoq.nb_packets >= MIN_MIN_FRAMES || is->video_stream < 0 || is->videoq.abort_request)) {
                // 音视频队列的缓冲区差不多了,> MIN_MIN,则去除暂停状态,
                ffp_toggle_buffering(ffp, 0);
            }
        }
    }
}

void ffp_toggle_buffering(FFPlayer *ffp, int start_buffering) {
    SDL_LockMutex(ffp->is->play_mutex);
    ffp_toggle_buffering_l(ffp, start_buffering);
    SDL_UnlockMutex(ffp->is->play_mutex);
}

void ffp_toggle_buffering_l(FFPlayer *ffp, int buffering_on) {
    if (!ffp->packet_buffering) {
        // 缓存机制是否开启
        return;
    }

    VideoState *is = ffp->is;
    if (buffering_on && !is->buffering_on) {
        // 当前没buffering, 要去buffering,FFP_MSG_BUFFERING_START
        is->buffering_on = 1;
        stream_update_pause_l(ffp); // 暂停
    } else if (!buffering_on && is->buffering_on) {
        // 当前buffering,取消buffering,FFP_MSG_BUFFERING_END
        is->buffering_on = 0;
        stream_update_pause_l(ffp); // 取消暂停
    }
}


/*
  * 软件音频、软硬解视频时,阻塞等待直到退出或者有AVPacket数据
  * 在开启缓冲机制的情况下,会暂停进行缓冲,等待check buffering修复到播放状态
  */
static int packet_queue_get_or_buffering(FFPlayer *ffp, PacketQueue *q, AVPacket *pkt, int *serial,
                                         int *finished) {
    assert(finished);
    if (!ffp->packet_buffering){
        // 未开启缓冲机制
        return packet_queue_get(q, pkt, 1, serial); // queue为空时会阻塞等待
    }

    while (1) {
        int new_packet = packet_queue_get(q, pkt, 0, serial);
        if (new_packet < 0) {
            return -1;
        } else if (new_packet == 0) {
            if (q->is_buffer_indicator && !*finished) {
                ffp_toggle_buffering(ffp, 1);   // 暂停当前,去缓冲,设置为1
            }
            new_packet = packet_queue_get(q, pkt, 1, serial); // 缓冲了,再拿一次
            if (new_packet < 0) {
                return -1;
            }
        }

        if (*finished == *serial) {
            av_packet_unref(pkt);
            continue;
        } else {
            break;
        }
    }

    return 1;
}

猜你喜欢

转载自blog.csdn.net/m0_60259116/article/details/125070316