From b17ec7b6565adfa0436ba8080d54f2da383bce91 Mon Sep 17 00:00:00 2001 From: Alec Murphy Date: Fri, 17 Dec 2021 14:34:57 -0500 Subject: [PATCH] Launch TOSamp in its own separate Task, revert dr_mp3.h to version f6dbb2ef9e which includes built-in resampler --- Lib/Debug.HC | 2 +- Lib/ELF64.HC | 2 +- Player.HC | 109 ++-- Run.HC | 2 +- dr_mp3 | Bin 76064 -> 80664 bytes dr_mp3.h | 1473 ++++++++++++++++++++++++++------------------------ 6 files changed, 838 insertions(+), 750 deletions(-) diff --git a/Lib/Debug.HC b/Lib/Debug.HC index 4957ee1..0ce16c8 100644 --- a/Lib/Debug.HC +++ b/Lib/Debug.HC @@ -15,7 +15,7 @@ U0 debug_print(U8 *fmt, ...) { return; } U8 *buf = StrPrintJoin(NULL, fmt, argc, argv); - //"[%05d] %s", debug.counter, buf; + "[%05d] %s", debug.counter, buf; Free(buf); debug.counter++; } diff --git a/Lib/ELF64.HC b/Lib/ELF64.HC index 6b77e57..c43674a 100644 --- a/Lib/ELF64.HC +++ b/Lib/ELF64.HC @@ -218,7 +218,7 @@ U0 process_elf_debug_symbols(Elf *elf) { Elf64_Sym *symtab = elf->symtab; entry_name = elf->strtab; entry_name += symtab->st_name; - while (i < 243) { + while (i < 253) { entry_bind = symtab->st_info >> 4; entry_type = symtab->st_info & 0xf; switch (entry_type) { diff --git a/Player.HC b/Player.HC index f479697..aa0617b 100644 --- a/Player.HC +++ b/Player.HC @@ -32,7 +32,11 @@ class @id3v1 { U8 year[4]; }; -class @media : @id3v1 { +class @media { + U8 title[256]; + U8 artist[256]; + U8 album[256]; + U8 year[16]; U32 channels; U32 sampleRate; U8 *source_data; @@ -44,6 +48,7 @@ class @media : @id3v1 { I64 runtime_secs; Bool paused; Bool stopped; + Bool loading; }; class @marquee_char { @@ -284,13 +289,12 @@ U0 UpdatePlayerMarqueeTextPos() { } } +@drmp3_config config; + U0 DecodeMP3Stream() { *GCC_FUN_ADDR(U64 *) = player.decoder_addr; - - @drmp3_config config; - config.channels = 0; - config.sampleRate = 0; - + config.channels = 2; + config.sampleRate = 48000; *HOLYC_ARG1(U64 *) = player.media.source_data; *HOLYC_ARG2(I32 *) = player.media.source_len; *HOLYC_ARG3(U64 *) = &config; @@ -319,18 +323,17 @@ U0 CalculateMediaRunTime() { (player.media.pcm_frames / player.media.sampleRate) % 60; } -U0 ParseID3() { - StrCpy(&player.media.title, ""); - StrCpy(&player.media.artist, ""); - StrCpy(&player.media.album, ""); - StrCpy(&player.media.year, ""); - @id3v1 *tag = player.media.source_data + player.media.source_len - 128; +U0 ParseID3(@id3v1 *tag) { + MemSet(player.media.title, NULL, 64); + MemSet(player.media.artist, NULL, 64); + MemSet(player.media.album, NULL, 64); + MemSet(player.media.year, NULL, 16); if (MemCmp(tag->header, "TAG", 3)) return; - StrCpy(&player.media.title, tag->title); - StrCpy(&player.media.artist, tag->artist); - StrCpy(&player.media.album, tag->album); - StrCpy(&player.media.year, tag->year); + MemCpy(player.media.title, tag->title, 20); + MemCpy(&player.media.artist, tag->artist, 30); + MemCpy(&player.media.album, tag->album, 30); + MemCpy(&player.media.year, tag->year, 4); } U0 PlayerTask() { @@ -346,23 +349,32 @@ U0 PlayerTask() { U0 OpenAndPlayFile(U8 *filename) { if (!FileFind(filename)) { - PopUpOk("Error: File not found."); // FIXME - this will probably freeze, - // can't do PopUp in DrawIt task + PopUpOk("Error: File not found."); return; } + + player.media.loading = TRUE; player.media.source_data = FileRead(filename, &player.media.source_len); player.media.source_type = SRC_TYPE_MP3; + // I shouldn't have to do this, but I get weird memory access/corruption + // issues if I pass the memory location to ParseID3() if one of the fields is + // over 20 characters... :/ + U8 id3_buffer[128]; + MemCpy(id3_buffer, player.media.source_data + player.media.source_len - 128, + 128); + switch (player.media.source_type) { case SRC_TYPE_MP3: - ParseID3; + WinFocus(player.windowTask); + Sleep(100); // Give WinMgr some time to remove the PopUpPickFile window + ParseID3(id3_buffer); DecodeMP3Stream; CalculateMediaRunTime; break; default: - PopUpOk( - "Error: Unsupported media type."); // FIXME - this will probably freeze, - // can't do PopUp in DrawIt task + PopUpOk("Error: Unsupported media type."); + player.media.loading = FALSE; return; break; } @@ -377,20 +389,11 @@ U0 OpenAndPlayFile(U8 *filename) { @audio_play_sfx(player.media.pcm_data, player.media.pcm_frames); Audio.output_frames[0] = 0; + player.media.loading = FALSE; player.media.stopped = FALSE; player.media.paused = FALSE; } -U0 UpdatePlayerActions() { - if (player.windowTask->dbg_task) { // Was a new track selected? If so, let's - // try to open it! - if (StrCmp(player.windowTask->dbg_task, "")) { - OpenAndPlayFile(player.windowTask->dbg_task); - } - player.windowTask->dbg_task = NULL; - } -} - U0 DrawPlayerTimeElapsed(CDC *dc) { if (player.media.stopped) return; @@ -451,24 +454,19 @@ U0 DrawPlayerPlayPauseStopped(CDC *dc) { } U0 DrawPlayer(CTask *, CDC *dc) { - GrBlot(dc, 3, 2, IMG_MAIN); - UpdatePlayerActions; - UpdatePlayerMarqueeTextPos; - DrawPlayerMarqueeText(dc, 113, 29); - DrawPlayerPlayPauseStopped(dc); - DrawPlayerTimeElapsed(dc); - DrawPlayerControlsActive(dc, 22, 91); + if (!player.media.loading) { + GrBlot(dc, 3, 2, IMG_MAIN); + UpdatePlayerMarqueeTextPos; + DrawPlayerMarqueeText(dc, 113, 29); + DrawPlayerPlayPauseStopped(dc); + DrawPlayerTimeElapsed(dc); + DrawPlayerControlsActive(dc, 22, 91); + } } U0 InitPlayer() { MemSet(&player, sizeof(@player), NULL); - player.windowTask = User; - XTalkWait( - player.windowTask, - "DocClear; while (1) { switch(Fs->user_data) { case 1: " - "Fs->dbg_task = PopUpPickFile(\"T:/Music/\"); Fs->user_data = NULL; " - "break; default: break; };" - "Sleep(1); };\n"); + player.windowTask = Fs; player.windowTask->win_width = 35; WinHorz((TEXT_COLS / 2) - (player.windowTask->win_width / 2), (TEXT_COLS / 2) - (player.windowTask->win_width / 2) + @@ -479,7 +477,6 @@ U0 InitPlayer() { (TEXT_ROWS / 2) - (player.windowTask->win_height / 2) + (player.windowTask->win_height - 1), player.windowTask); - StrCpy(player.windowTask->task_title, "...TOSamp..."); player.images = CAlloc(sizeof(CDC *) * 32); IMG_MAIN = PNGRead("T:/Images/MAIN.png"); @@ -500,4 +497,22 @@ U0 InitPlayer() { player.windowTask->draw_it = &DrawPlayer; Spawn(&PlayerTask, , "TOSamp Player Task", 1); + + DocClear; + U8 *res = NULL; + while (1) { + StrCpy(player.windowTask->task_title, "...TOSamp..."); + switch (Fs->user_data) { + case ACTION_PICK_FILE: + res = PopUpPickFile("T:/Music/"); + Fs->user_data = NULL; + if (res) + if (StrCmp(res, "")) + OpenAndPlayFile(res); + break; + default: + break; + }; + Sleep(1); + } } diff --git a/Run.HC b/Run.HC index 7ade337..18221ff 100644 --- a/Run.HC +++ b/Run.HC @@ -1 +1 @@ -#include "Load"; +User("Cd(\"T:\");\n#include \"Load\";\n"); diff --git a/dr_mp3 b/dr_mp3 index 11df20361085e29974aa4f84721d6132ee70eb9c..34d63b1953399164cbba2b0f3e2715ed861a8378 100755 GIT binary patch delta 21090 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 /* For size_t. */ -/* Sized types. */ -typedef signed char drmp3_int8; -typedef unsigned char drmp3_uint8; -typedef signed short drmp3_int16; -typedef unsigned short drmp3_uint16; -typedef signed int drmp3_int32; -typedef unsigned int drmp3_uint32; -#if defined(_MSC_VER) && !defined(__clang__) - typedef signed __int64 drmp3_int64; - typedef unsigned __int64 drmp3_uint64; -#else - #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) +/* Sized types. Prefer built-in types. Fall back to stdint. */ +#ifdef _MSC_VER + #if defined(__clang__) #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wlong-long" - #if defined(__clang__) - #pragma GCC diagnostic ignored "-Wc++11-long-long" - #endif + #pragma GCC diagnostic ignored "-Wlanguage-extension-token" + #pragma GCC diagnostic ignored "-Wlong-long" + #pragma GCC diagnostic ignored "-Wc++11-long-long" #endif - typedef signed long long drmp3_int64; - typedef unsigned long long drmp3_uint64; - #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + typedef signed __int8 drmp3_int8; + typedef unsigned __int8 drmp3_uint8; + typedef signed __int16 drmp3_int16; + typedef unsigned __int16 drmp3_uint16; + typedef signed __int32 drmp3_int32; + typedef unsigned __int32 drmp3_uint32; + typedef signed __int64 drmp3_int64; + typedef unsigned __int64 drmp3_uint64; + #if defined(__clang__) #pragma GCC diagnostic pop #endif -#endif -#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__) - typedef drmp3_uint64 drmp3_uintptr; #else - typedef drmp3_uint32 drmp3_uintptr; + #include + typedef int8_t drmp3_int8; + typedef uint8_t drmp3_uint8; + typedef int16_t drmp3_int16; + typedef uint16_t drmp3_uint16; + typedef int32_t drmp3_int32; + typedef uint32_t drmp3_uint32; + typedef int64_t drmp3_int64; + typedef uint64_t drmp3_uint64; #endif -typedef drmp3_uint8 drmp3_bool8; -typedef drmp3_uint32 drmp3_bool32; -#define DRMP3_TRUE 1 -#define DRMP3_FALSE 0 +typedef drmp3_uint8 drmp3_bool8; +typedef drmp3_uint32 drmp3_bool32; +#define DRMP3_TRUE 1 +#define DRMP3_FALSE 0 #if !defined(DRMP3_API) #if defined(DRMP3_DLL) @@ -239,17 +206,10 @@ typedef drmp3_int32 drmp3_result; #else #define DRMP3_INLINE inline __attribute__((always_inline)) #endif -#elif defined(__WATCOMC__) - #define DRMP3_INLINE __inline #else #define DRMP3_INLINE #endif - -DRMP3_API void drmp3_version(drmp3_uint32* pMajor, drmp3_uint32* pMinor, drmp3_uint32* pRevision); -DRMP3_API const char* drmp3_version_string(void); - - /* Low Level Push API ================== @@ -263,17 +223,17 @@ typedef struct { float mdct_overlap[2][9*32], qmf_state[15*2*32]; int reserv, free_format_bytes; - drmp3_uint8 header[4], reserv_buf[511]; + unsigned char header[4], reserv_buf[511]; } drmp3dec; /* Initializes a low level decoder. */ DRMP3_API void drmp3dec_init(drmp3dec *dec); /* Reads a frame from a low level decoder. */ -DRMP3_API int drmp3dec_decode_frame(drmp3dec *dec, const drmp3_uint8 *mp3, int mp3_bytes, void *pcm, drmp3dec_frame_info *info); +DRMP3_API int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes, void *pcm, drmp3dec_frame_info *info); /* Helper for converting between f32 and s16. */ -DRMP3_API void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, size_t num_samples); +DRMP3_API void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, int num_samples); @@ -281,6 +241,58 @@ DRMP3_API void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, size_t num Main API (Pull API) =================== */ +#ifndef DR_MP3_DEFAULT_CHANNELS +#define DR_MP3_DEFAULT_CHANNELS 2 +#endif +#ifndef DR_MP3_DEFAULT_SAMPLE_RATE +#define DR_MP3_DEFAULT_SAMPLE_RATE 44100 +#endif + +typedef struct drmp3_src drmp3_src; +typedef drmp3_uint64 (* drmp3_src_read_proc)(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, void* pUserData); /* Returns the number of frames that were read. */ + +typedef enum +{ + drmp3_src_algorithm_none, + drmp3_src_algorithm_linear +} drmp3_src_algorithm; + +#define DRMP3_SRC_CACHE_SIZE_IN_FRAMES 512 +typedef struct +{ + drmp3_src* pSRC; + float pCachedFrames[2 * DRMP3_SRC_CACHE_SIZE_IN_FRAMES]; + drmp3_uint32 cachedFrameCount; + drmp3_uint32 iNextFrame; +} drmp3_src_cache; + +typedef struct +{ + drmp3_uint32 sampleRateIn; + drmp3_uint32 sampleRateOut; + drmp3_uint32 channels; + drmp3_src_algorithm algorithm; + drmp3_uint32 cacheSizeInFrames; /* The number of frames to read from the client at a time. */ +} drmp3_src_config; + +struct drmp3_src +{ + drmp3_src_config config; + drmp3_src_read_proc onRead; + void* pUserData; + float bin[256]; + drmp3_src_cache cache; /* <-- For simplifying and optimizing client -> memory reading. */ + union + { + struct + { + double alpha; + drmp3_bool32 isPrevFramesLoaded : 1; + drmp3_bool32 isNextFramesLoaded : 1; + } linear; + } algo; +}; + typedef enum { drmp3_seek_origin_start, @@ -333,8 +345,8 @@ typedef struct typedef struct { - drmp3_uint32 channels; - drmp3_uint32 sampleRate; + drmp3_uint32 outputChannels; + drmp3_uint32 outputSampleRate; } drmp3_config; typedef struct @@ -354,11 +366,11 @@ typedef struct drmp3_uint8 pcmFrames[sizeof(float)*DRMP3_MAX_SAMPLES_PER_FRAME]; /* <-- Multipled by sizeof(float) to ensure there's enough room for DR_MP3_FLOAT_OUTPUT. */ drmp3_uint64 currentPCMFrame; /* The current PCM frame, globally, based on the output sample rate. Mainly used for seeking. */ drmp3_uint64 streamCursor; /* The current byte the decoder is sitting on in the raw stream. */ + drmp3_src src; drmp3_seek_point* pSeekPoints; /* NULL by default. Set with drmp3_bind_seek_table(). Memory is owned by the client. dr_mp3 will never attempt to free this pointer. */ drmp3_uint32 seekPointCount; /* The number of items in pSeekPoints. When set to 0 assumes to no seek table. Defaults to zero. */ size_t dataSize; size_t dataCapacity; - size_t dataConsumed; drmp3_uint8* pData; drmp3_bool32 atEnd : 1; struct @@ -382,7 +394,7 @@ Close the loader with drmp3_uninit(). See also: drmp3_init_file(), drmp3_init_memory(), drmp3_uninit() */ -DRMP3_API drmp3_bool32 drmp3_init(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_allocation_callbacks* pAllocationCallbacks); +DRMP3_API drmp3_bool32 drmp3_init(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_config* pConfig, const drmp3_allocation_callbacks* pAllocationCallbacks); /* Initializes an MP3 decoder from a block of memory. @@ -392,7 +404,7 @@ the lifetime of the drmp3 object. The buffer should contain the contents of the entire MP3 file. */ -DRMP3_API drmp3_bool32 drmp3_init_memory(drmp3* pMP3, const void* pData, size_t dataSize, const drmp3_allocation_callbacks* pAllocationCallbacks); +DRMP3_API drmp3_bool32 drmp3_init_memory(drmp3* pMP3, const void* pData, size_t dataSize, const drmp3_config* pConfig, const drmp3_allocation_callbacks* pAllocationCallbacks); #ifndef DR_MP3_NO_STDIO /* @@ -402,8 +414,8 @@ This holds the internal FILE object until drmp3_uninit() is called. Keep this in objects because the operating system may restrict the number of file handles an application can have open at any given time. */ -DRMP3_API drmp3_bool32 drmp3_init_file(drmp3* pMP3, const char* pFilePath, const drmp3_allocation_callbacks* pAllocationCallbacks); -DRMP3_API drmp3_bool32 drmp3_init_file_w(drmp3* pMP3, const wchar_t* pFilePath, const drmp3_allocation_callbacks* pAllocationCallbacks); +DRMP3_API drmp3_bool32 drmp3_init_file(drmp3* pMP3, const char* pFilePath, const drmp3_config* pConfig, const drmp3_allocation_callbacks* pAllocationCallbacks); +DRMP3_API drmp3_bool32 drmp3_init_file_w(drmp3* pMP3, const wchar_t* pFilePath, const drmp3_config* pConfig, const drmp3_allocation_callbacks* pAllocationCallbacks); #endif /* @@ -477,7 +489,7 @@ DRMP3_API drmp3_bool32 drmp3_bind_seek_table(drmp3* pMP3, drmp3_uint32 seekPoint /* Opens an decodes an entire MP3 stream as a single operation. -On output pConfig will receive the channel count and sample rate of the stream. +pConfig is both an input and output. On input it contains what you want. On output it contains what you got. Free the returned pointer with drmp3_free(). */ @@ -492,11 +504,6 @@ DRMP3_API float* drmp3_open_file_and_read_pcm_frames_f32(const char* filePath, d DRMP3_API drmp3_int16* drmp3_open_file_and_read_pcm_frames_s16(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); #endif -/* -Allocates a block of memory on the heap. -*/ -DRMP3_API void* drmp3_malloc(size_t sz, const drmp3_allocation_callbacks* pAllocationCallbacks); - /* Frees any memory that was allocated by a public drmp3 API. */ @@ -516,33 +523,10 @@ DRMP3_API void drmp3_free(void* p, const drmp3_allocation_callbacks* pAllocation ************************************************************************************************************************************************************ ************************************************************************************************************************************************************/ #if defined(DR_MP3_IMPLEMENTATION) || defined(DRMP3_IMPLEMENTATION) -#ifndef dr_mp3_c -#define dr_mp3_c - #include #include #include /* For INT_MAX */ -DRMP3_API void drmp3_version(drmp3_uint32* pMajor, drmp3_uint32* pMinor, drmp3_uint32* pRevision) -{ - if (pMajor) { - *pMajor = DRMP3_VERSION_MAJOR; - } - - if (pMinor) { - *pMinor = DRMP3_VERSION_MINOR; - } - - if (pRevision) { - *pRevision = DRMP3_VERSION_REVISION; - } -} - -DRMP3_API const char* drmp3_version_string(void) -{ - return DRMP3_VERSION_STRING; -} - /* Disable SIMD when compiling with TCC for now. */ #if defined(__TINYC__) #define DR_MP3_NO_SIMD @@ -590,7 +574,7 @@ DRMP3_API const char* drmp3_version_string(void) #if !defined(DR_MP3_NO_SIMD) -#if !defined(DR_MP3_ONLY_SIMD) && (defined(_M_X64) || defined(__x86_64__) || defined(__aarch64__) || defined(_M_ARM64)) +#if !defined(DR_MP3_ONLY_SIMD) && (defined(_M_X64) || defined(_M_ARM64) || defined(__x86_64__) || defined(__aarch64__)) /* x64 always have SSE2, arm64 always have neon, no need for generic code */ #define DR_MP3_ONLY_SIMD #endif @@ -640,7 +624,7 @@ static __inline__ __attribute__((always_inline)) void drmp3_cpuid(int CPUInfo[], #endif } #endif -static int drmp3_have_simd(void) +static int drmp3_have_simd() { #ifdef DR_MP3_ONLY_SIMD return 1; @@ -666,7 +650,7 @@ end: return g_have_simd - 1; #endif } -#elif defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64) +#elif defined(__ARM_NEON) || defined(__aarch64__) #include #define DRMP3_HAVE_SSE 0 #define DRMP3_HAVE_SIMD 1 @@ -681,7 +665,7 @@ end: #define DRMP3_VMUL_S(x, s) vmulq_f32(x, vmovq_n_f32(s)) #define DRMP3_VREV(x) vcombine_f32(vget_high_f32(vrev64q_f32(x)), vget_low_f32(vrev64q_f32(x))) typedef float32x4_t drmp3_f4; -static int drmp3_have_simd(void) +static int drmp3_have_simd() { /* TODO: detect neon for !DR_MP3_ONLY_SIMD */ return 1; } @@ -699,44 +683,6 @@ static int drmp3_have_simd(void) #endif -#if defined(__ARM_ARCH) && (__ARM_ARCH >= 6) && !defined(__aarch64__) && !defined(_M_ARM64) -#define DRMP3_HAVE_ARMV6 1 -static __inline__ __attribute__((always_inline)) drmp3_int32 drmp3_clip_int16_arm(drmp3_int32 a) -{ - drmp3_int32 x = 0; - __asm__ ("ssat %0, #16, %1" : "=r"(x) : "r"(a)); - return x; -} -#else -#define DRMP3_HAVE_ARMV6 0 -#endif - - -/* Standard library stuff. */ -#ifndef DRMP3_ASSERT -#include -#define DRMP3_ASSERT(expression) assert(expression) -#endif -#ifndef DRMP3_COPY_MEMORY -#define DRMP3_COPY_MEMORY(dst, src, sz) memcpy((dst), (src), (sz)) -#endif -#ifndef DRMP3_MOVE_MEMORY -#define DRMP3_MOVE_MEMORY(dst, src, sz) memmove((dst), (src), (sz)) -#endif -#ifndef DRMP3_ZERO_MEMORY -#define DRMP3_ZERO_MEMORY(p, sz) memset((p), 0, (sz)) -#endif -#define DRMP3_ZERO_OBJECT(p) DRMP3_ZERO_MEMORY((p), sizeof(*(p))) -#ifndef DRMP3_MALLOC -#define DRMP3_MALLOC(sz) malloc((sz)) -#endif -#ifndef DRMP3_REALLOC -#define DRMP3_REALLOC(p, sz) realloc((p), (sz)) -#endif -#ifndef DRMP3_FREE -#define DRMP3_FREE(p) free((p)) -#endif - typedef struct { const drmp3_uint8 *buf; @@ -1003,7 +949,7 @@ static int drmp3_L12_dequantize_granule(float *grbuf, drmp3_bs *bs, drmp3_L12_sc static void drmp3_L12_apply_scf_384(drmp3_L12_scale_info *sci, const float *scf, float *dst) { int i, k; - DRMP3_COPY_MEMORY(dst + 576 + sci->stereo_bands*18, dst + sci->stereo_bands*18, (sci->total_bands - sci->stereo_bands)*18*sizeof(float)); + memcpy(dst + 576 + sci->stereo_bands*18, dst + sci->stereo_bands*18, (sci->total_bands - sci->stereo_bands)*18*sizeof(float)); for (i = 0; i < sci->total_bands; i++, dst += 18, scf += 6) { for (k = 0; k < 12; k++) @@ -1148,14 +1094,14 @@ static void drmp3_L3_read_scalefactors(drmp3_uint8 *scf, drmp3_uint8 *ist_pos, c int cnt = scf_count[i]; if (scfsi & 8) { - DRMP3_COPY_MEMORY(scf, ist_pos, cnt); + memcpy(scf, ist_pos, cnt); } else { int bits = scf_size[i]; if (!bits) { - DRMP3_ZERO_MEMORY(scf, cnt); - DRMP3_ZERO_MEMORY(ist_pos, cnt); + memset(scf, 0, cnt); + memset(ist_pos, 0, cnt); } else { int max_scf = (scfsi < 0) ? (1 << bits) - 1 : -1; @@ -1226,16 +1172,16 @@ static void drmp3_L3_decode_scalefactors(const drmp3_uint8 *hdr, drmp3_uint8 *is int sh = 3 - scf_shift; for (i = 0; i < gr->n_short_sfb; i += 3) { - iscf[gr->n_long_sfb + i + 0] = (drmp3_uint8)(iscf[gr->n_long_sfb + i + 0] + (gr->subblock_gain[0] << sh)); - iscf[gr->n_long_sfb + i + 1] = (drmp3_uint8)(iscf[gr->n_long_sfb + i + 1] + (gr->subblock_gain[1] << sh)); - iscf[gr->n_long_sfb + i + 2] = (drmp3_uint8)(iscf[gr->n_long_sfb + i + 2] + (gr->subblock_gain[2] << sh)); + iscf[gr->n_long_sfb + i + 0] += gr->subblock_gain[0] << sh; + iscf[gr->n_long_sfb + i + 1] += gr->subblock_gain[1] << sh; + iscf[gr->n_long_sfb + i + 2] += gr->subblock_gain[2] << sh; } } else if (gr->preflag) { static const drmp3_uint8 g_preamp[10] = { 1,1,1,1,2,2,3,3,3,2 }; for (i = 0; i < 10; i++) { - iscf[11 + i] = (drmp3_uint8)(iscf[11 + i] + g_preamp[i]); + iscf[11 + i] += g_preamp[i]; } } @@ -1415,22 +1361,12 @@ static void drmp3_L3_midside_stereo(float *left, int n) int i = 0; float *right = left + 576; #if DRMP3_HAVE_SIMD - if (drmp3_have_simd()) + if (drmp3_have_simd()) for (; i < n - 3; i += 4) { - for (; i < n - 3; i += 4) - { - drmp3_f4 vl = DRMP3_VLD(left + i); - drmp3_f4 vr = DRMP3_VLD(right + i); - DRMP3_VSTORE(left + i, DRMP3_VADD(vl, vr)); - DRMP3_VSTORE(right + i, DRMP3_VSUB(vl, vr)); - } -#ifdef __GNUC__ - /* Workaround for spurious -Waggressive-loop-optimizations warning from gcc. - * For more info see: https://github.com/lieff/minimp3/issues/88 - */ - if (__builtin_constant_p(n % 4 == 0) && n % 4 == 0) - return; -#endif + drmp3_f4 vl = DRMP3_VLD(left + i); + drmp3_f4 vr = DRMP3_VLD(right + i); + DRMP3_VSTORE(left + i, DRMP3_VADD(vl, vr)); + DRMP3_VSTORE(right + i, DRMP3_VSUB(vl, vr)); } #endif for (; i < n; i++) @@ -1540,7 +1476,7 @@ static void drmp3_L3_reorder(float *grbuf, float *scratch, const drmp3_uint8 *sf *dst++ = src[2*len]; } } - DRMP3_COPY_MEMORY(grbuf, scratch, (dst - scratch)*sizeof(float)); + memcpy(grbuf, scratch, (dst - scratch)*sizeof(float)); } static void drmp3_L3_antialias(float *grbuf, int nbands) @@ -1709,8 +1645,8 @@ static void drmp3_L3_imdct_short(float *grbuf, float *overlap, int nbands) for (;nbands > 0; nbands--, overlap += 9, grbuf += 18) { float tmp[18]; - DRMP3_COPY_MEMORY(tmp, grbuf, sizeof(tmp)); - DRMP3_COPY_MEMORY(grbuf, overlap, 6*sizeof(float)); + memcpy(tmp, grbuf, sizeof(tmp)); + memcpy(grbuf, overlap, 6*sizeof(float)); drmp3_L3_imdct12(tmp, grbuf + 6, overlap + 6); drmp3_L3_imdct12(tmp + 1, grbuf + 12, overlap + 6); drmp3_L3_imdct12(tmp + 2, overlap, overlap + 6); @@ -1754,7 +1690,7 @@ static void drmp3_L3_save_reservoir(drmp3dec *h, drmp3dec_scratch *s) } if (remains > 0) { - DRMP3_MOVE_MEMORY(h->reserv_buf, s->maindata + pos, remains); + memmove(h->reserv_buf, s->maindata + pos, remains); } h->reserv = remains; } @@ -1763,8 +1699,8 @@ static int drmp3_L3_restore_reservoir(drmp3dec *h, drmp3_bs *bs, drmp3dec_scratc { int frame_bytes = (bs->limit - bs->pos)/8; int bytes_have = DRMP3_MIN(h->reserv, main_data_begin); - DRMP3_COPY_MEMORY(s->maindata, h->reserv_buf + DRMP3_MAX(0, h->reserv - main_data_begin), DRMP3_MIN(h->reserv, main_data_begin)); - DRMP3_COPY_MEMORY(s->maindata + bytes_have, bs->buf + bs->pos/8, frame_bytes); + memcpy(s->maindata, h->reserv_buf + DRMP3_MAX(0, h->reserv - main_data_begin), DRMP3_MIN(h->reserv, main_data_begin)); + memcpy(s->maindata + bytes_have, bs->buf + bs->pos/8, frame_bytes); drmp3_bs_init(&s->bs, s->maindata, bytes_have + frame_bytes); return h->reserv >= main_data_begin; } @@ -1897,7 +1833,7 @@ static void drmp3d_DCT_II(float *grbuf, int n) } else #endif #ifdef DR_MP3_ONLY_SIMD - {} /* for HAVE_SIMD=1, MINIMP3_ONLY_SIMD=1 case we do not need non-intrinsic "else" branch */ + {} #else for (; k < n; k++) { @@ -1966,17 +1902,11 @@ typedef drmp3_int16 drmp3d_sample_t; static drmp3_int16 drmp3d_scale_pcm(float sample) { drmp3_int16 s; -#if DRMP3_HAVE_ARMV6 - drmp3_int32 s32 = (drmp3_int32)(sample + .5f); - s32 -= (s32 < 0); - s = (drmp3_int16)drmp3_clip_int16_arm(s32); -#else if (sample >= 32766.5) return (drmp3_int16) 32767; if (sample <= -32767.5) return (drmp3_int16)-32768; s = (drmp3_int16)(sample + .5f); s -= (s < 0); /* away from zero, to be compliant */ -#endif - return s; + return (drmp3_int16)s; } #else typedef float drmp3d_sample_t; @@ -2130,7 +2060,7 @@ static void drmp3d_synth(float *xl, drmp3d_sample_t *dstl, int nch, float *lins) } else #endif #ifdef DR_MP3_ONLY_SIMD - {} /* for HAVE_SIMD=1, MINIMP3_ONLY_SIMD=1 case we do not need non-intrinsic "else" branch */ + {} #else for (i = 14; i >= 0; i--) { @@ -2171,7 +2101,7 @@ static void drmp3d_synth_granule(float *qmf_state, float *grbuf, int nbands, int drmp3d_DCT_II(grbuf + 576*i, nbands); } - DRMP3_COPY_MEMORY(lins, qmf_state, sizeof(float)*15*64); + memcpy(lins, qmf_state, sizeof(float)*15*64); for (i = 0; i < nbands; i += 2) { @@ -2187,7 +2117,7 @@ static void drmp3d_synth_granule(float *qmf_state, float *grbuf, int nbands, int } else #endif { - DRMP3_COPY_MEMORY(qmf_state, lins + nbands*64, sizeof(float)*15*64); + memcpy(qmf_state, lins + nbands*64, sizeof(float)*15*64); } } @@ -2248,7 +2178,7 @@ DRMP3_API void drmp3dec_init(drmp3dec *dec) dec->header[0] = 0; } -DRMP3_API int drmp3dec_decode_frame(drmp3dec *dec, const drmp3_uint8 *mp3, int mp3_bytes, void *pcm, drmp3dec_frame_info *info) +DRMP3_API int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes, void *pcm, drmp3dec_frame_info *info) { int i = 0, igr, frame_size = 0, success = 1; const drmp3_uint8 *hdr; @@ -2265,7 +2195,7 @@ DRMP3_API int drmp3dec_decode_frame(drmp3dec *dec, const drmp3_uint8 *mp3, int m } if (!frame_size) { - DRMP3_ZERO_MEMORY(dec, sizeof(drmp3dec)); + memset(dec, 0, sizeof(drmp3dec)); i = drmp3d_find_frame(mp3, mp3_bytes, &dec->free_format_bytes, &frame_size); if (!frame_size || i + frame_size > mp3_bytes) { @@ -2275,7 +2205,7 @@ DRMP3_API int drmp3dec_decode_frame(drmp3dec *dec, const drmp3_uint8 *mp3, int m } hdr = mp3 + i; - DRMP3_COPY_MEMORY(dec->header, hdr, DRMP3_HDR_SIZE); + memcpy(dec->header, hdr, DRMP3_HDR_SIZE); info->frame_bytes = i + frame_size; info->channels = DRMP3_HDR_IS_MONO(hdr) ? 1 : 2; info->hz = drmp3_hdr_sample_rate_hz(hdr); @@ -2301,7 +2231,7 @@ DRMP3_API int drmp3dec_decode_frame(drmp3dec *dec, const drmp3_uint8 *mp3, int m { for (igr = 0; igr < (DRMP3_HDR_TEST_MPEG1(hdr) ? 2 : 1); igr++, pcm = DRMP3_OFFSET_PTR(pcm, sizeof(drmp3d_sample_t)*576*info->channels)) { - DRMP3_ZERO_MEMORY(scratch.grbuf[0], 576*2*sizeof(float)); + memset(scratch.grbuf[0], 0, 576*2*sizeof(float)); drmp3_L3_decode(dec, &scratch, scratch.gr_info + igr*info->channels, info->channels); drmp3d_synth_granule(dec->qmf_state, scratch.grbuf[0], 18, info->channels, (drmp3d_sample_t*)pcm, scratch.syn[0]); } @@ -2320,7 +2250,7 @@ DRMP3_API int drmp3dec_decode_frame(drmp3dec *dec, const drmp3_uint8 *mp3, int m drmp3_L12_read_scale_info(hdr, bs_frame, sci); - DRMP3_ZERO_MEMORY(scratch.grbuf[0], 576*2*sizeof(float)); + memset(scratch.grbuf[0], 0, 576*2*sizeof(float)); for (i = 0, igr = 0; igr < 3; igr++) { if (12 == (i += drmp3_L12_dequantize_granule(scratch.grbuf[0] + i, bs_frame, sci, info->layer | 1))) @@ -2328,7 +2258,7 @@ DRMP3_API int drmp3dec_decode_frame(drmp3dec *dec, const drmp3_uint8 *mp3, int m i = 0; drmp3_L12_apply_scf_384(sci, sci->scf + igr, scratch.grbuf[0]); drmp3d_synth_granule(dec->qmf_state, scratch.grbuf[0], 12, info->channels, (drmp3d_sample_t*)pcm, scratch.syn[0]); - DRMP3_ZERO_MEMORY(scratch.grbuf[0], 576*2*sizeof(float)); + memset(scratch.grbuf[0], 0, 576*2*sizeof(float)); pcm = DRMP3_OFFSET_PTR(pcm, sizeof(drmp3d_sample_t)*384*info->channels); } if (bs_frame->pos > bs_frame->limit) @@ -2343,11 +2273,11 @@ DRMP3_API int drmp3dec_decode_frame(drmp3dec *dec, const drmp3_uint8 *mp3, int m return success*drmp3_hdr_frame_samples(dec->header); } -DRMP3_API void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, size_t num_samples) +DRMP3_API void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, int num_samples) { - size_t i = 0; + int i = 0; #if DRMP3_HAVE_SIMD - size_t aligned_count = num_samples & ~7; + int aligned_count = num_samples & ~7; for(; i < aligned_count; i+=8) { drmp3_f4 scale = DRMP3_VSET(32768.0f); @@ -2406,7 +2336,6 @@ DRMP3_API void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, size_t num Main Public API ************************************************************************************************************************************************************/ -#include /* For sin() and exp(). */ #if defined(SIZE_MAX) #define DRMP3_SIZE_MAX SIZE_MAX @@ -2423,70 +2352,46 @@ DRMP3_API void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, size_t num #define DRMP3_SEEK_LEADING_MP3_FRAMES 2 #endif -#define DRMP3_MIN_DATA_CHUNK_SIZE 16384 -/* The size in bytes of each chunk of data to read from the MP3 stream. minimp3 recommends at least 16K, but in an attempt to reduce data movement I'm making this slightly larger. */ -#ifndef DRMP3_DATA_CHUNK_SIZE -#define DRMP3_DATA_CHUNK_SIZE DRMP3_MIN_DATA_CHUNK_SIZE*4 +/* Standard library stuff. */ +#ifndef DRMP3_ASSERT +#include +#define DRMP3_ASSERT(expression) assert(expression) +#endif +#ifndef DRMP3_COPY_MEMORY +#define DRMP3_COPY_MEMORY(dst, src, sz) memcpy((dst), (src), (sz)) +#endif +#ifndef DRMP3_ZERO_MEMORY +#define DRMP3_ZERO_MEMORY(p, sz) memset((p), 0, (sz)) +#endif +#define DRMP3_ZERO_OBJECT(p) DRMP3_ZERO_MEMORY((p), sizeof(*(p))) +#ifndef DRMP3_MALLOC +#define DRMP3_MALLOC(sz) malloc((sz)) +#endif +#ifndef DRMP3_REALLOC +#define DRMP3_REALLOC(p, sz) realloc((p), (sz)) +#endif +#ifndef DRMP3_FREE +#define DRMP3_FREE(p) free((p)) #endif +#define drmp3_countof(x) (sizeof(x) / sizeof(x[0])) +#define drmp3_max(x, y) (((x) > (y)) ? (x) : (y)) +#define drmp3_min(x, y) (((x) < (y)) ? (x) : (y)) -#define DRMP3_COUNTOF(x) (sizeof(x) / sizeof(x[0])) -#define DRMP3_CLAMP(x, lo, hi) (DRMP3_MAX(lo, DRMP3_MIN(x, hi))) - -#ifndef DRMP3_PI_D -#define DRMP3_PI_D 3.14159265358979323846264 -#endif - -#define DRMP3_DEFAULT_RESAMPLER_LPF_ORDER 2 +#define DRMP3_DATA_CHUNK_SIZE 16384 /* The size in bytes of each chunk of data to read from the MP3 stream. minimp3 recommends 16K. */ static DRMP3_INLINE float drmp3_mix_f32(float x, float y, float a) { return x*(1-a) + y*a; } -static DRMP3_INLINE float drmp3_mix_f32_fast(float x, float y, float a) -{ - float r0 = (y - x); - float r1 = r0*a; - return x + r1; - /*return x + (y - x)*a;*/ -} - -/* -Greatest common factor using Euclid's algorithm iteratively. -*/ -static DRMP3_INLINE drmp3_uint32 drmp3_gcf_u32(drmp3_uint32 a, drmp3_uint32 b) +static void drmp3_blend_f32(float* pOut, float* pInA, float* pInB, float factor, drmp3_uint32 channels) { - for (;;) { - if (b == 0) { - break; - } else { - drmp3_uint32 t = a; - a = b; - b = t % a; - } + drmp3_uint32 i; + for (i = 0; i < channels; ++i) { + pOut[i] = drmp3_mix_f32(pInA[i], pInB[i], factor); } - - return a; -} - - -static DRMP3_INLINE double drmp3_sin(double x) -{ - /* TODO: Implement custom sin(x). */ - return sin(x); -} - -static DRMP3_INLINE double drmp3_exp(double x) -{ - /* TODO: Implement custom exp(x). */ - return exp(x); -} - -static DRMP3_INLINE double drmp3_cos(double x) -{ - return drmp3_sin((DRMP3_PI_D*0.5) - x); } @@ -2509,6 +2414,7 @@ static void drmp3__free_default(void* p, void* pUserData) } +#if 0 /* Unused, but leaving here in case I need to add it again later. */ static void* drmp3__malloc_from_callbacks(size_t sz, const drmp3_allocation_callbacks* pAllocationCallbacks) { if (pAllocationCallbacks == NULL) { @@ -2526,6 +2432,7 @@ static void* drmp3__malloc_from_callbacks(size_t sz, const drmp3_allocation_call return NULL; } +#endif static void* drmp3__realloc_from_callbacks(void* p, size_t szNew, size_t szOld, const drmp3_allocation_callbacks* pAllocationCallbacks) { @@ -2586,6 +2493,259 @@ static drmp3_allocation_callbacks drmp3_copy_allocation_callbacks_or_defaults(co } +void drmp3_src_cache_init(drmp3_src* pSRC, drmp3_src_cache* pCache) +{ + DRMP3_ASSERT(pSRC != NULL); + DRMP3_ASSERT(pCache != NULL); + + pCache->pSRC = pSRC; + pCache->cachedFrameCount = 0; + pCache->iNextFrame = 0; +} + +drmp3_uint64 drmp3_src_cache_read_frames(drmp3_src_cache* pCache, drmp3_uint64 frameCount, float* pFramesOut) +{ + drmp3_uint32 channels; + drmp3_uint64 totalFramesRead = 0; + + DRMP3_ASSERT(pCache != NULL); + DRMP3_ASSERT(pCache->pSRC != NULL); + DRMP3_ASSERT(pCache->pSRC->onRead != NULL); + DRMP3_ASSERT(frameCount > 0); + DRMP3_ASSERT(pFramesOut != NULL); + + channels = pCache->pSRC->config.channels; + + while (frameCount > 0) { + /* If there's anything in memory go ahead and copy that over first. */ + drmp3_uint32 framesToReadFromClient; + drmp3_uint64 framesRemainingInMemory = pCache->cachedFrameCount - pCache->iNextFrame; + drmp3_uint64 framesToReadFromMemory = frameCount; + if (framesToReadFromMemory > framesRemainingInMemory) { + framesToReadFromMemory = framesRemainingInMemory; + } + + DRMP3_COPY_MEMORY(pFramesOut, pCache->pCachedFrames + pCache->iNextFrame*channels, (drmp3_uint32)(framesToReadFromMemory * channels * sizeof(float))); + pCache->iNextFrame += (drmp3_uint32)framesToReadFromMemory; + + totalFramesRead += framesToReadFromMemory; + frameCount -= framesToReadFromMemory; + if (frameCount == 0) { + break; + } + + + /* At this point there are still more frames to read from the client, so we'll need to reload the cache with fresh data. */ + DRMP3_ASSERT(frameCount > 0); + pFramesOut += framesToReadFromMemory * channels; + + pCache->iNextFrame = 0; + pCache->cachedFrameCount = 0; + + framesToReadFromClient = drmp3_countof(pCache->pCachedFrames) / pCache->pSRC->config.channels; + if (framesToReadFromClient > pCache->pSRC->config.cacheSizeInFrames) { + framesToReadFromClient = pCache->pSRC->config.cacheSizeInFrames; + } + + pCache->cachedFrameCount = (drmp3_uint32)pCache->pSRC->onRead(pCache->pSRC, framesToReadFromClient, pCache->pCachedFrames, pCache->pSRC->pUserData); + + + /* Get out of this loop if nothing was able to be retrieved. */ + if (pCache->cachedFrameCount == 0) { + break; + } + } + + return totalFramesRead; +} + + +drmp3_uint64 drmp3_src_read_frames_passthrough(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, drmp3_bool32 flush); +drmp3_uint64 drmp3_src_read_frames_linear(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, drmp3_bool32 flush); + +drmp3_bool32 drmp3_src_init(const drmp3_src_config* pConfig, drmp3_src_read_proc onRead, void* pUserData, drmp3_src* pSRC) +{ + if (pSRC == NULL) { + return DRMP3_FALSE; + } + + DRMP3_ZERO_OBJECT(pSRC); + + if (pConfig == NULL || onRead == NULL) { + return DRMP3_FALSE; + } + + if (pConfig->channels == 0 || pConfig->channels > 2) { + return DRMP3_FALSE; + } + + pSRC->config = *pConfig; + pSRC->onRead = onRead; + pSRC->pUserData = pUserData; + + if (pSRC->config.cacheSizeInFrames > DRMP3_SRC_CACHE_SIZE_IN_FRAMES || pSRC->config.cacheSizeInFrames == 0) { + pSRC->config.cacheSizeInFrames = DRMP3_SRC_CACHE_SIZE_IN_FRAMES; + } + + drmp3_src_cache_init(pSRC, &pSRC->cache); + return DRMP3_TRUE; +} + +drmp3_bool32 drmp3_src_set_input_sample_rate(drmp3_src* pSRC, drmp3_uint32 sampleRateIn) +{ + if (pSRC == NULL) { + return DRMP3_FALSE; + } + + /* Must have a sample rate of > 0. */ + if (sampleRateIn == 0) { + return DRMP3_FALSE; + } + + pSRC->config.sampleRateIn = sampleRateIn; + return DRMP3_TRUE; +} + +drmp3_bool32 drmp3_src_set_output_sample_rate(drmp3_src* pSRC, drmp3_uint32 sampleRateOut) +{ + if (pSRC == NULL) { + return DRMP3_FALSE; + } + + /* Must have a sample rate of > 0. */ + if (sampleRateOut == 0) { + return DRMP3_FALSE; + } + + pSRC->config.sampleRateOut = sampleRateOut; + return DRMP3_TRUE; +} + +drmp3_uint64 drmp3_src_read_frames_ex(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, drmp3_bool32 flush) +{ + drmp3_src_algorithm algorithm; + + if (pSRC == NULL || frameCount == 0 || pFramesOut == NULL) { + return 0; + } + + algorithm = pSRC->config.algorithm; + + /* Always use passthrough if the sample rates are the same. */ + if (pSRC->config.sampleRateIn == pSRC->config.sampleRateOut) { + algorithm = drmp3_src_algorithm_none; + } + + /* Could just use a function pointer instead of a switch for this... */ + switch (algorithm) + { + case drmp3_src_algorithm_none: return drmp3_src_read_frames_passthrough(pSRC, frameCount, pFramesOut, flush); + case drmp3_src_algorithm_linear: return drmp3_src_read_frames_linear(pSRC, frameCount, pFramesOut, flush); + default: return 0; + } +} + +drmp3_uint64 drmp3_src_read_frames(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut) +{ + return drmp3_src_read_frames_ex(pSRC, frameCount, pFramesOut, DRMP3_FALSE); +} + +drmp3_uint64 drmp3_src_read_frames_passthrough(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, drmp3_bool32 flush) +{ + DRMP3_ASSERT(pSRC != NULL); + DRMP3_ASSERT(frameCount > 0); + DRMP3_ASSERT(pFramesOut != NULL); + + (void)flush; /* Passthrough need not care about flushing. */ + return pSRC->onRead(pSRC, frameCount, pFramesOut, pSRC->pUserData); +} + +drmp3_uint64 drmp3_src_read_frames_linear(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, drmp3_bool32 flush) +{ + double factor; + drmp3_uint64 totalFramesRead; + + DRMP3_ASSERT(pSRC != NULL); + DRMP3_ASSERT(frameCount > 0); + DRMP3_ASSERT(pFramesOut != NULL); + + /* For linear SRC, the bin is only 2 frames: 1 prior, 1 future. */ + + /* Load the bin if necessary. */ + if (!pSRC->algo.linear.isPrevFramesLoaded) { + drmp3_uint64 framesRead = drmp3_src_cache_read_frames(&pSRC->cache, 1, pSRC->bin); + if (framesRead == 0) { + return 0; + } + pSRC->algo.linear.isPrevFramesLoaded = DRMP3_TRUE; + } + if (!pSRC->algo.linear.isNextFramesLoaded) { + drmp3_uint64 framesRead = drmp3_src_cache_read_frames(&pSRC->cache, 1, pSRC->bin + pSRC->config.channels); + if (framesRead == 0) { + return 0; + } + pSRC->algo.linear.isNextFramesLoaded = DRMP3_TRUE; + } + + factor = (double)pSRC->config.sampleRateIn / pSRC->config.sampleRateOut; + + totalFramesRead = 0; + while (frameCount > 0) { + drmp3_uint32 i; + drmp3_uint32 framesToReadFromClient; + + /* The bin is where the previous and next frames are located. */ + float* pPrevFrame = pSRC->bin; + float* pNextFrame = pSRC->bin + pSRC->config.channels; + + drmp3_blend_f32((float*)pFramesOut, pPrevFrame, pNextFrame, (float)pSRC->algo.linear.alpha, pSRC->config.channels); + + pSRC->algo.linear.alpha += factor; + + /* The new alpha value is how we determine whether or not we need to read fresh frames. */ + framesToReadFromClient = (drmp3_uint32)pSRC->algo.linear.alpha; + pSRC->algo.linear.alpha = pSRC->algo.linear.alpha - framesToReadFromClient; + + for (i = 0; i < framesToReadFromClient; ++i) { + drmp3_uint64 framesRead; + drmp3_uint32 j; + + for (j = 0; j < pSRC->config.channels; ++j) { + pPrevFrame[j] = pNextFrame[j]; + } + + framesRead = drmp3_src_cache_read_frames(&pSRC->cache, 1, pNextFrame); + if (framesRead == 0) { + drmp3_uint32 k; + for (k = 0; k < pSRC->config.channels; ++k) { + pNextFrame[k] = 0; + } + + if (pSRC->algo.linear.isNextFramesLoaded) { + pSRC->algo.linear.isNextFramesLoaded = DRMP3_FALSE; + } else { + if (flush) { + pSRC->algo.linear.isPrevFramesLoaded = DRMP3_FALSE; + } + } + + break; + } + } + + pFramesOut = (drmp3_uint8*)pFramesOut + (1 * pSRC->config.channels * sizeof(float)); + frameCount -= 1; + totalFramesRead += 1; + + /* If there's no frames available we need to get out of this loop. */ + if (!pSRC->algo.linear.isNextFramesLoaded && (!flush || !pSRC->algo.linear.isPrevFramesLoaded)) { + break; + } + } + + return totalFramesRead; +} + static size_t drmp3__on_read(drmp3* pMP3, void* pBufferOut, size_t bytesToRead) { @@ -2641,8 +2801,112 @@ static drmp3_bool32 drmp3__on_seek_64(drmp3* pMP3, drmp3_uint64 offset, drmp3_se return DRMP3_TRUE; } +static drmp3_uint32 drmp3_decode_next_frame_ex(drmp3* pMP3, drmp3d_sample_t* pPCMFrames, drmp3_bool32 discard); +static drmp3_uint32 drmp3_decode_next_frame(drmp3* pMP3); -static drmp3_uint32 drmp3_decode_next_frame_ex__callbacks(drmp3* pMP3, drmp3d_sample_t* pPCMFrames) +static drmp3_uint64 drmp3_read_src(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, void* pUserData) +{ + drmp3* pMP3 = (drmp3*)pUserData; + float* pFramesOutF = (float*)pFramesOut; + drmp3_uint64 totalFramesRead = 0; + + DRMP3_ASSERT(pMP3 != NULL); + DRMP3_ASSERT(pMP3->onRead != NULL); + + while (frameCount > 0) { + /* Read from the in-memory buffer first. */ + while (pMP3->pcmFramesRemainingInMP3Frame > 0 && frameCount > 0) { + drmp3d_sample_t* frames = (drmp3d_sample_t*)pMP3->pcmFrames; +#ifndef DR_MP3_FLOAT_OUTPUT + if (pMP3->mp3FrameChannels == 1) { + if (pMP3->channels == 1) { + /* Mono -> Mono. */ + pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame] / 32768.0f; + } else { + /* Mono -> Stereo. */ + pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame] / 32768.0f; + pFramesOutF[1] = frames[pMP3->pcmFramesConsumedInMP3Frame] / 32768.0f; + } + } else { + if (pMP3->channels == 1) { + /* Stereo -> Mono */ + float sample = 0; + sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0] / 32768.0f; + sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1] / 32768.0f; + pFramesOutF[0] = sample * 0.5f; + } else { + /* Stereo -> Stereo */ + pFramesOutF[0] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0] / 32768.0f; + pFramesOutF[1] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1] / 32768.0f; + } + } +#else + if (pMP3->mp3FrameChannels == 1) { + if (pMP3->channels == 1) { + /* Mono -> Mono. */ + pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame]; + } else { + /* Mono -> Stereo. */ + pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame]; + pFramesOutF[1] = frames[pMP3->pcmFramesConsumedInMP3Frame]; + } + } else { + if (pMP3->channels == 1) { + /* Stereo -> Mono */ + float sample = 0; + sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0]; + sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1]; + pFramesOutF[0] = sample * 0.5f; + } else { + /* Stereo -> Stereo */ + pFramesOutF[0] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0]; + pFramesOutF[1] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1]; + } + } +#endif + + pMP3->pcmFramesConsumedInMP3Frame += 1; + pMP3->pcmFramesRemainingInMP3Frame -= 1; + totalFramesRead += 1; + frameCount -= 1; + pFramesOutF += pSRC->config.channels; + } + + if (frameCount == 0) { + break; + } + + DRMP3_ASSERT(pMP3->pcmFramesRemainingInMP3Frame == 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. + */ + if (drmp3_decode_next_frame(pMP3) == 0) { + break; + } + } + + return totalFramesRead; +} + +static drmp3_bool32 drmp3_init_src(drmp3* pMP3) +{ + drmp3_src_config srcConfig; + DRMP3_ZERO_OBJECT(&srcConfig); + srcConfig.sampleRateIn = DR_MP3_DEFAULT_SAMPLE_RATE; + srcConfig.sampleRateOut = pMP3->sampleRate; + srcConfig.channels = pMP3->channels; + srcConfig.algorithm = drmp3_src_algorithm_linear; + if (!drmp3_src_init(&srcConfig, drmp3_read_src, pMP3, &pMP3->src)) { + drmp3_uninit(pMP3); + return DRMP3_FALSE; + } + + return DRMP3_TRUE; +} + +static drmp3_uint32 drmp3_decode_next_frame_ex(drmp3* pMP3, drmp3d_sample_t* pPCMFrames, drmp3_bool32 discard) { drmp3_uint32 pcmFramesRead = 0; @@ -2653,20 +2917,14 @@ static drmp3_uint32 drmp3_decode_next_frame_ex__callbacks(drmp3* pMP3, drmp3d_sa return 0; } - for (;;) { + do { drmp3dec_frame_info info; + size_t leftoverDataSize; - /* minimp3 recommends doing data submission in chunks of at least 16K. If we don't have at least 16K bytes available, get more. */ - if (pMP3->dataSize < DRMP3_MIN_DATA_CHUNK_SIZE) { + /* minimp3 recommends doing data submission in 16K chunks. If we don't have at least 16K bytes available, get more. */ + if (pMP3->dataSize < DRMP3_DATA_CHUNK_SIZE) { size_t bytesRead; - /* First we need to move the data down. */ - if (pMP3->pData != NULL) { - DRMP3_MOVE_MEMORY(pMP3->pData, pMP3->pData + pMP3->dataConsumed, pMP3->dataSize); - } - - pMP3->dataConsumed = 0; - if (pMP3->dataCapacity < DRMP3_DATA_CHUNK_SIZE) { drmp3_uint8* pNewData; size_t newDataCap; @@ -2698,33 +2956,43 @@ static drmp3_uint32 drmp3_decode_next_frame_ex__callbacks(drmp3* pMP3, drmp3d_sa return 0; /* File too big. */ } - DRMP3_ASSERT(pMP3->pData != NULL); - DRMP3_ASSERT(pMP3->dataCapacity > 0); - - pcmFramesRead = drmp3dec_decode_frame(&pMP3->decoder, pMP3->pData + pMP3->dataConsumed, (int)pMP3->dataSize, pPCMFrames, &info); /* <-- Safe size_t -> int conversion thanks to the check above. */ - + pcmFramesRead = drmp3dec_decode_frame(&pMP3->decoder, pMP3->pData, (int)pMP3->dataSize, pPCMFrames, &info); /* <-- Safe size_t -> int conversion thanks to the check above. */ + /* Consume the data. */ + leftoverDataSize = (pMP3->dataSize - (size_t)info.frame_bytes); if (info.frame_bytes > 0) { - pMP3->dataConsumed += (size_t)info.frame_bytes; - pMP3->dataSize -= (size_t)info.frame_bytes; + memmove(pMP3->pData, pMP3->pData + info.frame_bytes, leftoverDataSize); + pMP3->dataSize = leftoverDataSize; } - /* pcmFramesRead will be equal to 0 if decoding failed. If it is zero and info.frame_bytes > 0 then we have successfully decoded the frame. */ - if (pcmFramesRead > 0) { + /* + pcmFramesRead will be equal to 0 if decoding failed. If it is zero and info.frame_bytes > 0 then we have successfully + decoded the frame. A special case is if we are wanting to discard the frame, in which case we return successfully. + */ + if (pcmFramesRead > 0 || (info.frame_bytes > 0 && discard)) { pcmFramesRead = drmp3_hdr_frame_samples(pMP3->decoder.header); pMP3->pcmFramesConsumedInMP3Frame = 0; pMP3->pcmFramesRemainingInMP3Frame = pcmFramesRead; pMP3->mp3FrameChannels = info.channels; pMP3->mp3FrameSampleRate = info.hz; + + /* We need to initialize the resampler if we don't yet have the channel count or sample rate. */ + if (pMP3->channels == 0 || pMP3->sampleRate == 0) { + if (pMP3->channels == 0) { + pMP3->channels = info.channels; + } + if (pMP3->sampleRate == 0) { + pMP3->sampleRate = info.hz; + } + drmp3_init_src(pMP3); + } + + drmp3_src_set_input_sample_rate(&pMP3->src, pMP3->mp3FrameSampleRate); break; } else if (info.frame_bytes == 0) { - /* Need more data. minimp3 recommends doing data submission in 16K chunks. */ size_t bytesRead; - /* First we need to move the data down. */ - DRMP3_MOVE_MEMORY(pMP3->pData, pMP3->pData + pMP3->dataConsumed, pMP3->dataSize); - pMP3->dataConsumed = 0; - + /* Need more data. minimp3 recommends doing data submission in 16K chunks. */ if (pMP3->dataCapacity == pMP3->dataSize) { /* No room. Expand. */ drmp3_uint8* pNewData; @@ -2750,60 +3018,15 @@ static drmp3_uint32 drmp3_decode_next_frame_ex__callbacks(drmp3* pMP3, drmp3d_sa pMP3->dataSize += bytesRead; } - }; + } while (DRMP3_TRUE); return pcmFramesRead; } -static drmp3_uint32 drmp3_decode_next_frame_ex__memory(drmp3* pMP3, drmp3d_sample_t* pPCMFrames) -{ - drmp3_uint32 pcmFramesRead = 0; - drmp3dec_frame_info info; - - DRMP3_ASSERT(pMP3 != NULL); - DRMP3_ASSERT(pMP3->memory.pData != NULL); - - if (pMP3->atEnd) { - return 0; - } - - for (;;) { - pcmFramesRead = drmp3dec_decode_frame(&pMP3->decoder, pMP3->memory.pData + pMP3->memory.currentReadPos, (int)(pMP3->memory.dataSize - pMP3->memory.currentReadPos), pPCMFrames, &info); - if (pcmFramesRead > 0) { - pcmFramesRead = drmp3_hdr_frame_samples(pMP3->decoder.header); - pMP3->pcmFramesConsumedInMP3Frame = 0; - pMP3->pcmFramesRemainingInMP3Frame = pcmFramesRead; - pMP3->mp3FrameChannels = info.channels; - pMP3->mp3FrameSampleRate = info.hz; - break; - } else if (info.frame_bytes > 0) { - /* No frames were read, but it looks like we skipped past one. Read the next MP3 frame. */ - pMP3->memory.currentReadPos += (size_t)info.frame_bytes; - } else { - /* Nothing at all was read. Abort. */ - break; - } - } - - /* Consume the data. */ - pMP3->memory.currentReadPos += (size_t)info.frame_bytes; - - return pcmFramesRead; -} - -static drmp3_uint32 drmp3_decode_next_frame_ex(drmp3* pMP3, drmp3d_sample_t* pPCMFrames) -{ - if (pMP3->memory.pData != NULL && pMP3->memory.dataSize > 0) { - return drmp3_decode_next_frame_ex__memory(pMP3, pPCMFrames); - } else { - return drmp3_decode_next_frame_ex__callbacks(pMP3, pPCMFrames); - } -} - static drmp3_uint32 drmp3_decode_next_frame(drmp3* pMP3) { DRMP3_ASSERT(pMP3 != NULL); - return drmp3_decode_next_frame_ex(pMP3, (drmp3d_sample_t*)pMP3->pcmFrames); + return drmp3_decode_next_frame_ex(pMP3, (drmp3d_sample_t*)pMP3->pcmFrames, DRMP3_FALSE); } #if 0 @@ -2827,14 +3050,32 @@ static drmp3_uint32 drmp3_seek_next_frame(drmp3* pMP3) } #endif -static drmp3_bool32 drmp3_init_internal(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_allocation_callbacks* pAllocationCallbacks) +static drmp3_bool32 drmp3_init_internal(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_config* pConfig, const drmp3_allocation_callbacks* pAllocationCallbacks) { + drmp3_config config; + DRMP3_ASSERT(pMP3 != NULL); DRMP3_ASSERT(onRead != NULL); /* This function assumes the output object has already been reset to 0. Do not do that here, otherwise things will break. */ drmp3dec_init(&pMP3->decoder); + /* The config can be null in which case we use defaults. */ + if (pConfig != NULL) { + config = *pConfig; + } else { + DRMP3_ZERO_OBJECT(&config); + } + + pMP3->channels = config.outputChannels; + + /* Cannot have more than 2 channels. */ + if (pMP3->channels > 2) { + pMP3->channels = 2; + } + + pMP3->sampleRate = config.outputSampleRate; + pMP3->onRead = onRead; pMP3->onSeek = onSeek; pMP3->pUserData = pUserData; @@ -2844,26 +3085,31 @@ static drmp3_bool32 drmp3_init_internal(drmp3* pMP3, drmp3_read_proc onRead, drm return DRMP3_FALSE; /* Invalid allocation callbacks. */ } + /* + We need a sample rate converter for converting the sample rate from the MP3 frames to the requested output sample rate. Note that if + we don't yet know the channel count or sample rate we defer this until the first frame is read. + */ + if (pMP3->channels != 0 && pMP3->sampleRate != 0) { + drmp3_init_src(pMP3); + } + /* Decode the first frame to confirm that it is indeed a valid MP3 stream. */ - if (drmp3_decode_next_frame(pMP3) == 0) { - drmp3__free_from_callbacks(pMP3->pData, &pMP3->allocationCallbacks); /* The call above may have allocated memory. Need to make sure it's freed before aborting. */ + if (!drmp3_decode_next_frame(pMP3)) { + drmp3_uninit(pMP3); return DRMP3_FALSE; /* Not a valid MP3 stream. */ } - pMP3->channels = pMP3->mp3FrameChannels; - pMP3->sampleRate = pMP3->mp3FrameSampleRate; - return DRMP3_TRUE; } -DRMP3_API drmp3_bool32 drmp3_init(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_allocation_callbacks* pAllocationCallbacks) +DRMP3_API drmp3_bool32 drmp3_init(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_config* pConfig, const drmp3_allocation_callbacks* pAllocationCallbacks) { if (pMP3 == NULL || onRead == NULL) { return DRMP3_FALSE; } DRMP3_ZERO_OBJECT(pMP3); - return drmp3_init_internal(pMP3, onRead, onSeek, pUserData, pAllocationCallbacks); + return drmp3_init_internal(pMP3, onRead, onSeek, pUserData, pConfig, pAllocationCallbacks); } @@ -2918,7 +3164,7 @@ static drmp3_bool32 drmp3__on_seek_memory(void* pUserData, int byteOffset, drmp3 return DRMP3_TRUE; } -DRMP3_API drmp3_bool32 drmp3_init_memory(drmp3* pMP3, const void* pData, size_t dataSize, const drmp3_allocation_callbacks* pAllocationCallbacks) +DRMP3_API drmp3_bool32 drmp3_init_memory(drmp3* pMP3, const void* pData, size_t dataSize, const drmp3_config* pConfig, const drmp3_allocation_callbacks* pAllocationCallbacks) { if (pMP3 == NULL) { return DRMP3_FALSE; @@ -2934,13 +3180,12 @@ DRMP3_API drmp3_bool32 drmp3_init_memory(drmp3* pMP3, const void* pData, size_t pMP3->memory.dataSize = dataSize; pMP3->memory.currentReadPos = 0; - return drmp3_init_internal(pMP3, drmp3__on_read_memory, drmp3__on_seek_memory, pMP3, pAllocationCallbacks); + return drmp3_init_internal(pMP3, drmp3__on_read_memory, drmp3__on_seek_memory, pMP3, pConfig, pAllocationCallbacks); } #ifndef DR_MP3_NO_STDIO #include -#include /* For wcslen(), wcsrtombs() */ /* drmp3_result_from_errno() is only used inside DR_MP3_NO_STDIO for now. Move this out if it's ever used elsewhere. */ #include @@ -3348,7 +3593,7 @@ static drmp3_result drmp3_result_from_errno(int e) static drmp3_result drmp3_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode) { -#if defined(_MSC_VER) && _MSC_VER >= 1400 +#if _MSC_VER && _MSC_VER >= 1400 errno_t err; #endif @@ -3360,7 +3605,7 @@ static drmp3_result drmp3_fopen(FILE** ppFile, const char* pFilePath, const char return DRMP3_INVALID_ARGS; } -#if defined(_MSC_VER) && _MSC_VER >= 1400 +#if _MSC_VER && _MSC_VER >= 1400 err = fopen_s(ppFile, pFilePath, pOpenMode); if (err != 0) { return drmp3_result_from_errno(err); @@ -3395,13 +3640,12 @@ _wfopen() isn't always available in all compilation environments. * MSVC seems to support it universally as far back as VC6 from what I can tell (haven't checked further back). * MinGW-64 (both 32- and 64-bit) seems to support it. * MinGW wraps it in !defined(__STRICT_ANSI__). - * OpenWatcom wraps it in !defined(_NO_EXT_KEYS). This can be reviewed as compatibility issues arise. The preference is to use _wfopen_s() and _wfopen() as opposed to the wcsrtombs() fallback, so if you notice your compiler not detecting this properly I'm happy to look at adding support. */ #if defined(_WIN32) - #if defined(_MSC_VER) || defined(__MINGW64__) || (!defined(__STRICT_ANSI__) && !defined(_NO_EXT_KEYS)) + #if defined(_MSC_VER) || defined(__MINGW64__) || !defined(__STRICT_ANSI__) #define DRMP3_HAS_WFOPEN #endif #endif @@ -3500,40 +3744,24 @@ static drmp3_bool32 drmp3__on_seek_stdio(void* pUserData, int offset, drmp3_seek return fseek((FILE*)pUserData, offset, (origin == drmp3_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; } -DRMP3_API drmp3_bool32 drmp3_init_file(drmp3* pMP3, const char* pFilePath, const drmp3_allocation_callbacks* pAllocationCallbacks) +DRMP3_API drmp3_bool32 drmp3_init_file(drmp3* pMP3, const char* pFilePath, const drmp3_config* pConfig, const drmp3_allocation_callbacks* pAllocationCallbacks) { - drmp3_bool32 result; FILE* pFile; - if (drmp3_fopen(&pFile, pFilePath, "rb") != DRMP3_SUCCESS) { return DRMP3_FALSE; } - result = drmp3_init(pMP3, drmp3__on_read_stdio, drmp3__on_seek_stdio, (void*)pFile, pAllocationCallbacks); - if (result != DRMP3_TRUE) { - fclose(pFile); - return result; - } - - return DRMP3_TRUE; + return drmp3_init(pMP3, drmp3__on_read_stdio, drmp3__on_seek_stdio, (void*)pFile, pConfig, pAllocationCallbacks); } -DRMP3_API drmp3_bool32 drmp3_init_file_w(drmp3* pMP3, const wchar_t* pFilePath, const drmp3_allocation_callbacks* pAllocationCallbacks) +DRMP3_API drmp3_bool32 drmp3_init_file_w(drmp3* pMP3, const wchar_t* pFilePath, const drmp3_config* pConfig, const drmp3_allocation_callbacks* pAllocationCallbacks) { - drmp3_bool32 result; FILE* pFile; - if (drmp3_wfopen(&pFile, pFilePath, L"rb", pAllocationCallbacks) != DRMP3_SUCCESS) { return DRMP3_FALSE; } - result = drmp3_init(pMP3, drmp3__on_read_stdio, drmp3__on_seek_stdio, (void*)pFile, pAllocationCallbacks); - if (result != DRMP3_TRUE) { - fclose(pFile); - return result; - } - - return DRMP3_TRUE; + return drmp3_init(pMP3, drmp3__on_read_stdio, drmp3__on_seek_stdio, (void*)pFile, pConfig, pAllocationCallbacks); } #endif @@ -3545,195 +3773,79 @@ DRMP3_API void drmp3_uninit(drmp3* pMP3) #ifndef DR_MP3_NO_STDIO if (pMP3->onRead == drmp3__on_read_stdio) { - FILE* pFile = (FILE*)pMP3->pUserData; - if (pFile != NULL) { - fclose(pFile); - pMP3->pUserData = NULL; /* Make sure the file handle is cleared to NULL to we don't attempt to close it a second time. */ - } + fclose((FILE*)pMP3->pUserData); } #endif drmp3__free_from_callbacks(pMP3->pData, &pMP3->allocationCallbacks); } -#if defined(DR_MP3_FLOAT_OUTPUT) -static void drmp3_f32_to_s16(drmp3_int16* dst, const float* src, drmp3_uint64 sampleCount) -{ - drmp3_uint64 i; - drmp3_uint64 i4; - drmp3_uint64 sampleCount4; - - /* Unrolled. */ - i = 0; - sampleCount4 = sampleCount >> 2; - for (i4 = 0; i4 < sampleCount4; i4 += 1) { - float x0 = src[i+0]; - float x1 = src[i+1]; - float x2 = src[i+2]; - float x3 = src[i+3]; - - x0 = ((x0 < -1) ? -1 : ((x0 > 1) ? 1 : x0)); - x1 = ((x1 < -1) ? -1 : ((x1 > 1) ? 1 : x1)); - x2 = ((x2 < -1) ? -1 : ((x2 > 1) ? 1 : x2)); - x3 = ((x3 < -1) ? -1 : ((x3 > 1) ? 1 : x3)); - - x0 = x0 * 32767.0f; - x1 = x1 * 32767.0f; - x2 = x2 * 32767.0f; - x3 = x3 * 32767.0f; - - dst[i+0] = (drmp3_int16)x0; - dst[i+1] = (drmp3_int16)x1; - dst[i+2] = (drmp3_int16)x2; - dst[i+3] = (drmp3_int16)x3; - - i += 4; - } - - /* Leftover. */ - for (; i < sampleCount; i += 1) { - float x = src[i]; - x = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); /* clip */ - x = x * 32767.0f; /* -1..1 to -32767..32767 */ - - dst[i] = (drmp3_int16)x; - } -} -#endif - -#if !defined(DR_MP3_FLOAT_OUTPUT) -static void drmp3_s16_to_f32(float* dst, const drmp3_int16* src, drmp3_uint64 sampleCount) -{ - drmp3_uint64 i; - for (i = 0; i < sampleCount; i += 1) { - float x = (float)src[i]; - x = x * 0.000030517578125f; /* -32768..32767 to -1..0.999969482421875 */ - dst[i] = x; - } -} -#endif - - -static drmp3_uint64 drmp3_read_pcm_frames_raw(drmp3* pMP3, drmp3_uint64 framesToRead, void* pBufferOut) +DRMP3_API drmp3_uint64 drmp3_read_pcm_frames_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut) { drmp3_uint64 totalFramesRead = 0; - DRMP3_ASSERT(pMP3 != NULL); - DRMP3_ASSERT(pMP3->onRead != NULL); + if (pMP3 == NULL || pMP3->onRead == NULL) { + return 0; + } - while (framesToRead > 0) { - drmp3_uint32 framesToConsume = (drmp3_uint32)DRMP3_MIN(pMP3->pcmFramesRemainingInMP3Frame, framesToRead); - if (pBufferOut != NULL) { - #if defined(DR_MP3_FLOAT_OUTPUT) - /* f32 */ - float* pFramesOutF32 = (float*)DRMP3_OFFSET_PTR(pBufferOut, sizeof(float) * totalFramesRead * pMP3->channels); - float* pFramesInF32 = (float*)DRMP3_OFFSET_PTR(&pMP3->pcmFrames[0], sizeof(float) * pMP3->pcmFramesConsumedInMP3Frame * pMP3->mp3FrameChannels); - DRMP3_COPY_MEMORY(pFramesOutF32, pFramesInF32, sizeof(float) * framesToConsume * pMP3->channels); - #else - /* s16 */ - drmp3_int16* pFramesOutS16 = (drmp3_int16*)DRMP3_OFFSET_PTR(pBufferOut, sizeof(drmp3_int16) * totalFramesRead * pMP3->channels); - drmp3_int16* pFramesInS16 = (drmp3_int16*)DRMP3_OFFSET_PTR(&pMP3->pcmFrames[0], sizeof(drmp3_int16) * pMP3->pcmFramesConsumedInMP3Frame * pMP3->mp3FrameChannels); - DRMP3_COPY_MEMORY(pFramesOutS16, pFramesInS16, sizeof(drmp3_int16) * framesToConsume * pMP3->channels); - #endif - } - - pMP3->currentPCMFrame += framesToConsume; - pMP3->pcmFramesConsumedInMP3Frame += framesToConsume; - pMP3->pcmFramesRemainingInMP3Frame -= framesToConsume; - totalFramesRead += framesToConsume; - framesToRead -= framesToConsume; - - if (framesToRead == 0) { - break; - } - - DRMP3_ASSERT(pMP3->pcmFramesRemainingInMP3Frame == 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. - */ - if (drmp3_decode_next_frame(pMP3) == 0) { - break; + if (pBufferOut == NULL) { + float temp[4096]; + while (framesToRead > 0) { + drmp3_uint64 framesJustRead; + drmp3_uint64 framesToReadRightNow = sizeof(temp)/sizeof(temp[0]) / pMP3->channels; + if (framesToReadRightNow > framesToRead) { + framesToReadRightNow = framesToRead; + } + + framesJustRead = drmp3_read_pcm_frames_f32(pMP3, framesToReadRightNow, temp); + if (framesJustRead == 0) { + break; + } + + framesToRead -= framesJustRead; + totalFramesRead += framesJustRead; } + } else { + totalFramesRead = drmp3_src_read_frames_ex(&pMP3->src, framesToRead, pBufferOut, DRMP3_TRUE); + pMP3->currentPCMFrame += totalFramesRead; } return totalFramesRead; } - -DRMP3_API drmp3_uint64 drmp3_read_pcm_frames_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut) -{ - if (pMP3 == NULL || pMP3->onRead == NULL) { - return 0; - } - -#if defined(DR_MP3_FLOAT_OUTPUT) - /* Fast path. No conversion required. */ - return drmp3_read_pcm_frames_raw(pMP3, framesToRead, pBufferOut); -#else - /* Slow path. Convert from s16 to f32. */ - { - drmp3_int16 pTempS16[8192]; - drmp3_uint64 totalPCMFramesRead = 0; - - while (totalPCMFramesRead < framesToRead) { - drmp3_uint64 framesJustRead; - drmp3_uint64 framesRemaining = framesToRead - totalPCMFramesRead; - drmp3_uint64 framesToReadNow = DRMP3_COUNTOF(pTempS16) / pMP3->channels; - if (framesToReadNow > framesRemaining) { - framesToReadNow = framesRemaining; - } - - framesJustRead = drmp3_read_pcm_frames_raw(pMP3, framesToReadNow, pTempS16); - if (framesJustRead == 0) { - break; - } - - drmp3_s16_to_f32((float*)DRMP3_OFFSET_PTR(pBufferOut, sizeof(float) * totalPCMFramesRead * pMP3->channels), pTempS16, framesJustRead * pMP3->channels); - totalPCMFramesRead += framesJustRead; - } - - return totalPCMFramesRead; - } -#endif -} - DRMP3_API drmp3_uint64 drmp3_read_pcm_frames_s16(drmp3* pMP3, drmp3_uint64 framesToRead, drmp3_int16* pBufferOut) { + float tempF32[4096]; + drmp3_uint64 pcmFramesJustRead; + drmp3_uint64 totalPCMFramesRead = 0; + if (pMP3 == NULL || pMP3->onRead == NULL) { return 0; } -#if !defined(DR_MP3_FLOAT_OUTPUT) - /* Fast path. No conversion required. */ - return drmp3_read_pcm_frames_raw(pMP3, framesToRead, pBufferOut); -#else - /* Slow path. Convert from f32 to s16. */ - { - float pTempF32[4096]; - drmp3_uint64 totalPCMFramesRead = 0; - - while (totalPCMFramesRead < framesToRead) { - drmp3_uint64 framesJustRead; - drmp3_uint64 framesRemaining = framesToRead - totalPCMFramesRead; - drmp3_uint64 framesToReadNow = DRMP3_COUNTOF(pTempF32) / pMP3->channels; - if (framesToReadNow > framesRemaining) { - framesToReadNow = framesRemaining; - } - - framesJustRead = drmp3_read_pcm_frames_raw(pMP3, framesToReadNow, pTempF32); - if (framesJustRead == 0) { - break; - } - - drmp3_f32_to_s16((drmp3_int16*)DRMP3_OFFSET_PTR(pBufferOut, sizeof(drmp3_int16) * totalPCMFramesRead * pMP3->channels), pTempF32, framesJustRead * pMP3->channels); - totalPCMFramesRead += framesJustRead; + /* Naive implementation: read into a temp f32 buffer, then convert. */ + for (;;) { + drmp3_uint64 pcmFramesToReadThisIteration = (framesToRead - totalPCMFramesRead); + if (pcmFramesToReadThisIteration > drmp3_countof(tempF32)/pMP3->channels) { + pcmFramesToReadThisIteration = drmp3_countof(tempF32)/pMP3->channels; } - return totalPCMFramesRead; + pcmFramesJustRead = drmp3_read_pcm_frames_f32(pMP3, pcmFramesToReadThisIteration, tempF32); + if (pcmFramesJustRead == 0) { + break; + } + + drmp3dec_f32_to_s16(tempF32, pBufferOut, (int)(pcmFramesJustRead * pMP3->channels)); /* <-- Safe cast since pcmFramesJustRead will be clamped based on the size of tempF32 which is always small. */ + pBufferOut += pcmFramesJustRead * pMP3->channels; + + totalPCMFramesRead += pcmFramesJustRead; + + if (pcmFramesJustRead < pcmFramesToReadThisIteration) { + break; + } } -#endif + + return totalPCMFramesRead; } static void drmp3_reset(drmp3* pMP3) @@ -3745,6 +3857,15 @@ static void drmp3_reset(drmp3* pMP3) pMP3->currentPCMFrame = 0; pMP3->dataSize = 0; pMP3->atEnd = DRMP3_FALSE; + pMP3->src.bin[0] = 0; + pMP3->src.bin[1] = 0; + pMP3->src.bin[2] = 0; + pMP3->src.bin[3] = 0; + pMP3->src.cache.cachedFrameCount = 0; + pMP3->src.cache.iNextFrame = 0; + pMP3->src.algo.linear.alpha = 0; + pMP3->src.algo.linear.isNextFramesLoaded = 0; + pMP3->src.algo.linear.isPrevFramesLoaded = 0; drmp3dec_init(&pMP3->decoder); } @@ -3763,24 +3884,73 @@ static drmp3_bool32 drmp3_seek_to_start_of_stream(drmp3* pMP3) return DRMP3_TRUE; } +static float drmp3_get_cached_pcm_frame_count_from_src(drmp3* pMP3) +{ + return (pMP3->src.cache.cachedFrameCount - pMP3->src.cache.iNextFrame) + (float)pMP3->src.algo.linear.alpha; +} +static float drmp3_get_pcm_frames_remaining_in_mp3_frame(drmp3* pMP3) +{ + float factor = (float)pMP3->src.config.sampleRateOut / (float)pMP3->src.config.sampleRateIn; + float frameCountPreSRC = drmp3_get_cached_pcm_frame_count_from_src(pMP3) + pMP3->pcmFramesRemainingInMP3Frame; + return frameCountPreSRC * factor; +} + +/* +NOTE ON SEEKING +=============== +The seeking code below is a complete mess and is broken for cases when the sample rate changes. The problem +is with the resampling and the crappy resampler used by dr_mp3. What needs to happen is the following: + +1) The resampler needs to be replaced. +2) The resampler has state which needs to be updated whenever an MP3 frame is decoded outside of + drmp3_read_pcm_frames_f32(). The resampler needs an API to "flush" some imaginary input so that it's + state is updated accordingly. +*/ static drmp3_bool32 drmp3_seek_forward_by_pcm_frames__brute_force(drmp3* pMP3, drmp3_uint64 frameOffset) { drmp3_uint64 framesRead; +#if 0 /* - Just using a dumb read-and-discard for now. What would be nice is to parse only the header of the MP3 frame, and then skip over leading - frames without spending the time doing a full decode. I cannot see an easy way to do this in minimp3, however, so it may involve some - kind of manual processing. + MP3 is a bit annoying when it comes to seeking because of the bit reservoir. It basically means that an MP3 frame can possibly + depend on some of the data of prior frames. This means it's not as simple as seeking to the first byte of the MP3 frame that + contains the sample because that MP3 frame will need the data from the previous MP3 frame (which we just seeked past!). To + resolve this we seek past a number of MP3 frames up to a point, and then read-and-discard the remainder. */ -#if defined(DR_MP3_FLOAT_OUTPUT) + drmp3_uint64 maxFramesToReadAndDiscard = (drmp3_uint64)(DRMP3_MAX_PCM_FRAMES_PER_MP3_FRAME * 3 * ((float)pMP3->src.config.sampleRateOut / (float)pMP3->src.config.sampleRateIn)); + + /* Now get rid of leading whole frames. */ + while (frameOffset > maxFramesToReadAndDiscard) { + float pcmFramesRemainingInCurrentMP3FrameF = drmp3_get_pcm_frames_remaining_in_mp3_frame(pMP3); + drmp3_uint32 pcmFramesRemainingInCurrentMP3Frame = (drmp3_uint32)pcmFramesRemainingInCurrentMP3FrameF; + if (frameOffset > pcmFramesRemainingInCurrentMP3Frame) { + frameOffset -= pcmFramesRemainingInCurrentMP3Frame; + pMP3->currentPCMFrame += pcmFramesRemainingInCurrentMP3Frame; + pMP3->pcmFramesConsumedInMP3Frame += pMP3->pcmFramesRemainingInMP3Frame; + pMP3->pcmFramesRemainingInMP3Frame = 0; + } else { + break; + } + + drmp3_uint32 pcmFrameCount = drmp3_decode_next_frame_ex(pMP3, pMP3->pcmFrames, DRMP3_FALSE); + if (pcmFrameCount == 0) { + break; + } + } + + /* The last step is to read-and-discard any remaining PCM frames to make it sample-exact. */ framesRead = drmp3_read_pcm_frames_f32(pMP3, frameOffset, NULL); -#else - framesRead = drmp3_read_pcm_frames_s16(pMP3, frameOffset, NULL); -#endif if (framesRead != frameOffset) { return DRMP3_FALSE; } +#else + /* Just using a dumb read-and-discard for now pending updates to the resampler. */ + framesRead = drmp3_read_pcm_frames_f32(pMP3, frameOffset, NULL); + if (framesRead != frameOffset) { + return DRMP3_FALSE; + } +#endif return DRMP3_TRUE; } @@ -3863,7 +4033,7 @@ static drmp3_bool32 drmp3_seek_to_pcm_frame__seek_table(drmp3* pMP3, drmp3_uint6 /* Whole MP3 frames need to be discarded first. */ for (iMP3Frame = 0; iMP3Frame < seekPoint.mp3FramesToDiscard; ++iMP3Frame) { - drmp3_uint32 pcmFramesRead; + drmp3_uint32 pcmFramesReadPreSRC; drmp3d_sample_t* pPCMFrames; /* Pass in non-null for the last frame because we want to ensure the sample rate converter is preloaded correctly. */ @@ -3872,9 +4042,9 @@ static drmp3_bool32 drmp3_seek_to_pcm_frame__seek_table(drmp3* pMP3, drmp3_uint6 pPCMFrames = (drmp3d_sample_t*)pMP3->pcmFrames; } - /* We first need to decode the next frame. */ - pcmFramesRead = drmp3_decode_next_frame_ex(pMP3, pPCMFrames); - if (pcmFramesRead == 0) { + /* We first need to decode the next frame, and then we need to flush the resampler. */ + pcmFramesReadPreSRC = drmp3_decode_next_frame_ex(pMP3, pPCMFrames, DRMP3_TRUE); + if (pcmFramesReadPreSRC == 0) { return DRMP3_FALSE; } } @@ -3882,6 +4052,17 @@ static drmp3_bool32 drmp3_seek_to_pcm_frame__seek_table(drmp3* pMP3, drmp3_uint6 /* We seeked to an MP3 frame in the raw stream so we need to make sure the current PCM frame is set correctly. */ pMP3->currentPCMFrame = seekPoint.pcmFrameIndex - seekPoint.pcmFramesToDiscard; + /* + Update resampler. This is wrong. Need to instead update it on a per MP3 frame basis. Also broken for cases when + the sample rate is being reduced in my testing. Should work fine when the input and output sample rate is the same + or a clean multiple. + */ + pMP3->src.algo.linear.alpha = (drmp3_int64)pMP3->currentPCMFrame * ((double)pMP3->src.config.sampleRateIn / pMP3->src.config.sampleRateOut); /* <-- Cast to int64 is required for VC6. */ + pMP3->src.algo.linear.alpha = pMP3->src.algo.linear.alpha - (drmp3_uint32)(pMP3->src.algo.linear.alpha); + if (pMP3->src.algo.linear.alpha > 0) { + pMP3->src.algo.linear.isPrevFramesLoaded = 1; + } + /* Now at this point we can follow the same process as the brute force technique where we just skip over unnecessary MP3 frames and then read-and-discard at least 2 whole MP3 frames. @@ -3913,6 +4094,7 @@ DRMP3_API drmp3_bool32 drmp3_get_mp3_and_pcm_frame_count(drmp3* pMP3, drmp3_uint drmp3_uint64 currentPCMFrame; drmp3_uint64 totalPCMFrameCount; drmp3_uint64 totalMP3FrameCount; + float totalPCMFrameCountFractionalPart; if (pMP3 == NULL) { return DRMP3_FALSE; @@ -3938,15 +4120,25 @@ DRMP3_API drmp3_bool32 drmp3_get_mp3_and_pcm_frame_count(drmp3* pMP3, drmp3_uint totalPCMFrameCount = 0; totalMP3FrameCount = 0; + totalPCMFrameCountFractionalPart = 0; /* <-- With resampling there will be a fractional part to each MP3 frame that we need to accumulate. */ for (;;) { - drmp3_uint32 pcmFramesInCurrentMP3Frame; + drmp3_uint32 pcmFramesInCurrentMP3FrameIn; + float srcRatio; + float pcmFramesInCurrentMP3FrameOutF; + drmp3_uint32 pcmFramesInCurrentMP3FrameOut; - pcmFramesInCurrentMP3Frame = drmp3_decode_next_frame_ex(pMP3, NULL); - if (pcmFramesInCurrentMP3Frame == 0) { + pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL, DRMP3_FALSE); + if (pcmFramesInCurrentMP3FrameIn == 0) { break; } - totalPCMFrameCount += pcmFramesInCurrentMP3Frame; + srcRatio = (float)pMP3->mp3FrameSampleRate / (float)pMP3->sampleRate; + DRMP3_ASSERT(srcRatio > 0); + + pcmFramesInCurrentMP3FrameOutF = totalPCMFrameCountFractionalPart + (pcmFramesInCurrentMP3FrameIn / srcRatio); + pcmFramesInCurrentMP3FrameOut = (drmp3_uint32)pcmFramesInCurrentMP3FrameOutF; + totalPCMFrameCountFractionalPart = pcmFramesInCurrentMP3FrameOutF - pcmFramesInCurrentMP3FrameOut; + totalPCMFrameCount += pcmFramesInCurrentMP3FrameOut; totalMP3FrameCount += 1; } @@ -4079,7 +4271,7 @@ DRMP3_API drmp3_bool32 drmp3_calculate_seek_points(drmp3* pMP3, drmp3_uint32* pS mp3FrameInfo[iMP3Frame].pcmFrameIndex = runningPCMFrameCount; /* We need to get information about this frame so we can know how many samples it contained. */ - pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL); + pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL, DRMP3_FALSE); if (pcmFramesInCurrentMP3FrameIn == 0) { return DRMP3_FALSE; /* This should never happen. */ } @@ -4111,19 +4303,19 @@ DRMP3_API drmp3_bool32 drmp3_calculate_seek_points(drmp3* pMP3, drmp3_uint32* pS The next seek point is not in the current MP3 frame, so continue on to the next one. The first thing to do is cycle the cached MP3 frame info. */ - for (i = 0; i < DRMP3_COUNTOF(mp3FrameInfo)-1; ++i) { + for (i = 0; i < drmp3_countof(mp3FrameInfo)-1; ++i) { mp3FrameInfo[i] = mp3FrameInfo[i+1]; } /* Cache previous MP3 frame info. */ - mp3FrameInfo[DRMP3_COUNTOF(mp3FrameInfo)-1].bytePos = pMP3->streamCursor - pMP3->dataSize; - mp3FrameInfo[DRMP3_COUNTOF(mp3FrameInfo)-1].pcmFrameIndex = runningPCMFrameCount; + mp3FrameInfo[drmp3_countof(mp3FrameInfo)-1].bytePos = pMP3->streamCursor - pMP3->dataSize; + mp3FrameInfo[drmp3_countof(mp3FrameInfo)-1].pcmFrameIndex = runningPCMFrameCount; /* Go to the next MP3 frame. This shouldn't ever fail, but just in case it does we just set the seek point and break. If it happens, it should only ever do it for the last seek point. */ - pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL); + pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL, DRMP3_TRUE); if (pcmFramesInCurrentMP3FrameIn == 0) { pSeekPoints[iSeekPoint].seekPosInBytes = mp3FrameInfo[0].bytePos; pSeekPoints[iSeekPoint].pcmFrameIndex = nextTargetPCMFrame; @@ -4180,7 +4372,7 @@ static float* drmp3__full_read_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, DRMP3_ASSERT(pMP3 != NULL); for (;;) { - drmp3_uint64 framesToReadRightNow = DRMP3_COUNTOF(temp) / pMP3->channels; + drmp3_uint64 framesToReadRightNow = drmp3_countof(temp) / pMP3->channels; drmp3_uint64 framesJustRead = drmp3_read_pcm_frames_f32(pMP3, framesToReadRightNow, temp); if (framesJustRead == 0) { break; @@ -4200,7 +4392,7 @@ static float* drmp3__full_read_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, oldFramesBufferSize = framesCapacity * pMP3->channels * sizeof(float); newFramesBufferSize = newFramesCap * pMP3->channels * sizeof(float); - if (newFramesBufferSize > (drmp3_uint64)DRMP3_SIZE_MAX) { + if (newFramesBufferSize > DRMP3_SIZE_MAX) { break; } @@ -4224,8 +4416,8 @@ static float* drmp3__full_read_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, } if (pConfig != NULL) { - pConfig->channels = pMP3->channels; - pConfig->sampleRate = pMP3->sampleRate; + pConfig->outputChannels = pMP3->channels; + pConfig->outputSampleRate = pMP3->sampleRate; } drmp3_uninit(pMP3); @@ -4247,7 +4439,7 @@ static drmp3_int16* drmp3__full_read_and_close_s16(drmp3* pMP3, drmp3_config* pC DRMP3_ASSERT(pMP3 != NULL); for (;;) { - drmp3_uint64 framesToReadRightNow = DRMP3_COUNTOF(temp) / pMP3->channels; + drmp3_uint64 framesToReadRightNow = drmp3_countof(temp) / pMP3->channels; drmp3_uint64 framesJustRead = drmp3_read_pcm_frames_s16(pMP3, framesToReadRightNow, temp); if (framesJustRead == 0) { break; @@ -4267,7 +4459,7 @@ static drmp3_int16* drmp3__full_read_and_close_s16(drmp3* pMP3, drmp3_config* pC oldFramesBufferSize = framesCapacity * pMP3->channels * sizeof(drmp3_int16); newFramesBufferSize = newFramesCap * pMP3->channels * sizeof(drmp3_int16); - if (newFramesBufferSize > (drmp3_uint64)DRMP3_SIZE_MAX) { + if (newFramesBufferSize > DRMP3_SIZE_MAX) { break; } @@ -4291,8 +4483,8 @@ static drmp3_int16* drmp3__full_read_and_close_s16(drmp3* pMP3, drmp3_config* pC } if (pConfig != NULL) { - pConfig->channels = pMP3->channels; - pConfig->sampleRate = pMP3->sampleRate; + pConfig->outputChannels = pMP3->channels; + pConfig->outputSampleRate = pMP3->sampleRate; } drmp3_uninit(pMP3); @@ -4308,7 +4500,7 @@ static drmp3_int16* drmp3__full_read_and_close_s16(drmp3* pMP3, drmp3_config* pC DRMP3_API float* drmp3_open_and_read_pcm_frames_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3 mp3; - if (!drmp3_init(&mp3, onRead, onSeek, pUserData, pAllocationCallbacks)) { + if (!drmp3_init(&mp3, onRead, onSeek, pUserData, pConfig, pAllocationCallbacks)) { return NULL; } @@ -4318,7 +4510,7 @@ DRMP3_API float* drmp3_open_and_read_pcm_frames_f32(drmp3_read_proc onRead, drmp DRMP3_API drmp3_int16* drmp3_open_and_read_pcm_frames_s16(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3 mp3; - if (!drmp3_init(&mp3, onRead, onSeek, pUserData, pAllocationCallbacks)) { + if (!drmp3_init(&mp3, onRead, onSeek, pUserData, pConfig, pAllocationCallbacks)) { return NULL; } @@ -4329,7 +4521,7 @@ DRMP3_API drmp3_int16* drmp3_open_and_read_pcm_frames_s16(drmp3_read_proc onRead DRMP3_API float* drmp3_open_memory_and_read_pcm_frames_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3 mp3; - if (!drmp3_init_memory(&mp3, pData, dataSize, pAllocationCallbacks)) { + if (!drmp3_init_memory(&mp3, pData, dataSize, pConfig, pAllocationCallbacks)) { return NULL; } @@ -4339,7 +4531,7 @@ DRMP3_API float* drmp3_open_memory_and_read_pcm_frames_f32(const void* pData, si DRMP3_API drmp3_int16* drmp3_open_memory_and_read_pcm_frames_s16(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3 mp3; - if (!drmp3_init_memory(&mp3, pData, dataSize, pAllocationCallbacks)) { + if (!drmp3_init_memory(&mp3, pData, dataSize, pConfig, pAllocationCallbacks)) { return NULL; } @@ -4351,7 +4543,7 @@ DRMP3_API drmp3_int16* drmp3_open_memory_and_read_pcm_frames_s16(const void* pDa DRMP3_API float* drmp3_open_file_and_read_pcm_frames_f32(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3 mp3; - if (!drmp3_init_file(&mp3, filePath, pAllocationCallbacks)) { + if (!drmp3_init_file(&mp3, filePath, pConfig, pAllocationCallbacks)) { return NULL; } @@ -4361,7 +4553,7 @@ DRMP3_API float* drmp3_open_file_and_read_pcm_frames_f32(const char* filePath, d DRMP3_API drmp3_int16* drmp3_open_file_and_read_pcm_frames_s16(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3 mp3; - if (!drmp3_init_file(&mp3, filePath, pAllocationCallbacks)) { + if (!drmp3_init_file(&mp3, filePath, pConfig, pAllocationCallbacks)) { return NULL; } @@ -4369,15 +4561,6 @@ DRMP3_API drmp3_int16* drmp3_open_file_and_read_pcm_frames_s16(const char* fileP } #endif -DRMP3_API void* drmp3_malloc(size_t sz, const drmp3_allocation_callbacks* pAllocationCallbacks) -{ - if (pAllocationCallbacks != NULL) { - return drmp3__malloc_from_callbacks(sz, pAllocationCallbacks); - } else { - return drmp3__malloc_default(sz, NULL); - } -} - DRMP3_API void drmp3_free(void* p, const drmp3_allocation_callbacks* pAllocationCallbacks) { if (pAllocationCallbacks != NULL) { @@ -4387,8 +4570,7 @@ DRMP3_API void drmp3_free(void* p, const drmp3_allocation_callbacks* pAllocation } } -#endif /* dr_mp3_c */ -#endif /*DR_MP3_IMPLEMENTATION*/ +#endif /*DR_MP3_IMPLEMENTATION*/ /* DIFFERENCES BETWEEN minimp3 AND dr_mp3 @@ -4473,116 +4655,7 @@ counts rather than sample counts. /* REVISION HISTORY ================ -v0.6.32 - 2021-12-11 - - Fix a warning with Clang. - -v0.6.31 - 2021-08-22 - - Fix a bug when loading from memory. - -v0.6.30 - 2021-08-16 - - Silence some warnings. - - Replace memory operations with DRMP3_* macros. - -v0.6.29 - 2021-08-08 - - Bring up to date with minimp3. - -v0.6.28 - 2021-07-31 - - Fix platform detection for ARM64. - - Fix a compilation error with C89. - -v0.6.27 - 2021-02-21 - - Fix a warning due to referencing _MSC_VER when it is undefined. - -v0.6.26 - 2021-01-31 - - Bring up to date with minimp3. - -v0.6.25 - 2020-12-26 - - Remove DRMP3_DEFAULT_CHANNELS and DRMP3_DEFAULT_SAMPLE_RATE which are leftovers from some removed APIs. - -v0.6.24 - 2020-12-07 - - Fix a typo in version date for 0.6.23. - -v0.6.23 - 2020-12-03 - - Fix an error where a file can be closed twice when initialization of the decoder fails. - -v0.6.22 - 2020-12-02 - - Fix an error where it's possible for a file handle to be left open when initialization of the decoder fails. - -v0.6.21 - 2020-11-28 - - Bring up to date with minimp3. - -v0.6.20 - 2020-11-21 - - Fix compilation with OpenWatcom. - -v0.6.19 - 2020-11-13 - - Minor code clean up. - -v0.6.18 - 2020-11-01 - - Improve compiler support for older versions of GCC. - -v0.6.17 - 2020-09-28 - - Bring up to date with minimp3. - -v0.6.16 - 2020-08-02 - - Simplify sized types. - -v0.6.15 - 2020-07-25 - - Fix a compilation warning. - -v0.6.14 - 2020-07-23 - - Fix undefined behaviour with memmove(). - -v0.6.13 - 2020-07-06 - - Fix a bug when converting from s16 to f32 in drmp3_read_pcm_frames_f32(). - -v0.6.12 - 2020-06-23 - - Add include guard for the implementation section. - -v0.6.11 - 2020-05-26 - - Fix use of uninitialized variable error. - -v0.6.10 - 2020-05-16 - - Add compile-time and run-time version querying. - - DRMP3_VERSION_MINOR - - DRMP3_VERSION_MAJOR - - DRMP3_VERSION_REVISION - - DRMP3_VERSION_STRING - - drmp3_version() - - drmp3_version_string() - -v0.6.9 - 2020-04-30 - - Change the `pcm` parameter of drmp3dec_decode_frame() to a `const drmp3_uint8*` for consistency with internal APIs. - -v0.6.8 - 2020-04-26 - - Optimizations to decoding when initializing from memory. - -v0.6.7 - 2020-04-25 - - Fix a compilation error with DR_MP3_NO_STDIO - - Optimization to decoding by reducing some data movement. - -v0.6.6 - 2020-04-23 - - Fix a minor bug with the running PCM frame counter. - -v0.6.5 - 2020-04-19 - - Fix compilation error on ARM builds. - -v0.6.4 - 2020-04-19 - - Bring up to date with changes to minimp3. - -v0.6.3 - 2020-04-13 - - Fix some pedantic warnings. - -v0.6.2 - 2020-04-10 - - Fix a crash in drmp3_open_*_and_read_pcm_frames_*() if the output config object is NULL. - -v0.6.1 - 2020-04-05 - - Fix warnings. - -v0.6.0 - 2020-04-04 - - API CHANGE: Remove the pConfig parameter from the following APIs: - - drmp3_init() - - drmp3_init_memory() - - drmp3_init_file() +v0.6.0 - TBD - Add drmp3_init_file_w() for opening a file from a wchar_t encoded path. v0.5.6 - 2020-02-12 @@ -4636,9 +4709,9 @@ v0.4.4 - 2019-05-06 - Fixes to the VC6 build. v0.4.3 - 2019-05-05 - - Use the channel count and/or sample rate of the first MP3 frame instead of DRMP3_DEFAULT_CHANNELS and - DRMP3_DEFAULT_SAMPLE_RATE when they are set to 0. To use the old behaviour, just set the relevant property to - DRMP3_DEFAULT_CHANNELS or DRMP3_DEFAULT_SAMPLE_RATE. + - Use the channel count and/or sample rate of the first MP3 frame instead of DR_MP3_DEFAULT_CHANNELS and + DR_MP3_DEFAULT_SAMPLE_RATE when they are set to 0. To use the old behaviour, just set the relevant property to + DR_MP3_DEFAULT_CHANNELS or DR_MP3_DEFAULT_SAMPLE_RATE. - Add s16 reading APIs - drmp3_read_pcm_frames_s16 - drmp3_open_memory_and_read_pcm_frames_s16