GB28181学习之路——PS流解析H264

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

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

GB28181学习之路——PS流解析H264_第1张图片

比如这个 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。

GB28181学习之路——PS流解析H264_第2张图片

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

GB28181学习之路——PS流解析H264_第3张图片

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

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

GB28181学习之路——PS流解析H264_第4张图片

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

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

GB28181学习之路——PS流解析H264_第5张图片

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

GB28181学习之路——PS流解析H264_第6张图片

如图我们就取到了一个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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

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;
}

 

你可能感兴趣的:(gb28181,ps,h264)