### Create and Start RTSP Server Source: https://context7.com/ngraziano/sharprtsp/llms.txt Initializes the RTSP server, sets up the TCP listener, and starts accepting client connections. Requires Microsoft.Extensions.Logging and Rtsp namespaces. ```csharp using Microsoft.Extensions.Logging; using Rtsp; using Rtsp.Messages; using System; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Text; // Create RTSP server var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); var tcpListener = new TcpListener(IPAddress.Any, 554); var rtspListener = new RtspListenSocket(tcpListener, loggerFactory: loggerFactory); rtspListener.Start(); // Accept client connections var cancellation = new System.Threading.CancellationTokenSource(); while (!cancellation.IsCancellationRequested) { IRtspTransport clientTransport = await rtspListener.AcceptAsync(cancellation.Token); var clientListener = new RtspListener(clientTransport); clientListener.MessageReceived += (sender, e) => { var listener = (RtspListener)sender; var request = e.Message as RtspRequest; switch (request) { case RtspRequestOptions: // Reply with supported methods var optionsResponse = request.CreateResponse(); optionsResponse.Headers["Public"] = "OPTIONS, DESCRIBE, SETUP, PLAY, PAUSE, TEARDOWN"; listener.SendMessage(optionsResponse); break; case RtspRequestDescribe: // Return SDP var sdp = new StringBuilder(); sdp.Append("v=0\n"); sdp.Append("o=- 0 0 IN IP4 0.0.0.0\n"); sdp.Append("s=RTSP Server\n"); sdp.Append("m=video 0 RTP/AVP 96\n"); sdp.Append("a=rtpmap:96 H264/90000\n"); sdp.Append("a=control:trackID=0\n"); var describeResponse = request.CreateResponse(); describeResponse.Headers["Content-Type"] = "application/sdp"; describeResponse.Data = Encoding.UTF8.GetBytes(sdp.ToString()); describeResponse.AdjustContentLength(); listener.SendMessage(describeResponse); break; case RtspRequestSetup setupRequest: // Parse client's transport request var transports = setupRequest.GetTransports(); var clientTransportReq = transports[0]; // Create response transport var responseTransport = new RtspTransport { LowerTransport = clientTransportReq.LowerTransport, SSrc = "AABBCCDD" }; if (clientTransportReq.LowerTransport == RtspTransport.LowerTransportType.TCP) { responseTransport.Interleaved = clientTransportReq.Interleaved; } else { responseTransport.ClientPort = clientTransportReq.ClientPort; responseTransport.ServerPort = new PortCouple(6970, 6971); } var setupResponse = request.CreateResponse(); setupResponse.Headers["Transport"] = responseTransport.ToString(); setupResponse.Session = "12345678"; setupResponse.Timeout = 60; listener.SendMessage(setupResponse); break; case RtspRequestPlay: var playResponse = request.CreateResponse(); playResponse.Headers["Range"] = "npt=0-"; playResponse.Headers["RTP-Info"] = "url=rtsp://localhost:554/trackID=0;seq=1;rtptime=0"; listener.SendMessage(playResponse); // Start streaming... break; case RtspRequestTeardown: listener.SendMessage(request.CreateResponse()); listener.Stop(); break; } }; clientListener.Start(); } rtspListener.Stop(); ``` -------------------------------- ### RTSP Camera Server Example Source: https://github.com/ngraziano/sharprtsp/wiki/Home Start an RTSP server, generate a test YUV image, encode it as H264 video, and send it to multiple RTSP clients. Currently supports RTP over RTSP transport. ```csharp using System; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Net; using System.Net.Sockets; using System.Threading; using RtspServer; // Assuming RtspServer namespace is available namespace RtspCameraServerExample { class Program { static void Main(string[] args) { Console.WriteLine("SharpRTSP Camera Server Example"); // Configuration for the RTSP server IPAddress ipAddress = IPAddress.Any; int port = 8554; string streamPath = "/live.sdp"; RtspServer.RtspServer server = new RtspServer.RtspServer(ipAddress, port, streamPath); // Start the server in a separate thread Thread serverThread = new Thread(server.Start); serverThread.Start(); Console.WriteLine($"RTSP Server started on {ipAddress}:{port}{streamPath}"); Console.WriteLine("Press Enter to stop the server..."); Console.ReadLine(); server.Stop(); Console.WriteLine("Server stopped."); } } } ``` -------------------------------- ### Send RTSP SETUP Message Source: https://github.com/ngraziano/sharprtsp/blob/dotnetcore/README.md Configures a specific media stream (video or audio) by sending an RTSP SETUP request. Requires the 'control' URI from the SDP and specifies the transport mode. ```C# // the value of 'control' comes from parsing the SDP for the desired video or audio sub-stream Rtsp.Messages.RtspRequest setup_message = new Rtsp.Messages.RtspRequestSetup(); setup_message.RtspUri = new Uri(url + "/" + control); setup_message.AddHeader("Transport: RTP/AVP/TCP;interleaved=0"); rtsp_client.SendMessage(setup_message); // The reply will include the Session ``` -------------------------------- ### Send RTSP DESCRIBE Message Source: https://github.com/ngraziano/sharprtsp/blob/dotnetcore/README.md Sends an RTSP DESCRIBE request to retrieve session description (SDP) from the server. The 'control' URI for SETUP is obtained from the SDP reply. ```C# // send the Describe Rtsp.Messages.RtspRequest describe_message = new Rtsp.Messages.RtspRequestDescribe(); describe_message.RtspUri = new Uri(url); rtsp_client.SendMessage(describe_message); // The reply will include the SDP data ``` -------------------------------- ### RTSP Client Example Source: https://github.com/ngraziano/sharprtsp/wiki/Home Connect to IP cameras, authenticate, send RTSP commands, process SDP, and receive/write video and audio data. Supports various transports including UDP, TCP, and Multicast. ```csharp using System; using System.IO; using System.Net.Sockets; using System.Text; using System.Threading; namespace RtspClientExample { class Program { static void Main(string[] args) { Console.WriteLine("SharpRTSP Client Example"); // Example usage: // RtspClient client = new RtspClient("rtsp://username:password@192.168.1.100:554/stream1"); // RtspClient client = new RtspClient("rtsp://192.168.1.100:554/stream1"); RtspClient client = new RtspClient("rtsp://admin:password@192.168.0.100:554/Streaming/Channels/101"); try { client.Connect(); client.Describe(); client.Setup(); client.Play(); // Keep the client running to receive data Thread.Sleep(Timeout.Infinite); } catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); } finally { client.Disconnect(); } } } } ``` -------------------------------- ### Create and Start RTSP Listener Source: https://github.com/ngraziano/sharprtsp/blob/dotnetcore/README.md Initializes an RtspListener attached to a TCP socket. This listener handles sending RTSP messages and receiving replies and RTP data via events. ```C# // Connect a RTSP Listener to the TCP Socket to send messages and listen for replies rtsp_client = new Rtsp.RtspListener(tcp_socket); rtsp_client.MessageReceived += Rtsp_client_MessageReceived; rtsp_client.DataReceived += Rtsp_client_DataReceived; rtsp_client.Start(); // start reading messages from the server ``` -------------------------------- ### Complete RTSP Client Workflow Source: https://context7.com/ngraziano/sharprtsp/llms.txt This C# code implements a full RTSP client. It handles connection, OPTIONS, DESCRIBE, SETUP, PLAY, and TEARDOWN requests. It also processes incoming RTP data for H.264 video and saves it to a file. Requires Microsoft.Extensions.Logging and Rtsp libraries. ```csharp using Microsoft.Extensions.Logging; using Rtsp; using Rtsp.Messages; using Rtsp.Rtp; using Rtsp.Sdp; using System; using System.IO; using System.Linq; using System.Net; var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); var logger = loggerFactory.CreateLogger("RTSPClient"); string url = "rtsp://admin:password@192.168.1.100:554/stream1"; var uri = new Uri(url); var credentials = new NetworkCredential(uri.UserInfo.Split(':')[0], uri.UserInfo.Split(':')[1]); // Connect var transport = new RtspTcpTransport(new Uri($"rtsp://{uri.Host}:{uri.Port}{uri.PathAndQuery}")); var rtspClient = new RtspListener(transport, loggerFactory.CreateLogger()); Authentication? auth = null; string? sessionId = null; H264Payload? h264Processor = null; FileStream? videoFile = null; rtspClient.MessageReceived += async (sender, e) => { if (e.Message is not RtspResponse response) return; // Handle 401 Unauthorized if (response.ReturnCode == 401 && response.Headers.TryGetValue("WWW-Authenticate", out var wwwAuth)) { auth = Authentication.Create(credentials, wwwAuth); var retry = (RtspRequest)response.OriginalRequest!.Clone(); retry.AddAuthorization(auth, uri, transport.NextCommandIndex()); rtspClient.SendMessage(retry); return; } if (!response.IsOk) { logger.LogError($"Error: {response.ReturnCode}"); return; } switch (response.OriginalRequest) { case RtspRequestOptions: var describe = new RtspRequestDescribe { RtspUri = uri }; describe.Headers.Add("Accept", "application/sdp"); describe.AddAuthorization(auth, uri, transport.NextCommandIndex()); rtspClient.SendMessage(describe); break; case RtspRequestDescribe: using (var reader = new StreamReader(new MemoryStream(response.Data.ToArray()))) { var sdp = SdpFile.ReadLoose(reader); var videoMedia = sdp.Medias.FirstOrDefault(m => m.MediaType == Media.MediaTypes.video); if (videoMedia == null) { logger.LogError("No video"); return; } var control = videoMedia.Attributs.FirstOrDefault(a => a.Key == "control")?.Value ?? "trackID=0"; var trackUri = control.StartsWith("rtsp://") ? new Uri(control) : new Uri(uri, control); h264Processor = new H264Payload(loggerFactory.CreateLogger()); videoFile = new FileStream("output.h264", FileMode.Create); var setup = new RtspRequestSetup { RtspUri = trackUri }; setup.AddTransport(new RtspTransport { LowerTransport = RtspTransport.LowerTransportType.TCP, Interleaved = new PortCouple(0, 1) }); setup.AddAuthorization(auth, uri, transport.NextCommandIndex()); rtspClient.SendMessage(setup); } break; case RtspRequestSetup: sessionId = response.Session; var play = new RtspRequestPlay { RtspUri = uri, Session = sessionId }; play.Headers.Add("Range", "npt=0.000-"); play.AddAuthorization(auth, uri, transport.NextCommandIndex()); rtspClient.SendMessage(play); break; case RtspRequestPlay: logger.LogInformation("Streaming started"); break; } }; rtspClient.DataReceived += (sender, e) => { if (e.Message is not RtspData data) return; if (data.Channel != 0) { data.Dispose(); return; } // Only process RTP, not RTCP var rtpPacket = new RtpPacket(data.Data.Span); if (!rtpPacket.IsWellFormed) { data.Dispose(); return; } using var frame = h264Processor!.ProcessPacket(rtpPacket); if (frame.Any()) { foreach (var nalUnit in frame.Data) { videoFile!.Write(nalUnit.Span); } } data.Dispose(); }; rtspClient.Start(); // Send OPTIONS to start the flow var options = new RtspRequestOptions { RtspUri = uri }; rtspClient.SendMessage(options); // Run for a while await Task.Delay(TimeSpan.FromSeconds(30)); // Teardown var teardown = new RtspRequestTeardown { RtspUri = uri, Session = sessionId }; teardown.AddAuthorization(auth, uri, transport.NextCommandIndex()); rtspClient.SendMessage(teardown); videoFile?.Dispose(); rtspClient.Dispose(); ``` -------------------------------- ### Send RTSP PLAY Message Source: https://github.com/ngraziano/sharprtsp/blob/dotnetcore/README.md Initiates the media stream playback by sending an RTSP PLAY request. Requires the 'session' identifier obtained from the SETUP response. ```C# // the value of 'session' comes from the reply of the SETUP command Rtsp.Messages.RtspRequest play_message = new Rtsp.Messages.RtspRequestPlay(); play_message.RtspUri = new Uri(url); play_message.Session = session; rtsp_client.SendMessage(play_message); ``` -------------------------------- ### Server-Side RTSP Authentication Validation Source: https://context7.com/ngraziano/sharprtsp/llms.txt Illustrates how an RTSP server can validate incoming client Authorization headers. It shows the setup for Digest authentication on the server and how to check if a client's provided credentials and response are valid. ```csharp // For server-side: validate incoming authentication var digestAuth = new AuthenticationDigest( credentials, realm: "MyRTSPServer", nonce: Guid.NewGuid().ToString("N"), qop: "auth", algorithm: AuthenticationDigest.HashAlgorithm.SHA256 ); // Get WWW-Authenticate header value for 401 response string serverChallenge = digestAuth.GetServerResponse(); // Validate client's Authorization header bool isValid = digestAuth.IsValid(incomingRequest); ``` -------------------------------- ### Handle Incoming RTP Packets Source: https://github.com/ngraziano/sharprtsp/blob/dotnetcore/README.md Processes incoming RTP packets, identifying the start of a new video frame using the Marker Bit. This code prepares for de-packetization of compressed video data. ```C# List temporary_rtp_payloads = new List(); private void Rtsp_client_DataReceived(object sender, Rtsp.RtspChunkEventArgs e) { // RTP Packet Header // 0 - Version, P, X, CC, M, PT and Sequence Number //32 - Timestamp //64 - SSRC //96 - CSRCs (optional) //nn - Extension ID and Length //nn - Extension header int rtp_version = (e.Message.Data[0] >> 6); int rtp_padding = (e.Message.Data[0] >> 5) & 0x01; int rtp_extension = (e.Message.Data[0] >> 4) & 0x01; int rtp_csrc_count = (e.Message.Data[0] >> 0) & 0x0F; int rtp_marker = (e.Message.Data[1] >> 7) & 0x01; ``` -------------------------------- ### Handle RTSP Response Messages Source: https://github.com/ngraziano/sharprtsp/blob/dotnetcore/README.md Processes incoming RTSP response messages, determining the next action based on the original request type. This includes sending DESCRIBE after OPTIONS, processing SDP after DESCRIBE, and sending PLAY after SETUP. ```C# Rtsp.Messages.RtspResponse message = e.Message as Rtsp.Messages.RtspResponse; Console.WriteLine("Received " + message.OriginalRequest.ToString()); if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestOptions) { // send the DESCRIBE Rtsp.Messages.RtspRequest describe_message = new Rtsp.Messages.RtspRequestDescribe(); describe_message.RtspUri = new Uri(url); rtsp_client.SendMessage(describe_message); } if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestDescribe) { // Got a reply for DESCRIBE // Examine the SDP Console.Write(System.Text.Encoding.UTF8.GetString(message.Data)); Rtsp.Sdp.SdpFile sdp_data; using (StreamReader sdp_stream = new StreamReader(new MemoryStream(message.Data))) { sdp_data = Rtsp.Sdp.SdpFile.Read(sdp_stream); } // Process each 'Media' Attribute in the SDP. // If the attribute is for Video, then send a SETUP for (int x = 0; x < sdp_data.Medias.Count; x++) { if (sdp_data.Medias[x].GetMediaType() == Rtsp.Sdp.Media.MediaType.video) { // seach the atributes for control, fmtp and rtpmap String control = ""; // the "track" or "stream id" String fmtp = ""; // holds SPS and PPS String rtpmap = ""; // holds the Payload format, 96 is often used with H264 foreach (Rtsp.Sdp.Attribut attrib in sdp_data.Medias[x].Attributs) { if (attrib.Key.Equals("control")) control = attrib.Value; if (attrib.Key.Equals("fmtp")) fmtp = attrib.Value; if (attrib.Key.Equals("rtpmap")) rtpmap = attrib.Value; } // Get the Payload format number for the Video Stream String[] split_rtpmap = rtpmap.Split(' '); video_payload = 0; bool result = Int32.TryParse(split_rtpmap[0], out video_payload); // Send SETUP for the Video Stream // using Interleaved mode (RTP frames over the RTSP socket) Rtsp.Messages.RtspRequest setup_message = new Rtsp.Messages.RtspRequestSetup(); setup_message.RtspUri = new Uri(url + "/" + control); setup_message.AddHeader("Transport: RTP/AVP/TCP;interleaved=0"); rtsp_client.SendMessage(setup_message); } } } if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestSetup) { // Got Reply to SETUP Console.WriteLine("Got reply from Setup. Session is " + message.Session); String session = message.Session; // Session value used with Play, Pause, Teardown // Send PLAY Rtsp.Messages.RtspRequest play_message = new Rtsp.Messages.RtspRequestPlay(); play_message.RtspUri = new Uri(url); play_message.Session = session; rtsp_client.SendMessage(play_message); } if (message.OriginalRequest != null && message.OriginalRequest is Rtsp.Messages.RtspRequestPlay) { // Got Reply to PLAY Console.WriteLine("Got reply from Play " + message.Command); } } ``` -------------------------------- ### Parse Fragmentation Unit Header (FU-A) Source: https://github.com/ngraziano/sharprtsp/blob/dotnetcore/README.md Parses the header of a Fragmentation Unit Type A (FU-A) packet. This involves extracting the start marker, end marker, reserved bit, and the original NAL unit header type. This logic is crucial for reassembling fragmented NAL units. ```csharp // There are 4 types of Aggregation Packet (multiple NALs in one RTP packet) Console.WriteLine("Agg STAP-A not supported"); stap_a++; } else if (nal_header_type == 25) { // There are 4 types of Aggregation Packet (multiple NALs in one RTP packet) Console.WriteLine("Agg STAP-B not supported"); stap_b++; } else if (nal_header_type == 26) { // There are 4 types of Aggregation Packet (multiple NALs in one RTP packet) Console.WriteLine("Agg MTAP16 not supported"); mtap16++; } else if (nal_header_type == 27) { // There are 4 types of Aggregation Packet (multiple NALs in one RTP packet) Console.WriteLine("Agg MTAP24 not supported"); mtap24++; } else if (nal_header_type == 28) { Console.WriteLine("Fragmented Packet Type FU-A"); fu_a++; // Parse Fragmentation Unit Header int fu_header_s = (rtp_payloads[payload_index][1] >> 7) & 0x01; // start marker int fu_header_e = (rtp_payloads[payload_index][1] >> 6) & 0x01; // end marker int fu_header_r = (rtp_payloads[payload_index][1] >> 5) & 0x01; // reserved. should be 0 int fu_header_type = (rtp_payloads[payload_index][1] >> 0) & 0x1F; // Original NAL unit header Console.WriteLine("Frag FU-A s="+fu_header_s + "e="+fu_header_e); // Start Flag set if (fu_header_s == 1) { // Write 00 00 00 01 header fs.Write(nal_header, 0, nal_header.Length); // 0x00 0x00 0x00 0x01 // Modify the NAL Header that was at the start of the RTP packet // Keep the F and NRI flags but substitute the type field with the fu_header_type byte reconstructed_nal_type = (byte)((nal_header_nri << 5) + fu_header_type); fs.WriteByte(reconstructed_nal_type); // NAL Unit Type fs.Write(rtp_payloads[payload_index], 2, rtp_payloads[payload_index].Length - 2); // start after NAL Unit Type and FU Header byte } if (fu_header_s == 0) { // append this payload to the output NAL stream // Data starts after the NAL Unit Type byte and the FU Header byte fs.Write(rtp_payloads[payload_index], 2, rtp_payloads[payload_index].Length-2); // start after NAL Unit Type and FU Header byte } } else if (nal_header_type == 29) { Console.WriteLine("Fragmented Packet FU-B not supported"); fu_b++; } else { Console.WriteLine("Unknown NAL header " + nal_header_type); } } // ensure video is written to disk fs.Flush(true); // Print totals Console.WriteLine("Norm=" + norm + " ST-A=" + stap_a + " ST-B=" + stap_b + " M16=" + mtap16 + " M24=" + mtap24 + " FU-A=" + fu_a + " FU-B=" + fu_b); } ``` -------------------------------- ### Extract RTP Packet Data Source: https://github.com/ngraziano/sharprtsp/blob/dotnetcore/README.md Parses an incoming RTP packet to extract header information such as payload type, sequence number, timestamp, and SSRC. It also calculates the start of the RTP payload, accounting for CSRC counts and optional extension headers. ```C# int rtp_payload_type = (e.Message.Data[1] >> 0) & 0x7F; uint rtp_sequence_number = ((uint)e.Message.Data[2] << 8) + (uint)(e.Message.Data[3]); uint rtp_timestamp = ((uint)e.Message.Data[4] <<24) + (uint)(e.Message.Data[5] << 16) + (uint)(e.Message.Data[6] << 8) + (uint)(e.Message.Data[7]); uint rtp_ssrc = ((uint)e.Message.Data[8] << 24) + (uint)(e.Message.Data[9] << 16) + (uint)(e.Message.Data[10] << 8) + (uint)(e.Message.Data[11]); int rtp_payload_start = 4 // V,P,M,SEQ + 4 // time stamp + 4 // ssrc + (4 * rtp_csrc_count); // zero or more csrcs uint rtp_extension_id = 0; uint rtp_extension_size = 0; if (rtp_extension == 1) { rtp_extension_id = ((uint)e.Message.Data[rtp_payload_start + 0] << 8) + (uint)(e.Message.Data[rtp_payload_start + 1] << 0); rtp_extension_size = ((uint)e.Message.Data[rtp_payload_start + 2] << 8) + (uint)(e.Message.Data[rtp_payload_start + 3] << 0); rtp_payload_start += 4 + (int)rtp_extension_size; // extension header and extension payload } Console.WriteLine("RTP Data" + " V=" + rtp_version + " P=" + rtp_padding + " X=" + rtp_extension + " CC=" + rtp_csrc_count + " M=" + rtp_marker + " PT=" + rtp_payload_type + " Seq=" + rtp_sequence_number + " Time=" + rtp_timestamp + " SSRC=" + rtp_ssrc + " Size=" + e.Message.Data.Length); if (rtp_payload_type != video_payload) { Console.WriteLine("Ignoring this RTP payload"); return; // ignore this data } // If rtp_marker is '1' then this is the final transmission for this packet. // If rtp_marker is '0' we need to accumulate data with the same timestamp // ToDo - Check Timestamp matches // Add to the tempoary_rtp List byte[] rtp_payload = new byte[e.Message.Data.Length - rtp_payload_start]; // payload with RTP header removed System.Array.Copy(e.Message.Data, rtp_payload_start, rtp_payload, 0, rtp_payload.Length); // copy payload temporary_rtp_payloads.Add(rtp_payload); if (rtp_marker == 1) { // Process the RTP frame Process_RTP_Frame(temporary_rtp_payloads); temporary_rtp_payloads.Clear(); } ``` -------------------------------- ### Connect to RTSP Server (TCP) Source: https://github.com/ngraziano/sharprtsp/blob/dotnetcore/README.md Establishes a TCP socket connection to an RTSP server. This is the initial step for a TCP mode RTSP/RTP session. ```C# // Connect to a RTSP Server tcp_socket = new Rtsp.RtspTcpTransport(host,port); if (tcp_socket.Connected == false) { Console.WriteLine("Error - did not connect"); return; } ``` -------------------------------- ### Parse RTP Packet and Process Payload in C# Source: https://context7.com/ngraziano/sharprtsp/llms.txt Demonstrates parsing an RTP packet from raw data, accessing header fields, and processing payload for H.264, H.265, G.711, and AAC codecs. Ensure correct SDP configuration for AAC. ```csharp using Rtsp.Rtp; using System; using System.Collections.Generic; // Parse RTP packet from received data byte[] receivedData = new byte[1400]; // From UDP or TCP interleaved var rtpPacket = new RtpPacket(receivedData); // Check packet validity if (!rtpPacket.IsWellFormed) { Console.WriteLine("Invalid RTP packet"); return; } // Access header fields Console.WriteLine($"Version: {rtpPacket.Version}"); Console.WriteLine($"Payload type: {rtpPacket.PayloadType}"); Console.WriteLine($"Sequence number: {rtpPacket.SequenceNumber}"); Console.WriteLine($"Timestamp: {rtpPacket.Timestamp}"); Console.WriteLine($"SSRC: {rtpPacket.Ssrc}"); Console.WriteLine($"Marker bit: {rtpPacket.IsMarker}"); Console.WriteLine($"Has extension: {rtpPacket.HasExtension}"); Console.WriteLine($"Payload size: {rtpPacket.PayloadSize}"); // Access payload data ReadOnlySpan payload = rtpPacket.Payload; ReadOnlySpan extension = rtpPacket.Extension; // Process H.264 video var h264Processor = new H264Payload(); RawMediaFrame frame = h264Processor.ProcessPacket(rtpPacket); if (frame.Any()) // Frame complete (marker bit was set) { Console.WriteLine($"Complete frame with {frame.Data.Count} NAL units"); Console.WriteLine($"RTP timestamp: {frame.RtpTimestamp}"); Console.WriteLine($"Clock timestamp: {frame.ClockTimestamp}"); // Write NAL units to file (each includes 00 00 00 01 start code) using var fileStream = new System.IO.FileStream("video.h264", System.IO.FileMode.Append); foreach (var nalUnit in frame.Data) { fileStream.Write(nalUnit.Span); } // Dispose frame to return memory to pool frame.Dispose(); } // Process H.265/HEVC video bool hasDonl = false; // Check SDP fmtp for sprop-max-don-diff var h265Processor = new H265Payload(hasDonl); RawMediaFrame h265Frame = h265Processor.ProcessPacket(rtpPacket); // Process G.711 audio var g711Processor = new G711Payload(); RawMediaFrame audioFrame = g711Processor.ProcessPacket(rtpPacket); // Process AAC audio (requires config from SDP fmtp) string aacConfig = "1210"; // From SDP fmtp config parameter var aacProcessor = new AACPayload(aacConfig); RawMediaFrame aacFrame = aacProcessor.ProcessPacket(rtpPacket); ``` -------------------------------- ### Send RTP Video Packets (C#) Source: https://context7.com/ngraziano/sharprtsp/llms.txt This C# code demonstrates how to construct and send RTP packets containing H.264 NAL units. It handles both single NAL unit packets and fragmentation using FU-A for larger NAL units. Ensure Rtsp and Rtsp.Rtp namespaces are available. The code sends packets via TCP interleaved or UDP. ```csharp using Rtsp; using Rtsp.Rtp; using System; using System.Buffers; using System.Collections.Generic; // Video parameters const uint ssrc = 0x12345678; const int payloadType = 96; ushort sequenceNumber = 0; uint rtpTimestamp = 0; // NAL units to send (without start codes) byte[] spsNal = Convert.FromBase64String("Z0LAHtkAoD2Q"); // Example SPS byte[] ppsNal = Convert.FromBase64String("aM48gA=="); // Example PPS List nalUnits = new() { spsNal, ppsNal }; // Add frame NALs // Build RTP packets for NAL units foreach (var nal in nalUnits) { bool isLastNal = nal == nalUnits[^1]; if (nal.Length <= 1400) // Single NAL unit packet { var rtpPacket = MemoryPool.Shared.Rent(12 + nal.Length); var packet = rtpPacket.Memory[..(12 + nal.Length)]; // Write RTP header RtpPacketUtil.WriteHeader(packet.Span, version: 2, padding: false, extension: false, csrcCount: 0, marker: isLastNal, payloadType: payloadType); RtpPacketUtil.WriteSequenceNumber(packet.Span, sequenceNumber++); RtpPacketUtil.WriteTimestamp(packet.Span, rtpTimestamp); RtpPacketUtil.WriteSSRC(packet.Span, ssrc); // Copy NAL unit nal.CopyTo(packet[12..]); // Send via TCP interleaved await rtspListener.SendDataAsync(channel: 0, packet); // Or send via UDP await udpSocket.WriteToDataPortAsync(packet); rtpPacket.Dispose(); } else // Fragmentation Unit A (FU-A) { byte nalHeader = nal[0]; byte fuIndicator = (byte)((nalHeader & 0xE0) | 28); // FU-A type int dataOffset = 1; int remaining = nal.Length - 1; bool isStart = true; while (remaining > 0) { int chunkSize = Math.Min(remaining, 1398); // Leave room for FU header bool isEnd = remaining <= chunkSize; var rtpPacket = MemoryPool.Shared.Rent(12 + 2 + chunkSize); var packet = rtpPacket.Memory[..(12 + 2 + chunkSize)]; // RTP header RtpPacketUtil.WriteHeader(packet.Span, 2, false, false, 0, marker: isLastNal && isEnd, payloadType: payloadType); RtpPacketUtil.WriteSequenceNumber(packet.Span, sequenceNumber++); RtpPacketUtil.WriteTimestamp(packet.Span, rtpTimestamp); RtpPacketUtil.WriteSSRC(packet.Span, ssrc); // FU-A header packet.Span[12] = fuIndicator; byte fuHeader = (byte)((isStart ? 0x80 : 0) | (isEnd ? 0x40 : 0) | (nalHeader & 0x1F)); packet.Span[13] = fuHeader; // NAL data fragment nal.AsSpan(dataOffset, chunkSize).CopyTo(packet[14..].Span); await rtspListener.SendDataAsync(channel: 0, packet); rtpPacket.Dispose(); dataOffset += chunkSize; remaining -= chunkSize; isStart = false; } } } // Increment timestamp for next frame (90kHz clock) rtpTimestamp += 3600; // 40ms at 90kHz = 3600 ``` -------------------------------- ### Send RTSP OPTIONS Message Source: https://github.com/ngraziano/sharprtsp/blob/dotnetcore/README.md Constructs and sends an RTSP OPTIONS request to the server. This is typically one of the first messages exchanged. ```C# Rtsp.Messages.RtspRequest options_message = new Rtsp. Messages.RtspRequestOptions(); options_message.RtspUri = new Uri(url); rtsp_client.SendMessage(options_message); ``` -------------------------------- ### Client-Side RTSP Authentication Source: https://context7.com/ngraziano/sharprtsp/llms.txt Demonstrates how a client can create and add an Authorization header to RTSP requests after receiving a 401 Unauthorized response. It covers parsing the WWW-Authenticate header and generating the correct response for Basic or Digest authentication. ```csharp using Rtsp; using Rtsp.Messages; using System.Net; // Create credentials var credentials = new NetworkCredential("admin", "secretpassword"); // Parse WWW-Authenticate header from 401 response // Basic: Basic realm="IPCamera" // Digest: Digest realm="IPCamera", nonce="abc123def456", qop="auth", algorithm=SHA-256 string wwwAuthHeader = response.Headers["WWW-Authenticate"]; Authentication auth = Authentication.Create(credentials, wwwAuthHeader); // Add authorization to subsequent requests var playRequest = new RtspRequestPlay { RtspUri = new Uri("rtsp://camera.local/stream"), Session = sessionId }; uint nonceCounter = 1; // Increment for each request with same nonce string uri = "/stream"; string method = "PLAY"; byte[] entityBody = Array.Empty(); string authHeader = auth.GetResponse(nonceCounter, uri, method, entityBody); playRequest.Headers["Authorization"] = authHeader; ``` -------------------------------- ### Establish TCP Connection to RTSP Server Source: https://context7.com/ngraziano/sharprtsp/llms.txt Connects to an RTSP server using a URI and provides access to the network stream for message exchange. Handles connection status and provides methods for reconnecting and cleanup. ```csharp using Rtsp; using System; // Connect to RTSP server using URI var uri = new Uri("rtsp://192.168.1.100:554/stream1"); var tcpTransport = new RtspTcpTransport(uri); // Check connection status if (tcpTransport.Connected) { Console.WriteLine($"Connected to {tcpTransport.RemoteEndPoint}"); Console.WriteLine($"Local endpoint: {tcpTransport.LocalEndPoint}"); // Get the network stream for message exchange var stream = tcpTransport.GetStream(); // Get next command index for CSeq uint commandIndex = tcpTransport.NextCommandIndex(); } // Reconnect if connection was lost if (!tcpTransport.Connected) { tcpTransport.Reconnect(); } // Clean up tcpTransport.Close(); tcpTransport.Dispose(); ``` -------------------------------- ### Create RTSP Request Messages in C# Source: https://context7.com/ngraziano/sharprtsp/llms.txt Use typed request classes for standard RTSP methods. Each class provides properties for method-specific parameters and sets appropriate defaults. Authentication can be added to any request. ```csharp using Rtsp; using Rtsp.Messages; using System; using System.Net; var uri = new Uri("rtsp://192.168.1.100:554/stream1"); // OPTIONS - Query server capabilities var optionsRequest = new RtspRequestOptions { RtspUri = uri }; // DESCRIBE - Get media description (SDP) var describeRequest = new RtspRequestDescribe { RtspUri = uri }; describeRequest.Headers.Add("Accept", "application/sdp"); // SETUP - Configure transport for a media stream var setupRequest = new RtspRequestSetup { RtspUri = new Uri(uri, "trackID=0") // Video track }; // TCP interleaved transport var tcpTransport = new RtspTransport { LowerTransport = RtspTransport.LowerTransportType.TCP, Interleaved = new PortCouple(0, 1) // RTP on 0, RTCP on 1 }; setupRequest.AddTransport(tcpTransport); // UDP unicast transport var udpTransport = new RtspTransport { LowerTransport = RtspTransport.LowerTransportType.UDP, IsMulticast = false, ClientPort = new PortCouple(50000, 50001) }; // PLAY - Start streaming var playRequest = new RtspRequestPlay { RtspUri = uri, Session = "12345678" // Session from SETUP response }; playRequest.Headers.Add("Range", "npt=0.000-"); // PAUSE - Pause streaming var pauseRequest = new RtspRequestPause { RtspUri = uri, Session = "12345678" }; // TEARDOWN - End session var teardownRequest = new RtspRequestTeardown { RtspUri = uri, Session = "12345678" }; // GET_PARAMETER - Keepalive or query parameters var getParamRequest = new RtspRequestGetParameter { RtspUri = uri, Session = "12345678" }; // Add authentication to any request var credentials = new NetworkCredential("admin", "password"); var authHeader = "Digest realm=\"IPCamera\", nonce=\"abc123\"" ; var auth = Authentication.Create(credentials, authHeader); playRequest.AddAuthorization(auth, uri, nonceCounter: 1); ``` -------------------------------- ### Access Session-Level SDP Information Source: https://context7.com/ngraziano/sharprtsp/llms.txt Retrieves and prints session-level details such as the session name, origin, and connection information from a parsed SdpFile object. The connection information is optional and checked for null. ```csharp // Access session-level information Console.WriteLine($"Session: {sdp.Session}"); Console.WriteLine($"Origin: {sdp.Origin}"); if (sdp.Connection != null) { Console.WriteLine($"Connection: {sdp.Connection}"); } ``` -------------------------------- ### Parse SDP from RTSP DESCRIBE Response Source: https://context7.com/ngraziano/sharprtsp/llms.txt Parses Session Description Protocol (SDP) data from a DESCRIBE response using SdpFile.ReadLoose for better compatibility or SdpFile.ReadStrict for RFC compliance. Ensure the response data is available as a byte array. ```csharp using Rtsp.Sdp; using System; using System.IO; using System.Linq; using System.Text; // Parse SDP from DESCRIBE response byte[] sdpBytes = response.Data.ToArray(); SdpFile sdp; using (var reader = new StreamReader(new MemoryStream(sdpBytes))) { // Use ReadLoose for better compatibility with non-standard cameras sdp = SdpFile.ReadLoose(reader); // Or use ReadStrict for RFC-compliant parsing // sdp = SdpFile.ReadStrict(reader); } ``` -------------------------------- ### Process Audio Media Streams from SDP Source: https://context7.com/ngraziano/sharprtsp/llms.txt Iterates through audio media streams in the parsed SDP, extracting payload type and audio codec information. Requires filtering media by MediaType.audio. ```csharp // Process audio media streams foreach (var media in sdp.Medias.Where(m => m.MediaType == Media.MediaTypes.audio)) { Console.WriteLine($"Audio stream, payload type: {media.PayloadType}"); var rtpmap = media.Attributs.FirstOrDefault(a => a.Key == "rtpmap") as AttributRtpMap; Console.WriteLine($"Audio codec: {rtpmap?.EncodingName}"); } ``` -------------------------------- ### Handle RTSP Messages and RTP Data with RtspListener Source: https://context7.com/ngraziano/sharprtsp/llms.txt Wraps a transport connection to provide event-driven RTSP message and interleaved RTP data handling. Manages session state and CSeq sequencing. Supports auto-reconnect and sending messages/data. ```csharp using Rtsp; using Rtsp.Messages; using System; // Create transport and listener var transport = new RtspTcpTransport(new Uri("rtsp://camera.local:554/live")); var rtspClient = new RtspListener(transport); // Set up event handlers rtspClient.MessageReceived += (sender, e) => { if (e.Message is RtspResponse response) { Console.WriteLine($"Received response: {response.ReturnCode} {response.ReturnMessage}"); Console.WriteLine($"Original request: {response.OriginalRequest?.GetType().Name}"); Console.WriteLine($"Session: {response.Session}"); // Check if response is successful if (response.IsOk) { Console.WriteLine("Request succeeded"); } } else if (e.Message is RtspRequest request) { // Server-initiated request (rare) Console.WriteLine($"Server request: {request.RequestTyped}"); } }; rtspClient.DataReceived += (sender, e) => { // Interleaved RTP/RTCP data received (TCP mode) var data = e.Message as RtspData; Console.WriteLine($"RTP data on channel {data?.Channel}, size: {data?.Data.Length}"); }; // Start listening for messages rtspClient.Start(); // Enable auto-reconnect rtspClient.AutoReconnect = true; // Send messages (CSeq automatically managed) var optionsMessage = new RtspRequestOptions { RtspUri = new Uri("rtsp://camera.local:554/live") }; bool sent = rtspClient.SendMessage(optionsMessage); // Send interleaved RTP data (for TCP mode) byte[] rtpPacket = new byte[1400]; await rtspClient.SendDataAsync(channel: 0, rtpPacket); // Stop and clean up rtspClient.Stop(); rtspClient.Dispose(); ``` -------------------------------- ### Process Video Media Streams from SDP Source: https://context7.com/ngraziano/sharprtsp/llms.txt Iterates through video media streams in the parsed SDP, extracting payload type, control URI, and codec information (encoding name, clock rate). Requires filtering media by MediaType.video. ```csharp // Process video media streams foreach (var media in sdp.Medias.Where(m => m.MediaType == Media.MediaTypes.video)) { Console.WriteLine($"Video stream, payload type: {media.PayloadType}"); // Get control URI for SETUP var controlAttr = media.Attributs.FirstOrDefault(a => a.Key == "control"); string? trackUri = controlAttr?.Value; Console.WriteLine($"Control URI: {trackUri}"); // Get codec information from rtpmap var rtpmap = media.Attributs.FirstOrDefault(a => a.Key == "rtpmap") as AttributRtpMap; if (rtpmap != null) { Console.WriteLine($"Codec: {rtpmap.EncodingName}"); Console.WriteLine($"Clock rate: {rtpmap.ClockRate}"); } // Get format parameters (SPS/PPS for H.264, VPS/SPS/PPS for H.265) var fmtp = media.Attributs.FirstOrDefault(a => a.Key == "fmtp") as AttributFmtp; if (fmtp != null && rtpmap?.EncodingName?.ToUpper() == "H264") { var h264Params = H264Parameters.Parse(fmtp.FormatParameter); foreach (var nalUnit in h264Params.SpropParameterSets) { Console.WriteLine($"NAL unit (base64): {Convert.ToBase64String(nalUnit)}"); } } else if (fmtp != null && rtpmap?.EncodingName?.ToUpper() == "H265") { var h265Params = H265Parameters.Parse(fmtp.FormatParameter); Console.WriteLine($"VPS count: {h265Params.VideoParameterSet.Count}"); Console.WriteLine($"SPS count: {h265Params.SequenceParameterSet.Count}"); Console.WriteLine($"PPS count: {h265Params.PictureParameterSet.Count}"); } } ```