簡介
在開發(fā)多媒體播放器或直播系統(tǒng)時,音視頻的同步是非常關(guān)鍵且復(fù)雜的點(diǎn)。要想把音視頻同步搞明白,我們必須要了解一些基本的知識。只有了解了這些基本知識,才能為你打下理解音視頻同步的基礎(chǔ)。
本文將從下面幾個主題介紹這些知識點(diǎn):
- I/B/P幀
- PTS/DTS
- 時間基
- ffmpeg的內(nèi)部時間基
- 不同時間基的換算
I/B/P幀
- I幀是關(guān)鍵幀,它采用幀內(nèi)壓縮技術(shù);
- B幀是前后參考幀,它屬由幀間壓縮技術(shù)。也就是說在壓縮成 B幀前,它會參考它前面的非壓縮視頻幀,和后面的非壓縮的視頻幀,記錄下前后兩幀都不存放的“殘差值”,這樣可以達(dá)到更好的壓縮率;
- P幀是向前參考幀,也就是它參考的是前一個關(guān)鍵幀的數(shù)據(jù)。P幀也屬于幀間壓縮技術(shù),相對于 B幀來說,P幀的壓縮率要比B幀低。
但在直播系統(tǒng)中,很少使用B幀。主要的原因是壓縮和解碼B幀時,由于要雙向參考,所以它需要緩沖更多的數(shù)據(jù),且使用的CPU也會更高。由于實(shí)時性的要求,所以一般不使用它。不過對于播放器來說,會經(jīng)常遇到帶有B幀的H264數(shù)據(jù)。
PTS/DTS
有了上面 I/B/P幀的概念,我們再來理解 PTS/DTS 就非常容易了。PTS(Presentation TimeStamp)是渲染用的時間戳,也就是說,我們的視頻幀是按照 PTS 的時間戳來展示的。DTS(Decoding TimeStamp)解碼時間戳,是用于視頻解碼的。
那為什么有了 PTS 還要有 DTS呢?這就與我們上面所講的 I/B/P幀有關(guān)了。如果我們的視頻中沒有B幀,那顯示的幀的順序與存放的幀的順序是一樣的,此時PTS與DTS 的值就是一樣的,也就沒有存在兩個時間戳的必要了。視頻解碼與播放不是一致的
但有了B幀之后,就不是這個樣子了。我們舉個簡單的例子:
第一行,實(shí)際應(yīng)展示的順序:I B B P
第二行,在緩存區(qū)存放順序:I P B B
第三行,解碼器解碼的順序:1 4 2 3
第四行,按實(shí)際順序號展示:1 2 3 4
對于上面這個例子我們作下說明:
- 我們實(shí)際應(yīng)該展示的幀的順序是 I, B, B, P 幀解碼后的視頻幀。
- 但實(shí)際上,這些幀到達(dá)之后,在緩沖區(qū)里就按照第二行的樣子存放的。為什么會這樣呢?這是由于我上面所講的,P幀解碼時參考的是 I幀,B幀是雙向參考幀。也就是說,如果 I幀和P幀沒有解碼的話,B幀是無法進(jìn)行解碼的。基于此,為了解決這個問題就出現(xiàn)了 PTS和DTS兩個時間戳。
- 第三行是視頻幀真正的解碼順序,先解 I幀,然后是P幀,然后是第一個B幀,最后是第二個B幀。(解碼順序)
- 最終的展示順序是 I幀解碼后的視頻幀,第一個B幀解碼后的視頻幀,第二個B幀解碼后的視頻幀,最后是P幀解碼后的視頻帖。(展示順序)
時間基
有了時間戳之后,最終進(jìn)行展示時還要需要將 PTS時間戳轉(zhuǎn)成以秒為單位的時間。那這里需要向大家介紹一下 ffmpeg的時間基。
時間基是個有點(diǎn)抽象的東西, 在這里不說抽象的概念,我就把它當(dāng)成時間的單位
例如25幀的視頻,如果不存在時間基這個東西, 我們打時間戳應(yīng)該是這樣的,0-40-80-120-...-1000,以此類推,40毫秒一幀圖像
可是出現(xiàn)了時間基之后就不能這么處理了;
例如,同樣25幀的視頻, 時間基設(shè)置為1/25, 那這個1/25是什么意思呢? 就是把1秒分成25份, 你的時間戳每增加1,就代表增加了1/25秒;
視頻是25幀的, 也就是每幀之間的間隔恰好就是1/25秒, 那么,我們的時間戳就可以每次遞增1了;
同理,時間基設(shè)置為1/50, 那么我們的時間戳就可以每次遞增2了;
時間基其實(shí)就是就是時間刻度。我們以幀率為例,如果每秒鐘的幀率是 25幀,那么它的時間基(時間刻度)就是 1/25。也就是說每隔1/25 秒后,顯示一幀。
所以如我們當(dāng)前的時間是 100, 時間基是 1/25,那么轉(zhuǎn)成秒的時間是多少呢? 100*(1/時間基),也就是100 * 1/25 = 4秒。是不是非常的簡單?
ffmpeg內(nèi)部時間基
ffmpeg內(nèi)部有一個時間基。即我們通過所見到的 AV_TIME_BASE。它在ffmpeg內(nèi)部定義如下,1000 000毫秒:
#define AV_TIME_BASE 1000000
它還有一種分?jǐn)?shù)所表式法:
#define AV_TIME_BASE_Q (AVRational){1, AV_TIME_BASE}
在 ffmpeg中進(jìn)行換算,將不同時間基的值轉(zhuǎn)成按秒為單位的值計(jì)算如下:
timestamp(秒) = pts * av_q2d(time_base)
這里引入了 av_q2d 這個函數(shù),它的定義非常簡單:
typedef struct AVRational{
int num; //分子
int den; //分母
} AVRational;
static inline double av_q2d(AVRational a){
/**
* Convert rational to double.
* @param a rational to convert
**/
return a.num / (double) a.den;
}
不同時間基的換算
ffmpeg有好幾種不同的時間基,有時候我們需要在不同的時間基之間做換算。ffmpeg為我們提供了非常方便的函數(shù)。即
av_rescale_q()
av_rescale_q(a,b,c)的作用是,把時間戳從一個時基調(diào)整到另外一個時基時候用的函數(shù)。其中,a 表式要換算的值;b 表式原來的時間基;c表式要轉(zhuǎn)換的時間基。其計(jì)算公式為 a * b / c。
既然公式這么簡單,我們自己寫就OK了,為什么ffmpeg還要單獨(dú)提供一個函數(shù)呢?其實(shí)這個看似簡單的方法,還要考慮數(shù)值溢出的問題。所以把這塊的邏輯加上之后,就沒我們看到的這么簡單了。不過沒關(guān)系,我們只要清楚 av_rescale_q 是做什么的,怎么用就可以了。
下面我再給出兩個算計(jì)公式:
- 時間戳轉(zhuǎn)秒
time_in_seconds = av_q2d(AV_TIME_BASE_Q) * timestamp
- 秒轉(zhuǎn)時間戳
timestamp = AV_TIME_BASE * time_in_seconds
音頻的timebase是如何確定的?
與視頻類似, 只是音頻換做采樣率這個概念,采樣率代表麥克風(fēng)一秒有多少個采樣產(chǎn)生,這個頻率是極為精確的.所以我的一般做法是設(shè)置為 1/采樣率; 時間戳每次遞增輸入的采樣數(shù);