使用rtcp實(shí)現(xiàn)音視頻同步

image

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 格式


image.png

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。


image.png

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í)間戳同步就行.


image.png

因?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)該是下面這樣


image.png

其實(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í)際情況如下圖:

image.png

發(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è)贊,謝謝
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容