Linux C 解析 FLV 文件
06 Nov 2013FLV文件组成
FLV文件由Header和Body两部分组成。
FLV Header
域名 | 类型 | 说明 |
---|---|---|
Signature | UI8 | Signature byte always ‘F’ (0x46) |
Signature | UI8 | Signature byte always ‘L’ (0x4C) |
Signature | UI8 | Signature byte always ‘V’ (0x56) |
Version | UI8 | File version (for example, 0x01 for FLV version 1) |
TypeFlagsReserved | UB[5] | Must be 0 |
TypeFlagsAudio | UB[1] | Audio tags are present |
TypeFlagsReserved | UB[1] | Must be 0 |
TypeFlagsVideo | UB[1] | Video tags are present |
DataOffset | UI32 | Offset in bytes from start of file to start of body (that is, size of header) |
FLV文件都是以’F”L”V’3个字符开始。在FLV version 1 中,DataOffset一般是9,也就是说FLV的头占据9个字节。
FLV文件头解析片段:
#define FLV_HEAD_LEN 9
FlvHeader* FlvReader::readHeader() {
unsigned char *header = new unsigned char[FLV_HEAD_LEN];
size_t readBytes = fread(header, sizeof(unsigned char), FLV_HEAD_LEN, m_fp);
if(readBytes == FLV_HEAD_LEN) {
if(header[0] != 'F' || header[1] != 'L' || header[2] != 'V') {
cerr << "Not a FLV file!!!" << endl;
delete header;
return NULL;
}
return new FlvHeader(header, FLV_HEAD_LEN);
} else {
delete header;
return NULL;
}
}
FLV Body
域名 | 类型 | 说明 |
---|---|---|
PreviousTagSize0 | UI32 | Always 0 |
Tag1 | FLVTAG | First tag |
PreviousTagSize1 | UI32 | Size of previous tag, including itsheader. For FLV version 1, this valueis 11 plus the DataSize of the previoustag. |
Tag2 | FLVTAG | Second tag |
… | … | … |
图片来自网络
FLV的Body部分是由许多个TAG组成,每一个TAG也是有Tag Header和Tag Body组成。查看上图注意到,每一个TAG之前都有一个PreviousTagSize,一个unsigned int值,用来指示本TAG之前那个TAG的长度,唯一的例外是紧跟在FLV Header之后的那个PreviousTagSize,它的值一定是0 。
FLV Tag组成
每一个TAG也是由Tag Header和Tag Body组成。
域名 | 类型 | 说明 |
---|---|---|
TagType | UI8 | Type of this tag. Values are:8: audio9: video18: script dataall others: reserved |
DataSize | UI24 | Length of the data in the Data field |
Timestamp | UI24 | Time in milliseconds at which thedata in this tag applies. This value isrelative to the first tag in the FLVfile, which always has a timestampof 0 |
TimestampExtended | UI8 | Extension of the Timestamp field toform a UI32 value. This fieldrepresents the upper 8 bits, whilethe previous Timestamp fieldrepresents the lower 24 bits of thetime in milliseconds |
StreamID | UI24 | Always 0 |
Data | If TagType = 8AUDIODATAIf TagType = 9VIDEODATAIf TagType = 18SCRIPTDATAOBJECT | Body of the tag |
Tag Header
一个TagHeader占据11个字节
Tag类型分类
script data
script data: 十六进制值为0x12(18)。这个类型的Tag通常被称为Metadata Tag,存有关于FLV视频和音频的参数信息,如duration、width、height等。通常该类型Tag会紧跟在FLV Header之后,一个FLV只能有一个Metadata Tag。 metadata Tag由两个AMF(Action Message Format)组成:
-
第1个AMF: 第1个字节表示AMF包类型,一般为0x02,表示后面跟的数据是字符串 第2,3个字节为UI16类型值,表示后面字符串的长度,一般为0x000A(即“onMetaData”的长度) 第4-13个字节为字符串数据,一般为“onMetaData”
-
第2个AMF: 第1个字节表示AMF包类型,一般为0x08,表示后面的数据部分是一个数组
第2-5个字节为UI32类型值,表示数组元素的个数,下图所示00000011(HEX) = 17(DEC),表示名称/值数组长度为17
后面即为各数组元素的封装,数组元素为名称/值对。表示方法如下: 第1-2个字节表示元素名称的长度,假设为L,读取后面的L个字节即可得到这个名称字符串。下图所示0008(HEX)=8(DEC),表示这个名称字符串的长度为8 (实际就是“duration”的长度)
“duration”字符串
接下来1byte是值类型,下图的00(HEX)表示Number type,占8byte,这样后面的8byte即为值(这里需要把16进制数转换成double)
AMF的格式:数据类型 + 数据长度 + 数据
数据类型占 1byte
AMF数据类型有:
0 = Number type (DOUBLE : 8byte)
1 = Boolean type (UI8 : 1byte)
2 = String type (2byte)
3 = Object type
4 = MovieClip type
5 = Null type
6 = Undefined type
7 = Reference type (UI16 : 2byte)
8 = ECMA array type
10 = Strict array type
11 = Date type
12 = Long string type (4byte)
上图中的0x12(18)意味着这个Tag是一个script。后面3个字节是Tag Body的大小。
读取一个MetaData的代码片段:
FlvMetaData* FlvReader::readMeta() {
unsigned char tagHeader[TAG_HEAD_LEN];
size_t readBytes;
fseek(m_fp, 9 + 4, SEEK_SET);
//read Tag header
readBytes = fread(tagHeader, sizeof(unsigned char), TAG_HEAD_LEN, m_fp);
if(readBytes == TAG_HEAD_LEN) {
//0x12: meta data
if(tagHeader[0] == 0x12 ) {
unsigned int tagBodySize = 0;
tagBodySize |= tagHeader[1];
tagBodySize = tagBodySize << 8;
tagBodySize |= tagHeader[2];
tagBodySize = tagBodySize << 8;
tagBodySize |= tagHeader[3];
//cerr << "MetaBody size: " << tagBodySize << endl;
unsigned char *tag = new unsigned char[tagBodySize + TAG_HEAD_LEN];
memcpy(tag, tagHeader, TAG_HEAD_LEN);
readBytes = fread(&tag[TAG_HEAD_LEN], sizeof(unsigned char), tagBodySize, m_fp);
if(readBytes == tagBodySize) {
return new FlvMetaData(tag, tagBodySize + TAG_HEAD_LEN);
} else {
delete tag;
return NULL;
}
}
}
return NULL;
}
分析一个MetaData的代码片段:
void FlvMetaData::parseMeta() {
unsigned int arrayLen = 0;
unsigned int offset = TAG_HEAD_LEN + 13;
unsigned int nameLen = 0;
double numValue = 0;
bool boolValue = false;
if(m_meta[offset++] == 0x08) {
arrayLen |= m_meta[offset++];
arrayLen = arrayLen << 8;
arrayLen |= m_meta[offset++];
arrayLen = arrayLen << 8;
arrayLen |= m_meta[offset++];
arrayLen = arrayLen << 8;
arrayLen |= m_meta[offset++];
} else {
//TODO:
cerr << "metadata format error!!!" << endl;
return ;
}
for(unsigned int i = 0; i < arrayLen; i++) {
numValue = 0;
boolValue = false;
nameLen = 0;
nameLen |= m_meta[offset++];
nameLen = nameLen << 8;
nameLen |= m_meta[offset++];
char name[nameLen + 1];
memset(name, 0, sizeof(name));
memcpy(name, &m_meta[offset], nameLen);
name[nameLen + 1] = '\0';
offset += nameLen;
switch(m_meta[offset++]) {
case 0x0: //Number type
numValue = hexStr2double(&m_meta[offset], 8);
offset += 8;
break;
case 0x1: //Boolean type
if(offset++ != 0x00) {
boolValue = true;
}
break;
case 0x2: //String type
nameLen = 0;
nameLen |= m_meta[offset++];
nameLen = nameLen << 8;
nameLen |= m_meta[offset++];
offset += nameLen;
break;
case 0x12: //Long string type
nameLen = 0;
nameLen |= m_meta[offset++];
nameLen = nameLen << 8;
nameLen |= m_meta[offset++];
nameLen = nameLen << 8;
nameLen |= m_meta[offset++];
nameLen = nameLen << 8;
nameLen |= m_meta[offset++];
offset += nameLen;
break;
//FIXME:
default:
break;
}
if(strncmp(name, "duration", 8) == 0) {
m_duration = numValue;
} else if(strncmp(name, "width", 5) == 0) {
m_width = numValue;
} else if(strncmp(name, "height", 6) == 0) {
m_height = numValue;
} else if(strncmp(name, "framerate", 9) == 0) {
m_framerate = numValue;
} else if(strncmp(name, "videodatarate", 13) == 0) {
m_videodatarate = numValue;
} else if(strncmp(name, "audiodatarate", 13) == 0) {
m_audiodatarate = numValue;
} else if(strncmp(name, "videocodecid", 12) == 0) {
m_videocodecid = numValue;
} else if(strncmp(name, "audiosamplerate", 15) == 0) {
m_audiosamplerate = numValue;
} else if(strncmp(name, "audiosamplesize", 15) == 0) {
m_audiosamplesize = numValue;
} else if(strncmp(name, "audiocodecid", 12) == 0) {
m_audiocodecid = numValue;
} else if(strncmp(name, "stereo", 6) == 0) {
m_stereo = boolValue;
}
}
}
git clone https://github.com/lnmcc/FlvParser.git