解析视频标签
入口函数CreateTag
流程如下:
1、解析标签头部
2、判断标签头部的类型
3、根据标签头部的类型,解析不同的标签
4、如果是视频类型的标签,那么就创建并解析视频标签
CFlvParser::Tag *CFlvParser::CreateTag(unsigned char *pBuf, int nLeftLen)
{
// 开始解析标签头部
TagHeader header;
header.nType = ShowU8(pBuf+0); // 类型
header.nDataSize = ShowU24(pBuf + 1); // 标签body的长度
header.nTimeStamp = ShowU24(pBuf + 4); // 时间戳
header.nTSEx = ShowU8(pBuf + 7); // 时间戳的扩展字段
header.nStreamID = ShowU24(pBuf + 8); // 流的id
header.nTotalTS = (unsigned int)((header.nTSEx << 24)) + header.nTimeStamp;
// 标签头部解析结束
cout << "total TS : " << header.nTotalTS << endl;
//cout << "nLeftLen : " << nLeftLen << " , nDataSize : " << pTag->header.nDataSize << endl;
if ((header.nDataSize + 11) > nLeftLen)
{
return NULL;
}
Tag *pTag;
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;
default: // script类型的Tag
pTag = new Tag();
pTag->Init(&header, pBuf, nLeftLen);
}
return pTag;
}
创建视频标签
流程如下:
1、初始化
2、解析帧类型
3、解析视频编码类型
4、解析视频标签
CFlvParser::CVideoTag::CVideoTag(TagHeader *pHeader, unsigned char *pBuf, int nLeftLen, CFlvParser *pParser)
{
// 初始化
Init(pHeader, pBuf, nLeftLen);
unsigned char *pd = _pTagData;
_nFrameType = (pd[0] & 0xf0) >> 4; // 帧类型
_nCodecID = pd[0] & 0x0f; // 视频编码类型
// 开始解析
if (_header.nType == 0x09 && _nCodecID == 7)
{
ParseH264Tag(pParser);
}
}
解析视频标签
流程如下:
1、解析数据包类型
2、如果数据包是配置信息,那么就解析配置信息
3、如果数据包是视频数据,那么就解析视频数据
int CFlvParser::CVideoTag::ParseH264Tag(CFlvParser *pParser)
{
unsigned char *pd = _pTagData;
/*
** 数据包的类型
** 视频数据被压缩之后被打包成数据包在网上传输
** 有两种类型的数据包:视频信息包(sps、pps等)和视频数据包(视频的压缩数据)
*/
int nAVCPacketType = pd[1];
int nCompositionTime = CFlvParser::ShowU24(pd + 2);
// 如果是视频配置信息
if (nAVCPacketType == 0)
{
ParseH264Configuration(pParser, pd);
}
// 如果是视频数据
else if (nAVCPacketType == 1)
{
ParseNalu(pParser, pd);
}
else
{
}
return 1;
}
解析视频配置信息
流程如下:
1、解析配置信息的长度
2、解析sps、pps的长度
3、保存元数据,元数据即sps、pps等
int CFlvParser::CVideoTag::ParseH264Configuration(CFlvParser *pParser, unsigned char *pTagData)
{
unsigned char *pd = pTagData;
// 配置信息长度
pParser->_nNalUnitLength = (pd[9] & 0x03) + 1;
int sps_size, pps_size;
// sps(序列参数集)的长度
sps_size = CFlvParser::ShowU16(pd + 11);
// pps(图像参数集)的长度
pps_size = CFlvParser::ShowU16(pd + 11 + (2 + sps_size) + 1);
// 元数据的长度
_nMediaLen = 4 + sps_size + 4 + pps_size;
_pMedia = new unsigned char[_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;
}
解析视频数据
流程如下:
1、如果一个Tag还没解析完成,那么执行下面步骤
2、计算NALU的长度
3、获取NALU的起始码
4、保存NALU的数据
5、调用自定义的处理函数对NALU数据进行处理
int CFlvParser::CVideoTag::ParseNalu(CFlvParser *pParser, unsigned char *pTagData)
{
unsigned char *pd = pTagData;
int nOffset = 0;
_pMedia = new unsigned char[_header.nDataSize+10];
_nMediaLen = 0;
nOffset = 5;
while (1)
{
// 如果解析玩了一个Tag,那么就跳出循环
if (nOffset >= _header.nDataSize)
break;
// 计算NALU(视频数据被包装成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);
nOffset += (pParser->_nNalUnitLength + nNaluLen);
}
return 1;
}
自定义的视频处理
把视频的NALU解析出来之后,可以根据自己的需要往视频中添加内容
// 用户可以根据自己的需要,对该函数进行修改或者扩展
// 下面这个函数的功能大致就是往视频中写入SEI信息
int CVideojj::Process(unsigned char *pNalu, int nNaluLen, int nTimeStamp)
{
// 如果起始码后面的两个字节是0x05或者0x06,那么表示IDR图像或者SEI信息
if (pNalu[4] != 0x06 || pNalu[5] != 0x05)
return 0;
unsigned char *p = pNalu + 4 + 2;
while (*p++ == 0xff);
// 往NALU中写入SEI信息
const char *szVideojjUUID = "VideojjLeonUUID";
char *pp = (char *)p;
for (int i = 0; i < strlen(szVideojjUUID); i++)
{
if (pp[i] != szVideojjUUID[i])
return 0;
}
VjjSEI sei;
sei.nTimeStamp = nTimeStamp;
sei.nLen = nNaluLen - (pp - (char *)pNalu) - 16 - 1;
sei.szUD = new char[sei.nLen];
memcpy(sei.szUD, pp + 16, sei.nLen);
_vVjjSEI.push_back(sei);
return 1;
}