Skip to content

Commit

Permalink
Make a generic interface for reception of message in rtp
Browse files Browse the repository at this point in the history
  • Loading branch information
ngraziano committed Mar 11, 2024
1 parent 485d7b3 commit cf26bff
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 73 deletions.
5 changes: 5 additions & 0 deletions RTSP/IRtpTransport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ namespace Rtsp
{
public interface IRtpTransport : IDisposable
{
event EventHandler<RtspDataEventArgs>? DataReceived;
event EventHandler<RtspDataEventArgs>? ControlReceived;

void Start();
void Stop();
void WriteToControlPort(ReadOnlySpan<byte> data);
void WriteToDataPort(ReadOnlySpan<byte> data);
}
Expand Down
40 changes: 36 additions & 4 deletions RTSP/RtpTcpTransport.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using Rtsp.Messages;
using System;

namespace Rtsp
{
Expand All @@ -7,8 +8,13 @@ public class RtpTcpTransport : IRtpTransport
private bool disposedValue;
private readonly RtspListener rtspListener;

public int ControlChannel { get; set; }
public int DataChannel { get; set; }
public event EventHandler<RtspDataEventArgs>? DataReceived;
public event EventHandler<RtspDataEventArgs>? ControlReceived;

public int ControlChannel { get; set; } = int.MaxValue;
public int DataChannel { get; set; } = int.MaxValue;

public PortCouple Channels => new(ControlChannel, DataChannel);

public RtpTcpTransport(RtspListener rtspListener)
{
Expand All @@ -31,7 +37,7 @@ protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// TODO: supprimer l'état managé (objets managés)
Stop();
}
disposedValue = true;
}
Expand All @@ -43,5 +49,31 @@ public void Dispose()
Dispose(disposing: true);
GC.SuppressFinalize(this);
}

public void Start()
{
rtspListener.DataReceived += RtspListenerDataReceived;
}

