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

来源:互联网 发布:手工皮具知乎 编辑:程序博客网 时间:2024/05/12 06:45

http://blog.csdn.net/huangxinfeng/article/details/5824934

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

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

[c-sharp] view plaincopy
  1. /// <summary>  
  2. /// Opens the stream.  
  3. /// </summary>  
  4. /// <param name="url">The URL.</param>  
  5. /// <returns>Succeeded or failed.</returns>  
  6. public bool OpenStream(string url)  
  7. {  
  8.     if (!this.isConnected)  
  9.     {  
  10.         return false;  
  11.     }  
  12.   
  13.     // Sets the request url:  
  14.     this.requestUrl = url;  
  15.   
  16.     // Sends "OPTIONS" command and then gets the response:  
  17.     bool result = this.SendOptionsCmd();  
  18.     if (!result)  
  19.     {  
  20.         this.CloseStream();  
  21.         return false;  
  22.     }  
  23.   
  24.     // Sends "DESCRIBE" command and then gets the SDP description:  
  25.     string sdpDescription = this.SendDescribeCmd();  
  26.     if (string.IsNullOrEmpty(sdpDescription))  
  27.     {  
  28.         this.CloseStream();  
  29.         return false;  
  30.     }  
  31.   
  32.     // Creates a media session object from the SDP description which  
  33.     // we have just received from the server:  
  34.     this.mediaSession = new MediaSession(sdpDescription);  
  35.   
  36.     // Then, resolves the SDP description and initializes all basic  
  37.     // information:  
  38.     result = this.mediaSession.ResolveSdpDescription();  
  39.     if (!result)  
  40.     {  
  41.         this.CloseStream();  
  42.         return false;  
  43.     }  
  44.   
  45.     // And then, creates the output file to write the data:  
  46.     result = this.CreateOutFile();  
  47.     if (!result)  
  48.     {  
  49.         this.CloseStream();  
  50.         return false;  
  51.     }  
  52.   
  53.     // After that, sends the "SETUP" command and setups the stream:  
  54.     result = this.SendSetupCmd();  
  55.     if (!result)  
  56.     {  
  57.         this.CloseStream();  
  58.         return false;  
  59.     }  
  60.   
  61.     // Finally, sends the "PLAY" command and starts playing stream:  
  62.     result = this.PlayStream();  
  63.     if (!result)  
  64.     {  
  65.         this.CloseStream();  
  66.         return false;  
  67.     }  
  68.   
  69.     this.OnStreamOpened();  
  70.   
  71.     return true;  
  72. }  

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

(1)OPTIONS

[c-sharp] view plaincopy
  1. /// <summary>  
  2. /// Sends the options CMD.  
  3. /// </summary>  
  4. /// <returns>Succeeded or failed.</returns>  
  5. private bool SendOptionsCmd()  
  6. {  
  7.     if (!this.isConnected)  
  8.     {  
  9.         return false;  
  10.     }  
  11.   
  12.     StringBuilder sb = new StringBuilder();  
  13.     sb.AppendFormat("{0} ", Constants.RTSP_CMD_OPTIONS);    // command name of 'OPTIONS'  
  14.     sb.AppendFormat("{0} RTSP/1.0/r/n"this.requestUrl);   // request url  
  15.     sb.AppendFormat("CSeq: {0}/r/n", ++rtspSeqNum); // sequence number  
  16.     sb.AppendFormat("User-Agent: {0}/r/n/r/n", Constants.USER_AGENT_HEADER);    // user agent header  
  17.   
  18.     bool isSucceeded = SendRtspRequest(sb.ToString());  
  19.     if (!isSucceeded)  
  20.     {  
  21.         return false;  
  22.     }  
  23.   
  24.     bool isOk = GetRtspResponse();  
  25.     if (!isOk)  
  26.     {  
  27.         return false;  
  28.     }  
  29.   
  30.     return true;  
  31. }  

