[TOC] 开始前的BB有些没有接触过的童鞋可能还不知道音视频同步是什么意思,大家印象中应该看到过这样的视频,画面中的人物说话和声音出来的不在一起,小时候看有些电视台转播的港片的时候(别想歪 TVB)有时候就会遇到 明明声音已经播出来了,但是播的图像比声音慢了很多,看的极为不舒服,这个时候就发生了音视频不同步的情况,而音视频同步,就是让声音与画面对应上 这里有个知识点需要记一下 人对于图像和声音的接受灵敏程度不一样,人对音频比对视频敏感;视频放快一点,可能察觉的不是特别明显,但音频加快或减慢,人耳听的很敏感 PTS的由来音视频同步依赖的一个东西就是pts(persentation time stamp )显示时间戳 告诉我们该什么时间显示这一帧 ,那么,这个东西是从哪里来的呢? 刨根问底栏目组将带你深度挖掘 PTS是在拍摄的时候打进去的时间戳,假如我们现在拍摄一段小视频(别想歪啊),什么特效都不加,那么走的就是以下的步骤 image 我们根据这个图可以知道,PTS是在录制的时候就打进Frame里的 音视频同步的方式在ffplay中 音视频同步有三种方式
视频基准如果以视频为基准进行同步,那么我们就要考虑可能出现的情况,比如: 掉帧 此时的音频应该怎么做呢?通常的方法有
音频基准如果以音频为基准进行同步,很不幸的碰到了掉帧的情况,那么视频应该怎么做呢?通常也有两种做法 1.视频丢帧 (画面跳帧,丢的多的话,俗称卡成PPT) 外部时钟为基准假如以外部时钟为基准,如果音视频出现了丢帧,怎么办呢? 如果丢帧较多,直接重新初始化外部时钟 (pts和时钟进行对比,超过一定阈值重设外部时钟,比如1s) 音视频时间换算PTS 时间换算之前我们稍微讲过pts的时间换算,pts换算成真正的秒是用以下操作
stream是当前的视频/音频流 我们这里主要讲一下在音频解码pts可能会遇到的情况,有时候音频帧的pts会以1/采样率为单位,像 pts1 = 0 像我们例子中的这个视频,我们在解码一帧音频之后打印出来他的pts image 我们知道当前视频的音频采样率为44100,那么这个音频帧pts的单位就是 pts1 = 0 * 1 / 44100 = 0 音频流的time_base里面正是记录了这个值,我们可以通过debug来看一下 image 利用 realTime = pts * av_q2d(stream.time_base) 我们可以直接算出来当前音频帧的pts image 另外需要注意 在ffplay中做音视频同步,都是以秒为单位 音视频帧播放时间换算音频帧播放时间计算音频帧的播放和音频的属性有关系,是采用
来计算,像AAC当个通道采样是1024个采样点,那么
视频帧的播放时间计算视频帧的播放时间也有两个计算方式
时间校正视频时间校正在看 音频时间校正音频的pts获取比视频的要复杂一点,在ffplay中对音频的pts做了三次修改
ffplay 时钟框架ffplay中的时钟框架主要依靠 /** 时钟结构体 **/typedef struct Clock { double pts; /* clock base 时间基准*/ double pts_drift; /* clock base minus time at which we updated the clock 时间基减去更新时钟的时间 */ double last_updated; double speed; int serial; /* clock is based on a packet with this serial */ int paused; int *queue_serial; /* pointer to the current packet queue serial, used for obsolete clock detection */} Clock;/** 初始化时钟 **/static void init_clock(Clock *c, int *queue_serial);/** 获取当前时钟 **/static double get_clock(Clock *c);/** 设置时钟 内部调用set_clock_at()**/static void set_clock(Clock *c, double pts, int serial);/** 设置时钟 **/static void set_clock_at(Clock *c, double pts, int serial, double time);/** 设置时钟速度 **/static void set_clock_speed(Clock *c, double speed);/** 音/视频设置时钟的时候都回去跟外部时钟进行对比,防止丢帧或者丢包情况下时间差距比较大而进行的纠偏 **/static void sync_clock_to_slave(Clock *c, Clock *slave);/** 获取做为基准的类型 音频 外部时钟 视频 **/static int get_master_sync_type(VideoState *is);/** 获取主时间轴的时间 **/static double get_master_clock(VideoState *is);/** 检查外部时钟的速度 **/static void check_external_clock_speed(VideoState *is); 这个时钟框架也是比较简单,可以直接去看FFplay的源码,这里就不过多的叙述 音视频同步时间轴在 image 就像这样子,有一个时钟一直在跑,所谓基于音频、视频、外部时间 做为基准,也就是将那个轴的的时间做为时间轴的基准,另一个在轴参照主时间轴进行同步 假如是以音频为基准,视频同步音频的方式,那么就是音频在每播放一帧的时候,就去将当前的时间同步到时间轴,视频参考时间轴做调整 音频时钟设置音频时钟的设置的话需要考虑注意 硬件缓存数据 设置音频时钟的时候需要将
这是就是将音频的pts - 硬件缓冲区里剩下的时间设置到了音频的时钟里 视频时钟设置视频时钟设置的话就比较简单了,直接设置pts,在ffplay中 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);} 音视频同步操作音视频在同步上出的处理我们上面有简单讲到过,我们这里来详细看一下他具体是真么做的 音频同步操作音频的同步操作是在
这个方法里面的操作有点多,我这边简单说一下这个方法,主要是利用音频时钟与主时钟相减得到差值(需要先判断音频是不是主时间轴),然后返回如果要同步需要的采样数,在 视频同步操作视频同步操作的主要步骤是在 /* compute nominal last_duration 根据当前帧和上一帧的pts计算出来上一帧显示的持续时间 */ last_duration = vp_duration(is, lastvp, vp); /** 计算当前帧需要显示的时间 **/ delay = compute_target_delay(last_duration, is); /** 获取当前的时间 **/ time= av_gettime_relative()/1000000.0; /** 如果当前时间小于显示时间 则直接进行显示**/ if (time < is->frame_timer + delay) { *remaining_time = FFMIN(is->frame_timer + delay - time, *remaining_time); goto display; } /** 更新视频的基准时间 **/ is->frame_timer += delay; /** 如果当前时间与基准时间偏差大于 AV_SYNC_THRESHOLD_MAX 则把视频基准时间设置为当前时间 **/ if (delay > 0 && time - is->frame_timer > AV_SYNC_THRESHOLD_MAX) is->frame_timer = time; /** 更新视频时间轴 **/ SDL_LockMutex(is->pictq.mutex); if (!isnan(vp->pts)) update_video_pts(is, vp->pts, vp->pos, vp->serial); SDL_UnlockMutex(is->pictq.mutex); /** 如果队列中有未显示的帧,如果开启了丢帧处理或者不是以视频为主时间轴,则进行丢帧处理 **/ if (frame_queue_nb_remaining(&is->pictq) > 1) { Frame *nextvp = frame_queue_peek_next(&is->pictq); duration = vp_duration(is, vp, nextvp); if(!is->step && (framedrop>0 || (framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) && time > is->frame_timer + duration){ is->frame_drops_late++; frame_queue_next(&is->pictq); goto retry; } } 到这里,ffplay中主要的音视频同步就讲完了,建议去看一下ffplay的源码,多体会体会 印象才会比较深刻,说实话ffplay中同步的操作是比较复杂的,我们在平常开发中要根据自己的实际业务进行一些简化和改进的,下一章我们就来写一个以音频为基准的视频播放器 未完持续... |
|