程序功能:
解析flv文件,重写一个h264文件,如输入movie.flv , 输出movie.h264
(只有视频,没有声音)
函数调用流程:
1
Process() 处理函数:1 读文件 2 解析flv 3 将flv的视频数据输出到.h264文件
2
Parse() 解析flv 文件,入口函数
DumpH264() 生成h264文件
3
CreateFlvHeader() 解析 flv head
4
CreateTag() 解析视频标签(tag)入口函数
5
CVideoTag() 创建video tag
6
ParseH264Tag() 解析 vido tag
7
ParseH264Configuration() 解析配置信息
8
ParseNalu() 解析nalu数据
9
DumpH264() 生成h264文件,通过_pMedia写入
部分FlvParse.h
class CFlvParser
{
public:
CFlvParser();
virtual ~CFlvParser();
int Parse(uint8_t *pBuf, int nBufSize, int &nUsedLen);
int PrintInfo();
int DumpH264(const std::string &path);
int DumpAAC(const std::string &path);
int DumpFlv(const std::string &path);
private:
// FLV头
typedef struct FlvHeader_s
{
int nVersion; // 版本
int bHaveVideo; // 是否包含视频
int bHaveAudio; // 是否包含音频
int nHeadSize; // FLV头部长度
/*
** 指向存放FLV头部的buffer
** 上面的三个成员指明了FLV头部的信息,是从FLV的头部中“翻译”得到的,
** 真实的FLV头部是一个二进制比特串,放在一个buffer中,由pFlvHeader成员指明
*/
uint8_t *pFlvHeader;
} FlvHeader;
// Tag头部
struct TagHeader
{
int nType; // 类型
int nDataSize; // 标签body的大小
int nTimeStamp; // 时间戳
int nTSEx; // 时间戳的扩展字节
int nStreamID; // 流的ID,总是0
uint32_t nTotalTS; // 完整的时间戳nTimeStamp和nTSEx拼装
TagHeader() : nType(0), nDataSize(0), nTimeStamp(0), nTSEx(0), nStreamID(0), nTotalTS(0) {}
~TagHeader() {}
};
class Tag
{
public:
Tag() : _pTagHeader(NULL), _pTagData(NULL), _pMedia(NULL), _nMediaLen(0) {}
void Init(TagHeader *pHeader, uint8_t *pBuf, int nLeftLen);
TagHeader _header;
uint8_t *_pTagHeader; // 指向标签头部
uint8_t *_pTagData; // 指向标签body,原始的tag data数据
uint8_t *_pMedia; // 指向标签的元数据,改造后的数据
int _nMediaLen; // 数据长度
};
class CVideoTag : public Tag
{
public:
/**
* @brief CVideoTag
* @param pHeader
* @param pBuf 整个tag的起始地址
* @param nLeftLen
* @param pParser
*/
CVideoTag(TagHeader *pHeader, uint8_t *pBuf, int nLeftLen, CFlvParser *pParser);
int _nFrameType; // 帧类型
int _nCodecID; // 视频编解码类型
int ParseH264Tag(CFlvParser *pParser);
int ParseH264Configuration(CFlvParser *pParser, uint8_t *pTagData);
int ParseNalu(CFlvParser *pParser, uint8_t *pTagData);
};
friend class Tag;
private:
FlvHeader *CreateFlvHeader(uint8_t *pBuf);
Tag *CreateTag(uint8_t *pBuf, int nLeftLen);
private:
FlvHeader* _pFlvHeader;
vector<Tag *> _vpTag;
部分FlvParse.cpp
int main()
{
fstream fin;
fin.open(argv[1], ios_base::in | ios_base::binary); // 打开文件
if (!fin)
return 0;
Process(fin); //输出文件
fin.close();
return 1;
}
// 循环读文件
void Process(fstream &fin)
{
CFlvParser parser;
int nBufSize = 2*1024 * 1024;
int nFlvPos = 0;
uint8_t *pBuf, *pBak;
pBuf = new uint8_t[nBufSize];
pBak = new uint8_t[nBufSize];
while (1)
{
int nReadNum = 0;
int nUsedLen = 0;
fin.read((char *)pBuf + nFlvPos, nBufSize - nFlvPos); // 循环读取数据,读到内存当中
nReadNum = fin.gcount();
if (nReadNum == 0)
break;
nFlvPos += nReadNum;
parser.Parse(pBuf, nFlvPos, nUsedLen);
if (nFlvPos != nUsedLen)
{
memcpy(pBak, pBuf + nUsedLen, nFlvPos - nUsedLen);
memcpy(pBuf, pBak, nFlvPos - nUsedLen);
}
nFlvPos -= nUsedLen;
}
parser.PrintInfo();
parser.DumpH264("parser.264");
delete []pBak;
delete []pBuf;
}
// 解析
int CFlvParser::Parse(uint8_t *pBuf, int nBufSize, int &nUsedLen){
CheckBuffer(9); // 检测9字节的head
// 检测header
while(1) {
CheckBuffer(15); // nPrevSize(4字节) + Tag header(11字节)
}
Tag *pTag = CreateTag(pBuf + nOffset, nBufSize-nOffset); //创建头部
if (pTag == NULL)
{
nOffset -= 4;
break;
}
nOffset += (11 + pTag->_header.nDataSize);
_vpTag.push_back(pTag); // vector<Tag *> _vpTag;
}
// 写flv header
CFlvParser::FlvHeader *CFlvParser::CreateFlvHeader(uint8_t *pBuf)
{
pHeader->pFlvHeader = new uint8_t[pHeader->nHeadSize];
memcpy(pHeader->pFlvHeader, pBuf, pHeader->nHeadSize);
}
// 写tag 头部
CFlvParser::Tag *CFlvParser::CreateTag(uint8_t *pBuf, int nLeftLen) {
TagHeader header;
header.nType = ShowU8(pBuf+0); // 类型
header.nDataSize = ShowU24(pBuf + 1); // 1 为 偏移 nType的字节数
header.nTimeStamp = ShowU24(pBuf + 4); // 4 为 偏移nType和 nDataSize的 字数之和
switch (header.nType) {
case 0x09: // 视频类型的Tag
pTag = new CVideoTag(&header, pBuf, nLeftLen, this);
break;
case 0x08: // 音频类型的Tag
pTag = new CAudioTag(&header, pBuf, nLeftLen, this);
break;
case 0x12: // script Tag
pTag = new CMetaDataTag(&header, pBuf, nLeftLen, this);
break;
default: // script类型的Tag
pTag = new Tag();
pTag->Init(&header, pBuf, nLeftLen);
}
}
// 之后解析tag的类型 :video aac script
// 构造函数
CFlvParser::CVideoTag::CVideoTag(TagHeader *pHeader, uint8_t *pBuf, int nLeftLen, CFlvParser *pParser)
{
// 初始化
Init(pHeader, pBuf, nLeftLen);
uint8_t *pd = _pTagData;
_nFrameType = (pd[0] & 0xf0) >> 4; // 帧类型
_nCodecID = pd[0] & 0x0f; // 视频编码类型
// 开始解析
if (_header.nType == 0x09 && _nCodecID == 7) //类型为7进行解析
{
ParseH264Tag(pParser);
}
}
int CFlvParser::CVideoTag::ParseH264Tag(CFlvParser *pParser)
{
uint8_t *pd = _pTagData;
/*
** 数据包的类型
** 视频数据被压缩之后被打包成数据包在网上传输
** 有两种类型的数据包:视频信息包(sps、pps等)和视频数据包(视频的压缩数据)
*/
int nAVCPacketType = pd[1];
int nCompositionTime = CFlvParser::ShowU24(pd + 2);
// 如果是视频配置信息
if (nAVCPacketType == 0) // AVC sequence header
{
ParseH264Configuration(pParser, pd);
}
// 如果是视频数据
else if (nAVCPacketType == 1) // AVC NALU
{
ParseNalu(pParser, pd);
}
return 1;
}
int CFlvParser::CVideoTag::ParseH264Configuration(CFlvParser *pParser, uint8_t *pTagData)
{
uint8_t *pd = pTagData;
// 跨过 Tag Data的VIDEODATA(1字节) AVCVIDEOPACKET(AVCPacketType(1字节) 和CompositionTime(3字节) 4字节)
// 总共跨过5个字节
// NalUnit长度表示占用的字节
pParser->_nNalUnitLength = (pd[9] & 0x03) + 1; // lengthSizeMinusOne 9 = 5 + 4 &0x03只占两个字节
int sps_size, pps_size;
// sps(序列参数集)的长度
sps_size = CFlvParser::ShowU16(pd + 11); // sequenceParameterSetLength 11 = 5 + 6
// pps(图像参数集)的长度
pps_size = CFlvParser::ShowU16(pd + 11 + (2 + sps_size) + 1); // pictureParameterSetLength
// 元数据的长度
_nMediaLen = 4 + sps_size + 4 + pps_size; // 添加start code
_pMedia = new uint8_t[_nMediaLen];
// 保存元数据
memcpy(_pMedia, &nH264StartCode, 4);
memcpy(_pMedia + 4, pd + 11 + 2, sps_size);
memcpy(_pMedia + 4 + sps_size, &nH264StartCode, 4);
memcpy(_pMedia + 4 + sps_size + 4, pd + 11 + 2 + sps_size + 2 + 1, pps_size);
return 1;
}
int CFlvParser::CVideoTag::ParseNalu(CFlvParser *pParser, uint8_t *pTagData)
{
uint8_t *pd = pTagData;
int nOffset = 0;
_pMedia = new uint8_t[_header.nDataSize+10];
_nMediaLen = 0;
// 跨过 Tag Data的VIDEODATA(1字节) AVCVIDEOPACKET(AVCPacketType和CompositionTime 4字节)
nOffset = 5; // 总共跨过5个字节 132 - 5 = 127 = _nNalUnitLength(4字节) + NALU(123字节)
// startcode(4字节) + NALU(123字节) = 127
while (1)
{
// 如果解析完了一个Tag,那么就跳出循环
if (nOffset >= _header.nDataSize)
break;
// 计算NALU(视频数据被包装成NALU在网上传输)的长度,
// 一个tag可能包含多个nalu, 所以每个nalu前面有NalUnitLength字节表示每个nalu的长度
// video tag data 如果有两个nalu ,一个长度是300字节,一个500字节
// 0x00 0x00 0x01 0x2c (300) ------nalu--------0x00 0x00 0x01 0xF4(500)
// _pMeida 先拷贝startcode,再拷贝nalu
int nNaluLen;
switch (pParser->_nNalUnitLength)
{
case 4:
nNaluLen = CFlvParser::ShowU32(pd + nOffset);
break;
case 3:
nNaluLen = CFlvParser::ShowU24(pd + nOffset);
break;
case 2:
nNaluLen = CFlvParser::ShowU16(pd + nOffset);
break;
default:
nNaluLen = CFlvParser::ShowU8(pd + nOffset);
}
// 获取NALU的起始码
memcpy(_pMedia + _nMediaLen, &nH264StartCode, 4);
// 复制NALU的数据
memcpy(_pMedia + _nMediaLen + 4, pd + nOffset + pParser->_nNalUnitLength, nNaluLen);
// 解析NALU
// pParser->_vjj->Process(_pMedia+_nMediaLen, 4+nNaluLen, _header.nTotalTS);
_nMediaLen += (4 + nNaluLen); // 4 startcode
nOffset += (pParser->_nNalUnitLength + nNaluLen);
}
return 1;
}
// 写h264数据
int CFlvParser::DumpH264(const std::string &path)
{
fstream f;
f.open(path.c_str(), ios_base::out|ios_base::binary);
vector<Tag *>::iterator it_tag;
for (it_tag = _vpTag.begin(); it_tag != _vpTag.end(); it_tag++)
{
if ((*it_tag)->_header.nType != 0x09)
continue;
f.write((char *)(*it_tag)->_pMedia, (*it_tag)->_nMediaLen);
}
f.close();
return 1;
}