We are currently migrating Bugzilla to GitHub issues.
Any changes made to the bug tracker now will be lost, so please do not post new bugs or make changes to them.
When we're done, all bug URLs will redirect to their equivalent location on the new bug tracker.

Bug 269 - Patch to add support for libmad as optional mp3 playback
Summary: Patch to add support for libmad as optional mp3 playback
Status: RESOLVED FIXED
Alias: None
Product: SDL_mixer
Classification: Unclassified
Component: misc (show other bugs)
Version: unspecified
Hardware: All All
: P2 enhancement
Assignee: Sam Lantinga
QA Contact: Sam Lantinga
URL: http://www.ddrose.com/~drose/SDL_mixe...
Keywords:
Depends on:
Blocks:
 
Reported: 2006-07-01 01:34 UTC by David Rose
Modified: 2007-07-14 22:34 UTC (History)
0 users

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description David Rose 2006-07-01 01:34:26 UTC
This patch adds optional support for libmad, to be selected at configure time.  If the user selects --disable-music-mp3 and --enable-music-mp3-mad, then the use of the smpeg library will be disabled, while the libmad library will be configured in its place for mp3 playback.  (It is also possible to configure both of them on in one build, in which case smpeg will be chosen most of the time at runtime, unless you name a file with the .mad extension.)

The patch is built against SDL_mixer-1.2.7.

libmad is particularly useful for embedded devices, which might not have hardware floating-point support.  On such devices, smpeg is not usable as an mp3 library, whereas libmad performs quite nicely.  Even on the PC, libmad seems to run slightly faster than smpeg in my test cases.

As Ryan Gordon pointed out on the mailing list, libmad is GPL rather than LGPL, which means that an end-user who enables libmad must be prepared to release his application under a GPL-compatible license.  This is a good reason to have libmad support off by default.  However, this issue does not impact SDL or SDL_mixer itself, which do not themselves incorporate libmad; it only affects users who decide to enable this feature and build a version of SDL_mixer that does incorporate libmad.

The patch is pasted into this bug report below.  In case the web form damages it, it is also available at the above URL.

Thanks!
David Rose

david@ddrose.com

Index: README
===================================================================
RCS file: /cvs/SDL_mixer/README,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -u -r1.1.1.1 -r1.2
--- README	29 Jun 2006 06:06:41 -0000	1.1.1.1
+++ README	29 Jun 2006 06:09:05 -0000	1.2
@@ -7,7 +7,7 @@
 Due to popular demand, here is a simple multi-channel audio mixer.
 It supports 8 channels of 16 bit stereo audio, plus a single channel
 of music, mixed by the popular MikMod MOD, Timidity MIDI and SMPEG MP3
-libraries.
+(or libmad MP3) libraries.
 
 See the header file SDL_mixer.h and the examples playwave.c and playmus.c
 for documentation on this mixer library.
@@ -16,7 +16,8 @@
 files as audio samples, and can load MIDI files via Timidity and the
 following music formats via MikMod:  .MOD .S3M .IT .XM. It can load
 Ogg Vorbis streams as music if built with the Ogg Vorbis libraries,
-and finally it can load MP3 music using the SMPEG library.
+and finally it can load MP3 music using either the SMPEG or the libmad
+libraries.
 
 The process of mixing MIDI files to wave output is very CPU intensive,
 so if playing regular WAVE files sound great, but playing MIDI files
Index: SDL_mixer.h
===================================================================
RCS file: /cvs/SDL_mixer/SDL_mixer.h,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -u -r1.1.1.1 -r1.2
--- SDL_mixer.h	29 Jun 2006 06:06:41 -0000	1.1.1.1
+++ SDL_mixer.h	29 Jun 2006 06:09:05 -0000	1.2
@@ -20,7 +20,7 @@
     slouken@libsdl.org
 */
 
-/* $Id: SDL_mixer.h,v 1.1.1.1 2006/06/29 06:06:41 drose Exp $ */
+/* $Id: SDL_mixer.h,v 1.2 2006/06/29 06:09:05 drose Exp $ */
 
 #ifndef _SDL_MIXER_H
 #define _SDL_MIXER_H
@@ -103,7 +103,8 @@
 	MUS_MOD,
 	MUS_MID,
 	MUS_OGG,
-	MUS_MP3
+	MUS_MP3,
+	MUS_MP3_MAD
 } Mix_MusicType;
 
 /* The internal format for a music chunk interpreted via mikmod */
Index: SDL_mixer.spec
===================================================================
RCS file: /cvs/SDL_mixer/SDL_mixer.spec,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -u -r1.1.1.1 -r1.2
--- SDL_mixer.spec	29 Jun 2006 06:06:41 -0000	1.1.1.1
+++ SDL_mixer.spec	29 Jun 2006 06:09:05 -0000	1.2
@@ -16,7 +16,7 @@
 Due to popular demand, here is a simple multi-channel audio mixer.
 It supports 4 channels of 16 bit stereo audio, plus a single channel
 of music, mixed by the popular MikMod MOD, Timidity MIDI and SMPEG MP3
-libraries.
+(or libmad MP3) libraries.
 
 %package devel
 Summary: Libraries, includes and more to develop SDL applications.
