[[FLV格式]]

这里可以看到 flv 的格式,本内容只介绍 flv 解析部分,其他就不介绍了。

Flv 数据包 Parser

FlvHeader

image.png

这里可以看到 flv header 是9个字节,分别是:文件标识,版本标识、类型和头大小(就是9)。

parser 后头的创建和销毁:

FlvParser::FlvHeader* FlvParser::CreateHeader(uint8_t* pBuf) {
    FlvHeader* pHeader = new FlvHeader;

    pHeader->nVersion = pBuf[3]; // 直接略过文件标识提提取版本
    pHeader->bHaveAudio = (pBuf[4] >> 2) & 0x01; // 提取是否有音频bit
    pHeader->bHaveVideo = (pBuf[4] >> 0) & 0x01; // 提取是否有视频bit
    pHeader->nHeaderSize = SolveU32(pBuf + 5);
    pHeader->pFlvHeader = new uint8_t[pHeader->nHeaderSize];

    memset(pHeader->pFlvHeader, 0, pHeader->nHeaderSize);
    memcpy(pHeader->pFlvHeader, pBuf, pHeader->nHeaderSize); // 存储头

    return pHeader;
}

void FlvParser::DestroyHeader(FlvParser::FlvHeader* pHeader) {
    if (pHeader) {
        if (pHeader->pFlvHeader) {
            delete pHeader->pFlvHeader;
        }
    }
}

Tag

tag 分为很多种,有:音频 Tag、视频 Tag、Script Tag。这里我放一张之前文章的整理的 tag header 截图。

image.png

tag header 中的数据是所有 tag 都有的,所以抽象出一个 tag 基类+三个集体的 tag 子类:

// 这里抽象出了Tag Header存储上面表表格中的数据
struct TagHeader {
    int nTagType;
    int nDataSize;
    int nTimeSamp;
    int nTimeSampExt;
    int nStreamID;

    uint32_t nTotalTS;

    TagHeader()
        : nTagType(0),
          nDataSize(0),
          nTimeSamp(0),
          nTimeSampExt(0),
          nStreamID(0) {};
    ~TagHeader() {};
};
class Tag {
   public:
	Tag() : _pTagData(nullptr), _pTagHeader(nullptr), _pMedia(nullptr) {}
	void Init(TagHeader *pHeader, uint8_t *pBuf, int nLeftLen);

	TagHeader _header;
	uint8_t *_pTagHeader; // Tag 头
	uint8_t *_pTagData; // Tag body
	uint8_t *_pMedia; // 媒体数据流
	int _nMediaLen; // 媒体数据流长度
};
// 视频Tag
class VideoTag : public Tag {
   public:
	VideoTag(TagHeader *pHeader, uint8_t *pBuf, int nLeftLen,
			 FlvParser *pParser);
	int _nFrameType;
	int _nCodecType;
	int ParseH264Tag(FlvParser *pParser);
	int ParseH264Configuration(FlvParser *pParser, uint8_t *pTagData);
	int ParseNalu(FlvParser *pParser, uint8_t *pTagData);
};
// 音频Tag
class AudioTag : public Tag {
   public:
	AudioTag(TagHeader *pHeader, uint8_t *pBuf, int nLeftLen,
			 FlvParser *pParser);

	int _nSoundType;
	int _nSoundRate;
	int _nSoundSize;
	int _nSoundFormat;

	static int _aacProfile;
	static int _sampleRateIndex;
	static int _channelConfig;

	int ParseAACTag(FlvParser *pParser);
	int ParseAudioSpecificConfig(FlvParser *pParser, uint8_t *pTagData);
	int ParseAACRaw(FlvParser *pParser, uint8_t *pTagData);
};
// 这里的元数据Tag就是Script Tag
class MetaTag : public Tag {
   public:
	MetaTag(TagHeader *pHeader, uint8_t *pBuf, int nLeftLen,
			FlvParser *pParser);

	double hexStr2Double(const unsigned char *hex,
						 const unsigned int length);
	int parseMeta(FlvParser *pParser);

	void printMeta();

	uint8_t m_amf1_type;
	uint32_t m_amf1_size;
	uint8_t m_amf2_type;

