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