FLV封裝格式介紹及解析

FLV封裝格式

FLV(Flash Video)是Adobe公司推出的一種流媒體格式,因爲其封裝後的音視頻文件體積小、封裝簡單等特色,很是適合於互聯網上使用。目前主流的視頻網站基本都支持FLV。採用FLV格式封裝的文件後綴爲.flv。數組

FLV封裝格式是由一個文件頭(flie header)和 文件體(file Body)組成。其中,FLV body由一對對的(Previous Tag Size字段 + tag)組成。Previous Tag Size字段 排列在Tag以前,佔用4個字節。Previous Tag Size記錄了前面一個Tag的大小,用於逆向讀取處理。FLV header後的第一個Pervious Tag Size的值爲0。Tag通常能夠分爲3種類型:腳本(幀)數據類型、音頻數據類型、視頻數據。FLV數據以大端序進行存儲,在解析時須要注意。一個標準FLV文件結構以下圖: bash

FLV文件結構

FLV文件的詳細內容結構以下圖:ide

FLV文件詳細內容結構

FLV header

注:在下面的數據type中,UI表示無符號整形,後面跟的數字表示其長度是多少位。好比UI8,表示沒法整形,長度一個字節。UI24是三個字節,UI[8*n]表示多個字節。UB表示位域,UB5表示一個字節的5位。能夠參考c中的位域結構體。函數

FLV頭佔9個字節,用來標識文件爲FLV類型,以及後續存儲的音視頻流。一個FLV文件,每種類型的tag都屬於一個流,也就是一個flv文件最多隻有一個音頻流,一個視頻流,不存在多個獨立的音視頻流在一個文件的狀況。FLV頭的結構以下:oop

Field Type Comment
簽名 UI8 'F'(0x46)
簽名 UI8 'L'(0x4C)
簽名 UI8 'V'(0x56)
版本 UI8 FLV的版本。0x01表示FLV版本爲1
保留字段 UB5 前五位都爲0
音頻流標識 UB1 是否存在音頻流
保留字段 UB1 爲0
視頻流標識 UB1 是否存在視頻流
文件頭大小 UI32 FLV版本1時填寫9,代表的是FLV頭的大小,爲後期的FLV版本擴展使用。包括這四個字節。數據的起始位置就是從文件開頭偏移這麼多的大小。

FLV Body

FLV Header以後,就是FLV File Body.FLV File Body是由一連串的back-pointers + tags構成。Back-pointer表示Previous Tag Size(前一個tag的字節數據長度),佔4個字節。網站

FLV Body結構

FLV Tag

每個Tag也是由兩部分組成:tag header 和 tag data。Tag Header裏存放的是當前tag的類型、數據區(tag data)的長度等信息。tag header通常佔11個字節的內存空間。FLV tag結構以下:ui

Field Type Comment
Tag類型 UI8 8:audeo
9:video
18:Script data(腳本數據)
all Others:reserved 其餘全部值未使用
數據區大小 UI24 當前tag的數據區的大小,不包含包頭
時戳 UI24 當前幀時戳,單位是毫秒。相對值,第一個tag的時戳老是爲0
時戳擴展字段 UI8 若是時戳大於0xFFFFFF,將會使用這個字節。這個字節是時戳的高8位,上面的三個字節是低24位。
StreamID UI24 老是爲0
數據區 UI[8*n] 數據區數據



FLV Tag的類型能夠是視頻、音頻和Script(腳本類型),下面分別介紹這三種Tag類型this

Script Tag Data結構(腳本類型、幀類型)

該類型Tag又被稱爲MetaData Tag,存放一些關於FLV視頻和音頻的元信息,好比:duration、width、height等。一般該類型Tag會做爲FLV文件的第一個tag,而且只有一個,跟在File Header後。該類型Tag DaTa的結構以下所示:編碼

幀類型 Tag Data結構
第一個AMF包:
第1個字節表示AMF包類型,通常老是0x02,表示字符串。第2-3個字節爲UI16類型值,標識字符串的長度,通常老是0x000A(「onMetaData」長度)。後面字節爲具體的字符串,通常總爲「onMetaData」(6F,6E,4D,65,74,61,44,61,74,61)。

第二個AMF包:
第1個字節表示AMF包類型,通常老是0x08,表示數組。第2-5個字節爲UI32類型值,表示數組元素的個數。後面即爲各數組元素的封裝,數組元素爲元素名稱和值組成的對。常見的數組元素以下表所示。url

Comment
duration 時長
width 視頻寬度
heiht 視頻高度
video data rate 視頻碼率
frame rate 視頻幀率
video codec id 視頻編碼方式
audio sample rate 音頻採樣率
audio sample size 音頻採樣精度
stereo 是否爲立體聲
audio codec id 音頻編碼方式
filesize 文件大小
... ...
Audio Tag Data結構(音頻類型)

音頻Tag Data區域開始的第一個字節包含了音頻數據的參數信息,從第二個字節開始爲音頻流數據。結構以下:

Audio Tag Data結構

第一個字節爲音頻的信息,格式以下:

Field Type Comment
音頻格式 UB4 0 = Linear PCM, platform endian
1 =ADPCM
2 = MP3
3 = Linear PCM, little endian
4 = Nellymoser 16-kHz mono
5 = Nellymoser 8-kHz mono
6 = Nellymoser
7 = G.711 A-law logarithmic PCM
8 = G.711 mu-law logarithmic PCM
9 = reserved
10 = AAC
11 = Speex
14 = MP3 8-Khz
15 = Device-specific sound
flv是不支持g711a的,若是要用,可能要用線性音頻。
採樣率 UB2 0 = 5.5-kHz
1 = 11-kHz
2 = 22-kHz
3 = 44-kHz
對於AAC老是3。由此能夠看出FLV封裝格式並不支持48KHz的採樣率
採樣精度 UB1 0 = snd8Bit
1 = snd16Bit
壓縮過的音頻都是16bit
音頻聲道 UB1 0 = sndMono 單聲道
1 = sndStereo 立體聲,雙聲道
對於AAC老是1

第二個字節開始爲音頻數據。

Field Type Comment
音頻數據 UI[8*n] 若是是PCM線性數據,存儲的時候每一個16bit小端存儲,有符號。
若是音頻格式是AAC,則存儲的數據是AAC AUDIO DATA,不然爲線性數組。
video Tag Data結構(視頻類型)

視頻Tag Data開始的第一個字節包含視頻數據的參數信息,從第二個字節開始爲視頻流數據。結構以下:

video Tag Data結構

第一個字節包含視頻信息,格式以下:

Field Type Comment
幀類型 UB4 1: keyframe (for AVC, a seekable frame)——h264的IDR,關鍵幀,可重入幀。
2: inter frame (for AVC, a non- seekable frame)——h264的普通幀
3: disposable inter frame (H.263 only)
4: generated keyframe (reserved for server use only)
5: video info/command frame
編碼ID UB4 使用哪一種編碼類型:
1: JPEG (currently unused)
2: Sorenson H.263
3: Screen video
4: On2 VP6
5: On2 VP6 with alpha channel
6: Screen video version 2
7: AVC

第二個字節開始爲視頻數據

Field Type Comment
視頻數據 UI[8*n] 若是是avc,則參考下面的介紹:AVC VIDEO PACKET
AVC VIDEO PACKET

關於下面這塊內容有興趣的話能夠結合h264結構來看,不感興趣的話能夠直接跳過。

AVC VIDEO PACKET的結構:

Field Type Comment
AVC packet類型 UI8 0:AVC序列頭
1:AVC NALU單元
2:AVC序列結束。低級別avc不須要。
CTS UI24 若是AVC packet類型是1,則爲cts偏移(見下面的解釋)。
若是AVC packet類型是0,則爲0
數據 UI[8*n] 若是AVC packet類型是0,則是解碼器配置,sps,pps。
若是是1,則是nalu單元,能夠是多個。

關於CTS:這是一個比較難以理解的概念,須要和pts,dts配合一塊兒理解。

首先,pts(presentation time stamps),dts(decoder timestamps),cts(CompositionTime)的概念:

pts:顯示時間,也就是接收方在顯示器顯示這幀的時間。單位爲1/90000 秒。

dts:解碼時間,也就是rtp包中傳輸的時間戳,代表解碼的順序。單位單位爲1/90000 秒。——根據後面的理解,pts就是標準中的CompositionTime

