本篇文章将简要介绍一个客户端是如何请求获取视频流的这么一个过程。
对于服务器与客户端而言,要建立通信、要传输数据都少不了网络通信模块的支持。在本项目中,通过TCP协议来建立并维护服务器与客户端之间的连接,并传输通信指令,就是在前面系列文章中提到的RTSP协议和SDP协议,不过在本项目中采用的RTSP协议和SDP协议均是笔者在标准的RTSP协议和SDP协议的基础上做了适当的改造。通过UDP协议来进行服务器与客户端之间的数据传输。
对于一个客户端来说,首先其根据外部输入的一个URL(如:rtsp://127.0.0.1:554/test.ts)来进行解析,分离出服务器IP地址、服务器端口号和请求的文件名,然后开始执行以下几个步骤:
(1)客户端尝试连接服务器,如果连接成功则继续往下执行。
(2)客户端尝试打开流,向服务器发送OPTIONS指令。
(3)服务器收到客户端的OPTIONS请求指令后,立即向客户端发送成功的状态码。
(4)客户端成功收到服务器的响应后,向服务器发送DESCRIBE指令。
(5)服务器收到客户端的DESCRIBE请求指令后,首先根据请求的文件名查找ServerMediaSession是否存在(首先判断请求文件是否存在,如不存在则直接返回并向客户端发送NotFound指令;如果文件存在但ServerMediaSession Table里面不存在对应的ServerMediaSession,则创建一个,针对.ts媒体源而言,这个创建的ServerMediaSession就是MPEG2TransportFileServerMediaSession),之后生成SDP会话描述信息(组装文件大小、正常播放时长等信息),然后向客户端发送响应信息。
(6)客户端成功收到服务器的响应后,创建一个MediaSession,并解析SDP会话描述信息,提取出文件大小、正常播放时长等信息,然后创建一个本地临时文件。
(7)客户端向服务器发送SETUP指令,该指令中包括了客户端的RTP/RTCP的端口号(RTP端口号在一个指定范围内随机生成,RTCP端口号是RTP端口号加1)。
(8)服务器收到客户端的SETUP指令后,首先判断请求文件名对应的ServerMediaSession是否存在,然后解析出客户端的RTP/RTCP端口号,接着服务器生成RTP/RTCP端口号(同理,RTP端口号在一个指定范围内随机生成,RTCP端口号是RTP端口号加1),之后通过获取流参数来输出StreamState实例(每个客户端会话均保有一个单独的StreamState实例),在这个获取流参数的过程中,主要完成了MediaSource(针对.ts媒体源而言,这个Source就是MPEG2TransportStreamFileSource)、RTP socket、RTCP socket、RtpSink及StreamState的创建;然后向客户端发送响应信息。
(9)客户端成功收到服务器的响应后,通过解析提取出端口号等信息,创建RtpSource,并开始异步模式下的流数据的接收工作。
(10)客户端向服务器发送PLAY指令,该指令包括了播放时间范围。
(11)服务器在收到客户端的PLAY指令后,首先提取播放时间范围等信息,然后定位流,之后开始播放流(在后面有简要的说明),同时向客户端发送响应信息。
至此,服务器与客户端之间的数据传输就已经开始了。
服务器的每个客户端会话播放流的这个工作是由RtpSink来完成的,在类RtpSink中,通过BackgroundWorker来进行文件数据帧的读取、组包、发送等工作,而这些工作都是在BackgroundWorker的DoWork事件中完成的。其部分代码实现如下所示:
/// <summary> /// Handles the DoWork event of the backgroundWorker control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="System.ComponentModel.DoWorkEventArgs"/> instance containing the event data.</param> void backgroundWorker_DoWork(object sender, DoWorkEventArgs e) { while (isCurrentlyPlaying) { /* Because we use UDP to send the packets, and it is an unreliable * transport mode, so we send the packets at low speed in the * begin, and after that, the speed will be more faster. */ if (rateControlCounter > maxCountInLowSpeed) { /* In order to avoids the high using of the CPU, * sleeps the current thread when it sends greater * than "maxCountToSendPerTime". * Note: "maxCountToSendPerTime" must not be set too large, * or else, there will result in a high packet lost rate. */ if (sendControlCounter > maxCountToSendPerTime) { Thread.Sleep(1); sendControlCounter = 0; } Interlocked.Increment(ref sendControlCounter); } else { Thread.Sleep(1); } BuildAndSendPacket(); } }
从初步的测试来看,上面这部分代码可以在局域网下比较好的工作,在广域网上笔者没有测试过,不知效果如何。当然,上面这部分的代码还有待进一步修改完善。