您的位置:首页 > 其它

FLV文件格式分析(附源码)

2017-08-14 08:54 555 查看


FLV文件格式分析(附源码)

FLV文件主要由两部分组成:Header和Body。



1. Header

header部分记录了flv的类型、版本等信息,是flv的开头,一般都差不多,占9bytes。具体格式如下:
文件类型3 bytes“FLV”
版本1 byte一般为0x0000 0001 (1)
流信息1 byte倒数第一位是1表示有视频,倒数第三位是1表示有音频.
其余备用字段必须为0, 音视频都具备为0x0000 0101 (5)
header长度4 bytes整个header的长度,一般为9; 大于9表示下面还有扩展信息 (9)
 
 

 

 

 

 

2. Body

body部分由一个个Tag组成,每个Tag的下面有一块4bytes的空间,用来记录这个tag的长度,这个后置用于逆向读取处理。

2.1.Tag

每个Tag由也是由两部分组成的:Tag Header和Tag Data。Tag Header里存放的是当前Tag的类型、数据区(Tag Data)长度等信息,具体如下:
名称长度介绍
Tag类型1 bytes8:音频

9:视频

18:脚本 (这里是一些描述信息)

其他:保留
数据区长度3 bytes数据区(Tag Data)的长度
时间戳3 bytes整数,单位是毫秒。对于脚本型的tag总是0.
相对于FLV文件的第一个TAG时戳。第一个tag的时戳总是0。
——不是时戳增量,rtmp中是时戳增量。
时间戳扩展1 bytes将时间戳扩展为4bytes,代表高8位。很少用到
StreamsID3 bytes总是0

 

 

 

 

 

 

 

 

 
 

2.2.Tag Data

数据区根据Tag类型的不同可分为三种,音频数据、视频数据和脚本数据。


2.2.1.音频数据

第一个byte是音频的信息,格式如下:
名称长度介绍
音频格式4 bits0 = 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
采样率2 bits0 = 5.5-kHz
1 = 11-kHz
2 = 22-kHz
3 = 44-kHz
对于AAC总是3
采样的长度1 bit0 = snd8Bit
1 = snd16Bit
压缩过的音频都是16bit
音频类型1 bit0 = sndMono
1 = sndStereo
对于AAC总是1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
第2byte开始就是音频流数据了。


2.2.2.视频数据

和音频数据一样,第一个byte是视频信息,格式如下:
名称长度介绍
帧类型4 bits1: keyframe (for AVC, a seekable frame)
2: inter frame (for AVC, a non-seekable frame)
3: disposable inter frame (H.263 only)
4: generated keyframe (reserved for server use only)
5: video info/command frame
编码ID4 bits1: 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
 
 


 


 


 


 


 

 
 
 
 


2.2.3脚本数据

脚本Tag一般只有一个,是flv的第一个Tag,用于存放flv的信息,比如duration、audiodatarate、creator、width等等信息。
首先介绍下脚本的数据类型。所有数据都是以数据类型+(数据长度)+数据的格式出现的,数据类型占1byte,数据长度看数据类型是否存在,后面才是数据。
可参考AMF数据格式。
其中数据类型的种类有:
0 = Number type
1 = Boolean type
2 = String type
3 = Object type
4 = MovieClip type
5 = Null type
6 = Undefined type
7 = Reference type
8 = ECMA array type
10 = Strict array type
11 = Date type
12 = Long string type
如果类型为String,后面的2bytes为字符串的长度(Long String是4bytes),再后面才是字符串数据;如果是Number类型,后面的8bytes为Double类型的数据;Boolean类型,后面1byte为Bool类型。
知道了这些后再来看看flv中的脚本,一般开头是0x02,表示String类型,后面的2bytes为字符串长度,一般是0x000a(“onMetaData”的长度),再后面就是字符串“onMetaData”。好像flv格式的文件都有onMetaData标记,在运行ActionScript的时候会用到它。后面跟的是0x08,表示ECMA Array类型,这个和Map比较相似,一个键跟着一个值。键都是String类型的,所以开头的0x02被省略了,直接跟着的是字符串的长度,然后是字符串,再是值的类型,也就是上面介绍的那些了。
文件内存显示:



 

