NALU數據打RTP包流程詳解

 最近在看RTP發送H264數據的文章,感覺很亂,沒有比較清晰易懂的教程,自己整理了一下各種資料,備忘!


  --------Part A  ----

   先說說H264數據,H264在網絡傳輸的是NALU(NAL單元),NALU的結構是:NAL頭+RBSP,實際傳輸中的數據流如圖所示:

  

NALU頭用來標識後面的RBSP是什麼類型的數據,他是否會被其他幀參考以及網絡傳輸是否有錯誤。

NALU頭結構爲1個字節,既 forbidden_bit(1bit) + nal_reference_bit(2bit) + nal_unit_type(5bit),如下


1.forbidden_bit: 禁止位,初始爲0,當網絡發現NAL單元有比特錯誤時可設置爲1,以便接收方糾錯或丟掉該單元。

2.nal_reference_bit:nal重要性指示,標誌該NAL單元的重要性,值越大,越重要,解碼器在解碼處理不過來的時候,可以丟掉重要性爲0的NALU

3.nal_unit_type:NALU類型取值如下表所示:



 H264的NALU頭裏面.nal_unit_type的有效值只會是1~23,在打RTP包時,在某些情況下我們會填24-31,後面我們會解釋,如下:


       圖3(打RTP包時會用到的擴展類型)


對H264數據打RTP包而言,我們瞭解這些基礎知識就ok了,深入瞭解H264,請查閱其它資料.



   --------Part B  ----

發送RTP數據報時,需要設置頭部(Header)和負載(Payload)兩部分,也就是數據頭+數據這樣的形式。先來看下Header


V版版號(2bit),

P填充位(1bit),

X擴展位(1bit),

CCCSRC的計數位(4bits);

