gb28181 ps流文件解析

Posted qianbo_insist

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了gb28181 ps流文件解析相关的知识,希望对你有一定的参考价值。

基础

假定是udp上接收包,端口 5060 ,如果是tcp是要加一些额外得代码,
可以使用jrtplib,或者自己写udpserver 放出udp socket 接收包,当然使用jrtplib等稍微简单一些。不过也一样失去了一些灵活性。

步骤

1 接收udp over rtp包
2 接收后分析ps包头,获取es流
3 如果不是ps则接收到包后根据rtp协议再解析rtp, 获取分片等信息组合成包
4 分析es流

code

#define FILE_STORAGE 0

Service_RTP_Analyse::Service_RTP_Analyse()




Service_RTP_Analyse::~Service_RTP_Analyse()


int Service_RTP_Analyse::analyse_rtp(uint8_t * rtp, size_t len)

	return 0;


//注意这里只是适应h264
int Service_RTP_Analyse::analyse_h264es(uint8_t * pos, int len, uint8_t ** nalu, size_t &nalulen)

	uint8_t* p = pos + 4 + 2 + 2; //跳过4字节00 00 01 E0 和两个字节长度 以及两个字节的跳空
	int skip = *p;
	p = p + skip + 1; //p 指向 00 00 00 01 67 等字节上
	*nalu = p;
	while (!*(p++));
	uint8_t c1 = (uint8_t)(*p);
	unsigned char nal_type = c1 & 0x1f;
	nalulen = len - (int)(*nalu - pos); //因为 00 00 01 E0 以及两个代表长度的字节没有计算
	return nal_type;


int Service_RTP_Analyse::analyseps0(uint8_t * ps, size_t len, int &type, int &length, uint8_t **next, size_t &nextlen)


#define D(y,x) (*(y+x))
	if (D(ps, 0) == 0x00 && D(ps, 1) == 0x00 && D(ps, 2) == 0x01)
	
		uint8_t num = D(ps, 3);
		switch (num)
		
		case 0xBA:
		
			int skip1 = D(ps, 13) & 0x07;
			uint8_t * nps = ps + 14 + skip1;
			length = 14;
			type = PS_PS;
			*next = nps;
			nextlen = len - (nps - ps);
		
		return 0;
		case 0xBB://系统标题起始码字段
		case 0xBC://映射流标识字段 
		
			char temp[2];
			temp[0] = D(ps, 5);
			temp[1] = D(ps, 4);
			length = *(unsigned short*)(&temp[0]) + 6; //00 00 01 xx 以及两个字节的长度
			type = PS_SYS;
			*next = ps + length;
			nextlen = len - (int)(*next - ps);
		
		return 0;
		case 0xE0:
		
			char temp[2];
			temp[0] = D(ps, 5);
			temp[1] = D(ps, 4);
			length = *(unsigned short*)(&temp[0]) + 6;//00 00 01 xx 以及两个字节的长度
			type = PS_VIDEO;
			*next = ps + length;
			nextlen = len -(int)(*next - ps);
		
		return 0;
		case 0xBD:
		case 0xC0:
		
			char temp[2];
			temp[0] = D(ps, 5);
			temp[1] = D(ps, 4);
			length = *(unsigned short*)(&temp[0]) + 6;//00 00 01 xx 以及两个字节的长度
			if (num == 0xBD)
				type = PS_PRIVATE;
			else
				type = PS_AUDIO;
			*next = ps + length;
			nextlen = len - (int)(*next - ps);
		
		return 0;
		default:
		
			length = 0;
			type = PS_UNKONWN;
		
		return -1;
		
	
	else
	
		length = 0;
		type = PS_UNKONWN;
		return -1;
	