public void Stop()
{
rtspListener.DataReceived -= RtspListenerDataReceived;
}
private void RtspListenerDataReceived(object? sender, RtspChunkEventArgs e)
{
if (e.Message is RtspData dataMessage && !dataMessage.Data.IsEmpty)
{
if (dataMessage.Channel == ControlChannel)
{
ControlReceived?.Invoke(this, new RtspDataEventArgs(dataMessage));
}
else if (dataMessage.Channel == DataChannel)
{
DataReceived?.Invoke(this, new RtspDataEventArgs(dataMessage));
}
}
}


}
}
133 changes: 64 additions & 69 deletions RtspClientExample/RTSPClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Rtsp.Sdp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
Expand Down Expand Up @@ -38,8 +39,10 @@ private enum RTSP_STATUS { WaitingToConnect, Connecting, ConnectFailed, Connecte
// this wraps around a the RTSP tcp_socket stream
RtspListener? rtspClient;
RTP_TRANSPORT rtpTransport = RTP_TRANSPORT.UDP; // Mode, either RTP over UDP or RTP over TCP using the RTSP socket
UDPSocket? videoUdpPair; // Pair of UDP ports used in RTP over UDP mode or in MULTICAST mode
UDPSocket? audioUdpPair; // Pair of UDP ports used in RTP over UDP mode or in MULTICAST mode
// Communication for the RTP (video and audio)
IRtpTransport? videoRtpTransport;
IRtpTransport? audioRtpTransport;

Uri? _uri; // RTSP URI (username & password will be stripped out
string session = ""; // RTSP Session
private Authentication? _authentication;
Expand Down Expand Up @@ -159,19 +162,22 @@ public void Connect(string url, string username, string password, RTP_TRANSPORT
this.rtpTransport = rtpTransport;
if (rtpTransport == RTP_TRANSPORT.UDP)
{
videoUdpPair = new UDPSocket(50000, 51000); // give a range of 500 pairs (1000 addresses) to try incase some address are in use
videoUdpPair.DataReceived += VideoRtpDataReceived;
videoUdpPair.ControlReceived += RtcpControlDataReceived;
videoUdpPair.Start(); // start listening for data on the UDP ports

audioUdpPair = new UDPSocket(50000, 51000); // give a range of 500 pairs (1000 addresses) to try incase some address are in use
audioUdpPair.DataReceived += AudioRtpDataReceived;
audioUdpPair.ControlReceived += RtcpControlDataReceived;
audioUdpPair.Start(); // start listening for data on the UDP ports
videoRtpTransport = new UDPSocket(50000, 51000); // give a range of 500 pairs (1000 addresses) to try incase some address are in use
audioRtpTransport = new UDPSocket(50000, 51000); // give a range of 500 pairs (1000 addresses) to try incase some address are in use
}
if (rtpTransport == RTP_TRANSPORT.TCP)
{
// Nothing to do. Data will arrive in the RTSP Listener
int nextFreeRtpChannel = 0;
videoRtpTransport = new RtpTcpTransport(rtspClient)
{
DataChannel = nextFreeRtpChannel++,
ControlChannel = nextFreeRtpChannel++,
};
audioRtpTransport = new RtpTcpTransport(rtspClient)
{
DataChannel = nextFreeRtpChannel++,
ControlChannel = nextFreeRtpChannel++,
};
}
if (rtpTransport == RTP_TRANSPORT.MULTICAST)
{
Expand Down Expand Up @@ -288,26 +294,22 @@ public void Play(DateTime seekTimeFrom, DateTime seekTimeTo, double speed = 1.0)

public void Stop()
{
if (rtspSocket is null || _uri is null)
{
throw new InvalidOperationException("Not connected");
}

// Send TEARDOWN
RtspRequest teardown_message = new RtspRequestTeardown
{
RtspUri = _uri,
Session = session
};
teardown_message.AddAuthorization(_authentication, _uri, rtspSocket.NextCommandIndex());
teardown_message.AddAuthorization(_authentication, _uri!, rtspSocket?.NextCommandIndex() ?? 0);
rtspClient?.SendMessage(teardown_message);

// Stop the keepalive timer
keepaliveTimer?.Stop();

// clear up any UDP sockets
videoUdpPair?.Stop();
audioUdpPair?.Stop();
videoRtpTransport?.Stop();
audioRtpTransport?.Stop();

// Drop the RTSP session
rtspClient?.Stop();
Expand Down Expand Up @@ -685,6 +687,10 @@ private void RtspMessageReceived(object? sender, RtspChunkEventArgs e)
{
keepaliveTimer.Interval = message.Timeout * 1000 / 2;
}

bool isVideoChannel = message.OriginalRequest.RtspUri == video_uri;
bool isAudioChannel = message.OriginalRequest.RtspUri == audio_uri;
Debug.Assert(isVideoChannel || isAudioChannel, "Unknown channel response");

// Check the Transport header
var transportString = message.Headers[RtspHeaderNames.Transport];
Expand All @@ -704,57 +710,50 @@ private void RtspMessageReceived(object? sender, RtspChunkEventArgs e)
&& videoRtcpChannel.HasValue)
{
// Create the Pair of UDP Sockets in Multicast mode
videoUdpPair = new MulticastUDPSocket(multicastAddress, videoDataChannel.Value, multicastAddress, videoRtcpChannel.Value);
videoUdpPair.DataReceived += VideoRtpDataReceived;
videoUdpPair.ControlReceived += RtcpControlDataReceived;
videoUdpPair.Start();
if(isVideoChannel)
{
videoRtpTransport = new MulticastUDPSocket(multicastAddress, videoDataChannel.Value, multicastAddress, videoRtcpChannel.Value);

} else if (isAudioChannel)
{
audioRtpTransport = new MulticastUDPSocket(multicastAddress, videoDataChannel.Value, multicastAddress, videoRtcpChannel.Value);
}
}
// TODO - Need to set audio_udp_pair for Multicast
}

// check if the requested Interleaved channels have been modified by the camera
// in the SETUP Reply (Panasonic have a camera that does this)
if (transport.LowerTransport == RtspTransport.LowerTransportType.TCP)
{
if (message.OriginalRequest.RtspUri == video_uri && rtspClient is not null)
RtpTcpTransport? tcpTransport = null;
if (isVideoChannel)
{
var videoDataChannel = transport.Interleaved?.First;
var videoRtcpChannel = transport.Interleaved?.Second;
rtspClient.DataReceived += (object? sender, RtspChunkEventArgs e) =>
{
if (e.Message is RtspData dataMessage && !dataMessage.Data.IsEmpty)
{
if (dataMessage.Channel == videoDataChannel)
{
VideoRtpDataReceived(sender, new RtspDataEventArgs(dataMessage));
}
else if (dataMessage.Channel == videoRtcpChannel)
{
RtcpControlDataReceived(sender, new RtspDataEventArgs(dataMessage));
}
}
};
tcpTransport = videoRtpTransport as RtpTcpTransport;
}

if (message.OriginalRequest.RtspUri == audio_uri && rtspClient is not null)
if (isAudioChannel)
{
var audioDataChannel = transport.Interleaved?.First;
var audioRtcpChannel = transport.Interleaved?.Second;
rtspClient.DataReceived += (object? sender, RtspChunkEventArgs e) =>
{
if (e.Message is RtspData dataMessage && !dataMessage.Data.IsEmpty)
{
if (dataMessage.Channel == audioDataChannel)
{
AudioRtpDataReceived(sender, new RtspDataEventArgs(dataMessage));
}
else if (dataMessage.Channel == audioRtcpChannel)
{
RtcpControlDataReceived(sender, new RtspDataEventArgs(dataMessage));
}
}
};
tcpTransport = audioRtpTransport as RtpTcpTransport;
}
if (tcpTransport is not null)
{
tcpTransport.DataChannel = transport.Interleaved?.First ?? tcpTransport.DataChannel;
tcpTransport.ControlChannel = transport.Interleaved?.Second ?? tcpTransport.ControlChannel;
}
}

if(isVideoChannel && videoRtpTransport is not null)
{
videoRtpTransport.DataReceived += VideoRtpDataReceived;
videoRtpTransport.ControlReceived += RtcpControlDataReceived;
videoRtpTransport.Start();
}

if (isAudioChannel && audioRtpTransport is not null)
{
audioRtpTransport.DataReceived += AudioRtpDataReceived;
audioRtpTransport.ControlReceived += RtcpControlDataReceived;
audioRtpTransport.Start();
}
}

Expand Down Expand Up @@ -810,11 +809,6 @@ private void HandleDescribeResponse(RtspResponse message)
sdp_data = SdpFile.Read(sdp_stream);
}

// RTP and RTCP 'channels' are used in TCP Interleaved mode (RTP over RTSP)
// These are the channels we request. The camera confirms the channel in the SETUP Reply.
// But, a Panasonic decides to use different channels in the reply.
int nextFreeRtpChannel = 0;

// For old sony cameras, we need to use the control uri from the sdp
var customControlUri = sdp_data.Attributs.FirstOrDefault(x => x.Key == "control");
if (customControlUri is not null)
Expand Down Expand Up @@ -919,7 +913,7 @@ private void HandleDescribeResponse(RtspResponse message)
// Send the SETUP RTSP command if we have a matching Payload Decoder
if (videoPayloadProcessor is not null)
{
RtspTransport? transport = CalculateTransport(ref nextFreeRtpChannel, videoUdpPair);
RtspTransport? transport = CalculateTransport(videoRtpTransport);

// Generate SETUP messages
if (transport != null)
Expand Down Expand Up @@ -980,7 +974,7 @@ private void HandleDescribeResponse(RtspResponse message)
// Send the SETUP RTSP command if we have a matching Payload Decoder
if (audioPayloadProcessor is not null)
{
RtspTransport? transport = CalculateTransport(ref nextFreeRtpChannel, audioUdpPair);
RtspTransport? transport = CalculateTransport(audioRtpTransport);

// Generate SETUP messages
if (transport != null)
Expand Down Expand Up @@ -1041,7 +1035,7 @@ private void HandleDescribeResponse(RtspResponse message)
return controlUri;
}

private RtspTransport? CalculateTransport(ref int nextFreeRtpChannel, UDPSocket? udp)
private RtspTransport? CalculateTransport(IRtpTransport? transport)
{
return rtpTransport switch
{
Expand All @@ -1051,21 +1045,22 @@ private void HandleDescribeResponse(RtspResponse message)
{
LowerTransport = RtspTransport.LowerTransportType.TCP,
// Eg Channel 0 for RTP video data. Channel 1 for RTCP status reports
Interleaved = new(nextFreeRtpChannel++, nextFreeRtpChannel++)
Interleaved = (transport as RtpTcpTransport)?.Channels ?? throw new ApplicationException("TCP transport asked and no tcp channel allocated"),
},
RTP_TRANSPORT.UDP => new RtspTransport()
{
LowerTransport = RtspTransport.LowerTransportType.UDP,
IsMulticast = false,
ClientPort = udp?.Ports ?? throw new ApplicationException("UDP transport asked and no udp port allocated"),
ClientPort = (transport as UDPSocket)?.Ports ?? throw new ApplicationException("UDP transport asked and no udp port allocated"),
},
// Server sends the RTP packets to a Pair of UDP ports (one for data, one for rtcp control messages)
// using Multicast Address and Ports that are in the reply to the SETUP message
// Example for MULTICAST mode Transport: RTP/AVP;multicast
RTP_TRANSPORT.MULTICAST => new RtspTransport()
{
LowerTransport = RtspTransport.LowerTransportType.UDP,
IsMulticast = true
IsMulticast = true,
ClientPort = new PortCouple(5000,5001)
},
_ => null,
};
Expand Down

0 comments on commit cf26bff

Please sign in to comment.