@@ -27,7 +27,7 @@
 Due to popular demand, here is a simple multi-channel audio mixer.
 It supports 4 channels of 16 bit stereo audio, plus a single channel
 of music, mixed by the popular MikMod MOD, Timidity MIDI and SMPEG MP3
-libraries.
+(or libmad MP3) libraries.
 
 %prep
 %setup 
Index: SDL_mixer.spec.in
===================================================================
RCS file: /cvs/SDL_mixer/SDL_mixer.spec.in,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -u -r1.1.1.1 -r1.2
--- SDL_mixer.spec.in	29 Jun 2006 06:06:07 -0000	1.1.1.1
+++ SDL_mixer.spec.in	29 Jun 2006 06:09:05 -0000	1.2
@@ -16,7 +16,7 @@
 Due to popular demand, here is a simple multi-channel audio mixer.
 It supports 4 channels of 16 bit stereo audio, plus a single channel
 of music, mixed by the popular MikMod MOD, Timidity MIDI and SMPEG MP3
-libraries.
+(or libmad MP3) libraries.
 
 %package devel
 Summary: Libraries, includes and more to develop SDL applications.
@@ -27,7 +27,7 @@
 Due to popular demand, here is a simple multi-channel audio mixer.
 It supports 4 channels of 16 bit stereo audio, plus a single channel
 of music, mixed by the popular MikMod MOD, Timidity MIDI and SMPEG MP3
-libraries.
+(or libmad MP3) libraries.
 
 %prep
 %setup 
Index: configure
===================================================================
RCS file: /cvs/SDL_mixer/configure,v
retrieving revision 1.1.1.1
retrieving revision 1.3
diff -u -r1.1.1.1 -r1.3
--- configure	29 Jun 2006 06:06:41 -0000	1.1.1.1
+++ configure	1 Jul 2006 05:16:29 -0000	1.3
@@ -1047,6 +1047,7 @@
   --disable-smpegtest       Do not try to compile and run a test SMPEG program
   --enable-music-mp3-shared
                           dynamically load MP3 support [default=yes]
+  --enable-music-mp3-mad  enable MP3 music via libmad [default=no]
 
 Optional Packages:
   --with-PACKAGE[=ARG]    use PACKAGE [ARG=yes]
@@ -3155,7 +3156,7 @@
   ;;
 *-*-irix6*)
   # Find out which ABI we are using.
-  echo '#line 3158 "configure"' > conftest.$ac_ext
+  echo '#line 3159 "configure"' > conftest.$ac_ext
   if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
   (eval $ac_compile) 2>&5
   ac_status=$?
@@ -4887,7 +4888,7 @@
 
 
 # Provide some information about the compiler.