cts偏移:cts = (pts - dts) / 90 。cts的單位是毫秒。

pts和dts的時間不同,應該只出如今含有B幀的狀況下,也就是profile main以上。baseline是沒有這個問題的,baseline的pts和dts一直相同,因此cts一直爲0。

AVC VIDEO PACKET中Data的結構:

Field Type Comment
長度 UI32 nalu單元的長度,不包括長度字段。
nalu數據 UI[8*n] NALU數據,沒有四個字節的nalu單元頭,直接從h264頭開始,好比:65 ** ** **,41 ** ** **
長度 UI32 nalu單元的長度,不包括長度字段。
nalu數據 UI[8*n] NALU數據,沒有四個字節的nalu單元頭,直接從h264頭開始,好比:65 ** ** **,41 ** ** **
... ... ...

解析FLV

在理解了FLV結構的基礎上,就能夠嘗試去解析一個FLV文件了。在閱讀代碼的過程當中,遇到不懂的地方能夠返回去看FLV結構,加深理解。

代碼實現

下面是一段解析FLV文件的實例代碼。 實現函數:

//
//  simplest_mediadata_flv.c
//  Codec_simple_demo
//
//  Created by guoqingping on 2018/4/25.
//  Copyright © 2018年 guoqingping. All rights reserved.
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//Important!
#pragma pack(1)


#define TAG_TYPE_SCRIPT 18 //幀tag
#define TAG_TYPE_AUDIO 8 //audio tag
#define TAG_TYPE_VIDEO 9 //video tag

typedef unsigned char byte;
typedef unsigned int uint;

typedef struct {
    byte Signature[3];  //文件標識,老是爲FLV
    byte Version;   //版本號,老是爲1
    byte Flags;   // 音視頻流存在標識
    uint DataOffset;
} FLV_HEADER;   // FLV 文件頭

typedef struct {
    byte TagType;  //tag類型
    byte DataSize[3];  //tag data字節數
    byte Timestamp[3];  //時戳
    uint Reserved;  //時間戳擴展字節+streamID 
} TAG_HEADER;  //tag頭



//翻轉字節,大段序轉成小段序
uint reverse_bytes(byte *p, char c) {
    int r = 0;
    int i;
    for (i=0; i<c; i++)
        r |= ( *(p+i) << (((c-1)*8)-8*i));
    return r;
}


/**
 分析FLV 文件
 @param url FLV文件路徑
 */
