关于对H264码流的PS的封装的相关代码实现

1、写在开始之前:

最近因为新工作要维护别人留下的GB模块代码,先熟悉了流程,然后也试着封装了下ps流,结果也能通过测试正常预览了,当然,其中开发读文档的头疼,预览花屏,卡帧的事情都有遇到,当时慢慢的看文档,整理逻辑,也就都顺利解决了,下面把大致的一些流程代码贴出来分享下。既然是对接国标,自然少不了通读它的标准文档和相关的RFC文档了!具体的我就不说了,可以用百度google下的。

注意:因为是GB要求ps封装后再加上rtp头的格式来的, 所以下面代码中我也加上了rtp头,如果不需要的话,直接屏蔽代码中的rtp即可。

2、封装的重点

当我们从读缓冲区中取得一帧音视频数据的时候,封装时其实每一帧数据有且只有一个ps头和psm头,如果是I帧的话,就还多一个system头,一个或者多个pes头和rtp头,

像如果帧数据过长的话,就得进行分片,每片都会包含一个pes头,rtp负载最好长度1460,所以会进行再分包操作!所以每一个包数据至少一个rtp+databuf,每一片数据,至少有个rtp+pes+databuf,每一帧数据至少有rtp+ps+psm+pes+databuf(关键帧的话:多一个system头)

3、具体的各个封装的代码实现

首先给去一个整体的封装rtp->ps->sys->psm->pes(如果只要ps的话,则为ps->sys->psm->pes)的大致流程,

然后再一一罗列出各个部件的封装接口

/***
 *@remark:  音视频数据的打包成ps流,并封装成rtp
 *@param :  pData      [in] 需要发送的音视频数据
 *          nFrameLen  [in] 发送数据的长度
 *          pPacker    [in] 数据包的一些信息,包括时间戳,rtp数据buff,发送的socket相关信息
 *          stream_type[in] 数据类型 0 视频 1 音频
 *@return:  0 success others failed
*/

int gb28181_streampackageForH264(char *pData, int nFrameLen, Data_Info_s* pPacker, int stream_type)
{
	char szTempPacketHead[256];
	int  nSizePos = 0;
	int  nSize = 0;
	char *pBuff = NULL;
	memset(szTempPacketHead, 0, 256);
	// 1 package for ps header
	gb28181_make_ps_header(szTempPacketHead + nSizePos, pPacker->s64CurPts);
	nSizePos += PS_HDR_LEN;
	//2 system header
	if( pPacker->IFrame == 1 )
	{
        // 如果是I帧的话,则添加系统头
		gb28181_make_sys_header(szTempPacketHead + nSizePos);
		nSizePos += SYS_HDR_LEN;
        //这个地方我是不管是I帧还是p帧都加上了map的,貌似只是I帧加也没有问题
//		gb28181_make_psm_header(szTempPacketHead + nSizePos);
//		nSizePos += PSM_HDR_LEN;

	}
    // psm头 (也是map)
	gb28181_make_psm_header(szTempPacketHead + nSizePos);
    nSizePos += PSM_HDR_LEN;

    //加上rtp发送出去,这样的话,后面的数据就只要分片分包就只有加上pes头和rtp头了
	if(gb28181_send_rtp_pack(szTempPacketHead, nSizePos, 0, pPacker) != 0 )
		return -1;	

    // 这里向后移动是为了方便拷贝pes头
    //这里是为了减少后面音视频裸数据的大量拷贝浪费空间,所以这里就向后移动,在实际处理的时候,要注意地址是否越界以及覆盖等问题
	pBuff = pData - PES_HDR_LEN;
	while(nFrameLen > 0)
	{
        //每次帧的长度不要超过short类型,过了就得分片进循环行发送
		nSize = (nFrameLen > PS_PES_PAYLOAD_SIZE) ? PS_PES_PAYLOAD_SIZE : nFrameLen;
        // 添加pes头
		gb28181_make_pes_header(pBuff, stream_type ? 0xC0:0xE0, nSize, (pPacker->s64CurPts / 100), (pPacker->s64CurPts/300));

        //最后在添加rtp头并发送数据
		if( gb28181_send_rtp_pack(pBuff, nSize + PES_HDR_LEN, ((nSize == nFrameLen)?1:0), pPacker) != 0 )
		{
			printf("gb28181_send_pack failed!\n");
			return -1;
		}
        //分片后每次发送的数据移动指针操作
		nFrameLen -= nSize;
        //这里也只移动nSize,因为在while向后移动的pes头长度,正好重新填充pes头数据
        pBuff     += nSize;

	}
	return 0;
}

