精华内容
下载资源
问答
  • ps流解析h264
    2021-03-15 01:17:13

    前文 《简单解析海康PS流获取H264》 针对海康摄像头的PS流解析做了简单处理,基本逻辑是正确的,但最近几个摄像头出现了一些奇怪的问题,需要针对相关现象进行查询。

    RTP传输层

    项目中为了可靠处理且因其他原因使用了TCP来传输28181的RTP数据,根据 rfc4571 记录,使用TCP传输的时候只需要在RTP包之前打上2个字节的长度,用来控制,毕竟TCP是流式传输,只能靠长度字段进行包的区分。

    因此使用了TCP方式的RTP看起来问题不大,简单处理下就可以提取出PS数据了,事实也基本如此。但是出问题的摄像头却经常在解析RTP的时候出错,即判断RTP标志和SSRC出错,证明不是RTP包或者说包错乱。经过调试,打印出RTP头的所有信息,确实是在某个包后就出错了。打开二进制进行查看,发现出错之后往后部分数据之后,还能发现新的RTP包,看来是丢数据了。

    将抓包数据写个测试程序测试,测试程序在解析RTP出错之后,往后查新的RTP包(主要靠SSRC),然后打印每个包的时间戳和序列号,发现确实是中间丢了几个包,TCP还能丢包?再看看日志发现,出现问题的摄像头都是上传速度非常慢的摄像头,即每秒仅能上传几帧视频的,而且丢包丢的都是I帧,毕竟I帧非常大。

    因此猜测摄像头本身逻辑可能有点问题,即内部编码数据后,可能累积了大量未发送的数据,然后进行了内部丢包,而且这个丢包应该并没有按照一个RTP包去丢,而是直接丢弃的缓存数据,因而造成了上述问题。当然这只是根据现象的猜测,至于具体嘛,咱也不知道,咱也没有问。

    还有一个现象是,这种丢包发生一会之后网络连接就被摄像头给断了。因为暂时不确定其他推流速度正常的摄像头是否会偶发这种现象,因此暂时任务可能偶发,针对性的类似测试程序一样,出现问题了通过查找下一个包来缓解挽救。但这由于丢包肯定会导致解析媒体数据出现错误,也就是下面的问题。

    PS解析的错误处理

    网络丢包造成的无法解析

    如上,在丢包后,肯定会造成数据无法解析,这个时候肯定不能再继续处理了,肯定要结束当前的解析状态。也就是要上面网络层的配合,即网络层发现丢包之后要反馈给PS解析,让PS解析器及时的停止和重置,具体说来,大概是如果当前已经解析了一些媒体数据了,则提交这些数据,等待下一个PS包的到来(0x000001ba)。

    PS数据格式错误?

    在没有丢包的情况下,在解析PS的时候依然出现了问题,其格式大概如下

    e00f4b21e71b792d61b55b2c1ead81dc.png

    如上 0x000372F0 偏移处是前一个PES包(大小65478),里面解析了一个I帧0x000372FC,然后下一个PES包是 0x0004C72C 偏移处。二者偏移远远超过了 0xFFFF 大小,而PES的大小就是两个字节表示的。那么这就意味着格式有误,同时在RTP层解析确认此处的RTP流是顺序的,是没有丢包发生的。

    虽然这个软件能解析出来后续的,但确实是对不上格式定义的,那这个也是一个坑。这个地方我也打算同上处理,解析的时候发生错误时候,查找下一个 0x000001XX 即可。同时也会兼顾RTP层的包及新的PS包 0x000001BA 出现。

    其他问题

    PS中媒体的真正时间戳

    一般一个PS中存在了一个时间戳的视频包和一个时间戳的音频包,但是这个音频包和视频包可能时间戳是不相同的,为了表示准确,解析的时候尽量是按照时间戳变化后提交一次数据,无论是音频还是视频,基本上时间戳就没问题了。

    更多相关内容
  • Gb28181之Ps流解析H264

    万次阅读 2018-05-20 20:30:54
    本文详细描述如何通过ps流解析H264码流。先研究下PSM(节目流映射),PSM头定义如下:这里找了一个标准的PS流里面的PSM数据进行研究分析:packet_start_code_prefix—24bit :00 00 01map_stream_id-8bit:BCprogram...

    gb28181发送码流选择PS流,PS流在封装H264的数据。本文详细描述如何通过ps流解析H264码流。

    *************************PSM流解析**************************************************

    先研究下PSM(节目流映射),PSM头定义如下:

    这里找了一个标准的PS流里面的PSM数据进行研究分析:


    packet_start_code_prefix—24bit :00 00 01

    map_stream_id-8bit:BC

    program_stream_map_length-16bit:00 5A  ----->90 此字段含义为紧随此字段还有90个字节,通过计算每行16*5+10=90,正确

    current_next_indicator—1bit:--->1    置于1时指示发送的节目流映射为当前有效。置于0它指示发送的节目流映射尚未有效并且下一个节目流映射表将生效

    reserved-2bit:---->11    预留位

    program_stream_map_version-5bit:--00000    整个节目流映射的版本号,每当节目流映射的定义改变时,该版本号必须增

    1模32,current_next_indicator置为1时,为当前有效的节目流映射的版本,当current_next_indicator置为0时,是下一个有效的节目流映射的版本。
    reserved-7bit--->1111111
    marker_bit-1bit--->1

    program_stream_info_length-16bit--->0x00 0x24--->36   指示紧随此字段的描述符的总长为36个字节,

    剩下的36个字节为:

    for (i = 0; i < N; i++) { 
    descriptor()                
    }                


    elementary_stream_map_length-16bit -->0x00 0x2C--->44  指示在此节目流映射中所有基本流信息的以字节为单位的总长度。

    剩下44个字节为:

    for (i = 0; i < N1; i++) {

    stream_type

    elementary_stream_id

    elementary_stream_info_length

    for (i = 0; i < N2; i++) {

    descriptor()}

    }

    stream_type_8bit--->1B  含义如下:

    1、MPEG-4 视频流: 0x10;

    2、H.264 视频流: 0x1B;

    3、SVAC 视频流: 0x80;

    4、G.711 音频流: 0x90;

    5、G.722.1 音频流: 0x92;

    6、G.723.1 音频流: 0x93;

    7、G.729 音频流: 0x99;

    8、SVAC音频流: 0x9B。

    elementary_stream_id-8bit--->E0     指示存储此基本流的PES包的PES包头内stream_id 字段的赋值elementary_stream_info_length-16bit--->00  10--->16  指示紧随此字段的描述符长度为16个字节

    这里一共花掉了1+1+2+16 =20个字节 而实际长度为44个字节 剩下的24个字节为什么呢?我们再分析剩下的24个字节。

    stream_type_8bit--->90  说明音频为G711

    elementary_stream_id-8bit--->C0 说明为音频

    elementary_stream_info_length-16bit--->00  0c--->12  指示紧随此字段的描述符长度为12个字节

    这里一共花掉了1+1+2+12 = 14个字节 而剩下的24-14=10个字节

    剩下的这10个字节,表示无法理解。由于这段视频取的是海康的私有码流,说明这段码流加了私有信息。这里从新取了一段公安一所的测试的PS流进行分析,如下:

    这段是符合上述标准的。

    CRC_32--->0x00 00 00 00

    **************************************************Pack_header流解析**************************************************





    ***************************我是分割线*********************************

    下面主要讲解如何把PS流解析出裸H264数据

    一个完整的ps流一般结构包括Pack_header +System_header+Program_stream_map+PES_pakcet(N个) 其中PES_pakcet可能有N个

    可以通过头的标识来判断当前的类型:

            public static readonly byte[] Pack_start_code = { 0x00, 0x00, 0x01, 0xBA };
            public static readonly byte[] System_header_start_code = { 0x00, 0x00, 0x01, 0xBB };
            public static readonly byte[] Pakcet_start_code_prefix = { 0x00, 0x00, 0x01 };
    		public static readonly byte Psm_map_stream_id = 0xBC;
    		public static readonly byte Pes_map_stream_id_video = 0xE0;
    		public static readonly byte Pes_map_stream_id_audio = 0xC0;

    我的思路是先解析Pack_header 在逐个解析System_header/Program_stream_map/PES_pakcet  到了PES_pakcet时候,要注意下面一包可能是Pack_header 也可能是PES_pakcet 。代码如下:

    static bool ParsingPs()
            {
                //find  Pack_header
                if (m_Buf.Count<4)
                {
                    return false;
                }
                if(packState == PackState.PES_pakcet)
                {
                    //find Pack_header
                    if(m_Buf[0] == PsDefine.Pack_start_code[0] && m_Buf[0 + 1] == PsDefine.Pack_start_code[1] &&
                            m_Buf[0 + 2] == PsDefine.Pack_start_code[2] && m_Buf[0 + 3] == PsDefine.Pack_start_code[3])
                    {
                        packState = PackState.Adding;
                    }
                }
                if (packState == PackState.Adding)
                {
                    bool bFinePacketHead = false;
                    for(int i =0;i<m_Buf.Count-4;i++)
                    {
                        if(m_Buf[i] == PsDefine.Pack_start_code[0] && m_Buf[i+1] == PsDefine.Pack_start_code[1]
                            && m_Buf[i+2] == PsDefine.Pack_start_code[2] && m_Buf[i+3] == PsDefine.Pack_start_code[3])
                        {
                            bFinePacketHead = true;
                            if (i>0)
                            {
                                m_Buf.RemoveRange(0, i);
                            }
                            break;
                        }
                    }
                    if(bFinePacketHead)
                    {
                        Pack_header pack_Header = new Pack_header();
                        if (!pack_Header.ParsingPackheader(m_Buf.ToArray()))
                        {
                            return false;
                        }
                        if(m_Buf.Count> pack_Header.Pack_headerCount + pack_Header.Pack_stuffing_lenth_3)
                        {
                            m_Buf.RemoveRange(0, pack_Header.Pack_headerCount + pack_Header.Pack_stuffing_lenth_3);
                            packState = PackState.Pack_header;
                        }
                        else
                        {
                            return false;
                        }
                       
                    }
                   
                }
                if (packState == PackState.Pack_header)
                {
                    //find System_header
                    if (m_Buf.Count < 4)
                    {
                        return false;
                    }
                    {
                        if (m_Buf[0] == PsDefine.System_header_start_code[0] && m_Buf[0 + 1] == PsDefine.System_header_start_code[1] &&
                            m_Buf[0 + 2] == PsDefine.System_header_start_code[2] && m_Buf[0 + 3] == PsDefine.System_header_start_code[3])
                        {
                            System_header system_Header = new System_header();
                            if (!system_Header.ParsingSystem_header(m_Buf.ToArray()))
                            {
                                return false;
                            }
                            if(m_Buf.Count> system_Header.Header_lenth+6)
                            {
                                m_Buf.RemoveRange(0, system_Header.Header_lenth + 6);
                            }
                            else
                            {
                                return false;
                            }
                        }
                    }
    
                    //find Program_stream_map
                    if (m_Buf.Count < 4)
                    {
                        return false;
                    }
                    {
                        if(m_Buf[0] == PsDefine.Pakcet_start_code_prefix[0] &&
                            m_Buf[0+1] == PsDefine.Pakcet_start_code_prefix[1] &&
                            m_Buf[0+2] == PsDefine.Pakcet_start_code_prefix[2] &&
                            m_Buf[0+3] == PsDefine.Psm_map_stream_id)
                        {
                            Program_stream_map program_Stream_Map = new Program_stream_map();
                            if(!program_Stream_Map.ParsingProgram_stream_map(m_Buf.ToArray()))
                            {
                                return false;
                            }
                            if (m_Buf.Count > program_Stream_Map.Program_steam_map_lenth_16 + 6)
                            {
                                m_Buf.RemoveRange(0, program_Stream_Map.Program_steam_map_lenth_16 + 6);
                            }
                            else
                            {
                                return false;
                            }
                        }
                    }
    
                    packState = PackState.PES_pakcet;
                }
                //find PES_pakcet
                if (m_Buf.Count < 4)
                {
                    return false;
                }
                if(m_Buf[0] == PsDefine.Pakcet_start_code_prefix[0] &&
                    m_Buf[1]== PsDefine.Pakcet_start_code_prefix[1] &&
                    m_Buf[2] == PsDefine.Pakcet_start_code_prefix[2])
                {
                    PES_pakcet pES_Pakcet = new PES_pakcet();
                    if(!pES_Pakcet.ParsingPES_pakcet(m_Buf.ToArray()))
                    {
                        return false;
                    }
                    if(m_Buf.Count < pES_Pakcet.PES_pakcet_lenth_16+6)
                    {
                        return false;
                    }
                    byte[] bPes = new byte[pES_Pakcet.PES_pakcet_lenth_16 + 6];
                    m_Buf.CopyTo(0, bPes, 0, bPes.Length);
                    m_Buf.RemoveRange(0,bPes.Length);
                         
                    if(bPes[3] == PsDefine.Pes_map_stream_id_video)
                    {
                        if(pES_Pakcet.PES_pakcet_lenth_16>0)
                        {
                            m_h264File.Write(bPes, 9 + bPes[8], bPes.Length - 9 - bPes[8]);
                            m_h264File.Flush();
                        }
                    }
                    else if(bPes[3] == PsDefine.Pes_map_stream_id_audio)
                    {
    
                    }
                    return true;
                }
                else
                {
                    packState = PackState.Adding;
                    return false;
                }
    
    
            }

    为了便于大家学习和交流,用vs2017开发,C#语言实现的ps解析为H264资源如下,欢迎大家提问:

    ps解析出H264










    展开全文
  • GB28181学习之路——PS流解析H264

    千次阅读 2020-07-01 10:31:00
    磕磕绊绊的做了出来,也算为自己留个资料吧。先讲理论再上代码。...2. 找到ps包之后就要从它的格式入手开始解析ps荷载h264是把一帧帧的数据打包传过来,一个完整的ps包会包含一帧的数据。 而h264的帧分为 i 帧和

    磕磕绊绊的做了出来,也算为自己留个资料吧。先讲理论再上代码。挑些重点讲。

    1. 首先就是获取到 rtp 包,rtp包的结构是:rtp包头+payload,payload就是我们要的ps包,rtp包头的长度是12个字节,所以rtp包去掉前12字节就是ps包了。

    比如这个 rtp 包,跳过12个字节,从00 00 01 ba 开始就是ps包了。

    2. 找到ps包之后就要从它的格式入手开始解析,ps荷载h264是把一帧帧的数据打包传过来,一个完整的ps包会包含一帧的数据。

    而h264的帧分为 i 帧和 p 帧,i 帧的结构是 ps header + ps system header + ps system map + pes + h264 (+ pes + 音频)。

    p 帧的结构为 ps header + pes + h264 (+ pes + 音频)。

    3. 首先我们要找到第一个 i 帧,找到ps头 00 00 01 ba,ps头长度为14个字节,最后一个字节的后三位的数字标识扩展字节,

    这里最后一个字节是 fe ,所以跳过6个字节,到00 00 01 bb。

    4. 00 00 01 bb标识 ps system header,它后面的两个字节共同表示ps system header的长度,注意是表示这之后的数据长度,

    比如这里ps system header 表示长度的两个字节是 00 12,换算成十进制,就是后面还有18个字节,跳过这18个字节就到了 00 00 01 bc。

    5. 00 00 01 bc表示ps system map,它后面的两个字节代表ps system map 的长度。也是这之后的数据长度。

    比如这个长度是 00 5e,跳过这 94 个字节就到了 00 00 01 e0,

    6,00 00 01 e0就是我们要找的pes 数据包,它后面的两个字节表示pes包剩余数据的长度,跳过两个字节的下一个字节代码pes包的扩展长度。

    比如这里 00 22表示后面还有 34个字节,08表示有8个扩展字节,扩展字节之后就是h264的数据,h264数据的长度为pes的长度减去到扩展字节的长度再减去扩展字节。也就是34-3-8 = 23个字节。

    如图我们就取到了一个h264的数据了。看到后面又是00 00 01 e0开始的,所以又是一个视频帧。循环往复即可。

    下面上代码,这里我使用的是 jrtp,方便获取 rtp 包,测试过海康和大华的两款设备都是没问题的。

    代码已上传CSDN:https://download.csdn.net/download/qq_39805297/12566110

    MyRTPSession.h

    #ifndef MYRTPSESSION_H
    #define MYRTPSESSION_H
    
    #include <jrtplib3/rtpsession.h>
    #include <jrtplib3/rtppacket.h>
    #include <jrtplib3/rtpudpv4transmitter.h>
    #include <jrtplib3/rtpipv4address.h>
    #include <jrtplib3/rtpsessionparams.h>
    #include <jrtplib3/rtperrors.h>
    #include <jrtplib3/rtpsourcedata.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <iostream>
    #include <string>
    
    using namespace jrtplib;
    //
    // The new class routine
    //
    
    class MyRTPSession : public RTPSession
    {
    protected:
    	void OnNewSource(RTPSourceData *dat)
    	{
    		if (dat->IsOwnSSRC())
    			return;
    
    		uint32_t ip;
    		uint16_t port;
    
    		if (dat->GetRTPDataAddress() != 0)
    		{
    			const RTPIPv4Address *addr = (const RTPIPv4Address *)(dat->GetRTPDataAddress());
    			ip = addr->GetIP();
    			port = addr->GetPort();
    		}
    		else if (dat->GetRTCPDataAddress() != 0)
    		{
    			const RTPIPv4Address *addr = (const RTPIPv4Address *)(dat->GetRTCPDataAddress());
    			ip = addr->GetIP();
    			port = addr->GetPort() - 1;
    		}
    		else
    			return;
    
    		RTPIPv4Address dest(ip, port);
    		AddDestination(dest);
    
    		struct in_addr inaddr;
    		inaddr.s_addr = htonl(ip);
    		std::cout << "Adding destination " << std::string(inet_ntoa(inaddr)) << ":" << port << std::endl;
    	}
    
    	void OnBYEPacket(RTPSourceData *dat)
    	{
    		if (dat->IsOwnSSRC())
    			return;
    
    		uint32_t ip;
    		uint16_t port;
    
    		if (dat->GetRTPDataAddress() != 0)
    		{
    			const RTPIPv4Address *addr = (const RTPIPv4Address *)(dat->GetRTPDataAddress());
    			ip = addr->GetIP();
    			port = addr->GetPort();
    		}
    		else if (dat->GetRTCPDataAddress() != 0)
    		{
    			const RTPIPv4Address *addr = (const RTPIPv4Address *)(dat->GetRTCPDataAddress());
    			ip = addr->GetIP();
    			port = addr->GetPort() - 1;
    		}
    		else
    			return;
    
    		RTPIPv4Address dest(ip, port);
    		DeleteDestination(dest);
    
    		struct in_addr inaddr;
    		inaddr.s_addr = htonl(ip);
    		std::cout << "Deleting destination " << std::string(inet_ntoa(inaddr)) << ":" << port << std::endl;
    	}
    
    	void OnRemoveSource(RTPSourceData *dat)
    	{
    		if (dat->IsOwnSSRC())
    			return;
    		if (dat->ReceivedBYE())
    			return;
    
    		uint32_t ip;
    		uint16_t port;
    
    		if (dat->GetRTPDataAddress() != 0)
    		{
    			const RTPIPv4Address *addr = (const RTPIPv4Address *)(dat->GetRTPDataAddress());
    			ip = addr->GetIP();
    			port = addr->GetPort();
    		}
    		else if (dat->GetRTCPDataAddress() != 0)
    		{
    			const RTPIPv4Address *addr = (const RTPIPv4Address *)(dat->GetRTCPDataAddress());
    			ip = addr->GetIP();
    			port = addr->GetPort() - 1;
    		}
    		else
    			return;
    
    		RTPIPv4Address dest(ip, port);
    		DeleteDestination(dest);
    
    		struct in_addr inaddr;
    		inaddr.s_addr = htonl(ip);
    		std::cout << "Deleting destination " << std::string(inet_ntoa(inaddr)) << ":" << port << std::endl;
    	}
    };
    
    #endif //MYRTPSESSION_H

    main.cpp

    #include "myrtpsession.h"
    
    #define PS_BUFF_SIZE 4096000
    #define SAVE_PS_FILE 1
    #define SAVE_H264_FILE 1
    
    //
    // This function checks if there was a RTP error. If so, it displays an error
    // message and exists.
    //
    
    uint8_t *_frameBuff;
    int _frameSize;
    int _buffLen;
    FILE* fp_h264;
    
    void checkerror(int rtperr)
    {
    	if (rtperr < 0)
    	{
    		std::cout << "ERROR: " << RTPGetErrorString(rtperr) << std::endl;
    		exit(-1);
    	}
    }
    
    void writeH264Frame()
    {
    	printf("write frame size=%d\n", _buffLen);
    	if (_frameSize != _buffLen)
    		printf("error:frameSize=%d bufflen=%d\n", _frameSize, _buffLen);
    #if SAVE_H264_FILE
    	fwrite(_frameBuff, 1, _buffLen, fp_h264);
    #endif
    	memset(_frameBuff, 0, sizeof(_frameBuff));
    	_buffLen = 0;
    	_frameSize = 0;
    }
    
    void getH264Frame(uint8_t* payloadData, int payloadLength)
    {
    	int pos = 0;
    	memset(_frameBuff, 0, sizeof(_frameBuff));
    	_buffLen = 0;
    	 _frameSize = 0;
    	/******  统一 帧  ******/
    	while (payloadData[pos] == 0x00 && payloadData[pos+1] == 0x00 && 
            payloadData[pos+2] == 0x01 && payloadData[pos+3] == 0xe0)
    	{
    		uint16_t h264_size = payloadData[pos+4] << 8 | payloadData[pos+5];
    		uint8_t expand_size = payloadData[pos+8];
    		_frameSize = h264_size - 3 - expand_size;
    		pos += 9 + expand_size;
    		//全部写入并保存帧
    		if (_frameSize <= payloadLength - pos)
    		{
    			{
    				memcpy(_frameBuff, payloadData + pos, _frameSize);
    				_buffLen += _frameSize;
    				pos += _frameSize;
    				writeH264Frame();
    			}
    			if (pos >= payloadLength)
    				break;
    		}
    		else
    		{
    			memcpy(_frameBuff, payloadData + pos, payloadLength - pos);
    			_buffLen += payloadLength - pos;
    			printf("Frame size:%d\n", _frameSize);
    			break;
    		}
    	}
    }
    
    void getH264FromPacket(uint8_t* payloadData, int payloadLength)
    {
    	int pos = 0;
        uint8_t expand_size = payloadData[13] & 0x07;//扩展字节
        pos += 14 + expand_size;//ps包头14
        /******  i 帧  ******/
        if (payloadData[pos] == 0x00 && payloadData[pos + 1] == 0x00 && 
            payloadData[pos + 2] == 0x01 && payloadData[pos + 3] == 0xbb)
        {//ps system header
    	    uint16_t psh_size = payloadData[pos + 4] << 8 | payloadData[pos + 5];//psh长度
    	    pos += 6 + psh_size;
    	    if (payloadData[pos] == 0x00 && payloadData[pos + 1] == 0x00 && 
                payloadData[pos + 2] == 0x01 && payloadData[pos + 3] == 0xbc)
    	    {//ps system map
    		    uint16_t psm_size = payloadData[pos + 4] << 8 | payloadData[pos + 5];
    		    pos += 6 + psm_size;
    		}
    	    else
    	    {
    		    printf("no system map and no video stream\n");
    		    return;
    	    }
    	}
        getH264Frame(payloadData + pos, payloadLength - pos);
    }
    
    
    void executeProcess(int port, int secs)
    {
    #ifdef RTP_SOCKETTYPE_WINSOCK
    	WSADATA dat;
    	WSAStartup(MAKEWORD(2, 2), &dat);
    #endif // RTP_SOCKETTYPE_WINSOCK
    
    #if SAVE_PS_FILE
    	FILE* fp_ps = fopen("gb.ps", "w");
    #endif // SAVE_PS_FILE
    #if SAVE_H264_FILE
    	fp_h264 = fopen("gb.h264", "w");
    #endif // SAVE_H264_FILE
    
    	MyRTPSession sess;
    	std::string ipstr;
    	int status, j;
    
    	// Now, we'll create a RTP session, set the destination
    	// and poll for incoming data.
    
    	RTPUDPv4TransmissionParams transparams;
    	RTPSessionParams sessparams;
    
    	// IMPORTANT: The local timestamp unit MUST be set, otherwise
    	//            RTCP Sender Report info will be calculated wrong
    	// In this case, we'll be just use 8000 samples per second.
    	sessparams.SetOwnTimestampUnit(1.0 / 8000.0);
    
    	sessparams.SetAcceptOwnPackets(true);
    	transparams.SetPortbase(port);
    	status = sess.Create(sessparams, &transparams);
    	checkerror(status);
    	_frameBuff = new uint8_t[PS_BUFF_SIZE];
    	memset(_frameBuff, 0, sizeof(_frameBuff));
    	_frameSize = 0;
    	_buffLen = 0;
    
    	for (j = 1; j <= secs; j++)
    	{
    		sess.BeginDataAccess();
    		printf("secs gone %d\n", j);
    		// check incoming packets
    		if (sess.GotoFirstSourceWithData())
    		{
    			do
    			{
    				RTPPacket *pack;
    				while ((pack = sess.GetNextPacket()) != NULL)
    				{
    					printf("Got packet\n");
    					// You can examine the data here
    					if (pack->GetPayloadType() == 96)
    					{
    #if SAVE_PS_FILE
    						fwrite(pack->GetPayloadData(), 1, pack->GetPayloadLength(), fp_ps);
    #endif
    						//查找ps头 0x000001BA
    						if (pack->GetPayloadData()[0] == 0x00 && pack->GetPayloadData()[1] == 0x00 &&
    							pack->GetPayloadData()[2] == 0x01 && pack->GetPayloadData()[3] == 0xba)
    						{
    							getH264FromPacket(pack->GetPayloadData(), pack->GetPayloadLength());
    						}
                            else if (pack->GetPayloadData()[0] == 0x00 && pack->GetPayloadData()[1] == 0x00 &&
    							pack->GetPayloadData()[2] == 0x01 && pack->GetPayloadData()[3] == 0xe0)
                            {
                                getH264Frame(pack->GetPayloadData(), pack->GetPayloadLength());
                            }
    						else  //当然如果开头不是0x000001BA,默认为一个帧的中间部分,我们将这部分内存顺着帧的开头向后存储
    						{
    							//排除音频和私有数据
    							if (pack->GetPayloadData()[0] == 0x00 && pack->GetPayloadData()[1] == 0x00 &&
    								pack->GetPayloadData()[2] == 0x01 && pack->GetPayloadData()[3] == 0xc0)
    							{ 
    							}
    							else if (pack->GetPayloadData()[0] == 0x00 && pack->GetPayloadData()[1] == 0x00 &&
    								pack->GetPayloadData()[2] == 0x01 && pack->GetPayloadData()[3] == 0xbd)
    							{ 
    							}
    							else   //这是正常的帧数据,像贪吃蛇一样,将它放在帧开头的后边
    							{
    								if (pack->GetPayloadLength() + _buffLen >= _frameSize)
    								{
    									int len = _frameSize - _buffLen;
    									memcpy(_frameBuff + _buffLen, pack->GetPayloadData(), len);
    									_buffLen += len;
    									writeH264Frame();
    									if (pack->GetPayloadLength() > len)
    										getH264FromPacket(pack->GetPacketData() + len, pack->GetPacketLength() - len);
    								}
    								else
    								{
    									memcpy(_frameBuff + _buffLen, pack->GetPayloadData(), pack->GetPayloadLength());
    									_buffLen += pack->GetPayloadLength();
    								}
    							}
    						}
    					}
    					// we don't longer need the packet, so
    					// we'll delete it
    					sess.DeletePacket(pack);
    				}
    			} while (sess.GotoNextSourceWithData());
    		}
    
    		sess.EndDataAccess();
    
    #ifndef RTP_SUPPORT_THREAD
    		status = sess.Poll();
    		checkerror(status);
    #endif // RTP_SUPPORT_THREAD
    
    		RTPTime::Wait(RTPTime(1, 0));
    	}
    
    	sess.BYEDestroy(RTPTime(10, 0), 0, 0);
    
    #ifdef RTP_SOCKETTYPE_WINSOCK
    	WSACleanup();
    #endif // RTP_SOCKETTYPE_WINSOCK
    #if SAVE_PS_FILE
    	fclose(fp_ps);
    #endif
    #if SAVE_H264_FILE
    	fclose(fp_h264);
    #endif
    	printf("StreamReciever exits\n");
    }
    
    int main()
    {
        executeProcess(19444, 100);
        return 0;
    }

     

    展开全文
  • c# VS2017代码 demo 模拟ehome从海康ipc获取的中提取h264流。设备型号DS-2CD2T10D-I3
  • gb28181-ps解析h264.rar

    2020-07-01 10:35:53
    基于jrtp,gb28181视频流,从ps流解析提取h264保存成文件。
  • 自己编写的一个PS流文件解析库,用户输入一个PS流多媒体文件的路径,然后对PS流进行分析,解析出原始的视频信息并保存下来,可以使用elecard进行播放, 还能够根据帧号进行快速的定位,输出帧号的偏移量、大小以及...
  • H264解码之PS流解析

    千次阅读 2019-07-30 15:06:56
    PS头封装格式 PS流是对PES的进一步封装,是将具有共同时间基准的一个或多个PES包组合而成的单一的数据流...PS流有一个结束码MPEG_program_end_code:占位32bit,其值为0x000001B9,PS流总是以0x000001BA开始,以0x00...

    PS头封装格式

    PS流是对PES的进一步封装,是将具有共同时间基准的一个或多个PES包组合而成的单一的数据流;其基本单位是PS包,PS流由很多个PS包组成,PS包主要由固定包头,系统头,和PES包组成,其具体组成如下图所示:
    图一
    下图更为清晰:
    ps
    PS流有一个结束码MPEG_program_end_code:占位32bit,其值为0x000001B9,PS流总是以0x000001BA开始,以0x000001B9结束,对于一个PS文件,有且只有一个结束码0x000001B9,不过对于网传的PS流,则应该是没有结束码的;
    具体PS包头字段组成顺序如下:
    包头

    • pack_start_code字段:起始码,占位32bit,标识PS包的开始,固定为0x000001BA
    • ‘01’字段:占位2bit;
    • SCR字段:占位46bit,其中包含42bit的SCR值和4个bit的marker_bit值;其中SCR值由system_clock_reference_basesystem_clock_reference_extension两部分组成;字节顺序依次是:
    • system_clock_reference_base [32..30]:占位3bit;
    • marker_bit:占位1bit;
    • system_clock_reference_base [29..15]:占位15bit;
    • marker_bit:占位1bit;
    • system_clock_reference_base [14..0]:占位15bit;
    • marker_bit:占位1bit;
    • system_clock_reference_extension:占位9bit;
    • marker_bit:占位1bit;
    • program_mux_rate字段:速率值字段,占位22bit,正整数,表示P-STD接收此字段所在包的PS流的速率;这个值以每秒50字节作为单位;禁止0值;
    • Marker_bit:标记字段,占位1bit,固定为’1’;
    • Marker_bit:标记字段,占位1bit,固定为’1’;
    • Reserved字段:保留字段,占位5bit;
    • pack_stuffing_length字段:长度字段,占位3bit;规定了此字段之后填充字段的长度;
    • stuffing_byte:填充字段,固定为0xFF;长度由前一字段确定;
    • system_header字段:系统头部字段,只有当下一个字段的值为系统头部起始码0x000001BB时才存在,长度不定,其组成字段顺序如下所示:
      header
    • system_header_start_code字段:系统头部起始码,占位32bit,值固定为0x000001BB,标志系统首部的开始;
    • header_length字段:头部长度字段,占位16bit,表示此字段之后的系统首部字节长度;
    • Marker_bit字段:占位1bit,固定值为1;
    • rate_bound字段:整数值,占位22bit,为一个大于或等于PS流所有PS包中的最大program_mux_rate值的整数;可以被解码器用来判断是否可以对整个流进行解码;
    • Marker_bit字段:占位1bit,固定值为1;
    • audio_bound字段:占位6bit;取值范围0到32间整数;大于或等于同时进行解码处理的PS流中的音频流的最大数目;
    • fixed_flag字段:标志位,占位1bit;置位1表示固定比特率操作,置位0则为可变比特率操作;
    • CSPS_flag字段:CSPS标志位,占位1bit;置位1表示此PS流满足标准的限制;
    • system_audio_lock_flag字段:标志位,占位1bit,表示音频采样率和STD的system_clock_frequency之间有一特定常数比例关系;
    • system_video_lock_flag字段:标志位,占位1bit,表示在系统目标解码器system_clock_frequency和视频帧速率之间存在一特定常数比例关系;
    • Marker_bit字段:占位1bit,固定值为1;
    • video_bound字段:整数,占位5bit,取值范围0到16;大于或等于同时进行解码处理的PS流中的视频流的最大数目;
    • packet_rate_restriction_flag字段:分组速率限制标志字段,占位1bit,若CSPS_flag == 1,则此字段表示哪种限制适用于分组速率;若CSPS_flag == 0,则此字段无意义;
    • reserved_bits字段:保留字段,占位7bit,固定为’1111111’;
    • LOOP:当下一个bit为1时进入
      • stream_id字段:占位8bit,表示其后的P-STD_buffer_bound_scaleP-STD_buffer_size_bound字段所涉及的流的编码和基本流的号码;若stream_id ==’1011 1000’,则其后的P-STD_buffer_bound_scaleP-STD_buffer_size_bound字段对应PS流中的所有音频流;若stream_id ==’1011 1001’,则其后的P-STD_buffer_bound_scaleP-STD_buffer_size_bound字段对应PS流中的所有视频流;若取其他值,则应大于’1011 1100’,且按照标准对应Stream id(详见附录1);PS流中的每个原始流都应在每个系统首部中通过这种机制精确地规定一次它的P-STD_buffer_bound_scaleP-STD_buffer_size_bound
      • ‘11’字段:占位2bit;
      • P-STD_buffer_bound_scale字段:占位1bit,表示用来解释后面P-STD_buffer_size_bound字段的比例因子;如果之前的stream_id表示音频流,则此值应为0,若之前的stream_id表示视频流,则此值应为1,对于其他stream类型,此值可以0或1;
        + P-STD_buffer_size_bound字段:占位13bit,无符号整数;大于或等于所有PS流分组的P-STD输入缓冲区大小BSn的最大值;若P-STD_buffer_bound_scale == 0,则P-STD_buffer_size_bound以128字节为单位;若P-STD_buffer_bound_scale == 1,则P-STD_buffer_size_bound以1024字节为单位;
    • LOOP End
      目前的系统头部好像是没有用到的,所以对于系统头部的解析,我们一般只要先首先判断是否存在系统头(根据系统头的起始码0x000001BB),然后我们读取系统头的头部长度,即header_length部分,然后根据系统头部的长度,跳过PS系统头,进入下一个部分,即PS的payload,PES包;在固定包头和系统头之后,就是PS包的payload,即PES包;若PSM存在,则第一个PES包即为PSM。

    PSM格式介绍

    PSM提供了对PS流中的原始流和他们之间的相互关系的描述信息;PSM是作为一个PES分组出现,当stream_id == 0xBC时,说明此PES包是一个PSM;PSM是紧跟在系统头部后面的;PSM是作为PS包的payload存在的;
    PSM由很多字段组成,其字节顺序如下所示:
    psm

    • Packet start code prefix字段:包头起始码,固定为0x000001,占位24bit;与后面的字段map_stream_id一起组成分组开始码,标志着分组的开始;
    • map_stream_id字段:类型字段,标志此分组是什么类型,占位8bit;如果此值为0xBC,则说明此PES包为PSM;
    • program_stream_map_length字段:长度字段,占位16bit;表示此字段之后PSM的总长度,最大值为1018(0x3FA);
    • current_next_indicator字段:标识符,占位1bit;置位1表示当前PSM是可用的,置位0则表示当前PSM不可以,下一个可用;
    • Reserved:保留字段,占位2bit;
    • program_stream_map_version字段:版本字段,占位5bit;表示PSM的版本号,取值范围1到32,随着PSM定义的改变循环累加;若current_next_indicator == 1,表示当前PSM的版本号,若current_next_indicator == 0,表示下一个PSM的版本号;
    • Reserved:保留字段,占位7bit;
    • marker_bit:标记字段,占位1bit,固定为1;
    • program_stream_info_length字段:长度字段,占位16bit;表示此字段后面的descriptor字段的长度;
    • Descriptor字段:program Stream信息描述字段,长度由前个字段确定;
    • elementary_stream_map_length字段:长度字段,占位16bit;表示在这个PSM中所有ES流信息的总长度;包括stream_type, elementary_stream_id, elementary_stream_info_length的长度,即N*32bit;是不包括具体ES流描述信息descriptor的长度的;
    • LOOP
      • stream_type字段:类型字段,占位8bit;表示原始流ES的类型;这个类型只能标志包含在PES包中的ES流类型;值0x05是被禁止的;常见取值类型有MPEG-4 视频流:0x10;H.264 视频流:0x1B;G.711 音频流:0x90;因为PSM只有在关键帧打包的时候,才会存在,所以如果要判断PS打包的流编码类型,就根据这个字段来判断;
      • elementary_stream_id字段:流ID字段,占位8bit;表示此ES流所在PES分组包头中的stream_id字段的值;其中0x(C0DF)指音频,0x(E0EF)为视频;
      • elementary_stream_info_length字段:长度字段,占位16bit;表示此字段之后的,ES流描述信息的长度;
      • Descriptor:描述信息,长度由前个字段确定;表示此类型的ES流的描述信息,这个描述信息的长度是不包含在elementary_stream_map_length字段里面的;
    • LOOP End
    • CRC_32:CRC字段,占位32bit,CRC校验值;
      如果要解析原始流编码类型,则需要解析PSM,我们需要找到0x000001BC的位串,然后根据PSM的结构进行解析;如果不需要解析,则在找到PSM的开始码之后,找到此字段的长度字段,跳过此长度即可,直接解析后面的PES包;
      PS总结:
      解析PS包,要先找到PS包的的起始码0x000001BA位串,然后解析出系统头部字段,之后进入PS包的负载,判断是否有PSM,根据PSM确定payload的PES包中所负载的ES流类型;然后再根据ES流类型和ID从PES包中解析出具体的ES流;解包过程则相反;若要从PS流中找出来帧类型,必须将PS包解析成ES并组成完整的帧,然后在帧数据开始根据NAL头来进行帧的类型的判断;

    PSM只有在关键帧打包的时候,才会存在;IDR包含了SPS,PPS和I帧;每个IDR NALU前一般都会包含SPS、PPS等NALU,因此将SPS、PPS、IDR的NALU 封装为一个PS 包,包括PS头,PS system header,PSM,PES;所以一个IDR NALU PS 包由外到内顺序是:PS header| PS system header | PSM| PES。对于其它非关键帧的PS包,就简单多了,直接加上PS头和PES 头就可以了。顺序为:PS header | PES header | h264raw data。以上是对只有视频video 的情况,如果要把音频Audio也打包进PS 封装,只需将数据加上PES header 放到视频PES 后就可以了。顺序如下:PS 包=PS头|PES(video)|PES(audio);
    附录1:stream_id类型定义

    stream_idNotestream coding
    1011 11001program_stream_map
    1011 11012private_stream_1
    1011 1110padding_stream
    1011 11113private_stream_2
    110x xxxxISO/IEC 13818-3 or ISO/IEC 11172-3 audio
    1110 xxxxITU-T Rec. H.262†
    1111 00003ECM_stream
    1111 00013EMM_stream
    1111 00105ITU-T Rec. H.222.0†
    1111 00112ISO/IEC_13522_stream
    1111 01006ITU-T Rec. H.222.1 type A
    1111 01016ITU-T Rec. H.222.1 type B
    1111 01106ITU-T Rec. H.222.1 type C
    1111 01116ITU-T Rec. H.222.1 type D
    1111 10006ITU-T Rec. H.222.1 type E
    1111 10017ancillary_stream
    1111 1010 Ö 1111 1110reserved data stream
    1111 11114program_stream_directory
    展开全文
  • H264视频PS流解析获取pes,raw数据流的源码
  • 本人PS流解析H264的其余文章: 01PS流格式 02封装时遇到的重点问题 03海康PS转H264的编码思想(带图码流解释) 1 海康的PS流发包的顺序到底是如何发送的? 根据01PS流格式我们知道他的格式,由PS头,系统头,映射头...
  • ps解析H264

    2018-05-26 16:19:32
    把示例采用VS2017开发,C#语言实现PS流解析H264数据 可以参考博客:https://blog.csdn.net/g0415shenw/article/details/80385088
  • GB28181 PS流解析

    千次阅读 2021-10-24 17:05:06
    本文详细描述如何通过PS流解析H264码流。 先研究下PSM(节目流映射),PSM头定义如下: 这里找了一个标准的PS流里面的PSM数据进行研究分析: packet_start_code_prefix—24bit :00 00 01。 map_stream_id...
  • 从rtp包中提取ps流,并将ps流转化为h264码流c2021-1-2下载地址https://www.codedown123.com/56403.html从rtp包中提取ps流,并将ps流转化为h264码流资源下载此资源下载价格为2D币,请先登录资源文件列表PSToH264/...
  • ps流解析器,可以解析本地ps文件,支持把ps文件转化为es的视频和音频文件
  • PS流详解(载荷H264

    千次阅读 2021-10-07 13:27:46
    目录PS简介标准结构标准H264流结构定长音频帧和其他流式私有数据的结构PS流封装标准PSH结构PES包结构PSM包结构体元素流 PS 封装规则H264元素流封装规则音频元素流封装规则私有信息封装规则 PS简介 PS 封装方式需要...
  • PS视频中提取H264数据

    千次阅读 2019-11-29 17:55:33
    最近一线同事反映,视频解码后出现花屏现象。于是我让现场人员用wireshark抓一下包,发现服务器...我就纳闷了,本着上次被海康平台坑过一回的阴影,还是自己写一个从PS文件里面提取H264裸码数据的小工具为好。 ...
  • 视频流PSPS封装H264

    2021-04-17 05:15:54
    出处: ISOIEC 13818-1PS流PS流由PSGOP组成,每个PSGOP是由I帧起始的多帧集合,每个GOP之间没有相互依赖信息,可以剪切拼接。| PSGOP0 | PSGOP1 | PSGOP2 | PSGOP3 | PSGOP4 | ...... |PSGOP:PSGOP由一个或多个PS...
  • 写在前面:RTP的解析,网上找了很多资料,但是都不全,所以我力图整理出一个比较全面的解析, 其中借鉴了很多文章,我都列在了文章最后,在此表示感谢。 互联网的发展离不开大家的无私奉献,我决定从我做起,希望...
  • h265编码的ps文件,可学习ps文件格式或测试gb28181用。
  • GB28181 ps流解析h264

    千次阅读 2019-03-24 10:58:21
    PS流格式可以自行网上搜索很多资料,参考网址:https://blog.csdn.net/u012519333/article/details/53208767 #ifndef __ACTIVE_PS_PARSER_H_ #define __ACTIVE_PS_PARSER_H_ typedef void APSPARSER_H; //ACTIVE...
  • 思路是通过设备网络sdk以回调取的形式获取到视频,确实获取视频了在后期的处理是,如psh264,检测不到包头,一致转换不了,具体不知道是种子获取的视频有问题,还是取过程有问题,还是视频有问题,...
  • H264和音频流打包成PS流 (MPEG2-PS)

    万次阅读 2018-06-19 19:11:47
    技术在于交流、沟通,转载请注明出处并保持作品的完整性。...H264和音频流打包成PS流 (MPEG2-PS) PS流解复用成H264和音频流(ES提取) H264和音频流打包成TS流 (MPEG2-TS) TS流解复用成H264和音频流(E...
  • h264 ps流vlc播放

    千次阅读 2019-09-29 10:46:46
    ps流打包中I帧和P帧打包略有差异:I帧:ps头+ps系统头+ps map+PES头+H264 raw data,P帧:pes头+H264 raw data (P帧也可以加上ps头+ps map不影响播放)。可以用VLC播放器转码MP4文件(vlc功能真的很强大...
  • PS流解复用成H264和音频流(ES提取)

    千次阅读 2018-06-21 14:07:53
    技术在于交流、沟通,转载请注明出处并保持作品的完整性。...H264和音频流打包成PS流 (MPEG2-PS) PS流解复用成H264和音频流(ES提取) H264和音频流打包成TS流 (MPEG2-TS) TS流解复用成H264和音频流(E...
  • 海康PSH264的编码思想(带图码解释)

    千次阅读 热门讨论 2020-10-11 22:54:21
    海康PS转H264的编码思想(带图码流解释) 01PS流格式 02封装时遇到的重点问题 1 海康的PS流发包的顺序到底是如何发送的? 根据01PS流格式我们知道他的格式,由PS头,系统头,映射头,PES包(包括包头和视频或者音频数据...
  • RTP打包与解析,荷载类型包括PSH264

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 18,402
精华内容 7,360
关键字:

ps流解析h264