FLV解码器FlvParser的实现
手写FlvParser
FLV格式
这里可以看到 flv 的格式,本内容只介绍 flv 解析部分,其他就不介绍了。
Flv 数据包 Parser
FlvHeader
这里可以看到 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; // 提取是否有音频bitpHeader->bHaveVideo = (pBuf[4] >> 0) & 0x01; // 提取是否有视频bitpHeader->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
截图。
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 bodyuint8_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
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
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+1pParser->_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,一般来说都是4case 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;
}
参考资料:https://github.com/0voice