(2)DESCRIBE

[c-sharp] view plaincopy
  1. /// <summary>  
  2. /// Sends the describe CMD.  
  3. /// </summary>  
  4. /// <returns>Succeeded or failed.</returns>  
  5. private string SendDescribeCmd()  
  6. {  
  7.     if (!this.isConnected)  
  8.     {  
  9.         return string.Empty;  
  10.     }  
  11.   
  12.     StringBuilder sb = new StringBuilder();  
  13.     sb.AppendFormat("{0} ", Constants.RTSP_CMD_DESCRIBE);    // command name of 'DESCRIBE'  
  14.     sb.AppendFormat("{0} RTSP/1.0/r/n"this.requestUrl);   // request url  
  15.     sb.AppendFormat("CSeq: {0}/r/n", ++rtspSeqNum); // sequence number  
  16.     sb.AppendFormat("User-Agent: {0}/r/n/r/n", Constants.USER_AGENT_HEADER);    // user agent header  
  17.   
  18.     bool isSucceeded = SendRtspRequest(sb.ToString());  
  19.     if (!isSucceeded)  
  20.     {  
  21.         return string.Empty;  
  22.     }  
  23.   
  24.     bool isOk = GetRtspResponse();  
  25.     if (!isOk)  
  26.     {  
  27.         return string.Empty;  
  28.     }  
  29.   
  30.     return string.Empty;  
  31. }  

(3)SETUP

[c-sharp] view plaincopy
  1. /// <summary>  
  2. /// Sends the setup CMD.  
  3. /// </summary>  
  4. /// <returns>Succeeded or failed.</returns>  
  5. private bool SendSetupCmd()  
  6. {  
  7.     if (!this.isConnected)  
  8.     {  
  9.         return false;  
  10.     }  
  11.   
  12.     StringBuilder sb = new StringBuilder();  
  13.     sb.AppendFormat("{0} ", Constants.RTSP_CMD_SETUP);    // command name of 'SETUP'  
  14.     sb.AppendFormat("{0} RTSP/1.0/r/n"this.requestUrl);   // request url  
  15.     sb.AppendFormat("CSeq: {0}/r/n", ++rtspSeqNum); // sequence number  
  16.     sb.AppendFormat("Session: {0}/r/n"this.sessionId);   // session id  
  17.     sb.AppendFormat("User-Agent: {0}/r/n/r/n", Constants.USER_AGENT_HEADER);    // user agent header  
  18.   
  19.     bool isSucceeded = SendRtspRequest(sb.ToString());  
  20.     if (!isSucceeded)  
  21.     {  
  22.         return false;  
  23.     }  
  24.   
  25.     bool isOk = GetRtspResponse();  
  26.     if (!isOk)  
  27.     {  
  28.         return false;  
  29.     }  
  30.   
  31.     return true;  
  32. }  

(4)PLAY

