Live555源代碼解讀(10)

十一  、h264 RTP傳輸詳解(3)
設計模式

書接上回:H264FUAFragmenter又對數據作了什麼呢?網絡

[cpp] view plaincopy架構

  1. void H264FUAFragmenter::doGetNextFrame()  app

  2. {  ide

  3.     if (fNumValidDataBytes == 1) {  函數

  4.         // We have no NAL unit data currently in the buffer.  Read a new one:  this

  5.         fInputSource->getNextFrame(&fInputBuffer[1], fInputBufferSize - 1,  spa

  6.                 afterGettingFrame, this, FramedSource::handleClosure, this);  .net

  7.     } else {  設計

  8.         // We have NAL unit data in the buffer.  There are three cases to consider:  

  9.         // 1. There is a new NAL unit in the buffer, and it's small enough to deliver  

  10.         //    to the RTP sink (as is).  

  11.         // 2. There is a new NAL unit in the buffer, but it's too large to deliver to  

  12.         //    the RTP sink in its entirety.  Deliver the first fragment of this data,  

  13.         //    as a FU-A packet, with one extra preceding header byte.  

  14.         // 3. There is a NAL unit in the buffer, and we've already delivered some  

  15.         //    fragment(s) of this.  Deliver the next fragment of this data,  

  16.         //    as a FU-A packet, with two extra preceding header bytes.  

  17.   

  18.         if (fMaxSize < fMaxOutputPacketSize) { // shouldn't happen  

  19.             envir() << "H264FUAFragmenter::doGetNextFrame(): fMaxSize ("  

  20.                     << fMaxSize << ") is smaller than expected\n";  

  21.         } else {  

  22.             fMaxSize = fMaxOutputPacketSize;  

  23.         }  

  24.   

  25.         fLastFragmentCompletedNALUnit = True; // by default  

  26.         if (fCurDataOffset == 1) { // case 1 or 2  

  27.             if (fNumValidDataBytes - 1 <= fMaxSize) { // case 1  

  28.                 memmove(fTo, &fInputBuffer[1], fNumValidDataBytes - 1);  

  29.                 fFrameSize = fNumValidDataBytes - 1;  

  30.                 fCurDataOffset = fNumValidDataBytes;  

  31.             } else { // case 2  

  32.                 // We need to send the NAL unit data as FU-A packets.  Deliver the first  

  33.                 // packet now.  Note that we add FU indicator and FU header bytes to the front  

  34.                 // of the packet (reusing the existing NAL header byte for the FU header).  

  35.                 fInputBuffer[0] = (fInputBuffer[1] & 0xE0) | 28; // FU indicator  

  36.                 fInputBuffer[1] = 0x80 | (fInputBuffer[1] & 0x1F); // FU header (with S bit)  

  37.                 memmove(fTo, fInputBuffer, fMaxSize);  

  38.                 fFrameSize = fMaxSize;  

  39.                 fCurDataOffset += fMaxSize - 1;  

  40.                 fLastFragmentCompletedNALUnit = False;  

  41.             }  

  42.         } else { // case 3  

  43.             // We are sending this NAL unit data as FU-A packets.  We've already sent the  

  44.             // first packet (fragment).  Now, send the next fragment.  Note that we add  

  45.             // FU indicator and FU header bytes to the front.  (We reuse these bytes that  

  46.             // we already sent for the first fragment, but clear the S bit, and add the E  

  47.             // bit if this is the last fragment.)  

  48.             fInputBuffer[fCurDataOffset - 2] = fInputBuffer[0]; // FU indicator  

  49.             fInputBuffer[fCurDataOffset - 1] = fInputBuffer[1] & ~0x80; // FU header (no S bit)  

  50.             unsigned numBytesToSend = 2 + fNumValidDataBytes - fCurDataOffset;  

  51.             if (numBytesToSend > fMaxSize) {  

  52.                 // We can't send all of the remaining data this time:  

  53.                 numBytesToSend = fMaxSize;  

  54.                 fLastFragmentCompletedNALUnit = False;  

  55.             } else {  

  56.                 // This is the last fragment:  

  57.                 fInputBuffer[fCurDataOffset - 1] |= 0x40; // set the E bit in the FU header  

  58.                 fNumTruncatedBytes = fSaveNumTruncatedBytes;  

  59.             }  

  60.             memmove(fTo, &fInputBuffer[fCurDataOffset - 2], numBytesToSend);  

  61.             fFrameSize = numBytesToSend;  

  62.             fCurDataOffset += numBytesToSend - 2;  

  63.         }  

  64.   

  65.         if (fCurDataOffset >= fNumValidDataBytes) {  

  66.             // We're done with this data.  Reset the pointers for receiving new data:  

  67.             fNumValidDataBytes = fCurDataOffset = 1;  

  68.         }  

  69.   

  70.         // Complete delivery to the client:  

  71.         FramedSource::afterGetting(this);  

  72.     }  

  73. }  

