http://blog.csdn.net/huangxinfeng/article/details/5824934
本篇文章简要介绍客户端有关RTSP的业务代码实现。
客户端有关RTSP的业务逻辑代码均在RtspClient类中实现,在该类中除了提供连接/断开服务器的公有方法之外,还提供了打开流、播放流、暂停流、停止流的公有方法。其中打开流描述了客户端从发出OPTIONS指令到开始传输流的基本步骤,其代码示例如下:
- /// <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
- /// <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
- /// <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
- /// <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
- /// <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
- /// <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
- /// <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方法完成,这两个方法的代码示例如下:
- /// <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>
- /// 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类来完成。