上面列出来了整个打包发包的过程,接下来一个一个接口的看

/***
 *@remark:   ps头的封装,里面的具体数据的填写已经占位,可以参考标准
 *@param :   pData  [in] 填充ps头数据的地址
 *           s64Src [in] 时间戳
 *@return:   0 success, others failed
*/
int gb28181_make_ps_header(char *pData, unsigned long long s64Scr)
{
	unsigned long long lScrExt = (s64Scr) % 100;
    s64Scr = s64Scr / 100;
    // 这里除以100是由于sdp协议返回的video的频率是90000,帧率是25帧/s,所以每次递增的量是3600,
    // 所以实际你应该根据你自己编码里的时间戳来处理以保证时间戳的增量为3600即可,
    //如果这里不对的话,就可能导致卡顿现象了
	bits_buffer_s  	bitsBuffer;
	bitsBuffer.i_size = PS_HDR_LEN;
	bitsBuffer.i_data = 0;
    bitsBuffer.i_mask = 0x80; // 二进制:10000000 这里是为了后面对一个字节的每一位进行操作,避免大小端夸字节字序错乱
	bitsBuffer.p_data =	(unsigned char *)(pData);
	memset(bitsBuffer.p_data, 0, PS_HDR_LEN);
    bits_write(&bitsBuffer, 32, 0x000001BA);			/*start codes*/
	bits_write(&bitsBuffer, 2, 	1);						/*marker bits '01b'*/
    bits_write(&bitsBuffer, 3, 	(s64Scr>>30)&0x07);     /*System clock [32..30]*/
	bits_write(&bitsBuffer, 1, 	1);						/*marker bit*/
    bits_write(&bitsBuffer, 15, (s64Scr>>15)&0x7FFF);   /*System clock [29..15]*/
	bits_write(&bitsBuffer, 1, 	1);						/*marker bit*/
    bits_write(&bitsBuffer, 15, s64Scr&0x7fff);         /*System clock [29..15]*/
	bits_write(&bitsBuffer, 1, 	1);						/*marker bit*/
	bits_write(&bitsBuffer, 9, 	lScrExt&0x01ff);		/*System clock [14..0]*/
	bits_write(&bitsBuffer, 1, 	1);						/*marker bit*/
	bits_write(&bitsBuffer, 22, (255)&0x3fffff);		/*bit rate(n units of 50 bytes per second.)*/
	bits_write(&bitsBuffer, 2, 	3);						/*marker bits '11'*/
	bits_write(&bitsBuffer, 5, 	0x1f);					/*reserved(reserved for future use)*/
	bits_write(&bitsBuffer, 3, 	0);						/*stuffing length*/
	return 0;
}
/***
 *@remark:   sys头的封装,里面的具体数据的填写已经占位,可以参考标准
 *@param :   pData  [in] 填充ps头数据的地址
 *@return:   0 success, others failed
*/
int gb28181_make_sys_header(char *pData)
{

	bits_buffer_s  	bitsBuffer;
	bitsBuffer.i_size = SYS_HDR_LEN;
	bitsBuffer.i_data = 0;
	bitsBuffer.i_mask = 0x80;
	bitsBuffer.p_data =	(unsigned char *)(pData);
	memset(bitsBuffer.p_data, 0, SYS_HDR_LEN);
	/*system header*/
	bits_write( &bitsBuffer, 32, 0x000001BB);	/*start code*/
    bits_write( &bitsBuffer, 16, SYS_HDR_LEN-6);/*header_length 表示次字节后面的长度,后面的相关头也是次意思*/
    bits_write( &bitsBuffer, 1,	 1);            /*marker_bit*/
	bits_write( &bitsBuffer, 22, 50000);		/*rate_bound*/
    bits_write( &bitsBuffer, 1,  1);            /*marker_bit*/
    bits_write( &bitsBuffer, 6,  1);            /*audio_bound*/
    bits_write( &bitsBuffer, 1,  0);            /*fixed_flag */
    bits_write( &bitsBuffer, 1,  1);        	/*CSPS_flag */
    bits_write( &bitsBuffer, 1,  1);        	/*system_audio_lock_flag*/
    bits_write( &bitsBuffer, 1,  1);        	/*system_video_lock_flag*/
    bits_write( &bitsBuffer, 1,  1);        	/*marker_bit*/
    bits_write( &bitsBuffer, 5,  1);        	/*video_bound*/
    bits_write( &bitsBuffer, 1,  0);        	/*dif from mpeg1*/
    bits_write( &bitsBuffer, 7,  0x7F);     	/*reserver*/
	/*audio stream bound*/
    bits_write( &bitsBuffer, 8,  0xC0);         /*stream_id*/
    bits_write( &bitsBuffer, 2,  3);        	/*marker_bit */
    bits_write( &bitsBuffer, 1,  0);            /*PSTD_buffer_bound_scale*/
    bits_write( &bitsBuffer, 13, 512);          /*PSTD_buffer_size_bound*/
	/*video stream bound*/
    bits_write( &bitsBuffer, 8,  0xE0);         /*stream_id*/
    bits_write( &bitsBuffer, 2,  3);        	/*marker_bit */
    bits_write( &bitsBuffer, 1,  1);        	/*PSTD_buffer_bound_scale*/
    bits_write( &bitsBuffer, 13, 2048);     	/*PSTD_buffer_size_bound*/
	return 0;
}
/***
 *@remark:   psm头的封装,里面的具体数据的填写已经占位,可以参考标准
 *@param :   pData  [in] 填充ps头数据的地址
 *@return:   0 success, others failed
*/
int gb28181_make_psm_header(char *pData)
{

	bits_buffer_s  	bitsBuffer;
	bitsBuffer.i_size = PSM_HDR_LEN;
	bitsBuffer.i_data = 0;
	bitsBuffer.i_mask = 0x80;
	bitsBuffer.p_data =	(unsigned char *)(pData);
	memset(bitsBuffer.p_data, 0, PS_SYS_MAP_SIZE);
	bits_write(&bitsBuffer, 24,0x000001);	/*start code*/
	bits_write(&bitsBuffer, 8, 0xBC);		/*map stream id*/
	bits_write(&bitsBuffer, 16,18);			/*program stream map length*/
	bits_write(&bitsBuffer, 1, 1);			/*current next indicator */
	bits_write(&bitsBuffer, 2, 3);			/*reserved*/
	bits_write(&bitsBuffer, 5, 0); 			/*program stream map version*/
    bits_write(&bitsBuffer, 7, 0x7F);		/*reserved */
	bits_write(&bitsBuffer, 1, 1);			/*marker bit */
	bits_write(&bitsBuffer, 16,0); 			/*programe stream info length*/
	bits_write(&bitsBuffer, 16, 8); 		/*elementary stream map length	is*/
	/*audio*/
    bits_write(&bitsBuffer, 8, 0x90);       /*stream_type*/
    bits_write(&bitsBuffer, 8, 0xC0);		/*elementary_stream_id*/
	bits_write(&bitsBuffer, 16, 0); 		/*elementary_stream_info_length is*/
	/*video*/
    bits_write(&bitsBuffer, 8, 0x1B);       /*stream_type*/
    bits_write(&bitsBuffer, 8, 0xE0);		/*elementary_stream_id*/
	bits_write(&bitsBuffer, 16, 0); 		/*elementary_stream_info_length */
	/*crc (2e b9 0f 3d)*/
	bits_write(&bitsBuffer, 8, 0x45);		/*crc (24~31) bits*/
	bits_write(&bitsBuffer, 8, 0xBD);		/*crc (16~23) bits*/
	bits_write(&bitsBuffer, 8, 0xDC);		/*crc (8~15) bits*/
	bits_write(&bitsBuffer, 8, 0xF4);		/*crc (0~7) bits*/
	return 0;
}
/***
 *@remark:   pes头的封装,里面的具体数据的填写已经占位,可以参考标准
 *@param :   pData      [in] 填充ps头数据的地址
 *           stream_id  [in] 码流类型
 *           paylaod_len[in] 负载长度
 *           pts        [in] 时间戳
 *           dts        [in]
 *@return:   0 success, others failed
*/
int gb28181_make_pes_header(char *pData, int stream_id, int payload_len, unsigned long long pts, unsigned long long dts)
{

	bits_buffer_s  	bitsBuffer;
	bitsBuffer.i_size = PES_HDR_LEN;
	bitsBuffer.i_data = 0;
	bitsBuffer.i_mask = 0x80;
	bitsBuffer.p_data =	(unsigned char *)(pData);
	memset(bitsBuffer.p_data, 0, PES_HDR_LEN);
	/*system header*/
	bits_write( &bitsBuffer, 24,0x000001);	/*start code*/
	bits_write( &bitsBuffer, 8, (stream_id));	/*streamID*/
	bits_write( &bitsBuffer, 16,(payload_len)+13);	/*packet_len*/ //指出pes分组中数据长度和该字节后的长度和
	bits_write( &bitsBuffer, 2, 2 );		/*'10'*/
	bits_write( &bitsBuffer, 2, 0 );		/*scrambling_control*/
	bits_write( &bitsBuffer, 1, 0 );		/*priority*/
	bits_write( &bitsBuffer, 1, 0 );		/*data_alignment_indicator*/
	bits_write( &bitsBuffer, 1, 0 );		/*copyright*/
	bits_write( &bitsBuffer, 1, 0 );		/*original_or_copy*/
	bits_write( &bitsBuffer, 1, 1 );		/*PTS_flag*/
	bits_write( &bitsBuffer, 1, 1 );		/*DTS_flag*/
	bits_write( &bitsBuffer, 1, 0 );		/*ESCR_flag*/
	bits_write( &bitsBuffer, 1, 0 );		/*ES_rate_flag*/
	bits_write( &bitsBuffer, 1, 0 );		/*DSM_trick_mode_flag*/
	bits_write( &bitsBuffer, 1, 0 );		/*additional_copy_info_flag*/
	bits_write( &bitsBuffer, 1, 0 );		/*PES_CRC_flag*/
	bits_write( &bitsBuffer, 1, 0 );		/*PES_extension_flag*/
	bits_write( &bitsBuffer, 8, 10);		/*header_data_length*/
	// 指出包含在 PES 分组标题中的可选字段和任何填充字节所占用的总字节数。该字段之前
	//的字节指出了有无可选字段。

	/*PTS,DTS*/
    bits_write( &bitsBuffer, 4, 3 );                    /*'0011'*/
    bits_write( &bitsBuffer, 3, ((pts)>>30)&0x07 );     /*PTS[32..30]*/
	bits_write( &bitsBuffer, 1, 1 );
    bits_write( &bitsBuffer, 15,((pts)>>15)&0x7FFF);    /*PTS[29..15]*/
	bits_write( &bitsBuffer, 1, 1 );
    bits_write( &bitsBuffer, 15,(pts)&0x7FFF);          /*PTS[14..0]*/
	bits_write( &bitsBuffer, 1, 1 );
    bits_write( &bitsBuffer, 4, 1 );                    /*'0001'*/
    bits_write( &bitsBuffer, 3, ((dts)>>30)&0x07 );     /*DTS[32..30]*/
	bits_write( &bitsBuffer, 1, 1 );
    bits_write( &bitsBuffer, 15,((dts)>>15)&0x7FFF);    /*DTS[29..15]*/
	bits_write( &bitsBuffer, 1, 1 );
    bits_write( &bitsBuffer, 15,(dts)&0x7FFF);          /*DTS[14..0]*/
	bits_write( &bitsBuffer, 1, 1 );
	return 0;
}
/***
 *@remark:   rtp头的打包,并循环发送数据
 *@param :   pData      [in] 发送的数据地址
 *           nDatalen   [in] 发送数据的长度
 *           mark_flag  [in] mark标志位
 *           curpts     [in] 时间戳
 *           pPacker    [in] 数据包的基本信息
 *@return:   0 success, others failed
*/

