image
正文
一 基礎(chǔ)知識(shí)
音視頻同步是指音視頻的rtp時(shí)間戳同步. audio/video rtp 時(shí)間戳不能自己同步,需要audio/video rtcp同步。
1 RTCP時(shí)間戳
發(fā)送端以一定的頻率發(fā)送RTCP SR(Sender Report)這個(gè)包,SR分為視頻SR和音頻SR,SR包內(nèi)包含一個(gè)RTP時(shí)間戳和對(duì)應(yīng)的NTP時(shí)間戳,可以用<ntp,rtp>對(duì)做音視頻同步.(同步過(guò)程在后面)
Rtcp sr 格式

2 音頻時(shí)間戳
例如:一個(gè)音頻包打包20ms的數(shù)據(jù).采樣率48k.對(duì)應(yīng)的采樣數(shù)為 48000 * 20 / 1000 = 960,也就是說(shuō)每個(gè)音頻包里攜帶960個(gè)音頻采樣,因?yàn)?個(gè)采樣對(duì)應(yīng)1個(gè)時(shí)間戳,那么相鄰兩個(gè)音頻RTP包的時(shí)間戳之差就是960。

3 視頻時(shí)間戳
視頻采樣率是幀率,視頻時(shí)間戳的單位為1/90k秒.比如: 25幀,每幀40ms.40ms有多少時(shí)間戳的基本單位呢? 40除以1/90k等于3600.
擴(kuò)展內(nèi)容:
Single Nalu:如果一個(gè)視頻幀包含1個(gè)NALU,可以單獨(dú)打包成一個(gè)RTP包,那么RTP時(shí)間戳就對(duì)應(yīng)這個(gè)幀的采集時(shí)間;
FU-A:如果一個(gè)視頻幀的NALU過(guò)大(超過(guò)MTU)需要拆分成多個(gè)包,可以使用FU-A方式來(lái)拆分并打到不同的RTP包里,那么這幾個(gè)包的RTP時(shí)間戳是一樣的;
STAP-A:如果某幀較大不能單獨(dú)打包,但是該幀內(nèi)部單獨(dú)的NALU比較小,可以使用STAP-A方式合并多個(gè)NALU打包發(fā)送,但是這些NALU的時(shí)間戳必須一致,打包后的RTP時(shí)間戳也必須一致
4 ntp時(shí)間戳
NTP時(shí)間戳是從1900年1月1日00:00:00以來(lái)經(jīng)過(guò)的秒數(shù).
rtp是相對(duì)時(shí)間,ntp是絕對(duì)時(shí)間.rtp時(shí)間戳和ntp時(shí)間戳表示的意義是相同的. 可以互相轉(zhuǎn)換,rtp=f(ntp) 類似中文名張三,英文名zhangjohn.
5 播放時(shí)間
rtp時(shí)間戳如何轉(zhuǎn)換成pts即顯示時(shí)間的呢? pts= rtp 時(shí)間戳 *timebase.
例如:flv封裝格式的time_base為{1,1000},ts封裝格式的time_base為{1,90000}.
flv: pkt_pts=80,pkt_pts_time=80/1000=0.080000;
pkt_pts=120,pkt_pts_time=0.120000;
pkt_pts=160,pkt_pts_time=0.160000;
pkt_pts=200,pkt_pts_time=0.200000
ts: pkt_pts=7200,pkt_pts_time=7200/90000=0.080000;
pkt_pts=10800,pkt_pts_time=0.120000;
pkt_pts=14400,pkt_pts_time=0.160000;
pkt_pts=18000,pkt_pts_time=0.200000
總結(jié):ntp,rtp,pts表示的是同一幀的時(shí)間.ntp是絕對(duì)時(shí)間,rtp是相對(duì)時(shí)間,pts是播放時(shí)間.rtp是用頻率表示,pts是用秒表示.
如果音視頻都從0同步開(kāi)始,rtp等于pts.
如果不是同步開(kāi)始,pts需要rtp 同步加減一個(gè)offer.具體實(shí)例看下文. rtp和pts是編解碼時(shí)間即播放時(shí)間,不是傳輸時(shí)間,傳輸延時(shí)與rtp時(shí)間戳沒(méi)關(guān)系.
二 音視頻同步
音視頻時(shí)間戳增長(zhǎng)rtp增量是不同的,所以需要換算成ntp時(shí)間同步.
1 理想情況:
音視頻同時(shí)編碼,而且視頻播放頻率始終不變. 使用rtp時(shí)間戳同步就行.

因?yàn)橐粢曨l被映射到同一個(gè)時(shí)間軸上了,音頻和視頻幀間的相對(duì)關(guān)系很清楚.
同時(shí)RTP規(guī)范要求時(shí)間戳的初始值應(yīng)該是一個(gè)隨機(jī)值,那么假設(shè)音頻幀時(shí)間戳的初始值是隨機(jī)值1234,視頻幀時(shí)間戳的初始值是隨機(jī)值5678,看起來(lái)應(yīng)該是下面這樣

