GB28181學習之路——PS流解析H264

磕磕絆絆的做了出來,也算爲自己留個資料吧。先講理論再上代碼。挑些重點講。

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章