rtmp傳輸h.264視訊流媒體的起始幀傳輸
rtmp傳輸h.264視訊流媒體是目前常見的功能,近日對其進行了一些研究及總結。
要想利用rtmp協定将h.264流媒體順利推流到rtmp伺服器,就需要将已經編碼好的h.264視訊流媒體按照rtmp協定中flv的格式的一些規則,進行頭封裝及相應的封裝才可以。
我們知道,如果想要rtmp用戶端連接配接服務端拉流,我們用戶端是怎麼知道資料源推流的視訊大小,視訊幀率等資訊呢?原因就是推流端需要提供,那怎麼提供的呢?答案就是推流端在初始連接配接到rtmp伺服器的時候要先傳送必要的SPS和PPS。
那SPS和PPS又是什麼呢?
其實在h.264中定義幀的類型有如下定義:
//H264定義的類型 values for nal_unit_type
typedef enum {
NALU_TYPE_SLICE = 1,//非關鍵幀
NALU_TYPE_DPA = 2,
NALU_TYPE_DPB = 3,
NALU_TYPE_DPC = 4,
NALU_TYPE_IDR = 5,//關鍵幀
NALU_TYPE_SEI = 6,
NALU_TYPE_SPS = 7,//SPS
NALU_TYPE_PPS = 8,//PPS
NALU_TYPE_AUD = 9,
NALU_TYPE_EOSEQ = 10,
NALU_TYPE_EOSTREAM = 11,
NALU_TYPE_FILL = 12,
#if (MVC_EXTENSION_ENABLE)
NALU_TYPE_PREFIX = 14,
NALU_TYPE_SUB_SPS = 15,
NALU_TYPE_SLC_EXT = 20,
NALU_TYPE_VDRD = 24 // View and Dependency Representation Delimiter NAL Unit
#endif
} NaluType;
SPS即Sequence Paramater Set,又稱作序列參數集。SPS中儲存了一組編碼視訊序列(Coded video sequence)的全局參數。所謂的編碼視訊序列即原始視訊的一幀一幀的像素資料經過編碼之後的結構組成的序列。而每一幀的編碼後資料所依賴的參數儲存于圖像參數集中。一般情況SPS和PPS的NAL Unit通常位于整個碼流的起始位置。但在某些特殊情況下,在碼流中間也可能出現這兩種結構,主要原因可能為:
- 解碼器需要在碼流中間開始解碼;
- 編碼器在編碼的過程中改變了碼流的參數(如圖像分辨率等);
在做視訊播放時,為了讓後續的解碼過程可以使用SPS中包含的參數,必須對其中的資料進行解析。
我們以一段H.264 資料進行解析說明SPS及PPS部分,但我們不詳細分析h.264,如需詳細檢視h.264資料解析,請看我的另一篇
,後面的資料以十六進制檢視,h.264每幀的界定符為00 00 00 01或者00 00 01。
比如下面的 h264 檔案片斷這就包含三幀資料:
00 00 00 01 67 42 c0 33 a6 81 e0 51 a1 00 00 03
00 01 00 00 03 00 32 8f 18 32 a0 00 00 00 01 68
ce 1f 20 00 00 01 06 05 ff ff 4c dc 45 e9 bd e6
d9 48 b7 96 2c d8 20 d9 23 ee ef 78 32 36 34 20
2d 20 63 6f 72 65 20 31 34 38 20 2d 20 48 2e 32
36 34 2f 4d 50 45 47 2d 34 20 41 56 43 20 63 6f
第一幀是00 00 00 01 67 42 c0 33 a6 81 e0 51 a1 00 00 03 00 01 00 00 03 00 32 8f 18 32 a0
第二幀是00 00 00 01 68 ce 1f 20
第三幀是00 00 01 06 05 ff ff 4c dc 45 e9 bd e6 d9 48 b7 96 2c d8 20 d9 23 ee ef 78 32 36 34 20 2d 20 63 6f 72 65 20 31 34 38 20 2d 20 48 2e 32 36 34 2f 4d 50 45 47 2d 34 20 41 56 43 20 63 6f
我們接下來對其詳細的進行分析:
我們對第一幀去掉界定符,就是剩下的67 42 c0 33 a6 81 e0 51 a1 00 00 03 00 01 00 00 03 00 32 8f 18 32 a0
這裡對于SPS有用的部分就是第一位元組67開始
幀類型的方式判斷為界面符後首位元組的低四位。
第一幀的幀類型為: 0x67 & 0x0F = 7,這是一個 SPS 幀
第二幀的幀類型為: 0x68 & 0x0F = 8,這是一個 PPS 幀
第三幀的幀類型為: 0x06 & 0x0F = 6,這是一個 SEI 幀
是以本文第一幀為SPS幀,而我們要将該SPS幀中的一些資料,按照剛剛說到的FLV的一些形式,拼接為相應的叫做Video Tag格式,如果是音頻就拼接為相應的叫做Audio Tag格式
FLV、F4V格式标準文檔
video_file_format_spec_v10.pdf

