本系列如下:
视频渲染流程
音频播放流程
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;
}