SharpStreaming项目开发纪实:构建基于RTSP协议的服务器及客户端应用(三)——客户端的业务代码实现

    本篇文章简要介绍客户端有关RTSP的业务代码实现。

    客户端有关RTSP的业务逻辑代码均在RtspClient类中实现,在该类中除了提供连接/断开服务器的公有方法之外,还提供了打开流、播放流、暂停流、停止流的公有方法。其中打开流描述了客户端从发出OPTIONS指令到开始传输流的基本步骤,其代码示例如下:

///

/// Opens the stream. /// /// The URL. /// Succeeded or failed. public bool OpenStream(string url) { if (!this.isConnected) { return false; } // Sets the request url: this.requestUrl = url; // Sends "OPTIONS" command and then gets the response: bool result = this.SendOptionsCmd(); if (!result) { this.CloseStream(); return false; } // Sends "DESCRIBE" command and then gets the SDP description: string sdpDescription = this.SendDescribeCmd(); if (string.IsNullOrEmpty(sdpDescription)) { this.CloseStream(); return false; } // Creates a media session object from the SDP description which // we have just received from the server: this.mediaSession = new MediaSession(sdpDescription); // Then, resolves the SDP description and initializes all basic // information: result = this.mediaSession.ResolveSdpDescription(); if (!result) { this.CloseStream(); return false; } // And then, creates the output file to write the data: result = this.CreateOutFile(); if (!result) { this.CloseStream(); return false; } // After that, sends the "SETUP" command and setups the stream: result = this.SendSetupCmd(); if (!result) { this.CloseStream(); return false; } // Finally, sends the "PLAY" command and starts playing stream: result = this.PlayStream(); if (!result) { this.CloseStream(); return false; } this.OnStreamOpened(); return true; }

以下是与每个请求指令相关的代码示例(注意这仅是初步版本,后续可能会进一步修改完善):

(1)OPTIONS
///

/// Sends the options CMD. /// /// Succeeded or failed. private bool SendOptionsCmd() { if (!this.isConnected) { return false; } StringBuilder sb = new StringBuilder(); sb.AppendFormat("{0} ", Constants.RTSP_CMD_OPTIONS); // command name of 'OPTIONS' sb.AppendFormat("{0} RTSP/1.0/r/n", this.requestUrl); // request url sb.AppendFormat("CSeq: {0}/r/n", ++rtspSeqNum); // sequence number sb.AppendFormat("User-Agent: {0}/r/n/r/n", Constants.USER_AGENT_HEADER); // user agent header bool isSucceeded = SendRtspRequest(sb.ToString()); if (!isSucceeded) { return false; } bool isOk = GetRtspResponse(); if (!isOk) { return false; } return true; }

(2)DESCRIBE
///

/// Sends the describe CMD. /// /// Succeeded or failed. private string SendDescribeCmd() { if (!this.isConnected) { return string.Empty; } StringBuilder sb = new StringBuilder(); sb.AppendFormat("{0} ", Constants.RTSP_CMD_DESCRIBE); // command name of 'DESCRIBE' sb.AppendFormat("{0} RTSP/1.0/r/n", this.requestUrl); // request url sb.AppendFormat("CSeq: {0}/r/n", ++rtspSeqNum); // sequence number sb.AppendFormat("User-Agent: {0}/r/n/r/n", Constants.USER_AGENT_HEADER); // user agent header bool isSucceeded = SendRtspRequest(sb.ToString()); if (!isSucceeded) { return string.Empty; } bool isOk = GetRtspResponse(); if (!isOk) { return string.Empty; } return string.Empty; }

(3)SETUP
///

/// Sends the setup CMD. /// /// Succeeded or failed. private bool SendSetupCmd() { if (!this.isConnected) { return false; } StringBuilder sb = new StringBuilder(); sb.AppendFormat("{0} ", Constants.RTSP_CMD_SETUP); // command name of 'SETUP' sb.AppendFormat("{0} RTSP/1.0/r/n", this.requestUrl); // request url sb.AppendFormat("CSeq: {0}/r/n", ++rtspSeqNum); // sequence number sb.AppendFormat("Session: {0}/r/n", this.sessionId); // session id sb.AppendFormat("User-Agent: {0}/r/n/r/n", Constants.USER_AGENT_HEADER); // user agent header bool isSucceeded = SendRtspRequest(sb.ToString()); if (!isSucceeded) { return false; } bool isOk = GetRtspResponse(); if (!isOk) { return false; } return true; }

(4)PLAY
///

/// Plays the stream. /// /// Succeeded or failed. public bool PlayStream() { if (!this.isConnected) { return false; } if (this.Duration < 0) { this.Duration = 0; } else if (this.Duration == 0 || this.Duration > this.mediaSession.PlayEndTime) { this.Duration = this.mediaSession.PlayEndTime - this.SeekTime; } double startTime = this.SeekTime; double endTime = this.SeekTime + this.Duration; string range; if (startTime < 0) { range = string.Empty; } else if (endTime < 0) { range = string.Format("Range: npt={0}-", startTime); } else { range = string.Format("Range: npt={0}-{1}", startTime, endTime); } StringBuilder sb = new StringBuilder(); sb.AppendFormat("{0} ", Constants.RTSP_CMD_PLAY); // command name of 'PLAY' sb.AppendFormat("{0} RTSP/1.0/r/n", this.requestUrl); // request url sb.AppendFormat("CSeq: {0}/r/n", ++rtspSeqNum); // sequence number sb.AppendFormat("Session: {0}/r/n", this.sessionId); // session id sb.AppendFormat("{0}/r/n", range); // range, 'Range: npt=' sb.AppendFormat("User-Agent: {0}/r/n/r/n", Constants.USER_AGENT_HEADER); // user agent header bool isSucceeded = SendRtspRequest(sb.ToString()); if (!isSucceeded) { this.CloseStream(); return false; } bool isOk = GetRtspResponse(); if (!isOk) { this.CloseStream(); return false; } this.OnStreamPlaying(); return true; }