是以按照文檔所說,就需要将要發往rtmp伺服器的SPS中的資料buffer[0]位置設定為
0x17 也就是圖中的高4位為1意味着關鍵幀,低4位為7意味着AVC格式(也就是h.264,AVC 實際上是 H.264 協定的别名。但自從H.264協定中增加了SVC的部分之後,人們習慣将不包含SVC的H.264協定那一部分稱為 AVC,而将SVC這一部分單獨稱為SVC.)
而根據如下文檔說明,我們要将AVCPacketType設定為AVC sequence header ,也就是0,其占用buffer[1]=0x00,其後為圖中的CompositionTime,如果為AVCPacketType=0的話,這三個位元組也是0,buffer[2]=0x00,buffer[3]=0x00,buffer[4]=0x00
然後後面的是AVCDecoderConfigurationRecord(其其實就是AVC sequence header),AVCDecoderConfigurationRecord.包含着是H.264解碼相關比較重要的sps和pps資訊,再給AVC解碼器送資料流之前一定要把sps和pps資訊送出,否則的話解碼器不能正常解碼。而且在解碼器stop之後再次start之前,如seek、快進快退狀态切換等,都需要重新送一遍sps和pps的資訊.AVCDecoderConfigurationRecord在FLV檔案中一般情況也是出現1次,也就是第一個video tag.
H.264 标準文檔
H.264-AVC-ISO_IEC_14496-15.pdf
從圖中可知,接下來的buffer[5]=0x01為配置版本号固定為1,而接下來的一些資料定義在另外一個文檔,
H.264 标準文檔
H.264-AVC-ISO_IEC_14496-10
由圖中可以知道AVCProfileIndication被定義為13種配置,其中在附錄A中顯示
Conformance of a bitstream to the Baseline profile is indicated by profile_idc being equal to 66.
我們剛剛h.264中的對十六進制第一幀去掉界定符,就是剩下的67 42 c0 33 a6 81 e0 51 a1 00 00 03 00 01 00 00 03 00 32 8f 18 32 a0,然後我們又提出了67是SPS的标志,那麼剩下的42為AVCProfileIndication(配置特征),也就是66,是以這裡是Baseline(基線配置)。是以我們這裡buffer[6]=0x42(十進制66).
對于基線配置的一些分析,我們放到後續的文章中,進行分析。
接線來剩下c0 33 a6 81 e0 51 a1 00 00 03 00 01 00 00 03 00 32 8f 18
c0是什麼呢?在H.264-AVC-ISO_IEC_14496-10中規定,對每一種profile的支援級别,如果全部支援就将相應的位bit設定為1,否則就是0,我們看c0=二進制(1100 0000) ,對應的下圖的8個bit,也就是前兩個constraint_set0_flag=1,constraint_set1_flag=1,而前一個constraint_set0_flag代表對Baseline profile的支援程度,後一個constraint_set1_flag代表對Main profile的支援程度,也就是它們都為1,就是都是全部支援的,是以這裡需要将buffer[7]=0xc0.
接下來的Level-IDC,我沒有找到相關文檔,引用另外一個人查找到的資料,
Profile_IDC:
LevelIDC:
由圖中我們也可知,我們剩下的33 a6 81 e0 51 a1 00 00 03 00 01 00 00 03 00 32 8f 18,buffer[8]=0x33(十進制的51),接下來是一個關鍵點,很多人都搞混了,先做一些解釋。
引用别人的文章進行一下描述:
h.264碼流格式
H.264标準中指定了視訊如何編碼成獨立的包,但如何存儲和傳輸這些包卻未作規範,雖然标準中包含了一個Annex附件,裡面描述了一種可能的格式Annex B,但這并不是一個必須要求的格式。
為了針對不同的存儲傳輸需求,出現了兩種打包方法。一種即Annex B格式,另一種稱為AVCC格式。
- Annex B
從上文可知,一個NALU中的資料并未包含他的大小(長度)資訊,是以我們并不能簡單的将一個個NALU連接配接起來生成一個流,因為資料流的接收端并不知道一個NALU從哪裡結束,另一個NALU從哪裡開始。
Annex B格式用起始碼(Start Code)來解決這個問題,它在每個NALU的開始處添加三位元組或四位元組的起始碼0x000001或0x00000001。通過定位起始碼,解碼器就可以很容易的識别NALU的邊界。
當然,用起始碼定位NALU邊界存在一個問題,即NALU中可能存在與起始碼相同的資料。為了防止這個問題,在建構NALU時,需要将資料中的0x000000,0x000001,0x000002,0x000003中插入防競争位元組(Emulation Prevention Bytes)0x03,使其變為:
0x000000 = 0x0000 03 00
0x000001 = 0x0000 03 01
0x000002 = 0x0000 03 02
0x000003 = 0x0000 03 03
解碼器在檢測到0x000003時,将0x03抛棄,恢複原始資料。
由于Annex B格式每個NALU都包含起始碼,是以解碼器可以從視訊流随機點開始進行解碼,常用于實時的流格式。在這種格式中通常會周期性的重複SPS和PPS,并且經常時在每一個關鍵幀之前。
- AVCC(也就是我們這裡的AVC)
AVCC格式不使用起始碼作為NALU的分界,這種格式在每個NALU前都加上一個指定NALU長度的大端格式表示的字首。這個字首可以是1、2或4個位元組,是以在解析AVCC格式的時候需要将指定的字首位元組數的值儲存在一個頭部對象中,這個都通常稱為extradata或者sequence header。同時,SPS和PPS資料也需要儲存在extradata中。
H.264 extradata文法如下:
bits | line by byte | remark | |
---|---|---|---|
8 | version | always | 0x01 |
8 | avc profile | sps[0][1] | |
8 | avc compatibility | sps[0][2] | |
8 | avc level | sps[0][3] | |
6 | reserved | all bits on | |
2 | NALULengthSizeMinusOne | ||
3 | reserved | all bits on | |
5 | number of SPS NALUs usually | 1 | |
16 | SPS size | ||
N | variable SPS NALU data | ||
8 | number of PPS NALUs usually | 1 | |
16 | PPS size | ||
N | variable PPS NALU data |
其中第5位元組的後2位表示的就是NAL size的位元組數。需要注意的是,這個NALULengthSizeMinusOne是NALU字首長度減一,即,假設字首長度為4,那麼這個值應該為3。
這裡還需要注意的一點是,雖然AVCC格式不使用起始碼,但防競争位元組還是有的。
AVCC格式的一個優點在于解碼器配置參數在一開始就配置好了,系統可以很容易的識别NALU的邊界,不需要額外的起始碼,減少了資源的浪費,同時可以在播放時調到視訊的中間位置。這種格式通常被用于可以被随機通路的多媒體資料,如存儲在硬碟的檔案。
大家明白了嗎,我們的buffer[9]=0xFF,為什麼是0xFF呢? 原因就是我們在文檔中看到的,
前6位為111111,後兩位代表前面頭的長度,也就是将h.264轉換成可以進行網絡rtmp傳輸的flv的相應的格式的媒體流的時候,不用原來的nalu 起始位00 00 00 01 來代表一個nalu了,而是用一個固定的4個位元組來代表一個nalu,而這4個位元組,在NALULengthSizeMinusOne這個位置表示的時候不能寫0x04,而是0x04-1=0x03,而0x03 的二進制是11,是以該處NALULengthSizeMinusOne的完整表示是二進制(1111 1111),十六進制是0xFF,這個是規則,是以用它來代表這裡的nalu的起始位置,是以buffer[9]=(0xFF).
接下來的就是,numOfSequenceParameterSets,也就是SPS的個數,我們常常隻獲得一個SPS,是以個數為1,而高位前三位的是保留的111,是以就是1110 0001,也就是0xE1,是以buffer[10]=(0xE1).
接下來的就是兩個位元組的是(SPS的長度),因為我們的前面資料可知,我們原來的第一幀不包含h.264起始位的是23位元組,是以小于255,則buffer[11]=0x00,buffer[12]=0x17(十進制23)
然後是将23個位元組的SPS資料添加到這個資料的末尾,是以現在buffer的長度就是13+23=36個位元組,
也就是buffer[13] ~ buffer[35] = 67 42 c0 33 a6 81 e0 51 a1 00 00 03 00 01 00 00 03 00 32 8f 18 32 a0
下一個資料的下标是36。
然後是numOfPictureParameterSets,也就是PPS的個數,這裡就是buffer[36]=0x01
然後是兩個位元組的是(PPS的長度),因為我們的前面資料可知,我們原來的第二幀不包含h.264起始位的是4位元組,是以小于255,則buffer[37]=0x00,buffer[38]=0x04。
然後是将4個位元組的PPS資料添加到這個資料的末尾,是以現在buffer的長度就是39+4=43個位元組,
也就是buffer[39] ~ buffer[42] = 68 ce 1f 20
最後總結一下,所傳輸的rtmp頭資料為
buffer[0]=0x17
buffer[1]=0x00
buffer[2]=0x00
buffer[3]=0x00
buffer[4]=0x00
buffer[5]=0x01
buffer[6]=0x42
buffer[7]=0xc0
buffer[8]=0x33
buffer[9]=0xff
buffer[10]=0xe1
buffer[11]=0x00
buffer[12]=0x17
buffer[13]=0x67
buffer[14]=0x42
buffer[15]=0xc0
buffer[16]=0x33
buffer[17]=0xa6
buffer[18]=0x81
buffer[19]=0xe0
buffer[20]=0x51
buffer[21]=0xa1
buffer[22]=0x00
buffer[23]=0x00
buffer[24]=0x03
buffer[25]=0x00
buffer[26]=0x01
buffer[27]=0x00
buffer[28]=0x00
buffer[29]=0x03
buffer[30]=0x00
buffer[31]=0x32
buffer[32]=0x8f
buffer[33]=0x18
buffer[34]=0x32
buffer[35]=0xa0
buffer[36]=0x01
buffer[37]=0x00
buffer[38]=0x04
buffer[39]=0x68
buffer[40]=0xce
buffer[41]=0x1f
buffer[42]=0x20
至此,我們的rtmp傳輸h.264起始幀部分介紹完畢,後面就是進行調用發送了。