shithub: sox

Download patch

ref: 0a9e2b9fc341754decd6b93e4faa1a4bbed0ee0e
parent: 993ff2b888fc03b4f6be1b3e45e9e683230d92e6
author: robs <robs>
date: Sun Oct 28 16:52:35 EDT 2007

initial mp3 duration support; please test

--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -64,6 +64,7 @@
 optional(HAVE_ALSA alsa/asoundlib.h asound snd_pcm_open alsa)
 optional(HAVE_AMRNB amrnb/sp_dec.h amrnb Decoder_Interface_init amr-nb)
 optional(HAVE_AMRWB amrwb/dec.h amrwb D_IF_init amr-wb)
+optional(HAVE_ID3TAG id3tag.h id3tag id3_file_open "")
 optional(HAVE_LIBAO ao/ao.h ao ao_play ao)
 optional(HAVE_FLAC FLAC/all.h FLAC FLAC__stream_encoder_new flac)
 optional(HAVE_MAD_H mad.h mad mad_stream_buffer mp3)
--- a/ChangeLog
+++ b/ChangeLog
@@ -11,10 +11,11 @@
 
   o Added support for non-standard, non-WAVE_FORMAT_EXTENSIBLE
     (esp. 24-bit) PCM wav.  (robs)
+  o Initial support for showing MP3 file duration with -V & -S.  (robs)
 
   Effects:
 
-  o Reimplemented reverb using freeverb.  (robs)
+  o Reimplemented reverb to be similar to freeverb.  (robs)
 
   Bug fixes:
 
--- a/configure.ac
+++ b/configure.ac
@@ -299,6 +299,26 @@
     fi
 fi
 
