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文件的詳細內容結構以下圖:ide
注:在下面的數據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 Header以後,就是FLV File Body.FLV File Body是由一連串的back-pointers + tags構成。Back-pointer表示Previous Tag Size(前一個tag的字節數據長度),佔4個字節。網站
每個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
該類型Tag又被稱爲MetaData Tag,存放一些關於FLV視頻和音頻的元信息,好比:duration、width、height等。一般該類型Tag會做爲FLV文件的第一個tag,而且只有一個,跟在File Header後。該類型Tag DaTa的結構以下所示:編碼
第一個AMF包:第二個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 | 文件大小 |
... | ... |
音頻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,不然爲線性數組。 |
視頻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 |
關於下面這塊內容有興趣的話能夠結合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文件的實例代碼。 實現函數:
//
// 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");
複製代碼
執行結果以下: