live555學習筆記11-h264 RTP傳輸詳解(3)


十一  h264 RTP傳輸詳解(3)


書接上回:H264FUAFragmenter又對數據做了什么呢?

  1. void H264FUAFragmenter::doGetNextFrame()  
  2. {  
  3.     if (fNumValidDataBytes == 1) {  
  4.         // We have no NAL unit data currently in the buffer.  Read a new one:   
  5.         fInputSource->getNextFrame(&fInputBuffer[1], fInputBufferSize - 1,  
  6.                 afterGettingFrame, this, FramedSource::handleClosure, this);  
  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中也是沒問題的.


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com