音频和视频流最佳选择?SRT协议解析及报文识别

我们所知道SRT 是由 Haivision 和 Wowza 开发的开源视频流协议。很多人会认为在不久的将来,它被是 RTMP 的替代品。因为RTMP协议安全性稍低,延迟相对较高 ,而相对于SRT协议支持高质量、稳定性、亚秒级延迟、强大的编解码器支持。SRT被许多行业专家认为是视频流的新协议。SRT究竟是什么?

什么是 SRT?

安全可靠传输 (SRT) 是一种开源数据传输协议。SRT 使用用户数据报协议 (UDP),旨在通过公共互联网发送高质量视频,因此该协议是音频和视频流的最佳选择。

在许多主要的开源技术Wireshare、FFMpeg中,应用了SRT安全可靠传输协议。

SRT的应用在哪些领域?

SRT协议主要的应用在直播、多流、视频编码、网关等领域。在技术方面,它提供类似于传输控制协议 (TCP) 的可靠传输。 然而,使用 UDP 协议作为底层传输层。

SRT 还支持低延迟(默认为 120 毫秒)的数据包恢复和使用高级加密标准 (AES) 的加密。

简而言之,通过 SRT,端到端流安全、视频弹性和基于网络条件的实时动态端点调整成为可能。

高质量视频传输

SRT 可以更轻松地通过互联网协议 (IP) 以低端到端延迟进行流式传输。截至目前,低延迟流媒体的协议偏好很少。

这是因为通过公共互联网流式传输可能会造成数据包丢失和抖动等障碍。 SRT 提供解决此问题的方法。

此外,该协议还包括防止数据包丢失、抖动和带宽波动的保护。这意味着如果网络状况不稳定,您的流可能会停止。但它几乎可以立即从这种丢包中恢复,您的观众在观看时几乎不会注意到任何问题。

其他有益于直播的功能包括:

1、 基于时间戳的数据包传输,通过源时间传输实现更好的延迟控制
2、 控制发送者的速度
3、 防止丢包未及时恢复造成丢包
4、数据包重传的定期 NAK 报告

SRT如何更好的保护你的视频流

如果您使用 SRT 协议流式传输视频,您肯定会受益于它的优势。 该协议保护您的视频流,并确保所有数据在发送时都经过加密。 它还消除了特殊互联网连接的负担,因为该协议可保证您交付的视频内容的质量。

SRT 通过提供可确保安全传输即使是最高级别的产品的加密技术而闻名。 SRT 可以启用端到端 AES 128/256 位加密算法,这是任何需要保护的内容的理想选择。 即使在不可靠的 WiFi 或蜂窝连接导致带宽波动期间,SRT 也能防止视频抖动和数据包丢失,可保护您的视频内容免遭分发。

SRT数据包

下面我们要对SRT协议要做进一步分析。

根据上图的红色框起来的方格F:

F=0 ;Data Packet

Data (content to transmit)
Filtering packet (FEC)

F=1;Control Packet

HANDSHAKE
KEEPALIVE
ACK
NAK (Loss Report)
SHUTDOWN
ACKACK

SRT流媒体传输协议握手过程

caller作为连接的发起者,知道对应设置Listener模式设备的公网IP地址及其监听的UDP端口。而Listener监听发起的SRT请求,需要知道使用哪个UDP端口,并在这个端口一直监听。

Caller-Listener Handshake

caller发起建立一个点对点传输的SRT连接,Listener监听发起SRT会话的请求。

Rendezvous Handshake

Rendezvous两端共同协商建立连接,基本不使用此种连接。

SRT在快速连接方面有明显优势,两次握手成功即可建连;简单了明白了握手过程,接来就是SRT协议解析了。

SRT协议解析及报文识别

下面我们对SRT协议进行解析。