M標記位(1bit;

PT有效載荷的類型(7bits,比如h264視頻對應的值就是PT= 96

sequence number2Bytes,RTP包的發送序號;

timestamp時間戳位(4Bytes);

SSRC同步標識位(4Bytes);

CSRC不是RTP必須的(4Bytes)。


 這樣的話,用一個結構體來存儲RTPHeader數據,如下,

typedef struct 
{
    /**//* 1byte (0) */
    unsigned char csrc_len:4;        /* expect 0, csrc計數器,沒啥用*/
    unsigned char extension:1;       /* expect 1, see RTP_OP below ,擴展位,不用關心*/
    unsigned char padding:1;         /*expect 0 , 填充位,不用關心*/
    unsigned char version:2;        /* expect 2,版本號,固定爲2 */
    /**//* 1byte (1) */
    unsigned char payload:7;        /* RTP_PAYLOAD_RTSP , 負載類型*/
    unsigned char marker:1;        /* expect 1,是否是尾包,後面解釋 */
    /**//* 2bytes (2, 3) */
    unsigned short seq_no;         /* RTP包序號,比如100,101,102,類推 */  
    /**//* 4bytes (4-7) */
    unsigned long timestamp;      /* 時間戳位 */     
    /**//* 4bytes (8-11) */
    unsigned long ssrc;            /* stream number is used here. 在本RTP會話中全局唯一就行*/
    // CSRC(4Bytes)不是RTP必須的,因此不定義它
} RTP_FIXED_HEADER;

在講負載(Payload)前,我們先看看RTPUDP發送h264數據時的3種打包情況。

由於UDP數據報長度超過1500字節時(俗稱MTU),會自動拆分發送,增大了丟包概率,那麼去除UDP數據報頭以及RTPHeader部分,一般設置Payload部分最大長度爲1400字節即可,那麼對H264NALU單元打RTP就意味着3種情況.


第一:RTP包裏只包含一個NALU,(它的數據小於1400字節)

第二:RTP包裏只包含N個NALU,(NNALU的數據累加小於1400字節)

第三:NALU數據大於1400字節, (比如5400字節,5400/1400>3.8,要拆分分4RTP)


但是我們處理H264數據時,一般是對NALU逐一進行處理的,因此我們只考慮第一和第三種情況。

我們來看第一種情況,RTP包裏只包含一個NALU的情況,這時的RTP負載(Payload)部分如下圖


 從內存分佈上可以理解爲 RTP PlayLoad = [PlayLoad_head] + [RBSP]

我們知道一個 NALU = [NAL_head] + [RBSP]

我們還發現,PlayLoad_head 和 NAL_head 都是一個字節,結構相同,如下


在這種情況下,PlayLoad_head 和 NAL_head的結構和值都是一樣的,因此

RTP PlayLoad = NALU= [NAL_head] + [RBSP]


假如一個H264的NALU是這樣的:[00 00 00 01 67 42 A0 1E 23 56 0E 2F ... ]

 這是一個序列參數集NAL單元。[00 00 00 01]是四個字節的開始碼,67是NALU頭,42開始的數據是NALU內容(RBSP),封裝成RTP包將如下:  [RTP HEADER] + [67 42 A0 1E 23 56 0E 2F ...]


發送單一NALU單元的僞代碼如下:

{
     RTP_FIXED_HEADER        *rtp_hdr;  
     rtp_hdr =(RTP_FIXED_HEADER*)&sendBuf[0];  
     rtp_hdr->payload     = H264;   //也就是96
     rtp_hdr->version     = 2;  
     rtp_hdr->marker      = 0;  
     rtp_hdr->ssrc        = htonl(10);   //全局唯一就行
  
   if(nalu_t->len<=MAX_RTP_PKT_LENGTH)
    {  
    rtp_hdr->marker = 1;  //一個Nalu單元只用了一個RTP包,當然也算尾包
    rtp_hdr->seq_no     = htons(seq_num ++);  //序號+1
  
    nalu_payload    = &sendBuf[12];   //RTP頭就12字節
    // nalu_t->buf第一個字節就是NALU_head
    memcpy(nalu_payload, nalu_t->buf, nalu_t->len); 
  
    ts_current=ts_current+timestamp_increse;  //時間戳
    rtp_hdr->timestamp   = htonl(ts_current);  
    bytes = nalu_t->len + 12 ;  
  	
    ::send(socketFd, sendBuf, bytes, 0);  
}


我們來看第三種情況,一個NALU拆分爲NRTP包的情況,這時的RTP負載(Payload)部分如下圖


這種模式叫分片封包模式,其中 FU IndicatorFU header都是1個字節,結構如下:



我們發現FU Indicator的結構和NALU頭結構是一樣的,不過值不完全一樣

U Indicator->F    = NALU頭結構-> F
FU Indicator->NRI  = NALU頭結構->NRI
FU Indicator->Type  = 28 (也就是擴展類型,FU-A)
另外

FU header->Type = NALU頭結構->Tpye
FU header->S = 開始位,設置成1,指示分片NAL單元的開始,也就是開始包
FU header->D = 結束位,設置成1,指示分片NAL單元的結束,也就是尾包
FU header->R = 保留位必須設置爲0


分包模式下的RTP包如下:

[RTP HEADER] + [FU Indicator] + [FU header] + [RBSP] 

分包模式的僞代碼如下:


     RTP_FIXED_HEADER        *rtp_hdr;  
     rtp_hdr =(RTP_FIXED_HEADER*)&sendBuf[0];  
     rtp_hdr->payload     = H264;  //
     rtp_hdr->version     = 2;  
     rtp_hdr->marker      = 0;  
     rtp_hdr->ssrc        = htonl(10);  


    int k=0,l=0;  
    k = nalu_t->len/MAX_RTP_PKT_LENGTH;   // 比如k爲7,表示有7個完整RTP包+1個尾包
    l = nalu_t->len%MAX_RTP_PKT_LENGTH;   // 比如l爲126,表示尾包的RBSP爲126個字節
    int t=0;  
    ts_current = ts_current+timestamp_increse;   //時間戳
    rtp_hdr->timestamp = htonl(ts_current);  

    while(t<=k)
    {  
        rtp_hdr->seq_no = htons(seq_num ++);   //序列號累加
        if(!t) //第一包
        {  
            rtp_hdr->marker=0;   // 不是尾包
            fu_ind =(FU_INDICATOR*)&sendBuf[12];  
            fu_ind->F=nalu_t->forbidden_bit;  
            fu_ind->NRI=nalu_t->nal_reference_idc>>5;  
            fu_ind->TYPE=28;  
  
            fu_hdr =(FU_HEADER*)&sendBuf[13];  
            fu_hdr->E=0;  
            fu_hdr->R=0;  
            fu_hdr->S=1;  
            fu_hdr->TYPE=nalu_t->nal_unit_type;  
  
            nalu_payload=&sendBuf[14];  
            memcpy(nalu_payload,nalu_t->buf+1,MAX_RTP_PKT_LENGTH);  
  
            bytes=MAX_RTP_PKT_LENGTH+14;  
            ::send( socketFd, sendBuf, bytes, 0 );  
            t++;  
        }
        else if(k==t)  // 尾包的情況
        {  
            rtp_hdr->marker=1;  // 尾包
            fu_ind =(FU_INDICATOR*)&sendBuf[12];  
            fu_ind->F=nalu_t->forbidden_bit;  
            fu_ind->NRI=nalu_t->nal_reference_idc>>5;  
            fu_ind->TYPE=28;  
  
            fu_hdr =(FU_HEADER*)&sendBuf[13];  
            fu_hdr->R=0;  
            fu_hdr->S=0;  
            fu_hdr->TYPE=nalu_t->nal_unit_type;  
            fu_hdr->E=1;  
  
            nalu_payload=&sendBuf[14];  
            memcpy(nalu_payload,nalu_t->buf+t*MAX_RTP_PKT_LENGTH+1,l-1);  
            bytes=l-1+14;  
            ::send( socketFd, sendBuf, bytes, 0 );  
            t++;  
        }
         else if(t<k&&0!=t) // 注意,頭包,中間包,尾包,fu_hdr->R,fu_hdr->S,fu_hdr->E 的值不一樣
         {  
            rtp_hdr->marker=0;  
            fu_ind =(FU_INDICATOR*)&sendBuf[12];  
            fu_ind->F=nalu_t->forbidden_bit;  
            fu_ind->NRI=nalu_t->nal_reference_idc>>5;  
            fu_ind->TYPE=28;  
  
            fu_hdr =(FU_HEADER*)&sendBuf[13];  
            fu_hdr->R=0; 
            fu_hdr->S=0;  
            fu_hdr->E=0;  
            fu_hdr->TYPE=nalu_t->nal_unit_type;  
  
            nalu_payload=&sendBuf[14];  
            memcpy(nalu_payload,nalu_t->buf+t*MAX_RTP_PKT_LENGTH+1,MAX_RTP_PKT_LENGTH);  
            bytes=MAX_RTP_PKT_LENGTH+14;  
            ::send( socketFd, sendBuf, bytes, 0 );  
            t++;  
        } 
   }


這裏只列出了FU-A(Type = 28)的分包模式,FU-B的分包模式沒測試過,應該差不多

測試源碼:http://download.csdn.net/detail/heker2010/9901919



參考文章:

通過RTP協議傳輸H264視頻

http://blog.csdn.net/westlor/article/details/50538058

《H264--2--語法及結構》

http://blog.csdn.net/yangzhongxuan/article/details/8003494














發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章