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 bytes 8:音频
9:视频
18:脚本 (这里是一些描述信息)
其他:保留
数据区长度 3 bytes 数据区(Tag Data)的长度
时间戳 3 bytes

整数,单位是毫秒。对于脚本型的tag总是0.

相对于FLV文件的第一个TAG时戳。第一个tag的时戳总是0。

——不是时戳增量,rtmp中是时戳增量。

时间戳扩展 1 bytes 将时间戳扩展为4bytes,代表高8位。很少用到
StreamsID 3 bytes 总是0

 

 

 

 

 

 

 

 

 

 

2.2.Tag Data

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

2.2.1.音频数据

第一个byte是音频的信息,格式如下:

名称长度介绍
音频格式 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 0 = 5.5-kHz
1 = 11-kHz
2 = 22-kHz
3 = 44-kHz
对于AAC总是3
采样的长度 1 bit 0 = snd8Bit
1 = snd16Bit
压缩过的音频都是16bit
音频类型 1 bit 0 = sndMono
1 = sndStereo
对于AAC总是1

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

第2byte开始就是音频流数据了。

2.2.2.视频数据

和音频数据一样,第一个byte是视频信息,格式如下:

名称长度介绍
帧类型 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

 

 

 

 

 

 

 

 

 

 

 

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

 

posted on 2017-07-08 17:06  仲达超  阅读(1039)  评论(0编辑  收藏  举报

导航