-echo "$as_me:4890:" \
+echo "$as_me:4891:" \
      "checking for Fortran 77 compiler version" >&5
 ac_compiler=`set X $ac_compile; echo $2`
 { (eval echo "$as_me:$LINENO: \"$ac_compiler --version </dev/null >&5\"") >&5
@@ -5960,11 +5961,11 @@
    -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
    -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
    -e 's:$: $lt_compiler_flag:'`
-   (eval echo "\"\$as_me:5963: $lt_compile\"" >&5)
+   (eval echo "\"\$as_me:5964: $lt_compile\"" >&5)
    (eval "$lt_compile" 2>conftest.err)
    ac_status=$?
    cat conftest.err >&5
-   echo "$as_me:5967: \$? = $ac_status" >&5
+   echo "$as_me:5968: \$? = $ac_status" >&5
    if (exit $ac_status) && test -s "$ac_outfile"; then
      # The compiler can only warn and ignore the option if not recognized
      # So say no if there are warnings other than the usual output.
@@ -6228,11 +6229,11 @@
    -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
    -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
    -e 's:$: $lt_compiler_flag:'`
-   (eval echo "\"\$as_me:6231: $lt_compile\"" >&5)
+   (eval echo "\"\$as_me:6232: $lt_compile\"" >&5)
    (eval "$lt_compile" 2>conftest.err)
    ac_status=$?
    cat conftest.err >&5
-   echo "$as_me:6235: \$? = $ac_status" >&5
+   echo "$as_me:6236: \$? = $ac_status" >&5
    if (exit $ac_status) && test -s "$ac_outfile"; then
      # The compiler can only warn and ignore the option if not recognized
      # So say no if there are warnings other than the usual output.
@@ -6332,11 +6333,11 @@
    -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
    -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
    -e 's:$: $lt_compiler_flag:'`
-   (eval echo "\"\$as_me:6335: $lt_compile\"" >&5)
+   (eval echo "\"\$as_me:6336: $lt_compile\"" >&5)
    (eval "$lt_compile" 2>out/conftest.err)
    ac_status=$?
    cat out/conftest.err >&5
-   echo "$as_me:6339: \$? = $ac_status" >&5
+   echo "$as_me:6340: \$? = $ac_status" >&5
    if (exit $ac_status) && test -s out/conftest2.$ac_objext
    then
      # The compiler can only warn and ignore the option if not recognized
@@ -8679,7 +8680,7 @@
   lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
   lt_status=$lt_dlunknown
   cat > conftest.$ac_ext <<EOF
-#line 8682 "configure"
+#line 8683 "configure"
 #include "confdefs.h"
 
 #if HAVE_DLFCN_H
@@ -8779,7 +8780,7 @@
   lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
   lt_status=$lt_dlunknown
   cat > conftest.$ac_ext <<EOF
-#line 8782 "configure"
+#line 8783 "configure"
 #include "confdefs.h"
 
 #if HAVE_DLFCN_H
@@ -11123,11 +11124,11 @@
    -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
    -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
    -e 's:$: $lt_compiler_flag:'`
-   (eval echo "\"\$as_me:11126: $lt_compile\"" >&5)
+   (eval echo "\"\$as_me:11127: $lt_compile\"" >&5)
    (eval "$lt_compile" 2>conftest.err)
    ac_status=$?
    cat conftest.err >&5
-   echo "$as_me:11130: \$? = $ac_status" >&5
+   echo "$as_me:11131: \$? = $ac_status" >&5
    if (exit $ac_status) && test -s "$ac_outfile"; then
      # The compiler can only warn and ignore the option if not recognized
      # So say no if there are warnings other than the usual output.
@@ -11227,11 +11228,11 @@
    -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
    -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
    -e 's:$: $lt_compiler_flag:'`
-   (eval echo "\"\$as_me:11230: $lt_compile\"" >&5)
+   (eval echo "\"\$as_me:11231: $lt_compile\"" >&5)
    (eval "$lt_compile" 2>out/conftest.err)
    ac_status=$?
    cat out/conftest.err >&5
-   echo "$as_me:11234: \$? = $ac_status" >&5
+   echo "$as_me:11235: \$? = $ac_status" >&5
    if (exit $ac_status) && test -s out/conftest2.$ac_objext
    then
      # The compiler can only warn and ignore the option if not recognized
@@ -12799,11 +12800,11 @@
    -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
    -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
    -e 's:$: $lt_compiler_flag:'`
-   (eval echo "\"\$as_me:12802: $lt_compile\"" >&5)
+   (eval echo "\"\$as_me:12803: $lt_compile\"" >&5)
    (eval "$lt_compile" 2>conftest.err)
    ac_status=$?
    cat conftest.err >&5
-   echo "$as_me:12806: \$? = $ac_status" >&5
+   echo "$as_me:12807: \$? = $ac_status" >&5
    if (exit $ac_status) && test -s "$ac_outfile"; then
      # The compiler can only warn and ignore the option if not recognized
      # So say no if there are warnings other than the usual output.
@@ -12903,11 +12904,11 @@
    -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
    -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
    -e 's:$: $lt_compiler_flag:'`
-   (eval echo "\"\$as_me:12906: $lt_compile\"" >&5)
+   (eval echo "\"\$as_me:12907: $lt_compile\"" >&5)
    (eval "$lt_compile" 2>out/conftest.err)
    ac_status=$?
    cat out/conftest.err >&5
-   echo "$as_me:12910: \$? = $ac_status" >&5
+   echo "$as_me:12911: \$? = $ac_status" >&5
    if (exit $ac_status) && test -s out/conftest2.$ac_objext
    then
      # The compiler can only warn and ignore the option if not recognized
@@ -15112,11 +15113,11 @@
    -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
    -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
    -e 's:$: $lt_compiler_flag:'`
-   (eval echo "\"\$as_me:15115: $lt_compile\"" >&5)
+   (eval echo "\"\$as_me:15116: $lt_compile\"" >&5)
    (eval "$lt_compile" 2>conftest.err)
    ac_status=$?
    cat conftest.err >&5
-   echo "$as_me:15119: \$? = $ac_status" >&5
+   echo "$as_me:15120: \$? = $ac_status" >&5
    if (exit $ac_status) && test -s "$ac_outfile"; then
      # The compiler can only warn and ignore the option if not recognized
      # So say no if there are warnings other than the usual output.
@@ -15380,11 +15381,11 @@
    -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
    -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
    -e 's:$: $lt_compiler_flag:'`
-   (eval echo "\"\$as_me:15383: $lt_compile\"" >&5)
+   (eval echo "\"\$as_me:15384: $lt_compile\"" >&5)
    (eval "$lt_compile" 2>conftest.err)
    ac_status=$?
    cat conftest.err >&5
-   echo "$as_me:15387: \$? = $ac_status" >&5
+   echo "$as_me:15388: \$? = $ac_status" >&5
    if (exit $ac_status) && test -s "$ac_outfile"; then
      # The compiler can only warn and ignore the option if not recognized
      # So say no if there are warnings other than the usual output.
@@ -15484,11 +15485,11 @@
    -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
    -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
    -e 's:$: $lt_compiler_flag:'`
-   (eval echo "\"\$as_me:15487: $lt_compile\"" >&5)
+   (eval echo "\"\$as_me:15488: $lt_compile\"" >&5)
    (eval "$lt_compile" 2>out/conftest.err)
    ac_status=$?
    cat out/conftest.err >&5
-   echo "$as_me:15491: \$? = $ac_status" >&5
+   echo "$as_me:15492: \$? = $ac_status" >&5
    if (exit $ac_status) && test -s out/conftest2.$ac_objext
    then
      # The compiler can only warn and ignore the option if not recognized
@@ -20408,6 +20409,74 @@
         fi
     fi
 fi
+# Check whether --enable-music-mp3-mad or --disable-music-mp3-mad was given.
+if test "${enable_music_mp3_mad+set}" = set; then
+  enableval="$enable_music_mp3_mad"
+
+else
+  enable_music_mp3_mad=no
+fi;
+if test x$enable_music_mp3_mad = xyes; then
+    echo "$as_me:$LINENO: checking for libmad headers" >&5
+echo $ECHO_N "checking for libmad headers... $ECHO_C" >&6
+    have_libmad=no
+    cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+
+     #include "mad.h"
+
+int
+main ()
+{
+
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+  (eval $ac_compile) 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } &&
+	 { ac_try='test -z "$ac_c_werror_flag"
+			 || test ! -s conftest.err'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; } &&
+	 { ac_try='test -s conftest.$ac_objext'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; }; then
+
+    have_libmad=yes
+
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+fi
+rm -f conftest.err conftest.$ac_objext conftest.$ac_ext
+    echo "$as_me:$LINENO: result: $have_libmad" >&5
+echo "${ECHO_T}$have_libmad" >&6
+    if test x$have_libmad = xyes; then
+        SOURCES="$SOURCES $srcdir/mp3_mad.c"
+        EXTRA_CFLAGS="$EXTRA_CFLAGS -DMP3_MAD_MUSIC"
+        EXTRA_LDFLAGS="$EXTRA_LDFLAGS -lmad"
+    fi
+fi
 
 OBJECTS=`echo $SOURCES | sed 's,[^ ]*/\([^ ]*\)\.c,$(objects)/\1.lo,g'`
 
Index: configure.in
===================================================================
RCS file: /cvs/SDL_mixer/configure.in,v
retrieving revision 1.1.1.1
retrieving revision 1.3
diff -u -r1.1.1.1 -r1.3
--- configure.in	29 Jun 2006 06:06:07 -0000	1.1.1.1
+++ configure.in	1 Jul 2006 05:14:21 -0000	1.3
@@ -292,6 +292,25 @@
         fi
     fi
 fi
+AC_ARG_ENABLE(music-mp3-mad,
+[  --enable-music-mp3-mad  enable MP3 music via libmad [[default=no]]],
+              , enable_music_mp3_mad=no)
+if test x$enable_music_mp3_mad = xyes; then
+    AC_MSG_CHECKING(for libmad headers)
+    have_libmad=no
+    AC_TRY_COMPILE([
+     #include "mad.h"
+    ],[
+    ],[
+    have_libmad=yes
+    ])
+    AC_MSG_RESULT($have_libmad)
+    if test x$have_libmad = xyes; then
+        SOURCES="$SOURCES $srcdir/mp3_mad.c"
+        EXTRA_CFLAGS="$EXTRA_CFLAGS -DMP3_MAD_MUSIC"
+        EXTRA_LDFLAGS="$EXTRA_LDFLAGS -lmad"
+    fi
+fi
 
 OBJECTS=`echo $SOURCES | sed 's,[[^ ]]*/\([[^ ]]*\)\.c,$(objects)/\1.lo,g'`
 
Index: mp3_mad.c
===================================================================
RCS file: mp3_mad.c
diff -N mp3_mad.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ mp3_mad.c	30 Jun 2006 23:55:43 -0000	1.3
@@ -0,0 +1,352 @@
+/*
+    SDL_mixer:  An audio mixer library based on the SDL library
+    Copyright (C) 1997-2004 Sam Lantinga
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Library 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
+    Library General Public License for more details.
+
+    You should have received a copy of the GNU Library General Public
+    License along with this library; if not, write to the Free
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+    Sam Lantinga
+    slouken@libsdl.org
+*/
+
+#ifdef MP3_MAD_MUSIC
+
+#include "mp3_mad.h"
+#include <stdio.h>
+#include <assert.h>
+#include <malloc.h>
+#include <string.h>
+
+
+mad_data *
+mad_openFile(const char *filename, SDL_AudioSpec *mixer) {
+  SDL_RWops *rw;
+
+  rw = SDL_RWFromFile(filename, "rb");
+  if (rw == NULL) {
+	return NULL;
+  }
+
+  return mad_openFileRW(rw, mixer);
+}
+
+mad_data *
+mad_openFileRW(SDL_RWops *rw, SDL_AudioSpec *mixer) {
+  mad_data *mp3_mad;
+
+  mp3_mad = (mad_data *)malloc(sizeof(mad_data));
+  mp3_mad->rw = rw;
+  mad_stream_init(&mp3_mad->stream);
+  mad_frame_init(&mp3_mad->frame);
+  mad_synth_init(&mp3_mad->synth);
+  mp3_mad->frames_read = 0;
+  mad_timer_reset(&mp3_mad->next_frame_start);
+  mp3_mad->volume = MIX_MAX_VOLUME;
+  mp3_mad->status = 0;
+  mp3_mad->output_begin = 0;
+  mp3_mad->output_end = 0;
+  mp3_mad->mixer = *mixer;
+
+  return mp3_mad;
+}
+
+void
+mad_closeFile(mad_data *mp3_mad) {
+  SDL_FreeRW(mp3_mad->rw);
+  mad_stream_finish(&mp3_mad->stream);
+  mad_frame_finish(&mp3_mad->frame);
+  mad_synth_finish(&mp3_mad->synth);
+
+  free(mp3_mad);
+}
+
+/* Starts the playback. */
+void
+mad_start(mad_data *mp3_mad) {
+  mp3_mad->status |= MS_playing;
+}
+
+/* Stops the playback. */
+void 
+mad_stop(mad_data *mp3_mad) {
+  mp3_mad->status &= ~MS_playing;
+}
+
+/* Returns true if the playing is engaged, false otherwise. */
+int
+mad_isPlaying(mad_data *mp3_mad) {
+  return ((mp3_mad->status & MS_playing) != 0);
+}
+
+/* Reads the next frame from the file.  Returns true on success or
+   false on failure. */
+static int
+read_next_frame(mad_data *mp3_mad) {
+  if (mp3_mad->stream.buffer == NULL || 
+	  mp3_mad->stream.error == MAD_ERROR_BUFLEN) {
+	size_t read_size;
+	size_t remaining;
+	unsigned char *read_start;
+	
+	/* There might be some bytes in the buffer left over from last
+	   time.  If so, move them down and read more bytes following
+	   them. */
+	if (mp3_mad->stream.next_frame != NULL) {
+	  remaining = mp3_mad->stream.bufend - mp3_mad->stream.next_frame;
+	  memmove(mp3_mad->input_buffer, mp3_mad->stream.next_frame, remaining);
+	  read_start = mp3_mad->input_buffer + remaining;
+	  read_size = MAD_INPUT_BUFFER_SIZE - remaining;
+	  
+	} else {
+	  read_size = MAD_INPUT_BUFFER_SIZE;
+	  read_start = mp3_mad->input_buffer;
+	  remaining = 0;
+	}
+
+	/* Now read additional bytes from the input file. */
+	read_size = SDL_RWread(mp3_mad->rw, read_start, 1, read_size);
+	
+	if (read_size <= 0) {
+	  if ((mp3_mad->status & (MS_input_eof | MS_input_error)) == 0) {
+		if (read_size == 0) {
+		  fprintf(stderr, "end of input stream\n");
+		  mp3_mad->status |= MS_input_eof;
+		} else {
+		  fprintf(stderr, "read error on input stream\n");
+		  mp3_mad->status |= MS_input_error;
+		}
+		
+		/* At the end of the file, we must stuff MAD_BUFFER_GUARD
+		   number of 0 bytes. */
+		memset(read_start + read_size, 0, MAD_BUFFER_GUARD);
+		read_size += MAD_BUFFER_GUARD;
+	  } else {
+		fprintf(stderr, "repeated eof\n");
+	  }
+	}
+	
+	/* Now feed those bytes into the libmad stream. */
+	mad_stream_buffer(&mp3_mad->stream, mp3_mad->input_buffer,
+					  read_size + remaining);
+	mp3_mad->stream.error = MAD_ERROR_NONE;
+  }
+  
+  /* Now ask libmad to extract a frame from the data we just put in
+	 its buffer. */
+  if (mad_frame_decode(&mp3_mad->frame, &mp3_mad->stream)) {
+	if (MAD_RECOVERABLE(mp3_mad->stream.error)) {
+	  return 0;
+	  
+	} else if (mp3_mad->stream.error == MAD_ERROR_BUFLEN) {
+	  return 0;
+	  
+	} else {
+	  fprintf(stderr,"unrecoverable frame level error (%s).\n",
+			  mad_stream_errorstr(&mp3_mad->stream));
+	  mp3_mad->status |= MS_decode_error;
+	  return 0;
+	}
+  }
+  
+  mp3_mad->frames_read++;
+  mad_timer_add(&mp3_mad->next_frame_start, mp3_mad->frame.header.duration);
+
+  return 1;
+}
+
+/* Scale a MAD sample to 16 bits for output. */
+static signed int
+scale(mad_fixed_t sample) {
+  /* round */
+  sample += (1L << (MAD_F_FRACBITS - 16));
+
+  /* clip */
+  if (sample >= MAD_F_ONE)
+    sample = MAD_F_ONE - 1;
+  else if (sample < -MAD_F_ONE)
+    sample = -MAD_F_ONE;
+
+  /* quantize */
+  return sample >> (MAD_F_FRACBITS + 1 - 16);
+}
+
+/* Once the frame has been read, copies its samples into the output
+   buffer. */
+static void
+decode_frame(mad_data *mp3_mad) {
+  struct mad_pcm *pcm;
+  unsigned int nchannels, nsamples;
+  mad_fixed_t const *left_ch, *right_ch;
+  unsigned char *out;
+  int ret;
+
+  mad_synth_frame(&mp3_mad->synth, &mp3_mad->frame);
+  pcm = &mp3_mad->synth.pcm;
+  out = mp3_mad->output_buffer + mp3_mad->output_end;
+
+  if ((mp3_mad->status & MS_cvt_decoded) == 0) {
+	mp3_mad->status |= MS_cvt_decoded;
+
+	/* The first frame determines some key properties of the stream.
+	   In particular, it tells us enough to set up the convert
+	   structure now. */
+	ret = SDL_BuildAudioCVT(&mp3_mad->cvt, 
+							AUDIO_S16, pcm->channels, mp3_mad->frame.header.samplerate, 
+							mp3_mad->mixer.format, mp3_mad->mixer.channels,
+							mp3_mad->mixer.freq);
+	if (ret == -1) {
+	  fprintf(stderr, "Couldn't convert audio!\n");
+	}
+  }
+
+  /* pcm->samplerate contains the sampling frequency */
+
+  nchannels = pcm->channels;
+  nsamples  = pcm->length;
+  left_ch   = pcm->samples[0];
+  right_ch  = pcm->samples[1];
+
+  while (nsamples--) {
+    signed int sample;
+
+    /* output sample(s) in 16-bit signed little-endian PCM */
+
+    sample = scale(*left_ch++);
+    *out++ = ((sample >> 0) & 0xff);
+    *out++ = ((sample >> 8) & 0xff);
+
+    if (nchannels == 2) {
+      sample = scale(*right_ch++);
+      *out++ = ((sample >> 0) & 0xff);
+      *out++ = ((sample >> 8) & 0xff);
+    }
+  }
+
+  mp3_mad->output_end = out - mp3_mad->output_buffer;
+  assert(mp3_mad->output_end <= MAD_OUTPUT_BUFFER_SIZE);
+}
+
+void
+mad_getSamples(mad_data *mp3_mad, Uint8 *stream, int len) {
+  int bytes_remaining;
+  int num_bytes;
+  Uint8 *out;
+
+  if ((mp3_mad->status & MS_playing) == 0) {
+	/* We're not supposed to be playing, so send silence instead. */
+	memset(stream, 0, len);
+	return;
+  }
+
+  out = stream;
+  bytes_remaining = len;
+  while (bytes_remaining > 0) {
+	if (mp3_mad->output_end == mp3_mad->output_begin) {
+	  /* We need to get a new frame. */
+	  mp3_mad->output_begin = 0;
+	  mp3_mad->output_end = 0;
+	  if (!read_next_frame(mp3_mad)) {
+		if ((mp3_mad->status & MS_error_flags) != 0) {
+		  /* Couldn't read a frame; either an error condition or
+			 end-of-file.  Stop. */
+		  memset(out, 0, bytes_remaining);
+		  mp3_mad->status &= ~MS_playing;
+		  return;
+		}
+	  } else {
+		decode_frame(mp3_mad);
+
+		/* Now convert the frame data to the appropriate format for
+		   output. */
+		mp3_mad->cvt.buf = mp3_mad->output_buffer;
+		mp3_mad->cvt.len = mp3_mad->output_end;
+		
+		mp3_mad->output_end = (int)(mp3_mad->output_end * mp3_mad->cvt.len_ratio);
+		assert(mp3_mad->output_end <= MAD_OUTPUT_BUFFER_SIZE);
+		if (SDL_ConvertAudio(&mp3_mad->cvt) < 0) {
+		  fprintf(stderr, "Unable to convert!\n");
+		}
+	  }
+	}
+
+	num_bytes = mp3_mad->output_end - mp3_mad->output_begin;
+	if (bytes_remaining < num_bytes) {
+	  num_bytes = bytes_remaining;
+	}
+
+	if (mp3_mad->volume == MIX_MAX_VOLUME) {
+	  memcpy(out, mp3_mad->output_buffer + mp3_mad->output_begin, num_bytes);
+	} else {
+	  SDL_MixAudio(out, mp3_mad->output_buffer + mp3_mad->output_begin,
+				   num_bytes, mp3_mad->volume);
+	}
+	out += num_bytes;
+	mp3_mad->output_begin += num_bytes;
+	bytes_remaining -= num_bytes;
+  }
+}
+
+void
+mad_seek(mad_data *mp3_mad, double position) {
+  mad_timer_t target;
+  int int_part;
+
+  int_part = (int)position;
+  mad_timer_set(&target, int_part, 
+				(int)((position - int_part) * 1000000), 1000000);
+
+  if (mad_timer_compare(mp3_mad->next_frame_start, target) > 0) {
+	/* In order to seek backwards in a VBR file, we have to rewind and
+	   start again from the beginning.  This isn't necessary if the
+	   file happens to be CBR, of course; in that case we could seek
+	   directly to the frame we want.  But I leave that little
+	   optimization for the future developer who discovers she really
+	   needs it. */
+	mp3_mad->frames_read = 0;
+	mad_timer_reset(&mp3_mad->next_frame_start);
+	mp3_mad->status &= ~MS_error_flags;
+	mp3_mad->output_begin = 0;
+	mp3_mad->output_end = 0;
+
+	SDL_RWseek(mp3_mad->rw, 0, SEEK_SET);
+  }
+
+  /* Now we have to skip frames until we come to the right one.
+	 Again, only truly necessary if the file is VBR. */
+  while (mad_timer_compare(mp3_mad->next_frame_start, target) < 0) {
+	if (!read_next_frame(mp3_mad)) {
+	  if ((mp3_mad->status & MS_error_flags) != 0) {
+		/* Couldn't read a frame; either an error condition or
+		   end-of-file.  Stop. */
+		mp3_mad->status &= ~MS_playing;
+		return;
+	  }
+	}
+  }
+
+  /* Here we are, at the beginning of the frame that contains the
+	 target time.  Ehh, I say that's close enough.  If we wanted to,
+	 we could get more precise by decoding the frame now and counting
+	 the appropriate number of samples out of it. */
+}
+
+void
+mad_setVolume(mad_data *mp3_mad, int volume) {
+  mp3_mad->volume = volume;
+}
+
+
+#endif  /* MP3_MAD_MUSIC */
+
+
Index: mp3_mad.h
===================================================================
RCS file: mp3_mad.h
diff -N mp3_mad.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ mp3_mad.h	29 Jun 2006 16:27:25 -0000	1.2
@@ -0,0 +1,74 @@
+/*
+    SDL_mixer:  An audio mixer library based on the SDL library
+    Copyright (C) 1997-2004 Sam Lantinga
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Library 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
+    Library General Public License for more details.
+
+    You should have received a copy of the GNU Library General Public
+    License along with this library; if not, write to the Free
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+    Sam Lantinga
+    slouken@libsdl.org
+*/
+
+
+#ifdef MP3_MAD_MUSIC
+
+#include "mad.h"
+#include "SDL_rwops.h"
+#include "SDL_audio.h"
+#include "SDL_mixer.h"
+
+#define MAD_INPUT_BUFFER_SIZE	(5*8192)
+#define MAD_OUTPUT_BUFFER_SIZE	8192
+
+enum {
+  MS_input_eof    = 0x0001,
+  MS_input_error  = 0x0001,
+  MS_decode_eof   = 0x0002,
+  MS_decode_error = 0x0004,
+  MS_error_flags  = 0x000f,
+
+  MS_playing      = 0x0100,
+  MS_cvt_decoded  = 0x0200,
+};
+
+typedef struct {
+  SDL_RWops *rw;
+  struct mad_stream stream;
+  struct mad_frame frame;
+  struct mad_synth synth;
+  int frames_read;
+  mad_timer_t next_frame_start;
+  int volume;
+  int status;
+  int output_begin, output_end;
+  SDL_AudioSpec mixer;
+  SDL_AudioCVT cvt;
+
+  unsigned char input_buffer[MAD_INPUT_BUFFER_SIZE + MAD_BUFFER_GUARD];
+  unsigned char output_buffer[MAD_OUTPUT_BUFFER_SIZE];
+} mad_data;
+
+mad_data *mad_openFile(const char *filename, SDL_AudioSpec *mixer);
+mad_data *mad_openFileRW(SDL_RWops *rw, SDL_AudioSpec *mixer);
+void mad_closeFile(mad_data *mp3_mad);
+
+void mad_start(mad_data *mp3_mad);
+void mad_stop(mad_data *mp3_mad);
+int mad_isPlaying(mad_data *mp3_mad);
+
+void mad_getSamples(mad_data *mp3_mad, Uint8 *stream, int len);
+void mad_seek(mad_data *mp3_mad, double position);
+void mad_setVolume(mad_data *mp3_mad, int volume);
+
+#endif
Index: music.c
===================================================================
RCS file: /cvs/SDL_mixer/music.c,v
retrieving revision 1.1.1.1
retrieving revision 1.3
diff -u -r1.1.1.1 -r1.3
--- music.c	29 Jun 2006 06:06:08 -0000	1.1.1.1
+++ music.c	29 Jun 2006 16:27:25 -0000	1.3
@@ -20,7 +20,7 @@
     slouken@libsdl.org
 */
 
-/* $Id: music.c,v 1.1.1.1 2006/06/29 06:06:08 drose Exp $ */
+/* $Id: music.c,v 1.3 2006/06/29 16:27:25 drose Exp $ */
 
 #include <stdlib.h>
 #include <string.h>
@@ -85,10 +85,16 @@
 #endif
 #ifdef MP3_MUSIC
 #include "dynamic_mp3.h"
+#endif
+#ifdef MP3_MAD_MUSIC
+#include "mp3_mad.h"
+#endif
 
+#if defined(MP3_MUSIC) || defined(MP3_MAD_MUSIC)
 static SDL_AudioSpec used_mixer;
 #endif
 
+
 int volatile music_active = 1;
 static int volatile music_stopped = 0;
 static int music_loops = 0;
@@ -124,6 +130,9 @@
 #ifdef MP3_MUSIC
 		SMPEG *mp3;
 #endif
+#ifdef MP3_MAD_MUSIC
+		mad_data *mp3_mad;
+#endif
 	} data;
 	Mix_Fading fading;
 	int fade_step;
@@ -341,6 +350,11 @@
 				smpeg.SMPEG_playAudio(music_playing->data.mp3, stream, len);
 				break;
 #endif
+#ifdef MP3_MAD_MUSIC
+			case MUS_MP3_MAD:
+			    mad_getSamples(music_playing->data.mp3_mad, stream, len);
+				break;
+#endif
 			default:
 				/* Unknown music type?? */
 				break;
@@ -455,7 +469,7 @@
 		++music_error;
 	}
 #endif
