Skip to content

Commit afb9369

Browse files
committed
F Added automatic JPEG width/height, added JPEG sample
1 parent c49e75b commit afb9369

6 files changed

Lines changed: 104 additions & 53 deletions

File tree

235 KB
Loading
233 KB
Loading
233 KB
Loading

src/RTSPServerApp/RTSPServerApp.csproj

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,31 @@
77
<Nullable>disable</Nullable>
88
</PropertyGroup>
99

10+
<ItemGroup>
11+
<None Remove="Images\image001.jpg" />
12+
<None Remove="Images\image002.jpg" />
13+
<None Remove="Images\image003.jpg" />
14+
<None Remove="Images\sample-birch-400x300.jpg" />
15+
<None Remove="Images\sample-blue-400x300.jpg" />
16+
<None Remove="Images\sample-city-park-400x300.jpg" />
17+
<None Remove="Images\sample-clouds-400x300.jpg" />
18+
<None Remove="Images\sample-green-400x300.jpg" />
19+
<None Remove="Images\sample-red-400x300.jpg" />
20+
</ItemGroup>
21+
1022
<ItemGroup>
1123
<Content Include="frag_bunny.mp4">
1224
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
1325
</Content>
26+
<Content Include="Images\image001.jpg">
27+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
28+
</Content>
29+
<Content Include="Images\image002.jpg">
30+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
31+
</Content>
32+
<Content Include="Images\image003.jpg">
33+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
34+
</Content>
1435
</ItemGroup>
1536

1637
<ItemGroup>

src/RTSPServerApp/RTSPServerWorker.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
121121
parsedMDAT[videoTrackId][i].Add(File.ReadAllBytes(jpgFiles[i]));
122122
}
123123

124-
rtspVideoTrack = new SharpRTSPServer.MJpegTrack() { Width = 1160, Height = 768 };
124+
rtspVideoTrack = new SharpRTSPServer.MJpegTrack();
125125
_server.AddVideoTrack(rtspVideoTrack);
126126

127127
videoFrameRate = 1;

src/SharpRTSPServer/MJpegTrack.cs

Lines changed: 82 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,6 @@ public class MJpegTrack : TrackBase
2121
/// <inheritdoc/>
2222
public override int ID { get; set; }
2323

