2
2
using System . Buffers ;
3
3
using System . Buffers . Binary ;
4
4
using System . Collections . Generic ;
5
+ using System . IO ;
6
+ using System . Linq ;
5
7
using System . Text ;
6
8
7
9
namespace SharpRTSPServer
@@ -64,14 +66,14 @@ public override (List<Memory<byte>>, List<IMemoryOwner<byte>>) CreateRtpPackets(
64
66
throw new InvalidOperationException ( $ "JPEG image must start with SOI marker { EoiMarker . ToString ( "X4" ) } and { header . ToString ( "X4" ) } ") ;
65
67
}
66
68
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
68
70
byte q = 255 ; // https://datatracker.ietf.org/doc/html/rfc2435#section-3.1.4, https://datatracker.ietf.org/doc/html/rfc2435#section-4.2
69
71
70
72
var firstQuantizationtable = ReadOnlySpan < byte > . Empty ;
71
73
var secondQuantizationtable = ReadOnlySpan < byte > . Empty ;
72
74
73
75
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 ) ;
75
77
76
78
// Build a list of 1 or more RTP packets
77
79
// The last packet will have the M bit set to '1'
@@ -132,10 +134,10 @@ public override (List<Memory<byte>>, List<IMemoryOwner<byte>>) CreateRtpPackets(
132
134
rtpPacketSpan = rtpPacketSpan . Slice ( 4 ) ;
133
135
134
136
// Write JPEG Header - https://datatracker.ietf.org/doc/html/rfc2435#section-3.1
135
- rtpPacketSpan [ 0 ] = type ;
137
+ rtpPacketSpan [ 0 ] = jpegInfo . type ;
136
138
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 ) ;
139
141
rtpPacketSpan = rtpPacketSpan . Slice ( 4 ) ;
140
142
141
143
// write quantization tables
@@ -197,12 +199,26 @@ public override (List<Memory<byte>>, List<IMemoryOwner<byte>>) CreateRtpPackets(
197
199
return ( rtpPackets , memoryOwners ) ;
198
200
}
199
201
202
+ public struct JpgComponent
203
+ {
204
+ public byte id ;
205
+ public byte samp ;
206
+ public byte qt ;
200
207
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 )
202
217
{
203
218
first = ReadOnlySpan < byte > . Empty ;
204
219
second = ReadOnlySpan < byte > . Empty ;
205
220
Span < byte > br = binaryReader ;
221
+ bool isDriPresent = false ;
206
222
207
223
// JPG magic bytes
208
224
if ( br [ 0 ] != 0xff || br [ 1 ] != 0xd8 )
@@ -234,7 +250,48 @@ private static (int width, int height, int bpp) ParseJpeg(Span<byte> binaryReade
234
250
int width = ( br [ 0 ] << 8 ) | br [ 1 ] ;
235
251
br = br . Slice ( 2 ) ;
236
252
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 ) ;
238
295
}
239
296
240
297
br = br . Slice ( 1 ) ;
@@ -258,6 +315,12 @@ private static (int width, int height, int bpp) ParseJpeg(Span<byte> binaryReade
258
315
throw new InvalidOperationException ( "Error: More than 2 quantization tables in JPEG image" ) ;
259
316
}
260
317
318
+ // restart marker
319
+ if ( marker == 0xdd )
320
+ {
321
+ isDriPresent = true ;
322
+ }
323
+
261
324
if ( chunkLength < 0 )
262
325
{
263
326
ushort uchunkLength = ( ushort ) chunkLength ;
0 commit comments