diff --git a/mini_al.h b/mini_al.h index 507dcda2..5463f3d2 100644 --- a/mini_al.h +++ b/mini_al.h @@ -809,6 +809,7 @@ typedef struct mal_uint32 sampleRateOut; mal_channel channelMapOut[MAL_MAX_CHANNELS]; mal_channel_mix_mode channelMixMode; + mal_src_algorithm srcAlgorithm; mal_uint32 cacheSizeInFrames; // Applications should set this to 0 for now. } mal_dsp_config; @@ -821,11 +822,16 @@ struct mal_dsp mal_format_converter formatConverterOut; // For converting data to the requested output format. Used as the final step in the processing pipeline. mal_channel_router channelRouter; // For channel conversion. mal_src src; // For sample rate conversion. + mal_bool32 isPreFormatConversionRequired : 1; + mal_bool32 isPostFormatConversionRequired : 1; + mal_bool32 isChannelRoutingRequired : 1; + mal_bool32 isSRCRequired : 1; + mal_bool32 isChannelRoutingAtStart : 1; + mal_bool32 isPassthrough : 1; // <-- Will be set to true when the DSP pipeline is an optimized passthrough. + mal_channel channelMapInPostMix[MAL_MAX_CHANNELS]; // <-- When mixing, new channels may need to be created. This represents the channel map after mixing. mal_channel channelShuffleTable[MAL_MAX_CHANNELS]; mal_bool32 isChannelMappingRequired : 1; - mal_bool32 isSRCRequired : 1; - mal_bool32 isPassthrough : 1; // <-- Will be set to true when the DSP pipeline is an optimized passthrough. }; @@ -1861,7 +1867,7 @@ mal_result mal_channel_router_init_deinterleaved(const mal_channel_router_config mal_uint64 mal_channel_router_read_frames_deinterleaved(mal_channel_router* pRouter, mal_uint64 frameCount, void** ppSamplesOut); // Helper for initializing a channel router config. -mal_channel_router_config mal_channel_router_config_init(mal_uint32 channelsIn, mal_channel channelMapIn[MAL_MAX_CHANNELS], mal_uint32 channelsOut, mal_channel channelMapOut[MAL_MAX_CHANNELS], mal_channel_mix_mode mixingMode); +mal_channel_router_config mal_channel_router_config_init(mal_uint32 channelsIn, const mal_channel channelMapIn[MAL_MAX_CHANNELS], mal_uint32 channelsOut, const mal_channel channelMapOut[MAL_MAX_CHANNELS], mal_channel_mix_mode mixingMode); /////////////////////////////////////////////////////////////////////////////// @@ -1894,6 +1900,7 @@ mal_uint64 mal_src_read_frames(mal_src* pSRC, mal_uint64 frameCount, void* pFram // pipelines. The problem, however, is that sometimes you need those last few samples (such as if you're doing a bulk conversion // of a static file). Enabling flushing will fix this for you. mal_uint64 mal_src_read_frames_ex(mal_src* pSRC, mal_uint64 frameCount, void* pFramesOut, mal_bool32 flush); +mal_uint64 mal_src_read_frames_deinterleaved_ex(mal_src* pSRC, mal_uint64 frameCount, void** ppSamplesOut, mal_bool32 flush); @@ -17288,7 +17295,7 @@ float mal_calculate_channel_position_planar_weight(mal_channel channelPositionA, // - front/left (in) and back/left (out): 1 shared plane (left) // - front/left (in) and top/front/left (out): 2 shared planes (front and left) // - // We now have enough information to know how much audio the input speaker gives to each of it's output: + // We now have enough information to know how much audio the input speaker gives to each of it's outputs: // // volumeToGive = volumePerInputSpeakerPlane * sharedPlaneCount // @@ -17307,7 +17314,6 @@ float mal_calculate_channel_position_planar_weight(mal_channel channelPositionA, // front/left to back/left = (1/2 * 1) * (1/2 * 1) = 0.5*1.0 * 0.5*1.0 = 0.5*0.5 = 0.25 // front/left to top/front/left = (1/2 * 2) * (1/3 * 2) = 0.5*2.0 * 0.33*2.0 = 1.0*0.66 = 0.66 - mal_vec3 roomPosA = g_malDefaultChannelPositionsInRoom[channelPositionA]; mal_vec3 roomPosB = g_malDefaultChannelPositionsInRoom[channelPositionB]; @@ -17778,7 +17784,7 @@ mal_uint64 mal_channel_router_read_frames_deinterleaved(mal_channel_router* pRou return totalFramesRead; } -mal_channel_router_config mal_channel_router_config_init(mal_uint32 channelsIn, mal_channel channelMapIn[MAL_MAX_CHANNELS], mal_uint32 channelsOut, mal_channel channelMapOut[MAL_MAX_CHANNELS], mal_channel_mix_mode mixingMode) +mal_channel_router_config mal_channel_router_config_init(mal_uint32 channelsIn, const mal_channel channelMapIn[MAL_MAX_CHANNELS], mal_uint32 channelsOut, const mal_channel channelMapOut[MAL_MAX_CHANNELS], mal_channel_mix_mode mixingMode) { mal_channel_router_config config; mal_zero_object(&config); @@ -17969,6 +17975,22 @@ mal_uint64 mal_src_read_frames_ex(mal_src* pSRC, mal_uint64 frameCount, void* pF } } +mal_uint64 mal_src_read_frames_deinterleaved(mal_src* pSRC, mal_uint64 frameCount, void** ppSamplesOut) +{ + return mal_src_read_frames_deinterleaved_ex(pSRC, frameCount, ppSamplesOut, MAL_FALSE); +} + +mal_uint64 mal_src_read_frames_deinterleaved_ex(mal_src* pSRC, mal_uint64 frameCount, void** ppSamplesOut, mal_bool32 flush) +{ + (void)pSRC; + (void)frameCount; + (void)ppSamplesOut; + (void)flush; + + // TODO: Implement me. + return 0; +} + mal_uint64 mal_src_read_frames_passthrough(mal_src* pSRC, mal_uint64 frameCount, void* pFramesOut, mal_bool32 flush) { mal_assert(pSRC != NULL); @@ -18657,6 +18679,49 @@ void mal_dsp_mix_channels(float* pFramesOut, mal_uint32 channelsOut, const mal_c } +mal_uint32 mal_dsp__pre_format_converter_on_read(mal_format_converter* pConverter, mal_uint32 frameCount, void* pFramesOut, void* pUserData) +{ + (void)pConverter; + + mal_dsp* pDSP = (mal_dsp*)pUserData; + mal_assert(pDSP != NULL); + + return pDSP->onRead(pDSP, frameCount, pFramesOut, pDSP->pUserDataForOnRead); +} + +mal_uint32 mal_dsp__post_format_converter_on_read(mal_format_converter* pConverter, mal_uint32 frameCount, void* pFramesOut, void* pUserData) +{ + (void)pConverter; + + mal_dsp* pDSP = (mal_dsp*)pUserData; + mal_assert(pDSP != NULL); + + // When this version of this callback is used it means we're reading directly from the client. + mal_assert(pDSP->isPreFormatConversionRequired == MAL_FALSE); + mal_assert(pDSP->isChannelRoutingRequired == MAL_FALSE); + mal_assert(pDSP->isSRCRequired == MAL_FALSE); + + return pDSP->onRead(pDSP, frameCount, pFramesOut, pDSP->pUserDataForOnRead); +} + +mal_uint32 mal_dsp__post_format_converter_on_read_deinterleaved(mal_format_converter* pConverter, mal_uint32 frameCount, void** ppSamplesOut, void* pUserData) +{ + (void)pConverter; + + mal_dsp* pDSP = (mal_dsp*)pUserData; + mal_assert(pDSP != NULL); + + if (!pDSP->isChannelRoutingAtStart) { + return (mal_uint32)mal_channel_router_read_frames_deinterleaved(&pDSP->channelRouter, frameCount, ppSamplesOut); + } else { + if (pDSP->isSRCRequired) { + return (mal_uint32)mal_src_read_frames_deinterleaved_ex(&pDSP->src, frameCount, ppSamplesOut, MAL_FALSE); // TODO: Replace MAL_FALSE with a flush argument. + } else { + return (mal_uint32)mal_format_converter_read_frames_deinterleaved(&pDSP->formatConverterIn, frameCount, ppSamplesOut); + } + } +} + mal_uint32 mal_dsp__src_on_read(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut, void* pUserData) { (void)pSRC; @@ -18667,6 +18732,25 @@ mal_uint32 mal_dsp__src_on_read(mal_src* pSRC, mal_uint32 frameCount, void* pFra return pDSP->onRead(pDSP, frameCount, pFramesOut, pDSP->pUserDataForOnRead); } +mal_uint32 mal_dsp__channel_router_on_read(mal_channel_router* pRouter, mal_uint32 frameCount, void** ppSamplesOut, void* pUserData) +{ + (void)pRouter; + + mal_dsp* pDSP = (mal_dsp*)pUserData; + mal_assert(pDSP != NULL); + + // If the channel routing stage is at the front of the pipeline we read from the pre format converter. Otherwise we read from the sample rate converter. + if (pDSP->isChannelRoutingAtStart) { + return (mal_uint32)mal_format_converter_read_frames_deinterleaved(&pDSP->formatConverterIn, frameCount, ppSamplesOut); + } else { + if (pDSP->isSRCRequired) { + return (mal_uint32)mal_src_read_frames_deinterleaved_ex(&pDSP->src, frameCount, ppSamplesOut, MAL_FALSE); // TODO: Replace MAL_FALSE with a flush argument. + } else { + return (mal_uint32)mal_format_converter_read_frames_deinterleaved(&pDSP->formatConverterIn, frameCount, ppSamplesOut); + } + } +} + mal_result mal_dsp_init(const mal_dsp_config* pConfig, mal_dsp_read_proc onRead, void* pUserData, mal_dsp* pDSP) { if (pDSP == NULL) { @@ -18682,23 +18766,153 @@ mal_result mal_dsp_init(const mal_dsp_config* pConfig, mal_dsp_read_proc onRead, pDSP->config.cacheSizeInFrames = MAL_SRC_CACHE_SIZE_IN_FRAMES; } + // This is generally the pipeline used for data conversion. Note that this can actually change which is explained later. + // + // Pre Format Conversion -> Sample Rate Conversion -> Channel Routing -> Post Format Conversion + // + // Pre Format Conversion + // --------------------- + // This is where the sample data is converted to a format that's usable by the later stages in the pipeline. Input data + // is converted from the original input data to deinterleaved floating-point. + // + // Channel Routing + // --------------- + // Channel routing is where stereo is converted to 5.1, mono is converted to stereo, etc. This stage depends on the + // pre format conversion stage. + // + // Sample Rate Conversion + // ---------------------- + // Sample rate conversion depends on the pre format conversion stage and as the name implies performs sample rate conversion. + // + // Post Format Conversion + // ---------------------- + // This stage is where our deinterleaved floating-point data from the previous stages are converted to the requested output + // format. + // + // + // Optimizations + // ------------- + // Sometimes the conversion pipeline is rearranged for efficiency. The first obvious optimization is to eliminate unnecessary + // stages in the pipeline. When no channel routing nor sample rate conversion is necessary, the entire pipeline is optimized + // down to just this: + // + // Post Format Conversion + // + // When sample rate conversion is not unnecessary: + // + // Pre Format Conversion -> Channel Routing -> Post Format Conversion + // + // When channel routing is unnecessary: + // + // Pre Format Conversion -> Sample Rate Conversion -> Post Format Conversion + // + // A slightly less obvious optimization is used depending on whether or not we are increasing or decreasing the number of + // channels. Because everything in the pipeline works on a per-channel basis, the efficiency of the pipeline is directly + // proportionate to the number of channels that need to be processed. Therefore, it's can be more efficient to move the + // channel conversion stage to an earlier or later stage. When the channel count is being reduced, we move the channel + // conversion stage to the start of the pipeline so that later stages can work on a smaller number of channels at a time. + // Otherwise, we move the channel conversion stage to the end of the pipeline. When reducing the channel count, the pipeline + // will look like this: + // + // Pre Format Conversion -> Channel Routing -> Sample Rate Conversion -> Post Format Conversion + + // First we need to determin what's required and what's not. if (pConfig->sampleRateIn != pConfig->sampleRateOut) { pDSP->isSRCRequired = MAL_TRUE; + } + if (pConfig->channelsIn != pConfig->channelsOut || !mal_channel_map_equal(pConfig->channelsIn, pConfig->channelMapIn, pConfig->channelMapOut)) { + pDSP->isChannelRoutingRequired = MAL_TRUE; + } - mal_src_config srcConfig; - srcConfig.sampleRateIn = pConfig->sampleRateIn; - srcConfig.sampleRateOut = pConfig->sampleRateOut; - srcConfig.formatIn = pConfig->formatIn; - srcConfig.formatOut = mal_format_f32; - srcConfig.channels = pConfig->channelsIn; - srcConfig.algorithm = mal_src_algorithm_linear; - srcConfig.cacheSizeInFrames = pConfig->cacheSizeInFrames; - mal_result result = mal_src_init(&srcConfig, mal_dsp__src_on_read, pDSP, &pDSP->src); + // If neither a sample rate conversion nor channel conversion is necessary we can skip the pre format conversion. + if (!pDSP->isSRCRequired && !pDSP->isChannelRoutingRequired) { + // We don't need a pre format conversion stage, but we may still need a post format conversion stage. + if (pConfig->formatIn != pConfig->formatOut) { + pDSP->isPostFormatConversionRequired = MAL_TRUE; + } + } else { + pDSP->isPreFormatConversionRequired = MAL_TRUE; + pDSP->isPostFormatConversionRequired = MAL_TRUE; + } + + // Use a passthrough if none of the stages are being used. + if (!pDSP->isPreFormatConversionRequired && !pDSP->isPostFormatConversionRequired && !pDSP->isChannelRoutingRequired && !pDSP->isSRCRequired) { + pDSP->isPassthrough = MAL_TRUE; + } + + // Move the channel conversion stage to the start of the pipeline if we are reducing the channel count. + if (pConfig->channelsOut < pConfig->channelsIn) { + pDSP->isChannelRoutingAtStart = MAL_TRUE; + } + + + // We always initialize every stage of the pipeline regardless of whether or not the stage is used because it simplifies + // a few things when it comes to dynamically changing properties post-initialization. + mal_result result = MAL_SUCCESS; + + // Pre format conversion. + { + mal_format_converter_config preFormatConverterConfig; + mal_zero_object(&preFormatConverterConfig); + preFormatConverterConfig.formatIn = pConfig->formatIn; + preFormatConverterConfig.formatOut = mal_format_f32; + preFormatConverterConfig.channels = pConfig->channelsIn; + preFormatConverterConfig.streamFormatIn = mal_stream_format_pcm; + preFormatConverterConfig.streamFormatOut = mal_stream_format_pcm; + result = mal_format_converter_init(&preFormatConverterConfig, mal_dsp__pre_format_converter_on_read, pDSP, &pDSP->formatConverterIn); if (result != MAL_SUCCESS) { return result; } } + // Post format conversion. The exact configuration for this depends on whether or not we are reading data directly from the client + // or from an earlier stage in the pipeline. + { + mal_format_converter_config postFormatConverterConfig; + mal_zero_object(&postFormatConverterConfig); + postFormatConverterConfig.formatIn = pConfig->formatIn; + postFormatConverterConfig.formatOut = pConfig->formatOut; + postFormatConverterConfig.channels = pConfig->channelsOut; + postFormatConverterConfig.streamFormatIn = mal_stream_format_pcm; + postFormatConverterConfig.streamFormatOut = mal_stream_format_pcm; + if (pDSP->isPreFormatConversionRequired) { + // Converting from an earlier stage in the pipeline which is always deinterleaved floating point. + postFormatConverterConfig.formatIn = mal_format_f32; + result = mal_format_converter_init_deinterleaved(&postFormatConverterConfig, mal_dsp__post_format_converter_on_read_deinterleaved, pDSP, &pDSP->formatConverterIn); + } else { + // Converting directly from the input client data. + result = mal_format_converter_init(&postFormatConverterConfig, mal_dsp__post_format_converter_on_read, pDSP, &pDSP->formatConverterIn); + } + if (result != MAL_SUCCESS) { + return result; + } + } + + + // SRC + mal_src_config srcConfig; + mal_zero_object(&srcConfig); + srcConfig.sampleRateIn = pConfig->sampleRateIn; + srcConfig.sampleRateOut = pConfig->sampleRateOut; + srcConfig.formatIn = pConfig->formatIn; + srcConfig.formatOut = mal_format_f32; + srcConfig.channels = pConfig->channelsIn; + srcConfig.algorithm = pConfig->srcAlgorithm; + srcConfig.cacheSizeInFrames = pConfig->cacheSizeInFrames; + result = mal_src_init(&srcConfig, mal_dsp__src_on_read, pDSP, &pDSP->src); + if (result != MAL_SUCCESS) { + return result; + } + + + // Channel conversion + mal_channel_router_config routerConfig = mal_channel_router_config_init(pConfig->channelsIn, pConfig->channelMapIn, pConfig->channelsOut, pConfig->channelMapOut, pConfig->channelMixMode); + result = mal_channel_router_init_deinterleaved(&routerConfig, mal_dsp__channel_router_on_read, pDSP, &pDSP->channelRouter); + if (result != MAL_SUCCESS) { + return result; + } + + pDSP->isChannelMappingRequired = MAL_FALSE; @@ -18787,7 +19001,7 @@ mal_result mal_dsp_refresh_sample_rate(mal_dsp* pDSP) srcConfig.formatIn = pDSP->config.formatIn; srcConfig.formatOut = mal_format_f32; srcConfig.channels = pDSP->config.channelsIn; - srcConfig.algorithm = mal_src_algorithm_linear; + srcConfig.algorithm = pDSP->config.srcAlgorithm; srcConfig.cacheSizeInFrames = pDSP->config.cacheSizeInFrames; mal_result result = mal_src_init(&srcConfig, mal_dsp__src_on_read, pDSP, &pDSP->src); if (result != MAL_SUCCESS) {