/* 实际解析数据包的代码 
 *
 */
 static void dissect_srt_control_packet(u_char *data_info,int PayloadLen)
 {
	 int offset = 0;
	 
	 offset += 4;
	 
	 if (data_info[0] == 0x80 && data_info[1] == 0x02)/*UMSG_ACK*/
	 {

        int ack_number = ntohl(*(uint32_t*)(data_info + offset));
		printf("ACK Number: %d\n",ack_number);
		
		offset += 4; /*Time Stamp*/

        int time_stamp = ntohl(*(uint32_t*)(data_info + offset));
		printf("Time Stamp: %d\n",time_stamp);
		
		offset += 4; /*Destination Socket ID*/
		
		int dst_sock_id = ntohl(*(uint32_t*)(data_info + offset));
		printf("Destination Socket ID: %d\n",dst_sock_id);
		
		offset += 4; /*ACKD_RCVLASTACK*/
		
		int ack_rcv = ntohl(*(uint32_t*)(data_info + offset));
		printf("ACKD_RCVLASTACK: %d \n",ack_rcv);
		
		offset += 4; /*ACKD_RTT*/
		
		int ack_rtt = ntohl(*(uint32_t*)(data_info + offset));
		printf("ACKD_RTT: %d us \n",ack_rtt);
		
		offset += 4; /*ACKD_RTTVAR*/
		
		int ack_rttvar = ntohl(*(uint32_t*)(data_info + offset));
		printf("ACKD_RTTVAR: %d us \n",ack_rttvar);
		
		offset += 4; /*ACKD_BUFFERLEFT*/
		
		int ack_buffer= ntohl(*(uint32_t*)(data_info + offset));
		printf("ACKD_BUFFERLEFT: %d pkts \n",ack_buffer);
		
		offset += 4; /*ACKD_RCVSPEED*/
		
		int ack_rcvspeed= ntohl(*(uint32_t*)(data_info + offset));
		printf("ACKD_RCVSPEED: %d pkts/s \n",ack_rcvspeed);
		
		
		offset += 4; /*ACKD_BANDWIDTH*/
		
		int ack_banwidth= ntohl(*(uint32_t*)(data_info + offset));
		printf("ACKD_BANDWIDTH: %d pkts/s \n",ack_banwidth);
		
		offset += 4; /*ACKD_RCVRATE*/
		
		int ack_rcvate= ntohl(*(uint32_t*)(data_info + offset));
		printf("ACKD_RCVRATE: %d pkts/s \n",ack_rcvate);
		
	 }
	 else if (data_info[0] == 0x80 && data_info[1] == 0x00)/*UMSG_HANDSHAKE*/
	 {
		char ipbuf[IP_BUFFER_SIZE];
		const int final_length = PayloadLen;
		int baselen = 64;
		offset += 12;
		const int version = ntohl(*(uint32_t*)(data_info + offset));
		/*包含握手版本(当前为4或5) */
		printf("Handshake version:%d\n",version);

		offset += 2; /*Encryption Field*/
		
		offset += 2; /*Extended Field*/
		
		offset += 4; /*Initial Sequence Number*/
		
		int srt_handshake_isn= ntohl(*(uint32_t*)(data_info + offset));
		printf("Initial Sequence Number: %d\n",srt_handshake_isn);
		
		offset += 4; /*MTU*/
		
		int srt_handshake_mtu= ntohl(*(uint32_t*)(data_info + offset));
		printf("MTU: %d \n",srt_handshake_mtu);
		
		offset += 4; /*Flow Window*/
		
		int srt_handshake_flow_window= ntohl(*(uint32_t*)(data_info + offset));
		printf("Flow Window: %d\n",srt_handshake_flow_window);
		
		offset += 4; /*Hanshake Type*/
		
		int srt_handshake_reqtype= ntohl(*(uint32_t*)(data_info + offset));
		printf("Hanshake Type: %d\n",srt_handshake_reqtype);

		offset += 4; /*Socket ID*/
		
		int srt_handshake_id= ntohl(*(uint32_t*)(data_info + offset));
		printf("Socket ID: %d\n",srt_handshake_id);
		
		offset += 4; /*SYN Cookie*/
		
		int srt_handshake_cookie= ntohl(*(uint32_t*)(data_info + offset));
		printf("SYN Cookie: %d\n",srt_handshake_cookie);
		
		offset += 4; /*Peer IP Address*/
		
		srt_format_ip_address(ipbuf, sizeof ipbuf,strdup((const char*)(data_info+offset)));
		
		printf("Peer IP Address: %s\n",ipbuf);
		
		if (final_length > baselen)
		{
			
			/* 提取SRT握手扩展块
			 并相应地增加baselen。 
			*/
            int begin = baselen;
			
			for (;;)
			{
				const uint16_t blockid  = ntohs(*(uint16_t*)(data_info + begin));

				begin += 2;
                const uint16_t blocklen = ntohs(*(uint16_t*)(data_info + begin));
				
				
				// Shift to the payload
                begin += 2;
				
				switch (blockid)
				{
					case SRT_CMD_HSREQ:
					case SRT_CMD_HSRSP:
						if (blocklen == 3)
						{
							//uint32_t version = 0;
							const int vmajor = (data_info[begin+1]) & 0xff;
							const int vminor = (data_info[begin+2]) & 0xff;
							const int vpatch = data_info[begin+3] & 0xff;
							printf("SRT HS Extension type:%d \n",blockid);
							printf("SRT HS Extension size:%d \n",blocklen);
							printf("SRT Version(%d.%d.%d)\n", vmajor, vminor, vpatch);

						}
						else
						{
								
						}
						break;

					case SRT_CMD_KMREQ:
					case SRT_CMD_KMRSP:
						// Rely on the extracted blocklen
						//srt_format_kmx(tree, tvb, begin, blocklen*4);
						break;

					case SRT_CMD_SID:
						break;

					case SRT_CMD_CONJESTCTRL:
						break;

					default:
						printf( "Ext Type value is %u\n",blockid);
						break;
				}

				/* Move the index pointer past the block and repeat. */
				begin += blocklen * 4;

				/* OK, once one block is done, interrupt the loop. */
				if (begin >= final_length)
					break;
			}
			baselen = begin;
		}
			
			
	 }
	 else
	 {
		 
	 }
		
	 
 }

