H264 RTP包解析


1.  預備

      視頻

             由一副副連續的圖像構成,由於數據量比較大,因此為了節省帶寬以及存儲,就需要進行必要的壓縮與解壓縮,也就是編解碼。

      h264裸碼流

             對一個圖像或者一個視頻序列進行壓縮,即產生碼流,采用H264編碼后形成的碼流就是h264裸碼流。

      碼流傳輸

             發送端將H264裸碼流打包后進行網絡傳輸,接收端接收后進行組包還原裸碼流,然后可以再進行存儲,轉發,或者播放等等相關的處理。

             存儲轉發可以直接使用裸碼流,播放則需要進行解碼和顯示處理

      解碼顯示:       

             一般會解成YUV數據,然后交給顯示相關模塊做處理,如使用openGL或者D3D等進行渲染(采用3d的方式來顯示2d的圖像稱為渲染)

              解碼又分軟件解碼和硬件解碼,

              軟解一般ffmpeg

              硬解則各不同,由各硬件廠商開放sdk來處理,如:hisi,intel media sdk,nvida gpu,apple ios videotoolbox等。

  

2.  h264碼流傳輸

      類似發送網頁文本數據有http一樣,視頻數據在網絡上傳輸也有專門的網絡協議來支持,如:rtsp,rtmp等

      由於碼流是一幀一幀的圖片數據,所以傳輸的時候也是一幀幀來傳輸的,因此這里就會涉及到各種類型的幀處理了

                            I 幀: 參考幀或者關鍵幀,可以理解為是一幀完整畫面,解碼時只需要本幀數據就解碼成一幅完整的圖片,數據量比較大。

                            P幀: 差別幀,只有與前面的幀或I幀的差別數據,需要依賴前面已編碼圖象作為參考圖象,才能解碼成一幅完整的圖片,數據量小。

                            B幀: 雙向差別幀,也就是B幀記錄的是本幀與前后幀的差別,需要依賴前后幀和I幀才能解碼出一幅完整的圖片,數據量小。

      由於i幀比較大,已經超出mtu最大1500,所以需要拆包分片傳輸,這里說的拆包發送不是指發送超過1500的數據包時tcp的分段傳輸或者upd的ip分片傳輸,而是指rtp協議本身對264的拆包。

      rtp打包后將數據交給tcp或者upd進行傳輸的時候就已經控制在1500以內,這樣可以提高傳輸效率,避免一個I幀萬一丟失就會造成花屏或者重傳造成延時卡頓等等問題。

      (順便提一句,rtmp打包就比較簡單,由於是基於tcp的協議,大包直接交給tcp去做分段傳輸,rtmp通過設置合適的trunk size去發送一幀幀數據)

      既然要進行拆包發送與接收,就少不了需要相關的包結構以及打包組包了,繼續。。。。。。

                            

 3.   H264在網絡傳輸的單元:NALU

        NALU結構:NALU header(1byte) + NALU payload

        header 部分可以判斷類型等信息,從右往左5個bit位

SPS:  0x67    header & 0x1F = 7  I Frame: 0x65  header & 0x1F = 5 
PPS:  0x68    header & 0x1F = 8   P Frame: 0x41   header & 0x1F = 1
SEI:  0x66    header & 0x1F = 6  

        payload 部分可以簡單理解為 編碼后的264幀數據      

        詳細可以去查閱 h264 NALU 語法結構

      

4.   h264的RTP打包

               1.  單NALU:   P幀或者B幀比較小的包,直接將NALU打包成RTP包進行傳輸    RTP header(12bytes) + NALU header (1byte) + NALU payload

               2.  多NALU:   特別小的包幾個NALU放在一個RTP包中  

               3.  FUs(Fragment Units):   I幀長度超過MTU的,就必須要拆包組成RTP包了,有FU-A,FU-B

                    RTP header (12bytes)+ FU Indicator (1byte)  +  FU header(1 byte) + NALU payload

                         看到這里會不禁思考,

                     1. NALU頭不見了,如何判斷類型?實際上NALU頭被分散填充到FU indicator和FU header里面了

                         bit位按照從左到右編號0-7來算,nalu頭中0-2前三個bit放在FU indicator的0-2前三個bit中,后3-7五個bit放入FU header的后3-7五個中      

