語音通信是實時通信,一定要保證實時性,不然用戶體驗會很糟糕。IETF設計了RTP來承載語音等實時性要求很高的數據,同時設計了RTCP來保證服務質量(RTP不保證服務質量)。在傳輸層,一般選用UDP而不是TCP來承載 RTP包。下圖給出了這三個協議所在的協議層次。
本文先簡單講一下這三個協議(網上好多文章都講,這里主要講關鍵點),然后講軟件實現注意點。
1,RTP
RTP全稱是Real-time Transport Protocol(實時傳輸協議),它是IETF提出的一個標准,對應的RFC文檔為RFC3550。一般用其承載實時性要求很高的數據形成RTP包,在語音通信中,把PCM數據編碼后得到的碼流作為RTP的payload。下圖是其包頭結構。
這里主要講一些關鍵點:
a) 包頭的版本信息等都是用幾個比特位來表示的,共兩個字節,在軟件實現時要用位域的形式表示。在大小端情況下有不同的表示方式,主要是在一個字節里大小端表示時位置要互換,具體如下圖RTP頭數據結構定義。
b) RTP包都是以網絡序/大端的形式在網絡中傳輸,這樣就有一個網絡序主機序互轉的過程。
c) M 位即Mark位,表示語音的開始,在通話剛開始的第一個語音包,M位要置1。如果 VAD使能,從VAD包切到語音包時,第一個語音包M位也要置1。
d) 在通話剛開始的第一個語音包中,sequence/timestamp/SSRC等都要是隨機值。在后續的包中,SSRC代表通話的一方,在整個通話過程中都要保持不變。Sequence要每次加一。Timestamp要依據采樣率以及幀長每次加本幀內采樣的點數值,比如8000 Hz采用率,幀長為20ms, 每次timestamp要加160。
軟件實現RTP協議時,先要初始化(主要是包頭字段的初始化)。發送方把每一幀PCM數據編碼后得到碼流,將其作為RTP的payload,同時填充好包頭中的字段,然后通過UDP socket發送到網絡中去。接收方通過UDP socket收到RTP包,解析包頭得到payload type/sequence等信息,同時也得到payload,然后將它們送給下一個模塊處理。
實現完RTP后要檢查實現的是否正確,抓包看是主要的方式。抓到包后把UDP轉換成RTP后就可以看相關信息了,主要看格式是否對以及包頭中的字段的值是否對。如果格式不對,抓包工具(wireshark)會提示。
2,RTCP
RTCP全稱是Real-time Control Protocol(實時控制協議),它也是IETF提出的一個標准,對應的RFC文檔為RFC3551,它的主要功能是:服務質量的監視與反饋、媒體間的同步。在RTP會話期間,各參與者周期性地(一般是5秒,應用層可以配)傳送RTCP包,RTCP包中含有已發送的數據包的數量、丟失的數據包的數量、數據包到達的平均時間間隔等統計信息。
RTCP協議處理機定義了五種類型的報文,它們完成接收、分析、產生和發送控制報文的功能,如下表所示:
其中SR用來使發送端周期的向所有接收端用多播方式進行報告。RR用來使接收端周期性的向所有的點用多播方式進行報告。當參加者既發又收時就發SR,只收不發時就發RR。SDES給出會話中參加者的描述,包括參加者的規范名(CNAME)。BYE用來關閉一個數據流。APP能夠定義新的分組類型。前四種類型經常用到,APP類型很少用到,我是沒用過。
這五種類型中SR應用最頻繁,就以它為例來講,其他類型可以舉一反三。它的封裝結構見下圖:
同RTP一樣,這里也主要講一些關鍵點:
a) 與RTP類似,RTCP包頭中一些值也用比特位表示,實現時也要用位域表示,也有大小端的問題。
b) 算length時,計算公式是length = size/4 -1。其中size是SR包的真實大小(單位是字節)。
c) 算周期內丟包率(fraction lost)時,是以定點小數形式表示,即 fraction lost = (周期內丟包數 << 8) / 周期內期望接收包數.
d) 算DLSR時,是以1/65536秒為單位。
軟件實現RTCP協議時,一般是幾種類型的RTCP包組成組合包,所以一般先要判斷要發幾種類型的包。當處於SendReceive模式時,要發SR/SDES包,如果要停止通話,還要發BYE包;當處於ReceiveOnly模式時,要發RR/SDES包;如果要停止通話,還要發BYE包。RTCP中有RTP包的統計,所以實現RTCP前要先在RTP中把相關的統計做好,同時還要做好sequence number的管理等。實現RTCP時發送方先要實現SR或者RR包,然后是SDES包,如果是停止通話,還要加上BYE包。實現每種RTCP包時是把相應的字段值填好,然后再把包頭填好。這些包都實現后拼在一起形成組合包,然后通過UDP socket發送到網絡中去。接收方收到RTCP組合包后也是一個包一個包的去解析,然后把相應的信息報告給上層。
實現完RTCP后同樣要檢查實現的是否正確,抓包看同樣是主要的方式。抓到包后把UDP轉換成RTCP后就可以看相關信息了,主要看格式是否對以及包中相應的字段是否對。如果格式不對,抓包工具會提示。
個人覺得實現RTP相對簡單,RTCP相對復雜一些,它是基於RTP的,有對RTP的各種統計,有對RTP sequence number的管理等。同時還要理解RTCP中為什么要設計這些字段以及它們的計算方法。這些都搞清楚了也就不難了。
3,UDP
UDP 全稱是User Datagram Protocol(用戶數據報協議),屬於傳輸層協議(跟TCP在同一層),提供面向事務的簡單不可靠信息傳送服務,在IETF中對應的RFC文檔為 RFC 768。至於為啥用UDP而不用TCP來傳輸實時性要求較高的數據,個人覺得主要有以下幾點:TCP的重傳機制(這時最主要的原因),TCP的包頭較大浪費了帶寬,TCP不支持組播。
實現時主要是調用系統提供的socket API。具體到linux上,我做過兩種實現,一種是在user space里(這也是絕大多數使用者用的方法),另外一種是在kernel space里,用kernel 提供的socket API做。不管是user space還是kernel space里實現,主要是掌握socket API的使用,都是些套路,這里就不詳細講了。UDP的socket創建好后給RTP、RTCP用(RTP、RTCP各用一個socket,各有一個port號,一般RTP用的是偶數, RTCP的port號是相對應的RTP的port號加一)。