ijk播放器缓冲机制

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 的条件:

  1. infinite_buffer→不是无限的缓冲;
  2. seek_req→没有seek 请求;
  3. 音频、视频和字幕总的包数据大小达到预设值,或者每个流中的包个数达到了预设的值

以上即是用包的大小或者包的个数来进行限制
如果达到了限制,即缓冲区的音视频数据足够了,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 修复到播放状态

缓冲的机制,牺牲了延迟而保障流畅

缓冲区的大小的影响

  • 缓冲区越大,抗抖动能力越强
  • 缓冲区越大,内存占用越高
  • 缓冲区越大,播放延时越大
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值