From a7dc597a53c39034047d056e9c49f1c9b26b4689 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Sun, 15 Jul 2007 05:33:35 +0000 Subject: [PATCH] Fixed bug #269 David Rose - Sat Jul 14 22:16:09 PDT 2007 * Added support for MP3 playback with libmad (for GPL projects only!) --- CHANGES | 2 + Makefile.in | 2 +- README | 12 +- SDL_mixer.h | 3 +- SDL_mixer.spec.in | 8 +- configure.in | 19 +++ music.c | 72 +++++++++- music_mad.c | 333 ++++++++++++++++++++++++++++++++++++++++++++++ music_mad.h | 73 ++++++++++ 9 files changed, 515 insertions(+), 9 deletions(-) create mode 100644 music_mad.c create mode 100644 music_mad.h diff --git a/CHANGES b/CHANGES index 46d7ee95..d5eacf5f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,6 @@ 1.2.8: +David Rose - Sat Jul 14 22:16:09 PDT 2007 + * Added support for MP3 playback with libmad (for GPL projects only!) Sam Lantinga - Sat Jul 14 21:39:30 PDT 2007 * Fixed the final loop of audio samples of a certain size Sam Lantinga - Sat Jul 14 21:05:09 PDT 2007 diff --git a/Makefile.in b/Makefile.in index 7acfc62b..391047d6 100644 --- a/Makefile.in +++ b/Makefile.in @@ -35,7 +35,7 @@ TARGET = libSDL_mixer.la SOURCES = @SOURCES@ OBJECTS = @OBJECTS@ -DIST = CHANGES COPYING CWProjects.sea.bin MPWmake.sea.bin Makefile.in README SDL_mixer.h SDL_mixer.qpg.in SDL_mixer.spec SDL_mixer.spec.in VisualC.zip Watcom-OS2.zip Xcode.tar.gz acinclude autogen.sh build-scripts configure configure.in dynamic_mp3.c dynamic_mp3.h dynamic_ogg.c dynamic_ogg.h effect_position.c effect_stereoreverse.c effects_internal.c effects_internal.h gcc-fat.sh load_aiff.c load_aiff.h load_ogg.c load_ogg.h load_voc.c load_voc.h mikmod mixer.c music.c music_cmd.c music_cmd.h music_ogg.c music_ogg.h native_midi native_midi_gpl playmus.c playwave.c timidity wavestream.c wavestream.h +DIST = CHANGES COPYING CWProjects.sea.bin MPWmake.sea.bin Makefile.in README SDL_mixer.h SDL_mixer.qpg.in SDL_mixer.spec SDL_mixer.spec.in VisualC.zip Watcom-OS2.zip Xcode.tar.gz acinclude autogen.sh build-scripts configure configure.in dynamic_mp3.c dynamic_mp3.h dynamic_ogg.c dynamic_ogg.h effect_position.c effect_stereoreverse.c effects_internal.c effects_internal.h gcc-fat.sh load_aiff.c load_aiff.h load_ogg.c load_ogg.h load_voc.c load_voc.h mikmod mixer.c music.c music_cmd.c music_cmd.h music_mad.c music_mad.h music_ogg.c music_ogg.h native_midi native_midi_gpl playmus.c playwave.c timidity wavestream.c wavestream.h LT_AGE = @LT_AGE@ LT_CURRENT = @LT_CURRENT@ diff --git a/README b/README index e38f7cdc..2553d443 100644 --- a/README +++ b/README @@ -16,12 +16,20 @@ The mixer can currently load Microsoft WAVE files and Creative Labs VOC 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 Ogg Vorbis or Tremor libraries, -and finally it can load MP3 music using the SMPEG library. +and finally it can load MP3 music using the SMPEG or libmad libraries. -Tremor decoding is disabled by default, you can enable it by passing +Tremor decoding is disabled by default; you can enable it by passing --enable-music-ogg-tremor to configure, or by defining OGG_MUSIC and OGG_USE_TREMOR. +libmad decoding is disabled by default; you can enable it by passing + --enable-music-mp3-mad +to configure, or by defining MP3_MAD_MUSIC +vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv +WARNING: The license for libmad is GPL, which means that in order to + use it your application must also be GPL! +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 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 sound choppy, try using 8-bit audio, mono audio, or lower frequencies. diff --git a/SDL_mixer.h b/SDL_mixer.h index e2d77d47..e9cf2b44 100644 --- a/SDL_mixer.h +++ b/SDL_mixer.h @@ -103,7 +103,8 @@ typedef enum { 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 */ diff --git a/SDL_mixer.spec.in b/SDL_mixer.spec.in index c23130bf..8512e856 100644 --- a/SDL_mixer.spec.in +++ b/SDL_mixer.spec.in @@ -15,8 +15,8 @@ Prefix: %{_prefix} %description 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. +of music, mixed by the popular MikMod MOD, Timidity MIDI, Ogg Vorbis, +Tremor, SMPEG MP3, and libmad MP3 libraries. %package devel Summary: Libraries, includes and more to develop SDL applications. @@ -26,8 +26,8 @@ Requires: %{name} %description devel 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. +of music, mixed by the popular MikMod MOD, Timidity MIDI, Ogg Vorbis, +Tremor, SMPEG MP3, and libmad MP3 libraries. %prep %setup diff --git a/configure.in b/configure.in index 5d090f18..3af59cc9 100644 --- a/configure.in +++ b/configure.in @@ -320,6 +320,25 @@ AC_HELP_STRING([--enable-music-mp3-shared], [dynamically load MP3 support [[defa fi fi fi +AC_ARG_ENABLE(music-mp3-mad-gpl, +AC_HELP_STRING([--enable-music-mp3-mad-gpl], [enable MP3 music via libmad GPL code [[default=no]]]), + [], [enable_music_mp3_mad_gpl=no]) +if test x$enable_music_mp3_mad_gpl = 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/music_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'` diff --git a/music.c b/music.c index 358f5ae3..3a37e104 100644 --- a/music.c +++ b/music.c @@ -85,10 +85,16 @@ #endif #ifdef MP3_MUSIC #include "dynamic_mp3.h" +#endif +#ifdef MP3_MAD_MUSIC +#include "music_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; @@ -123,6 +129,9 @@ struct _Mix_Music { #endif #ifdef MP3_MUSIC SMPEG *mp3; +#endif +#ifdef MP3_MAD_MUSIC + mad_data *mp3_mad; #endif } data; Mix_Fading fading; @@ -340,6 +349,11 @@ void music_mixer(void *udata, Uint8 *stream, int len) case MUS_MP3: 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?? */ @@ -455,7 +469,7 @@ int open_music(SDL_AudioSpec *mixer) ++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 @@ Mix_Music *Mix_LoadMUS(const char *file) } } 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; @@ -697,6 +725,11 @@ void Mix_FreeMusic(Mix_Music *music) smpeg.SMPEG_delete(music->data.mp3); Mix_QuitMP3(); break; +#endif +#ifdef MP3_MAD_MUSIC + case MUS_MP3_MAD: + mad_closeFile(music->data.mp3_mad); + break; #endif default: /* Unknown music type?? */ @@ -786,6 +819,11 @@ static int music_internal_play(Mix_Music *music, double position) smpeg.SMPEG_enablevideo(music->data.mp3,0); 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"); @@ -879,6 +917,11 @@ int music_internal_position(double position) smpeg.SMPEG_play(music_playing->data.mp3); } 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 */ @@ -958,6 +1001,11 @@ static void music_internal_volume(int volume) case MUS_MP3: 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?? */ @@ -1026,6 +1074,11 @@ static void music_internal_halt(void) case MUS_MP3: 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?? */ @@ -1169,6 +1222,13 @@ static int music_internal_playing() if ( smpeg.SMPEG_status(music_playing->data.mp3) != SMPEG_PLAYING ) 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; @@ -1413,6 +1473,16 @@ Mix_Music *Mix_LoadMUS_RW(SDL_RWops *rw) { } } 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 ) { diff --git a/music_mad.c b/music_mad.c new file mode 100644 index 00000000..106315e3 --- /dev/null +++ b/music_mad.c @@ -0,0 +1,333 @@ +/* + 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 + +#include "music_mad.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) { + mp3_mad->status |= MS_input_eof; + } else { + 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; + } + } + + /* 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 { + 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. */ + 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); + } + + /* 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);*/ + SDL_ConvertAudio(&mp3_mad->cvt); + } + } + + 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 */ diff --git a/music_mad.h b/music_mad.h new file mode 100644 index 00000000..2556862e --- /dev/null +++ b/music_mad.h @@ -0,0 +1,73 @@ +/* + 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