打包H264碼流到FLV文件

FLV格式非常簡單,頭信息數據量很少,適合網絡傳輸,因此被廣泛的應用。
1. H264 NALU結構
    h264 NALU:  0x00 00 00 01 | nalu_type(1字節)| nalu_data (N 字節) | 0x00 00 00 01 | ...
                      起始碼(4字節)          類型                            數據               下一個NALU起始碼
             H264 NALU固定以 0x00 00 00 01爲起始,NALU_data部分不會出現這個起始碼;
             在找到下一個起始碼之前,當前NALU數據長度不知;
             NALU_type 1字節,定義爲:1比特禁止位 | 2比特 重要性指示位  | 5比特 類型
                                                             固定爲0           11重要 不能少          1-12 由h264使用
                                                                                    00不重要 可以丟棄     
             幾個常用Nalu_type:
                               0x67 (0 11 00111) SPS    非常重要       type = 7
                               0x68 (0 11 01000) PPS     非常重要       type = 8
                               0x65 (0 11 00101) IDR幀  關鍵幀  非常重要 type = 5
                               0x61 (0 11 00001) I幀        重要         type=1    非IDR的I幀 不大常見
                               0x41 (0 10 00001) P幀      重要         type = 1     
                               0x01 (0 00 00001) B幀     不重要        type = 1
                               0x06 (0 00 00110) SEI     不重要        type = 6
2. FLV tag
    前面講過FLV文件就是由無數個Tag組成的,Tag有Video Tag, Audio Tag和Script Tag.
    A/V Tag裏面存儲的就是音視頻編碼數據,Script Tag裏面是一些碼流描述信息。
    理論上來說,不解析Script tag也可以對A/V Tag完整解碼。tag的固定格式是:
     Tag Type(1字節) | DataSize(3字節) | TimeStamp(3字節) | TimeStampExtended (1字節)| StreamID (3) | ...
     下面將分別介紹各種NALU封到tag裏面的結構。

2. 一般Video tag
                                         字節位置    意義
0x09,                              // 0,        TagType
0xzz, 0xzz, 0xzz,              // 1-3,     DataSize,    
0xzz, 0xzz, 0xzz, 0xzz,    // 4-6, 7 TimeStamp | TimeStampExtend    
0x00, 0x00, 0x00,            // 8-10,   StreamID
 
0xz7,                                  // 11,       FrameType | CodecID
0x01,                                  // 12,       AVCPacketType       
0x00, 0x00, 0x00,                // 13-15, CompositionTime
 
0xzz, 0xzz, 0xzz, 0xzz,        // 16-19,   NaluLength   NBytes
0xzz, ...., ...., 0xzz,             // NBytes,  NaluData  
 
0xzz, 0xzz, 0xzz, 0xzz.        // N+1-N+3, PreviousTagSize

    其中 0xzz的意思是該字節根據實際情況付不同的值
    2.1 DataSize[1,2,3] = NaluLength + 5 + 4;
           5 是 AVCPacket頭5比特,(FrameType+CodecID | AVCPacketType | CompositionTime)

           4 是寫入NaluLength       


[2015/12/31]對於多slice幀,多個slice依次寫入,結構都是 slice length(4字節) 類似上面16-19字節;+ slice data

      DataSize[1,2,3] = 5 + 4*slice_number + slice_data0 + slice_dataN


    2.2 對於一個裸h264流,沒有時間戳的概念,可以默認以25fps,即40ms一幀數據。
        int cts = 0;
        TimeStamp[0,1,2]   = cts[0,1,2];
        TimeStampExtend[0] = cts[3];   
        cts += 40;
    
    2.3  if(nalu_type == IDR)  FrameType | CodecID = 0x17;
            else                          FrameType | CodecID = 0x27;

    2.4 NaluLength就是nalu長度,然後緊跟N字節的Nalu數據。
    2.5 PreviousTagSize在這裏計算最爲方便,PreviousTagSize = 11 + 5 + 4 + NaluLength
                                                            11 是video tag頭數據 (TagType到StreamID)
     IDR,I,P,B幀的NALU都是這個結構

3. SPS/PPS NALU
   SPS和PPS在FLV裏面稱爲序列頭信息sequence header,它的AVCPacketType爲0x00
                                      字節位置   意義
0x09,                               // 0,       TagType
0xzz, 0xzz, 0xzz,               // 1-3,     DataSize,    
0x00, 0x00, 0x00, 0x00,      // 4-6, 7;  TimeStamp | TimeStampExtend    
0x00, 0x00, 0x00,             // 8-10,    StreamID
 
                                            // AVC video tage header 5Bytes  
0x17,                                  // 11,      FrameType | CodecID
0x00,                                  // 12,      AVCPacketType       
0x00, 0x00, 0x00,                // 13-15,   CompositionTime
       
                                            // AVCDecoderConfigurationRecord 6 Bytes
0x01,                                  // 16, ConfigurationVersion
0xzz,                                   // 17,  AVC Profile SPS[1]
0x00,                                  // 18,  profile_compatibility  SPS[2]
0xzz,                                  // 19,  AVC Level  SPS[3]
0xFF,                                  // 20,  lengthSizeMinusOne,
                                           //         reserved 6bits | NAL unit length-1, commonly be 3
0xzz,                                  // 21,  numOfSequenceParameterSets,
                                           //         reserved 3bits | SPS count, commonly be 1

0xzz, 0xzz,                       // 22-23,   SPS0 Length N0 Byte
0xzz, ...., 0xzz,                 // N0 Byte  SPS0 Data
0xzz, 0xzz,                       // SPSm Length Nm Byte (如果存在)  循環存放最多31個SPS       
0xzz, ...., 0xzz,                 // Nm Byte  SPSm Data
        
