Skip to content

Commit

Permalink
mp3: Implemented MP3 decoding with dr_mp3.
Browse files Browse the repository at this point in the history
Also works with .mp1 and .mp2 files.  :)
  • Loading branch information
icculus committed Jul 17, 2018
1 parent f257d65 commit 848a763
Show file tree
Hide file tree
Showing 5 changed files with 3,035 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CMakeLists.txt
Expand Up @@ -52,6 +52,7 @@ sdlsound_decoder_option(RAW "raw PCM audio" ".RAW")
sdlsound_decoder_option(SHN "Shorten" ".SHN")
sdlsound_decoder_option(SPEEX "Speex" ".SPEEX")
sdlsound_decoder_option(MODPLUG "ModPlug" "various tracker formats")
sdlsound_decoder_option(MP3 "MPEG-1 Layers I-III" ".MP3, .MP2, .MP1")

if(APPLE)
sdlsound_decoder_option(COREAUDIO "CoreAudio" "various audio formats")
Expand Down Expand Up @@ -109,6 +110,7 @@ set(SDLSOUND_SRCS
src/SDL_sound_coreaudio.c
src/SDL_sound_flac.c
src/SDL_sound_modplug.c
src/SDL_sound_mp3.c
src/SDL_sound_raw.c
src/SDL_sound_shn.c
src/SDL_sound_speex.c
Expand Down Expand Up @@ -233,6 +235,7 @@ message_bool_option("RAW support" SDLSOUND_DECODER_RAW)
message_bool_option("SHN support" SDLSOUND_DECODER_SHN)
message_bool_option("SPEEX support" SDLSOUND_DECODER_SPEEX)
message_bool_option("MODPLUG support" SDLSOUND_DECODER_MODPLUG)
message_bool_option("MP3 support" SDLSOUND_DECODER_MP3)
message_bool_option("COREAUDIO support" SDLSOUND_DECODER_COREAUDIO)
message_bool_option("Build static library" SDLSOUND_BUILD_STATIC)
message_bool_option("Build shared library" SDLSOUND_BUILD_SHARED)
Expand Down
4 changes: 4 additions & 0 deletions src/SDL_sound.c
Expand Up @@ -20,6 +20,7 @@

/* All these externs may be missing; we check SOUND_SUPPORTS_xxx before use. */
extern const Sound_DecoderFunctions __Sound_DecoderFunctions_MODPLUG;
extern const Sound_DecoderFunctions __Sound_DecoderFunctions_MP3;
extern const Sound_DecoderFunctions __Sound_DecoderFunctions_WAV;
extern const Sound_DecoderFunctions __Sound_DecoderFunctions_AIFF;
extern const Sound_DecoderFunctions __Sound_DecoderFunctions_AU;
Expand All @@ -43,6 +44,9 @@ static decoder_element decoders[] =
#if SOUND_SUPPORTS_MODPLUG
{ 0, &__Sound_DecoderFunctions_MODPLUG },
#endif
#if SOUND_SUPPORTS_MP3
{ 0, &__Sound_DecoderFunctions_MP3 },
#endif
#if SOUND_SUPPORTS_WAV
{ 0, &__Sound_DecoderFunctions_WAV },
#endif
Expand Down
3 changes: 3 additions & 0 deletions src/SDL_sound_internal.h
Expand Up @@ -39,6 +39,9 @@
#define SNDDBG(x)
#endif

#ifndef SOUND_SUPPORTS_MP3
#define SOUND_SUPPORTS_MP3 1
#endif
#ifndef SOUND_SUPPORTS_MODPLUG
#define SOUND_SUPPORTS_MODPLUG 1
#endif
Expand Down
193 changes: 193 additions & 0 deletions src/SDL_sound_mp3.c
@@ -0,0 +1,193 @@
/**
* SDL_sound; An abstract sound format decoding API.
*
* Please see the file LICENSE.txt in the source's root directory.
*
* This file written by Ryan C. Gordon.
*/

/*
* MP3 decoder for SDL_sound.
*
* !!! FIXME: write something here.
*
* dr_mp3 is here: https://github.com/mackron/dr_libs/
*/

#define __SDL_SOUND_INTERNAL__
#include "SDL_sound_internal.h"

#if SOUND_SUPPORTS_MP3

#define DR_MP3_IMPLEMENTATION
#define DR_MP3_NO_STDIO 1
#define DRMP3_ASSERT(x) SDL_assert((x))
#define DRMP3_MALLOC(sz) SDL_malloc((sz))
#define DRMP3_REALLOC(p, sz) SDL_realloc((p), (sz))
#define DRMP3_FREE(p) SDL_free((p))
#define DRMP3_COPY_MEMORY(dst, src, sz) SDL_memcpy((dst), (src), (sz))
#define DRMP3_ZERO_MEMORY(p, sz) SDL_memset((p), 0, (sz))

#if !defined(__clang_analyzer__)
#ifdef memset
#undef memset
#endif
#ifdef memcpy
#undef memcpy
#endif
#ifdef memmove
#undef memmove
#endif
#define memset SDL_memset
#define memcpy SDL_memcpy
#define memmove SDL_memmove
#endif

#include "dr_mp3.h"

static int MP3_init(void);
static void MP3_quit(void);
static int MP3_open(Sound_Sample *sample, const char *ext);
static void MP3_close(Sound_Sample *sample);
static Uint32 MP3_read(Sound_Sample *sample);
static int MP3_rewind(Sound_Sample *sample);
static int MP3_seek(Sound_Sample *sample, Uint32 ms);

/* dr_mp3 will play layer 1 and 2 files, too */
static const char *extensions_mp3[] = { "MP3", "MP2", "MP1", NULL };

const Sound_DecoderFunctions __Sound_DecoderFunctions_MP3 =
{
{
extensions_mp3,
"MPEG-1 Audio Layer I-III",
"Ryan C. Gordon <icculus@icculus.org>",
"https://icculus.org/SDL_sound/"
},

MP3_init, /* init() method */
MP3_quit, /* quit() method */
MP3_open, /* open() method */
MP3_close, /* close() method */
MP3_read, /* read() method */
MP3_rewind, /* rewind() method */
MP3_seek /* seek() method */
};

static size_t mp3_read(void* pUserData, void* pBufferOut, size_t bytesToRead)
{
Uint8 *ptr = (Uint8 *) pBufferOut;
Sound_Sample *sample = (Sound_Sample *) pUserData;
Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
SDL_RWops *rwops = internal->rw;
size_t retval = 0;

/* !!! FIXME: dr_mp3 treats returning less than bytesToRead as EOF. So we can't EAGAIN. */
while (retval < bytesToRead)
{
const size_t rc = SDL_RWread(rwops, ptr, 1, bytesToRead);
if (rc == 0)
{
sample->flags |= SOUND_SAMPLEFLAG_EOF;
break;
} /* if */
else if (retval == -1)
{
sample->flags |= SOUND_SAMPLEFLAG_ERROR;
break;
} /* else if */
else
{
retval += rc;
ptr += rc;
} /* else */
} /* while */

return retval;
} /* mp3_read */

static drmp3_bool32 mp3_seek(void* pUserData, int offset, drmp3_seek_origin origin)
{
const int whence = (origin == drmp3_seek_origin_start) ? RW_SEEK_SET : RW_SEEK_CUR;
Sound_Sample *sample = (Sound_Sample *) pUserData;
Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
return (SDL_RWseek(internal->rw, offset, whence) != -1) ? DRMP3_TRUE : DRMP3_FALSE;
} /* mp3_seek */


static int MP3_init(void)
{
return 1; /* always succeeds. */
} /* MP3_init */


static void MP3_quit(void)
{
/* it's a no-op. */
} /* MP3_quit */

static int MP3_open(Sound_Sample *sample, const char *ext)
{
Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
drmp3 *dr = (drmp3 *) SDL_calloc(1, sizeof (drmp3));

BAIL_IF_MACRO(!dr, ERR_OUT_OF_MEMORY, 0);
if (drmp3_init(dr, mp3_read, mp3_seek, sample, NULL) != DRMP3_TRUE)
{
SDL_free(dr);
BAIL_IF_MACRO(sample->flags & SOUND_SAMPLEFLAG_ERROR, ERR_IO_ERROR, 0);
BAIL_MACRO("MP3: Not an MPEG-1 layer 1-3 stream.", 0);
} /* if */

SNDDBG(("MP3: Accepting data stream.\n"));
sample->flags = SOUND_SAMPLEFLAG_CANSEEK;

sample->actual.channels = dr->channels;
sample->actual.rate = dr->sampleRate;
sample->actual.format = AUDIO_F32SYS; /* dr_mp3 only does float. */

internal->total_time = -1; /* !!! FIXME? */
internal->decoder_private = dr;

return 1;
} /* MP3_open */

static void MP3_close(Sound_Sample *sample)
{
Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
drmp3 *dr = (drmp3 *) internal->decoder_private;
drmp3_uninit(dr);
SDL_free(dr);
} /* MP3_close */

static Uint32 MP3_read(Sound_Sample *sample)
{
Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
const int channels = (int) sample->actual.channels;
drmp3 *dr = (drmp3 *) internal->decoder_private;
const drmp3_uint64 frames_to_read = (internal->buffer_size / channels) / sizeof (float);
const drmp3_uint64 rc = drmp3_read_f32(dr, frames_to_read, (float *) internal->buffer);
/* !!! FIXME: the mp3_read callback sets ERROR and EOF flags, but this only tells you about i/o errors, not corruption. */
return rc * channels * sizeof (float);
} /* MP3_read */

static int MP3_rewind(Sound_Sample *sample)
{
Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
drmp3 *dr = (drmp3 *) internal->decoder_private;
return (drmp3_seek_to_frame(dr, 0) == DRMP3_TRUE);
} /* MP3_rewind */

static int MP3_seek(Sound_Sample *sample, Uint32 ms)
{
Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque;
drmp3 *dr = (drmp3 *) internal->decoder_private;
const float frames_per_ms = ((float) sample->actual.rate) / 1000.0f;
const drmp3_uint64 frame_offset = (drmp3_uint64) (frames_per_ms * ((float) ms));
return (drmp3_seek_to_frame(dr, frame_offset) == DRMP3_TRUE);
} /* MP3_seek */

#endif /* SOUND_SUPPORTS_MP3 */

/* end of SDL_sound_mp3.c ... */

0 comments on commit 848a763

Please sign in to comment.