当前位置: 首页 > news >正文

FLV解码器FlvParser的实现

手写FlvParser

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; // 提取是否有音频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 截图。

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 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

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+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

http://www.dtcms.com/a/427162.html

相关文章:

  • Ansible自动化运维:从入门到实战,告别重复劳动!
  • 辽阳企业网站建设费用成品网站货源1277
  • 深度学习激活函数:从Sigmoid到GELU的演变历程——早期激活函数的局限与突破
  • Transformer模型:深度解析自然语言处理的革命性架构——从预训练范式到产业级实践
  • 网站建设公司网站建设专业品牌租服务器价格一览表
  • [ARC114 C] - Sequence Scores
  • php网站开发实例教程 源码表格在网站后台是居中可到前台为什么不居中
  • 网站建设是什么?政务网站建设目的_意义
  • 【微调大模型】中的梯度概念
  • Android TabLayout使用记录
  • 打开这个你会感谢我的网站网络考试
  • 核心营销词库管理助力品牌提升竞争力
  • UNIX下C语言编程与实践19-UNIX 三级索引结构:直接索引、一级/二级/三级间接索引的文件存储计算
  • 有了实名制域名怎么做网站国内跨境电商公司排行榜
  • 每种字符至少取K个
  • random.gauss()函数和numpy.random.normal()函数生成正态分布随机数
  • 【C++】STL -- 仿函数的定义与使用
  • Linux新环境安装solana开发所需全部套件(持续更新)
  • 一个高性能的HTTP和反向代理服务器:Nginx
  • 人工智能客服应用如何重塑电商服务生态?智能AI软件带来的三大变革
  • 网站建设的注意学校网站开发方案模板
  • 分布式架构初识:为什么需要分布式
  • asp网站用ftp怎么替换图片办公室oa管理系统
  • 个性化的个人网站广州企业开办一网通
  • Transformer(一)---背景介绍及架构介绍
  • 【完整源码+数据集+部署教程】气动铣刀型号分类图像分割系统: yolov8-seg-C2f-SCConv
  • 【Android】强制使用 CPU 大核或超大核
  • 【算法竞赛学习笔记】基础概念篇:算法复杂度
  • SLA操作系统双因素认证实现Windows远程桌面OTP双因子安全登录—从零搭建企业级RDP安全加固体系
  • 现在主流的网站开发语言360房产网郑州官网