Skip to content

Commit 5a9aa75

Browse files
committed
R Added SendRawRTP, GenerateSDP
1 parent c35c2f7 commit 5a9aa75

1 file changed

Lines changed: 64 additions & 107 deletions

File tree

src/SharpRTSPServer/RTSPServer.cs

Lines changed: 64 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ namespace SharpRTSPServer
2323
/// Creates a server to listen for RTSP Commands (eg OPTIONS, DESCRIBE, SETUP, PLAY)
2424
/// Accepts VPS/SPS/PPS/NAL H264/H265 video data and sends out to RTSP clients
2525
/// </summary>
26+
/// <remarks>
27+
/// Stream with ffmpeg: ffmpeg.exe -re -stream_loop -1 -i frag_bunny.mp4 -vcodec copy -an -f rtp rtp://127.0.0.1:11111 -vn -acodec copy -f rtp rtp://127.0.0.1:11113
28+
/// </remarks>
2629
public class RTSPServer : IDisposable
2730
{
2831
/// <summary>
@@ -459,6 +462,22 @@ private void HandleDescribe(RtspListener listener, RtspRequest message)
459462
return;
460463
}
461464

465+
string sdp = GenerateSDP();
466+
byte[] sdpBytes = Encoding.ASCII.GetBytes(sdp);
467+
468+
// Create the reponse to DESCRIBE
469+
// This must include the Session Description Protocol (SDP)
470+
RtspResponse describeResponse = message.CreateResponse();
471+
472+
describeResponse.AddHeader("Content-Base: " + message.RtspUri);
473+
describeResponse.AddHeader("Content-Type: application/sdp");
474+
describeResponse.Data = sdpBytes;
475+
describeResponse.AdjustContentLength();
476+
listener.SendMessage(describeResponse);
477+
}
478+
479+
private string GenerateSDP()
480+
{
462481
StringBuilder sdp = new StringBuilder();
463482

464483
// Generate the SDP
@@ -471,21 +490,11 @@ private void HandleDescribe(RtspListener listener, RtspRequest message)
471490

472491
// VIDEO
473492
VideoTrack.BuildSDP(sdp);
474-
493+
475494
// AUDIO
476495
AudioTrack?.BuildSDP(sdp);
477496

478-
byte[] sdpBytes = Encoding.ASCII.GetBytes(sdp.ToString());
479-
480-
// Create the reponse to DESCRIBE
481-
// This must include the Session Description Protocol (SDP)
482-
RtspResponse describeResponse = message.CreateResponse();
483-
484-
describeResponse.AddHeader("Content-Base: " + message.RtspUri);
485-
describeResponse.AddHeader("Content-Type: application/sdp");
486-
describeResponse.Data = sdpBytes;
487-
describeResponse.AdjustContentLength();
488-
listener.SendMessage(describeResponse);
497+
return sdp.ToString();
489498
}
490499

