22using System . Buffers ;
33using System . Buffers . Binary ;
44using System . Collections . Generic ;
5+ using System . IO ;
6+ using System . Linq ;
57using System . Text ;
68
79namespace 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