@@ -6,6 +6,7 @@ namespace audio_tools {
6
6
/* *
7
7
* @brief MP3 header parser to check if the data is a valid mp3 and
8
8
* to extract some relevant audio information.
9
+ * See https://www.codeproject.com/KB/audio-video/mpegaudioinfo.aspx
9
10
* @ingroup codecs
10
11
* @ingroup decoder
11
12
* @author Phil Schatzmann
@@ -61,7 +62,7 @@ class MP3HeaderParser {
61
62
bool Protection : 1 ;
62
63
63
64
// sample & bitrate indexes meaning differ depending on MPEG version
64
- // use getBitrate () and GetSamplerate()
65
+ // use getBitRate () and GetSamplerate()
65
66
bool BitrateIndex : 4 ;
66
67
bool SampleRateIndex : 2 ;
67
68
@@ -89,8 +90,6 @@ class MP3HeaderParser {
89
90
// indicates whether the frame is located on the original media or a copy
90
91
bool Original : 1 ;
91
92
92
- uint16_t crc; // crc data if Protection is true
93
-
94
93
// indicates to the decoder that the file must be de-emphasized, ie the
95
94
// decoder must 're-equalize' the sound after a Dolby-like noise supression.
96
95
// It is rarely used.
@@ -106,7 +105,7 @@ class MP3HeaderParser {
106
105
ANY = 0 ,
107
106
};
108
107
109
- signed int getBitrate () const {
108
+ signed int getBitRate () const {
110
109
// version, layer, bit index
111
110
static signed char rateTable[4 ][4 ][16 ] = {
112
111
// version[00] = MPEG_2_5
@@ -153,8 +152,12 @@ class MP3HeaderParser {
153
152
{0 , 4 , 8 , 12 , 16 , 20 , 24 , 28 , 32 , 36 , 40 , 44 , 48 , 52 , 56 , -1 },
154
153
},
155
154
};
156
-
157
- return rateTable[AudioVersion][Layer][BitrateIndex] * 8000 ;
155
+ char rate_byte = rateTable[AudioVersion][Layer][BitrateIndex];
156
+ if (rate_byte == -1 ) {
157
+ LOGE (" Unsupported bitrate" );
158
+ return 0 ;
159
+ }
160
+ return rate_byte * 8000 ;
158
161
}
159
162
160
163
enum SpecialSampleRate {
@@ -178,71 +181,94 @@ class MP3HeaderParser {
178
181
}
179
182
180
183
int getFrameLength () {
181
- return int ((144 * getBitrate () / getSampleRate ()) + Padding);
184
+ int sample_rate = getSampleRate ();
185
+ if (sample_rate == 0 ) return 0 ;
186
+ return int ((144 * getBitRate () / sample_rate) + Padding);
182
187
}
183
-
184
188
};
185
189
186
190
public:
187
191
// / parses the header string and returns true if this is a valid mp3 file
188
192
bool isValid (const uint8_t * data, int len) {
189
193
memset (&header, 0 , sizeof (header));
190
- StrView str ((char *)data, len);
191
194
192
- if (str.startsWith (" ID3" )) {
195
+ // if we start with ID3 -> valid mp3
196
+ if (memcmp (data, " ID3" , 3 ) == 0 ) {
197
+ LOGI (" ID3 found" );
193
198
return true ;
194
199
}
195
200
196
- int pos = seekFrameSync (str );
197
- if (pos == -1 ) {
201
+ int sync_pos = seekFrameSync (data, len );
202
+ if (sync_pos == -1 ) {
198
203
LOGE (" Could not find FrameSync" );
199
204
return false ;
200
205
}
201
206
202
- // xing header
203
- if (pos > 0 && str.contains (" Xing" )) {
207
+ // xing header -> valid mp3
208
+ if (sync_pos >= 0 && contains (data, " Xing" , len)) {
209
+ LOGI (" Xing found" );
204
210
return true ;
205
211
}
206
212
207
- // xing header
208
- if (pos > 0 && str.contains (" Info" )) {
213
+ // xing header -> valid mp3
214
+ if (sync_pos >= 0 && contains (data, " Info" , len)) {
215
+ LOGI (" Xing Info found" );
209
216
return true ;
210
217
}
211
218
212
- int len_available = len - pos;
213
- if (len_available < sizeof (header)) {
214
- LOGE (" Not enough data to determine mp3 header" );
215
- return false ;
216
- }
217
-
218
- // fill header with data
219
- StrView header_str ((char *)data + pos, len_available);
220
- header = readFrameHeader (header_str);
219
+ // find valid segement in available data
220
+ bool is_valid_mp3 = false ;
221
+ while (true ) {
222
+ LOGI (" checking header at %d" , sync_pos);
223
+ int len_available = len - sync_pos;
221
224
222
- // check end of frame: it must contains a sync word
223
- int end_pos = findSyncWord ((uint8_t *)header_str.c_str (), header_str.length ());
224
- int pos_expected = getFrameLength ();
225
- if (pos_expected < header_str.length ()){
226
- if (end_pos != pos_expected){
227
- LOGE (" Expected SynchWord missing" );
228
- return false ;
225
+ // check if we have enough data for header
226
+ if (len_available < sizeof (header)) {
227
+ LOGE (" Not enough data to determine mp3 header" );
228
+ break ;
229
229
}
230
- }
231
230
232
- // calculate crc
233
- uint16_t crc = crc16 ((uint8_t *)header_str.c_str (),
234
- sizeof (FrameHeader) - sizeof (uint16_t ));
235
- // validate
236
- return FrameReason::VALID == validateFrameHeader (header, crc);
231
+ readFrameHeader (data);
232
+ is_valid_mp3 = validate (data + sync_pos, len_available);
233
+
234
+ // find end sync
235
+ int pos = seekFrameSync (data + sync_pos + 2 , len_available - 2 );
236
+ // no more data to be validated
237
+ if (pos == -1 ) break ;
238
+ // calculate new sync_pos
239
+ sync_pos = pos + sync_pos + 2 ;
240
+
241
+ // success and we found an end sync with a bit rate
242
+ if (is_valid_mp3 && getSampleRate () != 0 ) break ;
243
+ }
244
+ if (is_valid_mp3) {
245
+ LOGI (" -------------------" );
246
+ LOGI (" is mp3: %s" , is_valid_mp3 ? " yes" : " no" );
247
+ LOGI (" frame size: %d" , getFrameLength ());
248
+ LOGI (" sample rate: %u" , getSampleRate ());
249
+ LOGI (" bit rate index: %d" , getFrameHeader ().BitrateIndex );
250
+ LOGI (" bit rate: %d" , getBitRate ());
251
+ LOGI (" Padding: %d" , getFrameHeader ().Padding );
252
+ LOGI (" Version: %s (0x%x)" , getVersionStr (),
253
+ getFrameHeader ().AudioVersion );
254
+ LOGI (" Layer: %s (0x%x)" , getLayerStr (), getFrameHeader ().Layer );
255
+ }
256
+ return is_valid_mp3;
237
257
}
238
258
239
259
uint16_t getSampleRate () const { return header.getSampleRate (); }
240
260
241
- int getBitrate () const { return header.getBitrate (); }
261
+ int getBitRate () const { return header.getBitRate (); }
242
262
243
263
// / Determines the frame length
244
- int getFrameLength () {
245
- return header.getFrameLength ();
264
+ int getFrameLength () { return header.getFrameLength (); }
265
+
266
+ // / Provides the estimated playing time in seconds based on the bitrate of the
267
+ // / first segment
268
+ size_t getPlayingTime (size_t fileSizeBytes) {
269
+ int bitrate = getBitRate ();
270
+ if (bitrate == 0 ) return 0 ;
271
+ return fileSizeBytes / bitrate;
246
272
}
247
273
248
274
const char * getVersionStr () const {
@@ -264,7 +290,7 @@ class MP3HeaderParser {
264
290
FrameHeader getFrameHeader () { return header; }
265
291
266
292
// / Finds the mp3/aac sync word
267
- int findSyncWord (uint8_t * buf, int nBytes, uint8_t SYNCWORDH = 0xFF ,
293
+ int findSyncWord (const uint8_t * buf, int nBytes, uint8_t SYNCWORDH = 0xFF ,
268
294
uint8_t SYNCWORDL = 0xF0 ) {
269
295
for (int i = 0 ; i < nBytes - 1 ; i++) {
270
296
if ((buf[i + 0 ] & SYNCWORDH) == SYNCWORDH &&
@@ -277,34 +303,31 @@ class MP3HeaderParser {
277
303
protected:
278
304
FrameHeader header;
279
305
280
- uint16_t crc16 (const uint8_t * data_p, size_t length) {
281
- uint8_t x;
282
- uint16_t crc = 0xFFFF ;
306
+ bool validate (const uint8_t * data, size_t len) {
307
+ assert (header.FrameSyncByte = 0xFF );
308
+ // check end of frame: it must contains a sync word
309
+ return FrameReason::VALID == validateFrameHeader (header);
310
+ }
283
311
284
- while (length--) {
285
- x = crc >> 8 ^ *data_p++;
286
- x ^= x >> 4 ;
287
- crc = (crc << 8 ) ^ ((unsigned short )(x << 12 )) ^
288
- ((unsigned short )(x << 5 )) ^ ((unsigned short )x);
312
+ bool contains (const uint8_t * data, const char * toFind, size_t len) {
313
+ int find_str_len = strlen (toFind);
314
+ for (int j = 0 ; j < len - find_str_len; j++) {
315
+ if (memcmp (data + j, toFind, find_str_len) == 0 ) return true ;
289
316
}
290
- return crc ;
317
+ return false ;
291
318
}
292
319
293
320
// Seeks to the byte at the end of the next continuous run of 11 set bits.
294
321
// (ie. after seeking the cursor will be on the byte of which its 3 most
295
322
// significant bits are part of the frame sync)
296
- int seekFrameSync (StrView str) {
323
+ int seekFrameSync (const uint8_t * str, size_t len ) {
297
324
char cur;
298
- for (int j = 0 ; j < str. length () - 1 ; j++) {
325
+ for (int j = 0 ; j < len - 1 ; j++) {
299
326
cur = str[j];
300
327
// read bytes until EOF or a byte with all bits set is encountered
301
328
if ((cur & 0b11111111 ) != 0b11111111 ) continue ;
302
329
303
- // peek next byte, ensure its not past EOF, and check that its 3 most
304
- // significant bits are set to complete the continuous run of 11
305
- char next = str[j + 1 ];
306
-
307
- if ((next & 0b11100000 ) != 0b11100000 ) {
330
+ if ((str[j + 1 ] & 0b11100000 ) != 0b11100000 ) {
308
331
// if the next byte does not have its 3 most significant bits set it is
309
332
// not the end of the framesync and it also cannot be the start of a
310
333
// framesync so just skip over it here without the check
@@ -316,10 +339,14 @@ class MP3HeaderParser {
316
339
return -1 ;
317
340
}
318
341
319
- FrameHeader readFrameHeader (StrView in) {
320
- FrameHeader header;
321
- memcpy (&header, in.c_str (), sizeof (header));
322
- return header;
342
+ void readFrameHeader (const uint8_t * data) {
343
+ assert (data[0 ] == 0xFF );
344
+ assert ((data[1 ] & 0b11100000 ) == 0b11100000 );
345
+
346
+ memcpy (&header, data, sizeof (header));
347
+
348
+ LOGI (" - sample rate: %u" , getSampleRate ());
349
+ LOGI (" - bit rate: %d" , getBitRate ());
323
350
}
324
351
325
352
enum class FrameReason {
@@ -333,14 +360,7 @@ class MP3HeaderParser {
333
360
INVALID_CRC,
334
361
};
335
362
336
- FrameReason validateFrameHeader (const FrameHeader& header, uint16_t crc) {
337
- if (header.Protection ) {
338
- if (header.crc != crc) {
339
- LOGI (" invalid CRC" );
340
- return FrameReason::INVALID_CRC;
341
- }
342
- }
343
-
363
+ FrameReason validateFrameHeader (const FrameHeader& header) {
344
364
if (header.AudioVersion == FrameHeader::AudioVersionID::INVALID) {
345
365
LOGI (" invalid mpeg version" );
346
366
return FrameReason::INVALID_MPEG_VERSION;
@@ -351,7 +371,7 @@ class MP3HeaderParser {
351
371
return FrameReason::INVALID_LAYER;
352
372
}
353
373
354
- if (header.getBitrate () == FrameHeader::SpecialBitrate::INVALID) {
374
+ if (header.getBitRate () == FrameHeader::SpecialBitrate::INVALID) {
355
375
LOGI (" invalid bitrate" );
356
376
return FrameReason::INVALID_BITRATE_FOR_VERSION;
357
377
}
@@ -365,17 +385,17 @@ class MP3HeaderParser {
365
385
// not allowed
366
386
if (header.Layer == FrameHeader::LayerID::LAYER_2) {
367
387
if (header.ChannelMode == FrameHeader::ChannelModeID::SINGLE) {
368
- if (header.getBitrate () >= 224000 ) {
388
+ if (header.getBitRate () >= 224000 ) {
369
389
LOGI (" invalid bitrate >224000" );
370
390
return FrameReason::INVALID_LAYER_II_BITRATE_AND_MODE;
371
391
}
372
392
} else {
373
- if (header.getBitrate () >= 32000 && header.getBitrate () <= 56000 ) {
393
+ if (header.getBitRate () >= 32000 && header.getBitRate () <= 56000 ) {
374
394
LOGI (" invalid bitrate >32000" );
375
395
return FrameReason::INVALID_LAYER_II_BITRATE_AND_MODE;
376
396
}
377
397
378
- if (header.getBitrate () == 80000 ) {
398
+ if (header.getBitRate () == 80000 ) {
379
399
LOGI (" invalid bitrate >80000" );
380
400
return FrameReason::INVALID_LAYER_II_BITRATE_AND_MODE;
381
401
}
0 commit comments