static void dissect_srt(u_char *data_info,int PayloadLen)
{
	/* Other misc. local variables. */
    bool is_control = 0;
	
	/*必须至少有24个捕获的字节才能进行检查 */
	if (PayloadLen < 24)
		return ;	  
	printf("SrtHdr 0x%.2X,0x%.2X,0x%.2X,0x%.2X\n",data_info[0],data_info[1],data_info[2],data_info[3]);	
	
	if ((data_info[0] == 0x80 && data_info[1] == 0x00 && data_info[2] == 0x00 && data_info[3] == 0x00)
	|| (data_info[0] == 0x80 && data_info[1] == 0x02 && data_info[2] == 0x00 && data_info[3] == 0x00)/*UMSG_ACK*/
	|| (data_info[0] == 0x80 && data_info[1] == 0x06 && data_info[2] == 0x00 && data_info[3] == 0x00)/*UMSG_ACKACK*/
	)
	{
		is_control = true;
	}
	
	if (is_control)
	{
		
		dissect_srt_control_packet(data_info,PayloadLen);
	}
	else
	{
		/*srt data type*/
	}
}

编译运行:

这里把srt协议识别出来,并且解析各个字段。

对比RTMP和SRT协议特点

RTMP是实时消息协议,它保持持久、稳定的连接并允许低延迟通信。RTMP 协议的另一个缺点是可能由于带宽低而中断,直到您的流可能根本无法启动。添加到缺点列表中,由于交付视频的安全性低,一些严密的防火墙可能不允许 RTMP 连接。虽然,我们不得不说这种情况很少发生。

RTMP协议目前使用 H.264 视频编解码器和 AAC 音频编解码器,它们相当陈旧,不能提供最佳质量。

最后总结一下RTMP优点及缺点:

优点:多播支持、低缓冲、宽平台支持。

缺点:旧的编解码器,安全性稍低,延迟相对较高。

SRT是安全可靠传输协议,SRT 是由 Haivision 和 Wowza 开发的开源视频流协议。在不久的将来,它被广泛认为是 RTMP 的替代品。共享相同的优势,SRT 正在迈出下一步,使亚秒级延迟的稳定直播的梦想成为现实。它允许您通过次优网络直播您的内容。然而,一个很大的缺点是播放选项不可用。

SRT 可以保护您的实时视频免受抖动、带宽波动和数据包丢失的影响。此外,在亚秒级延迟方面,SRT 与 FTL 和 WebRTC 类似,可以实现近乎实时的通信。

此外,还声明该协议与编解码器无关,这意味着它支持任何现代视频和音频编解码器。

说了这么多,SRT优点及缺点分别是:

优点:高质量、稳定性、亚秒级延迟、强大的编解码器支持。

缺点:平台支持弱,无法播放。

总结

如果您使用 SRT 协议流式传输视频,您肯定会受益于它的优势。 该协议保护您的视频流,并确保所有数据在发送时都经过加密。 它还消除了特殊互联网连接的负担,因为该协议可保证您交付的视频内容的质量。