diff --git a/skyscraper8/GS/GSE-BFBS/BfbsGseReader.cs b/skyscraper8/GS/GSE-BFBS/BfbsGseReader.cs index d97a751..6c6cb7f 100644 --- a/skyscraper8/GS/GSE-BFBS/BfbsGseReader.cs +++ b/skyscraper8/GS/GSE-BFBS/BfbsGseReader.cs @@ -120,6 +120,11 @@ namespace skyscraper8.GS.GSE_BFBS if (!startIndicator && endIndicator) gseLength -= 4; + if (span.GetAvailableBytes() < gseLength) + { + //broken gse packet, invalid length + return; + } gsePacket.GseDataBytes = span.ReadBytes(gseLength); if (!startIndicator && endIndicator) diff --git a/skyscraper8/Program.cs b/skyscraper8/Program.cs index 5facd7b..9ea9c0a 100644 --- a/skyscraper8/Program.cs +++ b/skyscraper8/Program.cs @@ -288,8 +288,17 @@ namespace skyscraper5 QuickAndDirtySatIpClient qadsipc = new QuickAndDirtySatIpClient(args); qadsipc.Run(); return; + } + + if (args[0].ToLowerInvariant().Equals("satip-playout")) + { + QuickAndDirtySatIpClient qadsipc = new QuickAndDirtySatIpClient(args); + qadsipc.SetPlayoutMode(args); + qadsipc.Run(); + return; } + if (args[0].ToLowerInvariant().Equals("shannon")) { if (args.Length != 2) @@ -392,6 +401,7 @@ namespace skyscraper5 Console.WriteLine(" or: .\\skyscraper8.exe \"C:\\path\\to\\file.ts\\"); Console.WriteLine(" or: .\\skyscraper8.exe \"C:\\path\\to\\my\\folder\\with\\ts\\files\\\" (for batch extraction)"); Console.WriteLine(" or: .\\skyscraper8.exe satip IP_ADDRESS DISEQC POLARITY FREQUENCY SYSTEM SYMBOL_RATE (see README file)"); + Console.WriteLine(" or: .\\skyscraper8.exe satip-playout IP_ADDRESS DISEQC POLARITY FREQUENCY SYSTEM SYMBOL_RATE DESTINATION_PORT"); Console.WriteLine(" or: .\\skyscraper8.exe pcap-on - to write a configuration file that allows PCAP writing during scraping."); Console.WriteLine(" or: .\\skyscraper8.exe pcap-off - to write a configuration file that turns off PCAP writing during scraping."); Console.WriteLine(" or: .\\skyscraper8.exe subts-on - to write a configuration file that allows extraction of nested TS."); diff --git a/skyscraper8/Properties/launchSettings.json b/skyscraper8/Properties/launchSettings.json index 6513d14..e263e51 100644 --- a/skyscraper8/Properties/launchSettings.json +++ b/skyscraper8/Properties/launchSettings.json @@ -2,6 +2,7 @@ "profiles": { "skyscraper8": { "commandName": "Project", + "commandLineArgs": "satip-playout auto 1 V 12597 S2 45000 6970", "remoteDebugEnabled": false }, "Container (Dockerfile)": { diff --git a/skyscraper8/QuickAndDirtySatIpClient.cs b/skyscraper8/QuickAndDirtySatIpClient.cs index 96d549d..046a7ac 100644 --- a/skyscraper8/QuickAndDirtySatIpClient.cs +++ b/skyscraper8/QuickAndDirtySatIpClient.cs @@ -74,6 +74,17 @@ namespace skyscraper8 symbolRate = Int32.Parse(args[6]); } + public void SetPlayoutMode(string[] args) + { + if (args.Length == 7) + { + logger.Fatal("What's the target port?"); + return; + } + destinationPort = int.Parse(args[7]); + playoutMode = true; + } + private IPAddress AutodetectIPAddress() { SsdpDevice firstSatIpServer = SsdpClient.GetFirstSatIpServer(1000, null); @@ -105,7 +116,7 @@ namespace skyscraper8 SessionDescriptionProtocol sessionDescriptionProtocol = describe.GetSessionDescriptionProtocol(); packetQueue = new Queue(); - RtspSetupResponse setup = rtspClient.GetSetup(url); + RtspSetupResponse setup = rtspClient.GetSetup(url, destinationPort); if (setup.RtspStatusCode == 404) { logger.Fatal("Your SAT>IP server doesn't have a tuner available that can talk to the requested frequency."); @@ -118,37 +129,59 @@ namespace skyscraper8 dataStorage = new InMemoryScraperStorage(); if (objectStorage == null) objectStorage = new FilesystemStorage(new DirectoryInfo(".")); - context = new SkyscraperContext(new TsContext(), dataStorage, objectStorage); - context.EnableTimeout = true; - context.TimeoutSeconds = 60; - context.InitalizeFilterChain(); + if (!playoutMode) + { + context = new SkyscraperContext(new TsContext(), dataStorage, objectStorage); + context.EnableTimeout = true; + context.TimeoutSeconds = 60; + context.InitalizeFilterChain(); + } RtspPlayResponse play = rtspClient.GetPlay(setup); DateTime lastTimestamp = DateTime.Now; + bool initMessagePrinted = false; while (true) { - if (packetQueue.Count >= 1) - { - byte[] buffer; - lock (packetQueue) - { - buffer = packetQueue.Dequeue(); - } - context.IngestSinglePacket(buffer); + if (playoutMode) + { + Thread.Sleep(1000); + Keepalive(url); + if (!initMessagePrinted) + { + logger.InfoFormat("Began SAT>IP playout to port {0}", destinationPort); + initMessagePrinted = true; + } } else - { - Thread.Sleep(1); - } - - if (context.IsAbortConditionMet()) - break; - - if (DateTime.Now.Second != lastTimestamp.Second) - { - Keepalive(url); - lastTimestamp = DateTime.Now; + { + if (packetQueue.Count >= 1) + { + byte[] buffer; + lock (packetQueue) + { + buffer = packetQueue.Dequeue(); + } + context.IngestSinglePacket(buffer); + if (!initMessagePrinted) + { + logger.InfoFormat("Began SAT>IP stream."); + initMessagePrinted = true; + } + } + else + { + Thread.Sleep(1); + } + + if (context.IsAbortConditionMet()) + break; + + if (DateTime.Now.Second != lastTimestamp.Second) + { + Keepalive(url); + lastTimestamp = DateTime.Now; + } } } @@ -199,8 +232,12 @@ namespace skyscraper8 fs.Write(buffer, 0, buffer.Length); } - private int rtcps; - private void Setup_OnRtcpPacket(byte[] data, int length) + private int rtcps; + private IPAddress destinationIp; + private int destinationPort; + private bool playoutMode; + + private void Setup_OnRtcpPacket(byte[] data, int length) { //rtcps++; } diff --git a/skyscraper8/SatIp/RtspClient.cs b/skyscraper8/SatIp/RtspClient.cs index cb14525..56335ba 100644 --- a/skyscraper8/SatIp/RtspClient.cs +++ b/skyscraper8/SatIp/RtspClient.cs @@ -1,347 +1,356 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Text; -using System.Threading.Tasks; -using skyscraper5.Skyscraper; -using skyscraper5.Skyscraper.IO.CrazycatStreamReader; -using skyscraper8.SatIp.RtspRequests; -using skyscraper8.SatIp.RtspResponses; - -namespace skyscraper8.SatIp -{ - internal class RtspClient : Validatable, IDisposable - { - private uint cseqCounter; - private const string USER_AGENT = "sophiaNetRtspClient/1.0"; - - public RtspClient(string ip, int port) - { - IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse(ip), port); - ConstructStep2(ipEndPoint); - } - - public RtspClient(IPAddress ip, int port) - { - IPEndPoint ipEndPoint = new IPEndPoint(ip, port); - ConstructStep2(ipEndPoint); - } - - private void ConstructStep2(IPEndPoint ipEndPoint) - { - this.TcpClient = new TcpClient(); - this.TcpClient.Connect(ipEndPoint); - this.RootPath = string.Format("rtsp://{0}:{1}", ipEndPoint.Address.ToString(), ipEndPoint.Port.ToString()); - this.NetworkStream = TcpClient.GetStream(); - this.BufferedStream = new BufferedStream(this.NetworkStream); - this.StreamReader = new StreamReader(this.BufferedStream); - this.StreamWriter = new StreamWriter(this.BufferedStream); - this.cseqCounter = 2; - this.ListenIp = GetListenIp(this.TcpClient.Client.LocalEndPoint); - RtspOptionsResponse rtspOptionsResponse = GetOptions("/"); - this.Valid = rtspOptionsResponse.Valid; - } - - public bool AutoReconnect { get; set; } - public IPAddress ListenIp { get; set; } - - private IPAddress GetListenIp(EndPoint clientLocalEndPoint) - { - IPEndPoint ipEndPoint = clientLocalEndPoint as IPEndPoint; - if (ipEndPoint == null) - { - throw new NotImplementedException(clientLocalEndPoint.GetType().Name); - } - - if (ipEndPoint.Address.AddressFamily == AddressFamily.InterNetwork) - { - return ipEndPoint.Address; - } - - if (ipEndPoint.Address.IsIPv4MappedToIPv6) - { - return ipEndPoint.Address.MapToIPv4(); - } - - throw new NotImplementedException(String.Format("Don't know whether I can listen on IP {0}", ipEndPoint.ToString())); - } - - public string RootPath { get; set; } - - private TcpClient TcpClient { get; set; } - private NetworkStream NetworkStream { get; set; } - private BufferedStream BufferedStream { get; set; } - private StreamReader StreamReader { get; set; } - private StreamWriter StreamWriter { get; set; } - - public RtspOptionsResponse GetOptions(string url) - { - RtspOptionsRequest request = new RtspOptionsRequest(); - request.RequestPath = url; - request.CSeq = cseqCounter++; - request.UserAgent = USER_AGENT; - RtspResponseHeader header = GetResponse(request.ListHeaders(RootPath)); - RtspOptionsResponse result = new RtspOptionsResponse(header); - return result; - } - - public RtspDescribeResponse GetDescribe(string url) - { - RtspDescribeRequest request = new RtspDescribeRequest(); - request.RequestPath = url; - request.CSeq = cseqCounter++; - request.UserAgent = USER_AGENT; - request.Accept = "application/sdp"; - RtspResponseHeader header = GetResponse(request.ListHeaders(RootPath)); - RtspDescribeResponse result = new RtspDescribeResponse(header); - return result; - } - - public RtspSetupResponse GetSetup(string url) - { - RtspSetupRequest request = new RtspSetupRequest(); - request.RequestPath = url; - request.CSeq = cseqCounter++; - request.UserAgent = USER_AGENT; - - Socket rtpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - rtpSocket.Bind(new IPEndPoint(ListenIp, 0)); - int rtpPort = GetPortFromEndPoint(rtpSocket.LocalEndPoint); - - Socket rtcpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - rtcpSocket.Bind(new IPEndPoint(ListenIp, 0)); - int rtcpPort = GetPortFromEndPoint(rtcpSocket.LocalEndPoint); - - request.SetRtpAvpUnicast(rtpPort, rtcpPort); - - RtspResponseHeader response = GetResponse(request.ListHeaders(RootPath)); - RtspSetupResponse setupResponse = new RtspSetupResponse(response); - switch (response.statusCode) - { - case 200: - setupResponse.RtpSocket = rtpSocket; - setupResponse.RtcpSocket = rtcpSocket; - setupResponse.SetupListeners(); - setupResponse.Valid = true; - break; - case 404: - setupResponse.Valid = true; - break; - default: - throw new NotImplementedException(setupResponse.RtspStatusCode.ToString()); - } - return setupResponse; - } - - public RtspPlayResponse GetPlay(RtspSetupResponse setupData) - { - RtspPlayRequest request = new RtspPlayRequest(); - request.RequestPath = string.Format("/stream={0}", setupData.StreamId); - request.Session = setupData.Session; - request.UserAgent = USER_AGENT; - - RtspPlayResponse response = new RtspPlayResponse(GetResponse(request.ListHeaders(RootPath))); - return response; - } - - public RtspTeardownResponse GetTeardown(RtspSetupResponse setupData) - { - RtspTeardownRequest request = new RtspTeardownRequest(); - request.RequestPath = string.Format("/stream={0}", setupData.StreamId); - request.Session = setupData.Session; - request.UserAgent = USER_AGENT; - - RtspTeardownResponse response = new RtspTeardownResponse(GetResponse(request.ListHeaders(RootPath))); - if (response.RtspStatusCode == 200) - { - setupData.InvokeCancellationTokens(); - if (AutoReconnect) - { - Reconnect(); - } - } - return response; - } - - private int GetPortFromEndPoint(EndPoint endpoint) - { - IPEndPoint ipEndPoint = endpoint as IPEndPoint; - if (ipEndPoint == null) - throw new NotImplementedException(endpoint.GetType().Name); - return ipEndPoint.Port; - } - - private RtspResponseHeader GetResponse(string request) - { - StreamWriter.Write(request); - StreamWriter.Flush(); - - RtspResponseHeader result = new RtspResponseHeader(); - - string response = StreamReader.ReadLine(); - if (!response.StartsWith("RTSP/")) - throw new RtspException("Invalid RTSP response."); - - response = response.Substring(5); - - string versionString = response.Substring(0, 3); - result.rtspVersion = Version.Parse(versionString); - response = response.Substring(4); - - string statusCodeString = response.Substring(0,3); - result.statusCode = ushort.Parse(statusCodeString); - - response = response.Substring(4); - result.statusLine = response; - - long contentLength = 0; - - result.kv = new Dictionary(); - while (true) - { - string lineIn = StreamReader.ReadLine(); - if (string.IsNullOrEmpty(lineIn)) - break; - - int indexOf = lineIn.IndexOf(": "); - string key = lineIn.Substring(0, indexOf); - string value = lineIn.Substring(indexOf + 2); - result.kv.Add(key, value); - - if (key.Equals("Content-Length")) - { - contentLength = long.Parse(value); - } - } - - if (contentLength > 0) - { - StreamReader.DiscardBufferedData(); - byte[] buffer = new byte[contentLength]; - int sucessfullyRead = BufferedStream.Read(buffer, 0, (int)contentLength); - if (sucessfullyRead != contentLength) - { - throw new IOException("incomplete read"); - } - - result.payload = buffer; - } - - return result; - } - - public void Reconnect() - { - EndPoint clientRemoteEndPoint = this.TcpClient.Client.RemoteEndPoint; - IPEndPoint ipEndPoint = clientRemoteEndPoint as IPEndPoint; - if (ipEndPoint == null) - { - throw new NotImplementedException(clientRemoteEndPoint.GetType().ToString()); - } - - this.TcpClient.Close(); - this.TcpClient = new TcpClient(); - this.TcpClient.Connect(ipEndPoint); - this.RootPath = string.Format("rtsp://{0}:{1}", ipEndPoint.Address, ipEndPoint.Port); - this.NetworkStream = TcpClient.GetStream(); - this.BufferedStream = new BufferedStream(this.NetworkStream); - this.StreamReader = new StreamReader(this.BufferedStream); - this.StreamWriter = new StreamWriter(this.BufferedStream); - this.cseqCounter = 2; - this.ListenIp = GetListenIp(this.TcpClient.Client.LocalEndPoint); - } - - /// - /// Generates a SAT>IP Tuning string. - /// - /// The DiSEqC Command to send. - /// The frequency to tune to in MHz. - /// Set this to true if tuning to DVB-S2/S2X, or false for DVB-S - /// The transponder's symbol rate in Ksyms. - /// Set this to true to force a STiD135 to BBFrame mode. Set this to false if you do not want this, or if the tuner isn't a STiD135. false is always safe here. - /// A SAT>IP Tuning String - /// Thrown when an invalid DiSEqC Command is supplied. - public static string MakeUrl(DiSEqC_Opcode diseqcChannel, int freq, bool isS2, int symbolrate, byte? mis = null, bool forceBbframeMode = false) - { - bool diseqcOk = false; - byte diseqc = 1; - if (diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_OPTION_A) && diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_POSITION_A)) - { - diseqc = 1; - diseqcOk = true; - } - if (diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_OPTION_A) && diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_POSITION_B)) - { - diseqc = 2; - diseqcOk = true; - } - if (diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_OPTION_B) && diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_POSITION_A)) - { - diseqc = 3; - diseqcOk = true; - } - if (diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_OPTION_B) && diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_POSITION_B)) - { - diseqc = 4; - diseqcOk = true; - } - if (!diseqcOk) - { - throw new ArgumentOutOfRangeException(nameof(diseqcChannel)); - } - - char pol; - if (diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_HORIZONTAL)) - { - pol = 'h'; - } - else - { - pol = 'v'; - } - - StringBuilder sb = new StringBuilder(); - sb.AppendFormat("/?src={0}", diseqc); - sb.AppendFormat("&freq={0}", freq); - sb.AppendFormat("&pol={0}", pol); - sb.AppendFormat("&msys={0}", isS2 ? "dvbs2" : "dvbs"); - sb.AppendFormat("&sr={0}", symbolrate); - sb.AppendFormat("&pids=all"); - - //Thanks to the Digital Devices Customer Support for giving me this advice. - if (forceBbframeMode) - { - sb.Append("&x_isi=0x80000000"); - } - else if (mis.HasValue) - { - sb.AppendFormat("&x_isi={0}", mis.Value); - } - - return sb.ToString(); - } - - private bool disposed; - public void Dispose() - { - if (disposed) - throw new ObjectDisposedException(nameof(RtspClient)); - - ListenIp = null; - RootPath = null; - if (TcpClient.Connected) - { - TcpClient.Close(); - } - - TcpClient.Dispose(); - NetworkStream = null; - BufferedStream = null; - StreamReader = null; - StreamWriter = null; - disposed = true; - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; +using skyscraper5.Skyscraper; +using skyscraper5.Skyscraper.IO.CrazycatStreamReader; +using skyscraper8.SatIp.RtspRequests; +using skyscraper8.SatIp.RtspResponses; + +namespace skyscraper8.SatIp +{ + internal class RtspClient : Validatable, IDisposable + { + private uint cseqCounter; + private const string USER_AGENT = "sophiaNetRtspClient/1.0"; + + public RtspClient(string ip, int port) + { + IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse(ip), port); + ConstructStep2(ipEndPoint); + } + + public RtspClient(IPAddress ip, int port) + { + IPEndPoint ipEndPoint = new IPEndPoint(ip, port); + ConstructStep2(ipEndPoint); + } + + private void ConstructStep2(IPEndPoint ipEndPoint) + { + this.TcpClient = new TcpClient(); + this.TcpClient.Connect(ipEndPoint); + this.RootPath = string.Format("rtsp://{0}:{1}", ipEndPoint.Address.ToString(), ipEndPoint.Port.ToString()); + this.NetworkStream = TcpClient.GetStream(); + this.BufferedStream = new BufferedStream(this.NetworkStream); + this.StreamReader = new StreamReader(this.BufferedStream); + this.StreamWriter = new StreamWriter(this.BufferedStream); + this.cseqCounter = 2; + this.ListenIp = GetListenIp(this.TcpClient.Client.LocalEndPoint); + RtspOptionsResponse rtspOptionsResponse = GetOptions("/"); + this.Valid = rtspOptionsResponse.Valid; + } + + public bool AutoReconnect { get; set; } + public IPAddress ListenIp { get; set; } + + private IPAddress GetListenIp(EndPoint clientLocalEndPoint) + { + IPEndPoint ipEndPoint = clientLocalEndPoint as IPEndPoint; + if (ipEndPoint == null) + { + throw new NotImplementedException(clientLocalEndPoint.GetType().Name); + } + + if (ipEndPoint.Address.AddressFamily == AddressFamily.InterNetwork) + { + return ipEndPoint.Address; + } + + if (ipEndPoint.Address.IsIPv4MappedToIPv6) + { + return ipEndPoint.Address.MapToIPv4(); + } + + throw new NotImplementedException(String.Format("Don't know whether I can listen on IP {0}", ipEndPoint.ToString())); + } + + public string RootPath { get; set; } + + private TcpClient TcpClient { get; set; } + private NetworkStream NetworkStream { get; set; } + private BufferedStream BufferedStream { get; set; } + private StreamReader StreamReader { get; set; } + private StreamWriter StreamWriter { get; set; } + + public RtspOptionsResponse GetOptions(string url) + { + RtspOptionsRequest request = new RtspOptionsRequest(); + request.RequestPath = url; + request.CSeq = cseqCounter++; + request.UserAgent = USER_AGENT; + RtspResponseHeader header = GetResponse(request.ListHeaders(RootPath)); + RtspOptionsResponse result = new RtspOptionsResponse(header); + return result; + } + + public RtspDescribeResponse GetDescribe(string url) + { + RtspDescribeRequest request = new RtspDescribeRequest(); + request.RequestPath = url; + request.CSeq = cseqCounter++; + request.UserAgent = USER_AGENT; + request.Accept = "application/sdp"; + RtspResponseHeader header = GetResponse(request.ListHeaders(RootPath)); + RtspDescribeResponse result = new RtspDescribeResponse(header); + return result; + } + + public RtspSetupResponse GetSetup(string url, int destinationPort = 0) + { + RtspSetupRequest request = new RtspSetupRequest(); + request.RequestPath = url; + request.CSeq = cseqCounter++; + request.UserAgent = USER_AGENT; + + int rtpPort = 0; + Socket rtpSocket = null; + if (destinationPort == 0) + { + rtpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + rtpSocket.Bind(new IPEndPoint(ListenIp, 0)); + rtpPort = GetPortFromEndPoint(rtpSocket.LocalEndPoint); + } + else + { + rtpPort = destinationPort; + } + + Socket rtcpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + rtcpSocket.Bind(new IPEndPoint(ListenIp, 0)); + int rtcpPort = GetPortFromEndPoint(rtcpSocket.LocalEndPoint); + + request.SetRtpAvpUnicast(rtpPort, rtcpPort); + + RtspResponseHeader response = GetResponse(request.ListHeaders(RootPath)); + RtspSetupResponse setupResponse = new RtspSetupResponse(response); + switch (response.statusCode) + { + case 200: + setupResponse.RtpSocket = rtpSocket; + setupResponse.RtcpSocket = rtcpSocket; + setupResponse.SetupListeners(); + setupResponse.Valid = true; + break; + case 404: + setupResponse.Valid = true; + break; + default: + throw new NotImplementedException(setupResponse.RtspStatusCode.ToString()); + } + return setupResponse; + } + + public RtspPlayResponse GetPlay(RtspSetupResponse setupData) + { + RtspPlayRequest request = new RtspPlayRequest(); + request.RequestPath = string.Format("/stream={0}", setupData.StreamId); + request.Session = setupData.Session; + request.UserAgent = USER_AGENT; + + RtspPlayResponse response = new RtspPlayResponse(GetResponse(request.ListHeaders(RootPath))); + return response; + } + + public RtspTeardownResponse GetTeardown(RtspSetupResponse setupData) + { + RtspTeardownRequest request = new RtspTeardownRequest(); + request.RequestPath = string.Format("/stream={0}", setupData.StreamId); + request.Session = setupData.Session; + request.UserAgent = USER_AGENT; + + RtspTeardownResponse response = new RtspTeardownResponse(GetResponse(request.ListHeaders(RootPath))); + if (response.RtspStatusCode == 200) + { + setupData.InvokeCancellationTokens(); + if (AutoReconnect) + { + Reconnect(); + } + } + return response; + } + + private int GetPortFromEndPoint(EndPoint endpoint) + { + IPEndPoint ipEndPoint = endpoint as IPEndPoint; + if (ipEndPoint == null) + throw new NotImplementedException(endpoint.GetType().Name); + return ipEndPoint.Port; + } + + private RtspResponseHeader GetResponse(string request) + { + StreamWriter.Write(request); + StreamWriter.Flush(); + + RtspResponseHeader result = new RtspResponseHeader(); + + string response = StreamReader.ReadLine(); + if (!response.StartsWith("RTSP/")) + throw new RtspException("Invalid RTSP response."); + + response = response.Substring(5); + + string versionString = response.Substring(0, 3); + result.rtspVersion = Version.Parse(versionString); + response = response.Substring(4); + + string statusCodeString = response.Substring(0,3); + result.statusCode = ushort.Parse(statusCodeString); + + response = response.Substring(4); + result.statusLine = response; + + long contentLength = 0; + + result.kv = new Dictionary(); + while (true) + { + string lineIn = StreamReader.ReadLine(); + if (string.IsNullOrEmpty(lineIn)) + break; + + int indexOf = lineIn.IndexOf(": "); + string key = lineIn.Substring(0, indexOf); + string value = lineIn.Substring(indexOf + 2); + result.kv.Add(key, value); + + if (key.Equals("Content-Length")) + { + contentLength = long.Parse(value); + } + } + + if (contentLength > 0) + { + StreamReader.DiscardBufferedData(); + byte[] buffer = new byte[contentLength]; + int sucessfullyRead = BufferedStream.Read(buffer, 0, (int)contentLength); + if (sucessfullyRead != contentLength) + { + throw new IOException("incomplete read"); + } + + result.payload = buffer; + } + + return result; + } + + public void Reconnect() + { + EndPoint clientRemoteEndPoint = this.TcpClient.Client.RemoteEndPoint; + IPEndPoint ipEndPoint = clientRemoteEndPoint as IPEndPoint; + if (ipEndPoint == null) + { + throw new NotImplementedException(clientRemoteEndPoint.GetType().ToString()); + } + + this.TcpClient.Close(); + this.TcpClient = new TcpClient(); + this.TcpClient.Connect(ipEndPoint); + this.RootPath = string.Format("rtsp://{0}:{1}", ipEndPoint.Address, ipEndPoint.Port); + this.NetworkStream = TcpClient.GetStream(); + this.BufferedStream = new BufferedStream(this.NetworkStream); + this.StreamReader = new StreamReader(this.BufferedStream); + this.StreamWriter = new StreamWriter(this.BufferedStream); + this.cseqCounter = 2; + this.ListenIp = GetListenIp(this.TcpClient.Client.LocalEndPoint); + } + + /// + /// Generates a SAT>IP Tuning string. + /// + /// The DiSEqC Command to send. + /// The frequency to tune to in MHz. + /// Set this to true if tuning to DVB-S2/S2X, or false for DVB-S + /// The transponder's symbol rate in Ksyms. + /// Set this to true to force a STiD135 to BBFrame mode. Set this to false if you do not want this, or if the tuner isn't a STiD135. false is always safe here. + /// A SAT>IP Tuning String + /// Thrown when an invalid DiSEqC Command is supplied. + public static string MakeUrl(DiSEqC_Opcode diseqcChannel, int freq, bool isS2, int symbolrate, byte? mis = null, bool forceBbframeMode = false) + { + bool diseqcOk = false; + byte diseqc = 1; + if (diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_OPTION_A) && diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_POSITION_A)) + { + diseqc = 1; + diseqcOk = true; + } + if (diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_OPTION_A) && diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_POSITION_B)) + { + diseqc = 2; + diseqcOk = true; + } + if (diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_OPTION_B) && diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_POSITION_A)) + { + diseqc = 3; + diseqcOk = true; + } + if (diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_OPTION_B) && diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_POSITION_B)) + { + diseqc = 4; + diseqcOk = true; + } + if (!diseqcOk) + { + throw new ArgumentOutOfRangeException(nameof(diseqcChannel)); + } + + char pol; + if (diseqcChannel.HasFlag(DiSEqC_Opcode.DISEQC_HORIZONTAL)) + { + pol = 'h'; + } + else + { + pol = 'v'; + } + + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("/?src={0}", diseqc); + sb.AppendFormat("&freq={0}", freq); + sb.AppendFormat("&pol={0}", pol); + sb.AppendFormat("&msys={0}", isS2 ? "dvbs2" : "dvbs"); + sb.AppendFormat("&sr={0}", symbolrate); + sb.AppendFormat("&pids=all"); + + //Thanks to the Digital Devices Customer Support for giving me this advice. + if (forceBbframeMode) + { + sb.Append("&x_isi=0x80000000"); + } + else if (mis.HasValue) + { + sb.AppendFormat("&x_isi={0}", mis.Value); + } + + return sb.ToString(); + } + + private bool disposed; + public void Dispose() + { + if (disposed) + throw new ObjectDisposedException(nameof(RtspClient)); + + ListenIp = null; + RootPath = null; + if (TcpClient.Connected) + { + TcpClient.Close(); + } + + TcpClient.Dispose(); + NetworkStream = null; + BufferedStream = null; + StreamReader = null; + StreamWriter = null; + disposed = true; + } + } +} diff --git a/skyscraper8/SatIp/RtspResponses/RtspSetupResponse.cs b/skyscraper8/SatIp/RtspResponses/RtspSetupResponse.cs index ebe5553..c364c12 100644 --- a/skyscraper8/SatIp/RtspResponses/RtspSetupResponse.cs +++ b/skyscraper8/SatIp/RtspResponses/RtspSetupResponse.cs @@ -53,23 +53,29 @@ namespace skyscraper8.SatIp.RtspResponses if (listenersStarted) throw new RtspException("Listener already started."); - rtpCancellation = new CancellationTokenSource(); + if (RtpSocket != null) + { + rtpCancellation = new CancellationTokenSource(); + } rtcpCancellation = new CancellationTokenSource(); - Task.Run(async () => - { - byte[] buffer = new byte[2048]; - - while (!rtpCancellation.IsCancellationRequested) - { - Array.Clear(buffer); - int result = await RtpSocket.ReceiveAsync(buffer, rtpCancellation.Token); - OnRtpPacket?.Invoke(buffer, result); - } - - exitedThread++; - } - ); + if (RtpSocket != null) + { + Task.Run(async () => + { + byte[] buffer = new byte[2048]; + + while (!rtpCancellation.IsCancellationRequested) + { + Array.Clear(buffer); + int result = await RtpSocket.ReceiveAsync(buffer, rtpCancellation.Token); + OnRtpPacket?.Invoke(buffer, result); + } + + exitedThread++; + } + ); + } Task.Run(async () => { @@ -92,8 +98,8 @@ namespace skyscraper8.SatIp.RtspResponses internal void InvokeCancellationTokens() { - rtpCancellation.Cancel(); - rtcpCancellation.Cancel(); + rtpCancellation?.Cancel(); + rtcpCancellation?.Cancel(); } public event OnRtpPacket OnRtpPacket;