以前工作中参与了一些音视频程序的开发,不过使用的都是芯片公司的SDK,没有研究到更深入一层,比如说音视频同步是怎么回事。只好自己抽点时间出来分析开源代码了,做音视频编解码的人都知道ffmpeg,他在各种音视频播放软件当中已经使用很多了。当然,这里不是来分析音视频播放软件,如果真的想学习,自己可以研究一下ffmpeg自带的一个简单播放器ffplay,在这里不对ffplay做详细分析,只拿出来他的音视频同步一部分来详细分析(下面代码取自ffmpeg-0.5)。
在ffplay里的视频图像更新是在一个timer里面更新的,当有timer事件时就会调用video_refresh_timer()函数,而在这个函数里面会调用compute_frame_delay()计算下一帧图像显示的时间
video_refresh_timer()
/* launch timer for next picture */
schedule_refresh(is,
(int)(compute_frame_delay(vp->pts,
is) * 1000 +
0.5));
compute_frame_delay()的参数frame_current_pts表示当前图像的pts,参数is是全局videoState结构指针。
static
double compute_frame_delay(double frame_current_pts, VideoState
*is){
double actual_delay, delay, sync_threshold, ref_clock,
diff;
/* compute nominal
delay */
delay =
frame_current_pts - is->frame_last_pts; //
frame_last_pts存着上一帧图像的pts,用当前帧的pts减去上一帧的pts,从而计算出一个估计的delay值
if (delay <= 0 || delay >= 10.0) {
//
这个delay值有一个范围,如果超出范围的话,则用上一次的delay值
/* if incorrect delay, use previous one
*/
delay = is->frame_last_delay;
//
frame_last_delay存放前面一次计算得到的delay值
} else
{
is->frame_last_delay = delay;
//
如果计算出来的值合法,则保存下来
}
is->frame_last_pts =
frame_current_pts;
//
将当前帧的pts保存下来
/*
update delay to follow master synchronisation source */
//
更新delay值用于跟随主同步源
if
(((is->av_sync_type == AV_SYNC_AUDIO_MASTER && is->audio_st)
||
is->av_sync_type == AV_SYNC_EXTERNAL_CLOCK)) {
//
音频做主同步源或外部时钟做同步源时进入
/* if video is slave, we try to correct big delays
by
duplicating or deleting a frame
*/
ref_clock = get_master_clock(is);
//
得到主时钟源的当前时钟值做为参考时钟
diff = frame_current_pts - ref_clock;
//
当前帧的pts减去参考时钟,结果diff可能为正值,也可能为负值
/* skip or repeat frame. We take into account
the
delay to compute the threshold. I still don‘t
know
if it is the best guess
*/
sync_threshold = FFMAX(AV_SYNC_THRESHOLD, delay); //
delay和AV_SYNC_THRESHOLD之间取一个最大值
if (fabs(diff) < AV_NOSYNC_THRESHOLD)
{
if (diff <= -sync_threshold)
//
sync_threshold绝对是个>0的值
delay = 0;
//
如果diff是个很小的负数,则说明当前视频帧已经落后于主时钟源了,下一帧图像应该快点显示,所以delay=0
else if (diff >=
sync_threshold)
delay = 2 * delay;
//
如果diff是一个比较大的正数,则说明当前视频帧已经超前于主时钟源了,下一帧图像应该延迟显示
}
}
is->frame_timer +=
delay;
//
frame_timer是一个delay累加的值,
/* compute the REAL delay (we need to do that to
avoid
long
term errors */
actual_delay
= is->frame_timer - (av_gettime() / 1000000.0); //
frame_timer减去当前系统时钟,得到一个actual_delay值
if (actual_delay < 0.010)
{
/*
XXX: should skip picture
*/
actual_delay = 0.010;
}
#if
defined(DEBUG_SYNC)
printf("video: delay=%0.3f actual_delay=%0.3f pts=%0.3f
A-V=%f\n",
delay, actual_delay, frame_current_pts,
-diff);
#endif
return actual_delay;
//
actual_delay将做为下一次帧图像更新的TIMER参数
}
****
1.
在ffplay当中并没有跳帧的作法,只是通过控制视频帧显示速度来实现音/视频同步的。
2.
一般情况下,音频不容易同步到视频,但视频比较容易同步到音频,因为音频输出的采样率等参数一般都是固定的,设定好以后消耗数据的速率基本是固定的,很难在播放过程当中动态调整,
而视频显示的图像更新速率更容易调整些。通常可以通过加速刷新,跳帧等办法来调整。
既然同步的时候用到的系统时钟,那当pause/resume时又做了些什么来保证同步不出问题呢?这是需要考虑的一个重要问题,来看stream_pause()函数
static
void stream_pause(VideoState
*is)
{
is->paused
= !is->paused; //
反转paused值
if (!is->paused)
{
//
如果是resume操作,则要更新当前视频帧的pts
is->video_current_pts =
get_video_clock(is);
//
video_current_pts_time记录了上一帧视频图像显示时的系统时钟, av_gettime() -
is->video_current_pts_time得到的结果刚好是pause这段时间间隔,通过这种方式保证了frame_timer永远记录的是ffplay启动后到当前时间点的时间间隔
is->frame_timer += (av_gettime() - is->video_current_pts_time) /
1000000.0;
}
}
好了,是不是对音视频同步有了一定认识呢。这里只做了一点点简单的分析,我的分析不一定准确,只做参考学习,如果有人发现问题,请告诉我。ffplay的代码实现很完整,如果有兴趣,可以自己尝试去分析一下音频同步到视频的或音/视频同步到外部时钟的实现。
相关文章
- 10-27十、详解FFplay音视频同步
- 10-27音视频中的PTS和DTS及同步
- 10-2799%的人都不知道的3款抖音视频数据分析工具(不看后悔)
- 10-27ffplay分析(从启动到读取线程的操作)
- 10-27如何理解直播APP源码开发中的音视频同步
- 10-27ffplay的音视频同步分析
- 10-27音视频同步录音监控的需求水涨船高
- 10-27JVM调优案例分析与实战(2):集群间同步导致的内存溢出
- 10-27WebRTC 音视频同步分析
- 10-27如何理解直播APP源码开发中的音视频同步