int gb28181_send_rtp_pack(char *databuff, int nDataLen, int mark_flag, Data_Info_s* pPacker)
{
	int nRes = 0;
	int nPlayLoadLen = 0;
	int nSendSize    = 0;
	char szRtpHdr[RTP_HDR_LEN];
	memset(szRtpHdr, 0, RTP_HDR_LEN);

    if(nDataLen + RTP_HDR_LEN <= RTP_MAX_PACKET_BUFF)// 1460 pPacker指针本来有一个1460大小的buffer数据缓存
    {
        // 一帧数据发送完后,给mark标志位置1
		gb28181_make_rtp_header(szRtpHdr, ((mark_flag == 1 )? 1 : 0 ), ++pPacker->u16CSeq, (pPacker->s64CurPts /300), pPacker->u32Ssrc);
		memcpy(pPacker->szBuff, szRtpHdr, RTP_HDR_LEN);
		memcpy(pPacker->szBuff + RTP_HDR_LEN, databuff, nDataLen);
        nRet = SendDataBuff(databuff, RTP_HDR_LEN + nSendSize, pPacker);
		if (nRes != (RTP_HDR_LEN + nDataLen))
		{
			printf(" udp send error !\n");
			return -1;
		}

	}
	else
	{
		nPlayLoadLen = RTP_MAX_PACKET_BUFF - RTP_HDR_LEN; // 每次只能发送的数据长度 除去rtp头
		gb28181_make_rtp_header(pPacker->szBuff, 0, ++pPacker->u16CSeq, (pPacker->s64CurPts/100), pPacker->u32Ssrc);
		memcpy(pPacker->szBuff + RTP_HDR_LEN, databuff, nPlayLoadLen);
                nRet = SendDataBuff(databuff, RTP_HDR_LEN + nSendSize, pPacker);
		if (nRes != (RTP_HDR_LEN + nPlayLoadLen))
		{
			printf(" udp send error !\n");
			return -1;
		}

		nDataLen -= nPlayLoadLen;
		// databuff += (nPlayLoadLen - RTP_HDR_LEN);
		databuff += nPlayLoadLen; // 表明前面到数据已经发送出去
		databuff -= RTP_HDR_LEN; // 用来存放rtp头
		while(nDataLen > 0)
		{
			if(nDataLen <= nPlayLoadLen)
			{
				//一帧数据发送完,置mark标志位
				gb28181_make_rtp_header(databuff, mark_flag, ++pPacker->u16CSeq, (pPacker->s64CurPts/100), pPacker->u32Ssrc);
				nSendSize = nDataLen;
			}
			else
			{
				gb28181_make_rtp_header(databuff, 0, ++pPacker->u16CSeq, (pPacker->s64CurPts/100), pPacker->u32Ssrc);
				nSendSize = nPlayLoadLen;
			}
                        nRet = SendDataBuff(databuff, RTP_HDR_LEN + nSendSize, pPacker);
			if (nRes != (RTP_HDR_LEN + nSendSize))
			{
				printf(" udp send error !\n");
				return -1;
			}
			nDataLen -= nSendSize;
			databuff += nSendSize;
			//因为buffer指针已经向后移动一次rtp头长度后,
			//所以每次循环发送rtp包时,只要向前移动裸数据到长度即可,这是buffer指针实际指向到位置是
			//databuff向后重复的rtp长度的裸数据到位置上 

		}

	}
	return 0;
}