[c-sharp] view plaincopy
  1. /// <summary>  
  2. /// Plays the stream.  
  3. /// </summary>  
  4. /// <returns>Succeeded or failed.</returns>  
  5. public bool PlayStream()  
  6. {  
  7.     if (!this.isConnected)  
  8.     {  
  9.         return false;  
  10.     }  
  11.   
  12.     if (this.Duration < 0)  
  13.     {  
  14.         this.Duration = 0;  
  15.     }  
  16.     else if (this.Duration == 0 || this.Duration > this.mediaSession.PlayEndTime)  
  17.     {  
  18.         this.Duration = this.mediaSession.PlayEndTime - this.SeekTime;  
  19.     }  
  20.   
  21.     double startTime = this.SeekTime;  
  22.     double endTime = this.SeekTime + this.Duration;  
  23.   
  24.     string range;  
  25.     if (startTime < 0)  
  26.     {  
  27.         range = string.Empty;  
  28.     }  
  29.     else if (endTime < 0)  
  30.     {  
  31.         range = string.Format("Range: npt={0}-", startTime);  
  32.     }  
  33.     else  
  34.     {  
  35.         range = string.Format("Range: npt={0}-{1}", startTime, endTime);  
  36.     }  
  37.   
  38.     StringBuilder sb = new StringBuilder();  
  39.     sb.AppendFormat("{0} ", Constants.RTSP_CMD_PLAY);    // command name of 'PLAY'  
  40.     sb.AppendFormat("{0} RTSP/1.0/r/n"this.requestUrl);   // request url  
  41.     sb.AppendFormat("CSeq: {0}/r/n", ++rtspSeqNum); // sequence number  
  42.     sb.AppendFormat("Session: {0}/r/n"this.sessionId);    // session id  
  43.     sb.AppendFormat("{0}/r/n", range);  // range, 'Range: npt='  
  44.     sb.AppendFormat("User-Agent: {0}/r/n/r/n", Constants.USER_AGENT_HEADER);    // user agent header  
  45.   
  46.     bool isSucceeded = SendRtspRequest(sb.ToString());  
  47.     if (!isSucceeded)  
  48.     {  
  49.         this.CloseStream();  
  50.         return false;  
  51.     }  
  52.   
  53.     bool isOk = GetRtspResponse();  
  54.     if (!isOk)  
  55.     {  
  56.         this.CloseStream();  
  57.         return false;  
  58.     }  
  59.   
  60.     this.OnStreamPlaying();  
  61.   
  62.     return true;  
  63. }  

(5)PAUSE

[c-sharp] view plaincopy
  1. /// <summary>  
  2. /// Pauses the stream.  
  3. /// </summary>  
  4. /// <returns>Succeeded or failed.</returns>  
  5. public bool PauseStream()  
  6. {  
  7.     if (!this.isConnected)  
  8.     {  
  9.         return false;  
  10.     }  
  11.   
  12.     StringBuilder sb = new StringBuilder();  
  13.     sb.AppendFormat("{0} ", Constants.RTSP_CMD_PAUSE);    // command name of 'PAUSE'  
  14.     sb.AppendFormat("{0} RTSP/1.0/r/n"this.requestUrl);   // request url  
  15.     sb.AppendFormat("CSeq: {0}/r/n", ++rtspSeqNum); // sequence number  
  16.     sb.AppendFormat("Session: {0}/r/n"this.sessionId);    // session id  
  17.     sb.AppendFormat("User-Agent: {0}/r/n/r/n", Constants.USER_AGENT_HEADER);    // user agent header  
  18.   
  19.     bool isSucceeded = SendRtspRequest(sb.ToString());  
  20.     if (!isSucceeded)  
  21.     {  
  22.         this.CloseStream();  
  23.         return false;  
  24.     }  
  25.   
  26.     bool isOk = GetRtspResponse();  
  27.     if (!isOk)  
  28.     {  
  29.         this.CloseStream();  
  30.         return false;  
  31.     }  
  32.   
  33.     this.OnStreamPausing();  
  34.   
  35.     return true;  
  36. }  

(6)TEARDOWN

[c-sharp] view plaincopy
  1. /// <summary>  
  2. /// Tear downs the stream.  
  3. /// </summary>  
  4. /// <returns>Succeeded or failed.</returns>  
  5. public bool TeardownStream()  
  6. {  
  7.     if (!this.isConnected)  
  8.     {  
  9.         return false;  
  10.     }  
  11.   
  12.     StringBuilder sb = new StringBuilder();  
  13.     sb.AppendFormat("{0} ", Constants.RTSP_CMD_TEARDOWN);    // command name of 'TEARDOWN'  
  14.     sb.AppendFormat("{0} RTSP/1.0/r/n"this.requestUrl);   // request url  
  15.     sb.AppendFormat("CSeq: {0}/r/n", ++rtspSeqNum); // sequence number  
  16.     sb.AppendFormat("Session: {0}/r/n"this.sessionId);    // session id  
  17.     sb.AppendFormat("User-Agent: {0}/r/n/r/n", Constants.USER_AGENT_HEADER);    // user agent header  
  18.                   
  19.     bool isSucceeded = SendRtspRequest(sb.ToString());  
  20.     if (!isSucceeded)  
  21.     {  
  22.         this.CloseStream();  
  23.         return false;  
  24.     }  
  25.   
  26.     bool isOk = GetRtspResponse();  
  27.     if (!isOk)  
  28.     {  
  29.         this.CloseStream();  
  30.         return false;  
  31.     }  
  32.   
  33.     this.OnStreamStopped();  
  34.   
  35.     return true;  
  36. }  

 

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