3. 源码解析

flv的格式还是比较简单的,header部分很简洁,body部分都是由一个个tag,tag的话也就三种,脚本tag一般只有一个的,我想这也是flv能成为在线视频格式的原因吧。
只要了解了格式,我们就可以写个程序来解析flv文件了,下面我们看源码解析:
FLVFile.h

#pragma once
#include "VideoFile.h"

/*      PACKET_TYPE_...                0x00 */
#define PACKET_TYPE_CHUNK_SIZE         0x01
/*      PACKET_TYPE_...                0x02 */
#define PACKET_TYPE_BYTES_READ_REPORT  0x03
#define PACKET_TYPE_CONTROL            0x04
#define PACKET_TYPE_SERVER_BW          0x05
#define PACKET_TYPE_CLIENT_BW          0x06
/*      PACKET_TYPE_...                0x07 */
#define PACKET_TYPE_AUDIO              0x08
#define PACKET_TYPE_VIDEO              0x09
/*      PACKET_TYPE_...                0x0A */
/*      PACKET_TYPE_...                0x0B */
/*      PACKET_TYPE_...                0x0C */
/*      PACKET_TYPE_...                0x0D */
/*      PACKET_TYPE_...                0x0E */
#define PACKET_TYPE_FLEX_STREAM_SEND   0x0F
#define PACKET_TYPE_FLEX_SHARED_OBJECT 0x10
#define PACKET_TYPE_FLEX_MESSAGE       0x11
#define PACKET_TYPE_SCRIPT             0x12
#define PACKET_TYPE_SHARED_OBJECT      0x13
#define PACKET_TYPE_INVOKE             0x14
/*      PACKET_TYPE_...                0x15 */
#define PACKET_TYPE_FLASH_VIDEO        0x16

/************************************************************************************************************
header部分记录了flv的类型、版本等信息,是flv的开头,一般都差不多,占9bytes.

具体格式如下;
文件类型       3 bytes        "FLV";
版本            1 byte        一般为0x01;
流信息          1 byte        倒数第一位是1表示有视频,倒数第三位是1表示有音频,倒数第二、四位必须为0;
header长度      4 bytes       整个header的长度,一般为9;大于9表示下面还有扩展信息;
************************************************************************************************************/
struct FlvHeader
{
byte Type[3];
byte Version;
byte StreamInfo;
byte HeaderSize[4];
};

/************************************************************************************************************
每个Tag由也是由两部分组成的:Tag Header和Tag Data;
Tag Header里存放的是当前Tag的类型、数据区(Tag Data)长度等信息;
具体如下;

名称              长度                      介绍;
Tag类型           1 bytes                    8:音频 \ 9:视频 \ 18:脚本 \  其他:保留;
数据区长度         3 bytes                    在数据区的长度;
时间戳            3 bytes                    整数,单位是毫秒 对于脚本型的tag总是0;
时间戳扩展         1 bytes                    将时间戳扩展为4bytes,代表高8位 很少用到;
StreamsID        3 bytes                    总是0;
************************************************************************************************************/
struct TagHeader
{
byte TagType;
byte DataSize[3];
byte Timestamp[3];
byte TimeExtend;
byte StreamsID[3];
};

class CFLVFile: public CVideoFile
{
public:
CFLVFile();
virtual ~CFLVFile();

virtual bool ParseFile();

protected:
bool ParseFLVHeader();
bool ParseFLVBody();

bool ParseAudioData(int iDataSize);
bool ParseVideoData(int iDataSize);
bool ParseScriptData(int iDataSize);
};


FLVFile.cpp

#include "FLVFile.h"
#include "amf.h"

CFLVFile::CFLVFile()
{
}

CFLVFile::~CFLVFile()
{
}

bool CFLVFile::ParseFile()
{
if (!ParseFLVHeader())
{
return false;
}

if (!ParseFLVBody())
{
return false;
}

return true;
}