还有一个很重要的宏定义,之所以把它定义成宏,是因为会频繁调用,其功能是循环将一个字节的8位按位一个一个的压入数据,防止出现夸字节的导致字序出错问题具体实现如下,其实是挪用了vlc源码中的实现过来的,这也是读源码的一个好处,能很好的利用里面比较高级而又方便的功能代码模块

#define PS_HDR_LEN  14
#define SYS_HDR_LEN 18
#define PSM_HDR_LEN 24
#define PES_HDR_LEN 19
#define RTP_HDR_LEN 12
/***
 *@remark:  讲传入的数据按地位一个一个的压入数据
 *@param :  buffer   [in]  压入数据的buffer
 *          count    [in]  需要压入数据占的位数
 *          bits     [in]  压入的数值
 */
#define bits_write(buffer, count, bits){	bits_buffer_s *p_buffer = (buffer);	int i_count = (count);	uint64_t i_bits = (bits);	while( i_count > 0 )	{		i_count--;		if( ( i_bits >> i_count )&0x01 )		{			p_buffer->p_data[p_buffer->i_data] |= p_buffer->i_mask;		}		else		{			p_buffer->p_data[p_buffer->i_data] &= ~p_buffer->i_mask;		}		p_buffer->i_mask >>= 1;         /*操作完一个字节第一位后,操作第二位*/		if( p_buffer->i_mask == 0 )     /*循环完一个字节的8位后,重新开始下一位*/		{			p_buffer->i_data++;			p_buffer->i_mask = 0x80;		}	}}