+dnl Check for id3tag libraries
+AC_ARG_WITH(id3tag,
+    AC_HELP_STRING([--without-id3tag],
+        [Don't try to use id3tag]),
+        [with_id3tag=$withval])
+using_id3tag=no
+if test "$with_id3tag" != "no"; then
+    using_id3tag=yes
+    AC_CHECK_HEADER(id3tag.h,
+        [AC_CHECK_LIB(id3tag, id3_file_open, MP3_LIBS="$MP3_LIBS -lid3tag",using_id3tag=no)],
+        using_id3tag=no)
+    if test "$with_id3tag" = "yes" -a "$using_id3tag" = "no"; then
+        AC_MSG_FAILURE([cannot find id3tag])
+    fi
+fi
+if test "$using_id3tag" = yes; then
+   AC_DEFINE(HAVE_ID3TAG, 1, [Define to 1 if you have id3tag.])
+fi
+AM_CONDITIONAL(HAVE_ID3TAG, test x$using_id3tag = xyes)
+
 dnl Test for LAME library.
 AC_ARG_WITH(lame,
     AC_HELP_STRING([--without-lame],
@@ -419,6 +439,7 @@
 echo "FLAC format....................... $using_flac"
 echo "ffmpeg formats.................... $using_ffmpeg"
 echo "MAD MP3 reader.................... $using_mad"
+echo "id3tag library.................... $using_id3tag"
 echo "LAME MP3 writer................... $using_lame"
 echo "AMR-WB format..................... $using_amr_wb"
 echo "AMR-NB format..................... $using_amr_nb"
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -149,7 +149,7 @@
 pkglib_LTLIBRARIES += libsox_fmt_amr_nb.la
 endif
 if HAVE_MP3
-libsox_fmt_mp3_la_SOURCES = mp3.c
+libsox_fmt_mp3_la_SOURCES = mp3.c mp3-duration.h
 libsox_fmt_mp3_la_LIBADD = libsox.la @MP3_LIBS@
 pkglib_LTLIBRARIES += libsox_fmt_mp3.la
 endif
@@ -251,7 +251,7 @@
     sox_LDADD += @AMR_NB_LIBS@
 endif
 if HAVE_MP3
-    libsox_la_SOURCES += mp3.c
+    libsox_la_SOURCES += mp3.c mp3-duration.h
     libsox_la_LIBADD += @MP3_LIBS@
     sox_LDADD += @MP3_LIBS@
 endif
--- /dev/null
+++ b/src/mp3-duration.h
@@ -1,0 +1,179 @@
+/*
+ * Determine MP3 duration
+ * Copyright (c) 2007 robs@users.sourceforge.net
+ * Based on original ideas by Regis Boudin, Thibaut Varene & Pascal Giard
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, write to the Free Software Foundation,
+ * Fifth Floor, 51 Franklin Street, Boston, MA 02111-1301, USA.
+ */
+
+#include <sys/stat.h>
+
+#if HAVE_ID3TAG && HAVE_UNISTD_H
+
+static id3_utf8_t * utf8_id3tag_findframe(
+    struct id3_tag * tag, const char * const frameid, unsigned index)
+{
+  struct id3_frame const * frame = id3_tag_findframe(tag, frameid, index);
+  if (frame) {
+    union id3_field  const * field = id3_frame_field(frame, 1);
+    unsigned nstrings = id3_field_getnstrings(field);
+    while (nstrings--){
+      id3_ucs4_t const * ucs4 = id3_field_getstrings(field, nstrings);
+      if (ucs4)
+        return id3_ucs4_utf8duplicate(ucs4); /* Must call free() on this */
+    }
+  }
+  return NULL;
+}
+
+static sox_size_t id3tag_duration_ms(FILE * fp)
+{
+  struct id3_file   * id3struct;
+  struct id3_tag    * tag;
+  id3_utf8_t        * utf8;
+  sox_size_t        duration_ms = 0;
+  int               fd = dup(fileno(fp));
+
+  if ((id3struct = id3_file_fdopen(fd, ID3_FILE_MODE_READONLY))) {
+    if ((tag = id3_file_tag(id3struct)) && tag->frames)
+      if ((utf8 = utf8_id3tag_findframe(tag, "TLEN", 0))) {
+        if (atoi((char *)utf8) > 0)
+          duration_ms = atoi((char *)utf8);
+        free(utf8);
+      }
+    id3_file_close(id3struct);
+  }
+  else close(fd);
+  return duration_ms;
+}
+
+#endif
+
+static unsigned long xing_frames(struct mad_bitptr ptr, unsigned bitlen)
+{
+  #define XING_MAGIC ( ('X' << 24) | ('i' << 16) | ('n' << 8) | 'g' )
+  if (bitlen >= 96 && mad_bit_read(&ptr, 32) == XING_MAGIC &&
+      (mad_bit_read(&ptr, 32) & 1 )) /* XING_FRAMES */
+    return mad_bit_read(&ptr, 32);
+  return 0;
+}
+
+static void mad_timer_mult(mad_timer_t * t, double d)
+{
+  t->seconds = d *= (t->seconds + t->fraction * (1. / MAD_TIMER_RESOLUTION));
+  t->fraction = (d - t->seconds) * MAD_TIMER_RESOLUTION + .5;
+}
+
+static sox_size_t mp3_duration_ms(FILE * fp, unsigned char *buffer)
+{
+  struct mad_stream   mad_stream;
+  struct mad_header   mad_header;
+  struct mad_frame    mad_frame;
+  mad_timer_t         time = mad_timer_zero;
+  sox_size_t          initial_bitrate, tagsize = 0, consumed = 0, frames = 0;
+  sox_bool            vbr = sox_false, padded = sox_false;
+
+#if HAVE_ID3TAG && HAVE_UNISTD_H
+  sox_size_t duration_ms = id3tag_duration_ms(fp);
+  if (duration_ms) {
+    sox_debug("got exact duration from ID3 TLEN");
+    return duration_ms;
+  }
+#endif
+
+  mad_stream_init(&mad_stream);
+  mad_header_init(&mad_header);
+  mad_frame_init(&mad_frame);
+
+  while (sox_true) {
+    int read, padding = 0;
+    size_t leftover = mad_stream.bufend - mad_stream.next_frame;
+    memcpy(buffer, mad_stream.this_frame, leftover);
+    read = fread(buffer + leftover, 1, INPUT_BUFFER_SIZE - leftover, fp);
+    if (read <= 0) {
+      sox_debug("got exact duration by scan to EOF (frames=%u leftover=%u)", frames, leftover);
+      break;
+    }
+    for (; !padded && padding < read && !buffer[padding]; ++padding);
+    padded = sox_true;
+    mad_stream_buffer(&mad_stream, buffer + padding, leftover + read - padding);
+
+    while (sox_true) {
+      mad_stream.error = MAD_ERROR_NONE;
+      if (mad_header_decode(&mad_header, &mad_stream) == -1) {
+        if (mad_stream.error == MAD_ERROR_BUFLEN)
+          break;  /* Get some more data from the file */
+        if (!MAD_RECOVERABLE(mad_stream.error)) {
+          sox_warn("unrecoverable MAD error");
+          break;
+        }
+        if (mad_stream.error == MAD_ERROR_LOSTSYNC) {
+          unsigned available = (mad_stream.bufend - mad_stream.this_frame);
+          tagsize = tagtype(mad_stream.this_frame, available);
+          if (tagsize > 0) {   /* It's just some ID3 tags, so skip */
+            if (tagsize >= available) {
+              fseeko(fp, (off_t)(tagsize - available), SEEK_CUR);
+              padded = sox_false;
+            }
+            mad_stream_skip(&mad_stream, min(tagsize, available));
+            continue;
+          }
+          sox_warn("MAD lost sync");
+          continue;
+        }
+        sox_warn("recoverable MAD error");
+        continue;
+      }
+
+      mad_timer_add(&time, mad_header.duration);
+      consumed += mad_stream.next_frame - mad_stream.this_frame;
+
+      if (!frames) {
+        initial_bitrate = mad_header.bitrate;
+
+        /* Get the precise frame count from the XING header if present */
+        mad_frame.header = mad_header;
+        if (mad_frame_decode(&mad_frame, &mad_stream) == -1)
+          if (!MAD_RECOVERABLE(mad_stream.error)) {
+            sox_warn("unrecoverable MAD error");
+            break;
+          }
+        if ((frames = xing_frames(mad_stream.anc_ptr, mad_stream.anc_bitlen))) {
+          mad_timer_multiply(&time, (signed long)frames);
+          sox_debug("got exact duration from XING frame count (%u)", frames);
+          break;
+        }
+      }
+      else vbr |= initial_bitrate != mad_header.bitrate;
+
+      /* If not VBR, we can time just a few frames then extrapolate */
+      if (++frames == 10 && !vbr) {
+        struct stat filestat;
+        fstat(fileno(fp), &filestat);
+        mad_timer_mult(&time, (double)(filestat.st_size - tagsize) / consumed);
+        sox_debug("got approx. duration by FBR extrapolation");
+        break;
+      }
+    }
+
+    if (mad_stream.error != MAD_ERROR_BUFLEN)
+      break;
+  }
+  mad_frame_finish(&mad_frame);
+  mad_header_finish(&mad_header);
+  mad_stream_finish(&mad_stream);
+  rewind(fp);
+  return mad_timer_count(time, MAD_UNITS_MILLISECONDS);
+}
--- a/src/mp3.c
+++ b/src/mp3.c
@@ -23,6 +23,13 @@
 #include <math.h>
 #endif
 
+#if HAVE_ID3TAG && HAVE_UNISTD_H
+  #include <id3tag.h>
+  #include <unistd.h>
+#else
+  #define ID3_TAG_FLAG_FOOTERPRESENT 0x10
+#endif
+
 #define INPUT_BUFFER_SIZE       (sox_globals.bufsiz)
 
 /* Private data */
@@ -47,8 +54,6 @@
    from MAD's libid3tag, so we don't have to link to it
    Returns 0 if the frame is not an ID3 tag, tag length if it is */
 
-#define ID3_TAG_FLAG_FOOTERPRESENT 0x10
-
 static int tagtype(const unsigned char *data, size_t length)
 {
     /* TODO: It would be nice to look for Xing VBR headers
@@ -71,15 +76,18 @@
         unsigned char flags;
         unsigned int size;
         flags = data[5];
-        size = (data[6]<<21) + (data[7]<<14) + (data[8]<<7) + data[9];
+        size = 10 + (data[6]<<21) + (data[7]<<14) + (data[8]<<7) + data[9];
         if (flags & ID3_TAG_FLAG_FOOTERPRESENT)
             size += 10;
-        return 10 + size;
+        for (; size < length && ! data[size]; ++size);  /* Consume padding */
+        return size;
     }
 
     return 0;
 }
 
+#include "mp3-duration.h"
+
 /*
  * (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
@@ -174,6 +182,9 @@
     p->Timer=(mad_timer_t *)xmalloc(sizeof(mad_timer_t));
     p->InputBuffer=(unsigned char *)xmalloc(INPUT_BUFFER_SIZE);
 
+    if (ft->seekable)
+      ft->length = mp3_duration_ms(ft->fp, p->InputBuffer);
+
     mad_stream_init(p->Stream);
     mad_frame_init(p->Frame);
     mad_synth_init(p->Synth);
@@ -249,6 +260,8 @@
     mad_timer_add(p->Timer,p->Frame->header.duration);
     mad_synth_frame(p->Synth,p->Frame);
     ft->signal.rate=p->Synth->pcm.samplerate;
+    ft->length = ft->length * .001 * ft->signal.rate + .5;
+    ft->length *= ft->signal.channels;  /* Keep separate from line above! */
 
     p->cursamp = 0;
 
--- a/src/sox.h
+++ b/src/sox.h
@@ -28,9 +28,9 @@
 /* The following is the API version of libSoX.  It is not meant
  * to follow the version number of SoX but it has historically.
  * Please do not count on these numbers being in sync.
- * The following is at 14.0.0
+ * The following is at 14.0.1
  */
-#define SOX_LIB_VERSION_CODE 0x0e0000
+#define SOX_LIB_VERSION_CODE 0x0e0001
 #define SOX_LIB_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
 
 const char *sox_version(void);   /* Returns version number */
--- a/src/soxconfig.h.cmake
+++ b/src/soxconfig.h.cmake
@@ -1,23 +1,24 @@
-#define PACKAGE_VERSION "14.0.0"
+#define PACKAGE_VERSION "14.0.1"
 
 #cmakedefine EXTERNAL_GSM             1
 #cmakedefine HAVE_ALSA                1
+#cmakedefine HAVE_AMRNB               1
+#cmakedefine HAVE_AMRWB               1
 #cmakedefine HAVE_BYTESWAP_H          1
+#cmakedefine HAVE_FFMPEG              1
+#cmakedefine HAVE_FLAC                1
 #cmakedefine HAVE_FSEEKO              1
 #cmakedefine HAVE_GETOPT_LONG         1
 #cmakedefine HAVE_GETTIMEOFDAY        1
+#cmakedefine HAVE_ID3TAG              1
 #cmakedefine HAVE_INTTYPES_H          1
 #cmakedefine HAVE_IO_H                1
 #cmakedefine HAVE_LAME_LAME_H         1
-#cmakedefine HAVE_AMRNB               1
-#cmakedefine HAVE_AMRWB               1
 #cmakedefine HAVE_LIBAO               1
-#cmakedefine HAVE_FFMPEG              1
-#cmakedefine HAVE_FLAC                1
-#cmakedefine HAVE_OGG_VORBIS          1
 #cmakedefine HAVE_LTDL_H              1
 #cmakedefine HAVE_MACHINE_SOUNDCARD_H 1
 #cmakedefine HAVE_MAD_H               1
+#cmakedefine HAVE_OGG_VORBIS          1
 #cmakedefine HAVE_POPEN               1
 #cmakedefine HAVE_SAMPLERATE_H        1
 #cmakedefine HAVE_SNDFILE_1_0_12      1