上次分析處理PS流的結構後,仔細觀察了下具體的PS包結構,如下圖:
在pes包中跳過pes header後即是h264的碼流了,如圖圓圈所示。頭四個pes包攜帶h264幀類型即爲sps,pps,sei和I幀,如圖小圈標記的。解析代碼如下:
#ifndef PROGRAM_STRAM_PARSER_H
#define PROGRAM_STRAM_PARSER_H
#include <stdio.h>
#include <vector>
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<SRawVideoFrame*>& 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<SRawVideoFrame*> m_vecRawVideoFrames;
};
#endif
#include<string.h>
#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頭。