前言
对于音视频同步是有三种方案的,一种是以外部时钟为基准,音频时钟和视频时钟在播放时都以外部时钟为参考系,谁快了就等待,慢了就丢帧;第二种是以视频时钟为基准, 音频时钟在播放的过程中参考视频时钟;第三种是以音频时钟为基准,视频时钟在播放的过程中参考音频时钟。
由于人体器官对视觉的敏感度没有听觉的灵敏度高,因此为了更好的体验,在音视频同步时一般都是以音频时钟为基准的方案。那是不是说其他两种方案没有用处呢?也不是的,比如说 一个只有视频没有音频的的视频文件,在播放的时候就需要以视频为基准了。
今天介绍的音视频同步方案也是最普遍的视频同步音频的方案。
Clock时钟
我们再来看看Clock这个结构体:
// 时钟/同步时钟
typedef struct Clock {
double pts; // 当前正在播放的帧的pts /* clock base */
double pts_drift; // 当前的pts与系统时间的差值 保持设置pts时候的差值,后面就可以利用这个差值推算下一个pts播放的时间点
double last_updated; // 最后一次更新时钟的时间,应该是一个系统时间吧?
double speed; // 播放速度控制
int serial; // 播放序列 /* clock is based on a packet with this serial */
int paused; // 是否暂停
int *queue_serial; // 队列的播放序列 PacketQueue中的 serial /* pointer to the current packet queue serial, used for obsolete clock detection */
} Clock;
再结合关于时钟的几个函数看看:
// 主要由set_clock调用
static void set_clock_at(Clock *c, double pts, int serial, double time)
{
c->pts = pts;
c->last_updated = time;
c->pts_drift = c->pts - time;
c->serial = serial;
}
static void set_clock(Clock *c, double pts, int serial)
{
double time = av_gettime_relative() / 1000000.0;
set_clock_at(c, pts, serial, time);
}
static double get_clock(Clock *c)
{
// 如果时钟的播放序列与待解码包队列的序列不一直了,返回NAN,肯定就是不同步或者需要丢帧了
if (*c->queue_serial != c->serial)
return NAN;
if (c->paused) {
// 暂停状态则返回原来的pts
return c->pts;
} else {
double time = av_gettime_relative() / 1000000.0;
// speed可以先忽略播放速度控制
// 如果是1倍播放速度,c->pts_drift + time
return c->pts_drift + time - (time - c->last_updated) * (1.0 - c->speed);
}
}
音频和视频每次在播放新的一帧数据时都会调用函数set_clock
更新音频时钟或视频时钟。通过函数set_clock_at
我们发现,就是更新了 Clock 结构体的四个变量。其中pts_drift是当前帧的pts与系统时间的差值,有了这个差值在未来的某一刻就能够很方便地算出当前帧对于的时钟点。
【学习地址】:FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发
【文章福利】:免费领取更多音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击1079654574加群领取哦~
大致原理如图:
视频同步音频
视频同步到音频的基本方法是:如果视频超前音频,继续显示上一帧,以等待音频;如果视频落后音频,则显示下一帧,以追赶音频。
对于视频同步处理在ffplay有两处地方,一是在函数get_video_frame
做了简单的丢帧处理,二是在函数video_refresh
显示控制时做的同步处理。
对于函数get_video_frame
丢帧处理的主要