(5)PAUSE
///

/// Pauses the stream. /// /// Succeeded or failed. public bool PauseStream() { if (!this.isConnected) { return false; } StringBuilder sb = new StringBuilder(); sb.AppendFormat("{0} ", Constants.RTSP_CMD_PAUSE); // command name of 'PAUSE' sb.AppendFormat("{0} RTSP/1.0/r/n", this.requestUrl); // request url sb.AppendFormat("CSeq: {0}/r/n", ++rtspSeqNum); // sequence number sb.AppendFormat("Session: {0}/r/n", this.sessionId); // session id sb.AppendFormat("User-Agent: {0}/r/n/r/n", Constants.USER_AGENT_HEADER); // user agent header bool isSucceeded = SendRtspRequest(sb.ToString()); if (!isSucceeded) { this.CloseStream(); return false; } bool isOk = GetRtspResponse(); if (!isOk) { this.CloseStream(); return false; } this.OnStreamPausing(); return true; }

(6)TEARDOWN
///

/// Tear downs the stream. /// /// Succeeded or failed. public bool TeardownStream() { if (!this.isConnected) { return false; } StringBuilder sb = new StringBuilder(); sb.AppendFormat("{0} ", Constants.RTSP_CMD_TEARDOWN); // command name of 'TEARDOWN' sb.AppendFormat("{0} RTSP/1.0/r/n", this.requestUrl); // request url sb.AppendFormat("CSeq: {0}/r/n", ++rtspSeqNum); // sequence number sb.AppendFormat("Session: {0}/r/n", this.sessionId); // session id sb.AppendFormat("User-Agent: {0}/r/n/r/n", Constants.USER_AGENT_HEADER); // user agent header bool isSucceeded = SendRtspRequest(sb.ToString()); if (!isSucceeded) { this.CloseStream(); return false; } bool isOk = GetRtspResponse(); if (!isOk) { this.CloseStream(); return false; } this.OnStreamStopped(); return true; }

 

    客户端每次发出请求指令时,通常需要立即得到服务器的响应信息。所以针对这样的情形,客户端发送指令和接收响应信息采用了同步方式进行通信。发送请求通过SendRtspRequest方法完成,接收响应通过GetRtspResponse方法完成,这两个方法的代码示例如下:

///

/// Sends the RTSP request. /// /// The request. /// Success or failed. private bool SendRtspRequest(string request) { if (this.socket == null) { return false; } try { byte[] sendBuffer = Utils.StringToBytes(request); int sendBytesCount = this.socket.Send(sendBuffer, sendBuffer.Length, SocketFlags.None); if (sendBytesCount < 1) { return false; } return true; } catch (System.Exception e) { this.OnExceptionOccurred(e); return false; } }

 

///

/// Gets the RTSP response. /// /// Success or failed. private bool GetRtspResponse() { bool isOk = false; int revBytesCount = 0; byte[] revBuffer = new byte[1024 * 4]; // 4 KB buffer response = string.Empty; // Set the timeout for synchronous receive methods to // 5 seconds (5000 milliseconds.) socket.ReceiveTimeout = 5000; while (true) { try { revBytesCount = socket.Receive(revBuffer, revBuffer.Length, SocketFlags.None); if (revBytesCount >= 1) { // Here, we have received the data from the server successfully, so we break the loop. break; } } catch (SocketException se) { // Receive data exception, may be it has come to the ReceiveTimeout or other exception. this.OnExceptionOccurred(se); break; } } // Just looking for the RTSP status code: if (revBytesCount >= 1) { response = Utils.BytesToString(revBuffer, revBytesCount); if (response.StartsWith(Constants.RTSP_HEADER_VERSION)) { string[] dstArray = response.Split(' '); // Separate by a blank if (dstArray.Length > 1) { string code = dstArray[1]; if (code.Equals(Constants.RTSP_STATUS_CODE_OK)) // RTSP/1.0 200 OK ... { isOk = true; } } } } return isOk; }

    上述的GetRtspResponse方法的代码示例中,设置了同步接收的超时时长,并通过while循环不停地尝试接收,直到接收到数据或者发生了异常(如超时等)。当接收到响应数据后,首先进行解析,然后判断该响应串中是否包含了响应成功状态码(200)。之后就是返回继续执行其他工作。

    关于客户端在收到服务器对DESCRIBE请求的响应后,解析SDP描述信息的过程,这里不作介绍。客户端的与RTSP业务逻辑相关的工作主要由RtspClient类来完成,而与RTP/RTCP、文件处理等相关的初始工作则有MediaSession类来完成。

你可能感兴趣的:(程序开发,流媒体)