491500
private RTSPConnection ConnectionByRtpTransport(IRtpTransport rtpTransport)
@@ -544,83 +553,14 @@ public void FeedInRawVideoSamples(uint rtpTimestamp, List<byte[]> samples)
544553
{
545554
CheckTimeouts(out int currentRtspCount, out int currentRtspPlayCount);
546555

547-
if (currentRtspPlayCount == 0)
556+
if (currentRtspPlayCount == 0)
548557
return;
549558

550559
// Build a list of 1 or more RTP packets
551560
// The last packet will have the M bit set to '1'
552561
(List<Memory<byte>> rtpPackets, List<IMemoryOwner<byte>> memoryOwners) = VideoTrack.CreateRtpPackets(samples, rtpTimestamp);
553562

554-
lock (_connectionList)
555-
{
556-
// Go through each RTSP connection and output the NAL on the Video Session
557-
foreach (RTSPConnection connection in _connectionList.ToArray()) // ToArray makes a temp copy of the list. This lets us delete items in the foreach eg when there is Write Error
558-
{
559-
// Only process Sessions in Play Mode
560-
if (!connection.Play)
561-
continue;
562-
563-
if (connection.Video.RtpChannel == null)
564-
continue;
565-
566-
_logger.LogDebug("Sending video session {sessionId} {TransportLogName} RTP timestamp={rtpTimestamp}. Sequence={sequenceNumber}",
567-
connection.SessionId, TransportLogName(connection.Video.RtpChannel), rtpTimestamp, connection.Video.SequenceNumber);
568-
569-
if (connection.Video.MustSendRtcpPacket)
570-
{
571-
if (!SendRTCP(rtpTimestamp, connection, connection.Video))
572-
{
573-
RemoveSession(connection);
574-
}
575-
}
576-
577-
bool writeError = false;
578-
// There could be more than 1 RTP packet (if the data is fragmented)
579-
foreach (var rtpPacket in rtpPackets)
580-
{
581-
// Add the specific data for each transmission
582-
RTPPacketUtil.WriteSequenceNumber(rtpPacket.Span, connection.Video.SequenceNumber);
583-
connection.Video.SequenceNumber++;
584-
585-
// Add the specific SSRC for each transmission
586-
RTPPacketUtil.WriteSSRC(rtpPacket.Span, connection.SSRC);
587-
588-
//Debug.Assert(connection.Video.RtpChannel != null, "If connection.video.rptChannel is null here the program did not handle well connection problem");
589-
try
590-
{
591-
// send the whole NAL. ** We could fragment the RTP packet into smaller chuncks that fit within the MTU
592-
// Send to the IP address of the Client
593-
// Send to the UDP Port the Client gave us in the SETUP command
594-
var channel = connection.Video.RtpChannel;
595-
if (channel != null)
596-
{
597-
channel.WriteToDataPort(rtpPacket.Span);
598-
}
599-
else
600-
{
601-
writeError = true;
602-
}
603-
}
604-
catch (Exception e)
605-
{
606-
_logger.LogWarning("UDP Write Exception " + e);
607-
writeError = true;
608-
break; // exit out of foreach loop
609-
}
610-
}
611-
612-
if (writeError)
613-
{
614-
_logger.LogWarning("Error writing to listener " + connection.Listener.RemoteAdress);
615-
_logger.LogWarning("Removing session " + connection.SessionId + " due to write error");
616-
RemoveSession(connection);
617-
}
618-
else
619-
{
620-
connection.Video.OctetCount += (uint)samples.Sum(nal => nal.Length); // QUESTION - Do I need to include the RTP header bytes/fragmenting bytes
621-
}
622-
}
623-
}
563+
FeedInRawRTP(RTSPConnection.VIDEO, rtpTimestamp, rtpPackets);
624564

625565
foreach (var owner in memoryOwners)
626566
{
@@ -644,48 +584,62 @@ public void FeedInRawAudioSamples(uint rtpTimestamp, List<byte[]> samples)
644584
// The last packet will have the M bit set to '1'
645585
(List<Memory<byte>> rtpPackets, List<IMemoryOwner<byte>> memoryOwners) = AudioTrack.CreateRtpPackets(samples, rtpTimestamp);
646586

587+
FeedInRawRTP(RTSPConnection.AUDIO, rtpTimestamp, rtpPackets);
588+
589+
foreach (var owner in memoryOwners)
590+
{
591+
owner.Dispose();
592+
}
593+
}
594+
595+
public void FeedInRawRTP(int streamType, uint rtpTimestamp, List<Memory<byte>> rtpPackets)
596+
{
647597
lock (_connectionList)
648598
{
649-
// Go through each RTSP connection and output the NAL on the Video Session
599+
// Go through each RTSP connection and output the RTP on the Session
650600
foreach (RTSPConnection connection in _connectionList.ToArray()) // ToArray makes a temp copy of the list. This lets us delete items in the foreach eg when there is Write Error
651601
{
652602
// Only process Sessions in Play Mode
653-
if (!connection.Play)
603+
if (!connection.Play)
654604
continue;
655605

656-
// The client may have only subscribed to Video. Check if the client wants audio
657-
if (connection.Audio.RtpChannel is null)
606+
if (connection.Streams[streamType].RtpChannel == null)
658607
continue;
659608

660-
_logger.LogDebug("Sending audio session {sessionId} {TransportLogName} RTP timestamp={rtpTimestamp}. Sequence={sequenceNumber}",
661-
connection.SessionId, TransportLogName(connection.Audio.RtpChannel), rtpTimestamp, connection.Audio.SequenceNumber);
609+
_logger.LogDebug("Sending RTP session {sessionId} {TransportLogName} RTP timestamp={rtpTimestamp}. Sequence={sequenceNumber}",
610+
connection.SessionId, TransportLogName(connection.Streams[streamType].RtpChannel), rtpTimestamp, connection.Streams[streamType].SequenceNumber);
662611

663-
if (connection.Audio.MustSendRtcpPacket)
612+
if (connection.Streams[streamType].MustSendRtcpPacket)
664613
{
665-
if (!SendRTCP(rtpTimestamp, connection, connection.Audio))
614+
if (!SendRTCP(rtpTimestamp, connection, connection.Streams[streamType]))
666615
{
667616
RemoveSession(connection);
668617
}
669618
}
670619

671620
bool writeError = false;
621+
uint writtenBytes = 0;
672622
// There could be more than 1 RTP packet (if the data is fragmented)
673623
foreach (var rtpPacket in rtpPackets)
674624
{
675625
// Add the specific data for each transmission
676-
RTPPacketUtil.WriteSequenceNumber(rtpPacket.Span, connection.Audio.SequenceNumber);
677-
connection.Audio.SequenceNumber++;
626+
RTPPacketUtil.WriteSequenceNumber(rtpPacket.Span, connection.Streams[streamType].SequenceNumber);
627+
connection.Streams[streamType].SequenceNumber++;
678628

679629
// Add the specific SSRC for each transmission
680630
RTPPacketUtil.WriteSSRC(rtpPacket.Span, connection.SSRC);
681631

632+
//Debug.Assert(connection.Streams[streamType].RtpChannel != null, "If connection.Streams[streamType].RtpChannel is null here the program did not handle well connection problem");
682633
try
683634
{
684-
// send the whole RTP packet
685-
var channel = connection.Audio.RtpChannel;
635+
// send the whole NAL. ** We could fragment the RTP packet into smaller chuncks that fit within the MTU
636+
// Send to the IP address of the Client
637+
// Send to the UDP Port the Client gave us in the SETUP command
638+
var channel = connection.Streams[streamType].RtpChannel;
686639
if (channel != null)
687640
{
688641
channel.WriteToDataPort(rtpPacket.Span);
642+
writtenBytes += (uint)rtpPacket.Span.Length;
689643
}
690644
else
691645
{
@@ -694,30 +648,24 @@ public void FeedInRawAudioSamples(uint rtpTimestamp, List<byte[]> samples)
694648
}
695649
catch (Exception e)
696650
{
697-
_logger.LogWarning(e, "UDP Write Exception");
651+
_logger.LogWarning("UDP Write Exception " + e);
698652
writeError = true;
699-
break;
653+
break; // exit out of foreach loop
700654
}
701655
}
702656

703657
if (writeError)
704658
{
705-
_logger.LogWarning("Error writing to listener {address}", connection.Listener.RemoteAdress);
659+
_logger.LogWarning("Error writing to listener " + connection.Listener.RemoteAdress);
706660
_logger.LogWarning("Removing session " + connection.SessionId + " due to write error");
707661
RemoveSession(connection);
708662
}
709663
else
710664
{
711-
connection.Audio.RtpPacketCount++;
712-
connection.Audio.OctetCount += (uint)samples.Sum(frame => frame.Length); // QUESTION - Do I need to include the RTP header bytes/fragmenting bytes
665+
connection.Streams[streamType].OctetCount += writtenBytes;
713666
}
714667
}
715668
}
716-
717-
foreach (var owner in memoryOwners)
718-
{
719-
owner.Dispose();
720-
}
721669
}
722670

723671
private bool SendRTCP(uint rtpTimestamp, RTSPConnection connection, RTPStream stream)
@@ -839,6 +787,9 @@ public class RTPStream
839787
/// </summary>
840788
public class RTSPConnection
841789
{
790+
public const int VIDEO = 0;
791+
public const int AUDIO = 1;
792+
842793
/// <summary>
843794
/// RTSP conneciton listener.
844795
/// </summary>
@@ -865,12 +816,18 @@ public class RTSPConnection
865816
/// <summary>
866817
/// Video stream.
867818
/// </summary>
868-
public RTPStream Video { get; set; } = new RTPStream();
819+
public RTPStream Video { get { return Streams[0]; } }
869820

870821
/// <summary>
871822
/// Audio stream.
872823
/// </summary>
873-
public RTPStream Audio { get; set; } = new RTPStream();
824+
public RTPStream Audio { get { return Streams[1]; } }
825+
826+
public RTPStream[] Streams { get; } = new RTPStream[]
827+
{
828+
new RTPStream(),
829+
new RTPStream()
830+
};
874831

875832
/// <summary>
876833
/// Update the keepalive.

0 commit comments

Comments
 (0)