上兩篇文章單獨寫了FFmpeg解碼視頻與播放《FFmpeg音頻解碼播放》《FFmpeg視頻解碼播放》在項目中不可能直接使用一個線程來做視頻的解碼播放,通常視頻流與音頻流的解碼播放都是存在單獨的線程。線程隨機搶占cpu來執(zhí)行任務,解碼過程中各幀數(shù)據(jù)解碼的復雜度不一,導致執(zhí)行任務速度上存在差異的,就會存在聲音與畫面不同步,嚴重的影響觀看體驗。
要做音視頻的同步就要引入幀的緩沖對列,來緩存視頻解碼的幀數(shù)據(jù),通過調節(jié)音頻播放線程或畫面渲染線程控制播放的速度來達到聲音與畫面同步,也可以解決因為解碼復雜度的不一導致解碼的抖動導致的視頻播放卡頓,影響到視頻播放。
機器是并不能像我們?nèi)艘粯尤ジ兄曇襞c畫面是否在同一時間上播放。因此在視頻錄制過程中都會給每幀數(shù)據(jù)標記一個時間戳,以方便來確定什么時刻去播放。這樣就能嚴格的將聲音與畫面對應起來。
通常視頻畫面幀率25幀每秒,聲音的采樣率44100HZ,每秒都會抽取44100個采樣點,理論上來說每生產(chǎn)一幀數(shù)據(jù)都會有一個固定的時長,實際中還是會存在略微偏差。包括聲音和畫面都是分開采集的時間上也會存在略微偏差,但在一定的閾值之內(nèi)人是感覺不到音畫的差異。聲音與畫面的同步就是控制好在同一時刻播放器該去渲染幀畫面,播放那幀音頻數(shù)據(jù)。
音視頻同步的三種方式:
1.以聲音為播放基準,調控畫面渲染的速度
2.以畫面渲染為基準,調控聲音播放速度快慢
3.參考一個外部時鐘,將聲音與畫面同步至此時間
通常我們都采用第一種方式,以聲音為基準去調控畫面的播放速度,一般畫面的播放速度要比聲音播放要快,人耳對聲音的快慢感知度是比較強的,能夠快速的感知聲音的不正常(快或者是慢),對于畫面的渲染其實就是一張張的圖片刷新,反而人眼的感知度卻沒有那么強。
具體看看怎么實現(xiàn)畫面同步到音頻的先來了解一下FFmpeg 中一些時間相關概念
DTS與PTS
每幀數(shù)據(jù)都有標識DTS(解碼時間戳)和PTS(顯示時間戳),音頻的PTS和DTS是一致的,而某些視頻中因為有B幀的存在DTS和PTS不一至,DTS與PTS用來確定該幀數(shù)據(jù)的解碼時刻與顯示時刻。
時間基:time_base(時間的基準,作為時間的度量單位)
對于視頻流與音頻流的time_base不一定是相同的,因此在做同步的時候要取對應流中的時間基作為計算標準來計算當前的時間。time_base簡單的理解就是一個時間的度量單位,通過當前幀的PTS*time_base就可以獲取到當前的實際播放的時間。
獲取time_base
timeBase = pFormatContext->streams[stream_index]->time_base;
計算當前幀實際時間
double times = av_frame_get_best_effort_timestamp(pFrame) * av_q2d(timeBase);
畫面同步聲音,在音頻播放線程不斷的去計算當前播放的音頻幀的實際時間,在畫面渲染線程獲取當前畫面的播放實際時間,兩個時間做比較,如果畫面當前時間大于音頻播放時間,計算出畫面應該延時的時間。將畫面渲染線程進入休眠等待音頻追上畫面播放再去播放。如果畫面當前時間小于音頻時間(這種情況比較少出現(xiàn),一般畫面的速度都是快與聲音的播放速度),當時間差大于一點的閾值這時候我們可以不進行該幀的渲染(跳幀丟幀去追上音頻的播放)
double defaultDelayTime = 0.04;
//計算出畫面應該延時多少
double VideoPlayer::get_current_frame_delay_time(double audioTime,AVFrame *pFrame) {
//獲取當前將要播放的視頻幀時間
double times = av_frame_get_best_effort_timestamp(pFrame) * av_q2d(timeBase);
//記錄視頻畫面的當前時間
currentTime = times;
// 計算音頻時間與視頻時間相差多少
double diffTime = audioTime - currentTime;
if (diffTime > 0.016 || diffTime < -0.016) {
if (diffTime > 0.016) {
delayTime = delayTime * 2 / 3;
} else if (diffTime < -0.016) {
delayTime = delayTime * 3 / 2;
}
if (delayTime < defaultDelayTime / 2) {
delayTime = defaultDelayTime * 2 / 3;
} else if (delayTime > defaultDelayTime * 2) {
delayTime = defaultDelayTime * 3 / 2;
}
}
if (diffTime >= 0.25) {
delayTime = 0;
} else if (diffTime <= -0.25) {
delayTime = defaultDelayTime * 2;
}
return delayTime;
}