一、引言:
在前面的博客中,将音频解码播放及视频解码都分析了,这篇博客将单独针对视频同步及渲染来分析,看下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) ==<