其實(shí)道理和上面一樣.rtp可以同步,但是這是理想情況并不是實(shí)際情況,實(shí)際情況需要進(jìn)一步處理.
2 實(shí)際情況
2.1 音視頻不是同時(shí)產(chǎn)生
RTP規(guī)范并沒(méi)有規(guī)定第一個(gè)視頻幀的時(shí)間戳和第一個(gè)音頻幀的時(shí)間戳必須或者應(yīng)該對(duì)應(yīng)到絕對(duì)時(shí)間軸的同一個(gè)點(diǎn)上 。
也就是說(shuō)開(kāi)始的一小段時(shí)間內(nèi)可能只有音頻或者視頻,比如開(kāi)始攝像頭沒(méi)有開(kāi),只有音頻沒(méi)有視頻.也可能的開(kāi)始網(wǎng)絡(luò)丟包,同時(shí)視頻的降低幀率,有可能出現(xiàn)這樣的時(shí)間戳序列:0,丟包+降幀率,16200,... 第一個(gè)視頻幀rtp timestamp 是16200,就無(wú)法映射到絕對(duì)時(shí)間軸上.
實(shí)際情況如下圖:

發(fā)送端的音視頻流并沒(méi)有對(duì)齊,但是周期地發(fā)送SR包,接收端得到音視頻SR包的RTP時(shí)間戳、NTP時(shí)間戳后通過(guò)線性回歸得到NTP時(shí)間戳Tntp和RTP時(shí)間戳Trtp時(shí)間戳的對(duì)應(yīng)關(guān)系:
- Tntp_audio = f(Trtp_audio)
- Tntp_video = f(Trtp_video)
其中Tntp = f(Trtp) = kTrtp + b 為線性函數(shù),這樣接收端每收到一個(gè)RTP包,都可以將RTP時(shí)間戳換算成NTP時(shí)間戳,從而在同一時(shí)間基準(zhǔn)下進(jìn)行音視頻同步。
notes:rtp和ntp轉(zhuǎn)換是線性關(guān)系:以最簡(jiǎn)單的情況為例:絕對(duì)時(shí)間ntp=x+80ms,rtp=8090=7200;
則rtp=(ntp-x)90,所以是線性關(guān)系.
2.2 使用rtcp同步.(這節(jié)是重點(diǎn))
分成兩種情況,a) 視頻同步到音頻; b) 音頻同步到視頻. 通常使用前者.
同步原理:將audio/video第一幀時(shí)間戳同步到同一個(gè)絕對(duì)時(shí)間上.
實(shí)例分析:
問(wèn):音頻采樣率8k,視頻幀率25. Audio從頭開(kāi)始,音頻時(shí)間戳320會(huì)在多少ms播放呢?
答:320/8=40ms.
問(wèn):那么收到的視頻幀rtp時(shí)間戳是7200應(yīng)該在多少ms播放呢?
答:7200/90=80ms播放。答案是錯(cuò)誤的.
因?yàn)榈谝粋€(gè)音頻是從0開(kāi)始播放的,但是第一個(gè)視頻幀可能是在第1ms開(kāi)始的,而不是從0ms開(kāi)始的.
如果視頻從1ms開(kāi)始,rtcp第一個(gè)音視頻幀 video rtp 是3600,它表示41ms。而audio rtp 是160表示40ms.所以先要計(jì)算audio,video的絕對(duì)時(shí)間差,倒退開(kāi)始的時(shí)間差.
視頻同步到音頻為例:
diff=(video ntp time)-(audio ntp time)=1ms (rtcp中NTP時(shí)間相減).
對(duì)應(yīng)的rtp時(shí)間相減,3600-90=3510.3510就是視頻ntp對(duì)應(yīng)的40ms絕對(duì)時(shí)間(time base).
后面rtp 7200是多少ms呢?
7200-3510=7200-(3600-90)=3600+90=40ms+1=41ms;即在播放base 40ms之后的41ms播放,等于81ms的地方播放.
后續(xù)的每個(gè)audio/video rtp timestamp減去這個(gè)base就是播放時(shí)間.實(shí)現(xiàn)同步.
音頻同步到視頻原理一樣.
如果不能理解上面的實(shí)例,可以畫(huà)圖將音視頻ntp時(shí)間,rtp時(shí)間畫(huà)到同一個(gè)時(shí)間軸上即可.
三 音視頻同步的應(yīng)用場(chǎng)景
音視頻同步應(yīng)用分成兩種:一種是用于傳輸,一種是用于播放器.
1 用于傳輸
傳輸本來(lái)可以不用同步直接轉(zhuǎn)給播放器處理,但是給第三方庫(kù)的時(shí)候需要同步,比如將流轉(zhuǎn)給ffmpeg。ffmpeg 要求轉(zhuǎn)入的流的第一幀,必須audio,video 是同步的. 不能audio從0ms開(kāi)始,video從1ms開(kāi)始,那樣送流給ffmpeg一定音視頻不同步.所以給ffmpeg之前,需要做音視頻同步.根據(jù)上面的方法就可以實(shí)現(xiàn)。
notes: ffmpeg源碼要求第一幀同步開(kāi)始:http://www.itdecent.cn/p/67d3e6a1d72e
2 播放器的同步播放
播放器在上面同步的基礎(chǔ)上,還需要增加緩存. 緩存設(shè)計(jì)分成兩種情況,視頻快于音頻和音頻快于視頻.
i) 視頻快于音頻那么擴(kuò)大視頻緩沖區(qū),縮短音頻緩沖區(qū),讓視頻幀等一等音頻幀。
ii) 音頻快于視頻那么擴(kuò)大音頻緩沖區(qū),縮短視頻緩沖區(qū),讓音頻幀等一等音頻幀
四 QA
QA1:什么情況會(huì)出現(xiàn)不同步,為什么要同步?
比如視頻第1ms才有數(shù)據(jù),但是在第0ms播放,其他幀都是提前1ms,變成音視頻不同步.
QA2:什么時(shí)候同步,需要多少次同步?
第一幀開(kāi)始同步。根據(jù)上面的算法,第幾個(gè)rtcp包都能同步,從哪里開(kāi)始同步后面就跟著全同步了.
播放當(dāng)然需要從開(kāi)始同步哈,所以都是從第一幀同步。
只需要同步一次,后面就是對(duì)于基準(zhǔn)0的播放時(shí)間了。
QA3: 音視頻同步與幀率的關(guān)系?
動(dòng)態(tài)幀率與時(shí)間戳同步?jīng)]關(guān)系,播放器會(huì)根據(jù)fps讀取正確的ts。幀率不同只是視頻包多少不同,每個(gè)包的time stamp和ntp時(shí)間都是一一對(duì)應(yīng)的.
QA4: 傳輸延時(shí)與rtp時(shí)間戳有沒(méi)有關(guān)系?
ats:20,40,60,...
vts: 41,82,123....
會(huì)不會(huì)出現(xiàn)這種時(shí)間序列,累計(jì)造成時(shí)間戳不同步。
這個(gè)數(shù)列是不對(duì).
40ms肯定能編解碼完成,不是編解碼要41ms。而是開(kāi)始第一幀發(fā)送的晚造成41ms.
數(shù)列應(yīng)該是41,81,121...
QA5: 兩個(gè)不同的主播合流,如果有一個(gè)主播的時(shí)間戳不準(zhǔn)確如何同步?
比如:第一個(gè)13:00:00: 編碼:40ms第一幀
第二個(gè)在13:00:00:30ms開(kāi)始編碼,但是它的時(shí)間有問(wèn)題,顯示0:0:0:0開(kāi)始
如何將13:00:00:30ms map 0:0:0:0.
沒(méi)有實(shí)際經(jīng)驗(yàn)。原理應(yīng)該和音視頻同步類似,同步到其中一個(gè)主播時(shí)間軸即可。
哪位網(wǎng)友有經(jīng)驗(yàn)歡迎補(bǔ)充
五 實(shí)現(xiàn)
uint32_t ntp_compact_audio = get_compact_ntp_time( ntp_audio );
uint32_t ntp_compact_video = get_compact_ntp_time( ntp_video );
gint64 diff_ms;
if( ntp_compact_video > ntp_compact_audio ) //audio_ts be ahead of video_ts
{
diff_ms = ntp_compact_video - ntp_compact_audio;
diff_ms = ( diff_ms * 1000 + ( 1 << 16 ) / 2 ) / ( 1 << 16 );
diff_ms = diff_ms > 1 ? diff_ms : 1;
uint32_t diff_ts = diff_ms * 90;
//v_sr_ts is rtp ts
base_timestamp_video = v_sr_ts - diff_ts;
base_timestamp_audio = a_sr_ts;
}
### 代碼解析:
1) 獲取音視頻時(shí)間
uint32_t ntp_compact_audio = get_compact_ntp_time( ntp_audio );
uint32_t ntp_compact_video = get_compact_ntp_time( ntp_video );
2)音頻快于視頻分支
獲取時(shí)間差
diff_ms = ntp_compact_video - ntp_compact_audio;
diff_ms = ( diff_ms * 1000 + ( 1 << 16 ) / 2 ) / ( 1 << 16 );
diff_ms = diff_ms > 1 ? diff_ms : 1;
uint32_t diff_ts = diff_ms * 90;
3) 音頻時(shí)間基直接設(shè)置
base_timestamp_audio = a_sr_ts;
4) 視頻向音頻同步
//v_sr_ts is rtp ts
base_timestamp_video = v_sr_ts - diff_ts
部分引用:https://blog.csdn.net/sonysuqin/article/details/107297157
#### 本文如對(duì)您有幫助,請(qǐng)隨手點(diǎn)個(gè)贊,謝謝