Skip to content

Commit 4508067

Browse files
authored
Merge pull request #5 from paulomorgado/RTSPServerApp-as-worker
Refactor RTSP server to use hosted service pattern
2 parents 75ff42b + 47943a0 commit 4508067

4 files changed

Lines changed: 213 additions & 174 deletions

File tree

src/RTSPServerApp/Program.cs

Lines changed: 7 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -1,166 +1,9 @@
1-
using Microsoft.Extensions.Configuration;
2-
using SharpMp4;
3-
using SharpRTSPServer;
4-
using System;
5-
using System.Collections.Generic;
6-
using System.IO;
7-
using System.Linq;
8-
using System.Timers;
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Microsoft.Extensions.Hosting;
3+
using RTSPServerApp;
94

10-
IConfiguration config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
11-
string hostName = config["HostName"];
12-
ushort port = ushort.Parse(config["Port"]);
13-
string fileName = config["FilePath"];
14-
string userName = config["UserName"];
15-
string password = config["Password"];
5+
var builder = Host.CreateApplicationBuilder(args);
6+
builder.Services.AddHostedService<RTSPServerWorker>();
167

17-
using (var server = new RTSPServer(port, userName, password))
18-
{
19-
Dictionary<uint, IList<IList<byte[]>>> parsedMDAT;
20-
uint videoTrackId = 0;
21-
uint audioTrackId = 0;
22-
TrakBox audioTrackBox = null;
23-
TrakBox videoTrackBox = null;
24-
double videoFrameRate = 0;
25-
26-
ITrack rtspVideoTrack = null;
27-
ITrack rtspAudioTrack = null;
28-
29-
// frag_bunny.mp4 audio is not playable in VLC on Windows 11 (works on MacOS)
30-
using (Stream fs = new BufferedStream(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)))
31-
{
32-
using (var fmp4 = await FragmentedMp4.ParseAsync(fs))
33-
{
34-
videoTrackBox = fmp4.FindVideoTracks().FirstOrDefault();
35-
audioTrackBox = fmp4.FindAudioTracks().FirstOrDefault();
36-
37-
parsedMDAT = await fmp4.ParseMdatAsync();
38-
39-
if (videoTrackBox != null)
40-
{
41-
videoTrackId = fmp4.FindVideoTrackID().First();
42-
videoFrameRate = fmp4.CalculateFrameRate(videoTrackBox);
43-
44-
var h264VisualSample = videoTrackBox.GetMdia().GetMinf().GetStbl().GetStsd().Children.FirstOrDefault(x => x.Type == VisualSampleEntryBox.TYPE3 || x.Type == VisualSampleEntryBox.TYPE4) as VisualSampleEntryBox;
45-
if (h264VisualSample != null)
46-
{
47-
var avcC = (h264VisualSample.Children.First(x => x.Type == AvcConfigurationBox.TYPE) as AvcConfigurationBox).AvcDecoderConfigurationRecord;
48-
rtspVideoTrack = new SharpRTSPServer.H264Track(avcC.AvcProfileIndication, 0, avcC.AvcLevelIndication);
49-
server.AddVideoTrack(rtspVideoTrack);
50-
}
51-
else
52-
{
53-
var h265VisualSample = videoTrackBox.GetMdia().GetMinf().GetStbl().GetStsd().Children.FirstOrDefault(x => x.Type == VisualSampleEntryBox.TYPE6 || x.Type == VisualSampleEntryBox.TYPE7) as VisualSampleEntryBox;
54-
if(h265VisualSample != null)
55-
{
56-
rtspVideoTrack = new SharpRTSPServer.H265Track();
57-
server.AddVideoTrack(rtspVideoTrack);
58-
}
59-
else
60-
{
61-
throw new NotSupportedException("No supported video found!");
62-
}
63-
}
64-
}
65-
66-
if (audioTrackBox != null)
67-
{
68-
audioTrackId = fmp4.FindAudioTrackID().First();
69-
70-
var audioSampleEntry = audioTrackBox.GetAudioSampleEntryBox();
71-
if (audioSampleEntry.Type == AudioSampleEntryBox.TYPE3) // AAC
72-
{
73-
var audioConfigDescriptor = audioSampleEntry.GetAudioSpecificConfigDescriptor();
74-
int audioSamplingRate = audioConfigDescriptor.GetSamplingFrequency();
75-
rtspAudioTrack = new SharpRTSPServer.AACTrack(await audioConfigDescriptor.ToBytes(), audioSamplingRate, audioConfigDescriptor.ChannelConfiguration);
76-
server.AddAudioTrack(rtspAudioTrack);
77-
}
78-
else
79-
{
80-
// unsupported audio
81-
}
82-
}
83-
}
84-
}
85-
86-
int videoIndex = 0;
87-
int audioIndex = 0;
88-
Timer audioTimer = null;
89-
Timer videoTimer = null;
90-
91-
if (videoTrackBox != null)
92-
{
93-
var videoSamplingRate = SharpRTSPServer.H264Track.DEFAULT_CLOCK;
94-
var videoSampleDuration = videoSamplingRate / videoFrameRate;
95-
var videoTrack = parsedMDAT[videoTrackId];
96-
videoTimer = new Timer(videoSampleDuration * 1000 / videoSamplingRate);
97-
videoTimer.Elapsed += (s, e) =>
98-
{
99-
if (videoIndex == 0)
100-
{
101-
if (rtspVideoTrack is SharpRTSPServer.H264Track h264VideoTrack)
102-
{
103-
h264VideoTrack.SetParameterSets(videoTrack[0][0], videoTrack[0][1]);
104-
}
105-
else if (rtspVideoTrack is SharpRTSPServer.H265Track h265VideoTrack)
106-
{
107-
h265VideoTrack.SetParameterSets(videoTrack[0][0], videoTrack[0][1], videoTrack[0][2]);
108-
}
109-
videoIndex++;
110-
}
111-
112-
rtspVideoTrack.FeedInRawSamples((uint)(videoIndex * videoSampleDuration), (List<byte[]>)videoTrack[videoIndex++ % videoTrack.Count]);
113-
114-
if (videoIndex % videoTrack.Count == 0)
115-
{
116-
Reset(ref videoIndex, videoTimer, ref audioIndex, audioTimer);
117-
}
118-
};
119-
}
120-
121-
if (audioTrackBox != null)
122-
{
123-
var audioSampleDuration = SharpMp4.AACTrack.AAC_SAMPLE_SIZE;
124-
var audioTrack = parsedMDAT[audioTrackId];
125-
audioTimer = new Timer(audioSampleDuration * 1000 / (rtspAudioTrack as SharpRTSPServer.AACTrack).SamplingRate);
126-
audioTimer.Elapsed += (s, e) =>
127-
{
128-
rtspAudioTrack.FeedInRawSamples((uint)(audioIndex * audioSampleDuration), new List<byte[]>() { audioTrack[0][audioIndex++ % audioTrack[0].Count] });
129-
130-
if (audioIndex % audioTrack[0].Count == 0)
131-
{
132-
Reset(ref videoIndex, videoTimer, ref audioIndex, audioTimer);
133-
}
134-
};
135-
}
136-
137-
try
138-
{
139-
server.StartListen();
140-
}
141-
catch (Exception ex)
142-
{
143-
Console.WriteLine(ex.ToString());
144-
}
145-
146-
Console.WriteLine($"RTSP URL is rtsp://{userName}:{password}@{hostName}:{port}");
147-
148-
videoTimer?.Start();
149-
audioTimer?.Start();
150-
151-
Console.WriteLine("Press any key to exit");
152-
while (!Console.KeyAvailable)
153-
{
154-
System.Threading.Thread.Sleep(250);
155-
}
156-
}
157-
158-
static void Reset(ref int videoIndex, Timer videoTimer, ref int audioIndex, Timer audioTimer)
159-
{
160-
videoTimer?.Stop();
161-
audioTimer?.Stop();
162-
videoIndex = 0;
163-
audioIndex = 0;
164-
videoTimer?.Start();
165-
audioTimer?.Start();
166-
}
8+
var host = builder.Build();
9+
host.Run();

src/RTSPServerApp/RTSPServerApp.csproj

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk.Worker">
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
@@ -8,16 +8,13 @@
88
</PropertyGroup>
99

1010
<ItemGroup>
11-
<Content Include="appsettings.json">
12-
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
13-
</Content>
1411
<Content Include="frag_bunny.mp4">
1512
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
1613
</Content>
1714
</ItemGroup>
1815

1916
<ItemGroup>
20-
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.1" />
17+
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
2118
<PackageReference Include="SharpMp4" Version="0.0.7" />
2219
</ItemGroup>
2320

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
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

Comments
 (0)