bool CFLVFile::ParseFLVHeader()
{
printf("******************************FLV Header******************************\n");
// FLV Header;
{
FlvHeader flvHeader;
int iSize = sizeof(FlvHeader);
if (iSize != fread(&flvHeader, 1, iSize, m_pFile))
{
printf("Read FLV Header is Error. \n");
return false;
}

printf("\t File Type     \t : %c %c %c \n", flvHeader.Type[0], flvHeader.Type[1], flvHeader.Type[2]);
printf("\t Version       \t : %d \n", flvHeader.Version);
printf("\t Stream Info   \t : %d \n", flvHeader.StreamInfo);
printf("\t Header Length \t : %d \n\n", ByteToInt(flvHeader.HeaderSize, sizeof(flvHeader.HeaderSize)));
}

return true;
}

bool CFLVFile::ParseFLVBody()
{
printf("******************************FLV Body******************************\n");

bool bStop = false;
while (!bStop)
{
printf("\n      ************************Tag Header************************      \n");

_getw(m_pFile);

TagHeader tagHeader;
int iTagHeaderSize = sizeof(TagHeader);
if (iTagHeaderSize != fread(&tagHeader, 1, iTagHeaderSize, m_pFile))
{
// 读完了;
printf("\t Read File Finished. \n");
bStop = true;
}

printf("\t Tag Type   \t : %d \n", tagHeader.TagType);
printf("\t DataSize   \t : %d \n", ByteToInt(tagHeader.DataSize, sizeof(tagHeader.DataSize)));
printf("\t Timestamp  \t : %d \n", ByteToInt(tagHeader.Timestamp, sizeof(tagHeader.Timestamp)));
printf("\t TimeExtend \t : %d \n", tagHeader.TimeExtend);
printf("\t StreamsID  \t : %d \n", ByteToInt(tagHeader.StreamsID, sizeof(tagHeader.StreamsID)));

printf("      ************************Tag Body************************      \n");
const int iDataSize = ByteToInt(tagHeader.DataSize, sizeof(tagHeader.DataSize));

switch (tagHeader.TagType)
{
case PACKET_TYPE_AUDIO:
{
// 音频数据;
ParseAudioData(iDataSize);
}
break;
case PACKET_TYPE_VIDEO:
{
// 视频数据;
ParseVideoData(iDataSize);
}
break;
case PACKET_TYPE_SCRIPT:
{
// 类型数据;
ParseScriptData(iDataSize);
}
break;
default:
{
printf("\t Read Tag Body %d bytes. \n\n", iDataSize);
fseek(m_pFile, iDataSize, SEEK_CUR);
}
break;
}
}

return true;
}

