shithub: sox

Download patch

ref: 73db929a561f8c81959f29304801898de49f1637
parent: bd8e1c93d7c4803c0d00b3d58a983cf928c510da
author: cbagwell <cbagwell>
date: Fri Nov 10 23:04:18 EST 2006

Code cleanup/bugfixes for reading mp3 tags and frames.

--- a/Changelog
+++ b/Changelog
@@ -7,6 +7,8 @@
 sox-12.18.3
 -----------
   o Fix writing MP3 files on AMD64 processors.
+  o More fixes to MP3 tag reading.  Sometimes tags were
+    detected as valid MP3 frames.
 
 sox-12.18.2
 -----------
--- a/src/mp3.c
+++ b/src/mp3.c
@@ -40,7 +40,6 @@
         unsigned char           *InputBuffer;
         st_ssize_t              cursamp;
         st_size_t               FrameCount;
-        int                     eof;
 #endif /*HAVE_LIBMAD*/
 #if defined(HAVE_LAME)
         lame_global_flags       *gfp;
@@ -55,183 +54,227 @@
 
 #define ID3_TAG_FLAG_FOOTERPRESENT 0x10
 
-int tagtype(const unsigned char *data, int length)
+static int tagtype(const unsigned char *data, int length)
 {
-  if (length >= 3 && data[0] == 'T' && data[1] == 'A' && data[2] == 'G')
+    /* TODO: It would be nice to look for Xing VBR headers
+     * or TLE fields in ID3 to detect length of file
+     * and set ft->length.
+     * For CBR, we should fstat the file and divided
+     * by bitrate to find length.
+     */
+
+    if (length >= 3 && data[0] == 'T' && data[1] == 'A' && data[2] == 'G')
+    {
         return 128; /* ID3V1 */
+    }
 
-  if (length >= 10 &&
-      (data[0] == 'I' && data[1] == 'D' && data[2] == '3') &&
-      data[3] < 0xff && data[4] < 0xff &&
-      data[6] < 0x80 && data[7] < 0x80 && data[8] < 0x80 && data[9] < 0x80)
-  {     /* ID3V2 */
+    if (length >= 10 &&
+        (data[0] == 'I' && data[1] == 'D' && data[2] == '3') &&
+        data[3] < 0xff && data[4] < 0xff &&
+        data[6] < 0x80 && data[7] < 0x80 && data[8] < 0x80 && data[9] < 0x80)
+    {     /* ID3V2 */
         unsigned char flags;
         unsigned int size;
         flags = data[5];
         size = (data[6]<<21) + (data[7]<<14) + (data[8]<<7) + data[9];
         if (flags & ID3_TAG_FLAG_FOOTERPRESENT)
-                size += 10;
+            size += 10;
         return 10 + size;
-  }
+    }
 
-  return 0;
+    return 0;
 }
 
