jellyfin/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspSession.cs

689 lines
25 KiB
C#
Raw Normal View History

2016-03-31 13:04:07 -07:00
/*
Copyright (C) <2007-2016> <Kay Diefenthal>
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 <http://www.gnu.org/licenses/>.
*/
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;
2016-03-31 16:26:13 -07:00
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtsp
2016-03-31 13:04:07 -07:00
{
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;
2016-03-31 16:26:13 -07:00
private readonly ILogger _logger;
2016-03-31 13:04:07 -07:00
#endregion
#region Constructor
public RtspSession(string address, ILogger logger)
{
2016-03-31 16:26:13 -07:00
if (string.IsNullOrWhiteSpace(address))
{
throw new ArgumentNullException("address");
}
2016-03-31 13:04:07 -07:00
_address = address;
_logger = logger;
2016-03-31 16:26:13 -07:00
_logger.Info("Creating RtspSession with url {0}", address);
2016-03-31 13:04:07 -07:00
}
~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
/// <summary>
/// The LocalEndPoint Address
/// </summary>
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");
}
}
}
/// <summary>
/// The RemoteEndPoint Address
/// </summary>
public string Source
{
get { return _source; }
set
{
if (_source != value)
{
_source = value;
OnPropertyChanged("Source");
}
}
}
/// <summary>
/// The Media Data Delivery RemoteEndPoint Port if we use Unicast
/// </summary>
public int ServerRtpPort
{
get
{
return _serverRtpPort;
}
set { if (_serverRtpPort != value) { _serverRtpPort = value; OnPropertyChanged("ServerRtpPort"); } }
}
/// <summary>
/// The Media Metadata Delivery RemoteEndPoint Port if we use Unicast
/// </summary>
public int ServerRtcpPort
{
get { return _serverRtcpPort; }
set { if (_serverRtcpPort != value) { _serverRtcpPort = value; OnPropertyChanged("ServerRtcpPort"); } }
}
/// <summary>
/// The Media Data Delivery LocalEndPoint Port if we use Unicast
/// </summary>
public int ClientRtpPort
{
get { return _clientRtpPort; }
set { if (_clientRtpPort != value) { _clientRtpPort = value; OnPropertyChanged("ClientRtpPort"); } }
}
/// <summary>
/// The Media Metadata Delivery LocalEndPoint Port if we use Unicast
/// </summary>
public int ClientRtcpPort
{
get { return _clientRtcpPort; }
set { if (_clientRtcpPort != value) { _clientRtcpPort = value; OnPropertyChanged("ClientRtcpPort"); } }
}
/// <summary>
/// The Media Data Delivery RemoteEndPoint Port if we use Multicast
/// </summary>
public int RtpPort
{
get { return _rtpPort; }
set { if (_rtpPort != value) { _rtpPort = value; OnPropertyChanged("RtpPort"); } }
}
/// <summary>
/// The Media Meta Delivery RemoteEndPoint Port if we use Multicast
/// </summary>
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<int>();
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;
}
}
}