/*
//该函数解一帧完整的ps,输入时一帧完整的ps帧
//rc 上下文环境
//pos ps流数据指针
//len 长度
*/
int Service_RTP_Analyse::analyseps(rtp_context *prc, uint8_t * pos, size_t len)

	int type = 0;
	int length = 0; //当前段长度

	uint8_t * ps = pos;
	size_t pslen = len;
	uint8_t * nextpos = NULL;
	size_t nextlen = 0;
	uint8_t * sps = NULL;
	int spslen = 0;
	uint8_t * pps = NULL;
	int ppslen = 0;
	uint8_t * se = NULL;
	int selen = 0;
	uint8_t *nalu = NULL;
	size_t nalulen = 0;
	while (1)
	
		int ret = analyseps0(ps, pslen, type, length, &nextpos, nextlen);
		if (ret == -1)
			return -1;
		switch (type) //处理当前段
		
		case PS_PS:
			ps = nextpos;
			pslen = nextlen;
			break;
		case PS_SYS:
			ps = nextpos;
			pslen = nextlen;
			break;
		case PS_VIDEO:
		
			int type = analyse_h264es(ps, length, &nalu, nalulen);
			switch (type)
			
			case 0x07:
				sps = nalu;
				spslen = (int)nalulen;
				break;
			case 0x08:
				pps = nalu;
				ppslen = (int)nalulen;
				break;
			case 0x06: //skip
				se = nalu;
				selen = (int)nalulen;
				break;
			case 0x05:
			case 0x01://skip,because except sps pps ,only one 0x05 key frame nalu 

				break;
			default:
				break;
			
			ps = nextpos;
			pslen = nextlen;
		
		break;
		case PS_PRIVATE:
			ps = nextpos;
			pslen = nextlen;
			break;
		case PS_AUDIO:
			ps = nextpos;
			pslen = nextlen;
			break;
		
		if (nextlen <= 0)
			break;

	
	//顺序找到了流以后,开始移动
	//有关键帧,则移动sps和pps
#if FILE_STORAGE
	if (ar->fp == NULL)
		ar->fp = fopen("h264save.264", "wb");

	if (ar->fpps == NULL)
		ar->fpps = fopen("h264saveps.264", "wb");
	fwrite(pos, 1, len, ar->fpps);
#endif
	if (sps != NULL && pps != NULL)
	
		//获取完整的一帧
		//移动少量数据,达到意义上的零拷贝
		memmove(nalu - ppslen, pps, ppslen);
		memmove(nalu - ppslen - spslen, sps, spslen);
		sps = nalu - ppslen - spslen;
		pps = nalu - ppslen;
		if (prc->_callback != NULL)
		
			prc->_callback(prc->ip,
				prc->port,
				sps,
				ppslen + spslen + nalulen,
				KEY_FRAME,
				sps,
				spslen + ppslen,
				prc->pts++);
		
#if FILE_STORAGE
		fwrite(sps, 1, spslen + ppslen + nalulen, ar->fp);
#endif
	
	else //非关键帧
	
		if (prc->_callback != NULL)
			prc->_callback(prc->ip, prc->port, nalu, nalulen, NOT_KEY_FRAME,NULL,0, prc->pts++);

#if FILE_STORAGE
		fwrite(nalu, 1, nalulen, ar->fp);
#endif
	

	return 0;

//ar 因为零拷贝的事情buffer可能没有值



