From d4798adc6d4977064c66ef79dbfafa1665f2f318 Mon Sep 17 00:00:00 2001 From: David Reid Date: Sat, 24 Feb 2018 14:51:10 +1000 Subject: [PATCH] Initial work on the MP3 decoder. This currently uses minimp3, but this may change. --- examples/multi_playback.c | 18 ++- mini_al.h | 328 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 343 insertions(+), 3 deletions(-) diff --git a/examples/multi_playback.c b/examples/multi_playback.c index afe7152b..6323f93e 100644 --- a/examples/multi_playback.c +++ b/examples/multi_playback.c @@ -1,13 +1,25 @@ #define _CRT_SECURE_NO_WARNINGS // These are implemented at the bottom of this file. -#define STB_VORBIS_HEADER_ONLY -#include "../extras/stb_vorbis.c" +//#define STB_VORBIS_HEADER_ONLY +//#include "../extras/stb_vorbis.c" #include "../extras/dr_flac.h" #include "../extras/dr_wav.h" #include "../extras/jar_mod.h" #include "../extras/jar_xm.h" +#if defined(_MSC_VER) + #pragma warning(push) + #pragma warning(disable:4244) +#endif +#define MINIMP3_IMPLEMENTATION +#include "../extras/minimp3.h" +#if defined(_MSC_VER) + #pragma warning(pop) + #pragma warning(disable:4244) +#endif + + #define MAL_IMPLEMENTATION #include "../mini_al.h" @@ -254,7 +266,7 @@ end:; #endif #endif #undef STB_VORBIS_HEADER_ONLY -#include "../extras/stb_vorbis.c" +//#include "../extras/stb_vorbis.c" #if defined(_MSC_VER) #pragma warning(pop) #endif diff --git a/mini_al.h b/mini_al.h index 1b81bb5f..73aa1a2b 100644 --- a/mini_al.h +++ b/mini_al.h @@ -1416,6 +1416,9 @@ static inline mal_device_config mal_device_config_init_playback(mal_format forma // Initializes a sample rate conversion object. mal_result mal_src_init(mal_src_config* pConfig, mal_src_read_proc onRead, void* pUserData, mal_src* pSRC); +// Dynamically adjusts the input sample rate. +mal_result mal_src_set_input_sample_rate(mal_src* pSRC, mal_uint32 sampleRateIn); + // Dynamically adjusts the output sample rate. // // This is useful for dynamically adjust pitch. Keep in mind, however, that this will speed up or slow down the sound. If this @@ -1603,11 +1606,13 @@ mal_result mal_decoder_init(mal_decoder_read_proc onRead, mal_decoder_seek_proc mal_result mal_decoder_init_wav(mal_decoder_read_proc onRead, mal_decoder_seek_proc onSeek, void* pUserData, const mal_decoder_config* pConfig, mal_decoder* pDecoder); mal_result mal_decoder_init_flac(mal_decoder_read_proc onRead, mal_decoder_seek_proc onSeek, void* pUserData, const mal_decoder_config* pConfig, mal_decoder* pDecoder); mal_result mal_decoder_init_vorbis(mal_decoder_read_proc onRead, mal_decoder_seek_proc onSeek, void* pUserData, const mal_decoder_config* pConfig, mal_decoder* pDecoder); +mal_result mal_decoder_init_mp3(mal_decoder_read_proc onRead, mal_decoder_seek_proc onSeek, void* pUserData, const mal_decoder_config* pConfig, mal_decoder* pDecoder); mal_result mal_decoder_init_memory(const void* pData, size_t dataSize, const mal_decoder_config* pConfig, mal_decoder* pDecoder); mal_result mal_decoder_init_memory_wav(const void* pData, size_t dataSize, const mal_decoder_config* pConfig, mal_decoder* pDecoder); mal_result mal_decoder_init_memory_flac(const void* pData, size_t dataSize, const mal_decoder_config* pConfig, mal_decoder* pDecoder); mal_result mal_decoder_init_memory_vorbis(const void* pData, size_t dataSize, const mal_decoder_config* pConfig, mal_decoder* pDecoder); +mal_result mal_decoder_init_memory_mp3(const void* pData, size_t dataSize, const mal_decoder_config* pConfig, mal_decoder* pDecoder); #ifndef MAL_NO_STDIO mal_result mal_decoder_init_file(const char* pFilePath, const mal_decoder_config* pConfig, mal_decoder* pDecoder); @@ -10397,6 +10402,19 @@ mal_result mal_src_init(mal_src_config* pConfig, mal_src_read_proc onRead, void* return MAL_SUCCESS; } +mal_result mal_src_set_input_sample_rate(mal_src* pSRC, mal_uint32 sampleRateIn) +{ + if (pSRC == NULL) return MAL_INVALID_ARGS; + + // Must have a sample rate of > 0. + if (sampleRateIn == 0) { + return MAL_INVALID_ARGS; + } + + pSRC->config.sampleRateIn = sampleRateIn; + return MAL_SUCCESS; +} + mal_result mal_src_set_output_sample_rate(mal_src* pSRC, mal_uint32 sampleRateOut) { if (pSRC == NULL) return MAL_INVALID_ARGS; @@ -11787,6 +11805,8 @@ static mal_uint32 mal_vorbis_decoder_read(mal_vorbis_decoder* pVorbis, mal_decod if (pNewData == NULL) { return totalFramesRead; // Out of memory. } + + pVorbis->pData = pNewData; } // Fill in a chunk. @@ -11820,6 +11840,7 @@ static mal_result mal_vorbis_decoder_seek_to_frame(mal_vorbis_decoder* pVorbis, stb_vorbis_flush_pushdata(pVorbis->pInternalVorbis); pVorbis->framesConsumed = 0; pVorbis->framesRemaining = 0; + pVorbis->dataSize = 0; float buffer[4096]; while (frameIndex > 0) { @@ -11982,6 +12003,269 @@ mal_result mal_decoder_init_vorbis__internal(const mal_decoder_config* pConfig, } #endif +// MP3 +#if defined(MINIMP3_MAX_SAMPLES_PER_FRAME) || defined(MINIMP3_IMPLEMENTATION) +#define MAL_HAS_MP3 + +// The size in bytes of each chunk of data to read from the MP3 stream. minimp3 recommends 16K. +#define MAL_MP3_DATA_CHUNK_SIZE 16384 + +typedef struct +{ + mp3dec_t internalMP3; + mal_uint8* pData; + size_t dataSize; + size_t dataCapacity; + mal_uint32 framesConsumed; // The number of frames consumed in pPacketData. + mal_uint32 framesRemaining; // The number of frames remaining in pPacketData. + mal_uint32 packetChannels; // The channel count can technically be different across frames. + mal_uint32 packetSampleRate; // The sample rate can change across frames. + mal_int16 pPacketData[MINIMP3_MAX_SAMPLES_PER_FRAME]; + mal_src src; // For handling variable sample rates between frames. +} mal_mp3_decoder; + +static mal_uint32 mal_mp3_decoder_read_src(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut, void* pUserData) +{ + mal_decoder* pDecoder = (mal_decoder*)pUserData; + mal_assert(pDecoder != NULL); + mal_assert(pDecoder->onRead != NULL); + + mal_mp3_decoder* pMP3 = (mal_mp3_decoder*)pDecoder->pInternalDecoder; + mal_assert(pMP3 != NULL); + + mal_int16* pFramesOut16 = (mal_int16*)pFramesOut; + mal_uint32 totalFramesRead = 0; + + while (frameCount > 0) { + // Read from the in-memory buffer first. + while (pMP3->framesRemaining > 0 && frameCount > 0) { + if (pMP3->packetChannels == 1) { + pFramesOut16[0] = pMP3->pPacketData[pMP3->framesConsumed]; + pFramesOut16[1] = pMP3->pPacketData[pMP3->framesConsumed]; + } else { + mal_assert(pMP3->packetChannels == 2); + pFramesOut16[0] = pMP3->pPacketData[(pMP3->framesConsumed*pMP3->packetChannels)+0]; + pFramesOut16[1] = pMP3->pPacketData[(pMP3->framesConsumed*pMP3->packetChannels)+1]; + } + + pMP3->framesConsumed += 1; + pMP3->framesRemaining -= 1; + frameCount -= 1; + totalFramesRead += 1; + pFramesOut16 += pSRC->config.channels; // Should always be equal to 2. + } + + if (frameCount == 0) { + break; + } + + mal_assert(pMP3->framesRemaining == 0); + + // At this point we have exhausted our in-memory buffer so we need to re-fill. Note that the sample rate may have changed + // at this point which means we'll also need to update our sample rate conversion pipeline. + do + { + // minimp3 recommends doing data submission in 16K chunks. If we don't have at least 16K bytes available, get more. + if (pMP3->dataSize < MAL_MP3_DATA_CHUNK_SIZE) { + if (pMP3->dataCapacity < MAL_MP3_DATA_CHUNK_SIZE) { + pMP3->dataCapacity = MAL_MP3_DATA_CHUNK_SIZE; + mal_uint8* pNewData = (mal_uint8*)mal_realloc(pMP3->pData, pMP3->dataCapacity); + if (pNewData == NULL) { + return totalFramesRead; // Out of memory. + } + + pMP3->pData = pNewData; + } + + size_t bytesRead = pDecoder->onRead(pDecoder, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize)); + pMP3->dataSize += bytesRead; + } + + mp3dec_frame_info_t info; + mal_uint32 samplesRead = mp3dec_decode_frame(&pMP3->internalMP3, pMP3->pData, pMP3->dataSize, pMP3->pPacketData, &info); + if (samplesRead != 0) { + size_t leftoverDataSize = (pMP3->dataSize - (size_t)info.frame_bytes); + for (size_t i = 0; i < leftoverDataSize; ++i) { + pMP3->pData[i] = pMP3->pData[i + (size_t)info.frame_bytes]; + } + + pMP3->dataSize = leftoverDataSize; + pMP3->framesConsumed = 0; + pMP3->framesRemaining = samplesRead; + pMP3->packetChannels = info.channels; + pMP3->packetSampleRate = info.hz; + mal_src_set_input_sample_rate(&pMP3->src, pMP3->packetSampleRate); + break; + } else { + // Need more data. minimp3 recommends doing data submission in 16K chunks. + if (pMP3->dataCapacity == pMP3->dataSize) { + // No room. Expand. + pMP3->dataCapacity += MAL_MP3_DATA_CHUNK_SIZE; + mal_uint8* pNewData = (mal_uint8*)mal_realloc(pMP3->pData, pMP3->dataCapacity); + if (pNewData == NULL) { + return totalFramesRead; // Out of memory. + } + + pMP3->pData = pNewData; + } + + // Fill in a chunk. + size_t bytesRead = pDecoder->onRead(pDecoder, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize)); + if (bytesRead == 0) { + return totalFramesRead; // Error reading more data. + } + + pMP3->dataSize += bytesRead; + } + } while (MAL_TRUE); + } + + return totalFramesRead; +} + +static mal_uint32 mal_mp3_decoder_read(mal_mp3_decoder* pMP3, mal_decoder* pDecoder, mal_uint32 frameCount, void* pSamplesOut) +{ + (void)pDecoder; + + mal_assert(pMP3 != NULL); + mal_assert(pDecoder != NULL); + + return mal_src_read_frames_ex(&pMP3->src, frameCount, pSamplesOut, MAL_TRUE); +} + +static mal_result mal_mp3_decoder_seek_to_frame(mal_mp3_decoder* pMP3, mal_decoder* pDecoder, mal_uint64 frameIndex) +{ + mal_assert(pMP3 != NULL); + mal_assert(pDecoder != NULL); + mal_assert(pDecoder->onRead != NULL); + mal_assert(pDecoder->onSeek != NULL); + + // This is terribly inefficient because stb_vorbis does not have a good seeking solution with it's push API. Currently this just performs + // a full decode right from the start of the stream. Later on I'll need to write a layer that goes through all of the Ogg pages until we + // find the one containing the sample we need. Then we know exactly where to seek for stb_vorbis. + if (!pDecoder->onSeek(pDecoder, 0, mal_seek_origin_start)) { + return MAL_ERROR; + } + + pMP3->framesConsumed = 0; + pMP3->framesRemaining = 0; + pMP3->dataSize = 0; + + float buffer[4096]; + while (frameIndex > 0) { + mal_uint32 framesToRead = mal_countof(buffer)/pDecoder->internalChannels; + if (framesToRead > frameIndex) { + framesToRead = (mal_uint32)frameIndex; + } + + mal_uint32 framesRead = mal_mp3_decoder_read(pMP3, pDecoder, framesToRead, buffer); + if (framesRead == 0) { + return MAL_ERROR; + } + + frameIndex -= framesRead; + } + + return MAL_SUCCESS; +} + + +static mal_result mal_decoder_internal_on_seek_to_frame__mp3(mal_decoder* pDecoder, mal_uint64 frameIndex) +{ + mal_assert(pDecoder != NULL); + mal_assert(pDecoder->onRead != NULL); + mal_assert(pDecoder->onSeek != NULL); + + mal_mp3_decoder* pMP3 = (mal_mp3_decoder*)pDecoder->pInternalDecoder; + mal_assert(pMP3 != NULL); + + return mal_mp3_decoder_seek_to_frame(pMP3, pDecoder, frameIndex); +} + +static mal_result mal_decoder_internal_on_uninit__mp3(mal_decoder* pDecoder) +{ + mal_mp3_decoder* pMP3 = (mal_mp3_decoder*)pDecoder->pInternalDecoder; + mal_assert(pMP3 != NULL); + + mal_free(pMP3->pData); + mal_free(pMP3); + + return MAL_SUCCESS; +} + +static mal_uint32 mal_decoder_internal_on_read_frames__mp3(mal_dsp* pDSP, mal_uint32 frameCount, void* pSamplesOut, void* pUserData) +{ + (void)pDSP; + + mal_decoder* pDecoder = (mal_decoder*)pUserData; + mal_assert(pDecoder != NULL); + mal_assert(pDecoder->internalFormat == mal_format_f32); + mal_assert(pDecoder->onRead != NULL); + mal_assert(pDecoder->onSeek != NULL); + + mal_mp3_decoder* pMP3 = (mal_mp3_decoder*)pDecoder->pInternalDecoder; + mal_assert(pMP3 != NULL); + + return mal_mp3_decoder_read(pMP3, pDecoder, frameCount, pSamplesOut); +} + +mal_result mal_decoder_init_mp3__internal(const mal_decoder_config* pConfig, mal_decoder* pDecoder) +{ + mal_assert(pConfig != NULL); + mal_assert(pDecoder != NULL); + mal_assert(pDecoder->onRead != NULL); + mal_assert(pDecoder->onSeek != NULL); + + mal_mp3_decoder* pMP3 = (mal_mp3_decoder*)mal_malloc(sizeof(*pMP3)); + if (pMP3 == NULL) { + return MAL_OUT_OF_MEMORY; + } + + mal_zero_object(pMP3); + mp3dec_init(&pMP3->internalMP3); + + pDecoder->onSeekToFrame = mal_decoder_internal_on_seek_to_frame__mp3; + pDecoder->onUninit = mal_decoder_internal_on_uninit__mp3; + pDecoder->pInternalDecoder = pMP3; + + // minimp3 doesn't actually define the channel count nor sample rate global - it's instead per frame/packet. We therefore need + // to use some smarts to determine the most appropriate internal sample rate. These are the rules we're going to use: + // + // Sample Rates + // 1) If an output sample rate is specified in pConfig we just use that. Otherwise; + // 2) Fall back to 44100. + // + // The internal channel count is always stereo, and the internal format is always f32. + pDecoder->internalFormat = mal_format_f32; + pDecoder->internalChannels = 2; + pDecoder->internalSampleRate = (pConfig->outputSampleRate != 0) ? pConfig->outputSampleRate : 44100; + mal_get_default_device_config_channel_map(pDecoder->internalChannels, pDecoder->internalChannelMap); + + mal_src_config srcConfig; + srcConfig.sampleRateIn = 44100; + srcConfig.sampleRateOut = pDecoder->internalSampleRate; + srcConfig.formatIn = mal_format_s16; + srcConfig.formatOut = mal_format_f32; + srcConfig.channels = 2; + srcConfig.algorithm = mal_src_algorithm_linear; + srcConfig.cacheSizeInFrames = 0; + mal_result result = mal_src_init(&srcConfig, mal_mp3_decoder_read_src, pDecoder, &pMP3->src); + if (result != MAL_SUCCESS) { + mal_free(pMP3); + return result; + } + + + result = mal_decoder__init_dsp(pDecoder, pConfig, mal_decoder_internal_on_read_frames__mp3); + if (result != MAL_SUCCESS) { + mal_free(pMP3); + return result; + } + + return MAL_SUCCESS; +} +#endif + mal_result mal_decoder__preinit(mal_decoder_read_proc onRead, mal_decoder_seek_proc onSeek, void* pUserData, const mal_decoder_config* pConfig, mal_decoder* pDecoder) { mal_assert(pConfig != NULL); @@ -12049,6 +12333,22 @@ mal_result mal_decoder_init_vorbis(mal_decoder_read_proc onRead, mal_decoder_see #endif } +mal_result mal_decoder_init_mp3(mal_decoder_read_proc onRead, mal_decoder_seek_proc onSeek, void* pUserData, const mal_decoder_config* pConfig, mal_decoder* pDecoder) +{ + mal_decoder_config config = mal_decoder_config_init_copy(pConfig); + + mal_result result = mal_decoder__preinit(onRead, onSeek, pUserData, &config, pDecoder); + if (result != MAL_SUCCESS) { + return result; + } + +#ifdef MAL_HAS_MP3 + return mal_decoder_init_mp3__internal(&config, pDecoder); +#else + return MAL_NO_BACKEND; +#endif +} + mal_result mal_decoder_init(mal_decoder_read_proc onRead, mal_decoder_seek_proc onSeek, void* pUserData, const mal_decoder_config* pConfig, mal_decoder* pDecoder) { mal_decoder_config config = mal_decoder_config_init_copy(pConfig); @@ -12077,6 +12377,14 @@ mal_result mal_decoder_init(mal_decoder_read_proc onRead, mal_decoder_seek_proc } } #endif +#ifdef MAL_HAS_MP3 + if (result != MAL_SUCCESS) { + result = mal_decoder_init_mp3__internal(&config, pDecoder); + if (result != MAL_SUCCESS) { + onSeek(pDecoder, 0, mal_seek_origin_start); + } + } +#endif #ifdef MAL_HAS_VORBIS if (result != MAL_SUCCESS) { result = mal_decoder_init_vorbis__internal(&config, pDecoder); @@ -12194,6 +12502,16 @@ mal_result mal_decoder_init_memory_vorbis(const void* pData, size_t dataSize, co return mal_decoder_init_vorbis(mal_decoder__on_read_memory, mal_decoder__on_seek_memory, NULL, pConfig, pDecoder); } +mal_result mal_decoder_init_memory_mp3(const void* pData, size_t dataSize, const mal_decoder_config* pConfig, mal_decoder* pDecoder) +{ + mal_result result = mal_decoder__preinit_memory(pData, dataSize, pConfig, pDecoder); + if (result == MAL_SUCCESS) { + return MAL_SUCCESS; + } + + return mal_decoder_init_mp3(mal_decoder__on_read_memory, mal_decoder__on_seek_memory, NULL, pConfig, pDecoder); +} + #ifndef MAL_NO_STDIO #include #ifndef _MSC_VER @@ -12316,6 +12634,16 @@ mal_result mal_decoder_init_file(const char* pFilePath, const mal_decoder_config mal_decoder__on_seek_stdio(pDecoder, 0, mal_seek_origin_start); } + + // MP3 + if (mal_path_extension_equal(pFilePath, "mp3")) { + mal_result result = mal_decoder_init_mp3(mal_decoder__on_read_stdio, mal_decoder__on_seek_stdio, (void*)pFile, pConfig, pDecoder); + if (result == MAL_SUCCESS) { + return MAL_SUCCESS; + } + + mal_decoder__on_seek_stdio(pDecoder, 0, mal_seek_origin_start); + } // Trial and error. return mal_decoder_init(mal_decoder__on_read_stdio, mal_decoder__on_seek_stdio, (void*)pFile, pConfig, pDecoder);