@@ -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