From 4f1c0c8b109548c7747bf013cc0df69ef08e9dd3 Mon Sep 17 00:00:00 2001 From: feyris-tan <4116042+feyris-tan@users.noreply.github.com> Date: Mon, 18 Aug 2025 22:34:18 +0200 Subject: [PATCH] Got an RTSP Client that works in theory. --- skyscraper8/Program.cs | 17 +++ skyscraper8/SatIp/RtspClient.cs | 68 +++++++++++ .../SatIp/RtspRequests/RtspPlayRequest.cs | 64 ++++++++++ .../SatIp/RtspRequests/RtspSetupRequest.cs | 57 +++++++++ .../SatIp/RtspRequests/RtspTeardownRequest.cs | 51 ++++++++ .../SatIp/RtspResponses/RtspPlayResponse.cs | 47 ++++++++ .../SatIp/RtspResponses/RtspSetupResponse.cs | 112 ++++++++++++++++++ .../RtspResponses/RtspTeardownResponse.cs | 35 ++++++ 8 files changed, 451 insertions(+) create mode 100644 skyscraper8/SatIp/RtspRequests/RtspPlayRequest.cs create mode 100644 skyscraper8/SatIp/RtspRequests/RtspSetupRequest.cs create mode 100644 skyscraper8/SatIp/RtspRequests/RtspTeardownRequest.cs create mode 100644 skyscraper8/SatIp/RtspResponses/RtspPlayResponse.cs create mode 100644 skyscraper8/SatIp/RtspResponses/RtspSetupResponse.cs create mode 100644 skyscraper8/SatIp/RtspResponses/RtspTeardownResponse.cs diff --git a/skyscraper8/Program.cs b/skyscraper8/Program.cs index 63db317..c134800 100644 --- a/skyscraper8/Program.cs +++ b/skyscraper8/Program.cs @@ -42,6 +42,23 @@ namespace skyscraper5 string url = RtspClient.MakeUrl(DiSEqC_Opcode.DISEQC_OPTION_A | DiSEqC_Opcode.DISEQC_POSITION_A | DiSEqC_Opcode.DISEQC_HORIZONTAL, 11954, true, 27500); RtspDescribeResponse describe = rtspClient.GetDescribe(url); SessionDescriptionProtocol sessionDescriptionProtocol = describe.GetSessionDescriptionProtocol(); + + int rtcps = 0; + int rtps = 0; + + RtspSetupResponse setup = rtspClient.GetSetup(url); + setup.OnRtcpPacket += ((data, length) => + rtcps++); + setup.OnRtpPacket += (data, length) => + rtps++; + + RtspPlayResponse play = rtspClient.GetPlay(setup); + + Thread.Sleep(5000); + + rtspClient.GetTeardown(setup); + Console.WriteLine("{0} RTCPs",rtcps); + Console.WriteLine("{0} RTPs",rtps); } static void Main(string[] args) diff --git a/skyscraper8/SatIp/RtspClient.cs b/skyscraper8/SatIp/RtspClient.cs index 337b3b9..5766855 100644 --- a/skyscraper8/SatIp/RtspClient.cs +++ b/skyscraper8/SatIp/RtspClient.cs @@ -87,6 +87,74 @@ namespace skyscraper8.SatIp 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); + if (response.statusCode == 200) + { + setupResponse.RtpSocket = rtpSocket; + setupResponse.RtcpSocket = rtcpSocket; + setupResponse.SetupListeners(); + setupResponse.Valid = true; + } + else + { + setupResponse.Valid = false; + 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(); + } + 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); diff --git a/skyscraper8/SatIp/RtspRequests/RtspPlayRequest.cs b/skyscraper8/SatIp/RtspRequests/RtspPlayRequest.cs new file mode 100644 index 0000000..834e528 --- /dev/null +++ b/skyscraper8/SatIp/RtspRequests/RtspPlayRequest.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper8.SatIp.RtspRequests +{ + internal class RtspPlayRequest : RtspRequest + { + public RtspPlayRequest() : base("PLAY") + { + Range = "npt=0.000-"; + } + + public uint CSeq + { + set + { + base.args["CSeq"] = Convert.ToString(value); + } + get + { + return uint.Parse(base.args["CSeq"]); + } + } + + public string UserAgent + { + set + { + base.args["User-Agent"] = value; + } + get + { + return base.args["User-Agent"]; + } + } + + public uint Session + { + set + { + base.args["Session"] = value.ToString(); + } + get + { + return uint.Parse(base.args["Session"]); + } + } + + public string Range + { + set + { + base.args["Range"] = value; + } + get + { + return base.args["Range"]; + } + } + } +} diff --git a/skyscraper8/SatIp/RtspRequests/RtspSetupRequest.cs b/skyscraper8/SatIp/RtspRequests/RtspSetupRequest.cs new file mode 100644 index 0000000..cb582bb --- /dev/null +++ b/skyscraper8/SatIp/RtspRequests/RtspSetupRequest.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper8.SatIp.RtspRequests +{ + internal class RtspSetupRequest : RtspRequest + { + public RtspSetupRequest() : base("SETUP") + { + } + + public uint CSeq + { + set + { + base.args["CSeq"] = Convert.ToString(value); + } + get + { + return uint.Parse(base.args["CSeq"]); + } + } + + public string UserAgent + { + set + { + base.args["User-Agent"] = value; + } + get + { + return base.args["User-Agent"]; + } + } + + public string Transport + { + set + { + base.args["Transport"] = value; + } + get + { + return base.args["Transport"]; + } + } + + public void SetRtpAvpUnicast(int rtpPort, int rtcpPort) + { + Transport = String.Format("RTP/AVP;unicast;client_port={0}-{1}", rtpPort, rtcpPort); + } + } + +} diff --git a/skyscraper8/SatIp/RtspRequests/RtspTeardownRequest.cs b/skyscraper8/SatIp/RtspRequests/RtspTeardownRequest.cs new file mode 100644 index 0000000..67b0b46 --- /dev/null +++ b/skyscraper8/SatIp/RtspRequests/RtspTeardownRequest.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper8.SatIp.RtspRequests +{ + internal class RtspTeardownRequest : RtspRequest + { + public RtspTeardownRequest() : base("TEARDOWN") + { + } + + public uint CSeq + { + set + { + base.args["CSeq"] = Convert.ToString(value); + } + get + { + return uint.Parse(base.args["CSeq"]); + } + } + + public string UserAgent + { + set + { + base.args["User-Agent"] = value; + } + get + { + return base.args["User-Agent"]; + } + } + + public uint Session + { + set + { + base.args["Session"] = value.ToString(); + } + get + { + return uint.Parse(base.args["Session"]); + } + } + } +} diff --git a/skyscraper8/SatIp/RtspResponses/RtspPlayResponse.cs b/skyscraper8/SatIp/RtspResponses/RtspPlayResponse.cs new file mode 100644 index 0000000..a789052 --- /dev/null +++ b/skyscraper8/SatIp/RtspResponses/RtspPlayResponse.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper8.SatIp.RtspResponses +{ + internal class RtspPlayResponse : RtspResponse + { + public RtspPlayResponse(RtspResponseHeader header) : base(header) + { + } + + public uint CSeq + { + get + { + return uint.Parse(base.args["CSeq"]); + } + } + + public uint Session + { + set + { + base.args["Session"] = value.ToString(); + } + get + { + return uint.Parse(base.args["Session"]); + } + } + + public string RtpInfo + { + set + { + base.args["RTP-Info"] = value.ToString(); + } + get + { + return base.args["RTP-Info"]; + } + } + } +} diff --git a/skyscraper8/SatIp/RtspResponses/RtspSetupResponse.cs b/skyscraper8/SatIp/RtspResponses/RtspSetupResponse.cs new file mode 100644 index 0000000..95f5ff2 --- /dev/null +++ b/skyscraper8/SatIp/RtspResponses/RtspSetupResponse.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper8.SatIp.RtspResponses +{ + internal class RtspSetupResponse : RtspResponse + { + public RtspSetupResponse(RtspResponseHeader header) : base(header) + { + } + + public Socket RtpSocket { get; set; } + public Socket RtcpSocket { get; set; } + + public uint CSeq + { + get + { + return uint.Parse(base.args["CSeq"]); + } + } + + public uint Session + { + get + { + string s = base.args["Session"]; + string[] strings = s.Split(';'); + return uint.Parse(strings[0]); + } + } + + public uint StreamId + { + get + { + return uint.Parse(base.args["com.ses.streamID"]); + } + } + + private byte exitedThread; + private CancellationTokenSource rtpCancellation; + private CancellationTokenSource rtcpCancellation; + private Thread rtpThread; + private Thread rtcpThread; + private bool listenersStarted; + internal void SetupListeners() + { + if (listenersStarted) + throw new RtspException("Listener already started."); + + rtpCancellation = new CancellationTokenSource(); + rtcpCancellation = new CancellationTokenSource(); + rtpThread = new Thread(RtpThread); + rtcpThread = new Thread(RtcpThread); + rtpThread.Start(); + rtcpThread.Start(); + listenersStarted = true; + } + + private void RtpThread() + { + 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++; + } + ); + } + + private void RtcpThread() + { + Task.Run(async () => + { + byte[] buffer = new byte[2048]; + while (!rtcpCancellation.IsCancellationRequested) + { + Array.Clear(buffer); + ValueTask task = RtcpSocket.ReceiveAsync(buffer, rtcpCancellation.Token); + int taskResult = task.Result; + OnRtcpPacket?.Invoke(buffer, taskResult); + } + + exitedThread++; + } + ); + } + + internal void InvokeCancellationTokens() + { + rtpCancellation.Cancel(); + rtcpCancellation.Cancel(); + } + + public event OnRtpPacket OnRtpPacket; + public event OnRtpPacket OnRtcpPacket; + } + + public delegate void OnRtpPacket(byte[] data, int length); +} diff --git a/skyscraper8/SatIp/RtspResponses/RtspTeardownResponse.cs b/skyscraper8/SatIp/RtspResponses/RtspTeardownResponse.cs new file mode 100644 index 0000000..b4a3ff4 --- /dev/null +++ b/skyscraper8/SatIp/RtspResponses/RtspTeardownResponse.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper8.SatIp.RtspResponses +{ + internal class RtspTeardownResponse : RtspResponse + { + public RtspTeardownResponse(RtspResponseHeader header) : base(header) + { + } + + public uint CSeq + { + get + { + return uint.Parse(base.args["CSeq"]); + } + } + + public uint Session + { + set + { + base.args["Session"] = value.ToString(); + } + get + { + return uint.Parse(base.args["Session"]); + } + } + } +}