1+ using Microsoft . Extensions . Configuration ;
2+ using Microsoft . Extensions . Hosting ;
3+ using Microsoft . Extensions . Logging ;
4+ using SharpMp4 ;
5+ using SharpRTSPServer ;
6+ using System ;
7+ using System . Collections . Generic ;
8+ using System . IO ;
9+ using System . Linq ;
10+ using System . Threading ;
11+ using System . Threading . Tasks ;
12+ using Timer = System . Timers . Timer ;
13+
14+ namespace RTSPServerApp ;
15+
16+ internal class RTSPServerWorker : BackgroundService
17+ {
18+ private readonly ILoggerFactory _loggerFactory ;
19+ private readonly IConfiguration _configuration ;
20+ private RTSPServer ? _server ;
21+
22+ public RTSPServerWorker ( IConfiguration configuration , ILoggerFactory loggerFactory )
23+ {
24+ ArgumentNullException . ThrowIfNull ( configuration ) ;
25+ ArgumentNullException . ThrowIfNull ( loggerFactory ) ;
26+
27+ _loggerFactory = loggerFactory ;
28+ _configuration = configuration ;
29+ }
30+
31+ protected override async Task ExecuteAsync ( CancellationToken stoppingToken )
32+ {
33+ var _logger = _loggerFactory . CreateLogger < RTSPServerWorker > ( ) ;
34+
35+ var hostName = _configuration [ "RTSPServerApp:HostName" ] ;
36+ var port = ushort . Parse ( _configuration [ "RTSPServerApp:Port" ] ) ;
37+ var userName = _configuration [ "RTSPServerApp:UserName" ] ;
38+ var password = _configuration [ "RTSPServerApp:Password" ] ;
39+ var fileName = _configuration [ "RTSPServerApp:FilePath" ] ;
40+
41+ _server = new RTSPServer ( port , userName , password , _loggerFactory ) ;
42+
43+ Dictionary < uint , IList < IList < byte [ ] > > > parsedMDAT ;
44+ uint videoTrackId = 0 ;
45+ uint audioTrackId = 0 ;
46+ TrakBox audioTrackBox = null ;
47+ TrakBox videoTrackBox = null ;
48+ double videoFrameRate = 0 ;
49+
50+ ITrack rtspVideoTrack = null ;
51+ ITrack rtspAudioTrack = null ;
52+
53+ // frag_bunny.mp4 audio is not playable in VLC on Windows 11 (works on MacOS)
54+ using ( Stream fs = new BufferedStream ( new FileStream ( fileName , FileMode . Open , FileAccess . Read , FileShare . Read ) ) )
55+ {
56+ using ( var fmp4 = await FragmentedMp4 . ParseAsync ( fs ) )
57+ {
58+ videoTrackBox = fmp4 . FindVideoTracks ( ) . FirstOrDefault ( ) ;
59+ audioTrackBox = fmp4 . FindAudioTracks ( ) . FirstOrDefault ( ) ;
60+
61+ parsedMDAT = await fmp4 . ParseMdatAsync ( ) ;
62+
63+ if ( videoTrackBox != null )
64+ {
65+ videoTrackId = fmp4 . FindVideoTrackID ( ) . First ( ) ;
66+ videoFrameRate = fmp4 . CalculateFrameRate ( videoTrackBox ) ;
67+
68+ var h264VisualSample = videoTrackBox . GetMdia ( ) . GetMinf ( ) . GetStbl ( ) . GetStsd ( ) . Children . FirstOrDefault ( x => x . Type == VisualSampleEntryBox . TYPE3 || x . Type == VisualSampleEntryBox . TYPE4 ) as VisualSampleEntryBox ;
69+ if ( h264VisualSample != null )
70+ {
71+ var avcC = ( h264VisualSample . Children . First ( x => x . Type == AvcConfigurationBox . TYPE ) as AvcConfigurationBox ) . AvcDecoderConfigurationRecord ;
72+ rtspVideoTrack = new SharpRTSPServer . H264Track ( avcC . AvcProfileIndication , 0 , avcC . AvcLevelIndication ) ;
73+ _server . AddVideoTrack ( rtspVideoTrack ) ;
74+ }
75+ else
76+ {
77+ var h265VisualSample = videoTrackBox . GetMdia ( ) . GetMinf ( ) . GetStbl ( ) . GetStsd ( ) . Children . FirstOrDefault ( x => x . Type == VisualSampleEntryBox . TYPE6 || x . Type == VisualSampleEntryBox . TYPE7 ) as VisualSampleEntryBox ;
78+ if ( h265VisualSample != null )
79+ {
80+ rtspVideoTrack = new SharpRTSPServer . H265Track ( ) ;
81+ _server . AddVideoTrack ( rtspVideoTrack ) ;
82+ }
83+ else
84+ {
85+ throw new NotSupportedException ( "No supported video found!" ) ;
86+ }
87+ }
88+ }
89+
90+ if ( audioTrackBox != null )
91+ {
92+ audioTrackId = fmp4 . FindAudioTrackID ( ) . First ( ) ;
93+
94+ var audioSampleEntry = audioTrackBox . GetAudioSampleEntryBox ( ) ;
95+ if ( audioSampleEntry . Type == AudioSampleEntryBox . TYPE3 ) // AAC
96+ {
97+ var audioConfigDescriptor = audioSampleEntry . GetAudioSpecificConfigDescriptor ( ) ;
98+ int audioSamplingRate = audioConfigDescriptor . GetSamplingFrequency ( ) ;
99+ rtspAudioTrack = new SharpRTSPServer . AACTrack ( await audioConfigDescriptor . ToBytes ( ) , audioSamplingRate , audioConfigDescriptor . ChannelConfiguration ) ;
100+ _server . AddAudioTrack ( rtspAudioTrack ) ;
101+ }
102+ else
103+ {
104+ // unsupported audio
105+ }
106+ }
107+ }
108+ }
109+
110+ int videoIndex = 0 ;
111+ int audioIndex = 0 ;
112+ Timer audioTimer = null ;
113+ Timer videoTimer = null ;
114+
115+ if ( videoTrackBox != null )
116+ {
117+ var videoSamplingRate = SharpRTSPServer . H264Track . DEFAULT_CLOCK ;
118+ var videoSampleDuration = videoSamplingRate / videoFrameRate ;
119+ var videoTrack = parsedMDAT [ videoTrackId ] ;
120+ videoTimer = new Timer ( videoSampleDuration * 1000 / videoSamplingRate ) ;
121+ videoTimer . Elapsed += ( s , e ) =>
122+ {
123+ if ( videoIndex == 0 )
124+ {
125+ if ( rtspVideoTrack is SharpRTSPServer . H264Track h264VideoTrack )
126+ {
127+ h264VideoTrack . SetParameterSets ( videoTrack [ 0 ] [ 0 ] , videoTrack [ 0 ] [ 1 ] ) ;
128+ }
129+ else if ( rtspVideoTrack is SharpRTSPServer . H265Track h265VideoTrack )
130+ {
131+ h265VideoTrack . SetParameterSets ( videoTrack [ 0 ] [ 0 ] , videoTrack [ 0 ] [ 1 ] , videoTrack [ 0 ] [ 2 ] ) ;
132+ }
133+ videoIndex ++ ;
134+ }
135+
136+ rtspVideoTrack . FeedInRawSamples ( ( uint ) ( videoIndex * videoSampleDuration ) , ( List < byte [ ] > ) videoTrack [ videoIndex ++ % videoTrack . Count ] ) ;
137+
138+ if ( videoIndex % videoTrack . Count == 0 )
139+ {
140+ Reset ( ref videoIndex , videoTimer , ref audioIndex , audioTimer ) ;
141+ }
142+ } ;
143+ }
144+
145+ if ( audioTrackBox != null )
146+ {
147+ var audioSampleDuration = SharpMp4 . AACTrack . AAC_SAMPLE_SIZE ;
148+ var audioTrack = parsedMDAT [ audioTrackId ] ;
149+ audioTimer = new Timer ( audioSampleDuration * 1000 / ( rtspAudioTrack as SharpRTSPServer . AACTrack ) . SamplingRate ) ;
150+ audioTimer . Elapsed += ( s , e ) =>
151+ {
152+ rtspAudioTrack . FeedInRawSamples ( ( uint ) ( audioIndex * audioSampleDuration ) , new List < byte [ ] > ( ) { audioTrack [ 0 ] [ audioIndex ++ % audioTrack [ 0 ] . Count ] } ) ;
153+
154+ if ( audioIndex % audioTrack [ 0 ] . Count == 0 )
155+ {
156+ Reset ( ref videoIndex , videoTimer , ref audioIndex , audioTimer ) ;
157+ }
158+ } ;
159+ }
160+
161+ try
162+ {
163+ _server . StartListen ( ) ;
164+ }
165+ catch ( Exception ex )
166+ {
167+ _logger . LogError ( ex , ex . ToString ( ) ) ;
168+ }
169+
170+ _logger . LogInformation ( $ "RTSP URL is rtsp://{ userName } :{ password } @{ hostName } :{ port } ") ;
171+
172+ videoTimer ? . Start ( ) ;
173+ audioTimer ? . Start ( ) ;
174+ }
175+
176+ public override void Dispose ( )
177+ {
178+ base . Dispose ( ) ;
179+
180+ _server ? . Dispose ( ) ;
181+ }
182+
183+ static void Reset ( ref int videoIndex , Timer videoTimer , ref int audioIndex , Timer audioTimer )
184+ {
185+ videoTimer ? . Stop ( ) ;
186+ audioTimer ? . Stop ( ) ;
187+ videoIndex = 0 ;
188+ audioIndex = 0 ;
189+ videoTimer ? . Start ( ) ;
190+ audioTimer ? . Start ( ) ;
191+ }
192+ }
0 commit comments