SharpStreaming项目开发纪实:构建基于RTSP协议的服务器及客户端应用——准备知识(RTSP协议)

    技术2022-05-19  27

    本篇文章简要介绍RTSP协议的相关知识。

        在RTSP协议文档中有这样的描述:实时流协议(RTSP)建立并控制一个或几个时间同步的连续流媒体。尽管连续媒体流与控制流有可能交叉,但RTSP 本身通常并不发送连续媒体流。换言之,RTSP 充当多媒体服务器的网络远程控制。

        正因为如此,我们经常可以看到,RTSP协议的内容当中,在请求串中均带有会话状态,如SETUP、PLAY、PAUSE等,这就是RTSP的状态。虽然RTSP 中很多方法与状态无关,但下列方法在定义服务器流资源的分配与应用上起着重要的作用: SETUP:让服务器给流分配资源,启动 RTSP 会话。 PLAY 与RECORD:启动 SETUP 分配流的数据传输。 PAUSE:临时停止流,而不释放服务器资源。 TEARDOWN:释放流的资源,RTSP 会话停止。

        标识状态的 RTSP 方法使用会话(session)标题域识别RTSP 会话,为回应SETUP请求,服务器生成会话标识。

        一个RTSP协议的内容当中拥有非常多的参数,这些参数包括但不限于会话状态、RTSP协议版本、请求URL、会话标识、正常播放时间、回应的状态代码等等。

        关于RTSP协议的详细介绍可以查阅RFC2326文档,在此,笔者提供了中文版和英文版的下载地址,如下:

        RTSP_RFC2326(CN):http://download.csdn.net/source/2592342     RTSP_RFC2326(EN):http://download.csdn.net/source/2592347

        在SharpStreaming项目中,将会使用到的RTSP会话状态包括:OPTIONS、DESCRIBE、SETUP、PLAY、PAUSE、TEARDOWN、GET_PARAMETER、SET_PARAMETER。

        将会使用到的状态码(Status-Code)包括但不限于:"200"(OK)、"400" (Bad Request)、"401" (Unauthorized)、"404" (Not Found)、"405" (Method Not Allowed)、"461" (Unsupported transport)。

     

     

    本篇文章简要介绍服务器部分与RTSP协议实现相关的业务代码实现。

        在介绍服务器有关RTSP业务代码实现之前,我们首先要明确服务器与客户端建立RTSP通信的基本过程,如下所述:

        ·C --> S:Send OPTIONS Cmd,S --> C:Handle OPTIONS Cmd and Send Response;     ·C --> S:Send DESCRIBE Cmd,S --> C:Handle DESCRIBE Cmd and Send Response;     ·C --> S:Send SETUP Cmd,S --> C:Handle SETUP Cmd and Send Response;     ·C --> S:Send PLAY Cmd,S --> C:Handle PLAY Cmd and Send Response;     ·C --> S:Send PAUSE Cmd,S --> C:Handle PAUSE Cmd and Send Response;     ·C --> S:Send TEARDOWN Cmd,S --> C:Handle TEARDOWN Cmd and Send Response。

        正如前一篇文章中所提到的那样,在代码上实现RTSP协议的简单应用还是相对简单的。在参考了live555开源项目中关于RTSP协议的实现代码之后, 针对本项目,笔者将只对其中的几个重要会话状态OPTIONS、DESCRIBE、SETUP、PLAY、PAUSE、TEARDOWN作初步的代码实 现,同时在本项目中将不考虑用户验证的问题。 以下是关于上述会话状态和若干状态码的主要方法:

    + expand source view plain copy to clipboard print ? /// <summary>    /// Handles the incoming request.    /// </summary>    /// <param name="recvBuffer">The receive buffer.</param>    /// <param name="recvBufferSize">Size of the receive buffer.</param>    private   void  HandleIncomingRequest( byte [] recvBuffer,  int  recvBufferSize)   {       string  cmdName;       string  rtspUrl;       string  urlPreSuffix;       string  urlSuffix;       string  cseq;          string  request = Utils.BytesToString(recvBuffer, recvBufferSize);          // Parses the request string into command name and 'CSeq', then handle the command.        bool  isSucceeded = RtspCommon.ParseRequestString(           request, out  cmdName,  out  rtspUrl,  out  urlPreSuffix,  out  urlSuffix,  out  cseq);          if  (isSucceeded)       {           switch  (cmdName)           {               case  Constants.RTSP_CMD_OPTIONS:                   this .HandleOptionsCmd(cseq);                   break ;               case  Constants.RTSP_CMD_DESCRIBE:                   this .HandleDescribeCmd(cseq, rtspUrl, urlSuffix, request);                   break ;               case  Constants.RTSP_CMD_SETUP:                   this .HandleSetupCmd(cseq, urlPreSuffix, urlSuffix, request);                   break ;               case  Constants.RTSP_CMD_PLAY:                   this .HandlePlayCmd(cseq, request);                   break ;               case  Constants.RTSP_CMD_PAUSE:                   this .HandlePauseCmd(cseq);                   break ;               case  Constants.RTSP_CMD_TEARDOWN:                   this .HandleTearDownCmd(cseq);                   break ;               default :                   this .HandleNotSupportCmd(cseq);                   break ;           }       }       else        {           // Parses request string failed!            this .HandleBadCmd();       }          // After we handle the client request, we must send the response to the client.        // *****        //Converts string to bytes.        byte [] sendBuffer = Utils.StringToBytes( this .response);          // Sends the rtsp response to the client.        this .clientSocket.SendDatagram(sendBuffer);   }   /// <summary> /// Handles the incoming request. /// </summary> /// <param name="recvBuffer">The receive buffer.</param> /// <param name="recvBufferSize">Size of the receive buffer.</param> private void HandleIncomingRequest(byte[] recvBuffer, int recvBufferSize) { string cmdName; string rtspUrl; string urlPreSuffix; string urlSuffix; string cseq; string request = Utils.BytesToString(recvBuffer, recvBufferSize); // Parses the request string into command name and 'CSeq', then handle the command. bool isSucceeded = RtspCommon.ParseRequestString( request, out cmdName, out rtspUrl, out urlPreSuffix, out urlSuffix, out cseq); if (isSucceeded) { switch (cmdName) { case Constants.RTSP_CMD_OPTIONS: this.HandleOptionsCmd(cseq); break; case Constants.RTSP_CMD_DESCRIBE: this.HandleDescribeCmd(cseq, rtspUrl, urlSuffix, request); break; case Constants.RTSP_CMD_SETUP: this.HandleSetupCmd(cseq, urlPreSuffix, urlSuffix, request); break; case Constants.RTSP_CMD_PLAY: this.HandlePlayCmd(cseq, request); break; case Constants.RTSP_CMD_PAUSE: this.HandlePauseCmd(cseq); break; case Constants.RTSP_CMD_TEARDOWN: this.HandleTearDownCmd(cseq); break; default: this.HandleNotSupportCmd(cseq); break; } } else { // Parses request string failed! this.HandleBadCmd(); } // After we handle the client request, we must send the response to the client. // ***** //Converts string to bytes. byte[] sendBuffer = Utils.StringToBytes(this.response); // Sends the rtsp response to the client. this.clientSocket.SendDatagram(sendBuffer); }       该方法用来处理接收到的客户端请求,首先解析了请求字符串,输出会话命令、请求的URL、流名称、序号等内容(out string cmdName, out string rtspUrl, out string urlPreSuffix, out string urlSuffix, out string cseq);然后根据会话命令通过switch...case语句来分类处理;最后将生成的响应字符串发送给指定的客户端。

        以下是方法HandleOptionsCmd、HandleBadCmd、HandleNotSupportCmd、HandleNotFoundCmd、HandleUnsupportedTransportCmd的代码实现部分:

    + expand source view plain copy to clipboard print ? /// <summary>    /// Handles the options CMD.    /// </summary>    /// <param name="cseq">The cseq.</param>    private   void  HandleOptionsCmd( string  cseq)   {       this .response =  string .Format( "RTSP/1.0 200 OK/r/nCSeq: {0}/r/nDate: {1}/r/nPublic: {2}/r/n/r/n" ,           cseq, DateTime.Now.ToLocalTime(), Constants.RTSP_ALLOW_COMMAND);   }      /// <summary>    /// Handles the bad CMD.    /// </summary>    private   void  HandleBadCmd()   {       this .response =  string .Format( "RTSP/1.0 400 Bad Request/r/nDate: {0}/r/nAllow: {1}/r/n/r/n" ,           DateTime.Now.ToLocalTime(), Constants.RTSP_ALLOW_COMMAND);          this .BeginTeardown();   }      /// <summary>    /// Handles the not support CMD.    /// </summary>    /// <param name="cseq">The cseq.</param>    private   void  HandleNotSupportCmd( string  cseq)   {       this .response =  string .Format( "RTSP/1.0 405 Method Not Allowed/r/nCSeq: {0}/r/nDate: {1}/r/nAllow: {2}/r/n/r/n" ,           cseq, DateTime.Now.ToLocalTime(), Constants.RTSP_ALLOW_COMMAND);          this .BeginTeardown();   }      /// <summary>    /// Handles the not found CMD.    /// </summary>    /// <param name="cseq">The cseq.</param>    private   void  HandleNotFoundCmd( string  cseq)   {       this .response =  string .Format( "RTSP/1.0 404 Stream Not Found/r/nCSeq: {0}/r/nDate: {1}/r/n" ,           cseq, DateTime.Now.ToLocalTime());          this .BeginTeardown();   }      /// <summary>    /// Handles the unsupported transport CMD.    /// </summary>    /// <param name="cseq">The cseq.</param>    private   void  HandleUnsupportedTransportCmd( string  cseq)   {       this .response =  string .Format( "RTSP/1.0 461 Unsupported Transport/r/nCSeq: {0}/r/nDate: {1}/r/n" ,           cseq, DateTime.Now.ToLocalTime());          this .BeginTeardown();   }   /// <summary> /// Handles the options CMD. /// </summary> /// <param name="cseq">The cseq.</param> private void HandleOptionsCmd(string cseq) { this.response = string.Format("RTSP/1.0 200 OK/r/nCSeq: {0}/r/nDate: {1}/r/nPublic: {2}/r/n/r/n", cseq, DateTime.Now.ToLocalTime(), Constants.RTSP_ALLOW_COMMAND); } /// <summary> /// Handles the bad CMD. /// </summary> private void HandleBadCmd() { this.response = string.Format("RTSP/1.0 400 Bad Request/r/nDate: {0}/r/nAllow: {1}/r/n/r/n", DateTime.Now.ToLocalTime(), Constants.RTSP_ALLOW_COMMAND); this.BeginTeardown(); } /// <summary> /// Handles the not support CMD. /// </summary> /// <param name="cseq">The cseq.</param> private void HandleNotSupportCmd(string cseq) { this.response = string.Format("RTSP/1.0 405 Method Not Allowed/r/nCSeq: {0}/r/nDate: {1}/r/nAllow: {2}/r/n/r/n", cseq, DateTime.Now.ToLocalTime(), Constants.RTSP_ALLOW_COMMAND); this.BeginTeardown(); } /// <summary> /// Handles the not found CMD. /// </summary> /// <param name="cseq">The cseq.</param> private void HandleNotFoundCmd(string cseq) { this.response = string.Format("RTSP/1.0 404 Stream Not Found/r/nCSeq: {0}/r/nDate: {1}/r/n", cseq, DateTime.Now.ToLocalTime()); this.BeginTeardown(); } /// <summary> /// Handles the unsupported transport CMD. /// </summary> /// <param name="cseq">The cseq.</param> private void HandleUnsupportedTransportCmd(string cseq) { this.response = string.Format("RTSP/1.0 461 Unsupported Transport/r/nCSeq: {0}/r/nDate: {1}/r/n", cseq, DateTime.Now.ToLocalTime()); this.BeginTeardown(); }

        值得注意的是,这里并没有列出方法HandleDescribeCmd、HandleSetupCmd、HandlePlayCmd、 HandlePauseCmd和HandleTearDownCmd的代码实现,主要原因是其中牵扯到媒体源的选择(当然,笔者已初步决定了使用TS流格 式的文件作为媒体源)及RTP/RTCP等的建立过程,而笔者对其中的一些过程尚存在一些不明晰之处,因而思路仍在不断调整之中。

        不过,对于处理DESCRIBE指令,其首先要根据流名称查找对应的ServerMediaSession,然后生成SDP描述信息,接着将SDP描述信息等组装成响应字符串并最后发回给客户端。

        而对于处理SETUP指令,首先是要确认ServerMediaSession的存在,然后解析传输头信息 (ParseTransportHeader),然后要拿到相关的Parameters,最后组装成响应信息并发回给客户端。关于这一块目前的主要问题是 在对GetStreamParameters的处理尚有一些不明确之处。

        对于处理PLAY、PAUSE、TEARDOWN,当前的思路主要是通过ServerMediaSubsession调用相应的StartStream、PauseStream和DeleteStream(是否需要这么做,还有待进一步考虑)方法。

        另外,为支持TCP和UDP传输,特别编写了一个ClientSocketBase作为ClientSocketTcp和ClientSocketUdp的基类,该类包含一些事件和虚方法,代码如下所示:  

    + expand source view plain copy to clipboard print ? /// <summary>    /// A base class for client socket.    /// </summary>    public   class  ClientSocketBase   {       #region Public Class Events        public   event  EventHandler ClientTeardown;       public   event  EventHandler<TSocketEventArgs> DatagramReceived;       public   event  EventHandler<TExceptionEventArgs> ExceptionOccurred;       #endregion          #region Public Virtual Methods        /// <summary>        /// Sends the datagram with the asynchronous mode.        /// </summary>        /// <param name="sendBuffer">The send buffer.</param>        public   virtual   void  SendDatagram( byte [] sendBuffer)       {           // No implementation.        }          /// <summary>        /// Receives the datagram with the asynchronous mode.        /// </summary>        public   virtual   void  ReceiveDatagram()       {           // No implementation.        }          /// <summary>        /// Closes the client socket.        /// </summary>        public   virtual   void  CloseClientSocket()       {           // No implementation.        }       #endregion          #region Protected Virtual Methods        /// <summary>        /// Ends to send the datagram.        /// </summary>        /// <param name="iar">The iar.</param>        protected   virtual   void  EndSendDatagram(IAsyncResult iar)       {           // No implementation.        }          /// <summary>        /// Ends to receive the datagram.        /// </summary>        /// <param name="iar">The iar.</param>        protected   virtual   void  EndReceiveDatagram(IAsyncResult iar)       {           // No implementation.        }          /// <summary>        /// Called when [client tear down].        /// </summary>        protected   virtual   void  OnClientTeardown()       {           EventHandler handler = this .ClientTeardown;           if  (handler !=  null )           {               handler(this , EventArgs.Empty);           }       }          /// <summary>        /// Called when [datagram received].        /// </summary>        /// <param name="recvBuffer">The received buffer.</param>        /// <param name="recvBufferSize">Size of the received buffer.</param>        protected   virtual   void  OnDatagramReceived( byte [] recvBuffer,  int  recvBufferSize)       {           EventHandler<TSocketEventArgs> handler = this .DatagramReceived;           if  (handler !=  null )           {               TSocketEventArgs e = new  TSocketEventArgs(recvBuffer, recvBufferSize);               handler(this , e);           }       }          /// <summary>        /// Called when [exception occurred].        /// </summary>        /// <param name="ex">The ex.</param>        protected   virtual   void  OnExceptionOccurred(Exception ex)       {           EventHandler<TExceptionEventArgs> handler = this .ExceptionOccurred;           if  (handler !=  null )           {               TExceptionEventArgs e = new  TExceptionEventArgs(ex);               handler(this , e);           }       }       #endregion    }  

    /// <summary> /// A base class for client socket. /// </summary> public class ClientSocketBase { #region Public Class Events public event EventHandler ClientTeardown; public event EventHandler<TSocketEventArgs> DatagramReceived; public event EventHandler<TExceptionEventArgs> ExceptionOccurred; #endregion #region Public Virtual Methods /// <summary> /// Sends the datagram with the asynchronous mode. /// </summary> /// <param name="sendBuffer">The send buffer.</param> public virtual void SendDatagram(byte[] sendBuffer) { // No implementation. } /// <summary> /// Receives the datagram with the asynchronous mode. /// </summary> public virtual void ReceiveDatagram() { // No implementation. } /// <summary> /// Closes the client socket. /// </summary> public virtual void CloseClientSocket() { // No implementation. } #endregion #region Protected Virtual Methods /// <summary> /// Ends to send the datagram. /// </summary> /// <param name="iar">The iar.</param> protected virtual void EndSendDatagram(IAsyncResult iar) { // No implementation. } /// <summary> /// Ends to receive the datagram. /// </summary> /// <param name="iar">The iar.</param> protected virtual void EndReceiveDatagram(IAsyncResult iar) { // No implementation. } /// <summary> /// Called when [client tear down]. /// </summary> protected virtual void OnClientTeardown() { EventHandler handler = this.ClientTeardown; if (handler != null) { handler(this, EventArgs.Empty); } } /// <summary> /// Called when [datagram received]. /// </summary> /// <param name="recvBuffer">The received buffer.</param> /// <param name="recvBufferSize">Size of the received buffer.</param> protected virtual void OnDatagramReceived(byte[] recvBuffer, int recvBufferSize) { EventHandler<TSocketEventArgs> handler = this.DatagramReceived; if (handler != null) { TSocketEventArgs e = new TSocketEventArgs(recvBuffer, recvBufferSize); handler(this, e); } } /// <summary> /// Called when [exception occurred]. /// </summary> /// <param name="ex">The ex.</param> protected virtual void OnExceptionOccurred(Exception ex) { EventHandler<TExceptionEventArgs> handler = this.ExceptionOccurred; if (handler != null) { TExceptionEventArgs e = new TExceptionEventArgs(ex); handler(this, e); } } #endregion }       当前关于TCP和UDP的数据接收和发送,均采用异步模式,但是其中又有一些例外,这将在下一篇文章中讲述,敬请关注。

     

     

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

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

    + expand source view plain copy to clipboard print ? /// <summary>    /// Opens the stream.    /// </summary>    /// <param name="url">The URL.</param>    /// <returns>Succeeded or failed.</returns>    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 ;   }   /// <summary> /// Opens the stream. /// </summary> /// <param name="url">The URL.</param> /// <returns>Succeeded or failed.</returns> 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

    + expand source view plain copy to clipboard print ? /// <summary>    /// Sends the options CMD.    /// </summary>    /// <returns>Succeeded or failed.</returns>    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 ;   }   /// <summary> /// Sends the options CMD. /// </summary> /// <returns>Succeeded or failed.</returns> 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

    + expand source view plain copy to clipboard print ? /// <summary>    /// Sends the describe CMD.    /// </summary>    /// <returns>Succeeded or failed.</returns>    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;   }   /// <summary> /// Sends the describe CMD. /// </summary> /// <returns>Succeeded or failed.</returns> 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

    + expand source view plain copy to clipboard print ? /// <summary>    /// Sends the setup CMD.    /// </summary>    /// <returns>Succeeded or failed.</returns>    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 ;   }   /// <summary> /// Sends the setup CMD. /// </summary> /// <returns>Succeeded or failed.</returns> 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

    + expand source view plain copy to clipboard print ? /// <summary>    /// Plays the stream.    /// </summary>    /// <returns>Succeeded or failed.</returns>    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 ;   }   /// <summary> /// Plays the stream. /// </summary> /// <returns>Succeeded or failed.</returns> 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

    + expand source view plain copy to clipboard print ? /// <summary>    /// Pauses the stream.    /// </summary>    /// <returns>Succeeded or failed.</returns>    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 ;   }   /// <summary> /// Pauses the stream. /// </summary> /// <returns>Succeeded or failed.</returns> 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

    + expand source view plain copy to clipboard print ? /// <summary>    /// Tear downs the stream.    /// </summary>    /// <returns>Succeeded or failed.</returns>    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 ;   }   /// <summary> /// Tear downs the stream. /// </summary> /// <returns>Succeeded or failed.</returns> 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方法完成,这两个方法的代码示例如下:

    + expand source view plain copy to clipboard print ? /// <summary>    /// Sends the RTSP request.    /// </summary>    /// <param name="request">The request.</param>    /// <returns>Success or failed.</returns>    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 ;       }   }   /// <summary> /// Sends the RTSP request. /// </summary> /// <param name="request">The request.</param> /// <returns>Success or failed.</returns> 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; } }

    + expand source view plain copy to clipboard print ? /// <summary>    /// Gets the RTSP response.    /// </summary>    /// <returns>Success or failed.</returns>    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;   }   /// <summary> /// Gets the RTSP response. /// </summary> /// <returns>Success or failed.</returns> 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类来完成。


    最新回复(0)