-#ifdef MP3_MUSIC
+#if defined(MP3_MUSIC) || defined(MP3_MAD_MUSIC)
 	/* Keep a copy of the mixer */
 	used_mixer = *mixer;
 #endif
@@ -608,6 +622,20 @@
 		}
 	} else
 #endif
+#ifdef MP3_MAD_MUSIC
+	if ( (ext && MIX_string_equals(ext, "MPG")) ||
+	     (ext && MIX_string_equals(ext, "MP3")) ||
+	     (ext && MIX_string_equals(ext, "MPEG")) ||
+	     (ext && MIX_string_equals(ext, "MAD")) ||
+	     (magic[0] == 0xFF && (magic[1] & 0xF0) == 0xF0) ) {
+	    music->type = MUS_MP3_MAD;
+	    music->data.mp3_mad = mad_openFile(file, &used_mixer);
+		if (music->data.mp3_mad == 0) {
+		    Mix_SetError("Could not initialize MPEG stream.");
+			music->error = 1;
+		}
+	} else
+#endif
 #if defined(MOD_MUSIC) || defined(LIBMIKMOD_MUSIC)
 	if ( 1 ) {
 		music->type = MUS_MOD;
@@ -698,6 +726,11 @@
 				Mix_QuitMP3();
 				break;
 #endif
+#ifdef MP3_MAD_MUSIC
+			case MUS_MP3_MAD:
+			    mad_closeFile(music->data.mp3_mad);
+				break;
+#endif
 			default:
 				/* Unknown music type?? */
 				break;
@@ -787,6 +820,11 @@
 		smpeg.SMPEG_play(music_playing->data.mp3);
 		break;
 #endif
+#ifdef MP3_MAD_MUSIC
+	    case MUS_MP3_MAD:
+		mad_start(music->data.mp3_mad);
+		break;
+#endif
 	    default:
 		Mix_SetError("Can't play unknown music type");
 		retval = -1;
@@ -880,6 +918,11 @@
 		}
 		break;
 #endif
