ijkplayer播放器剖析(六)视频同步与渲染机制分析

一、引言:
在前面的博客中,将音频解码播放及视频解码都分析了,这篇博客将单独针对视频同步及渲染来分析,看下ijkplayer是如何做的。本博客分析的同步方式为以音频为主,视频去同步音频。

二、同步前提的确认:
ijkplayer的同步前提跟其他的播放器略有不同,在ijkplayer中,会创建用于维护音频,视频的时钟及一个外部时钟,所有的同步操作都是基于这三个时钟来进行的。具体的变量如下:

Clock audclk
Clock vidclk
Clock extclk
那么,对于同步而言,我们需要确认的是,音频和视频的时钟是何时更新的,只要知道了各自的时钟,那么就只需要去分析同步策略了,先来看视频。
在视频渲染线程video_refresh中,对于即将渲染的下一帧,会在渲染前更新pts:

vp = frame_queue_peek(&is->pictq);

然后将数据的pts及当前系统时间设置到vidclk:

SDL_LockMutex(is->pictq.mutex);
if (!isnan(vp->pts))
    update_video_pts(is, vp->pts, vp->pos, vp->serial);
SDL_UnlockMutex(is->pictq.mutex);

看一下update_video_pts:

static void update_video_pts(VideoState *is, double pts, int64_t pos, int serial) {
   
    /* update current video pts */
    set_clock(&is->vidclk, pts, serial);
    sync_clock_to_slave(&is->extclk, &is->vidclk);
}

我们前面知道,视频是去同步音频的,显然,音频pts的更新至关重要,看一下音频pts是如何更新的:audclk的时钟更新是在往audiotrack中写入数据的时候。
我们看一下ff_ffplay.c中用于往audiotrack中写数据的回调函数sdl_audio_callback

    if (!isnan(is->audio_clock)) {
   
        set_clock_at(&is->audclk, is->audio_clock - (double)(is->audio_write_buf_size) / is->audio_tgt.bytes_per_sec - SDL_AoutGetLatencySeconds(ffp->aout), is->audio_clock_serial, ffp->audio_callback_time / 1000000.0);
        sync_clock_to_slave(&is->extclk, &is->audclk);
    }

音频pts更新的首要条件是判断is->audio_clock是否有效,那么这个is->audio_clock是怎么更新的呢?这个值是在往audiotrack中写数据时,获取待写入数据audio_decode_frame中去更新的:

audio_decode_frame@ff_ffplay.c:

static int audio_decode_frame(FFPlayer *ffp)
{
   
...
 /* update the audio clock with the pts */
 if (!isnan(af->pts))
     is->audio_clock = af->pts + (double) af->frame->nb_samples / af->frame->sample_rate;
 else
     is->audio_clock = NAN;
....
}

当有效帧中含有效pts时,将进入if循环,audio_clock等于当前帧pts加上当前帧的采样总点数 / 采样率两者之和,当前帧的总点数 /采样率即当前帧的持续时间。得到了这个值之后,再看前面是如何更新音频时钟audclk的:

set_clock_at(&is->audclk, is->audio_clock - (double)(is->audio_write_buf_size) / is->audio_tgt.bytes_per_sec - SDL_AoutGetLatencySeconds(ffp->aout), is->audio_clock_serial, ffp->audio_callback_time / 1000000.0);

注意第二个入参是一个实时pts,因为上面我们分析了audio_clock是在pts的基础上加上整个帧的持续时间的,故这里会用audio_clock减去已经写入到audiotrack中的buffer数据持续时间,并且还进行了latency校准,可以说,ijkplayer在音频时钟的更新上做的非常细节。将校准后的音频时钟更新之后,下面就是同步策略的分析了。

三、同步策略分析:
ijkplayer视频同步音频的策略在video_refresh@ff_ffplay.c中实现,我们先看下video_refresh是如何调入的:

static int video_refresh_thread(void *arg)
{
   
    FFPlayer *ffp = arg;
    VideoState *is = ffp->is;
    double remaining_time = 0.0;
    /* 如果不退出播放 */
    while (!is->abort_request) {
   
    	/* 是否需要睡眠 */
        if (remaining_time > 0.0)
            av_usleep((int)(int64_t)(remaining_time * 1000000.0));
        remaining_time = REFRESH_RATE;
        if (is->show_mode != SHOW_MODE_NONE && (!is->paused || is->force_refresh))
        	/* 调用同步及渲染函数 */
            video_refresh(ffp, &remaining_time);
    }

    return 0;
}

ijkplayer专门创建了一个video_refresh_thread用于处理视频渲染,看代码中,有一个变量remaining_time用于处理是否睡眠,实际上这个变量的值是由后面的同步策略来决定的,一帧视频需要渲染多长时间,均由这个值来判定,看一下同步处理及渲染的函数video_refresh

static void video_refresh(FFPlayer *opaque, double *remaining_time)
{
   
    FFPlayer *ffp = opaque;
    VideoState *is = ffp->is;
    double time;

    Frame *sp, *sp2;
    /* 启用外部时钟 */
    if (!is->paused && get_master_sync_type(is) ==<
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值