@@ -6,6 +6,7 @@ namespace audio_tools {
66/* *
77 * @brief MP3 header parser to check if the data is a valid mp3 and
88 * to extract some relevant audio information.
9+ * See https://www.codeproject.com/KB/audio-video/mpegaudioinfo.aspx
910 * @ingroup codecs
1011 * @ingroup decoder
1112 * @author Phil Schatzmann
@@ -61,7 +62,7 @@ class MP3HeaderParser {
6162 bool Protection : 1 ;
6263
6364 // sample & bitrate indexes meaning differ depending on MPEG version
64- // use getBitrate () and GetSamplerate()
65+ // use getBitRate () and GetSamplerate()
6566 bool BitrateIndex : 4 ;
6667 bool SampleRateIndex : 2 ;
6768
@@ -89,8 +90,6 @@ class MP3HeaderParser {
8990 // indicates whether the frame is located on the original media or a copy
9091 bool Original : 1 ;
9192
92- uint16_t crc; // crc data if Protection is true
93-
9493 // indicates to the decoder that the file must be de-emphasized, ie the
9594 // decoder must 're-equalize' the sound after a Dolby-like noise supression.
9695 // It is rarely used.
@@ -106,7 +105,7 @@ class MP3HeaderParser {
106105 ANY = 0 ,
107106 };
108107
109- signed int getBitrate () const {
108+ signed int getBitRate () const {
110109 // version, layer, bit index
111110 static signed char rateTable[4 ][4 ][16 ] = {
112111 // version[00] = MPEG_2_5
@@ -153,8 +152,12 @@ class MP3HeaderParser {
153152 {0 , 4 , 8 , 12 , 16 , 20 , 24 , 28 , 32 , 36 , 40 , 44 , 48 , 52 , 56 , -1 },
154153 },
155154 };
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 ;
158161 }
159162
160163 enum SpecialSampleRate {
@@ -178,71 +181,94 @@ class MP3HeaderParser {
178181 }
179182
180183 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);
182187 }
183-
184188 };
185189
186190 public:
187191 // / parses the header string and returns true if this is a valid mp3 file
188192 bool isValid (const uint8_t * data, int len) {
189193 memset (&header, 0 , sizeof (header));
190- StrView str ((char *)data, len);
191194
192- if (str.startsWith (" ID3" )) {
195+ // if we start with ID3 -> valid mp3
196+ if (memcmp (data, " ID3" , 3 ) == 0 ) {
197+ LOGI (" ID3 found" );
193198 return true ;
194199 }
195200
196- int pos = seekFrameSync (str );
197- if (pos == -1 ) {
201+ int sync_pos = seekFrameSync (data, len );
202+ if (sync_pos == -1 ) {
198203 LOGE (" Could not find FrameSync" );
199204 return false ;
200205 }
201206
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" );
204210 return true ;
205211 }
206212
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" );
209216 return true ;
210217 }
211218
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;
221224
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 ;
229229 }
230- }
231230
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;
237257 }
238258
239259 uint16_t getSampleRate () const { return header.getSampleRate (); }
240260
241- int getBitrate () const { return header.getBitrate (); }
261+ int getBitRate () const { return header.getBitRate (); }
242262
243263 // / 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;
246272 }
247273
248274 const char * getVersionStr () const {
@@ -264,7 +290,7 @@ class MP3HeaderParser {
264290 FrameHeader getFrameHeader () { return header; }
265291
266292 // / 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 ,
268294 uint8_t SYNCWORDL = 0xF0 ) {
269295 for (int i = 0 ; i < nBytes - 1 ; i++) {
270296 if ((buf[i + 0 ] & SYNCWORDH) == SYNCWORDH &&
@@ -277,34 +303,31 @@ class MP3HeaderParser {
277303 protected:
278304 FrameHeader header;
279305
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+ }
283311
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 ;
289316 }
290- return crc ;
317+ return false ;
291318 }
292319
293320 // Seeks to the byte at the end of the next continuous run of 11 set bits.
294321 // (ie. after seeking the cursor will be on the byte of which its 3 most
295322 // significant bits are part of the frame sync)
296- int seekFrameSync (StrView str) {
323+ int seekFrameSync (const uint8_t * str, size_t len ) {
297324 char cur;
298- for (int j = 0 ; j < str. length () - 1 ; j++) {
325+ for (int j = 0 ; j < len - 1 ; j++) {
299326 cur = str[j];
300327 // read bytes until EOF or a byte with all bits set is encountered
301328 if ((cur & 0b11111111 ) != 0b11111111 ) continue ;
302329
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 ) {
308331 // if the next byte does not have its 3 most significant bits set it is
309332 // not the end of the framesync and it also cannot be the start of a
310333 // framesync so just skip over it here without the check
@@ -316,10 +339,14 @@ class MP3HeaderParser {
316339 return -1 ;
317340 }
318341
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 ());
323350 }
324351
325352 enum class FrameReason {
@@ -333,14 +360,7 @@ class MP3HeaderParser {
333360 INVALID_CRC,
334361 };
335362
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) {
344364 if (header.AudioVersion == FrameHeader::AudioVersionID::INVALID) {
345365 LOGI (" invalid mpeg version" );
346366 return FrameReason::INVALID_MPEG_VERSION;
@@ -351,7 +371,7 @@ class MP3HeaderParser {
351371 return FrameReason::INVALID_LAYER;
352372 }
353373
354- if (header.getBitrate () == FrameHeader::SpecialBitrate::INVALID) {
374+ if (header.getBitRate () == FrameHeader::SpecialBitrate::INVALID) {
355375 LOGI (" invalid bitrate" );
356376 return FrameReason::INVALID_BITRATE_FOR_VERSION;
357377 }
@@ -365,17 +385,17 @@ class MP3HeaderParser {
365385 // not allowed
366386 if (header.Layer == FrameHeader::LayerID::LAYER_2) {
367387 if (header.ChannelMode == FrameHeader::ChannelModeID::SINGLE) {
368- if (header.getBitrate () >= 224000 ) {
388+ if (header.getBitRate () >= 224000 ) {
369389 LOGI (" invalid bitrate >224000" );
370390 return FrameReason::INVALID_LAYER_II_BITRATE_AND_MODE;
371391 }
372392 } else {
373- if (header.getBitrate () >= 32000 && header.getBitrate () <= 56000 ) {
393+ if (header.getBitRate () >= 32000 && header.getBitRate () <= 56000 ) {
374394 LOGI (" invalid bitrate >32000" );
375395 return FrameReason::INVALID_LAYER_II_BITRATE_AND_MODE;
376396 }
377397
378- if (header.getBitrate () == 80000 ) {
398+ if (header.getBitRate () == 80000 ) {
379399 LOGI (" invalid bitrate >80000" );
380400 return FrameReason::INVALID_LAYER_II_BITRATE_AND_MODE;
381401 }
0 commit comments