//NALU header = (FU indicator & 0xe0) | (FU header & 0x1F)   取FU indicator前三和FU header后五  
headerStart[1] = (headerStart[0]&0xE0)|(headerStart[1]&0x1F);  

 因此查看I幀p幀類型,遇到FU分片的,直接看第二個字節,即Fu header后五位,這個跟直接看NALU頭並無差異,一般有:0x85,0x05,0x45等等

                   2.  多個RTP包如何還原組合成回一個完整的I幀? 在FU header中有標記為判斷

                         照舊從左到右,Fu header前兩個bit表示start和end標記,start為1表示一個i幀分片開始,end為1表示一個i幀分片結束

                  3.   如何查看是一個I幀分片開始?   

                        看第一個字節FU Indicator,照舊從左到右,Fu Indicator前三個bit是NALU頭的前三個bit,后五位為類型FU-A:28(11100),FU-B:29(11101)

                         RTP抓包看下來整個字節是0x7c開頭

                         如果不是分片,第一字節就是NALU頭,如:0x67,0x68,0x41等

 

5. 抓包分析如下圖:

        --->RTP包中接收的264包是不含有0x00,0x00,0x00,0x01頭的,這部分是rtp接收以后,另外再加上去的,解碼的時候再做判斷的

      1. SPS

       

     2.  I幀分片開始,第一個字節FU Indicator,0x7c, 后五位11100,28,FU-A

                                第二個字節FU Header,0x85,前兩個bit start位1,end位0 表示 分片開始,后五個bit值5,I幀

       

        I幀分片,0x7c開頭,第二個字節0x05, FU Header start和end位 0,后五個bit值5,I幀

         

        I幀分片結束,7c開頭,第二個字節0x45,FU Headr,start 0,end1,后五個bit值5,I幀, Mark標記一幀結束

          

 

    3.  p幀,第一個字節:0x41

          

 

       4. P幀分片開始:第一個字節FU Indicator,0x7c, 后五位11100,28,FU-A

                                   第二個字節FU Header,0x81, 前兩個bit start位1,end位0 表示 分片開始,后五個bit 值是1,p幀

           

           P幀分片結束:0x5c開頭,第二個字節0x41,FU Headr,start 0,end1,后五個bit值1,P幀, Mark標記一幀結束

         

 

6.  參考live555相關的源碼如下:

Boolean H264VideoRTPSource
::processSpecialHeader(BufferedPacket* packet,
                       unsigned& resultSpecialHeaderSize) {
  unsigned char* headerStart = packet->data();
  unsigned packetSize = packet->dataSize();
  unsigned numBytesToSkip;
  
  // Check the 'nal_unit_type' for special 'aggregation' or 'fragmentation' packets:
  if (packetSize < 1) return False;
  fCurPacketNALUnitType = (headerStart[0]&0x1F); //FU Indicator后五位即NALU類型 0x1F = 0001 1111 switch (fCurPacketNALUnitType) {
  case 24: { // STAP-A
    numBytesToSkip = 1; // discard the type byte
    break;
  }
  case 25: case 26: case 27: { // STAP-B, MTAP16, or MTAP24
    numBytesToSkip = 3; // discard the type byte, and the initial DON
    break;
  }
  case 28: case 29: { // // FU-A or FU-B
    // For these NALUs, the first two bytes are the FU indicator and the FU header.
    // If the start bit is set, we reconstruct the original NAL header into byte 1:
    if (packetSize < 2) return False;
    unsigned char startBit = headerStart[1]&0x80; //FU Header start標記位 0x80= 1000 0000 unsigned char endBit = headerStart[1]&0x40; //FU Header End標記位 0x40= 0100 0000 if (startBit) {
      fCurrentPacketBeginsFrame = True;

      headerStart[1] = (headerStart[0]&0xE0)|(headerStart[1]&0x1F); //還原NALU頭
      numBytesToSkip = 1;
    } else {
      // The start bit is not set, so we skip both the FU indicator and header:
      fCurrentPacketBeginsFrame = False;
      numBytesToSkip = 2;
    }
    fCurrentPacketCompletesFrame = (endBit != 0);
    break;
  }
  default: {
    // This packet contains one complete NAL unit:
    fCurrentPacketBeginsFrame = fCurrentPacketCompletesFrame = True;   //默認沒有分片,完整的NALU
    numBytesToSkip = 0;
    break;
  }
  }

  resultSpecialHeaderSize = numBytesToSkip;
  return True;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM