国标PS流解包(解封装)代码

2023-05-16

该代码最初的版本来自于互联网,首先感谢前辈无私分享的精神,这个PS流解析代码小巧和可读性好,是学习PS格式的一个很好的参考例子。但原来的代码有不少Bug,QuickGBLink在原先代码基础上做了很多问题修复和改进,现在对代码开源,希望能帮助更多国标开发者开发相关的功能。

实现的功能:

1. 解析PS包,从中提取出PES。

2. 解析PSM(节目流映射表),获得每个流的信息。

3. 对流格式做一些合法性判断和验证,检查数据是否完整。

4. 对实时流做了一些特殊处理,检查数据有无缺损或丢包,如果有则丢掉该块,从下一个PS头开始读起。

这个PS流解包的代码包含两个文件:streamdef.h, PsPacket.h,另外写了一个调用的例子(文件名:PsToEsConsole.cpp )。

streamdef.h文件

#ifndef _STREAMDEF_H_
#define _STREAMDEF_H_
#define MAX_PS_LENGTH  (1024*800)
#define MAX_PES_LENGTH (1024*1000)
#define MAX_ES_LENGTH  (0x100000)
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;

#pragma pack (1)

union littel_endian_size
{
	unsigned short int	length;
	unsigned char		byte[2];
};

struct pack_start_code
{
	unsigned char start_code[3];
	unsigned char stream_id[1];
};

typedef struct RTP_HEADER
{
#ifdef ORTP_BIGENDIAN
    uint16_t version:2;
    uint16_t padbit:1;
    uint16_t extbit:1;
    uint16_t cc:4;
    uint16_t markbit:1;
    uint16_t paytype:7;
#else
    uint16_t cc:4;
    uint16_t extbit:1;
    uint16_t padbit:1;
    uint16_t version:2;
    uint16_t paytype:7;  //负载类型
    uint16_t markbit:1;  //1表示前面的包为一个解码单元,0表示当前解码单元未结束
#endif
    uint16_t seq_number;  //序号
    uint32_t timestamp; //时间戳
    uint32_t ssrc;  //循环校验码
    //uint32_t csrc[16];
} RTP_header_t;
 
typedef struct ps_header{
    unsigned char pack_start_code[4];  //'0x000001BA'
 
    unsigned char system_clock_reference_base21:2;
    unsigned char marker_bit:1;
    unsigned char system_clock_reference_base1:3;
    unsigned char fix_bit:2;    //'01'
 
    unsigned char system_clock_reference_base22;
 
    unsigned char system_clock_reference_base31:2;
    unsigned char marker_bit1:1;
    unsigned char system_clock_reference_base23:5;
 
    unsigned char system_clock_reference_base32;
    unsigned char system_clock_reference_extension1:2;
    unsigned char marker_bit2:1;
    unsigned char system_clock_reference_base33:5; //system_clock_reference_base 33bit
 
    unsigned char marker_bit3:1;
    unsigned char system_clock_reference_extension2:7; //system_clock_reference_extension 9bit
 
    unsigned char program_mux_rate1;
 
    unsigned char program_mux_rate2;
    unsigned char marker_bit5:1;
    unsigned char marker_bit4:1;
    unsigned char program_mux_rate3:6;
 
    unsigned char pack_stuffing_length:3;
    unsigned char reserved:5;
}ps_header_t;  //14
 
typedef struct sh_header
{
    unsigned char system_header_start_code[4]; //32
 
    unsigned char header_length[2];            //16 uimsbf
 
    uint32_t marker_bit1:1;   //1  bslbf
    uint32_t rate_bound:22;   //22 uimsbf
    uint32_t marker_bit2:1;   //1 bslbf
    uint32_t audio_bound:6;   //6 uimsbf
    uint32_t fixed_flag:1;    //1 bslbf
    uint32_t CSPS_flag:1;     //1 bslbf
 
    uint16_t system_audio_lock_flag:1;  // bslbf
    uint16_t system_video_lock_flag:1;  // bslbf
    uint16_t marker_bit3:1;             // bslbf
    uint16_t video_bound:5;             // uimsbf
    uint16_t packet_rate_restriction_flag:1; //bslbf
    uint16_t reserved_bits:7;                //bslbf
    unsigned char reserved[6];
}sh_header_t; //18
 
//注意: 该PSM表结构有误,PSM表结构是可变长度的,包含的流的数目不同,长度也不一样。从22处开始后面的字段内容在不同情况下会有变化,详情请看PS格式中关于PSM的结构定义
typedef struct psm_header{
    unsigned char promgram_stream_map_start_code[4];
 
    unsigned char program_stream_map_length[2];
 
    unsigned char program_stream_map_version:5;
    unsigned char reserved1:2;
    unsigned char current_next_indicator:1;
 
    unsigned char marker_bit:1;
    unsigned char reserved2:7;
 
    unsigned char program_stream_info_length[2];
    unsigned char elementary_stream_map_length[2];
	//11: 下面可能会有多段Stream的定义,Stream的数目依据上面的elementary_stream_map_length的长度决定,StreamNum = elementary_stream_map_length/4
    unsigned char stream_type;
    unsigned char elementary_stream_id;
    unsigned char elementary_stream_info_length[2];
	//22:
    unsigned char CRC_32[4];
    unsigned char reserved[16];
}psm_header_t; 

struct program_stream_map
{
	pack_start_code PackStart;
	littel_endian_size PackLength;
};
 
typedef struct pes_header
{
    unsigned char pes_start_code_prefix[3];
    unsigned char stream_id;
    unsigned short PES_packet_length;
}pes_header_t; //6
 
typedef struct optional_pes_header{
    unsigned char original_or_copy:1;
    unsigned char copyright:1;
    unsigned char data_alignment_indicator:1;
    unsigned char PES_priority:1;
    unsigned char PES_scrambling_control:2;
    unsigned char fix_bit:2;
 
    unsigned char PES_extension_flag:1;
    unsigned char PES_CRC_flag:1;
    unsigned char additional_copy_info_flag:1;
    unsigned char DSM_trick_mode_flag:1;
    unsigned char ES_rate_flag:1;
    unsigned char ESCR_flag:1;
    unsigned char PTS_DTS_flags:2;
 
    unsigned char PES_header_data_length;
}optional_pes_header_t;
 
#pragma pack ()
 
 
enum PSStatus
{
    ps_padding, //未知状态
    ps_ps,      //ps状态
    ps_sh,
    ps_psm,
    ps_pes,
    ps_pes_video,
    ps_pes_audio
};

typedef struct _program_map_info
{
    unsigned char stream_type;
    unsigned char elementary_stream_id;
}program_map_info;
 
#endif

PsPacket.h文件

#ifndef _PSUNPACKET_H_
#define _PSUNPACKET_H_

#include <atlbase.h>
#include "streamdef.h"
#include <vector>
using namespace std;

#define MAX_VIDEO_BUFSIZE  (200*1024)

struct ESFrameData
{
	unsigned char*	buffer;
	int				len;
	int             frame_num; //缓冲区中帧的数目
	unsigned char	cFrameType;			//帧的类型, 'I'、 'P'、 'B'
	unsigned int	bDropFrame;			//是否丢掉该帧,如果有丢包,则丢掉
	//PSStatus        ps_status;   //PS的类型
	 __int64  first_pts_time; //帧缓存里可能含有多帧,记录第一帧的时间戳
	 __int64 ptsTime; //帧缓存里最后一帧的时间戳
	 __int64 dtsTime;
	 unsigned char pts_dts_flag; //一个PS帧由多个PES帧组成,其中开头帧有时间戳,值为2(有PTS)或3(有PTS和DTS),而除开头帧之外的其他帧,该值为0,并且PTS和DTS都为0.
};


//#include <tuple>
//using namespace std::tr1;

#ifndef AV_RB16
#   define AV_RB16(x)                           \
    ((((const unsigned char*)(x))[0] << 8) |          \
    ((const unsigned char*)(x))[1])
#endif
 
 
static inline unsigned __int64 ff_parse_pes_pts(const unsigned char* buf) {
    return (unsigned __int64)(*buf & 0x0e) << 29 |
        (AV_RB16(buf+1) >> 1) << 15 |
        AV_RB16(buf+3) >> 1;
}
 
static unsigned __int64 get_pts(optional_pes_header* option)
{
    if(option->PTS_DTS_flags != 2 && option->PTS_DTS_flags != 3 && option->PTS_DTS_flags != 0)
    {
        return 0;
    }
    if((option->PTS_DTS_flags & 2) == 2)
    {
        unsigned char* pts = (unsigned char*)option + sizeof(optional_pes_header);
        return ff_parse_pes_pts(pts);
    }
    return 0;
}
 
static unsigned __int64 get_dts(optional_pes_header* option)
{
    if(option->PTS_DTS_flags != 2 && option->PTS_DTS_flags != 3 && option->PTS_DTS_flags != 0)
    {
        return 0;
    }
    if((option->PTS_DTS_flags & 3) == 3)
    {
        unsigned char* dts = (unsigned char*)option + sizeof(optional_pes_header) + 5;
        return ff_parse_pes_pts(dts);
    }
    return 0;
}
 
bool inline is_ps_header(ps_header_t* ps)
{
    if(ps->pack_start_code[0] == 0 && ps->pack_start_code[1] == 0 && ps->pack_start_code[2] == 1 && ps->pack_start_code[3] == 0xBA)
        return true;
    return false;
}
bool inline is_sh_header(sh_header_t* sh)
{
    if(sh->system_header_start_code[0] == 0 && sh->system_header_start_code[1] == 0 && sh->system_header_start_code[2] == 1 && sh->system_header_start_code[3] == 0xBB)
        return true;
    return false;
}

//bool inline is_psm_header(psm_header_t* psm)
//{
//    if(psm->promgram_stream_map_start_code[0] == 0 && psm->promgram_stream_map_start_code[1] == 0 && psm->promgram_stream_map_start_code[2] == 1 && psm->promgram_stream_map_start_code[3] == 0xBC)
//        return true;
//    return false;
//}
bool inline is_psm_header(program_stream_map * psm)
{
	if(psm->PackStart.start_code[0] == 0 && psm->PackStart.start_code[1] == 0 && psm->PackStart.start_code[2] == 1 && psm->PackStart.stream_id[0] == 0xBC)
		return true;
	return false;
}

bool inline is_pes_video_header(pes_header_t* pes)
{
    if(pes->pes_start_code_prefix[0]==0 && pes->pes_start_code_prefix[1] == 0 && pes->pes_start_code_prefix[2] == 1 && (pes->stream_id == 0xE0 || pes->stream_id == 0xE2))
        return true;
    return false;
}
bool inline is_pes_audio_header(pes_header_t* pes)
{
    if(pes->pes_start_code_prefix[0]==0 && pes->pes_start_code_prefix[1] == 0 && pes->pes_start_code_prefix[2] == 1 && pes->stream_id == 0xC0)
        return true;
    return false;
}
 
bool inline is_pes_header(pes_header_t* pes)
{
    if(pes->pes_start_code_prefix[0]==0 && pes->pes_start_code_prefix[1] == 0 && pes->pes_start_code_prefix[2] == 1)
    {
        if(pes->stream_id == 0xC0 || (pes->stream_id == 0xE0 || pes->stream_id == 0xE2))
        {
            return true;
        }
    }
    return false;
}
PSStatus inline pes_type(pes_header_t* pes)
{
    if(pes->pes_start_code_prefix[0]==0 && pes->pes_start_code_prefix[1] == 0 && pes->pes_start_code_prefix[2] == 1)
    {
        if(pes->stream_id == 0xC0)
        {
            return ps_pes_audio;
        }
        else if(pes->stream_id == 0xE0 || pes->stream_id == 0xE2)
        {
            return ps_pes_video;
        }
    }
    return ps_padding;
}
 
static bool NextMpegStartCode(BYTE& code, char * pBuffer, int nLeftLen, int & nReadBytes)
{
	//BitByteAlign();
	int i = 0;
    nReadBytes = 0;

	DWORD dw = -1;
	do
	{
		if(nLeftLen-- == 0) return(false);
		dw = (dw << 8) | (BYTE)pBuffer[i++];
		nReadBytes+=1;;
	}
	while((dw&0xffffff00) != 0x00000100);
	code = (BYTE)(dw&0xff);
	return(true);
}



/*
_1 是否包含数据
_2 下一个PS状态
_3 数据指针
_4 数据长度
*/
//typedef std::tr1::tuple<bool, PSStatus, pes_header_t*> pes_tuple;

struct pes_tuple
{
	bool           bContainData;
	PSStatus       nStatus;
	pes_header_t * pPESPtr;
	bool  bDataLost; //是否丢包

	pes_tuple(bool bContain, PSStatus status, pes_header_t* pPes, bool bLost)
	{
      bContainData = bContain;
	  bDataLost  = bLost;
	  nStatus = status;
	  pPESPtr = pPes;
	}
};


/*
_1 是否包含数据
_2 数据类型
_3 PTS时间戳
_4 DTS时间戳
_5 数据指针
_6 数据长度
*/
//typedef std::tr1::tuple<bool, unsigned char, unsigned __int64, unsigned __int64, char*, unsigned int> naked_tuple;

struct naked_tuple
{
	bool bContainData;
    unsigned char nStreamID;//0xE0-- 视频;0xC0--音频
	unsigned __int64 ptsTime;
	unsigned __int64 dtsTime;
	char*  pDataPtr;
	unsigned int nDataLen;
	bool  bDataLost; //是否丢包
	unsigned char pts_dts_flag; // 是否包含有PTS,DTS。如果包含PTS,值等于2;如果包含PTS和DTS,值等于3;如果没有包含PTS和DTS,值等于0

	naked_tuple(bool bContain, unsigned char nStreamType, unsigned __int64 pts,  unsigned char fpts_dts, unsigned __int64 dts, char* pData, unsigned int nLen, bool bLost)
	{
		bContainData	= bContain;
		nStreamID		= nStreamType;
		ptsTime			= pts;
		dtsTime         = dts;
		pDataPtr		= pData;
		nDataLen		= nLen;
		bDataLost       = bLost;
		pts_dts_flag = fpts_dts;
	}
};

class PSPacket
{
public:
    PSPacket()
    {
		m_pPSBuffer		= new char[MAX_PS_LENGTH];
		m_pPESBuffer	= new char[MAX_PES_LENGTH];
		m_pESBuffer		= new char[MAX_ES_LENGTH];

        m_status = ps_padding;
        m_nESLength = m_nPESIndicator = m_nPSWrtiePos = m_nPESLength = 0;

		m_bRealTime = false;
    }
	~PSPacket()
	{
		delete m_pPSBuffer;
        delete m_pPESBuffer;
		delete m_pESBuffer;
	}

	int GetWritePos()
	{
		return m_nPSWrtiePos;
	}

	int  GetReadPos()
	{
		return m_nPESIndicator;
	}

    void PSWrite(char* pBuffer, unsigned int sz)
    {
        if(m_nPSWrtiePos + sz < MAX_PS_LENGTH)
        {
            memcpy((m_pPSBuffer + m_nPSWrtiePos), pBuffer, sz);
            m_nPSWrtiePos += sz;
        }
        else
        {
            m_status = ps_padding;
            m_nESLength = m_nPESIndicator = m_nPSWrtiePos = m_nPESLength = 0;

			if(m_nPSWrtiePos + sz < MAX_PS_LENGTH)
			{
				memcpy((m_pPSBuffer + m_nPSWrtiePos), pBuffer, sz);
				m_nPSWrtiePos += sz;
			}
        }
 
    }

	//清空数据指针
	void  Empty()
	{
	    m_status = ps_padding;
        m_nESLength = m_nPESIndicator = m_nPSWrtiePos = m_nPESLength = 0;
		m_ProgramMap.clear();
	}

	//设置实时流的标志
	void SetRealTime(bool bRealTime)
	{
		m_bRealTime = bRealTime;
	}

	int GetEsData(ESFrameData * pVideoESFrame, ESFrameData * pAudioESFrame, BOOL & bDataLost)
	{
		int nRet = 0;
        int nStreamType = 0;
		unsigned char * pV = NULL; 
		unsigned char * pA = NULL;

		if (pVideoESFrame != NULL)
		{
			pV = pVideoESFrame->buffer;
			pVideoESFrame->frame_num = 0;
		}
		if (pAudioESFrame != NULL)
		{
			pA = pAudioESFrame->buffer;
			pAudioESFrame->frame_num = 0;
		}

		while(1)
		{

			naked_tuple es_packet = naked_payload();

			if(!es_packet.bContainData || es_packet.nDataLen == 0)
			{
				break;
			}

			if(es_packet.nStreamID == 0xE0 || es_packet.nStreamID == 0xE2)
			{
				if(pVideoESFrame != NULL)
				{
					ATLASSERT(pVideoESFrame->len + es_packet.nDataLen < MAX_VIDEO_BUFSIZE);

					if (pVideoESFrame->len + es_packet.nDataLen > MAX_VIDEO_BUFSIZE)
					{
						OutputDebugString("Data Len is over buffer size \n");
						break;
					}

					memcpy(pV, es_packet.pDataPtr, es_packet.nDataLen);
					pV += es_packet.nDataLen;
					pVideoESFrame->len += es_packet.nDataLen;

					pVideoESFrame->pts_dts_flag = es_packet.pts_dts_flag; //如果该值不为0,表示这是一个帧的帧头,并且带时间戳

					pVideoESFrame->ptsTime = es_packet.ptsTime;

					if(pVideoESFrame->frame_num == 0)
					     pVideoESFrame->first_pts_time = pVideoESFrame->ptsTime;

					pVideoESFrame->frame_num++;
				}

				if(es_packet.bDataLost)
					 bDataLost = TRUE;
				
				nStreamType |= 0x01;

				nRet = 1;
			}
			else if(es_packet.nStreamID == 0xC0)
			{
				if(pAudioESFrame != NULL)
				{
					ATLASSERT(pAudioESFrame->len + es_packet.nDataLen < 192*1024);

					memcpy(pA, es_packet.pDataPtr, es_packet.nDataLen);
					pA += es_packet.nDataLen;
					pAudioESFrame->len += es_packet.nDataLen;

					pAudioESFrame->ptsTime = es_packet.ptsTime;

					pAudioESFrame->pts_dts_flag = es_packet.pts_dts_flag;

					if (pAudioESFrame->frame_num == 0)
						pAudioESFrame->first_pts_time = pAudioESFrame->ptsTime;

					pAudioESFrame->frame_num++;
				}

				nStreamType |= 0x02;

				nRet = 1;
			}
		}

  //      if(nStreamType == 0)
		//{
		//	ATLTRACE("Error: GetEsData found no pes----------- \n");
		//}

		return nRet;
	}

    void RTPWrite(char* pBuffer, unsigned int sz)
    {
        char* data = (pBuffer + sizeof(RTP_header_t));
        unsigned int length =  sz - sizeof(RTP_header_t);
        if(m_nPSWrtiePos + length < MAX_PS_LENGTH)
        {
            memcpy((m_pPSBuffer + m_nPSWrtiePos), data, length);
            m_nPSWrtiePos += length;
        }
        else
        {
            m_status = ps_padding;
            m_nESLength = m_nPESIndicator = m_nPSWrtiePos = m_nPESLength = 0;

			if(m_nPSWrtiePos + length < MAX_PS_LENGTH)
			{
				memcpy((m_pPSBuffer + m_nPSWrtiePos), data, length);
				m_nPSWrtiePos += length;
			}
        }
    }

	//获取PS类型,用来区分视频和音频,注意函数声明为Static类型
	static PSStatus DetectPSType(BYTE * pBuffer, int nSize)
	{
		PSStatus _status = ps_padding;
		ps_header_t * _ps;
		pes_header_t * _pes;

		const int nEndPos = nSize - 1;
		int nReadPos = 0;

		while(nReadPos <  nEndPos)
		{
			int nRead = 0;
			BYTE b = 0;

			if(!NextMpegStartCode(b, (char*)pBuffer + nReadPos, nEndPos - nReadPos, nRead))
			{
				//ATLASSERT(0);
				break;
			}

			nReadPos += nRead - 4; //定位到开始码

			_ps = (ps_header_t*)(pBuffer + nReadPos);
			if(is_ps_header(_ps))
			{
				_status = ps_ps;
				nReadPos += sizeof(ps_header_t); continue;
			}

			if(b >= 0xbd && b < 0xf0) //合法的PES帧
			{
				_pes = (pes_header_t*)(pBuffer + nReadPos);

				//unsigned short PES_packet_length = ntohs(_pes->PES_packet_length);

				if (is_pes_header(_pes))
				{
					_status = ps_pes;
				}

				if(_status == ps_pes)
				{
					_status = pes_type(_pes);

					return _status;
				}
				else
				{
					nReadPos += 1;
				}

			} // if(b >= 0xbd && b < 0xf0)
			else
			{
				nReadPos += 4;
			}

		}//while

		//_status = ps_padding;

		return _status;
	}

    pes_tuple pes_payload()
    {
		//int nLastPos = m_nPESIndicator; //缓存旧的读取位置,如果下面读取不到指定类型的帧(ps_ps/ps_pes),则将读取指针要重新定位到旧的位置
     
		while(m_nPESIndicator <  m_nPSWrtiePos)
		{
			int nRead = 0;
            BYTE b = 0;

			if(!NextMpegStartCode(b, m_pPSBuffer + m_nPESIndicator, m_nPSWrtiePos - m_nPESIndicator, nRead))
			{
				//ATLASSERT(0);
				break;
			}

		    m_nPESIndicator += nRead - 4; //定位到开始码

			m_ps = (ps_header_t*)(m_pPSBuffer + m_nPESIndicator);
			if(is_ps_header(m_ps))
			{
				m_status = ps_ps;
				m_nPESIndicator += sizeof(ps_header_t); continue;
			}

			if(b >= 0xbd && b < 0xf0) //合法的PES帧
			{
		        m_pes = (pes_header_t*)(m_pPSBuffer + m_nPESIndicator);

				unsigned short PES_packet_length = ntohs(m_pes->PES_packet_length);

				if (is_pes_header(m_pes))
				{
					m_status = ps_pes; //修改当前状态为ps_pes
				}

				if(m_status == ps_pes)
				{
					if(b == 0xbe || b == 0xbf)
					{
						 m_nPESIndicator += PES_packet_length + sizeof(pes_header_t);
						 continue;
					}

					if(m_pes->stream_id == 0xC0)
					{
						int dummy = 1;
					}


					if((m_nPESIndicator + PES_packet_length + sizeof(pes_header_t)) <= m_nPSWrtiePos)
					{
						m_nPESLength = PES_packet_length + sizeof(pes_header_t);
						memcpy(m_pPESBuffer, m_pes, m_nPESLength);

						PSStatus status = pes_type(m_pes);

					   char* next = (m_pPSBuffer + m_nPESIndicator + sizeof(pes_header_t) + PES_packet_length);

						m_ps = (ps_header_t*)next;
						m_pes = (pes_header_t*)next;
		               
						 //定位下一个pes 或者 ps
						m_nPESIndicator += m_nPESLength;

						m_status = ps_pes;

						return pes_tuple(true, status, (pes_header_t*)m_pPESBuffer, false);  
					}
					else//PES帧数据不完整,缓冲区已经读到尾部
					{
						int remain = m_nPSWrtiePos - m_nPESIndicator;

						if(m_bRealTime) //实时流丢包的情况处理
						{
							m_nPESLength = /*PES_packet_length + sizeof(pes_header_t)*/remain;
							memcpy(m_pPESBuffer, m_pes, m_nPESLength);

							PSStatus status = pes_type(m_pes);

							m_ps = NULL;
							m_pes = NULL;
			         
							m_nPESIndicator = 0;
							m_nPSWrtiePos = 0;

							m_status = ps_pes;

							return pes_tuple(true, status, (pes_header_t*)m_pPESBuffer, true); 
						}
						else //把剩余数据移到缓冲区头部,留到下一次解析
						{

							if(m_nPESIndicator > 0) //增加条件判断
							{
							   memmove(m_pPSBuffer, m_pPSBuffer + m_nPESIndicator , remain);
							}
							m_nPSWrtiePos = remain; 
							m_nPESIndicator = 0;

							m_ps = (ps_header_t*)m_pPSBuffer;
							m_pes = (pes_header_t*)m_pPSBuffer;

							m_status = ps_pes;

							 return pes_tuple(false, m_status, NULL, false);
						}
					}
					
					
				}// if(m_status == ps_pes)
				else
				{
                    m_nPESIndicator += 1;
				}


			} // if(b >= 0xbd && b < 0xf0)
			else
			{
                //读取PSM表,获得流的数目和每个流的信息
				program_stream_map * psm_hdr = NULL;
				psm_hdr = (program_stream_map*)(m_pPSBuffer + m_nPESIndicator);
				if(is_psm_header(psm_hdr))
				{
					m_status = ps_psm;
				
					unsigned short psm_packet_len = ntohs(psm_hdr->PackLength.length);

					printf("psm map length: %d \n", psm_packet_len);

				    if((m_nPESIndicator + psm_packet_len + 2 + 4 ) > m_nPSWrtiePos ||  psm_packet_len > 36) //检查长度是否越界 | PSM表的长度是否为非法值
						break;

					unsigned char * p = (unsigned char*)psm_hdr + 6;

					p += 2;
					unsigned short  program_stream_info_length = ntohs(*(unsigned short *)p);
					p += 2;

					unsigned short elementary_stream_map_length = ntohs(*(unsigned short*)p);
					p += 2;

					int nStreamNum = elementary_stream_map_length/4;
					for(int i = 0; i < nStreamNum; i++)
					{
						unsigned char stream_type;
						unsigned char elementary_stream_id;
						unsigned short elementary_stream_info_length;

						stream_type = *(unsigned char*)p;
						elementary_stream_id = *(unsigned char*)(p+1);
						elementary_stream_info_length = ntohs(*(unsigned short*)(p+2));

						p += 4;
						p += elementary_stream_info_length;

						if(!FindESStreamInfo(elementary_stream_id))
						{
							program_map_info mapInfo;
							mapInfo.elementary_stream_id = elementary_stream_id;
							mapInfo.stream_type = stream_type;

							m_ProgramMap.push_back(mapInfo);
						}

					}

					m_nPESIndicator += psm_packet_len + 2 + 4;

					continue;
				}

				m_nPESIndicator += 4;
			}

		}//while

		m_nPSWrtiePos = 0; 
		m_nPESIndicator = 0;

		m_status = ps_padding;

END_FIND_PAYLOAD:
        return pes_tuple(false, ps_padding, NULL, false);
        
    }

    naked_tuple naked_payload()
    {
        naked_tuple tuple = naked_tuple(false, 0, 0, 0, 0, NULL, 0, false);
        do
        {
            pes_tuple t = pes_payload();
			if(! t.bContainData)
            {
                break;
            }
			PSStatus status = t.nStatus;
			pes_header_t* pes = t.pPESPtr;
            optional_pes_header* option = (optional_pes_header*)((char*)pes + sizeof(pes_header_t));
            if(option->PTS_DTS_flags != 2 && option->PTS_DTS_flags != 3 && option->PTS_DTS_flags != 0)
            {
                break;
            }
            unsigned __int64 pts = get_pts(option);
            unsigned __int64 dts = get_dts(option);
            unsigned char stream_id = pes->stream_id;
            unsigned short PES_packet_length = ntohs(pes->PES_packet_length); //不包括PES头的长度
            char* pESBuffer = ((char*)option + sizeof(optional_pes_header) + option->PES_header_data_length);
            int nESLength = 0;
			
			if(m_bRealTime && t.bDataLost)
			{
				nESLength = (int)m_nPESLength - sizeof(pes_header_t) - (sizeof(optional_pes_header) + option->PES_header_data_length);
			}
			else
			{
				nESLength = (int)PES_packet_length - (sizeof(optional_pes_header) + option->PES_header_data_length);
			}

			if(m_nESLength + nESLength > MAX_ES_LENGTH || nESLength < 0) //检查是否超出缓冲区大小
			{
				ATLASSERT(0);
				break;
			}

            memcpy(m_pESBuffer + m_nESLength, pESBuffer, nESLength);
            m_nESLength += nESLength;
            if((stream_id == 0xE0 || stream_id == 0xE2) && (status == ps_ps || status == ps_pes_video))
            {
				tuple = naked_tuple(true, 0xE0, pts, dts, option->PTS_DTS_flags, m_pESBuffer, m_nESLength, t.bDataLost);
                m_nESLength = 0;
            }
            else if(stream_id == 0xC0)
            {
                tuple = naked_tuple(true, 0xC0, pts, dts, option->PTS_DTS_flags, m_pESBuffer, m_nESLength, false);
                m_nESLength = 0;
            }
        } while (false);
        return tuple;
    }

	bool FindESStreamInfo(unsigned char stream_id)
	{
		std::vector<program_map_info>::iterator it;
		for(it = m_ProgramMap.begin(); it != m_ProgramMap.end(); it++)
		{
			if(it->elementary_stream_id == stream_id)
			{
				return true;
			}
		}
		return false;
	}

private:
    PSStatus      m_status;                     //当前状态

    //char          m_pPSBuffer[MAX_PS_LENGTH];   //PS缓冲区
	char * m_pPSBuffer;

    unsigned int  m_nPSWrtiePos;                //PS写入位置
    unsigned int  m_nPESIndicator;              //PES指针
private:
   // char          m_pPESBuffer[MAX_PES_LENGTH]; //PES缓冲区
	char * m_pPESBuffer;

    unsigned int  m_nPESLength;                 //PES数据长度
private:
    ps_header_t*  m_ps;                         //PS头
    sh_header_t*  m_sh;                         //系统头
    //psm_header_t* m_psm;                        //节目流头
    pes_header_t* m_pes;                        //PES头
private:
   // char         m_pESBuffer[MAX_ES_LENGTH];    //裸码流
	char*				m_pESBuffer;

    unsigned int		m_nESLength;               //裸码流长度

	bool   m_bRealTime; //是否为实时流

public:
	std::vector<program_map_info> m_ProgramMap;
};
 
#endif

PsToEsConsole.cpp文件

// PsToEsConsole.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream>
#include <tchar.h>
#include "PsPacket.h"

#include <atlbase.h>

struct AppParam
{
	char  srcFile[256];
	char  dstFile[256];
};

AppParam g_appParam;


void processParams(int argc, char* argv[], AppParam *param)
{
	int paramNums;
	
    memset(param, 0, sizeof(AppParam));

	for (int i=1; i<argc; i++) 
	{

       if((_stricmp(argv[i], "-s") == 0) && (i<argc-1))
		{
			paramNums = _stscanf(argv[++i], _T("%s"), param->srcFile);
			if (paramNums != 1)
				continue;
		}

       else if((_stricmp(argv[i], "-d") == 0) && (i<argc-1))
		{
			paramNums = _stscanf(argv[++i], _T("%s"), param->dstFile);
			if (paramNums != 1)
				continue;

		}
	}
}


int GetH246FromPs(const char * szPsFile, const char * szEsFile, int *h264length)
{
	PSPacket psDemux;
    //psDemux.SetRealTime(true); //如果输入的是实时流,需要设置这个开关

	psDemux.m_ProgramMap.clear();

	FILE * poutfile =  NULL;	
    FILE * pinfile = NULL;

	if(pinfile == NULL)
	{
		if (NULL == (pinfile = fopen(szPsFile, "rb")))
		{
			printf("Error: Open inputfilename file error\n");
			return -1;
		}
	}

	if(poutfile == NULL)
	{
		if (NULL == (poutfile = fopen(szEsFile, "wb")))
		{
			printf("Error: Open outputfilename file error\n");
			return -1;
		}
	}

   fseek(pinfile, 0, SEEK_END);  
   int  file_len = ftell(pinfile);  

   fseek(pinfile, 0, SEEK_SET);  

   	char* buffer = NULL;
	int length = 0;

	const int read_chunk_size = 64*1024;

	buffer = new char[read_chunk_size];
	memset(buffer, 0, read_chunk_size);

	int nLeftLen = file_len;

    int nReadLen = 0;
	int nVFrameNum = 0;
	int nAFrameNum = 0;

	*h264length = 0;

	DWORD dwStartTick = GetTickCount();

	while(nLeftLen > 0)
	{
		nReadLen = min(read_chunk_size, nLeftLen); //每次从文件读64K
	
		fread(buffer, 1, nReadLen, pinfile); //将文件数据读进Buffer

	    psDemux.PSWrite(buffer, nReadLen);

		int nOutputVideoEsSize = 0;

		while(1)
		{

			naked_tuple es_packet = psDemux.naked_payload();
			if(!es_packet.bContainData || es_packet.nDataLen == 0)
				break;

			if(es_packet.nStreamID == 0xE0)
			{
				int fwrite_number = fwrite(es_packet.pDataPtr, 1, es_packet.nDataLen, poutfile);
				*h264length += es_packet.nDataLen;
				nOutputVideoEsSize += es_packet.nDataLen;

				printf("[%x]VFrameNum: %d, EsLen: %d, pts: %lld \n", es_packet.nStreamID, nVFrameNum++, es_packet.nDataLen, es_packet.ptsTime);
			}
			else
			{
				//printf("[%x]AFrameNum: %d, EsLen: %d \n", es_packet.nStreamID, nAFrameNum++, es_packet.nDataLen);
			}
		}

		//ATLTRACE("WritePos: %d, ReadPos: %d, OutputEsSize: %d \n",  psDemux.GetWritePos(), psDemux.GetReadPos(), nOutputVideoEsSize);

		nLeftLen -= nReadLen;
	}
	///

	int nSize = psDemux.m_ProgramMap.size();
	for(int i=0; i<nSize; i++)
	{
		program_map_info & map_info = psDemux.m_ProgramMap[i];
		printf("stream_id: %x, stream_type: %x \n", map_info.elementary_stream_id, map_info.stream_type);
	}
	printf("Time cost %ld secs \n", (GetTickCount() - dwStartTick)/1000);

	fclose(pinfile);
	fclose(poutfile);

	delete buffer;

	return *h264length;
}

int _tmain(int argc, _TCHAR* argv[])
{
	int nArgParams = __argc -1;

#if 0
	if( nArgParams >= 2 )
	{
		//processParams(argc, argv, &g_appParam);

        //命令行的参数: 第一个参数---PS文件路径, 第二个参数---ES文件路径
		 _tcscpy(g_appParam.srcFile, __argv[1]);
		
		 _tcscpy(g_appParam.dstFile, __argv[2]);
	}
	else
	{
		printf("input parameter invalid \n");
		return 0;
	}

#else
	strcpy(g_appParam.srcFile, "G:\\videos\\InputFile.ps");
	strcpy(g_appParam.dstFile, "G:\\videos\\out.h264");
#endif

	int  nH264Length = 0;
	int nRet = GetH246FromPs(g_appParam.srcFile, g_appParam.dstFile, &nH264Length);
	if(nRet > 0)
	{
		printf("Extract H264 file succeeded, length: %d \n", nH264Length);
	}
	else
	{
		printf("Error: GetH264FromPs return %d \n", nRet);
	}

	return 0;
}

如果你要开发PS打包+RTP发送的功能,可以参考本博客的另外一篇博文:

《国标PS流打包和RTP发送代码》: https://blog.csdn.net/QuickGBLink/article/details/103434753

关于PS流格式的一些学习资料:

https://www.cnblogs.com/yuweifeng/p/9717354.html

https://blog.csdn.net/g0415shenw/article/details/80385088

https://blog.csdn.net/y601500359/article/details/97649112

 

 

 

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

国标PS流解包(解封装)代码 的相关文章

  • orb 纯背景物体识别

    include lt chrono gt include lt iostream gt include lt opencv2 core core hpp gt include lt opencv2 features2d features2d
  • Windows逆向安全(一)之基础知识(九)

    汇编比较三种循环 众所周知 xff0c 在C语言可以使用可以使用三种循环 xff0c 分别是 xff1a while do while和for 本文从汇编的角度出发 xff0c 观察这三种循环的差异 范例代码 先贴出三种循环的代码 xff0
  • c++读取yolov5模型进行目标检测(读取摄像头实时监测)

    文章介绍 本文是篇基于yolov5模型的一个工程 xff0c 主要是利用c 43 43 将yolov5模型进行调用并测试 xff0c 从而实现目标检测任务 任务过程中主要重点有两个 xff0c 第一 版本问题 xff0c 第二配置问题 一
  • ROS初学(二):ROS的三种基本通信机制(来自赵虚左老师)

    一 综述 ROS引入通信机制 xff0c 是为了实现ROS节点 xff08 进程 xff09 之间的通信 利用ROS进程的分布式框架 xff0c 可以使得每个进程独立的工作 xff0c 甚至分布于不同的主机工作 机器人上的各种传感器 xff
  • webpack 5.5.1 compiled with 1 error in 63 ms

    新建项目空白目录 xff0c 并运行 npm init y 命令 xff0c 初始化包管理配置文件 package json 新建 src 源代码目录 新建 src gt index html 首页和 src gt index js 脚本文
  • TypeError: Class constructor ServeCommand cannot be invoked without ‘new‘

    安装webpack插件 webpack dev server配置时将package json里面webpack改为webpack serve后 xff0c 运行npm run 脚本名 时出现 TypeError Class construc
  • Field ‘browser‘ doesn‘t contain a valid alias configuration

    意思是字段 39 browser 39 不包含有效的别名配置 意思就是说你打包的css路径不对 这个是我的代码 这里js里面导入的css路径不对 xff0c css文件夹不是和index js平级 xff0c 应该是上级所以正确的代码应该是
  • Failed to load resource: the server responded with a status of 404 (Not Found)

    问题场景 xff1a 编写路由模块 解决方法 xff1a 1 先检测路径是否有写错 2 将vscode中开的插件vetur关闭 3 上面方法不管用时 xff0c 将浏览器插件关闭 xff0c 例如油猴 有什么错误望大佬指出 xff01 xf
  • Vue3.js【未完成】

    Vue3 js 如何关闭烦人的vscode的提示框 https blog csdn net liuyuemozhu article details 101056556 ES6模块化与异步编程高级用法 ES6模块化 1 回顾 xff1a no
  • npm ERR! code ERESOLVEnpm ERR! ERESOLVE unable to resolve dependency tree

    npm下载报错 xff0c 这个是因为npm版本问题 xff0c 例如我下载element ui运行npm i element ui S 因为版本问题我就无法下载 xff0c 解决办法就是在末尾加上 legacy peer deps 即np
  • 前端面试题

    浏览器 输入一个URL到页面过程中发生了什么 xff08 高频 xff09 首先在浏览器中输入URL 查找缓存 xff1a 浏览器先查看浏览器缓存 系统缓存 路由缓存中是否有该地址页面 xff0c 如果有则显示页面内容 如果没有则进行下一步
  • MATALAB动态爱心代码

    第一步 xff1a 先下载MATLAB软件 xff08 该代码只适用于此软件 xff0c 要下载软件才可运行 xff09 第二步 xff1a 点击主页 新建脚本 xff08 可自己给脚本命名 xff0c 我命名的是aixin xff09 第
  • Linux相关博文中使用的头文件

    收录博文中看到的已经封装好的文件时 xff0c 方便各位查看 Log hpp 日志信息 pragma once include lt iostream gt include lt cstdio gt include lt cstdarg g
  • 构造函数和析构函数执行顺序

    析构函数运行顺序 xff1a 1 主函数结束 xff1b 2 函数调用结束 xff1b 3 用delete删除 xff08 new创建的 xff09 对象时 注释代码运行结果 输入坐标6 xff0c 3 xff0c 6 xff0c 2 xf
  • 27.【C/C++ 最全vector数组的用法 (详解)】

    vector动态数组 xff08 一 xff09 什么是vector xff1f xff08 二 xff09 vector的作用是什么 xff08 三 xff09 经典用法 1 vector函数的定义 代码展示 效果展示 2 vector的
  • 析构函数运行顺序

    全局变量 xff0c 静态局部变量 xff0c 局部变量空间的堆分配和栈分配 其中全局变量和静态局部变量时从 静态存储区中划分的空间 xff0c 二者的区别在于作用域的不同 xff0c 全局变量作用域大于静态局部变量 xff08 只用于声明
  • 03_临界段的保护

    一 临界段的概念 所谓的临界段就是在执行时不能被中断的代码段 在FreeRTOS中 xff0c 临界段最常出现的地方就是对全局变量的操作 那么什么情况下临界段会被打断 xff1f 一个是系统调度 xff0c 还有一个是外部中断 在FreeR
  • Qt自定义窗口继承基类

    设计一个窗口基类模板 xff0c 方便子类的统一风格使用 xff0c 省略重写事件函数 xff0c 代码思路
  • ROS学习笔记4:创建工作空间和功能包

    一 linux命令行使用基础 xff1a 1 cd命令 xff08 change directory xff09 xff1a xff08 1 xff09 语法 xff1a cd dirName xff08 dirName xff1a 要切换
  • 一段代码给你讲清楚【链式存储结构】

    1 基础定义 链式存储结构 xff08 linked storage structure xff09 xff0c 是逻辑存储结构的一种 xff0c 链式存储结构中每一个逻辑元素用一个内存结点存储 xff0c 每一个结点都是单独分配的 xff

随机推荐

  • Flask之before_request与after_request

    Flask之before request与after request 更多的用法或说明请查看文档 xff1a https dormousehole readthedocs io en latest api html flask Flask
  • C++教程之迭代器Iterator

    前言 之前的两篇文章我们主要了解了vector和string的相关知识 xff0c 从中我们知道可以通过下标来访问vector的元素或者string的字符 xff0c 但是除了这种方式还有一种更为通用的方式获取元素 xff0c 那就是迭代器
  • Qt 绘制图表 - Qt Charts版

    一 前言 自从 Qt 发布以来 xff0c 给广大跨平台界面研发人员带来了无数的福利 但是Qt自己却一直没有提供自带的图表库 xff0c 这就使得 QWT QCustomPlot 等第三方图表库有了巨大的生存空间 xff0c 为了降低开发成
  • Qt 多线程使用moveToThread

    Qt有两种多线程的方法 xff0c 其中一种是继承QThread的run函数 xff0c 另外一种是把一个继承于QObject的类用moveToThread函数转移到一个Thread里 Qt4 8之前都是使用继承QThread的run这种方
  • Qt 之http学习

    在Qt网络编程中 xff0c 需要用到协议 xff0c 即HTTP 它是超文本传输协议 xff0c 它是一种文件传输协议 新建工程名为 http xff0c 然后选中QtNetwork模块 xff0c 最后Base class选择QWidg
  • QT之QString

    字符串类 QString xff1a 在Qt官方文档中是这样描述QString的 xff1a The QString class provides a Unicode character string 我们可以将做C 43 43 中的str
  • 什么是栈?什么是任务栈?

    在裸机系统中 xff0c 它们统统放在一个叫栈的地方 栈式单片机RAM里一段连续的内存空间 栈的大小一般在启动文件或者链接脚本里里面指定 最后由C库函数 main进行初始化 每个任务都是独立的 xff0c 互不干扰的 所以要为每个任务都分配
  • OPENMV上的目标检测,目标定位模型

    目标板定位模型 代码地址 前言 在17届省赛结束后 xff0c 因为担心国赛场地光照影响较大 xff0c 写了这个目标检测算法 但因为时间以及实力不足 xff0c 算法仅有了个雏形 xff0c 没能成功部署 xff0c 非常遗憾 今年寒假终
  • select函数当对端关闭后的状态

    struct timeval timeout 61 5 0 rset 61 allset nready 61 select maxfd 43 1 amp rset NULL NULL amp timeout 当使用select等待客户端发送
  • web渗透之攻击 Authentication-1-

    可利用常见漏洞 认证http明文传输 xff0c 未加密 xff0c 攻击者可通过中间人攻击获取默认密码弱口令 xff0c 简单的credentials xff0c 可以通过暴力破解获取通过其他漏洞绕过认证不正确的重置密码功能密码在本地存储
  • UART——学习总结

    UART 一 含义 xff1a 通用异步收发传输器 xff08 Universal Asynchronous Receiver Transmitter xff0c 通常称作UART xff09 是一种串行异步收发协议 xff0c 二 工作原
  • 海康威视web3.0+vue项目使用(踩坑之路)ps:附webVideoCtrl.js文件

    文章目录 1 海康威视web3 02 vue项目应用3 运行环境 最近公司开发了一版监控视频的vue项目 我作为实现需求的前端真的是难为我了 各种调查后知道海康威视有web开发包 跟海康威视的SDK联系后发给我两个web二次开发包 CH W
  • GB28181国标平台软件(包含服务器和客户端)

    GB28181是国家针对安防领域制定的一个通信协议标准 xff0c 目的是解决不同平台 不同设备间通信协议不统一的问题 GB28181标准从2011制定的第一个版本开始 xff0c 到最近的2016版本 xff0c 制定的协议内容越来越细
  • 国标服务器GBServerPlatform对接海康NVR的操作说明

    这一篇文章介绍QuickGBLink开发的国标服务器如何对接海康NVR xff08 网络硬盘录像机 xff09 xff0c 主要讲解相关的操作步骤 xff08 GBT28181平台软件下载地址 xff1a https github com
  • 国标服务器GBServerPlatform对接海康摄像头的操作说明

    这篇文章讲解一下QuickGBLink开发的国标服务器如何对接海康IPC xff08 GBT28181国标平台软件下载地址 xff1a https github com QuickGBLink88 GB28181 ServerPlatfor
  • 开源工具:ONVIF协议实现搜索局域网摄像机(IPC)+RTSP地址获取

    Onvif协议做安防监控的人应该都很熟悉 xff0c 它广泛被用在各种安防设备中 xff0c 比如IPC DVR NVR 在局域网中发现IPC设备很多就是基于Onvif协议实现的 本文章给大家介绍QuickGBLink开源的一个工具 xff
  • 开源C++ RTSP客户端(支持RTP Over TCP)

    QuickGBLink开源的这个RTSP客户端是用C 43 43 写的 xff0c 目前仅实现了RTP Over TCP传输 支持接收多种视音频格式的流 xff0c 视频格式支持MPEG4 H264 H265 xff0c 音频格式支持AAC
  • 开源工具:实现用FFmpeg接收RTSP/RTMP/HLS流和录制文件

    上一篇文章介绍了QuickGBLink开发的一个RTSP客户端 开源C 43 43 RTSP客户端 xff0c 实现了通过TCP接收RTP流的功能 xff0c 但是在局域网监控中 xff0c 很多时候RTSP服务器和客户端都是用UDP模式来
  • 国标PS流打包和RTP发送代码

    这个国标PS流打包 封包的代码来自于互联网 xff0c QuickGBLink在原先代码基础上做了一些改进 因为代码来自于别人的成果 xff0c 基于互联网知识分享的精神 xff0c 我们打算将其开源 xff0c 希望能对更多学习和开发国标
  • 国标PS流解包(解封装)代码

    该代码最初的版本来自于互联网 xff0c 首先感谢前辈无私分享的精神 xff0c 这个PS流解析代码小巧和可读性好 xff0c 是学习PS格式的一个很好的参考例子 但原来的代码有不少Bug xff0c QuickGBLink在原先代码基础上