上面忘记贴出rtp封装头了,这次补充,如果在实际不需要rtp的话,可以直接在gb28181_send_rtp_pack函数接口中屏蔽gb28181_make_rtp_header函数接口即可,当然需要注意一点问题,就是对应的buffer指针的位置就不需要移动rtp头的长度了!

int gb28181_make_rtp_header(char *pData, int marker_flag, unsigned short cseq, long long curpts, unsigned int ssrc)
{
	bits_buffer_s  	bitsBuffer;
	if (pData == NULL)
		return -1;
	bitsBuffer.i_size = RTP_HDR_LEN;
	bitsBuffer.i_data = 0;
	bitsBuffer.i_mask = 0x80;
	bitsBuffer.p_data =	(unsigned char *)(pData);
	memset(bitsBuffer.p_data, 0, RTP_HDR_SIZE);
	bits_write(&bitsBuffer, 2, RTP_VERSION);	/* rtp version 	*/
	bits_write(&bitsBuffer, 1, 0);				/* rtp padding 	*/
	bits_write(&bitsBuffer, 1, 0);				/* rtp extension 	*/
	bits_write(&bitsBuffer, 4, 0);				/* rtp CSRC count */
	bits_write(&bitsBuffer, 1, (marker_flag));			/* rtp marker  	*/
	bits_write(&bitsBuffer, 7, 96);			/* rtp payload type*/
	bits_write(&bitsBuffer, 16, (cseq));			/* rtp sequence 	 */
	bits_write(&bitsBuffer, 32, (curpts)); 		/* rtp timestamp 	 */
	bits_write(&bitsBuffer, 32, (ssrc)); 		/* rtp SSRC	 	 */
	return 0;
}

