zoukankan      html  css  js  c++  java
  • 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******************************
    ");
        // FLV Header;
        {
            FlvHeader flvHeader;
            int iSize = sizeof(FlvHeader);
            if (iSize != fread(&flvHeader, 1, iSize, m_pFile))
            {
                printf("Read FLV Header is Error. 
    ");
                return false;
            }
    
            printf("	 File Type     	 : %c %c %c 
    ", flvHeader.Type[0], flvHeader.Type[1], flvHeader.Type[2]);
            printf("	 Version       	 : %d 
    ", flvHeader.Version);
            printf("	 Stream Info   	 : %d 
    ", flvHeader.StreamInfo);
            printf("	 Header Length 	 : %d 
    
    ", ByteToInt(flvHeader.HeaderSize, sizeof(flvHeader.HeaderSize)));
        }
    
        return true;
    }
    
    bool CFLVFile::ParseFLVBody()
    {
        printf("******************************FLV Body******************************
    ");
    
        bool bStop = false;
        while (!bStop)
        {
            printf("
          ************************Tag Header************************      
    ");
    
            _getw(m_pFile);
    
            TagHeader tagHeader;
            int iTagHeaderSize = sizeof(TagHeader);
            if (iTagHeaderSize != fread(&tagHeader, 1, iTagHeaderSize, m_pFile))
            {
                // 读完了;
                printf("	 Read File Finished. 
    ");
                bStop = true;
            }
    
            printf("	 Tag Type   	 : %d 
    ", tagHeader.TagType);
            printf("	 DataSize   	 : %d 
    ", ByteToInt(tagHeader.DataSize, sizeof(tagHeader.DataSize)));
            printf("	 Timestamp  	 : %d 
    ", ByteToInt(tagHeader.Timestamp, sizeof(tagHeader.Timestamp)));
            printf("	 TimeExtend 	 : %d 
    ", tagHeader.TimeExtend);
            printf("	 StreamsID  	 : %d 
    ", ByteToInt(tagHeader.StreamsID, sizeof(tagHeader.StreamsID)));
    
            printf("      ************************Tag Body************************      
    ");
            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("	 Read Tag Body %d bytes. 
    
    ", 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("	 %s audio data: %d bytes 
    ", 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("	 %s audio data: %d bytes 
    ", 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("	 %s : %.0f
    ", 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("	 %s : %s
    ", 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

  • 相关阅读:
    MongoDB Shell
    mongo 日记
    java 堆栈 静态
    面向对象(2)
    面向对象(1)
    mongo 学习笔记
    深入浅出学Spring Data JPA
    java记录
    mongodb 2.6 window 安装启动服务
    CF1012F Passports
  • 原文地址:https://www.cnblogs.com/Kingfans/p/7137322.html
Copyright © 2011-2022 走看看