當fNumValidDataBytes等於1時,代表buffer(fInputBuffer)中沒有Nal Unit數據,那麼就讀入一個新的.從哪裏讀呢?還記得前面講過的嗎?H264FUAFragmenter在第一次讀數據時代替了H264VideoStreamFramer,同時也與H264VideoStreamFramer還有ByteStreamFileSource手牽着腳,腳牽着手造成了鏈結構.文件數據從ByteStreamFileSource讀入,經H264VideoStreamFramer處理傳給H264FUAFragmenter.ByteStreamFileSource返回給H264VideoStreamFramer一段數據,H264VideoStreamFramer返回一個H264FUAFragmenter一個Nal unit .
H264FUAFragmenter對Nal Unit作了什麼呢?先看註釋:
當咱們有了nal unit,要處理3種狀況:
1有一個完整的nal unit,而且它小到可以被打包進rtp包中.
2有一個完整的nal unit,可是它很大,那麼就得爲它分片傳送了,把第一片打入一個FU-A包,此時利用了緩衝中前面的一個字節的頭部.
3一個nal unit的已被髮送了一部分,那麼咱們繼續按FU-A包發送.此時利用了緩衝中前面的處理中已使用的兩個字節的頭部.
fNumValidDataBytes是H264FUAFragmenter緩衝fInputBuffer中有效數據的字節數.能夠看到fNumValidDataBytes重置時被置爲1,爲何不是0呢?由於fInputBuffer的第一個字節一直被留用做AU-A包的頭部.若是是single nal打包,則從fInputBuffer的第二字節開始把nal unit複製到輸出緩衝fTo,若是是FU-A包,則從fInputBuffer的第一字節開始複製.
結合下文,能夠很容易地把此段函數看明白(轉自http://blog.csdn.net/perfectpdl/article/details/6633841)
---------------------------------------------

H.264 視頻 RTP 負載格式

1. 網絡抽象層單元類型 (NALU)

NALU 頭由一個字節組成, 它的語法以下:

      +---------------+
      |0|1|2|3|4|5|6|7|
      +-+-+-+-+-+-+-+-+
      |F|NRI|  Type   |
      +---------------+

F: 1 個比特.
  forbidden_zero_bit. 在 H.264 規範中規定了這一位必須爲 0.

NRI: 2 個比特.
  nal_ref_idc. 取 00 ~ 11, 彷佛指示這個 NALU 的重要性, 如 00 的 NALU 解碼器能夠丟棄它而不影響圖像的回放. 不過通常狀況下不太關心

這個屬性.

Type: 5 個比特.
  nal_unit_type. 這個 NALU 單元的類型. 簡述以下:

  0     沒有定義
  1-23  NAL單元  單個 NAL 單元包.
  24    STAP-A   單一時間的組合包
  25    STAP-B   單一時間的組合包
  26    MTAP16   多個時間的組合包
  27    MTAP24   多個時間的組合包
  28    FU-A     分片的單元
  29    FU-B     分片的單元
  30-31 沒有定義

2. 打包模式

  下面是 RFC 3550 中規定的 RTP 頭的結構.

       0                   1                   2                   3
       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |V=2|P|X|  CC   |M|     PT      |       sequence number         |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                           timestamp                           |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |           synchronization source (SSRC) identifier            |
      +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
      |            contributing source (CSRC) identifiers             |
      |                             ....                              |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

  負載類型 Payload type (PT): 7 bits
  序列號 Sequence number (SN): 16 bits
  時間戳 Timestamp: 32 bits
  
  H.264 Payload 格式定義了三種不一樣的基本的負載(Payload)結構. 接收端可能經過 RTP Payload 
  的第一個字節來識別它們. 這一個字節相似 NALU 頭的格式, 而這個頭結構的 NAL 單元類型字段
  則指出了表明的是哪種結構,

  這個字節的結構以下, 能夠看出它和 H.264 的 NALU 頭結構是同樣的.
      +---------------+
      |0|1|2|3|4|5|6|7|
      +-+-+-+-+-+-+-+-+
      |F|NRI|  Type   |
      +---------------+
  字段 Type: 這個 RTP payload 中 NAL 單元的類型. 這個字段和 H.264 中類型字段的區別是, 當 type
  的值爲 24 ~ 31 表示這是一個特別格式的 NAL 單元, 而 H.264 中, 只取 1~23 是有效的值.
   
  24    STAP-A   單一時間的組合包
  25    STAP-B   單一時間的組合包
  26    MTAP16   多個時間的組合包
  27    MTAP24   多個時間的組合包
  28    FU-A     分片的單元
  29    FU-B     分片的單元
  30-31 沒有定義

  可能的結構類型分別有:

  1. 單一 NAL 單元模式
     即一個 RTP 包僅由一個完整的 NALU 組成. 這種狀況下 RTP NAL 頭類型字段和原始的 H.264的
  NALU 頭類型字段是同樣的.

  2. 組合封包模式
    便可能是由多個 NAL 單元組成一個 RTP 包. 分別有4種組合方式: STAP-A, STAP-B, MTAP16, MTAP24.
  那麼這裏的類型值分別是 24, 25, 26 以及 27.

  3. 分片封包模式
    用於把一個 NALU 單元封裝成多個 RTP 包. 存在兩種類型 FU-A 和 FU-B. 類型值分別是 28 和 29.

2.1 單一 NAL 單元模式

  對於 NALU 的長度小於 MTU 大小的包, 通常採用單一 NAL 單元模式.
  對於一個原始的 H.264 NALU 單元常由 [Start Code] [NALU Header] [NALU Payload] 三部分組成, 其中 Start Code 用於標示這是一個

NALU 單元的開始, 必須是 "00 00 00 01" 或 "00 00 01", NALU 頭僅一個字節, 其後都是 NALU 單元內容.
  打包時去除 "00 00 01" 或 "00 00 00 01" 的開始碼, 把其餘數據封包的 RTP 包便可.

       0                   1                   2                   3
       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |F|NRI|  type   |                                               |
      +-+-+-+-+-+-+-+-+                                               |
      |                                                               |
      |               Bytes 2..n of a Single NAL unit                 |
      |                                                               |
      |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                               :...OPTIONAL RTP padding        |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+


  若有一個 H.264 的 NALU 是這樣的:

  [00 00 00 01 67 42 A0 1E 23 56 0E 2F ... ]

  這是一個序列參數集 NAL 單元. [00 00 00 01] 是四個字節的開始碼, 67 是 NALU 頭, 42 開始的數據是 NALU 內容.

  封裝成 RTP 包將以下:

  [ RTP Header ] [ 67 42 A0 1E 23 56 0E 2F ]

  即只要去掉 4 個字節的開始碼就能夠了.


2.2 組合封包模式

  其次, 當 NALU 的長度特別小時, 能夠把幾個 NALU 單元封在一個 RTP 包中.

  
       0                   1                   2                   3
       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                          RTP Header                           |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |STAP-A NAL HDR |         NALU 1 Size           | NALU 1 HDR    |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                         NALU 1 Data                           |
      :                                                               :
      +               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |               | NALU 2 Size                   | NALU 2 HDR    |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                         NALU 2 Data                           |
      :                                                               :
      |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                               :...OPTIONAL RTP padding        |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+


2.3 Fragmentation Units (FUs).

  而當 NALU 的長度超過 MTU 時, 就必須對 NALU 單元進行分片封包. 也稱爲 Fragmentation Units (FUs).
  
       0                   1                   2                   3
       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      | FU indicator  |   FU header   |                               |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               |
      |                                                               |
      |                         FU payload                            |
      |                                                               |
      |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                               :...OPTIONAL RTP padding        |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

      Figure 14.  RTP payload format for FU-A

   The FU indicator octet has the following format:

      +---------------+
      |0|1|2|3|4|5|6|7|
      +-+-+-+-+-+-+-+-+
      |F|NRI|  Type   |
      +---------------+

   The FU header has the following format:

      +---------------+
      |0|1|2|3|4|5|6|7|
      +-+-+-+-+-+-+-+-+
      |S|E|R|  Type   |
      +---------------+


---------------------------------------------

H264FUAFragmenter中只支持single和FU-A模式,不支持其它模式.

咱們如今還得出一個結論,咱們能夠看出RTPSink與Source怎樣分工:RTPSink只作造成通用RTP包頭的工做,各類媒體格式的Source纔是實現媒體數據RTP封包的地方,其實按習慣感受XXXRTPSink纔是進行封包的地方.可是,從文件的安排上,H264FUAFragmenter被隱藏在H264VideoRTPSink中,並在程序中暗渡陳倉地把H264VideoStreamFramer替換掉,其實仍是按習慣的架構(設計模式)來作的,因此若是把H264FUAFragmenter的工做移到H264VideoRTPSink中也是沒問題的.

相關文章
相關標籤/搜索