[c-sharp] view plaincopy
  1. /// <summary>  
  2. /// Sends the RTSP request.  
  3. /// </summary>  
  4. /// <param name="request">The request.</param>  
  5. /// <returns>Success or failed.</returns>  
  6. private bool SendRtspRequest(string request)  
  7. {  
  8.     if (this.socket == null)  
  9.     {  
  10.         return false;  
  11.     }  
  12.   
  13.     try  
  14.     {  
  15.         byte[] sendBuffer = Utils.StringToBytes(request);  
  16.         int sendBytesCount = this.socket.Send(sendBuffer, sendBuffer.Length, SocketFlags.None);  
  17.         if (sendBytesCount < 1)  
  18.         {  
  19.             return false;  
  20.         }  
  21.   
  22.         return true;  
  23.     }  
  24.     catch (System.Exception e)  
  25.     {  
  26.         this.OnExceptionOccurred(e);  
  27.         return false;  
  28.     }  
  29. }  

 

[c-sharp] view plaincopy
  1. /// <summary>  
  2. /// Gets the RTSP response.  
  3. /// </summary>  
  4. /// <returns>Success or failed.</returns>  
  5. private bool GetRtspResponse()  
  6. {  
  7.     bool isOk = false;  
  8.     int revBytesCount = 0;  
  9.     byte[] revBuffer = new byte[1024 * 4]; // 4 KB buffer  
  10.     response = string.Empty;  
  11.   
  12.     // Set the timeout for synchronous receive methods to   
  13.     // 5 seconds (5000 milliseconds.)  
  14.     socket.ReceiveTimeout = 5000;  
  15.   
  16.     while (true)  
  17.     {  
  18.         try  
  19.         {  
  20.             revBytesCount = socket.Receive(revBuffer, revBuffer.Length, SocketFlags.None);  
  21.             if (revBytesCount >= 1)  
  22.             {  
  23.                 // Here, we have received the data from the server successfully, so we break the loop.  
  24.                 break;  
  25.             }  
  26.         }  
  27.         catch (SocketException se)  
  28.         {  
  29.             // Receive data exception, may be it has come to the ReceiveTimeout or other exception.  
  30.             this.OnExceptionOccurred(se);  
  31.             break;  
  32.         }  
  33.     }  
  34.   
  35.     // Just looking for the RTSP status code:  
  36.     if (revBytesCount >= 1)  
  37.     {  
  38.         response = Utils.BytesToString(revBuffer, revBytesCount);  
  39.   
  40.         if (response.StartsWith(Constants.RTSP_HEADER_VERSION))  
  41.         {  
  42.             string[] dstArray = response.Split(' ');  // Separate by a blank  
  43.             if (dstArray.Length > 1)  
  44.             {  
  45.                 string code = dstArray[1];  
  46.                 if (code.Equals(Constants.RTSP_STATUS_CODE_OK)) // RTSP/1.0 200 OK ...  
  47.                 {  
  48.                     isOk = true;  
  49.                 }  
  50.             }  
  51.         }  
  52.     }  
  53.   
  54.     return isOk;  
  55. }  

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

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


0 0