From 36b33c0e46976f572d8820ab524e1c4dafca08a3 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Wed, 27 Feb 2008 07:31:03 +0000 Subject: [PATCH] Austen Dicken - Tue Feb 26 23:28:27 PST 2008 Ok, here is the patch I made for FLAC support. I have tested it relatively thoroughly and currently the patch allows: 1. Pre-loading FLAC files and playing them via LoadWAV 2. The patch allows for FLAC support in the LoadMUS setting as well as: * Pause / Resume * Volume control * Seeking I also did a little benchmarking by comparing memory/cpu usage of playmus to that of mplayer, and the results were very good. playmus typically took about half the RAM as mplayer, though that may be attributed to mplayer being a more "bulky" program. As such I would say that the two are probably about equal in efficiency. Also, it is important to note that, similar to the OGG support currently built-in, my FLAC patch only supports 16 bit stereo-encoded sound. Also, it is only for Native FLAC (standard) and not the derivative, Ogg-FLAC. I have tried to find a simple way to detect Ogg-FLAC files, as the only difference between Ogg-FLAC and Native FLAC support is changing the init_ function call, but after digging a little deeper it seems that Ogg-FLAC is basically FLAC wrapped in an Ogg transport layer, so it would be better to have a way to read the Ogg transport layer which then reads the inner audio files according to the proper codec. But anyway, that's another job for another day! For now this should provide Native FLAC support! --- CHANGES | 2 + SDL_mixer.h | 1 + configure.in | 35 +++ dynamic_flac.c | 176 +++++++++++++++ dynamic_flac.h | 68 ++++++ load_flac.c | 332 +++++++++++++++++++++++++++ load_flac.h | 29 +++ mixer.c | 10 +- music.c | 71 ++++++ music_flac.c | 591 +++++++++++++++++++++++++++++++++++++++++++++++++ music_flac.h | 93 ++++++++ 11 files changed, 1407 insertions(+), 1 deletion(-) create mode 100644 dynamic_flac.c create mode 100644 dynamic_flac.h create mode 100644 load_flac.c create mode 100644 load_flac.h create mode 100644 music_flac.c create mode 100644 music_flac.h diff --git a/CHANGES b/CHANGES index 95e871c9..0d156df9 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,6 @@ 1.2.9: +Austen Dicken - Tue Feb 26 23:28:27 PST 2008 + * Added support for FLAC audio both as chunks and streaming Tilman Sauerbeck - Tue Feb 26 03:44:47 PST 2008 * Added support for streaming WAV files with Mix_LoadMUS_RW() diff --git a/SDL_mixer.h b/SDL_mixer.h index e9cf2b44..58e0713e 100644 --- a/SDL_mixer.h +++ b/SDL_mixer.h @@ -103,6 +103,7 @@ typedef enum { MUS_MOD, MUS_MID, MUS_OGG, + MUS_FLAC, MUS_MP3, MUS_MP3_MAD } Mix_MusicType; diff --git a/configure.in b/configure.in index 31aa3c74..ed7d522e 100644 --- a/configure.in +++ b/configure.in @@ -318,6 +318,41 @@ if test x$enable_music_ogg = xyes; then fi fi fi +AC_ARG_ENABLE([music-flac], +AC_HELP_STRING([--enable-music-flac], [enable FLAC music [[default=yes]]]), + [], [enable_music_flac=yes]) +AC_ARG_ENABLE([music-flac-shared], +AC_HELP_STRING([--enable-music-flac-shared], + [dynamically load FLAC support [[default=yes]]]), + [], [enable_music_flac_shared=yes]) +if test x$enable_music_flac = xyes; then + AC_CHECK_HEADER([FLAC/stream_decoder.h], [have_flac_hdr=yes]) + AC_CHECK_LIB([FLAC], [FLAC__stream_decoder_new], [have_flac_lib=yes]) + if test x$have_flac_hdr = xyes -a x$have_flac_lib = xyes; then + case "$host" in + *-*-darwin*) + flac_lib=[`find_lib FLAC*.dylib`] + ;; + *-*-cygwin* | *-*-mingw32*) + flac_lib=[`find_lib "libFLAC*.dll"`] + ;; + *) + flac_lib=[`find_lib "libFLAC.so.[0-9]"`] + if test x$flac_lib = x; then + flac_lib=[`find_lib "libFLAC.so.[0-9]*"`] + fi + ;; + esac + SOURCES="$SOURCES $srcdir/*_flac.c" + EXTRA_CFLAGS="$EXTRA_CFLAGS -DFLAC_MUSIC" + if test x$enable_music_flac_shared = xyes && test x$flac_lib != x; then + echo "-- dynamic libFLAC -> $flac_lib" + EXTRA_CFLAGS="$EXTRA_CFLAGS -DFLAC_DYNAMIC=\\\"$flac_lib\\\"" + else + EXTRA_LDFLAGS="$EXTRA_LDFLAGS -lFLAC" + fi + fi +fi AC_ARG_ENABLE(music-mp3, [ --enable-music-mp3 enable MP3 music via smpeg [[default=yes]]], , enable_music_mp3=yes) diff --git a/dynamic_flac.c b/dynamic_flac.c new file mode 100644 index 00000000..9d58715f --- /dev/null +++ b/dynamic_flac.c @@ -0,0 +1,176 @@ +/* + 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 + + + Implementation of the dynamic loading functionality for libFLAC. + ~ Austen Dicken (admin@cvpcs.org) +*/ + +#ifdef FLAC_MUSIC + +#include "SDL_loadso.h" + +#include "dynamic_flac.h" + +flac_loader flac = { + 0, NULL +}; + +#ifdef FLAC_DYNAMIC + +int Mix_InitFLAC() { + if ( flac.loaded == 0 ) { + flac.handle = SDL_LoadObject(FLAC_DYNAMIC); + if ( flac.handle == NULL ) { + return -1; + } + flac.FLAC__stream_decoder_new = + (FLAC__StreamDecoder *(*)()) + SDL_LoadFunction(flac.handle, "FLAC__stream_decoder_new"); + if ( flac.FLAC__stream_decoder_new == NULL ) { + SDL_UnloadObject(flac.handle); + return -1; + } + flac.FLAC__stream_decoder_delete = + (void (*)(FLAC__StreamDecoder *)) + SDL_LoadFunction(flac.handle, "FLAC__stream_decoder_delete"); + if ( flac.FLAC__stream_decoder_delete == NULL ) { + SDL_UnloadObject(flac.handle); + return -1; + } + flac.FLAC__stream_decoder_init_stream = + (FLAC__StreamDecoderInitStatus (*)( + FLAC__StreamDecoder *, + FLAC__StreamDecoderReadCallback, + FLAC__StreamDecoderSeekCallback, + FLAC__StreamDecoderTellCallback, + FLAC__StreamDecoderLengthCallback, + FLAC__StreamDecoderEofCallback, + FLAC__StreamDecoderWriteCallback, + FLAC__StreamDecoderMetadataCallback, + FLAC__StreamDecoderErrorCallback, + void *)) + SDL_LoadFunction(flac.handle, "FLAC__stream_decoder_init_stream"); + if ( flac.FLAC__stream_decoder_init_stream == NULL ) { + SDL_UnloadObject(flac.handle); + return -1; + } + flac.FLAC__stream_decoder_finish = + (FLAC__bool (*)(FLAC__StreamDecoder *)) + SDL_LoadFunction(flac.handle, "FLAC__stream_decoder_finish"); + if ( flac.FLAC__stream_decoder_finish == NULL ) { + SDL_UnloadObject(flac.handle); + return -1; + } + flac.FLAC__stream_decoder_flush = + (FLAC__bool (*)(FLAC__StreamDecoder *)) + SDL_LoadFunction(flac.handle, "FLAC__stream_decoder_flush"); + if ( flac.FLAC__stream_decoder_flush == NULL ) { + SDL_UnloadObject(flac.handle); + return -1; + } + flac.FLAC__stream_decoder_process_single = + (FLAC__bool (*)(FLAC__StreamDecoder *)) + SDL_LoadFunction(flac.handle, + "FLAC__stream_decoder_process_single"); + if ( flac.FLAC__stream_decoder_process_single == NULL ) { + SDL_UnloadObject(flac.handle); + return -1; + } + flac.FLAC__stream_decoder_process_until_end_of_metadata = + (FLAC__bool (*)(FLAC__StreamDecoder *)) + SDL_LoadFunction(flac.handle, + "FLAC__stream_decoder_process_until_end_of_metadata"); + if ( flac.FLAC__stream_decoder_process_until_end_of_metadata == NULL ) { + SDL_UnloadObject(flac.handle); + return -1; + } + flac.FLAC__stream_decoder_process_until_end_of_stream = + (FLAC__bool (*)(FLAC__StreamDecoder *)) + SDL_LoadFunction(flac.handle, + "FLAC__stream_decoder_process_until_end_of_stream"); + if ( flac.FLAC__stream_decoder_process_until_end_of_stream == NULL ) { + SDL_UnloadObject(flac.handle); + return -1; + } + flac.FLAC__stream_decoder_seek_absolute = + (FLAC__bool (*)(FLAC__StreamDecoder *, FLAC__uint64)) + SDL_LoadFunction(flac.handle, "FLAC__stream_decoder_seek_absolute"); + if ( flac.FLAC__stream_decoder_seek_absolute == NULL ) { + SDL_UnloadObject(flac.handle); + return -1; + } + flac.FLAC__stream_decoder_get_state = + (FLAC__StreamDecoderState (*)(const FLAC__StreamDecoder *decoder)) + SDL_LoadFunction(flac.handle, "FLAC__stream_decoder_get_state"); + if ( flac.FLAC__stream_decoder_get_state == NULL ) { + SDL_UnloadObject(flac.handle); + return -1; + } + } + ++flac.loaded; + + return 0; +} +void Mix_QuitFLAC() { + if ( flac.loaded == 0 ) { + return; + } + if ( flac.loaded == 1 ) { + SDL_UnloadObject(flac.handle); + } + --flac.loaded; +} +#else +int Mix_InitFLAC() { + if ( flac.loaded == 0 ) { + flac.FLAC__stream_decoder_new = FLAC__stream_decoder_new; + flac.FLAC__stream_decoder_delete = FLAC__stream_decoder_delete; + flac.FLAC__stream_decoder_init_stream = + FLAC__stream_decoder_init_stream; + flac.FLAC__stream_decoder_finish = FLAC__stream_decoder_finish; + flac.FLAC__stream_decoder_flush = FLAC__stream_decoder_flush; + flac.FLAC__stream_decoder_process_single = + FLAC__stream_decoder_process_single; + flac.FLAC__stream_decoder_process_until_end_of_metadata = + FLAC__stream_decoder_process_until_end_of_metadata; + flac.FLAC__stream_decoder_process_until_end_of_stream = + FLAC__stream_decoder_process_until_end_of_stream; + flac.FLAC__stream_decoder_seek_absolute = + FLAC__stream_decoder_seek_absolute; + flac.FLAC__stream_decoder_get_state = + FLAC__stream_decoder_get_state; + } + ++flac.loaded; + + return 0; +} +void Mix_QuitFLAC() { + if ( flac.loaded == 0 ) { + return; + } + if ( flac.loaded == 1 ) { + } + --flac.loaded; +} +#endif // FLAC_DYNAMIC + +#endif // FLAC_MUSIC diff --git a/dynamic_flac.h b/dynamic_flac.h new file mode 100644 index 00000000..090a2cac --- /dev/null +++ b/dynamic_flac.h @@ -0,0 +1,68 @@ +/* + 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 + + + The following file defines all of the functions/objects used to dynamically + link to the libFLAC library. + ~ Austen Dicken (admin@cvpcs.org) +*/ + +#ifdef FLAC_MUSIC + +#include + +typedef struct { + int loaded; + void *handle; + FLAC__StreamDecoder *(*FLAC__stream_decoder_new)(); + void (*FLAC__stream_decoder_delete)(FLAC__StreamDecoder *decoder); + FLAC__StreamDecoderInitStatus (*FLAC__stream_decoder_init_stream)( + FLAC__StreamDecoder *decoder, + FLAC__StreamDecoderReadCallback read_callback, + FLAC__StreamDecoderSeekCallback seek_callback, + FLAC__StreamDecoderTellCallback tell_callback, + FLAC__StreamDecoderLengthCallback length_callback, + FLAC__StreamDecoderEofCallback eof_callback, + FLAC__StreamDecoderWriteCallback write_callback, + FLAC__StreamDecoderMetadataCallback metadata_callback, + FLAC__StreamDecoderErrorCallback error_callback, + void *client_data); + FLAC__bool (*FLAC__stream_decoder_finish)(FLAC__StreamDecoder *decoder); + FLAC__bool (*FLAC__stream_decoder_flush)(FLAC__StreamDecoder *decoder); + FLAC__bool (*FLAC__stream_decoder_process_single)( + FLAC__StreamDecoder *decoder); + FLAC__bool (*FLAC__stream_decoder_process_until_end_of_metadata)( + FLAC__StreamDecoder *decoder); + FLAC__bool (*FLAC__stream_decoder_process_until_end_of_stream)( + FLAC__StreamDecoder *decoder); + FLAC__bool (*FLAC__stream_decoder_seek_absolute)( + FLAC__StreamDecoder *decoder, + FLAC__uint64 sample); + FLAC__StreamDecoderState (*FLAC__stream_decoder_get_state)( + const FLAC__StreamDecoder *decoder); +} flac_loader; + +extern flac_loader flac; + +extern int Mix_InitFLAC(); +extern void Mix_QuitFLAC(); + +#endif // FLAC_MUSIC diff --git a/load_flac.c b/load_flac.c new file mode 100644 index 00000000..5f20bc59 --- /dev/null +++ b/load_flac.c @@ -0,0 +1,332 @@ +/* + 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 + + This is the source needed to decode a FLAC into a waveform. + ~ Austen Dicken (admin@cvpcs.org). +*/ + +#ifdef FLAC_MUSIC + +#include +#include +#include + +#include "SDL_mutex.h" +#include "SDL_endian.h" +#include "SDL_timer.h" + +#include "SDL_mixer.h" +#include "dynamic_flac.h" +#include "load_flac.h" + +#include + +typedef struct { + SDL_RWops* sdl_src; + SDL_AudioSpec* sdl_spec; + Uint8** sdl_audio_buf; + Uint32* sdl_audio_len; + int sdl_audio_read; + FLAC__uint64 flac_total_samples; + unsigned flac_bps; +} FLAC_SDL_Data; + +static FLAC__StreamDecoderReadStatus flac_read_load_cb( + const FLAC__StreamDecoder *decoder, + FLAC__byte buffer[], + size_t *bytes, + void *client_data) { + // make sure there is something to be reading + if (*bytes > 0) { + FLAC_SDL_Data *data = (FLAC_SDL_Data *)client_data; + + *bytes = SDL_RWread (data->sdl_src, buffer, sizeof (FLAC__byte), + *bytes); + + if(*bytes < 0) { // error in read + return FLAC__STREAM_DECODER_READ_STATUS_ABORT; + } + else if(*bytes == 0) { // no data was read (EOF) + return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; + } + else { // data was read, continue + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; + } + } + else { + return FLAC__STREAM_DECODER_READ_STATUS_ABORT; + } +} + +static FLAC__StreamDecoderSeekStatus flac_seek_load_cb( + const FLAC__StreamDecoder *decoder, + FLAC__uint64 absolute_byte_offset, + void *client_data) { + FLAC_SDL_Data *data = (FLAC_SDL_Data *)client_data; + + if (SDL_RWseek (data->sdl_src, absolute_byte_offset, SEEK_SET) < 0) { + return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; + } + else { + return FLAC__STREAM_DECODER_SEEK_STATUS_OK; + } +} + +static FLAC__StreamDecoderTellStatus flac_tell_load_cb( + const FLAC__StreamDecoder *decoder, + FLAC__uint64 *absolute_byte_offset, + void *client_data) { + FLAC_SDL_Data *data = (FLAC_SDL_Data *)client_data; + + int pos = SDL_RWtell (data->sdl_src); + + if (pos < 0) { + return FLAC__STREAM_DECODER_TELL_STATUS_ERROR; + } + else { + *absolute_byte_offset = (FLAC__uint64)pos; + return FLAC__STREAM_DECODER_TELL_STATUS_OK; + } +} + +static FLAC__StreamDecoderLengthStatus flac_length_load_cb( + const FLAC__StreamDecoder *decoder, + FLAC__uint64 *stream_length, + void *client_data) { + FLAC_SDL_Data *data = (FLAC_SDL_Data *)client_data; + + int pos = SDL_RWtell (data->sdl_src); + int length = SDL_RWseek (data->sdl_src, 0, SEEK_END); + + if (SDL_RWseek (data->sdl_src, pos, SEEK_SET) != pos || length < 0) { + /* there was an error attempting to return the stream to the original + * position, or the length was invalid. */ + return FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR; + } + else { + *stream_length = (FLAC__uint64)length; + return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; + } +} + +static FLAC__bool flac_eof_load_cb(const FLAC__StreamDecoder *decoder, + void *client_data) { + FLAC_SDL_Data *data = (FLAC_SDL_Data *)client_data; + + int pos = SDL_RWtell (data->sdl_src); + int end = SDL_RWseek (data->sdl_src, 0, SEEK_END); + + // was the original position equal to the end (a.k.a. the seek didn't move)? + if (pos == end) { + // must be EOF + return true; + } + else { + // not EOF, return to the original position + SDL_RWseek (data->sdl_src, pos, SEEK_SET); + + return false; + } +} + +static FLAC__StreamDecoderWriteStatus flac_write_load_cb( + const FLAC__StreamDecoder *decoder, + const FLAC__Frame *frame, + const FLAC__int32 *const buffer[], + void *client_data) { + FLAC_SDL_Data *data = (FLAC_SDL_Data *)client_data; + size_t i; + + if (data->flac_total_samples == 0) { + SDL_SetError ("Given FLAC file does not specify its sample count."); + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + } + + if (data->sdl_spec->channels != 2 || data->flac_bps != 16) { + SDL_SetError ("Current FLAC support is only for 16 bit Stereo files."); + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + } + + // check if it is the first audio frame so we can initialize the output + // buffer + if (frame->header.number.sample_number == 0) { + *(data->sdl_audio_len) = data->sdl_spec->size; + data->sdl_audio_read = 0; + *(data->sdl_audio_buf) = malloc (*(data->sdl_audio_len)); + + if (*(data->sdl_audio_buf) == NULL) { + SDL_SetError + ("Unable to allocate memory to store the FLAC stream."); + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + } + } + + Uint8 *buf = *(data->sdl_audio_buf); + + for (i = 0; i < frame->header.blocksize; i++) { + FLAC__int16 i16; + FLAC__uint16 ui16; + + i16 = (FLAC__int16)buffer[0][i]; + ui16 = (FLAC__uint16)i16; + + *(buf + (data->sdl_audio_read++)) = (char)(ui16); + *(buf + (data->sdl_audio_read++)) = (char)(ui16 >> 8); + + i16 = (FLAC__int16)buffer[1][i]; + ui16 = (FLAC__uint16)i16; + + *(buf + (data->sdl_audio_read++)) = (char)(ui16); + *(buf + (data->sdl_audio_read++)) = (char)(ui16 >> 8); + } + + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + +static void flac_metadata_load_cb( + const FLAC__StreamDecoder *decoder, + const FLAC__StreamMetadata *metadata, + void *client_data) { + FLAC_SDL_Data *data = (FLAC_SDL_Data *)client_data; + FLAC__uint64 total_samples; + unsigned bps; + + if (metadata->type == FLAC__METADATA_TYPE_STREAMINFO) { + // save the metadata right now for use later on + *(data->sdl_audio_buf) = NULL; + *(data->sdl_audio_len) = 0; + memset (data->sdl_spec, '\0', sizeof (SDL_AudioSpec)); + + data->sdl_spec->format = AUDIO_S16; + data->sdl_spec->freq = (int)(metadata->data.stream_info.sample_rate); + data->sdl_spec->channels = (Uint8)(metadata->data.stream_info.channels); + data->sdl_spec->samples = 8192; /* buffer size */ + + total_samples = metadata->data.stream_info.total_samples; + bps = metadata->data.stream_info.bits_per_sample; + + data->sdl_spec->size = total_samples * data->sdl_spec->channels * + (bps / 8); + data->flac_total_samples = total_samples; + data->flac_bps = bps; + } +} + +static void flac_error_load_cb( + const FLAC__StreamDecoder *decoder, + FLAC__StreamDecoderErrorStatus status, + void *client_data) { + // print an SDL error based on the error status + switch (status) { + case FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC: + SDL_SetError ("Error processing the FLAC file [LOST_SYNC]."); + break; + case FLAC__STREAM_DECODER_ERROR_STATUS_BAD_HEADER: + SDL_SetError ("Error processing the FLAC file [BAD_HEADER]."); + break; + case FLAC__STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH: + SDL_SetError ("Error processing the FLAC file [CRC_MISMATCH]."); + break; + case FLAC__STREAM_DECODER_ERROR_STATUS_UNPARSEABLE_STREAM: + SDL_SetError ("Error processing the FLAC file [UNPARSEABLE]."); + break; + default: + SDL_SetError ("Error processing the FLAC file [UNKNOWN]."); + break; + } +} + +/* don't call this directly; use Mix_LoadWAV_RW() for now. */ +SDL_AudioSpec *Mix_LoadFLAC_RW (SDL_RWops *src, int freesrc, + SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len) { + FLAC__StreamDecoder *decoder = 0; + FLAC__StreamDecoderInitStatus init_status; + int was_error = 1; + int was_init = 0; + Uint32 samplesize; + + // create the client data passing information + FLAC_SDL_Data* client_data; + client_data = (FLAC_SDL_Data *)malloc (sizeof (FLAC_SDL_Data)); + + if ((!src) || (!audio_buf) || (!audio_len)) /* sanity checks. */ + goto done; + + if (Mix_InitFLAC() < 0) + goto done; + + if ((decoder = flac.FLAC__stream_decoder_new ()) == NULL) { + SDL_SetError ("Unable to allocate FLAC decoder."); + goto done; + } + + init_status = flac.FLAC__stream_decoder_init_stream (decoder, + flac_read_load_cb, flac_seek_load_cb, + flac_tell_load_cb, flac_length_load_cb, + flac_eof_load_cb, flac_write_load_cb, + flac_metadata_load_cb, flac_error_load_cb, + client_data); + + if (init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { + SDL_SetError ("Unable to initialize FLAC stream decoder."); + goto done; + } + + was_init = 1; + + client_data->sdl_src = src; + client_data->sdl_spec = spec; + client_data->sdl_audio_buf = audio_buf; + client_data->sdl_audio_len = audio_len; + + if (!flac.FLAC__stream_decoder_process_until_end_of_stream (decoder)) { + SDL_SetError ("Unable to process FLAC file."); + goto done; + } + + was_error = 0; + + /* Don't return a buffer that isn't a multiple of samplesize */ + samplesize = ((spec->format & 0xFF) / 8) * spec->channels; + *audio_len &= ~(samplesize - 1); + +done: + if(was_init && decoder) { + flac.FLAC__stream_decoder_finish (decoder); + } + + if(decoder) { + flac.FLAC__stream_decoder_delete (decoder); + } + + if (src) { + if (freesrc) + SDL_RWclose (src); + else + SDL_RWseek (src, 0, SEEK_SET); + } + + if (was_error) + spec = NULL; + + Mix_QuitFLAC (); + + return spec; +} + +#endif // FLAC_MUSIC diff --git a/load_flac.h b/load_flac.h new file mode 100644 index 00000000..26a8b83c --- /dev/null +++ b/load_flac.h @@ -0,0 +1,29 @@ +/* + 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 + + This is the source needed to decode a FLAC into a waveform. + ~ Austen Dicken (admin@cvpcs.org). +*/ + +/* $Id: $ */ + +#ifdef FLAC_MUSIC +/* Don't call this directly; use Mix_LoadWAV_RW() for now. */ +SDL_AudioSpec *Mix_LoadFLAC_RW (SDL_RWops *src, int freesrc, + SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len); +#endif diff --git a/mixer.c b/mixer.c index 73e6eb9f..fb7f284c 100644 --- a/mixer.c +++ b/mixer.c @@ -34,6 +34,7 @@ #include "load_aiff.h" #include "load_voc.h" #include "load_ogg.h" +#include "load_flac.h" #define __MIX_INTERNAL_EFFECT__ #include "effects_internal.h" @@ -43,7 +44,8 @@ #define WAVE 0x45564157 /* "WAVE" */ #define FORM 0x4d524f46 /* "FORM" */ #define OGGS 0x5367674f /* "OggS" */ -#define CREA 0x61657243 /* "Crea" */ +#define CREA 0x61657243 /* "Crea" */ +#define FLAC 0x43614C66 /* "fLaC" */ static int audio_opened = 0; static SDL_AudioSpec mixer; @@ -454,6 +456,12 @@ Mix_Chunk *Mix_LoadWAV_RW(SDL_RWops *src, int freesrc) loaded = Mix_LoadOGG_RW(src, freesrc, &wavespec, (Uint8 **)&chunk->abuf, &chunk->alen); break; +#endif +#ifdef FLAC_MUSIC + case FLAC: + loaded = Mix_LoadFLAC_RW(src, freesrc, &wavespec, + (Uint8 **)&chunk->abuf, &chunk->alen); + break; #endif case CREA: loaded = Mix_LoadVOC_RW(src, freesrc, &wavespec, diff --git a/music.c b/music.c index e256c5b4..1cd27d15 100644 --- a/music.c +++ b/music.c @@ -89,6 +89,9 @@ #ifdef MP3_MAD_MUSIC #include "music_mad.h" #endif +#ifdef FLAC_MUSIC +#include "music_flac.h" +#endif #if defined(MP3_MUSIC) || defined(MP3_MAD_MUSIC) static SDL_AudioSpec used_mixer; @@ -132,6 +135,9 @@ struct _Mix_Music { #endif #ifdef MP3_MAD_MUSIC mad_data *mp3_mad; +#endif +#ifdef FLAC_MUSIC + FLAC_music *flac; #endif } data; Mix_Fading fading; @@ -345,6 +351,13 @@ void music_mixer(void *udata, Uint8 *stream, int len) break; #endif +#ifdef FLAC_MUSIC + case MUS_FLAC: + len = FLAC_playAudio(music_playing->data.flac, stream, len); + if (len > 0 && music_halt_or_loop()) + FLAC_playAudio(music_playing->data.flac, stream, len); + break; +#endif #ifdef MP3_MUSIC case MUS_MP3: smpeg.SMPEG_playAudio(music_playing->data.mp3, stream, len); @@ -469,6 +482,11 @@ int open_music(SDL_AudioSpec *mixer) ++music_error; } #endif +#ifdef FLAC_MUSIC + if ( FLAC_init(mixer) < 0 ) { + ++music_error; + } +#endif #if defined(MP3_MUSIC) || defined(MP3_MAD_MUSIC) /* Keep a copy of the mixer */ used_mixer = *mixer; @@ -602,6 +620,17 @@ Mix_Music *Mix_LoadMUS(const char *file) } } else #endif +#ifdef FLAC_MUSIC + /* FLAC files have the magic four bytes "fLaC" */ + if ( (ext && MIX_string_equals(ext, "FLAC")) || + strcmp((char *)magic, "fLaC") == 0 ) { + music->type = MUS_FLAC; + music->data.flac = FLAC_new(file); + if ( music->data.flac == NULL ) { + music->error = 1; + } + } else +#endif #ifdef MP3_MUSIC if ( (ext && MIX_string_equals(ext, "MPG")) || (ext && MIX_string_equals(ext, "MP3")) || @@ -720,6 +749,11 @@ void Mix_FreeMusic(Mix_Music *music) OGG_delete(music->data.ogg); break; #endif +#ifdef FLAC_MUSIC + case MUS_FLAC: + FLAC_delete(music->data.flac); + break; +#endif #ifdef MP3_MUSIC case MUS_MP3: smpeg.SMPEG_delete(music->data.mp3); @@ -813,6 +847,11 @@ static int music_internal_play(Mix_Music *music, double position) OGG_play(music->data.ogg); break; #endif +#ifdef FLAC_MUSIC + case MUS_FLAC: + FLAC_play(music->data.flac); + break; +#endif #ifdef MP3_MUSIC case MUS_MP3: smpeg.SMPEG_enableaudio(music->data.mp3,1); @@ -908,6 +947,11 @@ int music_internal_position(double position) OGG_jump_to_time(music_playing->data.ogg, position); break; #endif +#ifdef FLAC_MUSIC + case MUS_FLAC: + FLAC_jump_to_time(music_playing->data.flac, position); + break; +#endif #ifdef MP3_MUSIC case MUS_MP3: if ( position > 0.0 ) { @@ -997,6 +1041,11 @@ static void music_internal_volume(int volume) OGG_setvolume(music_playing->data.ogg, volume); break; #endif +#ifdef FLAC_MUSIC + case MUS_FLAC: + FLAC_setvolume(music_playing->data.flac, volume); + break; +#endif #ifdef MP3_MUSIC case MUS_MP3: smpeg.SMPEG_setvolume(music_playing->data.mp3,(int)(((float)volume/(float)MIX_MAX_VOLUME)*100.0)); @@ -1070,6 +1119,11 @@ static void music_internal_halt(void) OGG_stop(music_playing->data.ogg); break; #endif +#ifdef FLAC_MUSIC + case MUS_FLAC: + FLAC_stop(music_playing->data.flac); + break; +#endif #ifdef MP3_MUSIC case MUS_MP3: smpeg.SMPEG_stop(music_playing->data.mp3); @@ -1217,6 +1271,13 @@ static int music_internal_playing() } break; #endif +#ifdef FLAC_MUSIC + case MUS_FLAC: + if ( ! FLAC_playing(music_playing->data.flac) ) { + playing = 0; + } + break; +#endif #ifdef MP3_MUSIC case MUS_MP3: if ( smpeg.SMPEG_status(music_playing->data.mp3) != SMPEG_PLAYING ) @@ -1474,6 +1535,16 @@ Mix_Music *Mix_LoadMUS_RW(SDL_RWops *rw) } } else #endif +#ifdef FLAC_MUSIC + /* FLAC files have the magic four bytes "fLaC" */ + if ( strcmp((char *)magic, "fLaC") == 0 ) { + music->type = MUS_FLAC; + music->data.flac = FLAC_new_RW(rw); + if ( music->data.flac == NULL ) { + music->error = 1; + } + } else +#endif #ifdef MP3_MUSIC if ( magic[0] == 0xFF && (magic[1] & 0xF0) == 0xF0 ) { if ( Mix_InitMP3() == 0 ) { diff --git a/music_flac.c b/music_flac.c new file mode 100644 index 00000000..cfe47df8 --- /dev/null +++ b/music_flac.c @@ -0,0 +1,591 @@ +/* + 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 + + + This file is used to support SDL_LoadMUS playback of FLAC files. + ~ Austen Dicken (admin@cvpcs.org) +*/ + +#ifdef FLAC_MUSIC + +#include +#include +#include + +#include "SDL_mixer.h" +#include "dynamic_flac.h" +#include "music_flac.h" + +/* This is the format of the audio mixer data */ +static SDL_AudioSpec mixer; + +/* Initialize the FLAC player, with the given mixer settings + This function returns 0, or -1 if there was an error. + */ +int FLAC_init(SDL_AudioSpec *mixerfmt) { + mixer = *mixerfmt; + return(0); +} + +/* Set the volume for an FLAC stream */ +void FLAC_setvolume(FLAC_music *music, int volume) { + music->volume = volume; +} + +/* Load an FLAC stream from the given file */ +FLAC_music *FLAC_new(const char *file) { + SDL_RWops *rw; + + rw = SDL_RWFromFile (file, "rb"); + if (rw == NULL) { + SDL_SetError ("Couldn't open %s", file); + return NULL; + } + return FLAC_new_RW (rw); +} + +static FLAC__StreamDecoderReadStatus flac_read_music_cb( + const FLAC__StreamDecoder *decoder, + FLAC__byte buffer[], + size_t *bytes, + void *client_data) { + FLAC_music *data = (FLAC_music*)client_data; + + // make sure there is something to be reading + if (*bytes > 0) { + *bytes = SDL_RWread (data->rwops, buffer, sizeof (FLAC__byte), *bytes); + + if (*bytes < 0) { // error in read + return FLAC__STREAM_DECODER_READ_STATUS_ABORT; + } + else if (*bytes == 0 ) { // no data was read (EOF) + return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; + } + else { // data was read, continue + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; + } + } + else { + return FLAC__STREAM_DECODER_READ_STATUS_ABORT; + } +} + +static FLAC__StreamDecoderSeekStatus flac_seek_music_cb( + const FLAC__StreamDecoder *decoder, + FLAC__uint64 absolute_byte_offset, + void *client_data) { + FLAC_music *data = (FLAC_music*)client_data; + + if (SDL_RWseek (data->rwops, absolute_byte_offset, SEEK_SET) < 0) { + return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; + } + else { + return FLAC__STREAM_DECODER_SEEK_STATUS_OK; + } +} + +static FLAC__StreamDecoderTellStatus flac_tell_music_cb( + const FLAC__StreamDecoder *decoder, + FLAC__uint64 *absolute_byte_offset, + void *client_data ) { + FLAC_music *data = (FLAC_music*)client_data; + + int pos = SDL_RWtell (data->rwops); + + if (pos < 0) { + return FLAC__STREAM_DECODER_TELL_STATUS_ERROR; + } + else { + *absolute_byte_offset = (FLAC__uint64)pos; + return FLAC__STREAM_DECODER_TELL_STATUS_OK; + } +} + +static FLAC__StreamDecoderLengthStatus flac_length_music_cb ( + const FLAC__StreamDecoder *decoder, + FLAC__uint64 *stream_length, + void *client_data) { + FLAC_music *data = (FLAC_music*)client_data; + + int pos = SDL_RWtell (data->rwops); + int length = SDL_RWseek (data->rwops, 0, SEEK_END); + + if (SDL_RWseek (data->rwops, pos, SEEK_SET) != pos || length < 0) { + /* there was an error attempting to return the stream to the original + * position, or the length was invalid. */ + return FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR; + } + else { + *stream_length = (FLAC__uint64)length; + return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; + } +} + +static FLAC__bool flac_eof_music_cb( + const FLAC__StreamDecoder *decoder, + void *client_data ) { + FLAC_music *data = (FLAC_music*)client_data; + + int pos = SDL_RWtell (data->rwops); + int end = SDL_RWseek (data->rwops, 0, SEEK_END); + + // was the original position equal to the end (a.k.a. the seek didn't move)? + if (pos == end) { + // must be EOF + return true; + } + else { + // not EOF, return to the original position + SDL_RWseek (data->rwops, pos, SEEK_SET); + + return false; + } +} + +static FLAC__StreamDecoderWriteStatus flac_write_music_cb( + const FLAC__StreamDecoder *decoder, + const FLAC__Frame *frame, + const FLAC__int32 *const buffer[], + void *client_data) { + FLAC_music *data = (FLAC_music *)client_data; + size_t i; + + if (data->flac_data.total_samples == 0) { + SDL_SetError ("Given FLAC file does not specify its sample count."); + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + } + + if (data->flac_data.channels != 2 || + data->flac_data.bits_per_sample != 16) { + SDL_SetError("Current FLAC support is only for 16 bit Stereo files."); + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + } + + for (i = 0; i < frame->header.blocksize; i++) { + // make sure we still have at least two bytes that can be read (one for + // each channel) + if (data->flac_data.max_to_read >= 4) { + // does the data block exist? + if (!data->flac_data.data) { + data->flac_data.data_len = data->flac_data.max_to_read; + data->flac_data.data_read = 0; + + // create it + data->flac_data.data = + (char *)malloc (data->flac_data.data_len); + + if (!data->flac_data.data) { + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + } + } + + FLAC__int16 i16; + FLAC__uint16 ui16; + + i16 = (FLAC__int16)buffer[0][i]; + ui16 = (FLAC__uint16)i16; + + *((data->flac_data.data) + (data->flac_data.data_read++)) = + (char)(ui16); + *((data->flac_data.data) + (data->flac_data.data_read++)) = + (char)(ui16 >> 8); + + i16 = (FLAC__int16)buffer[1][i]; + ui16 = (FLAC__uint16)i16; + + *((data->flac_data.data) + (data->flac_data.data_read++)) = + (char)(ui16); + *((data->flac_data.data) + (data->flac_data.data_read++)) = + (char)(ui16 >> 8); + + data->flac_data.max_to_read -= 4; + + if (data->flac_data.max_to_read < 4) { + // we need to set this so that the read halts from the + // FLAC_getsome function. + data->flac_data.max_to_read = 0; + } + } + else { + // we need to write to the overflow + if (!data->flac_data.overflow) { + data->flac_data.overflow_len = + 4 * (frame->header.blocksize - i); + data->flac_data.overflow_read = 0; + + // make it big enough for the rest of the block + data->flac_data.overflow = + (char *)malloc (data->flac_data.overflow_len); + + if (!data->flac_data.overflow) { + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + } + } + + FLAC__int16 i16; + FLAC__uint16 ui16; + + i16 = (FLAC__int16)buffer[0][i]; + ui16 = (FLAC__uint16)i16; + + *((data->flac_data.overflow) + (data->flac_data.overflow_read++)) = + (char)(ui16); + *((data->flac_data.overflow) + (data->flac_data.overflow_read++)) = + (char)(ui16 >> 8); + + i16 = (FLAC__int16)buffer[1][i]; + ui16 = (FLAC__uint16)i16; + + *((data->flac_data.overflow) + (data->flac_data.overflow_read++)) = + (char)(ui16); + *((data->flac_data.overflow) + (data->flac_data.overflow_read++)) = + (char)(ui16 >> 8); + } + } + + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + +static void flac_metadata_music_cb( + const FLAC__StreamDecoder *decoder, + const FLAC__StreamMetadata *metadata, + void *client_data) { + FLAC_music *data = (FLAC_music *)client_data; + + if (metadata->type == FLAC__METADATA_TYPE_STREAMINFO) { + data->flac_data.sample_rate = metadata->data.stream_info.sample_rate; + data->flac_data.channels = metadata->data.stream_info.channels; + data->flac_data.total_samples = + metadata->data.stream_info.total_samples; + data->flac_data.bits_per_sample = + metadata->data.stream_info.bits_per_sample; + data->flac_data.sample_size = data->flac_data.channels * + ((data->flac_data.bits_per_sample) / 8); + } +} + +static void flac_error_music_cb( + const FLAC__StreamDecoder *decoder, + FLAC__StreamDecoderErrorStatus status, + void *client_data) { + // print an SDL error based on the error status + switch (status) { + case FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC: + SDL_SetError ("Error processing the FLAC file [LOST_SYNC]."); + break; + case FLAC__STREAM_DECODER_ERROR_STATUS_BAD_HEADER: + SDL_SetError ("Error processing the FLAC file [BAD_HEADER]."); + break; + case FLAC__STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH: + SDL_SetError ("Error processing the FLAC file [CRC_MISMATCH]."); + break; + case FLAC__STREAM_DECODER_ERROR_STATUS_UNPARSEABLE_STREAM: + SDL_SetError ("Error processing the FLAC file [UNPARSEABLE]."); + break; + default: + SDL_SetError ("Error processing the FLAC file [UNKNOWN]."); + break; + } +} + +/* Load an FLAC stream from an SDL_RWops object */ +FLAC_music *FLAC_new_RW(SDL_RWops *rw) { + FLAC_music *music; + int init_stage = 0; + int was_error = 1; + + music = (FLAC_music *)malloc ( sizeof (*music)); + if (music) { + /* Initialize the music structure */ + memset (music, 0, (sizeof (*music))); + FLAC_stop (music); + FLAC_setvolume (music, MIX_MAX_VOLUME); + music->section = -1; + music->rwops = rw; + music->flac_data.max_to_read = 0; + music->flac_data.overflow = NULL; + music->flac_data.overflow_len = 0; + music->flac_data.overflow_read = 0; + music->flac_data.data = NULL; + music->flac_data.data_len = 0; + music->flac_data.data_read = 0; + + if (Mix_InitFLAC () >= 0) { + init_stage++; // stage 1! + + music->flac_decoder = flac.FLAC__stream_decoder_new (); + + if (music->flac_decoder != NULL) { + init_stage++; // stage 2! + + if (flac.FLAC__stream_decoder_init_stream( + music->flac_decoder, + flac_read_music_cb, flac_seek_music_cb, + flac_tell_music_cb, flac_length_music_cb, + flac_eof_music_cb, flac_write_music_cb, + flac_metadata_music_cb, flac_error_music_cb, + music) == FLAC__STREAM_DECODER_INIT_STATUS_OK ) { + init_stage++; // stage 3! + + if (flac.FLAC__stream_decoder_process_until_end_of_metadata + (music->flac_decoder)) { + was_error = 0; + } + } + } + } + + if (was_error) { + switch (init_stage) { + case 3: + flac.FLAC__stream_decoder_finish( music->flac_decoder ); + case 2: + flac.FLAC__stream_decoder_delete( music->flac_decoder ); + case 1: + Mix_QuitFLAC(); + case 0: + free(music); + SDL_RWclose(rw); + break; + } + + SDL_SetError ("There was an error in stage [%d] of FLAC init.", + init_stage); + + return NULL; + } + } + else { + SDL_OutOfMemory(); + } + + return music; +} + +/* Start playback of a given FLAC stream */ +void FLAC_play(FLAC_music *music) { + music->playing = 1; +} + +/* Return non-zero if a stream is currently playing */ +int FLAC_playing(FLAC_music *music) { + return(music->playing); +} + +/* Read some FLAC stream data and convert it for output */ +static void FLAC_getsome(FLAC_music *music) { + int section; + SDL_AudioCVT *cvt; + + /* GET AUDIO wAVE DATA */ + // set the max number of characters to read + music->flac_data.max_to_read = 8192; + + // clear out the data buffer if it exists + if (music->flac_data.data) { + free (music->flac_data.data); + } + + music->flac_data.data_len = music->flac_data.max_to_read; + music->flac_data.data_read = 0; + music->flac_data.data = (char *)malloc (music->flac_data.data_len); + + // we have data to read + while(music->flac_data.max_to_read > 0) { + // first check if there is data in the overflow from before + if (music->flac_data.overflow) { + size_t overflow_len = music->flac_data.overflow_read; + + if (overflow_len > music->flac_data.max_to_read) { + size_t overflow_extra_len = overflow_len - + music->flac_data.max_to_read; + + char* new_overflow = (char *)malloc (overflow_extra_len); + memcpy (music->flac_data.data+music->flac_data.data_read, + music->flac_data.overflow, music->flac_data.max_to_read); + music->flac_data.data_read += music->flac_data.max_to_read; + memcpy (new_overflow, + music->flac_data.overflow + music->flac_data.max_to_read, + overflow_extra_len); + free (music->flac_data.overflow); + music->flac_data.overflow = new_overflow; + music->flac_data.overflow_len = overflow_extra_len; + music->flac_data.overflow_read = 0; + music->flac_data.max_to_read = 0; + } + else { + memcpy (music->flac_data.data+music->flac_data.data_read, + music->flac_data.overflow, overflow_len); + music->flac_data.data_read += overflow_len; + free (music->flac_data.overflow); + music->flac_data.overflow = NULL; + music->flac_data.overflow_len = 0; + music->flac_data.overflow_read = 0; + music->flac_data.max_to_read -= overflow_len; + } + } + else { + if (!flac.FLAC__stream_decoder_process_single ( + music->flac_decoder)) { + music->flac_data.max_to_read = 0; + } + + if (flac.FLAC__stream_decoder_get_state (music->flac_decoder) + == FLAC__STREAM_DECODER_END_OF_STREAM) { + music->flac_data.max_to_read = 0; + } + } + } + + if (music->flac_data.data_read <= 0) { + if (music->flac_data.data_read == 0) { + music->playing = 0; + } + return; + } + cvt = &music->cvt; + if (section != music->section) { + + SDL_BuildAudioCVT (cvt, AUDIO_S16, (Uint8)music->flac_data.channels, + (int)music->flac_data.sample_rate, mixer.format, + mixer.channels, mixer.freq); + if (cvt->buf) { + free (cvt->buf); + } + cvt->buf = (Uint8 *)malloc (music->flac_data.data_len * cvt->len_mult); + music->section = section; + } + if (cvt->buf) { + memcpy (cvt->buf, music->flac_data.data, music->flac_data.data_read); + if (cvt->needed) { + cvt->len = music->flac_data.data_read; + SDL_ConvertAudio (cvt); + } + else { + cvt->len_cvt = music->flac_data.data_read; + } + music->len_available = music->cvt.len_cvt; + music->snd_available = music->cvt.buf; + } + else { + SDL_SetError ("Out of memory"); + music->playing = 0; + } +} + +/* Play some of a stream previously started with FLAC_play() */ +int FLAC_playAudio(FLAC_music *music, Uint8 *snd, int len) { + int mixable; + + while ((len > 0) && music->playing) { + if (!music->len_available) { + FLAC_getsome (music); + } + mixable = len; + if (mixable > music->len_available) { + mixable = music->len_available; + } + if (music->volume == MIX_MAX_VOLUME) { + memcpy (snd, music->snd_available, mixable); + } + else { + SDL_MixAudio (snd, music->snd_available, mixable, music->volume); + } + music->len_available -= mixable; + music->snd_available += mixable; + len -= mixable; + snd += mixable; + } + + return len; +} + +/* Stop playback of a stream previously started with FLAC_play() */ +void FLAC_stop(FLAC_music *music) { + music->playing = 0; +} + +/* Close the given FLAC_music object */ +void FLAC_delete(FLAC_music *music) { + if (music) { + if (music->flac_decoder) { + flac.FLAC__stream_decoder_finish (music->flac_decoder); + flac.FLAC__stream_decoder_delete (music->flac_decoder); + } + + if (music->flac_data.data) { + free (music->flac_data.data); + } + + if (music->flac_data.overflow) { + free (music->flac_data.overflow); + } + + if (music->cvt.buf) { + free (music->cvt.buf); + } + + free (music); + + Mix_QuitFLAC (); + } +} + +/* Jump (seek) to a given position (time is in seconds) */ +void FLAC_jump_to_time(FLAC_music *music, double time) { + if (music) { + if (music->flac_decoder) { + double seek_sample = music->flac_data.sample_rate * time; + + // clear data if it has data + if (music->flac_data.data) { + free (music->flac_data.data); + music->flac_data.data = NULL; + } + + // clear overflow if it has data + if (music->flac_data.overflow) { + free (music->flac_data.overflow); + music->flac_data.overflow = NULL; + } + + if (!flac.FLAC__stream_decoder_seek_absolute (music->flac_decoder, + (FLAC__uint64)seek_sample)) { + if (flac.FLAC__stream_decoder_get_state (music->flac_decoder) + == FLAC__STREAM_DECODER_SEEK_ERROR) { + flac.FLAC__stream_decoder_flush (music->flac_decoder); + } + + SDL_SetError + ("Seeking of FLAC stream failed: libFLAC seek failed."); + } + } + else { + SDL_SetError + ("Seeking of FLAC stream failed: FLAC decoder was NULL."); + } + } + else { + SDL_SetError ("Seeking of FLAC stream failed: music was NULL."); + } +} + +#endif /* FLAC_MUSIC */ diff --git a/music_flac.h b/music_flac.h new file mode 100644 index 00000000..d2d50073 --- /dev/null +++ b/music_flac.h @@ -0,0 +1,93 @@ +/* + 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 + + Header to handle loading FLAC music files in SDL. + ~ Austen Dicken (admin@cvpcs.org) +*/ + +/* $Id: $ */ + +#ifdef FLAC_MUSIC + +#include + +typedef struct { + FLAC__uint64 sample_size; + unsigned sample_rate; + unsigned channels; + unsigned bits_per_sample; + FLAC__uint64 total_samples; + + // the following are used to handle the callback nature of the writer + int max_to_read; + char *data; // pointer to beginning of data array + int data_len; // size of data array + int data_read; // amount of data array used + char *overflow; // pointer to beginning of overflow array + int overflow_len; // size of overflow array + int overflow_read; // amount of overflow array used +} FLAC_Data; + +typedef struct { + int playing; + int volume; + int section; + FLAC__StreamDecoder *flac_decoder; + FLAC_Data flac_data; + SDL_RWops *rwops; + SDL_AudioCVT cvt; + int len_available; + Uint8 *snd_available; +} FLAC_music; + +/* Initialize the FLAC player, with the given mixer settings + This function returns 0, or -1 if there was an error. + */ +extern int FLAC_init(SDL_AudioSpec *mixer); + +/* Set the volume for a FLAC stream */ +extern void FLAC_setvolume(FLAC_music *music, int volume); + +/* Load a FLAC stream from the given file */ +extern FLAC_music *FLAC_new(const char *file); + +/* Load an FLAC stream from an SDL_RWops object */ +extern FLAC_music *FLAC_new_RW(SDL_RWops *rw); + +/* Start playback of a given FLAC stream */ +extern void FLAC_play(FLAC_music *music); + +/* Return non-zero if a stream is currently playing */ +extern int FLAC_playing(FLAC_music *music); + +/* Play some of a stream previously started with FLAC_play() */ +extern int FLAC_playAudio(FLAC_music *music, Uint8 *stream, int len); + +/* Stop playback of a stream previously started with FLAC_play() */ +extern void FLAC_stop(FLAC_music *music); + +/* Close the given FLAC stream */ +extern void FLAC_delete(FLAC_music *music); + +/* Jump (seek) to a given position (time is in seconds) */ +extern void FLAC_jump_to_time(FLAC_music *music, double time); + +#endif /* FLAC_MUSIC */