gb28181 ps流文件解析

本文介绍了在处理RTP包时,UDP与TCP的不同之处。主要关注在UDP基础上接收RTP包,通过分析PS包头获取ES流,并解析H264数据。代码示例展示了如何解析RTP包,包括处理PS流、RTP头、NAL单元以及关键帧的识别。同时,提到了TCP中可能需要额外的代码来实现类似功能,但会牺牲一定的灵活性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基础

假定是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, 0x00, 0x00, 0x00, 0x00, 0xFC };
		int audioSamprate = 32000;//音频采样率
		int audioChannel = 2;//音频声道 1或2
		int audioBit = 16;//16位 固定
		switch (audioSamprate)
		{
		case  16000:
			ADTS[2] = 0x60;
			break;
		case  32000:
			ADTS[2] = 0x54;
			break;
		case  44100:
			ADTS[2] = 0x50;
			break;
		case  48000:
			ADTS[2] = 0x4C;
			break;
		case  96000:
			ADTS[2] = 0x40;
			break;
		default:
			break;
		}
		ADTS[3] = (audioChannel == 2) ? 0x80 : 0x40;

		int len = recvLen - 16 + 7;
		len <<= 5;//8bit * 2 - 11 = 5(headerSize 11bit)
		len |= 0x1F;//5 bit    1            
		ADTS[4] = len >> 8;
		ADTS[5] = len & 0xFF;
		*pBufOut = (char*)bufIn + 16 - 7;
		memcpy(*pBufOut, ADTS, sizeof(ADTS));
		*pOutLen = recvLen - 16 + 7;

		unsigned char* bufTmp = (unsigned char*)bufIn;
		bool bFinishFrame = false;
		if (bufTmp[1] & 0x80)
		{
			//DebugTrace::D("Marker");
			bFinishFrame = true;
		}
		else
		{
			bFinishFrame = false;
		}
		return true;
	}


int Service_RTP_Analyse::insert_rtp(uint32_t ip, uint16_t port, void * pkt_)
{
    RTPPacket * pkt = (RTPPacket*)pkt_;
	uint8_t *buf = pkt->GetPayloadData();
	size_t len   = pkt->GetPayloadLength();
	uint8_t * out_head = NULL;
	int       out_len = 0;
	uint64_t key = ((uint64_t)ip << 32) | port;
	//std::string key = itoa()
	uint32_t rssrc  = pkt->GetSSRC();
	uint32_t rstamp = pkt->GetTimestamp();
	

	auto iter = _hash.find(key);
	if (iter == _hash.end())
	{
		rtp_context s;
		ALLOC_MEM(s);
		_hash.insert(pair<uint64_t, rtp_context>(key,s));
		s.start_timestamp = rstamp;

		decode_rtp_h264(buf, len,(void**)&out_head, &out_len);
		if (s.blen - s.pos > out_len)
		{
			memcpy(s.buffer + s.pos, out_head, out_len);
			s.pos += out_len;
		}

	}
	else
	{
		decode_rtp_h264(buf, len, (void**)&out_head, &out_len);
		rtp_context &s = iter->second;
		if (s.blen - s.pos > out_len)
		{
			memcpy(s.buffer + s.pos, out_head, out_len);
			s.pos += out_len;
		}
	}
	return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qianbo_insist

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值