@@ -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