4、封装相关心得

博主已经分步验证过rtp或者ps又或者rtp+ps封装都能正常预览。其实这个封装真心没什么理论知识说的,看看标准都知道了,只要仔细看标准一步一步的向下走,分析各个封装的各个字段,就没有什么问题了,当然在实际中也有很多小问题苦恼我很久,但是无不是因为标准没注意或者理解有误。现在我也只是简单的把自己实现的代码大致贴出来分享了,希望相关开发的少走一些弯路而已。有问题或者有更好的方法,大家可以相互交流。

时间: 2024-11-06 03:22:21

关于对H264码流的PS的封装的相关代码实现的相关文章

关于对H264码流的TS的封装的相关代码实现

1 写在开始之前 在前段时间有分享一个H264封装ps流到相关文章的,这次和大家分享下将H264封装成TS流到相关实现,其实也是工作工作需要.依照上篇一样,分段说明每个数据头的封装情况,当然,一样也会加上rtp头,方便以后的这方面到需求,如果开发不需要的话,可   以自行屏蔽掉,当然需要主要buffer指针的移动情况 2 封装的各个头到规则要点 整个封装过程也是和ps类似,但是最大到区别在于TS流到数据长度都是固定188大小来传输的,而PS流则是可变包结构,正因为两者在结构上到差异,导致了它们在

(转)RTP协议全解(H264码流和PS流)

