上次分析处理PS流的结构后,仔细观察了下具体的PS包结构,如下图:
在pes包中跳过pes header后即是h264的码流了,如图圆圈所示。头四个pes包携带h264帧类型即为sps,pps,sei和I帧,如图小圈标记的。解析代码如下:
#ifndef PROGRAM_STRAM_PARSER_H
#define PROGRAM_STRAM_PARSER_H
#include
#include
enum enCurrentState
{
enCurrentState_parsing_ph,
enCurrentState_parsing_sh,
enCureentState_parsing_pes
};
#define PACK_START_CODE 0x000001BA
#define SYSTEM_HEADER_START_CODE 0x000001BB
#define PACKET_START_CODE_PREFIX 0x00000100
#define PES_START_CODE_PREFIX 0x01
struct SRawVideoFrame
{
SRawVideoFrame()
{
bMark = false;
pRawVideoFrame = NULL;
iRawVideoSize = 0;
type = 0;
}
~SRawVideoFrame()
{
if (NULL != pRawVideoFrame)
{
delete[] pRawVideoFrame;
pRawVideoFrame = NULL;
}
}
bool bMark;
unsigned char* pRawVideoFrame;
int iRawVideoSize;
//0表示完整的一帧数据 1表示一帧数据的开头
int type;
};
class C28181PsStreamParser
{
public:
C28181PsStreamParser()
{}
void SetData(unsigned char* pStreamData, int iSize)
{
m_pStreamData = pStreamData;
m_iSize = iSize;
}
C28181PsStreamParser(unsigned char* pStreamData,int iSize) :m_enState(enCurrentState_parsing_ph),
m_pStreamData(pStreamData),m_iSize(iSize), m_iOffset(0), m_iPayloadRemianLength(0)
{
}
~C28181PsStreamParser()
{
for (int i = 0; i < m_vecRawVideoFrames.size(); ++i)
{
if (NULL != m_vecRawVideoFrames[i])
{
delete m_vecRawVideoFrames[i];
m_vecRawVideoFrames[i] = NULL;
}
}
}
//获取当前解析出来的数据帧
void GetRawVideoFrames(std::vector& pVideoFrames)
{
pVideoFrames = m_vecRawVideoFrames;
}
public:
int Parse(const unsigned char* pData);
int GetRemainLength() { return m_iPayloadRemianLength; }
private:
static inline bool isPacketStartCode(unsigned code)
{
return (code & 0xFFFFFF00) == PACKET_START_CODE_PREFIX
&& code > SYSTEM_HEADER_START_CODE;
}
private:
int ParsePackHeader();
void ParseSystemHeader();
int PasePESPacket();
private:
unsigned int test4Bytes();
void skipBytes(int num);
unsigned short get2Bytes();
unsigned char get1Byte();
unsigned char* get3Bytes();
private:
enCurrentState m_enState;
unsigned char* m_pStreamData;
int m_iSize;
int m_iBits;
int m_iOffset;
int m_iPayloadRemianLength;
//unsigned char *m_pRawVideoData;
//int m_iRawVideoDataSize;
std::vector m_vecRawVideoFrames;
};
#endif
#include
#include "programstreamparser.h"
int C28181PsStreamParser::Parse(const unsigned char* pData)
{
do
{
if (m_iOffset >= m_iSize)
{//解析完毕就退出
return 0;
}
switch (m_enState)
{//状态机
case enCurrentState_parsing_ph:
{
ParsePackHeader();
break;
}
case enCurrentState_parsing_sh:
{
ParseSystemHeader();
break;
}
case enCureentState_parsing_pes:
{
PasePESPacket();
break;
}
default:
break;
}
} while (1);
}
int C28181PsStreamParser::ParsePackHeader()
{
int ioffset = 0;
unsigned int first4Bytes;
while (1)
{
first4Bytes = test4Bytes();
if (PACK_START_CODE == first4Bytes)
{//判断是否是头,跳过头
skipBytes(4);
break;
}
else if (first4Bytes == SYSTEM_HEADER_START_CODE)
{
m_enState = enCurrentState_parsing_sh;
return 0;
}
else
{
m_enState = enCureentState_parsing_pes;
return 0;
}
}
//跳过9字节
skipBytes(9);
//取stuffing_length
unsigned char* pLengthData = m_pStreamData + m_iOffset;
unsigned char uLenghth = pLengthData[0] & 7;
skipBytes(1);
skipBytes(uLenghth);
m_enState = enCurrentState_parsing_sh;
}
void C28181PsStreamParser::ParseSystemHeader()
{
unsigned StartCode = test4Bytes();
if (StartCode != SYSTEM_HEADER_START_CODE)
{
m_enState = enCureentState_parsing_pes;
return;
}
//跳过起始码
skipBytes(4);
unsigned short uHeaderLength = get2Bytes();
//直接跳过
skipBytes(uHeaderLength);
m_enState = enCureentState_parsing_pes;
}
int C28181PsStreamParser::PasePESPacket()
{
unsigned next4Bytes = test4Bytes();
if (SYSTEM_HEADER_START_CODE == next4Bytes)
{
m_enState = enCurrentState_parsing_sh;
return 0;
}
else if (PACK_START_CODE == next4Bytes)
{
m_enState = enCurrentState_parsing_ph;
return 0;
}
else
{
unsigned char* pData = m_pStreamData + m_iOffset;
if (pData[2] == 1 && pData[1] == 0 && pData[0] == 0)
{
}
else
{
m_enState = enCurrentState_parsing_ph;
return 0;
}
}
//获取stream id
unsigned char StreamID = get1Byte();
unsigned char StreamNum = StreamID;
//获取pes包长度
unsigned short PESLength = get2Bytes();
if (0xbc == StreamNum)
{//这是一个packet stream map
skipBytes(2);
//获取ps_info_length
unsigned short PSInfoLength = get2Bytes();
//跳过ps_info
skipBytes(PSInfoLength);
//获取es_map_length
unsigned short ESMapLength = get2Bytes();
unsigned char StreamType = get1Byte();
if (0x1B == StreamType)
{//H264
}
//跳过es map的长度
skipBytes(-1);
skipBytes(ESMapLength);
skipBytes(4);
}
else if (0xE0 == StreamNum)
{//为视频数据
//unsigned char* pData = get3Bytes();
unsigned char* pData = m_pStreamData + m_iOffset;
//取PES header data的长度
unsigned char uPesHeaderLength = pData[2];
//取PTS_DTS flag
unsigned char PTSDTSflag = pData[1];
PTSDTSflag >>= 6;
PTSDTSflag &= 0x3;
skipBytes(uPesHeaderLength);
//payload数据的长度
int iDataLength = PESLength - uPesHeaderLength - 3;
//rtp包中还剩余的字节数
int iRtpRemainLength = m_iSize - m_iOffset;
if (iDataLength > iRtpRemainLength)
{//payload的长度大于rtp包中剩余的字节数
m_iPayloadRemianLength = iDataLength - iRtpRemainLength;
m_enState = enCurrentState_parsing_ph;
SRawVideoFrame *pVideoFrame = new SRawVideoFrame;
pVideoFrame->bMark = false;
pVideoFrame->pRawVideoFrame = new unsigned char[iRtpRemainLength];
memcpy(pVideoFrame->pRawVideoFrame, m_pStreamData + m_iOffset, iRtpRemainLength);
pVideoFrame->iRawVideoSize = iRtpRemainLength;
pVideoFrame->type = 0;
m_vecRawVideoFrames.push_back(pVideoFrame);
m_iOffset += iRtpRemainLength;
return m_iPayloadRemianLength;
}
else
{//直接从该pes中取出一帧数据
SRawVideoFrame *pVideoFrame = new SRawVideoFrame;
pVideoFrame->pRawVideoFrame = new unsigned char[iDataLength];
memcpy(pVideoFrame->pRawVideoFrame, m_pStreamData + m_iOffset, iDataLength);
pVideoFrame->iRawVideoSize = iDataLength;
pVideoFrame->bMark = true;
pVideoFrame->type = 0;
m_vecRawVideoFrames.push_back(pVideoFrame);
m_iOffset += iDataLength;
}
}
else if (0xC0 == StreamNum)
{//为音频数据
}
m_enState = enCureentState_parsing_pes;
return 0;
}
unsigned int C28181PsStreamParser::test4Bytes()
{
unsigned char *ptr = m_pStreamData + m_iOffset;
return (ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3];
}
void C28181PsStreamParser::skipBytes(int num)
{
m_iOffset += num;
}
unsigned short C28181PsStreamParser::get2Bytes()
{
unsigned char* ptr = m_pStreamData + m_iOffset;
unsigned short ret = (ptr[0] << 8) | ptr[1];
m_iOffset += 2;
return ret;
}
unsigned char C28181PsStreamParser::get1Byte()
{
unsigned char* ptr = m_pStreamData + m_iOffset;
m_iOffset += 1;
return ptr[0];
}
unsigned char* C28181PsStreamParser::get3Bytes()
{
unsigned char* pData = new unsigned char[3];
unsigned char* ptr = m_pStreamData + m_iOffset;
memcpy(pData, ptr, 3);
m_iOffset += 3;
return pData;
}
以上代码只对视频流进行了解析,没有解析音频,处理音频也很简单,只要判断pes包中StreamNum值为0xC0即是音频包,在该例中,音频全部为g711A。当帧类型为I帧时,码流会被多个rtp包携带,所以涉及到组帧操作,在结构体SRawVideoFrame中的type字段用于表示当前是否为视频帧的开始部分。该海康设备发出的rtp流,每个rtp包都固定为1400个字节,所以I帧数据将近46个包,不同于FU-A的包,它不会添加FU-A的头,只是将数据分散到了多个rtp包中携带。通过长度判断数据包的个数后,直接拼接即可。需要注意的是在pes中带的h264流是带了nal头的,在一帧的开头需要去掉 nal头。