/* Copyright (C) <2007-2016> SatIp.RtspSample is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SatIp.RtspSample is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SatIp.RtspSample. If not, see . */ using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Text.RegularExpressions; using MediaBrowser.Model.Logging; namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtsp { public class RtspSession : IDisposable { #region Private Fields private static readonly Regex RegexRtspSessionHeader = new Regex(@"\s*([^\s;]+)(;timeout=(\d+))?"); private const int DefaultRtspSessionTimeout = 30; // unit = s private static readonly Regex RegexDescribeResponseSignalInfo = new Regex(@";tuner=\d+,(\d+),(\d+),(\d+),", RegexOptions.Singleline | RegexOptions.IgnoreCase); private string _address; private string _rtspSessionId; public string RtspSessionId { get { return _rtspSessionId; } set { _rtspSessionId = value; } } private int _rtspSessionTimeToLive = 0; private string _rtspStreamId; private int _clientRtpPort; private int _clientRtcpPort; private int _serverRtpPort; private int _serverRtcpPort; private int _rtpPort; private int _rtcpPort; private string _rtspStreamUrl; private string _destination; private string _source; private string _transport; private int _signalLevel; private int _signalQuality; private Socket _rtspSocket; private int _rtspSequenceNum = 1; private bool _disposed = false; private readonly ILogger _logger; #endregion #region Constructor public RtspSession(string address, ILogger logger) { if (string.IsNullOrWhiteSpace(address)) { throw new ArgumentNullException("address"); } _address = address; _logger = logger; _logger.Info("Creating RtspSession with url {0}", address); } ~RtspSession() { Dispose(false); } #endregion #region Properties #region Rtsp public string RtspStreamId { get { return _rtspStreamId; } set { if (_rtspStreamId != value) { _rtspStreamId = value; OnPropertyChanged("RtspStreamId"); } } } public string RtspStreamUrl { get { return _rtspStreamUrl; } set { if (_rtspStreamUrl != value) { _rtspStreamUrl = value; OnPropertyChanged("RtspStreamUrl"); } } } public int RtspSessionTimeToLive { get { if (_rtspSessionTimeToLive == 0) _rtspSessionTimeToLive = DefaultRtspSessionTimeout; return _rtspSessionTimeToLive * 1000 - 20; } set { if (_rtspSessionTimeToLive != value) { _rtspSessionTimeToLive = value; OnPropertyChanged("RtspSessionTimeToLive"); } } } #endregion #region Rtp Rtcp /// /// The LocalEndPoint Address /// public string Destination { get { if (string.IsNullOrEmpty(_destination)) { var result = ""; var host = Dns.GetHostName(); var hostentry = Dns.GetHostEntry(host); foreach (var ip in hostentry.AddressList.Where(ip => ip.AddressFamily == AddressFamily.InterNetwork)) { result = ip.ToString(); } _destination = result; } return _destination; } set { if (_destination != value) { _destination = value; OnPropertyChanged("Destination"); } } } /// /// The RemoteEndPoint Address /// public string Source { get { return _source; } set { if (_source != value) { _source = value; OnPropertyChanged("Source"); } } } /// /// The Media Data Delivery RemoteEndPoint Port if we use Unicast /// public int ServerRtpPort { get { return _serverRtpPort; } set { if (_serverRtpPort != value) { _serverRtpPort = value; OnPropertyChanged("ServerRtpPort"); } } } /// /// The Media Metadata Delivery RemoteEndPoint Port if we use Unicast /// public int ServerRtcpPort { get { return _serverRtcpPort; } set { if (_serverRtcpPort != value) { _serverRtcpPort = value; OnPropertyChanged("ServerRtcpPort"); } } } /// /// The Media Data Delivery LocalEndPoint Port if we use Unicast /// public int ClientRtpPort { get { return _clientRtpPort; } set { if (_clientRtpPort != value) { _clientRtpPort = value; OnPropertyChanged("ClientRtpPort"); } } } /// /// The Media Metadata Delivery LocalEndPoint Port if we use Unicast /// public int ClientRtcpPort { get { return _clientRtcpPort; } set { if (_clientRtcpPort != value) { _clientRtcpPort = value; OnPropertyChanged("ClientRtcpPort"); } } } /// /// The Media Data Delivery RemoteEndPoint Port if we use Multicast /// public int RtpPort { get { return _rtpPort; } set { if (_rtpPort != value) { _rtpPort = value; OnPropertyChanged("RtpPort"); } } } /// /// The Media Meta Delivery RemoteEndPoint Port if we use Multicast /// public int RtcpPort { get { return _rtcpPort; } set { if (_rtcpPort != value) { _rtcpPort = value; OnPropertyChanged("RtcpPort"); } } } #endregion public string Transport { get { if (string.IsNullOrEmpty(_transport)) { _transport = "unicast"; } return _transport; } set { if (_transport != value) { _transport = value; OnPropertyChanged("Transport"); } } } public int SignalLevel { get { return _signalLevel; } set { if (_signalLevel != value) { _signalLevel = value; OnPropertyChanged("SignalLevel"); } } } public int SignalQuality { get { return _signalQuality; } set { if (_signalQuality != value) { _signalQuality = value; OnPropertyChanged("SignalQuality"); } } } #endregion #region Private Methods private void ProcessSessionHeader(string sessionHeader, string response) { if (!string.IsNullOrEmpty(sessionHeader)) { var m = RegexRtspSessionHeader.Match(sessionHeader); if (!m.Success) { _logger.Error("Failed to tune, RTSP {0} response session header {1} format not recognised", response, sessionHeader); } _rtspSessionId = m.Groups[1].Captures[0].Value; _rtspSessionTimeToLive = m.Groups[3].Captures.Count == 1 ? int.Parse(m.Groups[3].Captures[0].Value) : DefaultRtspSessionTimeout; } } private void ProcessTransportHeader(string transportHeader) { if (!string.IsNullOrEmpty(transportHeader)) { var transports = transportHeader.Split(','); foreach (var transport in transports) { if (transport.Trim().StartsWith("RTP/AVP")) { var sections = transport.Split(';'); foreach (var section in sections) { var parts = section.Split('='); if (parts[0].Equals("server_port")) { var ports = parts[1].Split('-'); _serverRtpPort = int.Parse(ports[0]); _serverRtcpPort = int.Parse(ports[1]); } else if (parts[0].Equals("destination")) { _destination = parts[1]; } else if (parts[0].Equals("port")) { var ports = parts[1].Split('-'); _rtpPort = int.Parse(ports[0]); _rtcpPort = int.Parse(ports[1]); } else if (parts[0].Equals("ttl")) { _rtspSessionTimeToLive = int.Parse(parts[1]); } else if (parts[0].Equals("source")) { _source = parts[1]; } else if (parts[0].Equals("client_port")) { var ports = parts[1].Split('-'); var rtp = int.Parse(ports[0]); var rtcp = int.Parse(ports[1]); //if (!rtp.Equals(_rtpPort)) //{ // Logger.Error("SAT>IP base: server specified RTP client port {0} instead of {1}", rtp, _rtpPort); //} //if (!rtcp.Equals(_rtcpPort)) //{ // Logger.Error("SAT>IP base: server specified RTCP client port {0} instead of {1}", rtcp, _rtcpPort); //} _rtpPort = rtp; _rtcpPort = rtcp; } } } } } } private void Connect() { _rtspSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); var ip = IPAddress.Parse(_address); var rtspEndpoint = new IPEndPoint(ip, 554); _rtspSocket.Connect(rtspEndpoint); } private void Disconnect() { if (_rtspSocket != null && _rtspSocket.Connected) { _rtspSocket.Shutdown(SocketShutdown.Both); _rtspSocket.Close(); } } private void SendRequest(RtspRequest request) { if (_rtspSocket == null) { Connect(); } try { request.Headers.Add("CSeq", _rtspSequenceNum.ToString()); _rtspSequenceNum++; byte[] requestBytes = request.Serialise(); if (_rtspSocket != null) { var requestBytesCount = _rtspSocket.Send(requestBytes, requestBytes.Length, SocketFlags.None); if (requestBytesCount < 1) { } } } catch (Exception e) { _logger.Error(e.Message); } } private void ReceiveResponse(out RtspResponse response) { response = null; var responseBytesCount = 0; byte[] responseBytes = new byte[1024]; try { responseBytesCount = _rtspSocket.Receive(responseBytes, responseBytes.Length, SocketFlags.None); response = RtspResponse.Deserialise(responseBytes, responseBytesCount); string contentLengthString; int contentLength = 0; if (response.Headers.TryGetValue("Content-Length", out contentLengthString)) { contentLength = int.Parse(contentLengthString); if ((string.IsNullOrEmpty(response.Body) && contentLength > 0) || response.Body.Length < contentLength) { if (response.Body == null) { response.Body = string.Empty; } while (responseBytesCount > 0 && response.Body.Length < contentLength) { responseBytesCount = _rtspSocket.Receive(responseBytes, responseBytes.Length, SocketFlags.None); response.Body += System.Text.Encoding.UTF8.GetString(responseBytes, 0, responseBytesCount); } } } } catch (SocketException) { } } #endregion #region Public Methods public RtspStatusCode Setup(string query, string transporttype) { RtspRequest request; RtspResponse response; //_rtspClient = new RtspClient(_rtspDevice.ServerAddress); if ((_rtspSocket == null)) { Connect(); } if (string.IsNullOrEmpty(_rtspSessionId)) { request = new RtspRequest(RtspMethod.Setup, string.Format("rtsp://{0}:{1}/?{2}", _address, 554, query), 1, 0); switch (transporttype) { case "multicast": request.Headers.Add("Transport", string.Format("RTP/AVP;multicast")); break; case "unicast": var activeTcpConnections = IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections(); var usedPorts = new HashSet(); foreach (var connection in activeTcpConnections) { usedPorts.Add(connection.LocalEndPoint.Port); } for (var port = 40000; port <= 65534; port += 2) { if (!usedPorts.Contains(port) && !usedPorts.Contains(port + 1)) { _clientRtpPort = port; _clientRtcpPort = port + 1; break; } } request.Headers.Add("Transport", string.Format("RTP/AVP;unicast;client_port={0}-{1}", _clientRtpPort, _clientRtcpPort)); break; } } else { request = new RtspRequest(RtspMethod.Setup, string.Format("rtsp://{0}:{1}/?{2}", _address, 554, query), 1, 0); switch (transporttype) { case "multicast": request.Headers.Add("Transport", string.Format("RTP/AVP;multicast")); break; case "unicast": request.Headers.Add("Transport", string.Format("RTP/AVP;unicast;client_port={0}-{1}", _clientRtpPort, _clientRtcpPort)); break; } } SendRequest(request); ReceiveResponse(out response); //if (_rtspClient.SendRequest(request, out response) != RtspStatusCode.Ok) //{ // Logger.Error("Failed to tune, non-OK RTSP SETUP status code {0} {1}", response.StatusCode, response.ReasonPhrase); //} if (!response.Headers.TryGetValue("com.ses.streamID", out _rtspStreamId)) { _logger.Error(string.Format("Failed to tune, not able to locate Stream ID header in RTSP SETUP response")); } string sessionHeader; if (!response.Headers.TryGetValue("Session", out sessionHeader)) { _logger.Error(string.Format("Failed to tune, not able to locate Session header in RTSP SETUP response")); } ProcessSessionHeader(sessionHeader, "Setup"); string transportHeader; if (!response.Headers.TryGetValue("Transport", out transportHeader)) { _logger.Error(string.Format("Failed to tune, not able to locate Transport header in RTSP SETUP response")); } ProcessTransportHeader(transportHeader); return response.StatusCode; } public RtspStatusCode Play(string query) { if ((_rtspSocket == null)) { Connect(); } //_rtspClient = new RtspClient(_rtspDevice.ServerAddress); RtspResponse response; string data; if (string.IsNullOrEmpty(query)) { data = string.Format("rtsp://{0}:{1}/stream={2}", _address, 554, _rtspStreamId); } else { data = string.Format("rtsp://{0}:{1}/stream={2}?{3}", _address, 554, _rtspStreamId, query); } var request = new RtspRequest(RtspMethod.Play, data, 1, 0); request.Headers.Add("Session", _rtspSessionId); SendRequest(request); ReceiveResponse(out response); //if (_rtspClient.SendRequest(request, out response) != RtspStatusCode.Ok) //{ // Logger.Error("Failed to tune, non-OK RTSP SETUP status code {0} {1}", response.StatusCode, response.ReasonPhrase); //} //Logger.Info("RtspSession-Play : \r\n {0}", response); string sessionHeader; if (!response.Headers.TryGetValue("Session", out sessionHeader)) { _logger.Error(string.Format("Failed to tune, not able to locate Session header in RTSP Play response")); } ProcessSessionHeader(sessionHeader, "Play"); string rtpinfoHeader; if (!response.Headers.TryGetValue("RTP-Info", out rtpinfoHeader)) { _logger.Error(string.Format("Failed to tune, not able to locate Rtp-Info header in RTSP Play response")); } return response.StatusCode; } public RtspStatusCode Options() { if ((_rtspSocket == null)) { Connect(); } //_rtspClient = new RtspClient(_rtspDevice.ServerAddress); RtspRequest request; RtspResponse response; if (string.IsNullOrEmpty(_rtspSessionId)) { request = new RtspRequest(RtspMethod.Options, string.Format("rtsp://{0}:{1}/", _address, 554), 1, 0); } else { request = new RtspRequest(RtspMethod.Options, string.Format("rtsp://{0}:{1}/", _address, 554), 1, 0); request.Headers.Add("Session", _rtspSessionId); } SendRequest(request); ReceiveResponse(out response); //if (_rtspClient.SendRequest(request, out response) != RtspStatusCode.Ok) //{ // Logger.Error("Failed to tune, non-OK RTSP SETUP status code {0} {1}", response.StatusCode, response.ReasonPhrase); //} //Logger.Info("RtspSession-Options : \r\n {0}", response); string sessionHeader; if (!response.Headers.TryGetValue("Session", out sessionHeader)) { _logger.Error(string.Format("Failed to tune, not able to locate session header in RTSP Options response")); } ProcessSessionHeader(sessionHeader, "Options"); string optionsHeader; if (!response.Headers.TryGetValue("Public", out optionsHeader)) { _logger.Error(string.Format("Failed to tune, not able to Options header in RTSP Options response")); } return response.StatusCode; } public RtspStatusCode Describe(out int level, out int quality) { if ((_rtspSocket == null)) { Connect(); } //_rtspClient = new RtspClient(_rtspDevice.ServerAddress); RtspRequest request; RtspResponse response; level = 0; quality = 0; if (string.IsNullOrEmpty(_rtspSessionId)) { request = new RtspRequest(RtspMethod.Describe, string.Format("rtsp://{0}:{1}/", _address, 554), 1, 0); request.Headers.Add("Accept", "application/sdp"); } else { request = new RtspRequest(RtspMethod.Describe, string.Format("rtsp://{0}:{1}/stream={2}", _address, 554, _rtspStreamId), 1, 0); request.Headers.Add("Accept", "application/sdp"); request.Headers.Add("Session", _rtspSessionId); } SendRequest(request); ReceiveResponse(out response); //if (_rtspClient.SendRequest(request, out response) != RtspStatusCode.Ok) //{ // Logger.Error("Failed to tune, non-OK RTSP Describe status code {0} {1}", response.StatusCode, response.ReasonPhrase); //} //Logger.Info("RtspSession-Describe : \r\n {0}", response); string sessionHeader; if (!response.Headers.TryGetValue("Session", out sessionHeader)) { _logger.Error(string.Format("Failed to tune, not able to locate session header in RTSP Describe response")); } ProcessSessionHeader(sessionHeader, "Describe"); var m = RegexDescribeResponseSignalInfo.Match(response.Body); if (m.Success) { //isSignalLocked = m.Groups[2].Captures[0].Value.Equals("1"); level = int.Parse(m.Groups[1].Captures[0].Value) * 100 / 255; // level: 0..255 => 0..100 quality = int.Parse(m.Groups[3].Captures[0].Value) * 100 / 15; // quality: 0..15 => 0..100 } /* v=0 o=- 1378633020884883 1 IN IP4 192.168.2.108 s=SatIPServer:1 4 t=0 0 a=tool:idl4k m=video 52780 RTP/AVP 33 c=IN IP4 0.0.0.0 b=AS:5000 a=control:stream=4 a=fmtp:33 ver=1.0;tuner=1,0,0,0,12344,h,dvbs2,,off,,22000,34;pids=0,100,101,102,103,106 =sendonly */ return response.StatusCode; } public RtspStatusCode TearDown() { if ((_rtspSocket == null)) { Connect(); } //_rtspClient = new RtspClient(_rtspDevice.ServerAddress); RtspResponse response; var request = new RtspRequest(RtspMethod.Teardown, string.Format("rtsp://{0}:{1}/stream={2}", _address, 554, _rtspStreamId), 1, 0); request.Headers.Add("Session", _rtspSessionId); SendRequest(request); ReceiveResponse(out response); //if (_rtspClient.SendRequest(request, out response) != RtspStatusCode.Ok) //{ // Logger.Error("Failed to tune, non-OK RTSP Teardown status code {0} {1}", response.StatusCode, response.ReasonPhrase); //} return response.StatusCode; } #endregion #region Public Events ////public event PropertyChangedEventHandler PropertyChanged; #endregion #region Protected Methods protected void OnPropertyChanged(string name) { //var handler = PropertyChanged; //if (handler != null) //{ // handler(this, new PropertyChangedEventArgs(name)); //} } #endregion public void Dispose() { Dispose(true); GC.SuppressFinalize(this);//Disconnect(); } protected virtual void Dispose(bool disposing) { if (!_disposed) { if (disposing) { TearDown(); Disconnect(); } } _disposed = true; } } }