/************************************************************************************************************
第一个字节是音频信息格式,格式如下;

名称            长度                    介绍;

音频格式        4 bits
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

采样率            2 bits                    (对于AAC总是3)
0 = 5.5-kHz
1 = 11-kHz
2 = 22-kHz
3 = 44-kHz

采样的长度        1 bit                    (压缩过的音频总是16bit)
0 = snd8Bit
1 = snd16Bit

音频类型        1 bit                    (对于AAC总是1)
0 = sndMono
1 = sndStereo

第二个字节开始就是音频流数据了;
************************************************************************************************************/
bool CFLVFile::ParseAudioData(int iDataSize)
{
// 解析音频格式;
byte audioHeader;
fread(&audioHeader, 1, sizeof(audioHeader), m_pFile);

char strAudioInfo[100] = { 0 };
{
byte audioFormat = audioHeader >> 4;
switch (audioFormat)
{
case 0:strcat(strAudioInfo, "Linear PCM, platform endian"); break;
case 1:strcat(strAudioInfo, "ADPCM"); break;
case 2:strcat(strAudioInfo, "MP3"); break;
case 3:strcat(strAudioInfo, "Linear PCM, little endian"); break;
case 4:strcat(strAudioInfo, "Nellymoser 16-kHz mono"); break;
case 5:strcat(strAudioInfo, "Nellymoser 8-kHz mono"); break;
case 6:strcat(strAudioInfo, "Nellymoser"); break;
case 7:strcat(strAudioInfo, "G.711 A-law logarithmic PCM"); break;
case 8:strcat(strAudioInfo, "G.711 mu-law logarithmic PCM"); break;
case 9:strcat(strAudioInfo, "reserved"); break;
case 10:strcat(strAudioInfo, "AAC"); break;
case 11:strcat(strAudioInfo, "Speex"); break;
case 14:strcat(strAudioInfo, "MP3 8-Khz"); break;
case 15:strcat(strAudioInfo, "Device-specific sound"); break;
default:strcat(strAudioInfo, "UNKNOWN"); break;
}
strcat(strAudioInfo, "| ");
}

{
byte sampBits = audioHeader << 4;
sampBits = sampBits >> 6;
switch (sampBits)
{
case 0:strcat(strAudioInfo, "5.5-kHz"); break;
case 1:strcat(strAudioInfo, "1-kHz"); break;
case 2:strcat(strAudioInfo, "22-kHz"); break;
case 3:strcat(strAudioInfo, "44-kHz"); break;
default:strcat(strAudioInfo, "UNKNOWN"); break;
}
strcat(strAudioInfo, "| ");
}

{
byte sampLen = audioHeader << 6;
sampLen = sampLen >> 7;
switch (sampLen)
{
case 0:strcat(strAudioInfo, "8Bit"); break;
case 1:strcat(strAudioInfo, "16Bit"); break;
default:strcat(strAudioInfo, "UNKNOWN"); break;
}
strcat(strAudioInfo, "| ");
}

{
byte audioType = audioHeader << 7;
audioType = audioType >> 7;
switch (audioType)
{
case 0:strcat(strAudioInfo, "Mono"); break;
case 1:strcat(strAudioInfo, "Stereo"); break;
default:strcat(strAudioInfo, "UNKNOWN"); break;
}
strcat(strAudioInfo, "| ");
}

printf("\t %s audio data: %d bytes \n", strAudioInfo, iDataSize - 1);
fseek(m_pFile, iDataSize - 1, SEEK_CUR);

return true;
}

/************************************************************************************************************
第一个字节是视频信息格式,格式如下;

名称            长度                    介绍;

帧类型            4 bits
1: keyframe(for AVC, a seekable frame)
2 : inter frame(for AVC, a non - seekable frame)
3 : disposable inter frame(H.263 only)
4 : generated keyframe(reserved for server use only)
5 : video info / command frame

编码ID            4 bits
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

第二个字节开始就是视频流数据了;
************************************************************************************************************/
bool CFLVFile::ParseVideoData(int iDataSize)
{
// 解析音频格式;
byte videoHeader;
fread(&videoHeader, 1, sizeof(videoHeader), m_pFile);

char strVideoInfo[100] = { 0 };
{
byte frameType = videoHeader >> 4;
switch (frameType)
{
case 1:strcat(strVideoInfo, "key frame  "); break;
case 2:strcat(strVideoInfo, "inter frame"); break;
case 3:strcat(strVideoInfo, "disposable inter frame"); break;
case 4:strcat(strVideoInfo, "generated keyframe"); break;
case 5:strcat(strVideoInfo, "video info/command frame"); break;
default:strcat(strVideoInfo, "UNKNOWN"); break;
}
strcat(strVideoInfo, "| ");
}

{
byte sampBits = videoHeader << 4;
sampBits = sampBits >> 4;
switch (sampBits)
{
case 1:strcat(strVideoInfo, "JPEG (currently unused)"); break;
case 2:strcat(strVideoInfo, "Sorenson H.263"); break;
case 3:strcat(strVideoInfo, "Screen video"); break;
case 4:strcat(strVideoInfo, "On2 VP6"); break;
case 5:strcat(strVideoInfo, "On2 VP6 with alpha channel"); break;
case 6:strcat(strVideoInfo, "Screen video version 2"); break;
case 7:strcat(strVideoInfo, "AVC"); break;
default:strcat(strVideoInfo, "UNKNOWN"); break;
}
strcat(strVideoInfo, "| ");
}

printf("\t %s audio data: %d bytes \n", strVideoInfo, iDataSize - 1);
fseek(m_pFile, iDataSize - 1, SEEK_CUR);

return true;
}

