From de02f14283d217ded3a2cfd50de971bae3b8247f Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 17 Nov 2018 10:04:12 +0100 Subject: [PATCH] Updated mini_al --- src/external/mini_al.c | 1 - src/external/mini_al.h | 473 ++++++++++++++++++++++++++++++----------- 2 files changed, 347 insertions(+), 127 deletions(-) diff --git a/src/external/mini_al.c b/src/external/mini_al.c index eabb63654..0bde1180f 100644 --- a/src/external/mini_al.c +++ b/src/external/mini_al.c @@ -3,6 +3,5 @@ #define MINI_AL_IMPLEMENTATION #define MAL_NO_JACK #define MAL_NO_OPENAL -#define MAL_NO_SDL #define MAL_NO_NULL #include "mini_al.h" diff --git a/src/external/mini_al.h b/src/external/mini_al.h index 759662c7b..0372f3bb4 100644 --- a/src/external/mini_al.h +++ b/src/external/mini_al.h @@ -1,5 +1,5 @@ // Audio playback and capture library. Public domain. See "unlicense" statement at the end of this file. -// mini_al - v0.8.8 - 2018-09-14 +// mini_al - v0.8.10 - 2018-10-21 // // David Reid - davidreidsoftware@gmail.com @@ -1114,6 +1114,11 @@ void mal_pcm_f32_to_s24(void* pOut, const void* pIn, mal_uint64 count, mal_dithe void mal_pcm_f32_to_s32(void* pOut, const void* pIn, mal_uint64 count, mal_dither_mode ditherMode); void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_format formatIn, mal_uint64 sampleCount, mal_dither_mode ditherMode); +// Deinterleaves an interleaved buffer. +void mal_deinterleave_pcm_frames(mal_format format, mal_uint32 channels, mal_uint32 frameCount, const void* pInterleavedPCMFrames, void** ppDeinterleavedPCMFrames); + +// Interleaves a group of deinterleaved buffers. +void mal_interleave_pcm_frames(mal_format format, mal_uint32 channels, mal_uint32 frameCount, const void** ppDeinterleavedPCMFrames, void* pInterleavedPCMFrames); ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1629,6 +1634,7 @@ struct mal_context mal_proc pa_stream_get_sample_spec; mal_proc pa_stream_get_channel_map; mal_proc pa_stream_get_buffer_attr; + mal_proc pa_stream_set_buffer_attr; mal_proc pa_stream_get_device_name; mal_proc pa_stream_set_write_callback; mal_proc pa_stream_set_read_callback; @@ -4864,7 +4870,7 @@ mal_bool32 mal_device__get_current_frame__null(mal_device* pDevice, mal_uint32* mal_assert(pCurrentPos != NULL); *pCurrentPos = 0; - mal_uint64 currentFrameAbs = (mal_uint64)(mal_timer_get_time_in_seconds(&pDevice->null_device.timer) * pDevice->sampleRate) / pDevice->channels; + mal_uint64 currentFrameAbs = (mal_uint64)(mal_timer_get_time_in_seconds(&pDevice->null_device.timer) * pDevice->sampleRate); *pCurrentPos = (mal_uint32)(currentFrameAbs % pDevice->bufferSizeInFrames); return MAL_TRUE; @@ -4917,11 +4923,11 @@ mal_uint32 mal_device__wait_for_frames__null(mal_device* pDevice) while (!pDevice->null_device.breakFromMainLoop) { mal_uint32 framesAvailable = mal_device__get_available_frames__null(pDevice); - if (framesAvailable > 0) { + if (framesAvailable >= (pDevice->bufferSizeInFrames/pDevice->periods)) { return framesAvailable; } - mal_sleep(16); + mal_sleep(pDevice->bufferSizeInMilliseconds/pDevice->periods); } // We'll get here if the loop was terminated. Just return whatever's available. @@ -11784,6 +11790,7 @@ typedef mal_pa_stream_state_t (* mal_pa_stream_get_state_proc) typedef const mal_pa_sample_spec* (* mal_pa_stream_get_sample_spec_proc) (mal_pa_stream* s); typedef const mal_pa_channel_map* (* mal_pa_stream_get_channel_map_proc) (mal_pa_stream* s); typedef const mal_pa_buffer_attr* (* mal_pa_stream_get_buffer_attr_proc) (mal_pa_stream* s); +typedef mal_pa_operation* (* mal_pa_stream_set_buffer_attr_proc) (mal_pa_stream* s, const mal_pa_buffer_attr* attr, mal_pa_stream_success_cb_t cb, void* userdata); typedef const char* (* mal_pa_stream_get_device_name_proc) (mal_pa_stream* s); typedef void (* mal_pa_stream_set_write_callback_proc) (mal_pa_stream* s, mal_pa_stream_request_cb_t cb, void* userdata); typedef void (* mal_pa_stream_set_read_callback_proc) (mal_pa_stream* s, mal_pa_stream_request_cb_t cb, void* userdata); @@ -12305,6 +12312,10 @@ void mal_pulse_device_write_callback(mal_pa_stream* pStream, size_t sizeInBytes, mal_context* pContext = pDevice->pContext; mal_assert(pContext != NULL); +#ifdef MAL_DEBUG_OUTPUT + printf("[PulseAudio] write_callback: sizeInBytes=%d\n", (int)sizeInBytes); +#endif + size_t bytesRemaining = sizeInBytes; while (bytesRemaining > 0) { size_t bytesToReadFromClient = bytesRemaining; @@ -12319,19 +12330,35 @@ void mal_pulse_device_write_callback(mal_pa_stream* pStream, size_t sizeInBytes, return; } +#ifdef MAL_DEBUG_OUTPUT + printf(" bytesToReadFromClient=%d", (int)bytesToReadFromClient); +#endif + if (pBuffer != NULL && bytesToReadFromClient > 0) { mal_uint32 framesToReadFromClient = (mal_uint32)bytesToReadFromClient / (pDevice->internalChannels*mal_get_bytes_per_sample(pDevice->internalFormat)); if (framesToReadFromClient > 0) { mal_device__read_frames_from_client(pDevice, framesToReadFromClient, pBuffer); +#ifdef MAL_DEBUG_OUTPUT + printf(", framesToReadFromClient=%d\n", (int)framesToReadFromClient); +#endif + error = ((mal_pa_stream_write_proc)pContext->pulse.pa_stream_write)((mal_pa_stream*)pDevice->pulse.pStream, pBuffer, bytesToReadFromClient, NULL, 0, MAL_PA_SEEK_RELATIVE); if (error < 0) { mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to write data to the PulseAudio stream.", mal_result_from_pulse(error)); return; } + } else { +#ifdef MAL_DEBUG_OUTPUT + printf(", framesToReadFromClient=0\n"); +#endif } bytesRemaining -= bytesToReadFromClient; + } else { +#ifdef MAL_DEBUG_OUTPUT + printf(", framesToReadFromClient=0\n"); +#endif } } } @@ -12460,6 +12487,7 @@ mal_result mal_device_init__pulse(mal_context* pContext, mal_device_type type, c mal_pa_sample_spec ss; mal_pa_channel_map cmap; mal_pa_buffer_attr attr; + mal_pa_stream_flags_t streamFlags; const mal_pa_sample_spec* pActualSS = NULL; const mal_pa_channel_map* pActualCMap = NULL; @@ -12533,53 +12561,6 @@ mal_result mal_device_init__pulse(mal_context* pContext, mal_device_type type, c ((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); } - -#if 0 - mal_pa_sample_spec deviceSS; - mal_pa_channel_map deviceCMap; - if (type == mal_device_type_playback) { - deviceSS = sinkInfo.sample_spec; - deviceCMap = sinkInfo.channel_map; - } else { - deviceSS = sourceInfo.sample_spec; - deviceCMap = sourceInfo.channel_map; - } - - if (pDevice->usingDefaultFormat) { - ss.format = deviceSS.format; - } else { - ss.format = mal_format_to_pulse(pConfig->format); - } - if (ss.format == MAL_PA_SAMPLE_INVALID) { - ss.format = MAL_PA_SAMPLE_S16LE; - } - - if (pDevice->usingDefaultChannels) { - ss.channels = deviceSS.channels; - } else { - ss.channels = pConfig->channels; - } - - if (pDevice->usingDefaultSampleRate) { - ss.rate = deviceSS.rate; - } else { - ss.rate = pConfig->sampleRate; - } - - - if (pDevice->usingDefaultChannelMap) { - cmap = deviceCMap; - } else { - cmap.channels = pConfig->channels; - for (mal_uint32 iChannel = 0; iChannel < pConfig->channels; ++iChannel) { - cmap.map[iChannel] = mal_channel_position_to_pulse(pConfig->channelMap[iChannel]); - } - - if (((mal_pa_channel_map_valid_proc)pContext->pulse.pa_channel_map_valid)(&cmap) == 0 || ((mal_pa_channel_map_compatible_proc)pContext->pulse.pa_channel_map_compatible)(&cmap, &ss) == 0) { - ((mal_pa_channel_map_init_extend_proc)pContext->pulse.pa_channel_map_init_extend)(&cmap, ss.channels, MAL_PA_CHANNEL_MAP_DEFAULT); // The channel map is invalid, so just fall back to the default. - } - } -#else if (type == mal_device_type_playback) { ss = sinkInfo.sample_spec; cmap = sinkInfo.channel_map; @@ -12587,7 +12568,7 @@ mal_result mal_device_init__pulse(mal_context* pContext, mal_device_type type, c ss = sourceInfo.sample_spec; cmap = sourceInfo.channel_map; } -#endif + // Buffer size. bufferSizeInFrames = pDevice->bufferSizeInFrames; @@ -12606,10 +12587,14 @@ mal_result mal_device_init__pulse(mal_context* pContext, mal_device_type type, c } attr.maxlength = bufferSizeInFrames * mal_get_bytes_per_sample(mal_format_from_pulse(ss.format))*ss.channels; - attr.tlength = attr.maxlength / pConfig->periods; + attr.tlength = attr.maxlength; attr.prebuf = (mal_uint32)-1; - attr.minreq = attr.tlength; - attr.fragsize = attr.tlength; + attr.minreq = attr.maxlength / pConfig->periods; + attr.fragsize = attr.maxlength / pConfig->periods; + +#ifdef MAL_DEBUG_OUTPUT + printf("[PulseAudio] attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; bufferSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, bufferSizeInFrames); +#endif char streamName[256]; if (pConfig->pulse.pStreamName != NULL) { @@ -12628,11 +12613,15 @@ mal_result mal_device_init__pulse(mal_context* pContext, mal_device_type type, c } - + streamFlags = MAL_PA_STREAM_START_CORKED; + if (dev != NULL) { + streamFlags |= MAL_PA_STREAM_DONT_MOVE | MAL_PA_STREAM_FIX_FORMAT | MAL_PA_STREAM_FIX_RATE | MAL_PA_STREAM_FIX_CHANNELS; + } + if (type == mal_device_type_playback) { - error = ((mal_pa_stream_connect_playback_proc)pContext->pulse.pa_stream_connect_playback)((mal_pa_stream*)pDevice->pulse.pStream, dev, &attr, MAL_PA_STREAM_START_CORKED, NULL, NULL); + error = ((mal_pa_stream_connect_playback_proc)pContext->pulse.pa_stream_connect_playback)((mal_pa_stream*)pDevice->pulse.pStream, dev, &attr, streamFlags, NULL, NULL); } else { - error = ((mal_pa_stream_connect_record_proc)pContext->pulse.pa_stream_connect_record)((mal_pa_stream*)pDevice->pulse.pStream, dev, &attr, MAL_PA_STREAM_START_CORKED); + error = ((mal_pa_stream_connect_record_proc)pContext->pulse.pa_stream_connect_record)((mal_pa_stream*)pDevice->pulse.pStream, dev, &attr, streamFlags); } if (error != MAL_PA_OK) { @@ -12652,6 +12641,21 @@ mal_result mal_device_init__pulse(mal_context* pContext, mal_device_type type, c // Internal format. pActualSS = ((mal_pa_stream_get_sample_spec_proc)pContext->pulse.pa_stream_get_sample_spec)((mal_pa_stream*)pDevice->pulse.pStream); if (pActualSS != NULL) { + // If anything has changed between the requested and the actual sample spec, we need to update the buffer. + if (ss.format != pActualSS->format || ss.channels != pActualSS->channels || ss.rate != pActualSS->rate) { + attr.maxlength = bufferSizeInFrames * mal_get_bytes_per_sample(mal_format_from_pulse(pActualSS->format))*pActualSS->channels; + attr.tlength = attr.maxlength; + attr.prebuf = (mal_uint32)-1; + attr.minreq = attr.maxlength / pConfig->periods; + attr.fragsize = attr.maxlength / pConfig->periods; + + pOP = ((mal_pa_stream_set_buffer_attr_proc)pContext->pulse.pa_stream_set_buffer_attr)((mal_pa_stream*)pDevice->pulse.pStream, &attr, NULL, NULL); + if (pOP != NULL) { + mal_device__wait_for_operation__pulse(pDevice, pOP); + ((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); + } + } + ss = *pActualSS; } @@ -12680,6 +12684,10 @@ mal_result mal_device_init__pulse(mal_context* pContext, mal_device_type type, c pDevice->bufferSizeInFrames = attr.maxlength / (mal_get_bytes_per_sample(pDevice->internalFormat)*pDevice->internalChannels); pDevice->periods = attr.maxlength / attr.tlength; +#ifdef MAL_DEBUG_OUTPUT + printf("[PulseAudio] actual attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; pDevice->bufferSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDevice->bufferSizeInFrames); +#endif + // Grab the name of the device if we can. dev = ((mal_pa_stream_get_device_name_proc)pContext->pulse.pa_stream_get_device_name)((mal_pa_stream*)pDevice->pulse.pStream); @@ -12930,6 +12938,7 @@ mal_result mal_context_init__pulse(mal_context* pContext) pContext->pulse.pa_stream_get_sample_spec = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_get_sample_spec"); pContext->pulse.pa_stream_get_channel_map = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_get_channel_map"); pContext->pulse.pa_stream_get_buffer_attr = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_get_buffer_attr"); + pContext->pulse.pa_stream_set_buffer_attr = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_set_buffer_attr"); pContext->pulse.pa_stream_get_device_name = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_get_device_name"); pContext->pulse.pa_stream_set_write_callback = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_set_write_callback"); pContext->pulse.pa_stream_set_read_callback = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_set_read_callback"); @@ -12972,6 +12981,7 @@ mal_result mal_context_init__pulse(mal_context* pContext) mal_pa_stream_get_sample_spec_proc _pa_stream_get_sample_spec = pa_stream_get_sample_spec; mal_pa_stream_get_channel_map_proc _pa_stream_get_channel_map = pa_stream_get_channel_map; mal_pa_stream_get_buffer_attr_proc _pa_stream_get_buffer_attr = pa_stream_get_buffer_attr; + mal_pa_stream_set_buffer_attr_proc _pa_stream_set_buffer_attr = pa_stream_set_buffer_attr; mal_pa_stream_get_device_name_proc _pa_stream_get_device_name = pa_stream_get_device_name; mal_pa_stream_set_write_callback_proc _pa_stream_set_write_callback = pa_stream_set_write_callback; mal_pa_stream_set_read_callback_proc _pa_stream_set_read_callback = pa_stream_set_read_callback; @@ -13013,6 +13023,7 @@ mal_result mal_context_init__pulse(mal_context* pContext) pContext->pulse.pa_stream_get_sample_spec = (mal_proc)_pa_stream_get_sample_spec; pContext->pulse.pa_stream_get_channel_map = (mal_proc)_pa_stream_get_channel_map; pContext->pulse.pa_stream_get_buffer_attr = (mal_proc)_pa_stream_get_buffer_attr; + pContext->pulse.pa_stream_set_buffer_attr = (mal_proc)_pa_stream_set_buffer_attr; pContext->pulse.pa_stream_get_device_name = (mal_proc)_pa_stream_get_device_name; pContext->pulse.pa_stream_set_write_callback = (mal_proc)_pa_stream_set_write_callback; pContext->pulse.pa_stream_set_read_callback = (mal_proc)_pa_stream_set_read_callback; @@ -14900,27 +14911,62 @@ OSStatus mal_on_output__coreaudio(void* pUserData, AudioUnitRenderActionFlags* p #if defined(MAL_DEBUG_OUTPUT) printf("INFO: Output Callback: busNumber=%d, frameCount=%d, mNumberBuffers=%d\n", busNumber, frameCount, pBufferList->mNumberBuffers); #endif + + // We need to check whether or not we are outputting interleaved or non-interleaved samples. The + // way we do this is slightly different for each type. + mal_stream_layout layout = mal_stream_layout_interleaved; + if (pBufferList->mBuffers[0].mNumberChannels != pDevice->internalChannels) { + layout = mal_stream_layout_deinterleaved; + } - // For now we can assume everything is interleaved. - for (UInt32 iBuffer = 0; iBuffer < pBufferList->mNumberBuffers; ++iBuffer) { - if (pBufferList->mBuffers[iBuffer].mNumberChannels == pDevice->internalChannels) { - mal_uint32 frameCountForThisBuffer = pBufferList->mBuffers[iBuffer].mDataByteSize / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels); - if (frameCountForThisBuffer > 0) { - mal_device__read_frames_from_client(pDevice, frameCountForThisBuffer, pBufferList->mBuffers[iBuffer].mData); + if (layout == mal_stream_layout_interleaved) { + // For now we can assume everything is interleaved. + for (UInt32 iBuffer = 0; iBuffer < pBufferList->mNumberBuffers; ++iBuffer) { + if (pBufferList->mBuffers[iBuffer].mNumberChannels == pDevice->internalChannels) { + mal_uint32 frameCountForThisBuffer = pBufferList->mBuffers[iBuffer].mDataByteSize / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels); + if (frameCountForThisBuffer > 0) { + mal_device__read_frames_from_client(pDevice, frameCountForThisBuffer, pBufferList->mBuffers[iBuffer].mData); + } + + #if defined(MAL_DEBUG_OUTPUT) + printf(" frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", frameCount, pBufferList->mBuffers[iBuffer].mNumberChannels, pBufferList->mBuffers[iBuffer].mDataByteSize); + #endif + } else { + // This case is where the number of channels in the output buffer do not match our internal channels. It could mean that it's + // not interleaved, in which case we can't handle right now since mini_al does not yet support non-interleaved streams. We just + // output silence here. + mal_zero_memory(pBufferList->mBuffers[iBuffer].mData, pBufferList->mBuffers[iBuffer].mDataByteSize); + + #if defined(MAL_DEBUG_OUTPUT) + printf(" WARNING: Outputting silence. frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", frameCount, pBufferList->mBuffers[iBuffer].mNumberChannels, pBufferList->mBuffers[iBuffer].mDataByteSize); + #endif } + } + } else { + // This is the deinterleaved case. We need to update each buffer in groups of internalChannels. This + // assumes each buffer is the same size. + mal_uint8 tempBuffer[4096]; + for (UInt32 iBuffer = 0; iBuffer < pBufferList->mNumberBuffers; iBuffer += pDevice->internalChannels) { + mal_uint32 frameCountPerBuffer = pBufferList->mBuffers[iBuffer].mDataByteSize / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels); - #if defined(MAL_DEBUG_OUTPUT) - printf(" frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", frameCount, pBufferList->mBuffers[iBuffer].mNumberChannels, pBufferList->mBuffers[iBuffer].mDataByteSize); - #endif - } else { - // This case is where the number of channels in the output buffer do not match our internal channels. It could mean that it's - // not interleaved, in which case we can't handle right now since mini_al does not yet support non-interleaved streams. We just - // output silence here. - mal_zero_memory(pBufferList->mBuffers[iBuffer].mData, pBufferList->mBuffers[iBuffer].mDataByteSize); - - #if defined(MAL_DEBUG_OUTPUT) - printf(" WARNING: Outputting silence. frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", frameCount, pBufferList->mBuffers[iBuffer].mNumberChannels, pBufferList->mBuffers[iBuffer].mDataByteSize); - #endif + mal_uint32 framesRemaining = frameCountPerBuffer; + while (framesRemaining > 0) { + mal_uint32 framesToRead = sizeof(tempBuffer) / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels); + if (framesToRead > framesRemaining) { + framesToRead = framesRemaining; + } + + mal_device__read_frames_from_client(pDevice, framesToRead, tempBuffer); + + void* ppDeinterleavedBuffers[MAL_MAX_CHANNELS]; + for (mal_uint32 iChannel = 0; iChannel < pDevice->internalChannels; ++iChannel) { + ppDeinterleavedBuffers[iChannel] = (void*)mal_offset_ptr(pBufferList->mBuffers[iBuffer].mData, (frameCountPerBuffer - framesRemaining) * mal_get_bytes_per_sample(pDevice->internalFormat)); + } + + mal_deinterleave_pcm_frames(pDevice->internalFormat, pDevice->internalChannels, framesToRead, tempBuffer, ppDeinterleavedBuffers); + + framesRemaining -= framesToRead; + } } } @@ -14941,6 +14987,13 @@ OSStatus mal_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFlags* pA AudioBufferList* pRenderedBufferList = (AudioBufferList*)pDevice->coreaudio.pAudioBufferList; mal_assert(pRenderedBufferList); + // We need to check whether or not we are outputting interleaved or non-interleaved samples. The + // way we do this is slightly different for each type. + mal_stream_layout layout = mal_stream_layout_interleaved; + if (pRenderedBufferList->mBuffers[0].mNumberChannels != pDevice->internalChannels) { + layout = mal_stream_layout_deinterleaved; + } + #if defined(MAL_DEBUG_OUTPUT) printf("INFO: Input Callback: busNumber=%d, frameCount=%d, mNumberBuffers=%d\n", busNumber, frameCount, pRenderedBufferList->mNumberBuffers); #endif @@ -14953,16 +15006,58 @@ OSStatus mal_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFlags* pA return status; } - // For now we can assume everything is interleaved. - for (UInt32 iBuffer = 0; iBuffer < pRenderedBufferList->mNumberBuffers; ++iBuffer) { - if (pRenderedBufferList->mBuffers[iBuffer].mNumberChannels == pDevice->internalChannels) { - mal_device__send_frames_to_client(pDevice, frameCount, pRenderedBufferList->mBuffers[iBuffer].mData); - #if defined(MAL_DEBUG_OUTPUT) - printf(" mDataByteSize=%d\n", pRenderedBufferList->mBuffers[iBuffer].mDataByteSize); - #endif - } else { - // This case is where the number of channels in the output buffer do not match our internal channels. It could mean that it's - // not interleaved, in which case we can't handle right now since mini_al does not yet support non-interleaved streams. + if (layout == mal_stream_layout_interleaved) { + for (UInt32 iBuffer = 0; iBuffer < pRenderedBufferList->mNumberBuffers; ++iBuffer) { + if (pRenderedBufferList->mBuffers[iBuffer].mNumberChannels == pDevice->internalChannels) { + mal_device__send_frames_to_client(pDevice, frameCount, pRenderedBufferList->mBuffers[iBuffer].mData); + #if defined(MAL_DEBUG_OUTPUT) + printf(" mDataByteSize=%d\n", pRenderedBufferList->mBuffers[iBuffer].mDataByteSize); + #endif + } else { + // This case is where the number of channels in the output buffer do not match our internal channels. It could mean that it's + // not interleaved, in which case we can't handle right now since mini_al does not yet support non-interleaved streams. + + mal_uint8 silentBuffer[4096]; + mal_zero_memory(silentBuffer, sizeof(silentBuffer)); + + mal_uint32 framesRemaining = frameCount; + while (framesRemaining > 0) { + mal_uint32 framesToSend = sizeof(silentBuffer) / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels); + if (framesToSend > framesRemaining) { + framesToSend = framesRemaining; + } + + mal_device__send_frames_to_client(pDevice, framesToSend, silentBuffer); + framesRemaining -= framesToSend; + } + + #if defined(MAL_DEBUG_OUTPUT) + printf(" WARNING: Outputting silence. frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", frameCount, pRenderBufferList->mBuffers[iBuffer].mNumberChannels, pRenderBufferList->mBuffers[iBuffer].mDataByteSize); + #endif + } + } + } else { + // This is the deinterleaved case. We need to interleave the audio data before sending it to the client. This + // assumes each buffer is the same size. + mal_uint8 tempBuffer[4096]; + for (UInt32 iBuffer = 0; iBuffer < pRenderedBufferList->mNumberBuffers; iBuffer += pDevice->internalChannels) { + mal_uint32 framesRemaining = frameCount; + while (framesRemaining > 0) { + mal_uint32 framesToSend = sizeof(tempBuffer) / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels); + if (framesToSend > framesRemaining) { + framesToSend = framesRemaining; + } + + void* ppDeinterleavedBuffers[MAL_MAX_CHANNELS]; + for (mal_uint32 iChannel = 0; iChannel < pDevice->internalChannels; ++iChannel) { + ppDeinterleavedBuffers[iChannel] = (void*)mal_offset_ptr(pRenderedBufferList->mBuffers[iBuffer].mData, (frameCount - framesRemaining) * mal_get_bytes_per_sample(pDevice->internalFormat)); + } + + mal_interleave_pcm_frames(pDevice->internalFormat, pDevice->internalChannels, framesToSend, (const void**)ppDeinterleavedBuffers, tempBuffer); + mal_device__send_frames_to_client(pDevice, framesToSend, tempBuffer); + + framesRemaining -= framesToSend; + } } } @@ -14976,42 +15071,53 @@ void on_start_stop__coreaudio(void* pUserData, AudioUnit audioUnit, AudioUnitPro mal_device* pDevice = (mal_device*)pUserData; mal_assert(pDevice != NULL); - UInt32 isRunning; - UInt32 isRunningSize = sizeof(isRunning); - OSStatus status = ((mal_AudioUnitGetProperty_proc)pDevice->pContext->coreaudio.AudioUnitGetProperty)(audioUnit, kAudioOutputUnitProperty_IsRunning, scope, element, &isRunning, &isRunningSize); - if (status != noErr) { - return; // Don't really know what to do in this case... just ignore it, I suppose... - } - - if (!isRunning) { - // The stop event is a bit annoying in Core Audio because it will be called when we automatically switch the default device. Some scenarios to consider: - // - // 1) When the device is unplugged, this will be called _before_ the default device change notification. - // 2) When the device is changed via the default device change notification, this will be called _after_ the switch. - // - // For case #1, we just check if there's a new default device available. If so, we just ignore the stop event. For case #2 we check a flag. - if (pDevice->isDefaultDevice && mal_device__get_state(pDevice) != MAL_STATE_STOPPING && mal_device__get_state(pDevice) != MAL_STATE_STOPPED) { - // It looks like the device is switching through an external event, such as the user unplugging the device or changing the default device - // via the operating system's sound settings. If we're re-initializing the device, we just terminate because we want the stopping of the - // device to be seamless to the client (we don't want them receiving the onStop event and thinking that the device has stopped when it - // hasn't!). - if (pDevice->coreaudio.isSwitchingDevice) { - return; - } - - // Getting here means the device is not reinitializing which means it may have been unplugged. From what I can see, it looks like Core Audio - // will try switching to the new default device seamlessly. We need to somehow find a way to determine whether or not Core Audio will most - // likely be successful in switching to the new device. - // - // TODO: Try to predict if Core Audio will switch devices. If not, the onStop callback needs to be posted. - return; - } - - // Getting here means we need to stop the device. + // There's been a report of a deadlock here when triggered by mal_device_uninit(). It looks like + // AudioUnitGetProprty (called below) and AudioComponentInstanceDispose (called in mal_device_uninit) + // can try waiting on the same lock. I'm going to try working around this by not calling any Core + // Audio APIs in the callback when the device has been stopped or uninitialized. + if (mal_device__get_state(pDevice) == MAL_STATE_UNINITIALIZED || mal_device__get_state(pDevice) == MAL_STATE_STOPPING) { mal_stop_proc onStop = pDevice->onStop; if (onStop) { onStop(pDevice); } + } else { + UInt32 isRunning; + UInt32 isRunningSize = sizeof(isRunning); + OSStatus status = ((mal_AudioUnitGetProperty_proc)pDevice->pContext->coreaudio.AudioUnitGetProperty)(audioUnit, kAudioOutputUnitProperty_IsRunning, scope, element, &isRunning, &isRunningSize); + if (status != noErr) { + return; // Don't really know what to do in this case... just ignore it, I suppose... + } + + if (!isRunning) { + // The stop event is a bit annoying in Core Audio because it will be called when we automatically switch the default device. Some scenarios to consider: + // + // 1) When the device is unplugged, this will be called _before_ the default device change notification. + // 2) When the device is changed via the default device change notification, this will be called _after_ the switch. + // + // For case #1, we just check if there's a new default device available. If so, we just ignore the stop event. For case #2 we check a flag. + if (pDevice->isDefaultDevice && mal_device__get_state(pDevice) != MAL_STATE_STOPPING && mal_device__get_state(pDevice) != MAL_STATE_STOPPED) { + // It looks like the device is switching through an external event, such as the user unplugging the device or changing the default device + // via the operating system's sound settings. If we're re-initializing the device, we just terminate because we want the stopping of the + // device to be seamless to the client (we don't want them receiving the onStop event and thinking that the device has stopped when it + // hasn't!). + if (pDevice->coreaudio.isSwitchingDevice) { + return; + } + + // Getting here means the device is not reinitializing which means it may have been unplugged. From what I can see, it looks like Core Audio + // will try switching to the new default device seamlessly. We need to somehow find a way to determine whether or not Core Audio will most + // likely be successful in switching to the new device. + // + // TODO: Try to predict if Core Audio will switch devices. If not, the onStop callback needs to be posted. + return; + } + + // Getting here means we need to stop the device. + mal_stop_proc onStop = pDevice->onStop; + if (onStop) { + onStop(pDevice); + } + } } } @@ -15288,11 +15394,14 @@ mal_result mal_device_init_internal__coreaudio(mal_context* pContext, mal_device if (result != MAL_SUCCESS) { return result; } + + pData->bufferSizeInFramesOut = actualBufferSizeInFrames * pData->periodsOut; #else actualBufferSizeInFrames = 4096; + pData->bufferSizeInFramesOut = actualBufferSizeInFrames; #endif - pData->bufferSizeInFramesOut = actualBufferSizeInFrames * pData->periodsOut; + // During testing I discovered that the buffer size can be too big. You'll get an error like this: // @@ -15556,6 +15665,15 @@ mal_result mal_context_uninit__coreaudio(mal_context* pContext) mal_result mal_context_init__coreaudio(mal_context* pContext) { mal_assert(pContext != NULL); + +#if defined(MAL_APPLE_MOBILE) + @autoreleasepool { + AVAudioSession* pAudioSession = [AVAudioSession sharedInstance]; + mal_assert(pAudioSession != NULL); + + [pAudioSession setCategory: AVAudioSessionCategoryPlayAndRecord error:nil]; + } +#endif #if !defined(MAL_NO_RUNTIME_LINKING) && !defined(MAL_APPLE_MOBILE) pContext->coreaudio.hCoreFoundation = mal_dlopen("CoreFoundation.framework/CoreFoundation"); @@ -19725,7 +19843,7 @@ mal_result mal_device_init__sdl(mal_context* pContext, mal_device_type type, con // SDL wants the buffer size to be a power of 2. The SDL_AudioSpec property for this is only a Uint16, so we need // to explicitly clamp this because it will be easy to overflow. - mal_uint32 bufferSize = pConfig->bufferSizeInFrames; + mal_uint32 bufferSize = pDevice->bufferSizeInFrames; if (bufferSize > 32768) { bufferSize = 32768; } else { @@ -19760,7 +19878,7 @@ mal_result mal_device_init__sdl(mal_context* pContext, mal_device_type type, con pDevice->sdl.deviceID = ((MAL_PFN_SDL_OpenAudioDevice)pDevice->pContext->sdl.SDL_OpenAudioDevice)(pDeviceName, isCapture, &desiredSpec, &obtainedSpec, MAL_SDL_AUDIO_ALLOW_ANY_CHANGE); if (pDevice->sdl.deviceID == 0) { - return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "Failed to open SDL device.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "Failed to open SDL2 device.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); } } else #endif @@ -19778,10 +19896,12 @@ mal_result mal_device_init__sdl(mal_context* pContext, mal_device_type type, con desiredSpec.format = MAL_AUDIO_S16; } - pDevice->sdl.deviceID = ((MAL_PFN_SDL_OpenAudio)pDevice->pContext->sdl.SDL_OpenAudio)(&desiredSpec, &obtainedSpec); - if (pDevice->sdl.deviceID != 0) { - return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "Failed to open SDL device.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + int deviceID = ((MAL_PFN_SDL_OpenAudio)pDevice->pContext->sdl.SDL_OpenAudio)(&desiredSpec, &obtainedSpec); + if (deviceID < 0) { + return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "Failed to open SDL1 device.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); } + + pDevice->sdl.deviceID = (mal_uint32)deviceID; } pDevice->internalFormat = mal_format_from_sdl(obtainedSpec.format); @@ -20429,6 +20549,14 @@ mal_result mal_context_init(const mal_backend backends[], mal_uint32 backendCoun mal_context_post_error(pContext, NULL, MAL_LOG_LEVEL_WARNING, "Failed to initialize mutex for device info retrieval. mal_context_get_device_info() is not thread safe.", MAL_FAILED_TO_CREATE_MUTEX); } +#ifdef MAL_DEBUG_OUTPUT + printf("[mini_al] Endian: %s\n", mal_is_little_endian() ? "LE" : "BE"); + printf("[mini_al] SSE2: %s\n", mal_has_sse2() ? "YES" : "NO"); + printf("[mini_al] AVX2: %s\n", mal_has_avx2() ? "YES" : "NO"); + printf("[mini_al] AVX512F: %s\n", mal_has_avx512f() ? "YES" : "NO"); + printf("[mini_al] NEON: %s\n", mal_has_neon() ? "YES" : "NO"); +#endif + pContext->backend = backend; return result; } @@ -20767,7 +20895,7 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi #ifdef MAL_DEBUG_OUTPUT - printf("[WASAPI] %s (%s)\n", pDevice->name, (pDevice->type == mal_device_type_playback) ? "Playback" : "Capture"); + printf("[%s] %s (%s)\n", mal_get_backend_name(pDevice->pContext->backend), pDevice->name, (pDevice->type == mal_device_type_playback) ? "Playback" : "Capture"); printf(" Format: %s -> %s\n", mal_get_format_name(pDevice->format), mal_get_format_name(pDevice->internalFormat)); printf(" Channels: %d -> %d\n", pDevice->channels, pDevice->internalChannels); printf(" Sample Rate: %d -> %d\n", pDevice->sampleRate, pDevice->internalSampleRate); @@ -20949,7 +21077,7 @@ mal_result mal_device_stop(mal_device* pDevice) // Asynchronous backends need to be handled differently. if (mal_context_is_backend_asynchronous(pDevice->pContext)) { - pDevice->pContext->onDeviceStop(pDevice); + result = pDevice->pContext->onDeviceStop(pDevice); } else { // Synchronous backends. @@ -26138,6 +26266,92 @@ void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_form } } +void mal_deinterleave_pcm_frames(mal_format format, mal_uint32 channels, mal_uint32 frameCount, const void* pInterleavedPCMFrames, void** ppDeinterleavedPCMFrames) +{ + if (pInterleavedPCMFrames == NULL || ppDeinterleavedPCMFrames == NULL) { + return; // Invalid args. + } + + // For efficiency we do this per format. + switch (format) { + case mal_format_s16: + { + const mal_int16* pSrcS16 = (const mal_int16*)pInterleavedPCMFrames; + for (mal_uint32 iPCMFrame = 0; iPCMFrame < frameCount; ++iPCMFrame) { + for (mal_uint32 iChannel = 0; iChannel < channels; ++iChannel) { + mal_int16* pDstS16 = (mal_int16*)ppDeinterleavedPCMFrames[iChannel]; + pDstS16[iPCMFrame] = pSrcS16[iPCMFrame*channels+iChannel]; + } + } + } break; + + case mal_format_f32: + { + const float* pSrcF32 = (const float*)pInterleavedPCMFrames; + for (mal_uint32 iPCMFrame = 0; iPCMFrame < frameCount; ++iPCMFrame) { + for (mal_uint32 iChannel = 0; iChannel < channels; ++iChannel) { + float* pDstF32 = (float*)ppDeinterleavedPCMFrames[iChannel]; + pDstF32[iPCMFrame] = pSrcF32[iPCMFrame*channels+iChannel]; + } + } + } break; + + default: + { + mal_uint32 sampleSizeInBytes = mal_get_bytes_per_sample(format); + + for (mal_uint32 iPCMFrame = 0; iPCMFrame < frameCount; ++iPCMFrame) { + for (mal_uint32 iChannel = 0; iChannel < channels; ++iChannel) { + void* pDst = mal_offset_ptr(ppDeinterleavedPCMFrames[iChannel], iPCMFrame*sampleSizeInBytes); + const void* pSrc = mal_offset_ptr(pInterleavedPCMFrames, (iPCMFrame*channels+iChannel)*sampleSizeInBytes); + memcpy(pDst, pSrc, sampleSizeInBytes); + } + } + } break; + } +} + +void mal_interleave_pcm_frames(mal_format format, mal_uint32 channels, mal_uint32 frameCount, const void** ppDeinterleavedPCMFrames, void* pInterleavedPCMFrames) +{ + switch (format) + { + case mal_format_s16: + { + mal_int16* pDstS16 = (mal_int16*)pInterleavedPCMFrames; + for (mal_uint32 iPCMFrame = 0; iPCMFrame < frameCount; ++iPCMFrame) { + for (mal_uint32 iChannel = 0; iChannel < channels; ++iChannel) { + const mal_int16* pSrcS16 = (const mal_int16*)ppDeinterleavedPCMFrames[iChannel]; + pDstS16[iPCMFrame*channels+iChannel] = pSrcS16[iPCMFrame]; + } + } + } break; + + case mal_format_f32: + { + float* pDstF32 = (float*)pInterleavedPCMFrames; + for (mal_uint32 iPCMFrame = 0; iPCMFrame < frameCount; ++iPCMFrame) { + for (mal_uint32 iChannel = 0; iChannel < channels; ++iChannel) { + const float* pSrcF32 = (const float*)ppDeinterleavedPCMFrames[iChannel]; + pDstF32[iPCMFrame*channels+iChannel] = pSrcF32[iPCMFrame]; + } + } + } break; + + default: + { + mal_uint32 sampleSizeInBytes = mal_get_bytes_per_sample(format); + + for (mal_uint32 iPCMFrame = 0; iPCMFrame < frameCount; ++iPCMFrame) { + for (mal_uint32 iChannel = 0; iChannel < channels; ++iChannel) { + void* pDst = mal_offset_ptr(pInterleavedPCMFrames, (iPCMFrame*channels+iChannel)*sampleSizeInBytes); + const void* pSrc = mal_offset_ptr(ppDeinterleavedPCMFrames[iChannel], iPCMFrame*sampleSizeInBytes); + memcpy(pDst, pSrc, sampleSizeInBytes); + } + } + } break; + } +} + typedef struct @@ -28296,6 +28510,13 @@ mal_uint64 mal_sine_wave_read_ex(mal_sine_wave* pSineWave, mal_uint64 frameCount // REVISION HISTORY // ================ // +// v0.8.10 - 2018-10-21 +// - Core Audio: Fix a hang when uninitializing a device. +// - Fix a bug where an incorrect value is returned from mal_device_stop(). +// +// v0.8.9 - 2018-09-28 +// - Fix a bug with the SDL backend where device initialization fails. +// // v0.8.8 - 2018-09-14 // - Fix Linux build with the ALSA backend. // - Minor documentation fix.