天天看點

RTP協定之Header結構解析

實時傳輸協定 RTP,RTP 提供帶有實時特性的端對端資料傳輸服務,傳輸的資料如:互動式的音頻和視訊。那些服務包括有效載荷類型定義,序列号,時間戳和傳輸監測控制。應用程式在 UDP 上運作 RTP 來使用它的多路技術和 checksum 服務。2 種協定都提供傳輸協定的部分功能。不過,RTP 可能被其他适當的下層網絡和傳輸協定使用。如

果下層網絡支援,RTP 支援資料使用多點傳播分發機制轉發到多個目的地。

注意 RTP 本身沒有提供任何的機制來確定實時的傳輸或其他的服務品質保證,而是由低層的服務來完成。它不保證傳輸或防止亂序傳輸,它不假定下層網絡是否可靠,是否按順序傳送資料包。RTP 包含的序列号允許接受方重構發送方的資料包順序,但序列号也用來确定一個資料包的正确位置,例如,在視訊解碼的時候不用按順序的對資料包進行解碼。

但是 RTP 原先的設計是用來滿足多參與者的多媒體會議的需要,它沒有限定于專門的應用。連續資料的儲存,互動分布式仿真,動态标記,以及控制和測量應用程式也可能會适合使用 RTP。

1、RTP 固定頭結構

RTP 固定頭結構如圖2所示。

RTP協定之Header結構解析

圖1 RTP固定頭格式

前 12 個位元組出現在每個 RTP 包中,僅僅在被混合器插入時,才出現 CSRC 識别符清單。各個域的含義如下所示:

(1)版本(V):2 比特,此域定義了 RTP 的版本。此協定定義的版本是 2。(值 1 被 RTP 草案版本使用,值 0 用在最初"vat"語音工具使用的協定中。)

(2)填充(P):1 比特,若填料比特被設定,則此包包含一到多個附加在末端的填充比特,填充比特不算作負載的一部分。填充的最後一個位元組指明可以忽略多少個填充比特。填充可能用于某些具有固定長度的加密算法,或者用于在底層資料單元中傳輸多個 RTP 包。

(3)擴充(X):1 比特,若設定擴充比特,固定頭(僅)後面跟随一個頭擴充。

(4)CSRC 計數(CC):4 比特,CSRC 計數包含了跟在固定頭後面 CSRC 識别符的數目。

(5)标志(M):1 比特,标志的解釋由具體協定規定。它用來允許在比特流中标記重要的事件,如幀邊界。

(6)負載類型(PT):7 比特,此域定義了負載的格式,由具體應用決定其解釋,協定可以規定負載類型碼和負載格式之間一個預設的比對。其他的負載類型碼可以通過非 RTP 方法動态定義。RTP發送端在任意給定時間發出一個單獨的 RTP 負載類型;此域不用來複用不同的媒體流。

(7)序列号(sequence number):16 比特,每發送一個 RTP 資料包,序列号加 1,接收端可以據此檢測丢包和重建包序列。序列号的初始值是随機的(不可預測),以使即便在源本身不加密時(有時包要通過翻譯器,它會這樣做),對加密算法泛知的普通文本攻擊也會更加困難。

(8)時間戳(timestamp) :32 比特,時間戳反映了 RTP 資料包中第一個位元組的采樣時間。時鐘頻率依賴于負載資料格式,并在描述檔案(profile)中進行描述。也可以通過 RTP 方法對負載格式動态描述。

如果 RTP 包是周期性産生的,那麼将使用由采樣時鐘決定的名義上的采樣時刻,而不是讀取系統時間。例如,對一個固定速率的音頻,采樣時鐘将在每個周期内增加 1。如果一個音頻從輸入裝置中讀取含有 160 個采樣周期的塊,那麼對每個塊,時間戳的值增加 160。時間戳的初始值應當是随機的,就像序号一樣。幾個連續的 RTP 包如果是同時産生的。如:屬于同一個視訊幀的 RTP 包,将有相同的序列号。

不同媒體流的 RTP 時間戳可能以不同的速率增長。而且會有獨立的随機偏移量。是以,雖然這些時間戳足以重構一個單獨的流的時間,但直接比較不同的媒體流的時間戳不能進行同步。對于每一個媒體,我們把與采樣時刻相關聯的 RTP 時間戳與來自于參考時鐘上的時間戳(NTP)相關聯。是以參考時鐘的時間戳就是資料的采樣時間。(即:RTP 時間戳可用來實作不同媒體流的同步,NTP 時間戳解決了 RTP 時間戳有随機偏移量的問題。)參考時鐘用于同步所有媒體的共同時間。這一時間戳對(RTP 時間戳和 NTP 時間戳),用于判斷 RTP 時間戳和 NTP 時間戳的對應關系,以進行媒體流的同步。它們不是在每一個資料包中都被發送,而在發送速率更低的 RTCP 的 SR(發送者報告)中。

如果傳輸的資料是存貯好的,而不是實時采樣得到的,那麼會使用從參考時鐘得到的虛的表示時間線(virtual presentation timeline)。以确定存貯資料中的每個媒體下一幀或下一

個單元應該呈現的時間。此種情況下 RTP 時間戳反映了每一個單元應當回放的時間。真正的回放将由接收者決定。

(9)SSRC:32 比特,用以識别同步源。辨別符被随機生成,以使在同一個 RTP 會話期中沒有任何兩個同步源有相同的 SSRC 識别符。盡管多個源選擇同一個 SSRC 識别符的機率很低,所有 RTP 實作工具都必須準備檢測和解決沖突。若一個源改變本身的源傳輸位址,必須選擇新的SSRC 識别符,以避免被當作一個環路源。

RTP 包流的源,用 RTP 報頭中 32 位數值的SSRC 辨別符進行辨別,使其不依賴于網絡位址。一個同步源的所有包構成了相同計時和序列号空間的一部分,這樣接收方就可以把一個同步源的包放在一起,來進行重放。

舉些同步源的例子,像來自同一信号源的包流的發送方,如麥克風、攝影機、RTP 混頻器就是同步源。一個同步源可能随着時間變化而改變其資料格式,如音頻編碼。SSRC 辨別符是一個随機選取的值,它在特定的 RTP 會話中是全局唯一(globally unique)的。參與者并不需要在一個多媒體會議的所有 RTP 會話中,使用相同的 SSRC 辨別符;SSRC 辨別符的綁定通過RTCP。如果參與者在一個 RTP 會話中生成了多個流,例如來自多個攝影機,則每個攝影機都必須辨別成單獨的同步源。

(10)CSRC 清單:0 到 15 項,每項 32 比特,CSRC 清單識别在此包中負載的所有貢獻源。識别符的數目在 CC 域中給定。若有貢獻源多于 15 個,僅識别 15 個。CSRC 識别符由混合器插入,并列出所有貢獻源的 SSRC 識别符。例如語音包,混合産生新包的所有源的 SSRC 辨別符都被列出,以在接收端處正确訓示參與者。

若一個 RTP 包流的源,對由 RTP 混頻器生成的組合流起了作用,則它就是一個作用源。對特定包的生成起作用的源,其 SSRC 辨別符組成的清單,被混頻器插入到包的 RTP 報頭中。這個清單叫做 CSRC 表。相關應用的例子如,在音頻會議中,混頻器向所有的說話人(talker)指出,誰的話語(speech)将被組合到即将發出的包中,即便所有的包都包含在同一個(混頻器的)SSRC 辨別符中,也可讓聽者(接收者)可以清楚誰是目前說話人。

2、RTP擴充頭結構

RTP 提供擴充機制以允許實作個性化:某些新的與負載格式獨立的功能要求的附加資訊在RTP 資料標頭中傳輸。設計此方法可以使其它沒有擴充的互動忽略此頭擴充。RTP擴充頭格式如圖2所示。

RTP協定之Header結構解析

圖2 RTP擴充頭格式

若 RTP 固定頭中的擴充比特位置 1,則一個長度可變的頭擴充部分被加到 RTP 固定頭之後。頭擴充包含 16 比特的長度域,訓示擴充項中 32 比特字的個數,不包括 4 個位元組擴充頭(是以零是有效值)。RTP 固定頭之後隻允許有一個頭擴充。為允許多個互操作實作獨立生成不同的頭擴充,或某種特定實作有多種不同的頭擴充,擴充項的前 16 比特用以識别辨別符或參數。這 16 比特的格式由具體實作的上層協定定義。基本的 RTP 說明并不定義任何頭擴充本身。

3、RTP包解析

RTP包解析示例如下所示:

static int parsingRTPPacket(uint8_t *data, size_t size) {  

    if (size < 12) {  

        //Too short to be a valid RTP header.  

        return -1;  

    }  

    if ((data[0] >> 6) != 2) {  

        //Currently, the version is 2, if is not 2, unsupported.  

    if (data[0] & 0x20) {  

        // Padding present.  

        size_t paddingLength = data[size - 1];  

        if (paddingLength + 12 > size) {  

            return -1;  

        }  

        size -= paddingLength;  

    int numCSRCs = data[0] & 0x0f;  

    size_t payloadOffset = 12 + 4 * numCSRCs;  

    if (size < payloadOffset) {  

        // Not enough data to fit the basic header and all the CSRC entries.  

    if (data[0] & 0x10) {  

        // Header extension present.  

        if (size < payloadOffset + 4) {  

            // Not enough data to fit the basic header, all CSRC entries and the first 4 bytes of the extension header.  

        const uint8_t *extensionData = &data[payloadOffset];  

        size_t extensionLength = 4 * (extensionData[2] << 8 | extensionData[3]);  

        if (size < payloadOffset + 4 + extensionLength) {  

        payloadOffset += (4 + extensionLength);  

    uint32_t rtpTime = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7];  

    uint32_t srcId = data[8] << 24 | data[9] << 16 | data[10] << 8 | data[11];  

    uint32_t seqNum = data[2] << 8 | data[3];  

    return 0;  

}  

繼續閱讀