写在前面:RTP的解析,网上找了很多资料,但是都不全,所以我力图整理出一个比较全面的解析, 其中借鉴了很多文章,我都列在了文章最后,在此表示感谢. 互联网的发展离不开大家的无私奉献,我决定从我做起,希望大家支持. 原创不易,转载请附上链接,谢谢http://blog.csdn.net/chen495810242/article/details/39207305 1.RTP Header解析   图1 1)        V:RTP协议的版本号,占2位,当前协议版本号为2 2)        P:

RTP协议全解(H264码流和PS流)

写在前面:RTP的解析,网上找了很多资料,但是都不全,所以我力图整理出一个比较全面的解析, 其中借鉴了很多文章,我都列在了文章最后,在此表示感谢. 互联网的发展离不开大家的无私奉献,我决定从我做起,希望大家支持. 原创不易,转载请附上链接,谢谢http://blog.csdn.net/chen495810242/article/details/39207305 1.RTP Header解析   图1 1)        V:RTP协议的版本号,占2位,当前协议版本号为2 2)        P:

RTP协议全解析(H264码流和PS流)

目录(?)[+] RTP Header解析 RTP荷载H264码流 1单个NAL单元包 2分片单元FU-A RTP荷载PS流 1PS包头 2系统标题 3节目映射流 4PES分组头部 写在前面:RTP的解析,网上找了很多资料,但是都不全,所以我力图整理出一个比较全面的解析, 其中借鉴了很多文章,我都列在了文章最后,在此表示感谢. 互联网的发展离不开大家的无私奉献,我决定从我做起,希望大家支持. 原创不易,转载请附上链接,谢谢http://blog.csdn.net/chen495810242/ar

H264码流中SPS PPS详解&lt;转&gt;

转载地址:https://zhuanlan.zhihu.com/p/27896239 1 SPS和PPS从何处而来? 2 SPS和PPS中的每个参数起什么作用? 3 如何解析SDP中包含的H.264的SPS和PPS串? 1 客户端抓包 在做客户端视频解码时,一般都会使用Wireshark抓包工具对接收的H264码流进行分析,如下所示: 在这里我们可以看到对解码视频起关键作用的SPS和PPS. 双击SPS内容如下: 双击PPS内容如下: 那么从上面的sps中我们知道图像的宽,高. 宽=(19+1

H264码流处理详解

码流(Data Rate)是指视频文件在单位时间内使用的数据流量,也叫码率,是视频编码中画面质量控制中最重要的部分.同样分辨率下,视频文件的码流越大,压缩比就越小,画面质量就越好. 一.简介 H.264的主要目标:1.高的视频压缩比 2.良好的网络亲和性 解决方案: (1)VCL video coding layer 视频编码层 (2)NAL network abstraction layer 网络提取层 (3)VCL:核心算法引擎,块,宏块及片的语法级别的定义 (4)NAL:片级以上的语法级别

(转)MP4文件两种格式AVC1和H264的区别及利用FFMPEG demux为h264码流事项

出自:http://www.mworkbox.com/wp/work/314.html 2013-05-04 MP4的视频H264封装有2种格式:h264和avc1,对于这个细节,很容易被忽略.笔者也是在改编LIVE555流媒体时,增加mp4文件类型支持时遇到了该问题. (一)首先,从原理上了解一下这2种格式的区别:AVC1 描述:H.264 bitstream without start codes.一般通过ffmpeg转码生成的视频,是不带起始码0×00000001的.H264 描述:H.2

h264码流分析工具

1.codecvisa: http://www.codecian.com/downloads.html 2.Three ways comes to mind (if you are looking for something free, else google "h264 analysis"):    a)  Download h.264 parser from:    http://www.w6rz.net/h264_parse.zip (from this thread @ doo

cuda+ffmpeg+opengl解码rtsp h264码流多路

Cuda 解码 全尺寸 解码 .全尺寸窗口绘制测试( 分别 测试 视频 文件和 IP 相机 实时视频 ) 1080 p 视屏 文件 全尺寸 解码 全尺寸 显示 72 0p IP 相机 全尺寸 解码 全尺寸 显示 (最多只能获取 6路)