	unsigned char *m_meta;
	unsigned int m_length;

	double m_duration;       // 视频时长
	double m_width;          // 视频宽度
	double m_height;         // 视频高度
	double m_videodatarate;  // 视频码率(kbps)
	double m_framerate;      // 视频帧率
	double m_videocodecid;   // 视频编码格式编号

	double m_audiodatarate;    // 音频码率
	double m_audiosamplerate;  // 音频采样率
	double m_audiosamplesize;  // 音频采样位数(通常是16bit)
	bool m_stereo;             // 是否立体声(多声道)
	double m_audiocodecid;     // 音频编码格式
							   //
	std::string m_major_brand;
	std::string m_minor_version;
	std::string m_compatible_brand;
	std::string m_encoder;

	double m_filesize;
};

解析 Audio Tag

image.png

FlvParser::AudioTag::AudioTag(TagHeader* pHeader, uint8_t* pBuf, int nLeftLen,
                              FlvParser* pParser) {
    Init(pHeader, pBuf, nLeftLen);

    uint8_t* pd = _pTagData;
    _nSoundFormat = (pd[0] & 0xf0) >> 4;
    _nSoundRate = (pd[0] & 0x0c) >> 2;
    _nSoundSize = (pd[0] & 0x02) >> 1;
    _nSoundType = (pd[0] & 0x01) >> 0;

    if (_nSoundFormat == 10) { // 如果编码格式为AAC,那就解析(这里只解析AAC)
        ParseAACTag(pParser);
    }
}

解析 AAC:

int FlvParser::AudioTag::ParseAACTag(FlvParser* pParser) {
    uint8_t* pd = _pTagData;
	// 第一个字节是音频格式信息,直接跳过,解析第二个
	// 第二个字节代表包类型
    int nAACPacketType = pd[1];

    if (nAACPacketType == 0) { // 如果为0就是音频配置信息
        ParseAudioSpecificConfig(pParser, pd);
    } else if (nAACPacketType == 1) { // 如果是1就是AAC音频流
        ParseAACRaw(pParser, pd);
    } else {
    }

    return 0;
}

解析 AAC 音频配置信息:

int FlvParser::AudioTag::ParseAudioSpecificConfig(FlvParser* pParser,
                                                  uint8_t* pTagData) {
    uint8_t* pd = pTagData;
	// pd[2] & 0b11111000 
    _aacProfile = (pd[2] & 0xf8) >> 3;
    // 采样频率索引
    _sampleRateIndex = ((pd[2] & 0x07) << 1 | (pd[3] >> 7));
    // 声道数量
    _channelConfig = (pd[3] >> 3) & 0x0f;

	// 打印出来
    printf("-----AAC-----\n");
    printf("aacProfile:%d\n", _aacProfile);
    printf("sampleRateIndex:%d\n", _sampleRateIndex);
    printf("channelConfig:%d\n", _channelConfig);

	// 如果是配置Tag那就没有媒体字节
    _pMedia = nullptr;
    _nMediaLen = 0;

    return 0;
}

解析 AAC 音频流:

int FlvParser::AudioTag::ParseAACRaw(FlvParser* pParser, uint8_t* pTagData) {
    uint64_t bits = 0;
    int datasize = _header.nDataSize - 2;

    WriteU64(bits, 12, 0xFFF);
    WriteU64(bits, 1, 0);
    WriteU64(bits, 2, 0);
    WriteU64(bits, 1, 1);
    // 这里虽然是使用的MPEG-4标准,但是这里依然需要-1,因为如何根据标准解复用是解复用器做的事情
    // 在数据包中都是从0开始计算
    WriteU64(bits, 2, _aacProfile - 1);
    WriteU64(bits, 4, _sampleRateIndex);
    WriteU64(bits, 1, 0);
    WriteU64(bits, 3, _channelConfig);
    WriteU64(bits, 1, 0);
    WriteU64(bits, 1, 0);
    WriteU64(bits, 1, 0);
    WriteU64(bits, 1, 0);
    WriteU64(bits, 13, datasize + 7);
    WriteU64(bits, 11, 0x7FF);
    WriteU64(bits, 2, 0);

    _nMediaLen = datasize + 7;
    _pMedia = new uint8_t[_nMediaLen];

    uint8_t p64[8] = {0};

    for (size_t i = 0; i < 8; i++) {
        p64[i] = bits >> (64 - 8 * (i + 1));
    }

    memcpy(_pMedia, p64 + 1, 7);
    memcpy(_pMedia + 7, pTagData + 2, datasize);
}