+#ifdef MP3_MAD_MUSIC
+	    case MUS_MP3_MAD:
+		mad_seek(music_playing->data.mp3_mad, position);
+		break;
+#endif
 	    default:
 		/* TODO: Implement this for other music backends */
 		retval = -1;
@@ -959,6 +1002,11 @@
 		smpeg.SMPEG_setvolume(music_playing->data.mp3,(int)(((float)volume/(float)MIX_MAX_VOLUME)*100.0));
 		break;
 #endif
+#ifdef MP3_MAD_MUSIC
+	    case MUS_MP3_MAD:
+		mad_setVolume(music_playing->data.mp3_mad, volume);
+		break;
+#endif
 	    default:
 		/* Unknown music type?? */
 		break;
@@ -1027,6 +1075,11 @@
 		smpeg.SMPEG_stop(music_playing->data.mp3);
 		break;
 #endif
+#ifdef MP3_MAD_MUSIC
+	    case MUS_MP3_MAD:
+		mad_stop(music_playing->data.mp3_mad);
+		break;
+#endif
 	    default:
 		/* Unknown music type?? */
 		return;
@@ -1170,6 +1223,13 @@
 			playing = 0;
 		break;
 #endif
+#ifdef MP3_MAD_MUSIC
+	    case MUS_MP3_MAD:
+		if (!mad_isPlaying(music_playing->data.mp3_mad)) {
+			playing = 0;
+		}
+		break;
+#endif
 	    default:
 		playing = 0;
 		break;
@@ -1408,6 +1468,16 @@
 		}
 	} else
 #endif
+#ifdef MP3_MAD_MUSIC
+	if ( magic[0] == 0xFF && (magic[1] & 0xF0) == 0xF0 ) {
+	    music->type = MUS_MP3_MAD;
+	    music->data.mp3_mad = mad_openFileRW(rw, &used_mixer);
+		if (music->data.mp3_mad == 0) {
+		    Mix_SetError("Could not initialize MPEG stream.");
+			music->error = 1;
+		}
+	} else
+#endif
 #ifdef MID_MUSIC
 	/* MIDI files have the magic four bytes "MThd" */
 	if ( strcmp((char *)magic, "MThd") == 0 ) {
Comment 1 Sam Lantinga 2007-07-14 20:15:13 UTC
This looks like a great patch, and I just checked out libmad and madplay on Mac OS X.  All I got was static trying to play an MP3 file.  I contacted the company and hopefully they'll respond.
Comment 2 Sam Lantinga 2007-07-14 21:56:33 UTC
I applied this patch anyway, and libmad itself works just fine. :)
Comment 3 Sam Lantinga 2007-07-14 22:34:19 UTC
This has been added in subversion revision 3278, thanks!