/************************************************************************************************************
脚本Tag一般只有一个,是flv的第一个Tag;
用于存放flv的信息,比如duration、audiodatarate、creator、width等;

首先介绍下脚本的数据类型;
所有数据都是以数据类型+(数据长度)+数据的格式出现的,数据类型占1byte,数据长度看数据类型是否存在,后面才是数据;

AMF数据格式解析;
************************************************************************************************************/
bool CFLVFile::ParseScriptData(int iDataSize)
{
// 解析类型信息;
byte* pData = new byte[iDataSize];
byte* pDataEnd = pData + iDataSize;
fread(pData, 1, iDataSize, m_pFile);

while (pDataEnd - pData > 0)
{
switch (pData[0])
{
case AMF_NUMBER:        // 数字(double);
{
//double dVal = AMF_DecodeNumber(pFileFlv);
}
break;
case AMF_BOOLEAN:        // 布尔;
{

}
break;
case AMF_STRING:        // 字符串;
{
pData = pData + 1;

AVal valName;
AMF_DecodeString((char*)pData, &valName);
pData = pData + (valName.av_len + 2);
}
break;
case AMF_OBJECT:        // 对象;
{

}
break;
case AMF_MOVIECLIP:        // 保留,未使用;
break;
// AMF_NULL,                // null;
// AMF_UNDEFINED,            // 未定义;
// AMF_REFERENCE,            // 引用;
case AMF_ECMA_ARRAY:            // 数组;
{
pData = pData + 1;

// 数组元素个数;
int iArrLen = ByteToInt(pData, 4);
pData = pData + 4;

AMFObject obj;
int iSize = pDataEnd - pData;
int nRes = AMF_DecodeArray(&obj, (char*)pData, iSize, iArrLen, TRUE);
if (nRes == -1)
{
return false;
}

for (int i = 0; i < iArrLen; i++)
{
std::string strName(obj.o_props[i].p_name.av_val, obj.o_props[i].p_name.av_len);
switch (obj.o_props[i].p_type)
{
case AMF_NUMBER:            // 数字(double);
{
printf("\t %s : %.0f\n", strName.c_str(), obj.o_props[i].p_vu.p_number);
}
break;
case AMF_STRING:            // 字符串;
{
std::string strValue(obj.o_props[i].p_vu.p_aval.av_val, obj.o_props[i].p_vu.p_aval.av_len);
printf("\t %s : %s\n", strName.c_str(), strValue.c_str());
}
break;
default:
break;
}
}

pData = pData + iSize;
}
break;
// AMF_OBJECT_END,            // 对象结束(0x09);
// AMF_STRICT_ARRAY,        // 严格的数组;
// AMF_DATE,                // 日期;
// AMF_LONG_STRING,            // 长字符串;
// AMF_UNSUPPORTED,            // 未支持;
// AMF_RECORDSET,            // 保留,未使用;
// AMF_XML_DOC,                // xml文档;
// AMF_TYPED_OBJECT,        // 有类型的对象;
// AMF_AVMPLUS,                // 需要扩展到AMF3;
// AMF_INVALID = 0xff        // 无效的;
default:
break;
}
}

return true;
}


main.cpp

#include <windows.h>
#include "FLVFile.h"
#include <assert.h>

// FLV 文件解析;
bool FLVParse_Test()
{
CFLVFile flvFile;
return flvFile.LoadFile("../testfile/flv_test.flv");
}

void main()
{
// FLV;
bool bRes = FLVParse_Test();
assert(bRes);
}


运行结果:



项目源码下载地址:https://github.com/kingsunc/AVFileParse
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: