From 632911b35035a08601feabe9f19669cb005c912b Mon Sep 17 00:00:00 2001 From: David Reid Date: Sun, 16 Apr 2017 14:34:25 +1000 Subject: [PATCH] Initial implementation of low quality linear sample rate conversion. --- mini_al.h | 203 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 196 insertions(+), 7 deletions(-) diff --git a/mini_al.h b/mini_al.h index d2dbc80b..6bffbfa8 100644 --- a/mini_al.h +++ b/mini_al.h @@ -164,6 +164,11 @@ extern "C" { #endif +#if defined(_MSC_VER) + #pragma warning(push) + #pragma warning(disable:4201) // nonstandard extension used: nameless struct/union +#endif + // Platform/backend detection. #ifdef _WIN32 #define MAL_WIN32 @@ -383,6 +388,15 @@ typedef enum mal_src_algorithm_linear } mal_src_algorithm; +#define MAL_SRC_CACHE_SIZE_IN_FRAMES 512 +typedef struct +{ + mal_src* pSRC; + float pCachedFrames[MAL_MAX_CHANNELS * MAL_SRC_CACHE_SIZE_IN_FRAMES]; + mal_uint32 cachedFrameCount; + mal_uint32 iNextFrame; +} mal_src_cache; + typedef struct { mal_uint32 sampleRateIn; @@ -400,6 +414,16 @@ struct mal_src void* pUserData; float ratio; float bin[256]; + mal_src_cache cache; // <-- For simplifying and optimizing client -> memory reading. + + union + { + struct + { + float alpha; + mal_bool32 isBinLoaded : 1; + } linear; + }; }; typedef struct mal_dsp mal_dsp; @@ -443,10 +467,6 @@ typedef struct mal_log_proc onLogCallback; } mal_device_config; -#if defined(_MSC_VER) - #pragma warning(push) - #pragma warning(disable:4201) // nonstandard extension used: nameless struct/union -#endif typedef struct { mal_backend backend; // DirectSound, ALSA, etc. @@ -974,6 +994,17 @@ mal_uint32 mal_dsp_read_frames(mal_dsp* pDSP, mal_uint32 frameCount, void* pFram +/////////////////////////////////////////////////////////////////////////////// +// +// Miscellaneous Helpers +// +/////////////////////////////////////////////////////////////////////////////// + +// Blends two frames in floating point format. +void mal_blend_f32(float* pOut, float* pInA, float* pInB, float factor, mal_uint32 channels); + + + /////////////////////////////////////////////////////////////////////////////// // // Format Conversion @@ -1001,6 +1032,7 @@ void mal_pcm_f32_to_s24(void* pOut, const float* pIn, unsigned int count); void mal_pcm_f32_to_s32(int* pOut, const float* pIn, unsigned int count); void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_format formatIn, unsigned int sampleCount); + #endif #ifdef __cplusplus @@ -1173,6 +1205,9 @@ mal_uint8 MAL_CHANNEL_MAP_5POINT1[MAL_MAX_CHANNELS] = { #define mal_max(x, y) (((x) > (y)) ? (x) : (y)) #define mal_min(x, y) (((x) < (y)) ? (x) : (y)) +#define mal_buffer_frame_capacity(buffer, channels, format) (sizeof(buffer) / mal_get_sample_size_in_bytes(format) / (channels)) + + // Return Values: // 0: Success // 22: EINVAL @@ -1264,6 +1299,12 @@ static inline float mal_clip_f32(float x) return x; } +static inline float mal_mix_f32(float x, float y, float a) +{ + return x*(1-a) + y*a; +} + + /////////////////////////////////////////////////////////////////////////////// // // Atomics @@ -5773,6 +5814,81 @@ mal_uint32 mal_get_sample_size_in_bytes(mal_format format) // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void mal_src_cache_init(mal_src* pSRC, mal_src_cache* pCache) +{ + mal_assert(pSRC != NULL); + mal_assert(pCache != NULL); + + pCache->pSRC = pSRC; + pCache->cachedFrameCount = 0; + pCache->iNextFrame = 0; +} + +mal_uint32 mal_src_cache_read_frames(mal_src_cache* pCache, mal_uint32 frameCount, float* pFramesOut) +{ + mal_assert(pCache != NULL); + mal_assert(pCache->pSRC != NULL); + mal_assert(pCache->pSRC->onRead != NULL); + mal_assert(frameCount > 0); + mal_assert(pFramesOut != NULL); + + mal_uint32 channels = pCache->pSRC->config.channels; + + mal_uint32 totalFramesRead = 0; + while (frameCount > 0) { + // If there's anything in memory go ahead and copy that over first. + mal_uint32 framesRemainingInMemory = pCache->cachedFrameCount - pCache->iNextFrame; + mal_uint32 framesToReadFromMemory = frameCount; + if (framesToReadFromMemory > framesRemainingInMemory) { + framesToReadFromMemory = framesRemainingInMemory; + } + + mal_copy_memory(pFramesOut, pCache->pCachedFrames + pCache->iNextFrame*channels, framesToReadFromMemory * channels * sizeof(float)); + pCache->iNextFrame += 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. + mal_assert(frameCount > 0); + pFramesOut += framesToReadFromMemory * channels; + + pCache->iNextFrame = 0; + pCache->cachedFrameCount = 0; + if (pCache->pSRC->config.formatIn == mal_format_f32) { + // No need for a conversion - read straight into the cache. + mal_uint32 framesToReadFromClient = mal_countof(pCache->pCachedFrames) / pCache->pSRC->config.channels; + if (framesToReadFromClient > frameCount) { + framesToReadFromClient = frameCount; + } + + pCache->cachedFrameCount = pCache->pSRC->onRead(framesToReadFromClient, pCache->pCachedFrames, pCache->pSRC->pUserData); + } else { + // A format conversion is required which means we need to use an intermediary buffer. + mal_uint8 pIntermediaryBuffer[sizeof(pCache->pCachedFrames)]; + mal_uint32 framesToReadFromClient = mal_min(mal_buffer_frame_capacity(pIntermediaryBuffer, channels, pCache->pSRC->config.formatIn), mal_buffer_frame_capacity(pCache->pCachedFrames, channels, mal_format_f32)); + + pCache->cachedFrameCount = pCache->pSRC->onRead(framesToReadFromClient, pIntermediaryBuffer, pCache->pSRC->pUserData); + + // Convert to f32. + mal_pcm_convert(pCache->pCachedFrames, mal_format_f32, pIntermediaryBuffer, pCache->pSRC->config.formatIn, pCache->cachedFrameCount * channels); + } + + + // Get out of this loop if nothing was able to be retrieved. + if (pCache->cachedFrameCount == 0) { + break; + } + } + + return totalFramesRead; +} + + mal_uint32 mal_src_read_frames_passthrough(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut); mal_uint32 mal_src_read_frames_linear(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut); @@ -5795,6 +5911,7 @@ mal_result mal_src_init(mal_src_config* pConfig, mal_src_read_proc onRead, void* pSRC->ratio = (float)pSRC->config.sampleRateIn / pSRC->config.sampleRateOut; + mal_src_cache_init(pSRC, &pSRC->cache); return MAL_SUCCESS; } @@ -5853,8 +5970,62 @@ mal_uint32 mal_src_read_frames_linear(mal_src* pSRC, mal_uint32 frameCount, void mal_assert(frameCount > 0); mal_assert(pFramesOut != NULL); - // TODO: Implement me properly. - return mal_src_read_frames_passthrough(pSRC, frameCount, pFramesOut); + // Load the bin if it's not been loaded yet. + if (!pSRC->linear.isBinLoaded) { + mal_uint32 framesRead = mal_src_cache_read_frames(&pSRC->cache, 2, pSRC->bin); + if (framesRead == 0) { + return 0; + } + + if (framesRead == 1) { + mal_pcm_convert(pFramesOut, pSRC->config.formatOut, pSRC->bin, mal_format_f32, framesRead * pSRC->config.channels); + return framesRead; + } + + pSRC->linear.isBinLoaded = MAL_TRUE; + } + + + float factor = pSRC->ratio; + + mal_uint32 totalFramesRead = 0; + while (frameCount > 0) { + // The bin is where the previous and next frames are located. + float* pPrevFrame = pSRC->bin; + float* pNextFrame = pSRC->bin + pSRC->config.channels; + + float pFrame[MAL_MAX_CHANNELS]; + mal_blend_f32(pFrame, pPrevFrame, pNextFrame, pSRC->linear.alpha, pSRC->config.channels); + + pSRC->linear.alpha += factor; + + // The new alpha value is how we determine whether or not we need to read fresh frames. + mal_uint32 framesToReadFromClient = (mal_uint32)pSRC->linear.alpha; + pSRC->linear.alpha = pSRC->linear.alpha - framesToReadFromClient; + + for (mal_uint32 i = 0; i < framesToReadFromClient; ++i) { + for (mal_uint32 j = 0; j < pSRC->config.channels; ++j) { + pPrevFrame[j] = pNextFrame[j]; + } + + mal_uint32 framesRead = mal_src_cache_read_frames(&pSRC->cache, 1, pNextFrame); + if (framesRead == 0) { + for (mal_uint32 j = 0; j < pSRC->config.channels; ++j) { + pNextFrame[j] = 0; + } + + pSRC->linear.isBinLoaded = MAL_FALSE; + } + } + + mal_pcm_convert(pFramesOut, pSRC->config.formatOut, pFrame, mal_format_f32, 1 * pSRC->config.channels); + + pFramesOut = (mal_uint8*)pFramesOut + (1 * pSRC->config.channels * mal_get_sample_size_in_bytes(pSRC->config.formatOut)); + frameCount -= 1; + totalFramesRead += 1; + } + + return totalFramesRead; } @@ -5874,7 +6045,7 @@ mal_uint32 mal_src_read_frames_linear(mal_src* pSRC, mal_uint32 frameCount, void ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#if 1 +#if 0 #include "tools/mal_build/bin/mini_al_dsp.c" #else #endif @@ -6292,6 +6463,24 @@ mal_uint32 mal_dsp_read_frames(mal_dsp* pDSP, mal_uint32 frameCount, void* pFram + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Miscellaneous Helpers +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void mal_blend_f32(float* pOut, float* pInA, float* pInB, float factor, mal_uint32 channels) +{ + for (mal_uint32 i = 0; i < channels; ++i) { + pOut[i] = mal_mix_f32(pInA[i], pInB[i], factor); + } +} + + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //