文章目录
ffp_toggle_buffering
先了解下触发缓冲的核心函数: ffp_toggle_buffering
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) {
av_log(ffp, AV_LOG_DEBUG, "ffp_toggle_buffering_l: start\n");
is->buffering_on = 1;
stream_update_pause_l(ffp);
if (is->seek_req) {
is->seek_buffering = 1;
ffp_notify_msg2(ffp, FFP_MSG_BUFFERING_START, 1);
} else {
ffp_notify_msg2(ffp, FFP_MSG_BUFFERING_START, 0);
}
} else if (!buffering_on && is->buffering_on){
av_log(ffp, AV_LOG_DEBUG, "ffp_toggle_buffering_l: end\n");
is->buffering_on = 0;
stream_update_pause_l(ffp);
if (is->seek_buffering) {
is->seek_buffering = 0;
ffp_notify_msg2(ffp, FFP_MSG_BUFFERING_END, 1);
} else {
ffp_notify_msg2(ffp, FFP_MSG_BUFFERING_END, 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) {
is->frame_timer += av_gettime_relative() / 1000000.0 - is->vidclk.last_updated;
#ifdef FFP_MERGE
if (is->read_pause_return != AVERROR(ENOSYS)) {
is->vidclk.paused = 0;
}
#endif
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);
}
}
实际到后面调用的就是 stream_toggle_pause_l 这个函数,也即主要就是控制播放的暂停与恢复,触发缓冲时会触发播放暂停,读包填充缓冲区
触发缓冲的相关代码
1. read_thread 读数据线程 for 循环中
/* if the queue are full, no need to read more */
if (ffp->infinite_buffer<1 && !is->seek_req &&
(is->audioq.size + is->videoq.size + is->subtitleq.size > ffp->dcc.max_buffer_size
|| (stream_has_enough_packets(is->audio_st, is->audio_stream, &is->audioq, MIN_FRAMES)
&& stream_has_enough_packets(is->video_st, is->video_stream, &is->videoq, MIN_FRAMES)
&& stream_has_enough_packets(is->subtitle_st, is->subtitle_stream, &is->subtitleq,MIN_FRAMES)))) {
if (!is->eof) {
ffp_toggle_buffering(ffp, 0);
}
/* wait 10 ms */
SDL_LockMutex(wait_mutex);
SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10);
SDL_UnlockMutex(wait_mutex);
continue;
}
解除 buffering 的条件:
- infinite_buffer→不是无限的缓冲;
- seek_req→没有seek 请求;
- 音频、视频和字幕总的包数据大小达到预设值,或者每个流中的包个数达到了预设的值
以上即是用包的大小或者包的个数来进行限制
如果达到了限制,即缓冲区的音视频数据足够了,ffp_toggle_buffering(ffp, 0) 代表不需要缓冲,然后等待 10ms,不需继续往下调用 av_read_frame 读包了,重新进入 read_thread 的 for 循环中
注:代码中的 MIN_FRAMES 是一个宏,默认值为 50000,可在 java 层进行预设值的修改;ffp->dcc.max_buffer_size 同理
#define MIN_FRAMES (ffp->dcc.min_frames)
#define DEFAULT_MIN_FRAMES 50000
#define MIN_MIN_FRAMES 2
#define MAX_MIN_FRAMES 50000
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "min-frames", 500);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max-buffer-size", 200*1024);
2. read_thread 读数据线程 for 循环中取包后
每读取一个 AVPacket 包后,还会根据 packet_buffering 标志位来判断是否检查缓冲
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)) {
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) {
prev_io_tick_counter = io_tick_counter;
ffp_check_buffering_l(ffp);
}
}
}
#define BUFFERING_CHECK_PER_MILLISECONDS (500)
#define FAST_BUFFERING_CHECK_PER_MILLISECONDS (50)
首帧未播放时每 50ms 检查是否可以恢复播放,首帧播放后每 500ms 检查是否可以恢复播放
ffp_check_buffering_l 做的事情为检查是否需要缓冲:
目的:检查是否缓冲够了(是否满足一定的播放时长或者满足一定的播放数据量),够了就取消缓冲状态,取消暂停状态,恢复播放
当检查到能满足播放时,就升级时间梯度,进行更严格的检查,让队列中缓存尽可能多的数据,以避免卡顿;同样,当触发卡顿时,也必须满足一定的时间梯度才能重新进入播放
一些时间梯度、数据量初始值如下:
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
//buffer缓冲时长 100ms -> 1s -> 2s -> 4s -> 5s,最大递增到5s;但只要数据量满足256K即可放行
current_high_water_mark_in_ms = DEFAULT_FIRST_HIGH_WATER_MARK_IN_MS; //100
由 first_high_water_mark_in_ms 字段可看出,播放器打开后,需要缓冲 100 ms 再进行播放;后面遇到卡顿、缓冲不够时,就会升级梯度,缓冲到 1000ms 再播放,再次遇到就会缓冲到 2000ms,依次类推直至 5000ms
3. 解码前从队列中取包时
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);
}
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);
}
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;
}
ffp->packet_buffering 代表是否开启缓冲机制,在未开启缓冲机制的情况下,当队列为空时,阻塞等待直到退出或者有 AVPacket 包数据
在开启缓冲机制的情况下,当取不到包时,会暂停并触发缓冲,等待 ffp_check_buffering_l 修复到播放状态
缓冲的机制,牺牲了延迟而保障流畅
缓冲区的大小的影响
- 缓冲区越大,抗抖动能力越强
- 缓冲区越大,内存占用越高
- 缓冲区越大,播放延时越大