十一 、h264 RTP傳輸詳解(3)
設計模式
書接上回:H264FUAFragmenter又對數據作了什麼呢?網絡
[cpp] view plaincopy架構
void H264FUAFragmenter::doGetNextFrame() app
{ ide
if (fNumValidDataBytes == 1) { 函數
// We have no NAL unit data currently in the buffer. Read a new one: this
fInputSource->getNextFrame(&fInputBuffer[1], fInputBufferSize - 1, spa
afterGettingFrame, this, FramedSource::handleClosure, this); .net
} else { 設計
// We have NAL unit data in the buffer. There are three cases to consider:
// 1. There is a new NAL unit in the buffer, and it's small enough to deliver
// to the RTP sink (as is).
// 2. There is a new NAL unit in the buffer, but it's too large to deliver to
// the RTP sink in its entirety. Deliver the first fragment of this data,
// as a FU-A packet, with one extra preceding header byte.
// 3. There is a NAL unit in the buffer, and we've already delivered some
// fragment(s) of this. Deliver the next fragment of this data,
// as a FU-A packet, with two extra preceding header bytes.
if (fMaxSize < fMaxOutputPacketSize) { // shouldn't happen
envir() << "H264FUAFragmenter::doGetNextFrame(): fMaxSize ("
<< fMaxSize << ") is smaller than expected\n";
} else {
fMaxSize = fMaxOutputPacketSize;
}
fLastFragmentCompletedNALUnit = True; // by default
if (fCurDataOffset == 1) { // case 1 or 2
if (fNumValidDataBytes - 1 <= fMaxSize) { // case 1
memmove(fTo, &fInputBuffer[1], fNumValidDataBytes - 1);
fFrameSize = fNumValidDataBytes - 1;
fCurDataOffset = fNumValidDataBytes;
} else { // case 2
// We need to send the NAL unit data as FU-A packets. Deliver the first
// packet now. Note that we add FU indicator and FU header bytes to the front
// of the packet (reusing the existing NAL header byte for the FU header).
fInputBuffer[0] = (fInputBuffer[1] & 0xE0) | 28; // FU indicator
fInputBuffer[1] = 0x80 | (fInputBuffer[1] & 0x1F); // FU header (with S bit)
memmove(fTo, fInputBuffer, fMaxSize);
fFrameSize = fMaxSize;
fCurDataOffset += fMaxSize - 1;
fLastFragmentCompletedNALUnit = False;
}
} else { // case 3
// We are sending this NAL unit data as FU-A packets. We've already sent the
// first packet (fragment). Now, send the next fragment. Note that we add
// FU indicator and FU header bytes to the front. (We reuse these bytes that
// we already sent for the first fragment, but clear the S bit, and add the E
// bit if this is the last fragment.)
fInputBuffer[fCurDataOffset - 2] = fInputBuffer[0]; // FU indicator
fInputBuffer[fCurDataOffset - 1] = fInputBuffer[1] & ~0x80; // FU header (no S bit)
unsigned numBytesToSend = 2 + fNumValidDataBytes - fCurDataOffset;
if (numBytesToSend > fMaxSize) {
// We can't send all of the remaining data this time:
numBytesToSend = fMaxSize;
fLastFragmentCompletedNALUnit = False;
} else {
// This is the last fragment:
fInputBuffer[fCurDataOffset - 1] |= 0x40; // set the E bit in the FU header
fNumTruncatedBytes = fSaveNumTruncatedBytes;
}
memmove(fTo, &fInputBuffer[fCurDataOffset - 2], numBytesToSend);
fFrameSize = numBytesToSend;
fCurDataOffset += numBytesToSend - 2;
}
if (fCurDataOffset >= fNumValidDataBytes) {
// We're done with this data. Reset the pointers for receiving new data:
fNumValidDataBytes = fCurDataOffset = 1;
}
// Complete delivery to the client:
FramedSource::afterGetting(this);
}
}
當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中也是沒問題的.