int Service_RTP_Analyse::insert(uint32_t ip, uint16_t port, void *pkt0)

	RTPPacket * pkt = (RTPPacket*)pkt0;
	//if (pkt->GetPayloadLength() > BUF_LENGTH) //这里还是要修改 钱波
	//	return -1;

	auto iter = _hash.find(ip);
	if (iter == _hash.end()) //没有找到该IP地址的存储
	
		OutputDebugString(L"not found");
		uint8_t * ps = pkt->GetPayloadData();
		//分析ps流
		if (*ps == 0x00 && *(ps + 1) == 0x00 && *(ps + 2) == 0x01 && *(ps + 3) == 0xBA)
		
			analyse_struct as;
			as.in = pkt->GetPayloadData();
			as.inlen = pkt->GetPayloadLength();
			AnalyseH264Frame(&as);
			if (as.ret == 0) //非关键帧
			
				OutputDebugString(L"第一RTP包非关键帧头部,丢弃\\n");
				return -1;
			
			rtp_context rc;
			rc.ip = ip;
			rc.port = port;
			rc._callback = _callback;
			if (pkt->HasMarker())
			
				rc.blen = 0;
				analyseps(&rc, pkt->GetPayloadData(), pkt->GetPayloadLength());
				//send_func(&rc, ip, port, pkt->GetPayloadData(), pkt->GetPayloadLength(), 0);
			
			else
			
				ALLOC_MEM(rc);
				rc.blen= pkt->GetPayloadLength();
				memcpy(rc.buffer, pkt->GetPayloadData(), rc.blen);
			
			
			//fixme:qianbo
			//_hash.emplace(pair<uint32_t, rtp_context>(ip, as0));
			_hash.insert(pair<uint32_t, rtp_context>(ip, rc));
		
		else //分析普通rtp流
		

		
	
	else //找到存储
	
		//代码优化零拷贝
		rtp_context &rc = iter->second;
		size_t &len = rc.blen;

		if (pkt->HasMarker()) // 回调启用
		
			if (len == 0) 
				//执行零拷贝操作
				analyseps(&rc, pkt->GetPayloadData(), pkt->GetPayloadLength());
				//send_func(&rc, ip, port, pkt->GetPayloadData(), pkt->GetPayloadLength(), 0);
			
			else // len!=0
			
				uint8_t * buf = iter->second.buffer;
				memcpy(buf + len, pkt->GetPayloadData(), pkt->GetPayloadLength());
				len += pkt->GetPayloadLength();
				analyseps(&rc, buf, len);
				//send_func(&rc, ip, port, buf, len, 0);
			
			len = 0;
		
		else
		
			ALLOC_MEM(rc);
			uint8_t * buf = iter->second.buffer;
			memcpy(buf + len, pkt->GetPayloadData(), pkt->GetPayloadLength());
			len += pkt->GetPayloadLength();
		
	
	return 0;




	// 功能:解码RTP H.264视频
	// 参数:1.RTP包缓冲地址 2.RTP包数据大小 3.H264输出地址 4.输出数据大小
	// 返回:true:表示一帧结束  false:FU-A分片未结束或帧未结束
	bool  Service_RTP_Analyse::decode_rtp_h264(void *bufIn, int len, void **pBufOut, int *pOutLen)
	
		*pOutLen = 0;
		if (len  <  RTP_HEADLEN)
		
			return   false;
		

		uint8_t *src = (uint8_t*)bufIn + RTP_HEADLEN;
		//第1字节
		uint8_t  head_1 = *src;
		//第2字节
		uint8_t  head_2 = *(src + 1);
		//FU indicator的类型域,
		uint8_t  nal = head_1 & 0x1f;
		//FU header的前三位,当前是分包的1开始 2中间 3结束
		uint8_t  flag = head_2 & 0xe0;
		uint8_t  nal_fua = (head_1 & 0xe0) | (head_2 & 0x1f); // FU_A nal
		bool  bFinishFrame = false;
		if (nal == 0x1c) // 判断NAL的类型为0x1c=28,说明是FU-A分片
		 // fu-a
			if (flag == 0x80) // 开始
			
				*pBufOut = src - 3;
				*((int *)(*pBufOut)) = 0x01000000; // 
				*((char *)(*pBufOut) + 4) = nal_fua;
				*pOutLen = len - RTP_HEADLEN + 3;
			
			else if (flag == 0x40) // 结束
			
				*pBufOut = src + 2;
				*pOutLen = len - RTP_HEADLEN - 2;
			
			else // 中间
			
				*pBufOut = src + 2;
				*pOutLen = len - RTP_HEADLEN - 2;
			
		
		else // 单包数据
		
			*pBufOut = src - 4;
			*((int *)(*pBufOut)) = 0x01000000; // 大模式会有问题
			*pOutLen = len - RTP_HEADLEN + 4;
		

		uint8_t *  tmp = (unsigned  char*)bufIn;
		bFinishFrame = (tmp[1] & 0x80) ? true : false;
		return  bFinishFrame;
	


	//功能:解RTP AAC音频包,声道和采样频率必须知道。
	//参数:1.RTP包缓冲地址 2.RTP包数据大小 3.H264输出地址 4.输出数据大小
	//返回:true:表示一帧结束  false:帧未结束 一般AAC音频包比较小,没有分片。
	bool Service_RTP_Analyse::decode_rtp_aac(void * bufIn, int recvLen, void** pBufOut, int* pOutLen)
	
		unsigned char*  bufRecv = (unsigned char*)bufIn;
		//char strFileName[20];

		unsigned char ADTS[] =  0xFF, 0xF1

以上是关于gb28181 ps流文件解析的主要内容,如果未能解决你的问题,请参考以下文章

gb28181提高效率一种通用解析ps流得方法以及零拷贝

GB28181设备端PS流封装和发送

使用ffmpeg来探测GB28181的ps流

GB28181 PS流传输格式详解

GB28181 ps流高速数据判断帧类型以及安全加密转发

PS流格式解析