diff --git a/mini_al.h b/mini_al.h index 18be6dad..e032fa97 100644 --- a/mini_al.h +++ b/mini_al.h @@ -689,6 +689,34 @@ typedef struct } mal_timer; + +typedef struct mal_format_converter mal_format_converter; +typedef mal_uint32 (* mal_format_converter_read_proc) (mal_format_converter* pConverter, mal_uint32 frameCount, void* pFramesOut, void* pUserData); +typedef mal_uint32 (* mal_format_converter_read_separated_proc)(mal_format_converter* pConverter, mal_uint32 frameCount, void** ppSamplesOut, void* pUserData); + +typedef struct +{ + mal_format formatIn; + mal_format formatOut; + mal_uint32 channels; + mal_stream_format streamFormatIn; + mal_stream_format streamFormatOut; + mal_dither_mode ditherMode; +} mal_format_converter_config; + +struct mal_format_converter +{ + mal_format_converter_config config; + mal_format_converter_read_proc onRead; + mal_format_converter_read_separated_proc onReadSeparated; + void* pUserData; + void (* onConvertPCM)(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode); + void (* onInterleavePCM)(void* dst, const void** src, mal_uint64 frameCount, mal_uint32 channels); + void (* onDeinterleavePCM)(void** dst, const void* src, mal_uint64 frameCount, mal_uint32 channels); +}; + + + typedef struct mal_src mal_src; typedef mal_uint32 (* mal_src_read_proc)(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut, void* pUserData); // Returns the number of frames that were read. @@ -758,12 +786,13 @@ struct mal_dsp mal_dsp_config config; mal_dsp_read_proc onRead; void* pUserDataForOnRead; - mal_src src; // For sample rate conversion. + mal_format_converter formatConverter; + mal_src src; // For sample rate conversion. 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. + mal_bool32 isPassthrough : 1; // <-- Will be set to true when the DSP pipeline is an optimized passthrough. }; @@ -1642,6 +1671,8 @@ mal_uint32 mal_device_get_buffer_size_in_bytes(mal_device* pDevice); // Thread Safety: SAFE // This is API is pure. mal_uint32 mal_get_sample_size_in_bytes(mal_format format); +static inline mal_uint32 mal_get_bytes_per_sample(mal_format format) { return mal_get_sample_size_in_bytes(format); } +static inline mal_uint32 mal_get_bytes_per_frame(mal_format format, mal_uint32 channels) { return mal_get_bytes_per_sample(format) * channels; } // Helper function for initializing a mal_context_config object. mal_context_config mal_context_config_init(mal_log_proc onLog); @@ -1747,33 +1778,6 @@ void mal_get_standard_channel_map(mal_standard_channel_map standardChannelMap, m // /////////////////////////////////////////////////////////////////////////////// -typedef struct mal_format_converter mal_format_converter; - -// Callback for reading input data for the format converter. -typedef mal_uint32 (* mal_format_converter_read_proc) (mal_format_converter* pConverter, mal_uint32 frameCount, void* pFramesOut, void* pUserData); -typedef mal_uint32 (* mal_format_converter_read_separated_proc)(mal_format_converter* pConverter, mal_uint32 frameCount, void** ppSamplesOut, void* pUserData); - -typedef struct -{ - mal_format formatIn; - mal_format formatOut; - mal_uint32 channels; - mal_stream_format streamFormatIn; - mal_stream_format streamFormatOut; - mal_dither_mode ditherMode; -} mal_format_converter_config; - -struct mal_format_converter -{ - mal_format_converter_config config; - mal_format_converter_read_proc onRead; - mal_format_converter_read_separated_proc onReadSeparated; - void* pUserData; - void (* onConvertPCM)(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode); - void (* onInterleavePCM)(void* dst, const void** src, mal_uint64 frameCount, mal_uint32 channels); - void (* onDeinterleavePCM)(void** dst, const void* src, mal_uint64 frameCount, mal_uint32 channels); -}; - // Initializes a format converter. mal_result mal_format_converter_init(const mal_format_converter_config* pConfig, mal_format_converter_read_proc onRead, void* pUserData, mal_format_converter* pConverter); @@ -15968,9 +15972,9 @@ void mal_pcm_interleave_s24__reference(void* dst, const void** src, mal_uint64 f for (iFrame = 0; iFrame < frameCount; iFrame += 1) { mal_uint32 iChannel; for (iChannel = 0; iChannel < channels; iChannel += 1) { - dst8[iFrame*3*channels + iChannel + 0] = src8[iChannel][iFrame*3 + 0]; - dst8[iFrame*3*channels + iChannel + 1] = src8[iChannel][iFrame*3 + 1]; - dst8[iFrame*3*channels + iChannel + 2] = src8[iChannel][iFrame*3 + 2]; + dst8[iFrame*3*channels + iChannel*3 + 0] = src8[iChannel][iFrame*3 + 0]; + dst8[iFrame*3*channels + iChannel*3 + 1] = src8[iChannel][iFrame*3 + 1]; + dst8[iFrame*3*channels + iChannel*3 + 2] = src8[iChannel][iFrame*3 + 2]; } } } @@ -15999,9 +16003,9 @@ void mal_pcm_deinterleave_s24__reference(void** dst, const void* src, mal_uint64 for (iFrame = 0; iFrame < frameCount; iFrame += 1) { mal_uint32 iChannel; for (iChannel = 0; iChannel < channels; iChannel += 1) { - dst8[iChannel][iFrame*3 + 0] = src8[iFrame*3*channels + iChannel + 0]; - dst8[iChannel][iFrame*3 + 1] = src8[iFrame*3*channels + iChannel + 1]; - dst8[iChannel][iFrame*3 + 2] = src8[iFrame*3*channels + iChannel + 2]; + dst8[iChannel][iFrame*3 + 0] = src8[iFrame*3*channels + iChannel*3 + 0]; + dst8[iChannel][iFrame*3 + 1] = src8[iFrame*3*channels + iChannel*3 + 1]; + dst8[iChannel][iFrame*3 + 2] = src8[iFrame*3*channels + iChannel*3 + 2]; } } } @@ -16748,6 +16752,11 @@ mal_uint64 mal_format_converter_read_frames(mal_format_converter* pConverter, ma mal_uint8 tempSamplesOfOutFormat[MAL_MAX_CHANNELS][MAL_MAX_PCM_SAMPLE_SIZE_IN_BYTES * 128]; mal_assert(sizeof(tempSamplesOfOutFormat[0]) <= 0xFFFFFFFFF); + void* ppTempSampleOfOutFormat[MAL_MAX_CHANNELS]; + for (mal_uint32 i = 0; i < pConverter->config.channels; ++i) { + ppTempSampleOfOutFormat[i] = &tempSamplesOfOutFormat[i]; + } + mal_uint32 maxFramesToReadAtATime = sizeof(tempSamplesOfOutFormat[0]) / sampleSizeIn; while (totalFramesRead < frameCount) { @@ -16761,7 +16770,7 @@ mal_uint64 mal_format_converter_read_frames(mal_format_converter* pConverter, ma if (pConverter->config.formatIn == pConverter->config.formatOut) { // Only interleaving. - framesJustRead = (mal_uint32)pConverter->onReadSeparated(pConverter, (mal_uint32)framesToReadRightNow, (void**)tempSamplesOfOutFormat, pConverter->pUserData); + framesJustRead = (mal_uint32)pConverter->onReadSeparated(pConverter, (mal_uint32)framesToReadRightNow, ppTempSampleOfOutFormat, pConverter->pUserData); if (framesJustRead == 0) { break; } @@ -16769,7 +16778,13 @@ mal_uint64 mal_format_converter_read_frames(mal_format_converter* pConverter, ma // Interleaving + Conversion. Convert first, then interleave. mal_uint8 tempSamplesOfInFormat[MAL_MAX_CHANNELS][MAL_MAX_PCM_SAMPLE_SIZE_IN_BYTES * 128]; - framesJustRead = (mal_uint32)pConverter->onReadSeparated(pConverter, (mal_uint32)framesToReadRightNow, (void**)tempSamplesOfInFormat, pConverter->pUserData); + void* ppTempSampleOfInFormat[MAL_MAX_CHANNELS]; + for (mal_uint32 i = 0; i < pConverter->config.channels; ++i) { + ppTempSampleOfInFormat[i] = &tempSamplesOfInFormat[i]; + } + + + framesJustRead = (mal_uint32)pConverter->onReadSeparated(pConverter, (mal_uint32)framesToReadRightNow, ppTempSampleOfInFormat, pConverter->pUserData); if (framesJustRead == 0) { break; } @@ -16779,7 +16794,7 @@ mal_uint64 mal_format_converter_read_frames(mal_format_converter* pConverter, ma } } - pConverter->onInterleavePCM(pNextFramesOut, (void**)tempSamplesOfOutFormat, framesJustRead, pConverter->config.channels); + pConverter->onInterleavePCM(pNextFramesOut, ppTempSampleOfOutFormat, framesJustRead, pConverter->config.channels); totalFramesRead += framesJustRead; pNextFramesOut += framesJustRead * frameSizeIn; @@ -16854,7 +16869,7 @@ mal_uint64 mal_format_converter_read_frames_separated(mal_format_converter* pCon framesToReadRightNow = 0xFFFFFFFF; } - mal_uint32 framesJustRead = (mal_uint32)pConverter->onReadSeparated(pConverter, (mal_uint32)framesToReadRightNow, (void**)ppNextSamplesOut, pConverter->pUserData); + mal_uint32 framesJustRead = (mal_uint32)pConverter->onReadSeparated(pConverter, (mal_uint32)framesToReadRightNow, ppNextSamplesOut, pConverter->pUserData); if (framesJustRead == 0) { break; } @@ -16878,7 +16893,7 @@ mal_uint64 mal_format_converter_read_frames_separated(mal_format_converter* pCon framesToReadRightNow = maxFramesToReadAtATime; } - mal_uint32 framesJustRead = (mal_uint32)pConverter->onReadSeparated(pConverter, (mal_uint32)framesToReadRightNow, (void**)ppNextSamplesOut, pConverter->pUserData); + mal_uint32 framesJustRead = (mal_uint32)pConverter->onReadSeparated(pConverter, (mal_uint32)framesToReadRightNow, ppNextSamplesOut, pConverter->pUserData); if (framesJustRead == 0) { break; } diff --git a/tests/mal_test_0.c b/tests/mal_test_0.c index 9242e876..ff8e0326 100644 --- a/tests/mal_test_0.c +++ b/tests/mal_test_0.c @@ -443,7 +443,7 @@ int do_format_conversion_test(mal_format formatIn, mal_format formatOut) onConvertPCM(pConvertedData, pBaseData, (mal_uint32)benchmarkFrameCount, mal_dither_mode_none); result = mal_pcm_compare(pBenchmarkData, pConvertedData, benchmarkFrameCount, formatOut, allowedDifference); if (result == 0) { - printf("PASSED.\n"); + printf("PASSED\n"); } } else { printf("FAILED. Out of memory.\n"); @@ -648,6 +648,294 @@ int do_format_conversion_tests() } +int compare_interleaved_and_separated_buffers(const void* interleaved, const void** separated, mal_uint32 frameCount, mal_uint32 channels, mal_format format) +{ + mal_uint32 bytesPerSample = mal_get_sample_size_in_bytes(format); + + const mal_uint8* interleaved8 = (const mal_uint8*)interleaved; + const mal_uint8** separated8 = (const mal_uint8**)separated; + + for (mal_uint32 iFrame = 0; iFrame < frameCount; iFrame += 1) { + const mal_uint8* interleavedFrame = interleaved8 + iFrame*channels*bytesPerSample; + + for (mal_uint32 iChannel = 0; iChannel < channels; iChannel += 1) { + const mal_uint8* separatedFrame = separated8[iChannel] + iFrame*bytesPerSample; + + int result = memcmp(interleavedFrame + iChannel*bytesPerSample, separatedFrame, bytesPerSample); + if (result != 0) { + return -1; + } + } + } + + // Getting here means nothing failed. + return 0; +} + +int do_interleaving_test(mal_format format) +{ + // This test is simple. We start with a deinterleaved buffer. We then test interleaving. Then we deinterleave the interleaved buffer + // and compare that the original. It should be bit-perfect. We do this for all channel counts. + + int result = 0; + + switch (format) + { + case mal_format_u8: + { + mal_uint8 src [MAL_MAX_CHANNELS][64]; + mal_uint8 dst [MAL_MAX_CHANNELS][64]; + mal_uint8 dsti[MAL_MAX_CHANNELS*64]; + void* ppSrc[MAL_MAX_CHANNELS]; + void* ppDst[MAL_MAX_CHANNELS]; + + mal_uint32 frameCount = mal_countof(src[0]); + mal_uint32 channelCount = mal_countof(src); + for (mal_uint32 iChannel = 0; iChannel < channelCount; iChannel += 1) { + for (mal_uint32 iFrame = 0; iFrame < frameCount; iFrame += 1) { + src[iChannel][iFrame] = (mal_uint8)iChannel; + } + + ppSrc[iChannel] = &src[iChannel]; + ppDst[iChannel] = &dst[iChannel]; + } + + // Now test every channel count. + for (mal_uint32 i = 0; i < channelCount; ++i) { + mal_uint32 channelCountForThisIteration = i + 1; + + // Interleave. + mal_pcm_interleave_u8__reference(dsti, ppSrc, frameCount, channelCountForThisIteration); + if (compare_interleaved_and_separated_buffers(dsti, ppSrc, frameCount, channelCountForThisIteration, format) != 0) { + printf("FAILED. Separated to Interleaved (Channels = %u)\n", i); + result = -1; + break; + } + + // Deinterleave. + mal_pcm_deinterleave_u8__reference(ppDst, dsti, frameCount, channelCountForThisIteration); + if (compare_interleaved_and_separated_buffers(dsti, ppDst, frameCount, channelCountForThisIteration, format) != 0) { + printf("FAILED. Interleaved to Separated (Channels = %u)\n", i); + result = -1; + break; + } + } + } break; + + case mal_format_s16: + { + mal_int16 src [MAL_MAX_CHANNELS][64]; + mal_int16 dst [MAL_MAX_CHANNELS][64]; + mal_int16 dsti[MAL_MAX_CHANNELS*64]; + void* ppSrc[MAL_MAX_CHANNELS]; + void* ppDst[MAL_MAX_CHANNELS]; + + mal_uint32 frameCount = mal_countof(src[0]); + mal_uint32 channelCount = mal_countof(src); + for (mal_uint32 iChannel = 0; iChannel < channelCount; iChannel += 1) { + for (mal_uint32 iFrame = 0; iFrame < frameCount; iFrame += 1) { + src[iChannel][iFrame] = (mal_int16)iChannel; + } + + ppSrc[iChannel] = &src[iChannel]; + ppDst[iChannel] = &dst[iChannel]; + } + + // Now test every channel count. + for (mal_uint32 i = 0; i < channelCount; ++i) { + mal_uint32 channelCountForThisIteration = i + 1; + + // Interleave. + mal_pcm_interleave_s16__reference(dsti, ppSrc, frameCount, channelCountForThisIteration); + if (compare_interleaved_and_separated_buffers(dsti, ppSrc, frameCount, channelCountForThisIteration, format) != 0) { + printf("FAILED. Separated to Interleaved (Channels = %u)\n", i); + result = -1; + break; + } + + // Deinterleave. + mal_pcm_deinterleave_s16__reference(ppDst, dsti, frameCount, channelCountForThisIteration); + if (compare_interleaved_and_separated_buffers(dsti, ppDst, frameCount, channelCountForThisIteration, format) != 0) { + printf("FAILED. Interleaved to Separated (Channels = %u)\n", i); + result = -1; + break; + } + } + } break; + + case mal_format_s24: + { + mal_uint8 src [MAL_MAX_CHANNELS][64*3]; + mal_uint8 dst [MAL_MAX_CHANNELS][64*3]; + mal_uint8 dsti[MAL_MAX_CHANNELS*64*3]; + void* ppSrc[MAL_MAX_CHANNELS]; + void* ppDst[MAL_MAX_CHANNELS]; + + mal_uint32 frameCount = mal_countof(src[0])/3; + mal_uint32 channelCount = mal_countof(src); + for (mal_uint32 iChannel = 0; iChannel < channelCount; iChannel += 1) { + for (mal_uint32 iFrame = 0; iFrame < frameCount; iFrame += 1) { + src[iChannel][iFrame*3 + 0] = (mal_uint8)iChannel; + src[iChannel][iFrame*3 + 1] = (mal_uint8)iChannel; + src[iChannel][iFrame*3 + 2] = (mal_uint8)iChannel; + } + + ppSrc[iChannel] = &src[iChannel]; + ppDst[iChannel] = &dst[iChannel]; + } + + // Now test every channel count. + for (mal_uint32 i = 0; i < channelCount; ++i) { + mal_uint32 channelCountForThisIteration = i + 1; + + // Interleave. + mal_pcm_interleave_s24__reference(dsti, ppSrc, frameCount, channelCountForThisIteration); + if (compare_interleaved_and_separated_buffers(dsti, ppSrc, frameCount, channelCountForThisIteration, format) != 0) { + printf("FAILED. Separated to Interleaved (Channels = %u)\n", channelCountForThisIteration); + result = -1; + break; + } + + // Deinterleave. + mal_pcm_deinterleave_s24__reference(ppDst, dsti, frameCount, channelCountForThisIteration); + if (compare_interleaved_and_separated_buffers(dsti, ppDst, frameCount, channelCountForThisIteration, format) != 0) { + printf("FAILED. Interleaved to Separated (Channels = %u)\n", channelCountForThisIteration); + result = -1; + break; + } + } + } break; + + case mal_format_s32: + { + mal_int32 src [MAL_MAX_CHANNELS][64]; + mal_int32 dst [MAL_MAX_CHANNELS][64]; + mal_int32 dsti[MAL_MAX_CHANNELS*64]; + void* ppSrc[MAL_MAX_CHANNELS]; + void* ppDst[MAL_MAX_CHANNELS]; + + mal_uint32 frameCount = mal_countof(src[0]); + mal_uint32 channelCount = mal_countof(src); + for (mal_uint32 iChannel = 0; iChannel < channelCount; iChannel += 1) { + for (mal_uint32 iFrame = 0; iFrame < frameCount; iFrame += 1) { + src[iChannel][iFrame] = (mal_int32)iChannel; + } + + ppSrc[iChannel] = &src[iChannel]; + ppDst[iChannel] = &dst[iChannel]; + } + + // Now test every channel count. + for (mal_uint32 i = 0; i < channelCount; ++i) { + mal_uint32 channelCountForThisIteration = i + 1; + + // Interleave. + mal_pcm_interleave_s32__reference(dsti, ppSrc, frameCount, channelCountForThisIteration); + if (compare_interleaved_and_separated_buffers(dsti, ppSrc, frameCount, channelCountForThisIteration, format) != 0) { + printf("FAILED. Separated to Interleaved (Channels = %u)\n", i); + result = -1; + break; + } + + // Deinterleave. + mal_pcm_deinterleave_s32__reference(ppDst, dsti, frameCount, channelCountForThisIteration); + if (compare_interleaved_and_separated_buffers(dsti, ppDst, frameCount, channelCountForThisIteration, format) != 0) { + printf("FAILED. Interleaved to Separated (Channels = %u)\n", i); + result = -1; + break; + } + } + } break; + + case mal_format_f32: + { + float src [MAL_MAX_CHANNELS][64]; + float dst [MAL_MAX_CHANNELS][64]; + float dsti[MAL_MAX_CHANNELS*64]; + void* ppSrc[MAL_MAX_CHANNELS]; + void* ppDst[MAL_MAX_CHANNELS]; + + mal_uint32 frameCount = mal_countof(src[0]); + mal_uint32 channelCount = mal_countof(src); + for (mal_uint32 iChannel = 0; iChannel < channelCount; iChannel += 1) { + for (mal_uint32 iFrame = 0; iFrame < frameCount; iFrame += 1) { + src[iChannel][iFrame] = (float)iChannel; + } + + ppSrc[iChannel] = &src[iChannel]; + ppDst[iChannel] = &dst[iChannel]; + } + + // Now test every channel count. + for (mal_uint32 i = 0; i < channelCount; ++i) { + mal_uint32 channelCountForThisIteration = i + 1; + + // Interleave. + mal_pcm_interleave_f32__reference(dsti, ppSrc, frameCount, channelCountForThisIteration); + if (compare_interleaved_and_separated_buffers(dsti, ppSrc, frameCount, channelCountForThisIteration, format) != 0) { + printf("FAILED. Separated to Interleaved (Channels = %u)\n", i); + result = -1; + break; + } + + // Deinterleave. + mal_pcm_deinterleave_f32__reference(ppDst, dsti, frameCount, channelCountForThisIteration); + if (compare_interleaved_and_separated_buffers(dsti, ppDst, frameCount, channelCountForThisIteration, format) != 0) { + printf("FAILED. Interleaved to Separated (Channels = %u)\n", i); + result = -1; + break; + } + } + } break; + + default: + { + printf("Unknown format."); + result = -1; + } break; + } + + + if (result == 0) { + printf("PASSED\n"); + } + + return result; +} + +int do_interleaving_tests() +{ + int result = 0; + + printf("u8... "); + if (do_interleaving_test(mal_format_u8) != 0) { + result = -1; + } + + printf("s16... "); + if (do_interleaving_test(mal_format_s16) != 0) { + result = -1; + } + + printf("s24... "); + if (do_interleaving_test(mal_format_s24) != 0) { + result = -1; + } + + printf("s32... "); + if (do_interleaving_test(mal_format_s32) != 0) { + result = -1; + } + + printf("f32... "); + if (do_interleaving_test(mal_format_f32) != 0) { + result = -1; + } + + return result; +} + + int do_backend_test(mal_backend backend) { mal_result result = MAL_SUCCESS; @@ -884,6 +1172,15 @@ int main(int argc, char** argv) hasErrorOccurred = MAL_TRUE; } printf("=== END TESTING FORMAT CONVERSION ===\n"); + + printf("\n"); + + printf("=== TESTING INTERLEAVING/DEINTERLEAVING ===\n"); + result = do_interleaving_tests(); + if (result < 0) { + hasErrorOccurred = MAL_TRUE; + } + printf("=== END TESTING INTERLEAVING/DEINTERLEAVING ===\n"); printf("\n");