RFC3984是H.264的baseline碼流在RTP方式下傳輸?shù)囊?guī)范驳糯,這里只討論FU-A分包方式,以及從RTP包里面得到H.264數(shù)據(jù)和AAC數(shù)據(jù)的方法慷彤。
H.264的NAL層處理
H264以NALU(NALunit)為單位來支持編碼數(shù)據(jù)在基于分組交換技術(shù)網(wǎng)絡(luò)中傳輸娄蔼。
NALU定義了可用于基于分組和基于比特流系統(tǒng)的基本格式,同時給出頭信息底哗,從而提供了視頻編碼和外部事件的接口岁诉。
H264編碼過程中的三種不同的數(shù)據(jù)形式:
SODB 數(shù)據(jù)比特串-->最原始的編碼數(shù)據(jù),即VCL數(shù)據(jù)跋选;
RBSP 原始字節(jié)序列載荷-->在SODB的后面填加了結(jié)尾比特(RBSP trailing bits 一個bit“1”)若干比特“0”,以便字節(jié)對齊涕癣;
EBSP 擴展字節(jié)序列載荷-->在RBSP基礎(chǔ)上填加了仿校驗字節(jié)(0X03)它的原因是: 在NALU加到Annexb上時,需要添加每組NALU之前的開始碼StartCodePrefix,如果該NALU對應(yīng)的slice為一幀的開始則用4位字節(jié)表示,ox00000001,否則用3位字節(jié)表示ox000001(是一幀的一部分)坠韩。另外距潘,為了使NALU主體中不包括與開始碼相沖突的,在編碼時只搁,每遇到兩個字節(jié)連續(xù)為0音比,就插入一個字節(jié)的0x03。解碼時將0x03去掉氢惋。也稱為脫殼操作洞翩。
編碼處理過程:
1.??將VCL層輸出的SODB封裝成nal_unit,?NALU是一個通用封裝格式焰望,可以適用于有序字節(jié)流方式和IP包交換方式骚亿。
2.??針對不同的傳送網(wǎng)絡(luò)(電路交換|包交換),將nal_unit封裝成針對不同網(wǎng)絡(luò)的封裝格式(比如把nalu封裝成rtp包)熊赖。
---------------------------------------------------
處理過程一来屠,VCL數(shù)據(jù)封裝成NALU
---------------------------------------------------
VCL層輸出的比特流SODB(String Of Data Bits),到nal_unit之間震鹉,經(jīng)過了以下三步處理:
1.SODB字節(jié)對齊處理后封裝成RBSP(Raw Byte Sequence Payload)俱笛。
2.為防止RBSP的字節(jié)流與有序字節(jié)流傳送方式下的SCP(start_code_prefix_one_3bytes,0x000001)出現(xiàn)字節(jié)競爭情形足陨,循環(huán)檢測RBSP前三個字節(jié)嫂粟,在出現(xiàn)字節(jié)競爭時在第三字節(jié)前加入emulation_prevention_three_byte(0x03),具體方法:
nal_unit( NumBytesInNALunit ) {
forbidden_zero_bit
nal_ref_idc
nal_unit_type
NumBytesInRBSP = 0
for( i = 1; i < NumBytesInNALunit; i++ ) {
if( i + 2 < NumBytesInNALunit && next_bits( 24 ) = = 0x000003 ) {
rbsp_byte[ NumBytesInRBSP++ ]
rbsp_byte[ NumBytesInRBSP++ ]
i += 2
emulation_prevention_three_byte /* equal to 0x03 */
} else
rbsp_byte[ NumBytesInRBSP++ ]
}
}
3.?防字節(jié)競爭處理后的RBSP再加一個字節(jié)的header(forbidden_zero_bit+ nal_ref_idc+ nal_unit_type)墨缘,封裝成nal_unit.
------------------------------------------------
處理過程二,NALU的RTP打包
一、NALU打包成RTP的方式有三種:
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.
還記得前面nal_unit_type的定義吧零抬,0~23是給H264用的镊讼,24~31未使用,在rtp打包時平夜,如果一個NALU放在一個RTP包里蝶棋,可以使用NALU的nal_unit_type,但是當(dāng)需要把多個NALU打包成一個RTP包忽妒,或者需要把一個NALU打包成多個RTP包時玩裙,就定義新的type來標(biāo)識。
Type??Packet????? Typename
---------------------------------------------------------
0?????undefined???????????????????????????????????-
1-23?? NAL unit??? Single NAL unit packet perH.264
24???? STAP-A???? Single-timeaggregation packet
25???? STAP-B???? Single-timeaggregation packet
26???? MTAP16??? Multi-time aggregationpacket
27???? MTAP24??? Multi-time aggregationpacket
28???? FU-A????? Fragmentationunit
29???? FU-B????? Fragmentationunit
30-31?undefined
二段直、三種打包方式的具體格式
1 .單一 NAL 單元模式
對于 NALU 的長度小于 MTU 大小的包, 一般采用單一 NAL 單元模式.
對于一個原始的?H.264 NALU 單元常由 [Start Code] [NALU Header] [NALU Payload] 三部分組成, 其中 Start Code 用于標(biāo)示這是一個
NALU 單元的開始, 必須是 "00 00 00 01" 或 "00 00 01", NALU 頭僅一個字節(jié), 其后都是 NALU 單元內(nèi)容.
打包時去除 "00 00 01" 或 "00 00 00 01" 的開始碼, 把其他數(shù)據(jù)封包的 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 01 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|F|NRI| type??|??????????????????????????????????????????????|
+-+-+-+-+-+-+-+-+??????????????????????????????????????????????|
|??????????????????????????????????????????????????????????????|
|??????????????Bytes 2..n of a Single NALunit????????????????|
|??????????????????????????????????????????????????????????????|
|??????????????????????????????+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|??????????????????????????????:...OPTIONAL RTP padding??????? |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
如有一個 H.264 的 NALU 是這樣的:
[00 00 00 01 67 42 A0 1E 23 56 0E 2F... ]
這是一個序列參數(shù)集 NAL 單元. [00 00 00 01] 是四個字節(jié)的開始碼,67 是 NALU 頭, 42 開始的數(shù)據(jù)是 NALU 內(nèi)容.
封裝成 RTP 包將如下:
[ RTP Header ] [ 67 42 A0 1E 23 56 0E 2F]
即只要去掉 4 個字節(jié)的開始碼就可以了.
2 組合封包模式
其次, 當(dāng) 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 01 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|?????????????????????????RTP Header??????????????????????????|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|STAP-A NAL HDR|???????? NALU 1Size?????????? | NALU 1HDR??? |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|????????????????????????NALU 1 Data??????????????????????????|
:??????????????????????????????????????????????????????????????:
+??????????????+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|??????????????| NALU 2Size??????????????????| NALU 2 HDR??? |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|????????????????????????NALU 2 Data??????????????????????????|
:??????????????????????????????????????????????????????????????:
|??????????????????????????????+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|??????????????????????????????:...OPTIONAL RTP padding??????? |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
3 FragmentationUnits (FUs).
而當(dāng) 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 01 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| FU indicator |?? FUheader??|??????????????????????????????|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+??????????????????????????????|
|??????????????????????????????????????????????????????????????|
|????????????????????????FU payload???????????????????????????|
|??????????????????????????????????????????????????????????????|
|??????????????????????????????+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|??????????????????????????????:...OPTIONAL RTP padding??????? |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
????? Figure 14. RTPpayload format for FU-A
FU indicator有以下格式:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI|? Type?? |
+---------------+
FU指示字節(jié)的類型域?Type=28表示FU-A吃溅。。NRI域的值必須根據(jù)分片NAL單元的NRI域的值設(shè)置鸯檬。
FU header的格式如下:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|S|E|R|? Type?? |
+---------------+
S: 1 bit
當(dāng)設(shè)置成1,開始位指示分片NAL單元的開始决侈。當(dāng)跟隨的FU荷載不是分片NAL單元荷載的開始,開始位設(shè)為0喧务。
E: 1 bit
當(dāng)設(shè)置成1,?結(jié)束位指示分片NAL單元的結(jié)束赖歌,即,?荷載的最后字節(jié)也是分片NAL單元的最后一個字節(jié)枉圃。當(dāng)跟隨的FU荷載不是分片NAL單元的最后分片,結(jié)束位設(shè)置為0。
R: 1 bit
保留位必須設(shè)置為0庐冯,接收者必須忽略該位孽亲。
Type: 5 bits
?
1、單個NAL包單元
12字節(jié)的RTP頭后面的就是音視頻數(shù)據(jù)展父,比較簡單返劲。一個封裝單個NAL單元包到RTP的NAL單元流的RTP序號必須符合NAL單元的解碼順序。
2犯祠、FU-A的分片格式
數(shù)據(jù)比較大的H264視頻包旭等,被RTP分片發(fā)送。12字節(jié)的RTP頭后面跟隨的就是FU-A分片:
FU indicator有以下格式:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI|? Type?? |
+---------------+
FU指示字節(jié)的類型域?Type=28表示FU-A衡载。搔耕。NRI域的值必須根據(jù)分片NAL單元的NRI域的值設(shè)置。
FU header的格式如下:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|S|E|R|? Type?? |
+---------------+
S: 1 bit
當(dāng)設(shè)置成1,開始位指示分片NAL單元的開始痰娱。當(dāng)跟隨的FU荷載不是分片NAL單元荷載的開始弃榨,開始位設(shè)為0。
E: 1 bit
當(dāng)設(shè)置成1, 結(jié)束位指示分片NAL單元的結(jié)束梨睁,即, 荷載的最后字節(jié)也是分片NAL單元的最后一個字節(jié)鲸睛。當(dāng)跟隨的FU荷載不是分片NAL單元的最后分片,結(jié)束位設(shè)置為0。
R: 1 bit
保留位必須設(shè)置為0坡贺,接收者必須忽略該位官辈。
Type: 5 bits
NAL單元荷載類型定義見下表
表1.? 單元類型以及荷載結(jié)構(gòu)總結(jié)
Type??Packet????? Typename
---------------------------------------------------------
0?????undefined???????????????????????????????????-
1-23?? NALunit??? Single NAL unit packet per H.264
24????STAP-A???? Single-time aggregation packet
25????STAP-B???? Single-time aggregation packet
26????MTAP16??? Multi-time aggregation packet
27????MTAP24??? Multi-time aggregation packet
28???? FU-A?????Fragmentation unit
29????FU-B????? Fragmentationunit
30-31?undefined???????????????????????????????????-
3、拆包和解包
拆包:當(dāng)編碼器在編碼時需要將原有一個NAL按照FU-A進行分片遍坟,原有的NAL的單元頭與分片后的FU-A的單元頭有如下關(guān)系:
原始的NAL頭的前三位為FU indicator的前三位拳亿,原始的NAL頭的后五位為FU header的后五位,F(xiàn)Uindicator與FU header的剩余位數(shù)根據(jù)實際情況決定愿伴。
解包:當(dāng)接收端收到FU-A的分片數(shù)據(jù)肺魁,需要將所有的分片包組合還原成原始的NAL包時,F(xiàn)U-A的單元頭與還原后的NAL的關(guān)系如下:
還原后的NAL頭的八位是由FU indicator的前三位加FU header的后五位組成隔节,即:
nal_unit_type = (fu_indicator & 0xe0) | (fu_header & 0x1f)
4鹅经、代碼實現(xiàn)
從RTP包里面得到H264視頻數(shù)據(jù)的方法:
//?功能:解碼RTP?H.264視頻
//?參數(shù):1.RTP包緩沖地址?2.RTP包數(shù)據(jù)大小?3.H264輸出地址?4.輸出數(shù)據(jù)大小
//?返回:true:表示一幀結(jié)束??false:FU-A分片未結(jié)束或幀未結(jié)束
#define??RTP_HEADLEN?12
bool??UnpackRTPH264(?void? * ?bufIn,??int?len,???void?** ?pBufOut,??int?? * ?pOutLen)
{
* pOutLen? = ? 0 ;
if??(len? < ?RTP_HEADLEN)
{
return???false?;
}
unsigned?char?* ?src? = ?(unsigned??char* )bufIn?+ ?RTP_HEADLEN;
unsigned?char??head1?= ? * src;?//?獲取第一個字節(jié)
unsigned??char??head2?= ? * (src + 1 );?//?獲取第二個字節(jié)
unsigned??char??nal?= ?head1?& ? 0x1f;?//?獲取FU?indicator的類型域,
unsigned??char??flag?= ?head2?& ? 0xe0 ;?//?獲取FU?header的前三位怎诫,判斷當(dāng)前是分包的開始瘾晃、中間或結(jié)束
unsigned??char??nal_fua?= ?(head1?& ? 0xe0 )? | ?(head2?& ? 0x1f);?//?FU_A?nal
bool??bFinishFrame?= ??false?;
if??(nal == 0x1c )?//?判斷NAL的類型為0x1c=28,說明是FU-A分片
{?//?fu-a
if??(flag== 0x80 )?//?開始
{
* pBufOut?= ?src - 3 ;
* ((?int?* )( * pBufOut))? = ? 0x01000000 ?;?//?zyf:大模式會有問題
* ((char?* )( * pBufOut) + 4 )? = ?nal_fua;
* ?pOutLen?= ?len?- ?RTP_HEADLEN?+ ? 3 ;
}
else???if?(flag == 0x40 )?//?結(jié)束
{
* pBufOut?= ?src + 2 ;
* ?pOutLen?= ?len?- ?RTP_HEADLEN?- ? 2 ;
}
else?//?中間
{
* pBufOut?= ?src + 2 ;
* ?pOutLen?= ?len?- ?RTP_HEADLEN?- ? 2 ;
}
}
else?//?單包數(shù)據(jù)
{
* pBufOut?= ?src - 4 ;
* ((?int?* )( * pBufOut))? = ? 0x01000000 ;?//?zyf:大模式會有問題
* ?pOutLen?= ?len?- ?RTP_HEADLEN?+ ? 4 ;
}
unsigned?char?* ?bufTmp? =?(unsigned??char* )bufIn;
if??(bufTmp[ 1 ]? & ? 0x80 )
{
bFinishFrame?= ??true?;?//?rtp?mark
}
else
{
bFinishFrame?= ??false?;
}
return??bFinishFrame;
}
從RTP包里面得到AAC音頻數(shù)據(jù)的方法:
//功能:解RTP?AAC音頻包刽虹,聲道和采樣頻率必須知道酗捌。
//參數(shù):1.RTP包緩沖地址?2.RTP包數(shù)據(jù)大小?3.H264輸出地址?4.輸出數(shù)據(jù)大小
//返回:true:表示一幀結(jié)束??false:幀未結(jié)束?一般AAC音頻包比較小,沒有分片。
bool?UnpackRTPAAC(void?*?bufIn,?int?recvLen,?void**?pBufOut,??int*?pOutLen)
{
unsigned?char*??bufRecv?=?(unsigned?char*)bufIn;
//char?strFileName[20];
unsigned?char?ADTS[]?=?{0xFF,?0xF1,?0x00,?0x00,?0x00,?0x00,?0xFC};
int?audioSamprate?=?32000;//音頻采樣率
int?audioChannel?=?2;//音頻聲道?1或2
int?audioBit?=?16;//16位?固定
switch(audioSamprate)
{
case??16000:
ADTS[2]?=?0x60;
break;
case??32000:
ADTS[2]?=?0x54;
break;
case??44100:
ADTS[2]?=?0x50;
break;
case??48000:
ADTS[2]?=?0x4C;
break;
case??96000:
ADTS[2]?=?0x40;
break;
default:
break;
}
ADTS[3]?=?(audioChannel==2)?0x80:0x40;
int?len?=?recvLen?-?16?+?7;
len?<<=?5;//8bit?*?2?-?11?=?5(headerSize?11bit)
len?|=?0x1F;//5?bit????1
ADTS[4]?=?len>>8;
ADTS[5]?=?len?&?0xFF;
*pBufOut?=?(char*)bufIn+16-7;
memcpy(*pBufOut,?ADTS,?sizeof(ADTS));
*pOutLen?=?recvLen?-?16?+?7;
unsigned?char*?bufTmp?=?(unsigned?char*)bufIn;
bool?bFinishFrame?=?false;
if?(bufTmp[1]?&?0x80)
{
//DebugTrace::D("Marker");
bFinishFrame?=?true;
}
else
{
bFinishFrame?=?false;
}
return?true;
}