22using System . Buffers ;
33using System . Buffers . Binary ;
44using System . Collections . Generic ;
5- using System . IO ;
65using System . Linq ;
76using System . Text ;
87
@@ -213,11 +212,23 @@ public JpgComponent(byte id, byte samp, byte qt)
213212 }
214213 }
215214
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 )
215+ /// <summary>
216+ /// Parse JPEG image and return the width, height, bit depth and type.
217+ /// </summary>
218+ /// <param name="jpegImage">Span holding the JPEG image buffer. <see cref="Span{Byte}"/>.</param>
219+ /// <param name="firstQuantizationTable">The first Quantization Table or an empty span when not present.</param>
220+ /// <param name="secondQuantizationTable">The second Quantization Table or an empty span when not present.</param>
221+ /// <param name="imageData">Image data.</param>
222+ /// <returns>JPEG image width, height, bit depth and the RTP payload type.</returns>
223+ /// <exception cref="ArgumentException">Thrown when jpegImage data do not contain a valid JPEG image.</exception>
224+ /// <exception cref="NotSupportedException">Thrown when JPEG image passed to this function does not meet the criteria for being used in RTP without re-encoding.
225+ /// The criteria are: image dimensions must be less than or equal to 2040 x 2040 pixels and chroma subsampling must be set to 4:2:0 or 4:2:2.
226+ /// </exception>
227+ public static ( int width , int height , int bpp , byte type ) ParseJpeg ( Span < byte > jpegImage , out ReadOnlySpan < byte > firstQuantizationTable , out ReadOnlySpan < byte > secondQuantizationTable , out Span < byte > imageData )
217228 {
218- first = ReadOnlySpan < byte > . Empty ;
219- second = ReadOnlySpan < byte > . Empty ;
220- Span < byte > br = binaryReader ;
229+ firstQuantizationTable = ReadOnlySpan < byte > . Empty ;
230+ secondQuantizationTable = ReadOnlySpan < byte > . Empty ;
231+ Span < byte > br = jpegImage ;
221232 bool isDriPresent = false ;
222233
223234 // JPG magic bytes
@@ -233,7 +244,7 @@ private static (int width, int height, int bpp, byte type) ParseJpeg(Span<byte>
233244 // Start-Of-Frame (SOF) has 4 possible values
234245 if ( br [ 1 ] == 0xc0 || br [ 1 ] == 0xc1 || br [ 1 ] == 0xc2 || br [ 1 ] == 0xc3 )
235246 {
236- retReader = br ;
247+ imageData = br ;
237248
238249 br = br . Slice ( 2 ) ;
239250 br = br . Slice ( 2 ) ;
@@ -250,6 +261,11 @@ private static (int width, int height, int bpp, byte type) ParseJpeg(Span<byte>
250261 int width = ( br [ 0 ] << 8 ) | br [ 1 ] ;
251262 br = br . Slice ( 2 ) ;
252263
264+ if ( width > 2040 || height > 2040 )
265+ {
266+ throw new NotSupportedException ( "JPEG image is too large. Maximum image dimensions allowed in JPEG over RTP are 2040x2040 pixels." ) ;
267+ }
268+
253269 int numComponents = br [ 0 ] ;
254270 br = br . Slice ( 1 ) ;
255271
@@ -269,6 +285,8 @@ private static (int width, int height, int bpp, byte type) ParseJpeg(Span<byte>
269285 components . Add ( component ) ;
270286 }
271287
288+ // https://datatracker.ietf.org/doc/html/rfc2435#section-4.1: supported types are only 0, 1 and 64, 65
289+ // JPEG must be re-encoded with chroma subsampling 4:2:0 (0x22,0x11,0x11) or 4:2:2 (0x21,0x11,0x11).
272290 var sortedComponents = components . OrderBy ( x => x . id ) ;
273291 byte type = 0 ;
274292 if ( sortedComponents . First ( ) . samp == 0x21 )
@@ -281,13 +299,20 @@ private static (int width, int height, int bpp, byte type) ParseJpeg(Span<byte>
281299 }
282300 else
283301 {
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)." ) ;
302+ throw new NotSupportedException ( $ "Unsupported chroma subsampling 0x{ sortedComponents . First ( ) . samp : X2} . Supported chroma subsampling values are 4:2:0 (0x22,0x11,0x11) or 4:2:2 (0x21,0x11,0x11).") ;
303+ }
304+
305+ foreach ( var comp in sortedComponents . Skip ( 1 ) )
306+ {
307+ if ( comp . samp != 0x11 )
308+ {
309+ throw new NotSupportedException ( $ "Unsupported chroma subsampling 0x{ comp . samp : X2} . Supported chroma subsampling values are 4:2:0 (0x22,0x11,0x11) or 4:2:2 (0x21,0x11,0x11).") ;
310+ }
287311 }
288312
289- if ( isDriPresent )
313+ if ( isDriPresent )
290314 {
315+ // when restart marker is present, we shift the type by 64
291316 type += 64 ;
292317 }
293318
@@ -307,12 +332,12 @@ private static (int width, int height, int bpp, byte type) ParseJpeg(Span<byte>
307332 {
308333 int matrix_length = chunkLength - 2 ;
309334 var matrix = br . Slice ( 0 , matrix_length ) ;
310- if ( first . IsEmpty )
311- first = matrix ;
312- else if ( second . IsEmpty )
313- second = matrix ;
335+ if ( firstQuantizationTable . IsEmpty )
336+ firstQuantizationTable = matrix ;
337+ else if ( secondQuantizationTable . IsEmpty )
338+ secondQuantizationTable = matrix ;
314339 else
315- throw new InvalidOperationException ( "Error: More than 2 quantization tables in JPEG image ") ;
340+ throw new NotSupportedException ( "Invalid JPEG image. More than 2 quantization tables found. ") ;
316341 }
317342
318343 // restart marker
0 commit comments