有些没有接触过的童鞋可能还不知道音视频同步是什么意思,大家印象中应该看到过这样的视频,画面中的人物说话和声音出来的不在一起,小时候看有些电视台转播的港片的时候(别想歪TVB)有时候就会遇到明明声音已经播出来了,但是播的图像比声音慢了很多,看的极为不舒服,这个时候就发生了音视频不同步的情况,而音视频同步,就是让声音与画面对应上这里有个知识点需要记一下 人对于图像和声音的接受灵敏程度不一样,人对音频比对视频敏感;视频放快一点,可能察觉的不是特别明显,但音频加快或减慢,人耳听的很敏感PTS的由来音视频同步依赖的一个东西就是pts(persentationtimestamp)显示时间戳告诉我们该什么时间显示这一帧,那么,这个东西是从哪里来的呢?刨根问底栏目组将带你深度挖掘PTS是在拍摄的时候打进去的时间戳,假如我们现在拍摄一段小视频(别想歪啊),什么特效都不加,那么走的就是以下的步骤 我们根据这个图可以知道,PTS是在录制的时候就打进Frame里的音视频同步的方式在ffplay中音视频同步有三种方式以视频为基准,同步音频到视频音频慢了就加快音频的播放速度,或者直接丢掉一部分音频帧音频快了就放慢音频的播放速度 以音频为基准,同步视频到音频视频慢了则加快播放或丢掉部分视频帧视频快了则延迟播放 以外部时钟为准,同步音频和视频到外部时钟根据外部时钟改版音频和视频的播放速度 视频基准如果以视频为基准进行同步,那么我们就要考虑可能出现的情况,比如:掉帧此时的音频应该怎么做呢?通常的方法有音频也丢掉相应的音频帧(会有断音,比如你说了一句,我的天啊,好漂亮的草原啊很不凑巧丢了几帧视频,就成,,,卧槽!)音频加快速度播放(此处可以直接用Audition加快个几倍速的播放一首音乐) 音频基准如果以音频为基准进行同步,很不幸的碰到了掉帧的情况,那么视频应该怎么做呢?通常也有两种做法1。视频丢帧(画面跳帧,丢的多的话,俗称卡成PPT)2。加快播放速度(画面加快播放)外部时钟为基准假如以外部时钟为基准,如果音视频出现了丢帧,怎么办呢?如果丢帧较多,直接重新初始化外部时钟(pts和时钟进行对比,超过一定阈值重设外部时钟,比如1s)音视频时间换算PTS时间换算之前我们稍微讲过pts的时间换算,pts换算成真正的秒是用以下操作realTimeptsavq2d(stream。timebase)stream是当前的视频音频流我们这里主要讲一下在音频解码pts可能会遇到的情况,有时候音频帧的pts会以1采样率为单位,像pts10pts21024pts32048像我们例子中的这个视频,我们在解码一帧音频之后打印出来他的ptsstd::coutaudiopts:ptsstd:: 我们知道当前视频的音频采样率为44100,那么这个音频帧pts的单位就是144100,那么pts101441000pts210241441000。232pts320481441000。464音频流的timebase里面正是记录了这个值,我们可以通过debug来看一下 利用realTimeptsavq2d(stream。timebase)我们可以直接算出来当前音频帧的pts 链接【FFmpeg音视频同步原理分析】 音视频资料分享 另外需要注意在ffplay中做音视频同步,都是以秒为单位音视频帧播放时间换算音频帧播放时间计算音频帧的播放和音频的属性有关系,是采用采样点数1采样率来计算,像AAC当个通道采样是1024个采样点,那么如果是44。1khz,那么一帧的播放时长就是102414410023。3毫秒如果是48khz,那么一帧的播放时长就是102414800021。33毫秒 视频帧的播放时间计算视频帧的播放时间也有两个计算方式利用1帧率获取每个帧平均播放时间,这个方式有一个很大的缺点就是,不能动态响应视频帧的变化,比如说我们做一些快速慢速的特效,有的厂商或者SDK(我们的SDK不是)是直接改变视频帧的增加减少视频帧之间的pts间距来实现的,这就导致在一些拿帧率计算显示时间的播放器上发现是整体(快慢)了,达不到想要的效果;还有一种情况就是丢帧之后,时间显示仍然是固定的相邻帧相减这大程度上避免利用帧率去算的各种弊端,但是缺点是使用起来比较复杂,尤其是暂停Seek之类的操作的时候需要进行一些时间差值的计算 时间校正视频时间校正在看ffplay的时候我们会发现,他在里面默认情况下是用了其实大多数情况下pts和bestefforttimestamp的值是一样的,这个值是利用各种探索方法去计算当前帧的视频戳音频时间校正音频的pts获取比视频的要复杂一点,在ffplay中对音频的pts做了三次修改frameptsavrescaleq(framepts,davctxpkttimebase,tb);将其由streamtimebase转为(1采样率)(decoderdecodeframe()中)afpts(frameptsAVNOPTSVALUE)?NAN:frameptsavq2d(tb);将其由(1采样率)转换为秒(audiothread()中)isaudioclock(double)(2isaudiohwbufsizeisaudiowritebufsize)isaudiotgt。bytespersec根据实际输入进SDL2播放的数据长度做调整(sdlaudiocallback中) ffplay时钟框架ffplay中的时钟框架主要依靠Clock结构体和相应的方法组成时钟结构体typedefstructClock{clockbase时间基准clockbaseminustimeatwhichweupdatedtheclock时间基减去更新时钟的时间pointertothecurrentpacketqueueserial,usedforobsoleteclockdetection}C初始化时钟staticvoidinitclock(Clockc,intqueueserial);获取当前时钟staticdoublegetclock(Clockc);设置时钟内部调用setclockat()staticvoidsetclock(Clockc,doublepts,intserial);设置时钟staticvoidsetclockat(Clockc,doublepts,intserial,doubletime);设置时钟速度staticvoidsetclockspeed(Clockc,doublespeed);音视频设置时钟的时候都回去跟外部时钟进行对比,防止丢帧或者丢包情况下时间差距比较大而进行的纠偏staticvoidsyncclocktoslave(Clockc,Clockslave);获取做为基准的类型音频外部时钟视频staticintgetmastersynctype(VideoStateis);获取主时间轴的时间staticdoublegetmasterclock(VideoStateis);检查外部时钟的速度staticvoidcheckexternalclockspeed(VideoStateis); 这个时钟框架也是比较简单,可以直接去看FFplay的源码,这里就不过多的叙述音视频同步时间轴 在ffplay中,我们不管是以哪个方式做为基准,都是有一个时间轴 就像这样子,有一个时钟一直在跑,所谓基于音频、视频、外部时间做为基准,也就是将那个轴的的时间做为时间轴的基准,另一个在轴参照主时间轴进行同步假如是以音频为基准,视频同步音频的方式,那么就是音频在每播放一帧的时候,就去将当前的时间同步到时间轴,视频参考时间轴做调整音频时钟设置音频时钟的设置的话需要考虑注意硬件缓存数据设置音频时钟的时候需要将pts硬件缓冲数据的播放时间详情参考ffplay中sdlaudiocallback(voidopaque,Uint8stream,intlen)setclockat(isaudclk,isaudioclock(double)(2isaudiohwbufsizeisaudiowritebufsize)isaudiotgt。bytespersec,isaudioclockserial,audiocallbacktime1000000。0); 这是就是将音频的pts硬件缓冲区里剩下的时间设置到了音频的时钟里视频时钟设置视频时钟设置的话就比较简单了,直接设置pts,在ffplay中queuepicture(VideoStateis,AVFramesrcframe,doublepts,doubleduration,int64tpos,intserial)内,我们可以直接看到,然后在videorefresh里面updatevideopts(is,vppts,vppos,vpserial);去调用了setclockstaticvoidupdatevideopts(VideoStateis,doublepts,int64tpos,intserial){updatecurrentvideoptssetclock(isvidclk,pts,serial);syncclocktoslave(isextclk,isvidclk);} 音视频同步操作音视频在同步上出的处理我们上面有简单讲到过,我们这里来详细看一下他具体是真么做的音频同步操作音频的同步操作是在audiodecodeframe()中的wantednbsamplessynchronizeaudio(is,afframenbsamples);,注意synchronizeaudio方法,我们来看他注释returnthewantednumberofsamplestogetbettersyncifsynctypeisvideoorexternalmasterclock如果同步类型为视频或外部主时钟,则返回所需的采样数来更好的同步。staticintsynchronizeaudio(VideoStateis,intnbsamples) 这个方法里面的操作有点多,我这边简单说一下这个方法,主要是利用音频时钟与主时钟相减得到差值(需要先判断音频是不是主时间轴),然后返回如果要同步需要的采样数,在audiodecodeframe()中用len2swrconvert(isswrctx,out,outcount,in,afframenbsamples);进行重采样,然后才在sdlaudiocallback()中进行播放视频同步操作视频同步操作的主要步骤是在videorefresh()方法中,我们来看一下关键的地方computenominallastduration根据当前帧和上一帧的pts计算出来上一帧显示的持续时间lastdurationvpduration(is,lastvp,vp);计算当前帧需要显示的时间delaycomputetargetdelay(lastduration,is);获取当前的时间timeavgettimerelative()1000000。0;如果当前时间小于显示时间则直接进行显示if(timeisframetimerdelay){remainingtimeFFMIN(isframetimerdelaytime,remainingtime);}更新视频的基准时间如果当前时间与基准时间偏差大于AVSYNCTHRESHOLDMAX则把视频基准时间设置为当前时间if(delay0timeisframetimerAVSYNCTHRESHOLDMAX)更新视频时间轴SDLLockMutex(ispictq。mutex);if(!isnan(vppts))updatevideopts(is,vppts,vppos,vpserial);SDLUnlockMutex(ispictq。mutex);如果队列中有未显示的帧,如果开启了丢帧处理或者不是以视频为主时间轴,则进行丢帧处理if(framequeuenbremaining(ispictq)1){Framenextvpframequeuepeeknext(ispictq);durationvpduration(is,vp,nextvp);if(!isstep(framedrop0(framedropgetmastersynctype(is)!AVSYNCVIDEOMASTER))timeisframetimerduration){framequeuenext(ispictq);}} 到这里,ffplay中主要的音视频同步就讲完了,建议去看一下ffplay的源码,多体会体会印象才会比较深刻,说实话ffplay中同步的操作是比较复杂的,我们在平常开发中要根据自己的实际业务进行一些简化和改进的,下一章我们就来写一个以音频为基准的视频播放器 原文链接:https:juejin。cnpost6844903815934640136