C語言將音視頻時(shí)鐘同步封裝成通用模塊的方法
前言
編寫視頻播放器時(shí)需要實(shí)現(xiàn)音視頻的時(shí)鐘同步,這個(gè)功能是不太容易實(shí)現(xiàn)的。雖然理論通常是知道的,但是不同過實(shí)際的調(diào)試很難寫出可用的時(shí)鐘同步功能。其實(shí)也是有可以參考的代碼,ffplay中實(shí)現(xiàn)了3種同步,但實(shí)現(xiàn)邏輯較為復(fù)雜,比較難直接提取出來使用。筆者通過參考ffplay在自己的播放器中實(shí)現(xiàn)了時(shí)鐘同步,參考。在實(shí)現(xiàn)過程中發(fā)現(xiàn)此功能可以做成一個(gè)通用的模塊,在任何音視頻播放的場(chǎng)景都可以使用。
一、視頻時(shí)鐘
1、時(shí)鐘計(jì)算方法
視頻的時(shí)鐘基于視頻幀的時(shí)間戳,由于視頻是通過一定的幀率渲染的,采用直接讀取當(dāng)前時(shí)間戳的方式獲取時(shí)鐘會(huì)造成一定的誤差,精度不足。我們要獲取準(zhǔn)確連續(xù)的時(shí)間,應(yīng)該使用一個(gè)系統(tǒng)的時(shí)鐘,視頻更新時(shí)記錄時(shí)鐘的起點(diǎn),用系統(tǒng)時(shí)鐘偏移后作為視頻時(shí)鐘,這樣才能得到足夠精度的時(shí)鐘。流程如下:
每次渲染視頻幀時(shí),更新視頻時(shí)鐘起點(diǎn)
視頻時(shí)鐘起點(diǎn) = 系統(tǒng)時(shí)鐘 − 視頻時(shí)間戳 視頻時(shí)鐘起點(diǎn)=系統(tǒng)時(shí)鐘-視頻時(shí)間戳 視頻時(shí)鐘起點(diǎn)=系統(tǒng)時(shí)鐘−視頻時(shí)間戳
任意時(shí)刻獲取視頻時(shí)鐘
視頻時(shí)鐘 = 系統(tǒng)時(shí)鐘 − 視頻時(shí)鐘起點(diǎn) 視頻時(shí)鐘=系統(tǒng)時(shí)鐘-視頻時(shí)鐘起點(diǎn) 視頻時(shí)鐘=系統(tǒng)時(shí)鐘−視頻時(shí)鐘起點(diǎn)
代碼示例如下:
定義相關(guān)變量
//視頻時(shí)鐘起點(diǎn),單位秒 double videoStartTime=0;
更新視頻時(shí)鐘起點(diǎn)
//更新視頻時(shí)鐘(或者說矯正更準(zhǔn)確)起點(diǎn),pts為當(dāng)前視頻幀的時(shí)間戳,getCurrentTime為獲取當(dāng)前系統(tǒng)時(shí)鐘的時(shí)間,單位秒 videoStartTime=getCurrentTime()-pts;
獲取視頻時(shí)鐘
//獲取視頻時(shí)鐘,單位秒 double videoTime=getCurrentTime()-videoStartTime;
2、同步視頻時(shí)鐘
有了上述時(shí)鐘的計(jì)算方法,我們可以獲得一個(gè)準(zhǔn)確的視頻時(shí)鐘。為了確保視頻能夠在正確的時(shí)間渲染我們還需要進(jìn)行視頻渲染時(shí)的時(shí)鐘同步。
同步流程如下所示,流程圖中的“更新視頻時(shí)鐘起點(diǎn)”和“獲取視頻時(shí)鐘”與一節(jié)的計(jì)算方法直接對(duì)應(yīng)。
核心代碼示例如下:
/// <summary> /// 同步視頻時(shí)鐘 /// 視頻幀顯示前調(diào)用此方法,傳入幀的pts和duration,根據(jù)此方法的返回值確定顯示還是丟幀或者延時(shí)。 /// </summary> /// <param name="pts">視頻幀pts,單位為s</param> /// <param name="duration">視頻幀時(shí)長,單位為s。</param> /// <returns>大于0則延時(shí),值為延時(shí)時(shí)長,單位s。等于0顯示。小于0丟幀</returns> double synVideo(double pts, double duration) { if (videoStartTime== 0) //初始化視頻起點(diǎn) { videoStartTime= getCurrentTime() - pts; } //以下變量時(shí)間單位為s //獲取視頻時(shí)鐘 double currentTime = getCurrentTime() - videoStartTime; //計(jì)算時(shí)間差,大于0則late,小于0則early。 double diff = currentTime - pts; //時(shí)間早了延時(shí) if (diff < -0.001) { if (diff < -0.1) { diff = -0.1; } return -diff; } //時(shí)間晚了丟幀,duration為一幀的持續(xù)時(shí)間,在一個(gè)duration內(nèi)是正常時(shí)間,加一個(gè)duration作為閾值來判斷丟幀。 if (diff > 2 * duration) { return -1; } //更新視頻時(shí)鐘起點(diǎn) videoStartTime= getCurrentTime() - pts; return 0; }
3、同步到另一個(gè)時(shí)鐘
我實(shí)現(xiàn)了視頻時(shí)鐘的同步,但有時(shí)還需要視頻同步到其他時(shí)鐘,比如同步到音頻時(shí)鐘或外部時(shí)鐘。將視頻時(shí)鐘同步到另外一個(gè)時(shí)鐘很簡(jiǎn)單,在計(jì)算出視頻時(shí)鐘偏差diff后再加上視頻時(shí)鐘與另外一個(gè)時(shí)鐘的差值就可以了。
上一節(jié)的流程圖基礎(chǔ)上添加如下加粗的步驟
上一節(jié)的代碼加入如下內(nèi)容
//計(jì)算時(shí)間差,大于0則late,小于0則early。 double diff = currentTime - pts; //同步到另一個(gè)時(shí)鐘-start double sDiff = 0; //anotherStartTime為另一個(gè)時(shí)鐘的起始時(shí)間 sDiff = videoStartTime - anotherStartTime; diff += sDiff; //同步到另一個(gè)時(shí)鐘-end //時(shí)間早了延時(shí) if (diff < -0.001)
二、音頻時(shí)鐘
1、時(shí)鐘計(jì)算方法
音頻時(shí)鐘的計(jì)算和視頻時(shí)鐘有點(diǎn)不一樣,但結(jié)構(gòu)上是差不多的,只是音頻是通過通過數(shù)據(jù)長度來計(jì)算時(shí)間的。
(1)、時(shí)間公式 公式一
時(shí)長 = 音頻數(shù)據(jù)長度( b y t e s ) / ( 聲道數(shù) ∗ 采樣率 ∗ 位深度 / 8 ) 時(shí)長=音頻數(shù)據(jù)長度(bytes)/(聲道數(shù)*采樣率*位深度/8) 時(shí)長=音頻數(shù)據(jù)長度(bytes)/(聲道數(shù)∗采樣率∗位深度/8)
代碼如下:
//聲道數(shù) int channels=2; //采樣率 int samplerate=48000; //位深 int bitsPerSample=32; //數(shù)據(jù)長度 int dataSize=8192; //時(shí)長,單位秒 double duration=(double)dataSize/(channels*samplerate*bitsPerSample/8); //duration 值為:0.0426666...
公式二
時(shí)長 = 采樣數(shù) / 采樣率
時(shí)長=采樣數(shù)/采樣率 時(shí)長=采樣數(shù)/采樣率
代碼如下:
//采樣數(shù) int samples=1024; //采樣率 int samplerate=48000; //時(shí)長,單位秒 double duration=(double)samples/samplerate; //duration值為0.021333333...
(2)、計(jì)算方法
計(jì)算音頻時(shí)鐘首先需要記錄音頻的時(shí)間戳,計(jì)算音頻時(shí)間戳需要將每次播放的音頻數(shù)據(jù)轉(zhuǎn)換成時(shí)長累加起來如下:(其中n表示累計(jì)的播放次數(shù))
音頻時(shí)間戳 = ∑ i = 0 n 音頻數(shù)據(jù)長度( b y t e s ) i / ( 聲道數(shù) ∗ 采樣率 ∗ 位深度 / 8 ) 音頻時(shí)間戳=\sum_{i=0}^n 音頻數(shù)據(jù)長度(bytes)i/(聲道數(shù)*采樣率*位深度/8) 音頻時(shí)間戳=i=0∑n?音頻數(shù)據(jù)長度(bytes)i/(聲道數(shù)∗采樣率∗位深度/8)
或者
音頻時(shí)間戳 = ∑ i = 0 n 采樣數(shù) i / 采樣率 音頻時(shí)間戳=\sum_{i=0}^n 采樣數(shù)i/采樣率 音頻時(shí)間戳=i=0∑n?采樣數(shù)i/采樣率
有了音頻時(shí)間戳就可可以計(jì)算出音頻時(shí)鐘的起點(diǎn)
音頻時(shí)鐘起點(diǎn) = 系統(tǒng)時(shí)鐘 − 音頻時(shí)間戳 音頻時(shí)鐘起點(diǎn)=系統(tǒng)時(shí)鐘-音頻時(shí)間戳 音頻時(shí)鐘起點(diǎn)=系統(tǒng)時(shí)鐘−音頻時(shí)間戳
通過音頻時(shí)鐘起點(diǎn)就可以計(jì)算音頻時(shí)鐘了
音頻時(shí)鐘 = 系統(tǒng)時(shí)鐘 − 音頻時(shí)鐘起點(diǎn) 音頻時(shí)鐘=系統(tǒng)時(shí)鐘-音頻時(shí)鐘起點(diǎn) 音頻時(shí)鐘=系統(tǒng)時(shí)鐘−音頻時(shí)鐘起點(diǎn)
代碼示例:
定義相關(guān)變量
//音頻時(shí)鐘起點(diǎn),單位秒 double audioStartTime=0; //音頻時(shí)間戳,單位秒 double currentPts=0;
更新音頻時(shí)鐘(通過采樣數(shù))
//計(jì)算時(shí)間戳,samples為當(dāng)前播放的采樣數(shù) currentPts += (double)samples / samplerate; //計(jì)算音頻起始時(shí)間 audioStartTime= getCurrentTime() - currentPts;
更新音頻時(shí)鐘(通過數(shù)據(jù)長度)
currentPts += (double)bytesSize / (channels *samplerate *bitsPerSample/8); //計(jì)算音頻起始時(shí)間 audioStartTime= getCurrentTime() - currentPts;
獲取音頻時(shí)鐘
//獲取音頻時(shí)鐘,單位秒 double audioTime= getCurrentTime() - audioStartTime;
2、同步音頻時(shí)鐘
有了時(shí)間戳的計(jì)算方法,接下來就需要確定同步的時(shí)機(jī),以確保計(jì)算的時(shí)鐘是準(zhǔn)確的。通常按照音頻設(shè)備播放音頻的耗時(shí)去更新音頻時(shí)鐘就是準(zhǔn)確的。
我們根據(jù)上述計(jì)算過方法封裝一個(gè)更新音頻時(shí)鐘的方法:
/// <summary> /// 更新音頻時(shí)鐘 /// </summary> /// <param name="samples">采樣數(shù)</param> /// <param name="samplerate">采樣率</param> /// <returns>應(yīng)該播放的采樣數(shù)</returns> int synAudio(int samples, int samplerate) { currentPts += (double)samples / samplerate; //getCurrentTime為獲取當(dāng)前系統(tǒng)時(shí)鐘的時(shí)間 audioStartTime= getCurrentTime() - currentPts; return samples; } /// <summary> /// 更新音頻時(shí)鐘,通過數(shù)據(jù)長度 /// </summary> /// <param name="bytesSize">數(shù)據(jù)長度</param> /// <param name="samplerate">采樣率</param> /// <param name="channels">聲道數(shù)</param> /// <param name="bitsPerSample">位深</param> /// <returns>應(yīng)該播放的采樣數(shù)</returns> int synAudioByBytesSize(size_t bytesSize, int samplerate, int channels, int bitsPerSample) { return synAudio((double)bytesSize / (channels * bitsPerSample/8), samplerate) * (bitsPerSample/8)* channels; }
(1)、阻塞式
播放音頻的時(shí)候可以使用阻塞的方式,輸入音頻數(shù)據(jù)等待設(shè)備播放完成再返回,這個(gè)等待時(shí)間可以真實(shí)的反映音頻設(shè)備播放音頻數(shù)據(jù)的耗時(shí),我們播放完成后更新音頻時(shí)鐘即可。
(2)、回調(diào)式
回調(diào)式的寫入音頻數(shù)據(jù),比如sdl就有這種方式。我們?cè)诨卣{(diào)中更新音頻時(shí)鐘。
3、同步到另一個(gè)時(shí)鐘
音頻通常情況是不需要同步到另一個(gè)時(shí)鐘的,因?yàn)橐纛l的播放本身就不需要時(shí)鐘校準(zhǔn)。但是還是有一些場(chǎng)景需要音頻同步到另一個(gè)時(shí)鐘比如多軌道播放。
關(guān)鍵流程如下所示:
計(jì)算應(yīng)寫入數(shù)據(jù)長度的代碼示例:(其中samples為音頻設(shè)備需要寫入的采樣數(shù))
//獲取音頻時(shí)鐘當(dāng)前時(shí)間 double audioTime = getCurrentTime() - audioStartTime; double diff = 0; //計(jì)算差值,getMasterTime獲取另一個(gè)時(shí)鐘的當(dāng)前時(shí)間 diff = getMasterTime(syn) - audioTime; int oldSamples = samples; if (fabs(diff) > 0.01) //超過偏差閾值,加上差值對(duì)應(yīng)的采樣數(shù) { samples += diff * samplerate; } if (samples < 0) //最小以不播放等待 { samples = 0; } if (samples > oldSamples * 2) //最大以2倍速追趕 { samples = oldSamples * 2; } //輸出samples為寫入數(shù)據(jù)長度,后面音頻時(shí)鐘計(jì)算也以此samples為準(zhǔn)。
三、外部時(shí)鐘
播放視頻的時(shí)候還可以參照一個(gè)外部的時(shí)鐘,視頻和音頻都向外部時(shí)鐘去同步。
1、絕對(duì)時(shí)鐘
播放視頻或者音頻的時(shí)候,偶爾因?yàn)橥獠吭?,比如系統(tǒng)卡頓、網(wǎng)絡(luò)變慢、磁盤讀寫變慢會(huì)導(dǎo)致播放時(shí)間延遲了。如果一個(gè)1分鐘的視頻,播放過程中卡頓了幾秒,那最終會(huì)在1分鐘零幾秒后才能播完視頻。如果我們一定要在1分鐘將視頻播放完成,那就可以使用絕對(duì)時(shí)鐘作為外部時(shí)鐘。
本文采用的就是這種時(shí)鐘,大致實(shí)現(xiàn)步驟如下:
視頻開始播放-->設(shè)置絕對(duì)時(shí)鐘起點(diǎn)-->視頻同步到絕對(duì)時(shí)鐘-->音頻同步到絕對(duì)時(shí)鐘
四、封裝成通用模塊
1、完整代碼
/************************************************************************ * @Project: Synchronize * @Decription: 視頻時(shí)鐘同步模塊 * 這是一個(gè)通用的視頻時(shí)鐘同步模塊,支持3種同步方式:同步到視頻時(shí)鐘、同步到音頻時(shí)鐘、以及同步到外部時(shí)鐘(絕對(duì)時(shí)鐘)。 * 使用方法也比較簡(jiǎn)單,初始化之后在視頻顯示和音頻播放處調(diào)用相應(yīng)方法即可。 * 非線程安全:內(nèi)部實(shí)現(xiàn)未使用線程安全機(jī)制。對(duì)于單純的同步一般不用線程安全機(jī)制。當(dāng)需要定位時(shí)可能需要一定的互斥操作。 * 沒有特殊依賴(目前版本依賴了ffmpeg的獲取系統(tǒng)時(shí)鐘功能,如果換成c++則一行代碼可以實(shí)現(xiàn),否則需要宏區(qū)分實(shí)現(xiàn)各個(gè)平臺(tái)的系統(tǒng)時(shí)鐘獲?。? * @Verision: v1.0 * @Author: Xin Nie * @Create: 2022/9/03 14:55:00 * @LastUpdate: 2022/9/22 16:36:00 ************************************************************************ * Copyright @ 2022. All rights reserved. ************************************************************************/ /// <summary> /// 時(shí)鐘對(duì)象 /// </summary> typedef struct { //起始時(shí)間 double startTime; //當(dāng)前pts double currentPts; }Clock; /// <summary> /// 時(shí)鐘同步類型 /// </summary> typedef enum { //同步到音頻 SYNCHRONIZETYPE_AUDIO, //同步到視頻 SYNCHRONIZETYPE_VIDEO, //同步到絕對(duì)時(shí)鐘 SYNCHRONIZETYPE_ABSOLUTE }SynchronizeType; /// <summary> /// 時(shí)鐘同步對(duì)象 /// </summary> typedef struct { /// <summary> /// 音頻時(shí)鐘 /// </summary> Clock audio; /// <summary> /// 視頻時(shí)鐘 /// </summary> Clock video; /// <summary> /// 絕對(duì)時(shí)鐘 /// </summary> Clock absolute; /// <summary> /// 時(shí)鐘同步類型 /// </summary> SynchronizeType type; /// <summary> /// 估算的視頻幀時(shí)長 /// </summary> double estimateVideoDuration; /// <summary> /// 估算視頻幀數(shù) /// </summary> double n; }Synchronize; /// <summary> /// 返回當(dāng)前時(shí)間 /// </summary> /// <returns>當(dāng)前時(shí)間,單位秒,精度微秒</returns> static double getCurrentTime() { //此處用的是ffmpeg的av_gettime_relative。如果沒有ffmpeg環(huán)境,則可替換成平臺(tái)獲取時(shí)鐘的方法:?jiǎn)挝粸槊耄刃枰⒚?,相?duì)絕對(duì)時(shí)鐘都可以。 return av_gettime_relative() / 1000000.0; } /// <summary> /// 重置時(shí)鐘同步 /// 通常用于暫停、定位 /// </summary> /// <param name="syn">時(shí)鐘同步對(duì)象</param> void synchronize_reset(Synchronize* syn) { SynchronizeType type = syn->type; memset(syn, 0, sizeof(Synchronize)); syn->type = type; } /// <summary> /// 獲取主時(shí)鐘 /// </summary> /// <param name="syn">時(shí)鐘同步對(duì)象</param> /// <returns>主時(shí)鐘對(duì)象</returns> Clock* synchronize_getMasterClock(Synchronize* syn) { switch (syn->type) { case SYNCHRONIZETYPE_AUDIO: return &syn->audio; case SYNCHRONIZETYPE_VIDEO: return &syn->video; case SYNCHRONIZETYPE_ABSOLUTE: return &syn->absolute; default: break; } return 0; } /// <summary> /// 獲取主時(shí)鐘的時(shí)間 /// </summary> /// <param name="syn">時(shí)鐘同步對(duì)象</param> /// <returns>時(shí)間,單位s</returns> double synchronize_getMasterTime(Synchronize* syn) { return getCurrentTime() - synchronize_getMasterClock(syn)->startTime; } /// <summary> /// 設(shè)置時(shí)鐘的時(shí)間 /// </summary> /// <param name="syn">時(shí)鐘同步對(duì)象</param> /// <param name="pts">當(dāng)前時(shí)間,單位s</param> void synchronize_setClockTime(Synchronize* syn, Clock* clock, double pts) { clock->currentPts = pts; clock->startTime = getCurrentTime() - pts; } /// <summary> /// 獲取時(shí)鐘的時(shí)間 /// </summary> /// <param name="syn">時(shí)鐘同步對(duì)象</param> /// <param name="clock">時(shí)鐘對(duì)象</param> /// <returns>時(shí)間,單位s</returns> double synchronize_getClockTime(Synchronize* syn, Clock* clock) { return getCurrentTime() - clock->startTime; } /// <summary> /// 更新視頻時(shí)鐘 /// </summary> /// <param name="syn">時(shí)鐘同步對(duì)象</param> /// <param name="pts">視頻幀pts,單位為s</param> /// <param name="duration">視頻幀時(shí)長,單位為s。缺省值為0,內(nèi)部自動(dòng)估算duration</param> /// <returns>大于0則延時(shí)值為延時(shí)時(shí)長,等于0顯示,小于0丟幀</returns> double synchronize_updateVideo(Synchronize* syn, double pts, double duration) { if (duration == 0) //估算duration { if (pts != syn->video.currentPts) syn->estimateVideoDuration = (syn->estimateVideoDuration * syn->n + pts - syn->video.currentPts) / (double)(syn->n + 1); duration = syn->estimateVideoDuration; //只估算最新3幀 if (syn->n++ > 3) syn->estimateVideoDuration = syn->n = 0; if (duration == 0) duration = 0.1; } if (syn->video.startTime == 0) { syn->video.startTime = getCurrentTime() - pts; } //以下變量時(shí)間單位為s //當(dāng)前時(shí)間 double currentTime = getCurrentTime() - syn->video.startTime; //計(jì)算時(shí)間差,大于0則late,小于0則early。 double diff = currentTime - pts; double sDiff = 0; if (syn->type != SYNCHRONIZETYPE_VIDEO && synchronize_getMasterClock(syn)->startTime != 0) //同步到主時(shí)鐘 { sDiff = syn->video.startTime - synchronize_getMasterClock(syn)->startTime; diff += sDiff; } //時(shí)間早了延時(shí) if (diff < -0.001) { if (diff < -0.1) { diff = -0.1; } return -diff; } syn->video.currentPts = pts; //時(shí)間晚了丟幀,duration為一幀的持續(xù)時(shí)間,在一個(gè)duration內(nèi)是正常時(shí)間,加一個(gè)duration作為閾值來判斷丟幀。 if (diff > 2 * duration) { return -1; } //更新視頻時(shí)鐘 printf("video-time:%.3lfs audio-time:%.3lfs absolute-time:%.3lfs synDiff:%.4lfms diff:%.4lfms \r", pts, getCurrentTime() - syn->audio.startTime, getCurrentTime() - syn->absolute.startTime, sDiff * 1000, diff * 1000); syn->video.startTime = getCurrentTime() - pts; if (syn->absolute.startTime == 0) //初始化絕對(duì)時(shí)鐘 { syn->absolute.startTime = syn->video.startTime; } return 0; } /// <summary> /// 更新音頻時(shí)鐘 /// </summary> /// <param name="syn">時(shí)鐘同步對(duì)象</param> /// <param name="samples">采樣數(shù)</param> /// <param name="samplerate">采樣率</param> /// <returns>應(yīng)該播放的采樣數(shù)</returns> int synchronize_updateAudio(Synchronize* syn, int samples, int samplerate) { if (syn->type != SYNCHRONIZETYPE_AUDIO && synchronize_getMasterClock(syn)->startTime != 0) { //同步到主時(shí)鐘 double audioTime = getCurrentTime() - syn->audio.startTime; double diff = 0; diff = synchronize_getMasterTime(syn) - audioTime; int oldSamples = samples; if (fabs(diff) > 0.01) { samples += diff * samplerate; } if (samples < 0) { samples = 0; } if (samples > oldSamples * 2) { samples = oldSamples * 2; } } syn->audio.currentPts += (double)samples / samplerate; syn->audio.startTime = getCurrentTime() - syn->audio.currentPts; if (syn->absolute.startTime == 0) //初始化絕對(duì)時(shí)鐘 { syn->absolute.startTime = syn->audio.startTime; } return samples; } /// <summary> /// 更新音頻時(shí)鐘,通過數(shù)據(jù)長度 /// </summary> /// <param name="syn">時(shí)鐘同步對(duì)象</param> /// <param name="bytesSize">數(shù)據(jù)長度</param> /// <param name="samplerate">采樣率</param> /// <param name="channels">聲道數(shù)</param> /// <param name="bitsPerSample">位深</param> /// <returns>應(yīng)該播放的數(shù)據(jù)長度</returns> int synchronize_updateAudioByBytesSize(Synchronize* syn, size_t bytesSize, int samplerate, int channels, int bitsPerSample) { return synchronize_updateAudio(syn, bytesSize / (channels * bitsPerSample/8), samplerate) * (bitsPerSample /8)* channels; }
五、使用示例
1、基本用法
(1)、初始化
Synchronize syn; memset(&syn,0,sizeof(Synchronize));
(2)、設(shè)置同步類型
設(shè)置同步類型,默認(rèn)不設(shè)置則為同步到音頻
//同步到音頻 syn->type=SYNCHRONIZETYPE_AUDIO; //同步到視頻 syn->type=SYNCHRONIZETYPE_VIDEO; //同步到絕對(duì)時(shí)鐘 syn->type=SYNCHRONIZETYPE_ABSOLUTE;
(3)、視頻同步
在視頻渲染處調(diào)用,如果只有視頻沒有音頻,需要注意將同步類型設(shè)置為SYNCHRONIZETYPE_VIDEO、或SYNCHRONIZETYPE_ABSOLUTE。
//當(dāng)前幀的pts,單位s double pts; //當(dāng)前幀的duration,單位s double duration; //視頻同步 double delay =synchronize_updateVideo(&syn,pts,duration); if (delay > 0) //延時(shí) { //延時(shí)delay時(shí)長,單位s } else if (delay < 0) //丟幀 { } else //播放 { }
(4)、音頻同步
在音頻播放處調(diào)用
//將要寫入的采樣數(shù) int samples; //音頻的采樣率 int samplerate; //時(shí)鐘同步,返回的samples為實(shí)際寫入的采樣數(shù),將要寫入的采樣數(shù)不能變,實(shí)際采樣數(shù)需要壓縮或拓展到將要寫入的采樣數(shù)。 samples = synchronize_updateAudio(&syn, samples, samplerate);
(5)、獲取播放時(shí)間
獲取當(dāng)前播放時(shí)間
//返回當(dāng)前時(shí)鐘,單位s。 double cursorTime=synchronize_getMasterTime(&syn);
(6)、暫停
暫停后直接將時(shí)鐘重置即可。但在重新開始播放之前將無法獲取正確的播放時(shí)間。
void pause(){ //暫停邏輯 ... //重置時(shí)鐘 synchronize_reset(&syn); }
(7)、定位
定位后直接將時(shí)鐘重置即可,需要注意多線程情況下避免重置時(shí)鐘后又被更新。
void seek(double pts){ //定位邏輯 ... //重置時(shí)鐘 synchronize_reset(&syn); }
在音頻解碼或即將播放處,校正音頻定位后的時(shí)間戳。
//音頻定位后第一幀的時(shí)間戳。 double pts; synchronize_setClockTime(&syn, &syn.audio, pts);
總結(jié)
以上就是今天要講的內(nèi)容,本文簡(jiǎn)單介紹了音視頻的時(shí)鐘同步的原理以及具體實(shí)現(xiàn),其中大部分原理參考了ffplay,做了一定的簡(jiǎn)化。本文提供的只是其中一種音視頻同步方法,其他方法比如視頻的同步可以直接替換時(shí)鐘,視頻直接參照給定的時(shí)鐘去做同步也是可以的。音頻時(shí)鐘的同步策略比如采樣數(shù)的計(jì)算也可以根據(jù)具體情況做調(diào)整??偟膩碚f,這是一個(gè)通用的音視頻同步模塊,能夠適用于一般的視頻播放需求,可以很大程度的簡(jiǎn)化實(shí)現(xiàn)。
附錄 1、獲取系統(tǒng)時(shí)鐘
由于完整代碼的獲取系統(tǒng)時(shí)鐘的方法依賴于ffmpeg環(huán)境,考慮到不需要ffmpeg的情況下需要自己實(shí)現(xiàn),這里貼出一些平臺(tái)獲取系統(tǒng)時(shí)鐘的方法
(1)、Windows
#include<Windows.h> /// <summary> /// 返回當(dāng)前時(shí)間 /// </summary> /// <returns>當(dāng)前時(shí)間,單位秒,精度微秒</returns> static double getCurrentTime() { LARGE_INTEGER ticks, Frequency; QueryPerformanceFrequency(&Frequency); QueryPerformanceCounter(&ticks); return (double)ticks.QuadPart / (double)Frequency.QuadPart; }
(2)、C++11
#include<chrono> /// <summary> /// 返回當(dāng)前時(shí)間 /// </summary> /// <returns>當(dāng)前時(shí)間,單位秒,精度微秒</returns> static double getCurrentTime() { return std::chrono::time_point_cast <std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now()).time_since_epoch().count() / 1e+9; }
到此這篇關(guān)于c語言將音視頻時(shí)鐘同步封裝成通用模塊的文章就介紹到這了,更多相關(guān)c語言音視頻時(shí)鐘內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vc控制臺(tái)程序關(guān)閉事件時(shí)的處理方式及注意點(diǎn)詳解
在本篇文章里小編給大家整理的是一篇關(guān)于vc控制臺(tái)程序關(guān)閉事件時(shí)的正確處理方式的相關(guān)知識(shí)點(diǎn)內(nèi)容,對(duì)此有需求的朋友們可以參閱下。2021-12-12C++關(guān)鍵字mutable學(xué)習(xí)筆記
這篇文章主要為大家介紹了C++關(guān)鍵字mutable學(xué)習(xí)筆記,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10ubuntu系統(tǒng)下C++調(diào)用matlab程序的方法詳解
學(xué)習(xí)c++與matlab混合編程一般是通過c++調(diào)用matlab函數(shù),因?yàn)閙atlab具有強(qiáng)大的數(shù)學(xué)函數(shù)庫,然而vc++具有界面設(shè)計(jì)靈活的優(yōu)點(diǎn),下面這篇文章主要給大家介紹了關(guān)于在ubuntu系統(tǒng)下C++調(diào)用matlab程序的方法,需要的朋友可以參考下。2017-08-08C++中std::count函數(shù)介紹和使用場(chǎng)景
std::count函數(shù)是一個(gè)非常實(shí)用的算法,它可以幫助我們快速統(tǒng)計(jì)給定值在指定范圍內(nèi)的出現(xiàn)次數(shù),本文主要介紹了C++中std::count函數(shù)介紹和使用場(chǎng)景,感興趣的可以了解一下2024-02-02C語言驅(qū)動(dòng)開發(fā)之通過ReadFile與內(nèi)核層通信
驅(qū)動(dòng)與應(yīng)用程序的通信是非常有必要的,內(nèi)核中執(zhí)行代碼后需要將其動(dòng)態(tài)顯示給應(yīng)用層。為了實(shí)現(xiàn)內(nèi)核與應(yīng)用層數(shù)據(jù)交互則必須有通信的方法,微軟為我們提供了三種通信方式,本文先來介紹通過ReadFile系列函數(shù)實(shí)現(xiàn)的通信模式2022-09-09C++實(shí)現(xiàn)LeetCode(199.二叉樹的右側(cè)視圖)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(199.二叉樹的右側(cè)視圖),本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08