解析 Video Tag

image.png

FlvParser::VideoTag::VideoTag(TagHeader* pHeader, uint8_t* pBuf, int nLeftLen,
                              FlvParser* pParser) {
    Init(pHeader, pBuf, nLeftLen);

    uint8_t* pd = _pTagData;
    // 视频帧类型
    _nFrameType = (pd[0] & 0xf0) >> 4;
    // 视频帧编码
    _nCodecType = (pd[0] & 0x0f) >> 0;

    if (_header.nTagType == 0x09 && _nCodecType == 7) {
        ParseH264Tag(pParser); // 解析H264编码
    }
}

int FlvParser::VideoTag::ParseH264Tag(FlvParser* pParser) {
    uint8_t* pd = _pTagData;
	// 跳过第一个字节,理由和音频Tag一样
    int nAVCPacketType = pd[1];

    int nCompositionTime = SolveU24(pd + 2);

    if (nAVCPacketType == 0) {
        ParseH264Configuration(pParser, pd);
    } else if (nAVCPacketType == 1) {
        ParseNalu(pParser, pd);
    }

    return 0;
}

解析 H264的配置 Tag:

int FlvParser::VideoTag::ParseH264Configuration(FlvParser* pParser,
                                                uint8_t* pTagData) {
    uint8_t* pd = pTagData;
	// 在第九个字节的最后2bit,加上1是因为长度就=x+1
    pParser->_nNaluUnitLength = (pd[9] & 0x03) + 1;

    int pps_size, sps_size;

    sps_size = SolveU16(pd + 11); // 序列参数数据长度

    pps_size = SolveU16(pd + 11 + 2 + (1 + sps_size) + 1); //图像参数数据长度

	// 这里的4是StartCode,每一个Nalu都有StartCode
    _nMediaLen = 4 + sps_size + 4 + pps_size;
    _pMedia = new uint8_t[_nMediaLen];

    memset(_pMedia, 0, _nMediaLen);

    memcpy(_pMedia, &nH264StartCode, 4);
    memcpy(_pMedia + 4, pd + 11 + 2, sps_size);
    memcpy(_pMedia + 4 + sps_size, &nH264StartCode, 4);
    memcpy(_pMedia + 8 + sps_size, pd + 11 + 2 + sps_size + 1 + 2, pps_size);

    return 0;
}

解析 H264Nalu:

int FlvParser::VideoTag::ParseNalu(FlvParser* pParser, uint8_t* pTagData) {
    uint8_t* pd = pTagData;

    int nOffset = 0;

    _nMediaLen = 0;
    // 这里加上10是因为还需要放入StartCode
    _pMedia = new uint8_t[_header.nDataSize + 10];

    nOffset = 5;

    while (1) {
        if (nOffset >= _header.nDataSize) break;

        int nNaluLen = 0;

        switch (pParser->_nNaluUnitLength) {
        // 匹配NaluUnitLength,一般来说都是4
            case 4:
                nNaluLen = SolveU32(pd + nOffset);
                break;
            case 3:
                nNaluLen = SolveU24(pd + nOffset);
                break;
            case 2:
                nNaluLen = SolveU16(pd + nOffset);
                break;
            default:
                nNaluLen = SolveU8(pd + nOffset);
                break;
        }

        memcpy(_pMedia + _nMediaLen, &nH264StartCode, 4);
        memcpy(_pMedia + _nMediaLen + 4,
               pd + nOffset + pParser->_nNaluUnitLength, nNaluLen);
        nOffset += (nNaluLen + pParser->_nNaluUnitLength);
        _nMediaLen += (4 + nNaluLen);
    }

    return 0;
}

源码