-int st_mp3startread(ft_t ft) 
+/*
+ * (Re)fill the stream buffer whish is to be decoded.  If any data
+ * still exists in the buffer then they are first shifted to be
+ * front of the stream buffer.
+ */
+static int st_mp3_input(ft_t ft)
 {
-        struct mp3priv *p = (struct mp3priv *) ft->priv;
-        size_t ReadSize;
+    struct mp3priv *p = (struct mp3priv *) ft->priv;
+    size_t bytes_read;
+    size_t remaining;
 
-        p->Stream=(struct mad_stream *)malloc(sizeof(struct mad_stream));
-        if (p->Stream == NULL){
-          st_fail_errno(ft, ST_ENOMEM, "Could not allocate memory");
-          return ST_EOF;
-        }
-        
-        p->Frame=(struct mad_frame *)malloc(sizeof(struct mad_frame));
-        if (p->Frame == NULL){
-          st_fail_errno(ft, ST_ENOMEM, "Could not allocate memory");
-          free(p->Stream);
-          return ST_EOF;
-        }
-        
-        p->Synth=(struct mad_synth *)malloc(sizeof(struct mad_synth));
-        if (p->Synth == NULL){
-          st_fail_errno(ft, ST_ENOMEM, "Could not allocate memory");
-          free(p->Stream);
-          free(p->Frame);
-          return ST_EOF;
-        }
-        
-        p->Timer=(mad_timer_t *)malloc(sizeof(mad_timer_t));
-        if (p->Timer == NULL){
-          st_fail_errno(ft, ST_ENOMEM, "Could not allocate memory");
-          free(p->Stream);
-          free(p->Frame);
-          free(p->Synth);
-          return ST_EOF;
-        }
-        
-        p->InputBuffer=(unsigned char *)malloc(INPUT_BUFFER_SIZE);
-        if (p->InputBuffer == NULL){
-          st_fail_errno(ft, ST_ENOMEM, "Could not allocate memory");
-          free(p->Stream);
-          free(p->Frame);
-          free(p->Synth);
-          free(p->Timer);
-          return ST_EOF;
-        }
-        
-        mad_stream_init(p->Stream);
-        mad_frame_init(p->Frame);
-        mad_synth_init(p->Synth);
-        mad_timer_reset(p->Timer);
+    remaining = p->Stream->bufend - p->Stream->next_frame;
 
-        ft->info.encoding = ST_ENCODING_MP3;
-        ft->info.size = ST_SIZE_WORD;
+    /* libmad does not consume all the buffer it's given. Some
+     * data, part of a truncated frame, is left unused at the
+     * end of the buffer. That data must be put back at the
+     * beginning of the buffer and taken in account for
+     * refilling the buffer. This means that the input buffer
+     * must be large enough to hold a complete frame at the
+     * highest observable bit-rate (currently 448 kb/s).
+     * TODO: Is 2016 bytes the size of the largest frame?
+     * (448000*(1152/32000))/8
+     */
+    memmove(p->InputBuffer, p->Stream->next_frame, remaining);
 
-        /* We need to decode the first frame,
-         * so we know the output format */
-more_data:
-        ReadSize=st_readbuf(ft, p->InputBuffer, 1, INPUT_BUFFER_SIZE);
-        if(ReadSize<=0)
-        {
-                if(st_error(ft))
-                        st_fail_errno(ft,ST_EOF,"read error on bitstream");
-                if(st_eof(ft))
-                        st_fail_errno(ft,ST_EOF,"end of input stream");
-                return(ST_EOF);
-        }
-        
-        mad_stream_buffer(p->Stream,p->InputBuffer,ReadSize);
-        p->Stream->error = 0;
+    bytes_read = st_readbuf(ft, p->InputBuffer+remaining, 1, 
+                            INPUT_BUFFER_SIZE-remaining);
+    if (bytes_read == 0)
+    {
+        return ST_EOF;
+    }
 
-        /* Find a valid frame before starting up.  This makes sure
-         * that we have a valid MP3 and also skips past ID3v2 tags
-         * at the beginning of the audio file.
-         */
-        while(mad_frame_decode(p->Frame,p->Stream)) {
-            int tagsize;
-            size_t remaining;
+    mad_stream_buffer(p->Stream, p->InputBuffer, bytes_read+remaining);
+    p->Stream->error = 0;
 
-            remaining = p->Stream->bufend - p->Stream->this_frame;
-            if (remaining <= 8) {
-                /* Read another buffer full of data. */
-                memmove(p->InputBuffer, p->Stream->this_frame, remaining);
-
-                ReadSize=st_readbuf(ft, p->InputBuffer+remaining, 1, INPUT_BUFFER_SIZE-remaining);
-                if (ReadSize <= 0) {
-                  st_fail_errno(ft,ST_EOF,"The file is not an MP3 file or it is corrupted");
-                  return ST_EOF;
-                }
+    return ST_SUCCESS;
+}
 
-                remaining+=ReadSize;
-                mad_stream_buffer(p->Stream, p->InputBuffer, remaining);
-                p->Stream->error = 0;
-            }
+/* Attempts to read an ID3 tag at the current location in stream and
+ * consume it all.  Returns ST_EOF if no tag is found.  Its up to
+ * caller to recover.
+ * */
+static int st_mp3_inputtag(ft_t ft)
+{
+    struct mp3priv *p = (struct mp3priv *) ft->priv;
+    int rc = ST_EOF;
+    size_t bytes_read;
+    size_t remaining;
+    size_t tagsize;
 
-            /* Skip past this frame, based on tag size.  If invalid
-             * tag then Walk threw the stream one byte at a time (tagsize=1)
-             * until we find a valid frame.  Previous if() will
-             * abort once we got a certain distance.
-             */
-            if ((tagsize=tagtype(p->Stream->this_frame, remaining)) == 0)
-                tagsize = 1; /* Walk through the stream. */
 
-            /* ID3v2 tags can be any size.  That means they can
-             * span a buffer larger then INPUT_BUFFER_SIZE.  That
-             * means that we really need a loop to continue reading
-             * more data.
-             */
-            if (tagsize >= remaining)
-            {
-                /* Discard the remaining data and read the rest of the tag
-                 * data from the file and start over.
-                 */
-                tagsize -= remaining;
-                while (tagsize > 0)
-                {
-                    if (tagsize < INPUT_BUFFER_SIZE)
-                        ReadSize=st_readbuf(ft, p->InputBuffer, 1, tagsize);
-                    else
-                        ReadSize=st_readbuf(ft, p->InputBuffer, 1, INPUT_BUFFER_SIZE);
-                    tagsize -= ReadSize;
-                }
-                goto more_data;
-            }
+    /* FIXME: This needs some more work if we are to ever
+     * look at the ID3 frame.  This is because the Stream
+     * may not be able to hold the complete ID3 frame.
+     * We should consume the whole frame inside tagtype()
+     * instead of outside of tagframe().  That would support
+     * recovering when Stream contains less then 8-bytes (header)
+     * and also when ID3v2 is bigger then Stream buffer size.
+     * Need to pass in stream so that buffer can be
+     * consumed as well as letting additional data to be
+     * read in.
+     */
+    remaining = p->Stream->bufend - p->Stream->next_frame;
+    if ((tagsize = tagtype(p->Stream->this_frame, remaining)))
+    {
+        mad_stream_skip(p->Stream, tagsize);
+        rc = ST_SUCCESS;
+    }
+
+    /* We know that a valid frame hasn't been found yet
+     * so help libmad out and go back into frame seek mode.
+     * This is true whether an ID3 tag was found or not.
+     */
+    mad_stream_sync(p->Stream);
+
+    return rc;
+}
 
-            mad_stream_skip(p->Stream, tagsize);
-        }
+int st_mp3startread(ft_t ft) 
+{
+    struct mp3priv *p = (struct mp3priv *) ft->priv;
+    size_t ReadSize;
 
-        /* TODO: It would be nice to look for Xing VBR headers
-         * or TLE fields in ID3 to detect length of file
-         * and set ft->length.
-         * For CBR, we should fstat the file and divided
-         * by bitrate to find length.
-         */
+    p->Stream = NULL;
+    p->Frame = NULL;
+    p->Synth = NULL;
+    p->Timer = NULL;
+    p->InputBuffer = NULL;
 
-        switch(p->Frame->header.mode)
+    p->Stream=(struct mad_stream *)malloc(sizeof(struct mad_stream));
+    p->Frame=(struct mad_frame *)malloc(sizeof(struct mad_frame));
+    p->Synth=(struct mad_synth *)malloc(sizeof(struct mad_synth));
+    p->Timer=(mad_timer_t *)malloc(sizeof(mad_timer_t));
+    p->InputBuffer=(unsigned char *)malloc(INPUT_BUFFER_SIZE);
+
+    if (!p->Stream || !p->Frame || !p->Synth || 
+        !p->Timer || !p->InputBuffer)
+    {
+        st_fail_errno(ft, ST_ENOMEM, "Could not allocate memory");
+        goto error;
+    }
+
+    mad_stream_init(p->Stream);
+    mad_frame_init(p->Frame);
+    mad_synth_init(p->Synth);
+    mad_timer_reset(p->Timer);
+
+    ft->info.encoding = ST_ENCODING_MP3;
+    ft->info.size = ST_SIZE_WORD;
+
+    /* Decode at least one valid frame to find out the input
+     * format.  The decoded frame will be saved off so that it
+     * can be processed later.
+     */
+    ReadSize=st_readbuf(ft, p->InputBuffer, 1, INPUT_BUFFER_SIZE);
+    if(ReadSize<=0)
+    {
+        if(st_error(ft))
+            st_fail_errno(ft,ST_EOF,"read error on bitstream");
+        if(st_eof(ft))
+            st_fail_errno(ft,ST_EOF,"end of input stream");
+        return(ST_EOF);
+    }
+
+    mad_stream_buffer(p->Stream, p->InputBuffer, ReadSize);
+
+    /* Find a valid frame before starting up.  This makes sure
+     * that we have a valid MP3 and also skips past ID3v2 tags
+     * at the beginning of the audio file.
+     */
+    p->Stream->error = 0;
+    while (mad_frame_decode(p->Frame,p->Stream)) 
+    {
+        /* check whether input buffer needs a refill */
+        if (p->Stream->error == MAD_ERROR_BUFLEN)
         {
-                case MAD_MODE_SINGLE_CHANNEL:
-                case MAD_MODE_DUAL_CHANNEL:
-                case MAD_MODE_JOINT_STEREO:
-                case MAD_MODE_STEREO:
-                  ft->info.channels = MAD_NCHANNELS(&p->Frame->header);
-                  break;
-                default:
-                  st_fail_errno(ft,ST_EFMT,"Cannot determine number of channels");
-                  return ST_EOF;
+            if (st_mp3_input(ft) == ST_EOF)
+                return ST_EOF;
+
+            continue;
         }
 
-        p->FrameCount=1;
-        ft->info.rate=p->Frame->header.samplerate;
+        /* Consume any ID3 tags */
+        st_mp3_inputtag(ft);
 
-        mad_timer_add(p->Timer,p->Frame->header.duration);
-        mad_synth_frame(p->Synth,p->Frame);
-        p->cursamp = 0;
-        p->eof     = 0;
+        /* FIXME: We should probably detect when we've read
+         * a bunch of non-ID3 data and still haven't found a
+         * frame.  In that case we can abort early without
+         * scanning the whole file.
+         */
+        p->Stream->error = 0;
+    }
 
-        return ST_SUCCESS;
+    if (p->Stream->error)
+    {
+        st_fail_errno(ft,ST_EOF,"No valid MP3 frame found");
+        return ST_EOF;
+    }
+
+    switch(p->Frame->header.mode)
+    {
+        case MAD_MODE_SINGLE_CHANNEL:
+        case MAD_MODE_DUAL_CHANNEL:
+        case MAD_MODE_JOINT_STEREO:
+        case MAD_MODE_STEREO:
+            ft->info.channels = MAD_NCHANNELS(&p->Frame->header);
+            break;
+        default:
+            st_fail_errno(ft, ST_EFMT, "Cannot determine number of channels");
+            return ST_EOF;
+    }
+
+    p->FrameCount=1;
+
+    mad_timer_add(p->Timer,p->Frame->header.duration);
+    mad_synth_frame(p->Synth,p->Frame);
+    ft->info.rate=p->Synth->pcm.samplerate;
+
+    p->cursamp = 0;
+
+    return ST_SUCCESS;
+
+error:
+    if (p->Stream) free(p->Stream);
+    if (p->Frame) free(p->Frame);
+    if (p->Synth) free(p->Synth);
+    if (p->Timer) free(p->Timer);
+    if (p->InputBuffer) free(p->InputBuffer);
+
+    return ST_EOF;
 }
 
 /*
@@ -240,98 +283,67 @@
  * Place in buf[].
  * Return number of samples read.
  */