int simplest_flv_parser(char *url){
    
    //whether output audio/video stream
    int output_a=1;
    int output_v=1;
    //-------------
    FILE *ifh=NULL,*vfh=NULL, *afh = NULL;
    
    //FILE *myout=fopen("output_log.txt","wb+");
    FILE *myout=stdout;
    
    FLV_HEADER flv;
    TAG_HEADER tagheader;
    uint previoustagsize, previoustagsize_z=0;
    uint ts=0, ts_new=0;
    
    ifh = fopen(url, "rb+");
    if ( ifh== NULL) {
        printf("Failed to open files!");
        return -1;
    }
    
    //FLV file header 先讀FLV頭
    fread((char *)&flv,1,sizeof(FLV_HEADER),ifh);
    
    fprintf(myout,"============== FLV Header ==============\n");
    fprintf(myout,"Signature: 0x %c %c %c\n",flv.Signature[0],flv.Signature[1],flv.Signature[2]);
    fprintf(myout,"Version: 0x %X\n",flv.Version);
    fprintf(myout,"Flags : 0x %X\n",flv.Flags);
    fprintf(myout,"HeaderSize: 0x %X\n",reverse_bytes((byte *)&flv.DataOffset, sizeof(flv.DataOffset)));
    fprintf(myout,"========================================\n");
    
    //move the file pointer to the end of the header
    fseek(ifh, reverse_bytes((byte *)&flv.DataOffset, sizeof(flv.DataOffset)), SEEK_SET);
    
    //process each tag
    do {
        //讀取Previous tag size
        previoustagsize =  getw(ifh);
        // 讀取tag header
        fread((void *)&tagheader,sizeof(TAG_HEADER),1,ifh);
        
        //int temp_datasize1=reverse_bytes((byte *)&tagheader.DataSize, sizeof(tagheader.DataSize));
        //獲取tag data的字節數
        int tagheader_datasize=tagheader.DataSize[0]*pow(2, 16)+tagheader.DataSize[1]*pow(2, 8)+tagheader.DataSize[2];
        
        //獲取時戳
        int tagheader_timestamp=tagheader.Timestamp[0]*pow(2, 16)+tagheader.Timestamp[1]*pow(2, 8)+tagheader.Timestamp[2];
        
        char tagtype_str[10];
        //獲取tag類型
        switch(tagheader.TagType){
            case TAG_TYPE_AUDIO:sprintf(tagtype_str,"AUDIO");break;
            case TAG_TYPE_VIDEO:sprintf(tagtype_str,"VIDEO");break;
            case TAG_TYPE_SCRIPT:sprintf(tagtype_str,"SCRIPT");break;
            default:sprintf(tagtype_str,"UNKNOWN");break;
        }
        fprintf(myout,"[%6s] %6d %6d |",tagtype_str,tagheader_datasize,tagheader_timestamp);
        
        //if we are not past the end of file, process the tag
        if (feof(ifh)) {
            break;
        }
        
        //process tag by type
        switch (tagheader.TagType) {
                
            case TAG_TYPE_AUDIO:{  //音頻
                char audiotag_str[100]={0};
                strcat(audiotag_str,"| ");
                char tagdata_first_byte;
                
                //讀取一個字符,音頻tag data區域的第一個字節,音頻的信息
                tagdata_first_byte=fgetc(ifh);
                // &操做獲取前四位,表明音頻格式
                int x=tagdata_first_byte&0xF0;
                //右移4位
                x=x>>4;
                //判斷音頻格式
                switch (x)
                {
                    case 0:strcat(audiotag_str,"Linear PCM, platform endian");break;
                    case 1:strcat(audiotag_str,"ADPCM");break;
                    case 2:strcat(audiotag_str,"MP3");break;
                    case 3:strcat(audiotag_str,"Linear PCM, little endian");break;
                    case 4:strcat(audiotag_str,"Nellymoser 16-kHz mono");break;
                    case 5:strcat(audiotag_str,"Nellymoser 8-kHz mono");break;
                    case 6:strcat(audiotag_str,"Nellymoser");break;
                    case 7:strcat(audiotag_str,"G.711 A-law logarithmic PCM");break;
                    case 8:strcat(audiotag_str,"G.711 mu-law logarithmic PCM");break;
                    case 9:strcat(audiotag_str,"reserved");break;
                    case 10:strcat(audiotag_str,"AAC");break;
                    case 11:strcat(audiotag_str,"Speex");break;
                    case 14:strcat(audiotag_str,"MP3 8-Khz");break;
                    case 15:strcat(audiotag_str,"Device-specific sound");break;
                    default:strcat(audiotag_str,"UNKNOWN");break;
                }
                strcat(audiotag_str,"| ");
                
                //獲取5~6位,採樣率
                x=tagdata_first_byte&0x0C;
                //右移2位
                x=x>>2;
                //判斷採樣率
                switch (x)
                {
                    case 0:strcat(audiotag_str,"5.5-kHz");break;
                    case 1:strcat(audiotag_str,"1-kHz");break;
                    case 2:strcat(audiotag_str,"22-kHz");break;
                    case 3:strcat(audiotag_str,"44-kHz");break;
                    default:strcat(audiotag_str,"UNKNOWN");break;
                }
                strcat(audiotag_str,"| ");
                
                //獲取第7位,採樣精度
                x=tagdata_first_byte&0x02;
                x=x>>1;
                switch (x)
                {
                    case 0:strcat(audiotag_str,"8Bit");break;
                    case 1:strcat(audiotag_str,"16Bit");break;
                    default:strcat(audiotag_str,"UNKNOWN");break;
                }
                strcat(audiotag_str,"| ");
                
                //獲取第8位,音頻聲道數
                x=tagdata_first_byte&0x01;
                switch (x)
                {
                    case 0:strcat(audiotag_str,"Mono");break;
                    case 1:strcat(audiotag_str,"Stereo");break;
                    default:strcat(audiotag_str,"UNKNOWN");break;
                }
                fprintf(myout,"%s",audiotag_str);
                
                //if the output file hasn't been opened, open it. if(output_a!=0&&afh == NULL){ afh = fopen("output.mp3", "wb"); } //TagData - First Byte Data //獲取tag Data字節數,須要減去Tag Data區域的第一個字節 int data_size=reverse_bytes((byte *)&tagheader.DataSize, sizeof(tagheader.DataSize))-1; //循環獲取字節寫入文件 if(output_a!=0){ //TagData+1 for (int i=0; i<data_size; i++) fputc(fgetc(ifh),afh); }else{ for (int i=0; i<data_size; i++) fgetc(ifh); } break; } case TAG_TYPE_VIDEO:{ //視頻 char videotag_str[100]={0}; strcat(videotag_str,"| "); //讀取TagData區域第一個字節,取出前4位。包含視頻幀類型 char tagdata_first_byte; tagdata_first_byte=fgetc(ifh); int x=tagdata_first_byte&0xF0; x=x>>4; switch (x) { case 1:strcat(videotag_str,"key frame ");break; case 2:strcat(videotag_str,"inter frame");break; case 3:strcat(videotag_str,"disposable inter frame");break; case 4:strcat(videotag_str,"generated keyframe");break; case 5:strcat(videotag_str,"video info/command frame");break; default:strcat(videotag_str,"UNKNOWN");break; } strcat(videotag_str,"| "); //讀取TagData區域第一個字節,取出後4位。包含視頻編碼類型 x=tagdata_first_byte&0x0F; switch (x) { case 1:strcat(videotag_str,"JPEG (currently unused)");break; case 2:strcat(videotag_str,"Sorenson H.263");break; case 3:strcat(videotag_str,"Screen video");break; case 4:strcat(videotag_str,"On2 VP6");break; case 5:strcat(videotag_str,"On2 VP6 with alpha channel");break; case 6:strcat(videotag_str,"Screen video version 2");break; case 7:strcat(videotag_str,"AVC");break; default:strcat(videotag_str,"UNKNOWN");break; } fprintf(myout,"%s",videotag_str); fseek(ifh, -1, SEEK_CUR); //if the output file hasn't been opened, open it.
                if (vfh == NULL&&output_v!=0) {
                    //write the flv header (reuse the original file's hdr) and first previoustagsize vfh = fopen("output.flv", "wb"); fwrite((char *)&flv,1, sizeof(flv),vfh); fwrite((char *)&previoustagsize_z,1,sizeof(previoustagsize_z),vfh); } #if 0 //Change Timestamp //Get Timestamp ts = reverse_bytes((byte *)&tagheader.Timestamp, sizeof(tagheader.Timestamp)); ts=ts*2; //Writeback Timestamp ts_new = reverse_bytes((byte *)&ts, sizeof(ts)); memcpy(&tagheader.Timestamp, ((char *)&ts_new) + 1, sizeof(tagheader.Timestamp)); #endif //TagData + Previous Tag Size int data_size=reverse_bytes((byte *)&tagheader.DataSize, sizeof(tagheader.DataSize))+4; if(output_v!=0){ //TagHeader fwrite((char *)&tagheader,1, sizeof(tagheader),vfh); //TagData for (int i=0; i<data_size; i++) fputc(fgetc(ifh),vfh); }else{ for (int i=0; i<data_size; i++) fgetc(ifh); } //rewind 4 bytes, because we need to read the previoustagsize again for the loop's sake
                fseek(ifh, -4, SEEK_CUR);
                
                break;
            }
            default:
                
                //skip the data of this tag
                fseek(ifh, reverse_bytes((byte *)&tagheader.DataSize, sizeof(tagheader.DataSize)), SEEK_CUR);
        }
        
        fprintf(myout,"\n");
        
    } while (!feof(ifh));
    
//
    fclose(ifh);
    fclose(vfh);
    fclose(afh);
    
    return 0;
}
複製代碼

函數調用方法以下:

//解析FLV
 simplest_flv_parser("cuc_ieschool.flv");
複製代碼

執行結果以下:

解析結果
相關文章
相關標籤/搜索