24-
public int Width { get; set; }
25-
26-
public int Height { get; set; }
27-
2824
/// <inheritdoc/>
2925
public override StringBuilder BuildSDP(StringBuilder sdp)
3026
{
@@ -51,8 +47,6 @@ public override (List<Memory<byte>>, List<IMemoryOwner<byte>>) CreateRtpPackets(
5147
const ushort SoiMarker = 0xFFD8; // SOI - Start of image header
5248
//const ushort App0Header = 0xFFE0; // Application Segment 0 header
5349
//const ushort App15Header = 0xFFEF; // Application Segment 15 header
54-
const ushort Sof0Marker = 0xFFC0; // SOF0 - Start of Frame marker
55-
const ushort DqtHeader = 0xFFDB; // Define Quantization Table header
5650
//const ushort SosMarker = 0xFFDA; // SOS - Start of Scan marker
5751
const ushort EoiMarker = 0xFFD9; // EOI - End of Image marker
5852

@@ -70,52 +64,14 @@ public override (List<Memory<byte>>, List<IMemoryOwner<byte>>) CreateRtpPackets(
7064
throw new InvalidOperationException($"JPEG image must start with SOI marker {EoiMarker.ToString("X4")} and {header.ToString("X4")}");
7165
}
7266

73-
//var reader = jpegImage[2..^2];
74-
var reader = jpegImage.Slice(2); // keep EOI
75-
7667
byte type = 1; // https://datatracker.ietf.org/doc/html/rfc2435#section-3.1.3
7768
byte q = 255; // https://datatracker.ietf.org/doc/html/rfc2435#section-3.1.4, https://datatracker.ietf.org/doc/html/rfc2435#section-4.2
7869

79-
int nbQuantizationTables = 0;
8070
var firstQuantizationtable = ReadOnlySpan<byte>.Empty;
8171
var secondQuantizationtable = ReadOnlySpan<byte>.Empty;
8272

83-
while (true)
84-
{
85-
header = BinaryPrimitives.ReadUInt16BigEndian(reader);
86-
87-
if (header == Sof0Marker)
88-
{
89-
break;
90-
}
91-
92-
reader = reader.Slice(2);
93-
94-
var size = BinaryPrimitives.ReadUInt16BigEndian(reader) - 2; reader = reader.Slice(2);
95-
96-
switch (header)
97-
{
98-
case DqtHeader:
99-
nbQuantizationTables++;
100-
if (nbQuantizationTables == 1)
101-
{
102-
firstQuantizationtable = reader.Slice(0, size);
103-
}
104-
else if (nbQuantizationTables == 2)
105-
{
106-
secondQuantizationtable = reader.Slice(0, size);
107-
}
108-
else
109-
{
110-
throw new InvalidOperationException("Error: More than 2 quantization tables in JPEG image");
111-
}
112-
break;
113-
default:
114-
break;
115-
}
116-
117-
reader = reader.Slice(size);
118-
}
73+
Span<byte> reader;
74+
var jpegSize = ParseJpeg(jpegImage, out firstQuantizationtable, out secondQuantizationtable, out reader);
11975

12076
// Build a list of 1 or more RTP packets
12177
// The last packet will have the M bit set to '1'
@@ -178,8 +134,8 @@ public override (List<Memory<byte>>, List<IMemoryOwner<byte>>) CreateRtpPackets(
178134
// Write JPEG Header - https://datatracker.ietf.org/doc/html/rfc2435#section-3.1
179135
rtpPacketSpan[0] = type;
180136
rtpPacketSpan[1] = q;
181-
rtpPacketSpan[2] = (byte)(Width / 8);
182-
rtpPacketSpan[3] = (byte)(Height / 8);
137+
rtpPacketSpan[2] = (byte)(jpegSize.width >> 3);
138+
rtpPacketSpan[3] = (byte)(jpegSize.height >> 3);
183139
rtpPacketSpan = rtpPacketSpan.Slice(4);
184140

185141
// write quantization tables
@@ -190,7 +146,7 @@ public override (List<Memory<byte>>, List<IMemoryOwner<byte>>) CreateRtpPackets(
190146

191147
// Write Quantization Table header https://datatracker.ietf.org/doc/html/rfc2435#section-3.1.8
192148

193-
if (nbQuantizationTables == 1)
149+
if (secondQuantizationtable.IsEmpty)
194150
{
195151
// MBZ
196152
rtpPacketSpan[0] = (byte)(firstQuantizationtable[0] & 0xf);
@@ -200,7 +156,7 @@ public override (List<Memory<byte>>, List<IMemoryOwner<byte>>) CreateRtpPackets(
200156

201157
// Length
202158
var qtSize = firstQuantizationtable.Length - 1;
203-
BinaryPrimitives.WriteInt16BigEndian(rtpPacketSpan.Slice(2), (short)(qtSize));
159+
BinaryPrimitives.WriteInt16BigEndian(rtpPacketSpan.Slice(2), (short)qtSize);
204160

205161
// Quantization Table Data
206162
firstQuantizationtable.Slice(1).CopyTo(rtpPacketSpan.Slice(4));
@@ -218,7 +174,7 @@ public override (List<Memory<byte>>, List<IMemoryOwner<byte>>) CreateRtpPackets(
218174

219175
// Length
220176
var qtSize = firstQuantizationtable.Length + secondQuantizationtable.Length - 2;
221-
BinaryPrimitives.WriteInt16BigEndian(rtpPacketSpan.Slice(2), (short)(qtSize));
177+
BinaryPrimitives.WriteInt16BigEndian(rtpPacketSpan.Slice(2), (short)qtSize);
222178

223179
// Quantization Table Data
224180
firstQuantizationtable.Slice(1).CopyTo(rtpPacketSpan.Slice(4));
@@ -230,7 +186,6 @@ public override (List<Memory<byte>>, List<IMemoryOwner<byte>>) CreateRtpPackets(
230186
}
231187

232188
// Write JPEG Payload
233-
234189
reader.Slice(0, rtpPacketSpan.Length).CopyTo(rtpPacketSpan);
235190
reader = reader.Slice(rtpPacketSpan.Length);
236191
dataPointer += rtpPacketSpan.Length;
@@ -241,5 +196,80 @@ public override (List<Memory<byte>>, List<IMemoryOwner<byte>>) CreateRtpPackets(
241196

242197
return (rtpPackets, memoryOwners);
243198
}
199+
200+
201+
private static (int width, int height, int bpp) ParseJpeg(Span<byte> binaryReader, out ReadOnlySpan<byte> first, out ReadOnlySpan<byte> second, out Span<byte> retReader)
202+
{
203+
first = ReadOnlySpan<byte>.Empty;
204+
second = ReadOnlySpan<byte>.Empty;
205+
Span<byte> br = binaryReader;
206+
207+
// JPG magic bytes
208+
if (br[0] != 0xff || br[1] != 0xd8)
209+
{
210+
throw new ArgumentException();
211+
}
212+
213+
br = br.Slice(2);
214+
215+
while (br[0] == 0xff)
216+
{
217+
// Start-Of-Frame (SOF) has 4 possible values
218+
if (br[1] == 0xc0 || br[1] == 0xc1 || br[1] == 0xc2 || br[1] == 0xc3)
219+
{
220+
retReader = br;
221+
222+
br = br.Slice(2);
223+
br = br.Slice(2);
224+
225+
// bits per pixel
226+
int bpp = br[0];
227+
br = br.Slice(1);
228+
229+
// image height
230+
int height = (br[0] << 8) | br[1];
231+
br = br.Slice(2);
232+
233+
// image width
234+
int width = (br[0] << 8) | br[1];
235+
br = br.Slice(2);
236+
237+
return (width, height, bpp);
238+
}
239+
240+
br = br.Slice(1);
241+
242+
byte marker = br[0];
243+
br = br.Slice(1);
244+
245+
short chunkLength = (short)((br[0] << 8) | br[1]);
246+
br = br.Slice(2);
247+
248+
// quantization tables
249+
if (marker == 0xdb)
250+
{
251+
int matrix_length = chunkLength - 2;
252+
var matrix = br.Slice(0, matrix_length);
253+
if (first.IsEmpty)
254+
first = matrix;
255+
else if (second.IsEmpty)
256+
second = matrix;
257+
else
258+
throw new InvalidOperationException("Error: More than 2 quantization tables in JPEG image");
259+
}
260+
261+
if (chunkLength < 0)
262+
{
263+
ushort uchunkLength = (ushort)chunkLength;
264+
br = br.Slice(uchunkLength - 2);
265+
}
266+
else
267+
{
268+
br = br.Slice(chunkLength - 2);
269+
}
270+
}
271+
272+
throw new ArgumentException();
273+
}
244274
}
245275
}

0 commit comments

Comments
 (0)