0xzz,                             //          PPS count
0xzz, 0xzz,                      //          PPS0 Length
0xzz, ...., 0xzz,                // N0 Byte  PPS0 Data
0xzz, 0xzz,                      //          PPSm Length Nm Byte (如果存在)  循環存放最多255個PPS       
0xzz, ...., 0xzz,                // Nm Byte  PPSm Data
 
0xzz, 0xzz, 0xzz, 0xzz. // N+1-N+3, PreviousTagSize

  3.1 在H.264碼流裏面reserved bit一般爲0; 而在FLV碼流裏面reserved bit定義爲1
  3.2 在H.264裏面 SPS和PPS是對立的NALU,但是在FLV裏面會把他們統一寫在一個Video Tag裏面。
         而且這個tag必須是FLV裏面第一個Video Tag,否則接收到其他video tag也沒法解碼.
         爲了防止SPS,PPS數據丟失,有些編碼器會在每個IDR幀之前重複發SPS,PPS。這些SPS其實是一樣的。
         但也不排除有些變態的編碼器前後的SPS會不同,畢竟標準容許這樣做。
         這樣就需要首先遍歷一邊h264碼流,將其中不同的SPS,PPS提起出來,先記錄下來,然後再統一寫到FLV。
         也可以大膽一點接收到第一個SPS和第一個PPS後就結束這個遍歷,就當作碼流裏面只有一個SPS和一個PPS。
 
  3.3 DataSize=5 +                  // AVC video tag header (FrameType + CodecID | .. CompositionTime)
                       6 +                   // AVCDecoderConfigurationRecord
                      SPSCount*2 +               // 每個SPS長度2字節
                      各個 SPSDataLength + // 所有SPS數據長度和
                      1 +                                     // PPS個數
                      PPSCount*2 +                // 每個PPS長度2字節

                      各個 PPSDataLength;   // 所有PPS數據長度和

  3.4 AVC Profile和 AVC Level就等於SPS NALU裏面第1字節和第3字節 (第0字節爲NaluType) 

  3.5 lengthSizeMinusOne,這個定義沒有理解,不知道低2比特是什麼含義,看到很多文檔裏面就直接設爲0b11, 所有這個字節爲 0xFF

  3.6 numOfSequenceParameterSets, 低5比特是SPS個數,H.264標準裏面定義最多SPS個數爲255,這裏只有31。

        不知道會不會存在問題,當然一般情況下就一個SPS,該值爲 0xE1 (0b111 00001)

  3.7 每個SPS,PPS數據長度都用兩個字節來表述,

  3.8 這個tag的 PreviousTagSize = 11 + DataSize。 11 是Video tag (TagType到StreamID)   

4. FLV頭
       'F', 'L', 'V',                              // 0-2 FLV file Signature, also can be 'f''l''v'
       0x01,                                     // FLV version,
       0x0z,                                     // AV tag Enable.  0x05 AV both, 0x03 audio only, 0x01 video only
       0x00, 0x00, 0x00, 0x09,        // Length of this header.
       0x00, 0x00, 0x00, 0x00.        // PreviousTagLength.

5. SEI NALU
   SEI是H.264裏面的附加增強信息NALU,他對解析解碼沒有幫助,但提供一些編碼器控制參數等信息。
   FLV沒有一個Tag單獨包含SEI數據,它把SEI數據和緊隨其後那個視頻NALU數據打在同一個Video Tag裏面。
   包含SEI數據的VideoTag結構如下
                                      字節位置   意義
0x09,                               // 0,       TagType
0xzz, 0xzz, 0xzz,               // 1-3,     DataSize,    
0xzz, 0xzz, 0xzz, 0xzz,     // 4-6, 7;  TimeStamp | TimeStampExtend    
0x00, 0x00, 0x00,             // 8-10,    StreamID
 
0x27,                                   // 11,      FrameType | CodecID
0x01,                                   // 12,      AVCPacketType       
0x00, 0x00, 0x00,                 // 13-15,   CompositionTime

0xzz, 0xzz, 0xzz, 0xzz,         // 16-19,   SEILength   NBytes
0xzz, ...., ...., 0xzz,              // NBytes,  SEIData  
 
0xzz, 0xzz, 0xzz, 0xzz,         //          NaluLength   NBytes
0xzz, ...., ...., 0xzz,              // NBytes,  NaluData  
 
0xzz, 0xzz, 0xzz, 0xzz.     // PreviousTagSize

   5.1 DataSize[0,1,2] = (NaluLength + 5 + 4) + (SEILength + 4);

6. 得到NALU代碼
// 輸入:  H264_fp 264文件指針
// 輸出: 找到的Nalu長度,
          *nalu_type返回找到的NALU類型
int h264_get_nalu(FILE *h264_fp, uint8_t *nalu_type) {
    int start_pos = -1;
    int nalu_size = 0;
    int zero_num = 0;
    uint8_t tmp;
    while(!feof(h264_fp)){
        fread(&tmp, 1, 1, h264_fp);
        if(tmp == 0) zero_num++;
        else if(tmp == 1) {
            if(zero_num >= 3) {
                if(start_pos == -1) {
                    start_pos = ftell(h264_fp);
                    fread(nalu_type, 1, 1, h264_fp);
                } else {
                    nalu_size = ftell(h264_fp) - start_pos - 4;
                    fseek(h264_fp, start_pos, 0);
                    break;
                }
            }
        } else
            zero_num = 0;
    }
    return nalu_size;
}
發佈了40 篇原創文章 · 獲贊 7 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章