Skip to content

Commit a779bec

Browse files
committed
F JPG type is now inferred from the input file - closes #19
1 parent 61708ae commit a779bec

File tree

1 file changed

+70
-7
lines changed

1 file changed

+70
-7
lines changed

src/SharpRTSPServer/MJpegTrack.cs

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
using System.Buffers;
33
using System.Buffers.Binary;
44
using System.Collections.Generic;
5+
using System.IO;
6+
using System.Linq;
57
using System.Text;
68

79
namespace SharpRTSPServer
@@ -64,14 +66,14 @@ public override (List<Memory<byte>>, List<IMemoryOwner<byte>>) CreateRtpPackets(
6466
throw new InvalidOperationException($"JPEG image must start with SOI marker {EoiMarker.ToString("X4")} and {header.ToString("X4")}");
6567
}
6668

67-
byte type = 1; // https://datatracker.ietf.org/doc/html/rfc2435#section-3.1.3
69+
//byte type = 1; // https://datatracker.ietf.org/doc/html/rfc2435#section-3.1.3
6870
byte q = 255; // https://datatracker.ietf.org/doc/html/rfc2435#section-3.1.4, https://datatracker.ietf.org/doc/html/rfc2435#section-4.2
6971

7072
var firstQuantizationtable = ReadOnlySpan<byte>.Empty;
7173
var secondQuantizationtable = ReadOnlySpan<byte>.Empty;
7274

7375
Span<byte> reader;
74-
var jpegSize = ParseJpeg(jpegImage, out firstQuantizationtable, out secondQuantizationtable, out reader);
76+
var jpegInfo = ParseJpeg(jpegImage, out firstQuantizationtable, out secondQuantizationtable, out reader);
7577

7678
// Build a list of 1 or more RTP packets
7779
// The last packet will have the M bit set to '1'
@@ -132,10 +134,10 @@ public override (List<Memory<byte>>, List<IMemoryOwner<byte>>) CreateRtpPackets(
132134
rtpPacketSpan = rtpPacketSpan.Slice(4);
133135

134136
// Write JPEG Header - https://datatracker.ietf.org/doc/html/rfc2435#section-3.1
135-
rtpPacketSpan[0] = type;
137+
rtpPacketSpan[0] = jpegInfo.type;
136138
rtpPacketSpan[1] = q;
137-
rtpPacketSpan[2] = (byte)(jpegSize.width >> 3);
138-
rtpPacketSpan[3] = (byte)(jpegSize.height >> 3);
139+
rtpPacketSpan[2] = (byte)(jpegInfo.width >> 3);
140+
rtpPacketSpan[3] = (byte)(jpegInfo.height >> 3);
139141
rtpPacketSpan = rtpPacketSpan.Slice(4);
140142

141143
// write quantization tables
@@ -197,12 +199,26 @@ public override (List<Memory<byte>>, List<IMemoryOwner<byte>>) CreateRtpPackets(
197199
return (rtpPackets, memoryOwners);
198200
}
199201

202+
public struct JpgComponent
203+
{
204+
public byte id;
205+
public byte samp;
206+
public byte qt;
200207

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)
208+
public JpgComponent(byte id, byte samp, byte qt)
209+
{
210+
this.id = id;
211+
this.samp = samp;
212+
this.qt = qt;
213+
}
214+
}
215+
216+
private static (int width, int height, int bpp, byte type) ParseJpeg(Span<byte> binaryReader, out ReadOnlySpan<byte> first, out ReadOnlySpan<byte> second, out Span<byte> retReader)
202217
{
203218
first = ReadOnlySpan<byte>.Empty;
204219
second = ReadOnlySpan<byte>.Empty;
205220
Span<byte> br = binaryReader;
221+
bool isDriPresent = false;
206222

207223
// JPG magic bytes
208224
if (br[0] != 0xff || br[1] != 0xd8)
@@ -234,7 +250,48 @@ private static (int width, int height, int bpp) ParseJpeg(Span<byte> binaryReade
234250
int width = (br[0] << 8) | br[1];
235251
br = br.Slice(2);
236252

237-
return (width, height, bpp);
253+
int numComponents = br[0];
254+
br = br.Slice(1);
255+
256+
List<JpgComponent> components = new List<JpgComponent>(numComponents);
257+
for (int i = 0; i < numComponents; i++)
258+
{
259+
byte id = br[0];
260+
br = br.Slice(1);
261+
262+
byte samp = br[0];
263+
br = br.Slice(1);
264+
265+
byte qt = br[0];
266+
br = br.Slice(1);
267+
268+
JpgComponent component = new JpgComponent(id, samp, qt);
269+
components.Add(component);
270+
}
271+
272+
var sortedComponents = components.OrderBy(x => x.id);
273+
byte type = 0;
274+
if (sortedComponents.First().samp == 0x21)
275+
{
276+
type = 0;
277+
}
278+
else if(sortedComponents.First().samp == 0x22)
279+
{
280+
type = 1;
281+
}
282+
else
283+
{
284+
// https://datatracker.ietf.org/doc/html/rfc2435#section-4.1: supported types are only 0, 1 and 64, 65
285+
// JPEG must be re-encoded with chroma subsampling 4:2:0 (0x22) or 4:2:2 (0x21).
286+
throw new NotSupportedException("Unsupported chroma subsampling. Please re-encode the JPEG with chroma subsampling 4:2:0 (0x22) or 4:2:2 (0x21).");
287+
}
288+
289+
if(isDriPresent)
290+
{
291+
type += 64;
292+
}
293+
294+
return (width, height, bpp, type);
238295
}
239296

240297
br = br.Slice(1);
@@ -258,6 +315,12 @@ private static (int width, int height, int bpp) ParseJpeg(Span<byte> binaryReade
258315
throw new InvalidOperationException("Error: More than 2 quantization tables in JPEG image");
259316
}
260317

318+
// restart marker
319+
if(marker == 0xdd)
320+
{
321+
isDriPresent = true;
322+
}
323+
261324
if (chunkLength < 0)
262325
{
263326
ushort uchunkLength = (ushort)chunkLength;

0 commit comments

Comments
 (0)