1. 程式人生 > >多媒體封裝格式學習:H264封裝成FLV(一)

多媒體封裝格式學習:H264封裝成FLV(一)

         搞了好幾天的FLV封裝,話說封裝真是個苦力活,有時候思路不是很清晰的時候,真心有點亂。

         網上關於H264封裝成FLV的文件,都分析的很詳細了,但是有幾個點沒有考慮到,一會在下面我會一一跟大家說明。圖什麼的我就不畫了,網上一搜應該有很多,那先看下面一個結構體吧。

typedef struct FLVHeader
{
  unsigned char First;// "F"
  unsigned char Second;//"L"
  unsigned char Last;//"V"
  unsigned char Version; //0x01
  unsigned char Flags;//"前五位保留且為0,第六位表示是否存在音訊Tag,第七位保留為0,第八位表示是否有視訊Tag"
  unsigned char HeaderLenth[4];//header 長度,一共9bytes
}FLVHeader;

瞭解過FLV的朋友都應該知道FLV是由FLVHeader和FLVBody兩大部分構成的,而FLVBody又可以劃分成N個更小的單元:Tag。我們先從簡單的看起,上述程式碼是一個FLVHeader的結構體,其前三個位元組分別是F,L,V 第四個位元組是Version也就是FLV的版本號,當前為1。第五個位元組是一個標誌位Flags,代表當前FLV是否包含音訊或者視訊Tag,此Flags的第六位代表是否含有音訊Tag,第八位表示是否含有視訊Tag,其他位為0;舉個例子,當Flag = 0X01的時候,代表只包含視訊Tag,等於0X04的時候代表只含有音訊Tag,當Flag等於0X05時,就代表此FLV既包含音訊Tag,也包含視訊Tag。最後有一個四位元組的HeaderLenth,代表著當前Header的長度。大家有沒有注意到,我用char[4]來表示這個四位元組的變數,而沒有用unsigned int型別嗎?是有一定道理的,大家可以自己去嘗試一下。

下面具體介紹一下FLVBody部分。FLVBody由多個previousdata size + Tag構成,其中Tag的型別多種多樣,由於我對音訊部分不是很熟悉,我這裡就不討論音訊Tag了。而previous data size 用四個位元組代表上一個Tag所佔位元組數,但是FLVBody和第一個Tag之間的previous data size為0.

那麼一個Tag是由哪幾個部分組成的呢?如果簡單分開來:Tag = TagHeader + TagData,如下結構體說明了TagHeader的結構:

typedef struct TagHeader
{
  unsigned char TagType;//tag型別,音訊為(0X08),視訊(0X09),script data(0x12),其他值保留
  unsigned char DataSize[3];//3位元組表示Tagdata的大小
  unsigned char Ts[3]      ;     //3位元組,為Tag的時間戳,  
  unsigned char TsEx     ;//時間戳的拓展位,當24位不夠用時,此八位作為時間戳最高位,將時間戳變為32位
  unsigned char StreamID[3];//一直為0;
}TagHeader;

Tagheader的第一個位元組為TagType,此位元組意在說明此Tag的型別,如果是音訊,TagType=0X08,視訊的話,TagType = 0X09;如果是Script data的話,TagType = 0X12;其他值保留。第2-4位元組為DataSize,表示TagHeader後面接著的TagData的資料大小。Ts和TsEx結構地內註釋已經很清楚了,就不重複了。StreamID恆定為0.

         對於一個Tag來說,TagHeader的結構都是一樣的,而TagData的結構就很複雜了,首先,針對不同的TagType,對應的TagData肯定不一樣,分為AudioTagData,VideoTagData,ScriptTagData。AudioTagData在本文中不做分析,因為這並不會影響H264碼流的封裝。

         當我們向一個FLV檔案中寫入FLVHeader後,緊接著的肯定是4個位元組的previousdata size,且為0。那下一步,我們應該來寫我們的Tag了,但是第一個是什麼Tag呢,是AudioTag,VideoTag,還是ScriptTag呢。我們第一個應該寫入的Tag是ScriptTag,那我們看一下它的結構是如何的:


ypedef struct ScriptTagData
{
  unsigned char MetaDataType;//0x02
  unsigned char StringLenth[2];//一般位10,即0x000A;
  unsigned char MetaString[10];//值為onMetaDat
  unsigned char InfoDataType;//0x08表示陣列,也就是第二個AMF包
  unsigned char EnumNum[4];//4bytes有多少個元素//18bytes
  //1
  unsigned char DurationLenth[2];//2bytes,duration的長度
  unsigned char DurationName[8];
  unsigned char DurationType;
  unsigned char DurationData[8];
  //2
  unsigned char WidthLenth[2];//
  unsigned char WidthName[5];
  unsigned char WidthType;
  unsigned char WidthData[8];
  //3
  unsigned char HeightLenth[2];
  unsigned char HeightName[6];
  unsigned char HeightType;
  unsigned char HeightData[8];
  //4
  unsigned char FrameRateLenth[2];
  unsigned char FrameRateName[9];
  unsigned char FrameRateType;
  unsigned char FrameRateData[8];
  //5
  unsigned char FileSizeLenth[2];
  unsigned char FileSizeName[8];
  unsigned char FileSizeType;
  unsigned char FileSizeData[8];
  
  unsigned char End[3];//0x000009
}ScriptTagData;

上面的結構是ScriptTag的TagData,ScriptTag的TagHeader按之前的結構來寫。ScriptTagData中的MetaDataType,StringLenth,MetaString,InfoDataType都是恆定不變的固定值,註釋裡都有他們的值,在這裡就不多做解釋了。接下來就是EnumNum[4]了,它包含4個位元組,它代表ScriptTagData中含有多少個欄位,上述結構體中我只定義了五個欄位:Duration,width,height,framrate和filesize,所以EnumNum[4] = 0x00 00 00 05,當然也可以定義更多的欄位,具體得去檢視FLV的封裝文件。接下來就是定義的欄位了,以durantion為例子,DurationLenth[2]代表duration這個字串的長度8,不包含’\0’所以DurationLenth[2]=0x00 08;  DurationName[8]代表這個字串”duration”也不包含’\0’。 DurationType代表著duration這個欄位所對應的值DurationData的型別,這裡DurationType為0,代表DurationData是Double型的,所以在結構體中我將其定義為了unsigned char DurationData[8],為8個位元組的unsignedchar 型,與Double所佔位元組數一樣。其他的幾個欄位也是一樣的,就不累述了。最後當欄位定義結束後,需要結束符End[3],為0X00 00 09。

既然SciptTag結束了,那麼就輪到VideoTag了,這裡確實有點噁心。搞過視訊的朋友應該都知道不管是H264還是H265都是以Nalu來封裝的吧,每條Nalu都是以0X 00 00 01或者0X 00 00 00 01開頭的,在這個頭之後的1Byte內,包含了此Nalu的Type,在這個位元組的低五位中,具體關於Nalu的請去檢視H264或者H265的說明。先寫到這裡了,明兒接著。


對了,有幾個不錯的連結,這裡給大家發一下:

http://blog.csdn.net/leixiaohua1020/article/details/17934487

http://niulei20012001.blog.163.com/blog/static/751472112012111901130404/