-
 st_ssize_t st_mp3read(ft_t ft, st_sample_t *buf, st_ssize_t len)
 {
-  struct mp3priv *p = (struct mp3priv *) ft->priv;
-  st_ssize_t donow,i,done=0;
-  mad_fixed_t sample;
-  int chan;
+    struct mp3priv *p = (struct mp3priv *) ft->priv;
+    st_ssize_t donow,i,done=0;
+    mad_fixed_t sample;
+    int chan;
 
-  do{
-    donow=MIN(len,(p->Synth->pcm.length - p->cursamp)*ft->info.channels);
-    i=0;
-    while(i<donow){
-      for(chan=0;chan<ft->info.channels;chan++){
-        sample=p->Synth->pcm.samples[chan][p->cursamp];
-        if (sample < -MAD_F_ONE)
-          sample=-MAD_F_ONE;
-        else if (sample >= MAD_F_ONE)
-          sample=MAD_F_ONE-1;
-        *buf++=(st_sample_t)(sample<<(32-1-MAD_F_FRACBITS));
-        i++;
-      }
-      p->cursamp++;
-    };
+    do {
+        donow=MIN(len,(p->Synth->pcm.length - p->cursamp)*ft->info.channels);
+        i=0;
+        while(i<donow){
+            for(chan=0;chan<ft->info.channels;chan++){
+                sample=p->Synth->pcm.samples[chan][p->cursamp];
+                if (sample < -MAD_F_ONE)
+                    sample=-MAD_F_ONE;
+                else if (sample >= MAD_F_ONE)
+                    sample=MAD_F_ONE-1;
+                *buf++=(st_sample_t)(sample<<(32-1-MAD_F_FRACBITS));
+                i++;
+            }
+            p->cursamp++;
+        };
 
-    len-=donow;
-    done+=donow;
+        len-=donow;
+        done+=donow;
 
-    if (len==0 || p->eof) break;
+        if (len==0) break;
 
-    /* check whether input buffer needs a refill */
-
-    if(p->Stream->error==MAD_ERROR_BUFLEN)
-      {
-        size_t          ReadSize, Remaining;
-        
-        /* libmad does not consume all the buffer it's given. Some
-         * datas, part of a truncated frame, is left unused at the
-         * end of the buffer. Those datas must be put back at the
-         * beginning of the buffer and taken in account for
-         * refilling the buffer. This means that the input buffer
-         * must be large enough to hold a complete frame at the
-         * highest observable bit-rate (currently 448 kb/s). XXX=XXX
-         * Is 2016 bytes the size of the largest frame?
-         * (448000*(1152/32000))/8
-         */
-        
-        Remaining=p->Stream->bufend - p->Stream->next_frame;
-        memmove(p->InputBuffer,p->Stream->next_frame,Remaining);
-
-        ReadSize=st_readbuf(ft, p->InputBuffer+Remaining, 1, 
-                         INPUT_BUFFER_SIZE-Remaining);
-        if(ReadSize == 0){
-          p->eof=1;
-          memset(p->InputBuffer+Remaining,0,MAD_BUFFER_GUARD);
-          ReadSize=MAD_BUFFER_GUARD;
+        /* check whether input buffer needs a refill */
+        if (p->Stream->error == MAD_ERROR_BUFLEN)
+        {
+            if (st_mp3_input(ft) == ST_EOF)
+                return ST_EOF;
         }
 
-        mad_stream_buffer(p->Stream,p->InputBuffer,ReadSize+Remaining);
-        p->Stream->error = 0;
-      }
-
-    if(mad_frame_decode(p->Frame,p->Stream)){
-      if(MAD_RECOVERABLE(p->Stream->error))
+        if (mad_frame_decode(p->Frame,p->Stream))
         {
-          int tagsize;
-          if ( (tagsize=tagtype(p->Stream->this_frame, p->Stream->bufend - p->Stream->this_frame)) == 0){
-            if (!p->eof)
-              st_report("recoverable frame level error (%s).\n",
-                        mad_stream_errorstr(p->Stream));
-          }
-          else mad_stream_skip(p->Stream,tagsize);
-          continue;
-        }
-      else
-        {
-          if(p->Stream->error==MAD_ERROR_BUFLEN)
-            continue;
-          else
+            if(MAD_RECOVERABLE(p->Stream->error))
             {
-              st_report("unrecoverable frame level error (%s).\n",
-                        mad_stream_errorstr(p->Stream));
-              return done;
+                st_mp3_inputtag(ft);
+                continue;
             }
+            else
+            {
+                if (p->Stream->error == MAD_ERROR_BUFLEN)
+                    continue;
+                else
+                {
+                    st_report("unrecoverable frame level error (%s).\n",
+                              mad_stream_errorstr(p->Stream));
+                    return done;
+                }
+            }
         }
-    }
-    p->FrameCount++;
-    mad_timer_add(p->Timer,p->Frame->header.duration);
-    mad_synth_frame(p->Synth,p->Frame);
-    p->cursamp=0;
-  }while(1);
+        p->FrameCount++;
+        mad_timer_add(p->Timer,p->Frame->header.duration);
+        mad_synth_frame(p->Synth,p->Frame);
+        p->cursamp=0;
+    } while(1);
 
-  return done;
+    return done;
 }
 
 int st_mp3stopread(ft_t ft)