Menu Home

linux c 解析FLV文件

FLV文件组成

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个字节。

aaab

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 its
header. For FLV version 1, this value
is 11 plus the DataSize of the previous
tag.
Tag2 FLVTAG Second tag
图片来自网络

图片来自网络

FLV的Body部分是由许多个TAG组成,每一个TAG也是有Tag Header和Tag Body组成。查看上图注意到,每一个TAG之前都有一个PreviousTagSize,一个unsigned int值,用来指示本TAG之前那个TAG的长度,唯一的例外是紧跟在FLV Header之后的那个PreviousTagSize,它的值一定是0 。

xxxx

FLV Tag组成

每一个TAG也是由Tag Header和Tag Body组成。

域名 类型 说明
TagType UI8 Type of this tag. Values are:
8: audio
9: video
18: script data
all others: reserved
DataSize UI24 Length of the data in the Data field
Timestamp UI24 Time in milliseconds at which the
data in this tag applies. This value is
relative to the first tag in the FLV
file, which always has a timestamp
of 0
TimestampExtended UI8 Extension of the Timestamp field to
form a UI32 value. This field
represents the upper 8 bits, while
the previous Timestamp field
represents the lower 24 bits of the
time in milliseconds
StreamID UI24 Always 0
Data If TagType = 8
AUDIODATA
If TagType = 9
VIDEODATA
If TagType = 18
SCRIPTDATAOBJECT
Body of the tag

Tag Header

一个TagHeader占据11个字节

we

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. 第1个AMF: 第1个字节表示AMF包类型,一般为0x02,表示后面跟的数据是字符串
    第2,3个字节为UI16类型值,表示后面字符串的长度,一般为0x000A(即“onMetaData”的长度)
    第4-13个字节为字符串数据,一般为“onMetaData”
    gx
  2. 第2个AMF: 第1个字节表示AMF包类型,一般为0x08,表示后面的数据部分是一个数组
    tttt

    第2-5个字节为UI32类型值,表示数组元素的个数,下图所示00000011(HEX) = 17(DEC),表示名称/值数组长度为17
    hhhh

    后面即为各数组元素的封装,数组元素为名称/值对。表示方法如下:
    第1-2个字节表示元素名称的长度,假设为L,读取后面的L个字节即可得到这个名称字符串。下图所示0008(HEX)=8(DEC),表示这个名称字符串的长度为8 (实际就是“duration”的长度)
    kkkk

    “duration”字符串
    llll

    接下来1byte是值类型,下图的00(HEX)表示Number type,占8byte,这样后面的8byte即为值(这里需要把16进制数转换成double)
    pppp

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)

we
上图中的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