Browse Source

Updated mini_al

Ray 6 years ago
parent
commit
de02f14283
2 changed files with 347 additions and 127 deletions
  1. 0 1
      src/external/mini_al.c
  2. 347 126
      src/external/mini_al.h

+ 0 - 1
src/external/mini_al.c

@@ -3,6 +3,5 @@
 #define MINI_AL_IMPLEMENTATION
 #define MINI_AL_IMPLEMENTATION
 #define MAL_NO_JACK
 #define MAL_NO_JACK
 #define MAL_NO_OPENAL
 #define MAL_NO_OPENAL
-#define MAL_NO_SDL
 #define MAL_NO_NULL
 #define MAL_NO_NULL
 #include "mini_al.h"
 #include "mini_al.h"

+ 347 - 126
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.
 // 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 - [email protected]
 // David Reid - [email protected]
 
 
@@ -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_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);
 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_sample_spec;
             mal_proc pa_stream_get_channel_map;
             mal_proc pa_stream_get_channel_map;
             mal_proc pa_stream_get_buffer_attr;
             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_get_device_name;
             mal_proc pa_stream_set_write_callback;
             mal_proc pa_stream_set_write_callback;
             mal_proc pa_stream_set_read_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);
     mal_assert(pCurrentPos != NULL);
     *pCurrentPos = 0;
     *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);
     *pCurrentPos = (mal_uint32)(currentFrameAbs % pDevice->bufferSizeInFrames);
     return MAL_TRUE;
     return MAL_TRUE;
@@ -4917,11 +4923,11 @@ mal_uint32 mal_device__wait_for_frames__null(mal_device* pDevice)
 
 
     while (!pDevice->null_device.breakFromMainLoop) {
     while (!pDevice->null_device.breakFromMainLoop) {
         mal_uint32 framesAvailable = mal_device__get_available_frames__null(pDevice);
         mal_uint32 framesAvailable = mal_device__get_available_frames__null(pDevice);
-        if (framesAvailable > 0) {
+        if (framesAvailable >= (pDevice->bufferSizeInFrames/pDevice->periods)) {
             return framesAvailable;
             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.
     // 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_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_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 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 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_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);
 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_context* pContext = pDevice->pContext;
     mal_assert(pContext != NULL);
     mal_assert(pContext != NULL);
 
 
+#ifdef MAL_DEBUG_OUTPUT
+    printf("[PulseAudio] write_callback: sizeInBytes=%d\n", (int)sizeInBytes);
+#endif
+
     size_t bytesRemaining = sizeInBytes;
     size_t bytesRemaining = sizeInBytes;
     while (bytesRemaining > 0) {
     while (bytesRemaining > 0) {
         size_t bytesToReadFromClient = bytesRemaining;
         size_t bytesToReadFromClient = bytesRemaining;
@@ -12319,19 +12330,35 @@ void mal_pulse_device_write_callback(mal_pa_stream* pStream, size_t sizeInBytes,
             return;
             return;
         }
         }
 
 
+#ifdef MAL_DEBUG_OUTPUT
+        printf("    bytesToReadFromClient=%d", (int)bytesToReadFromClient);
+#endif
+
         if (pBuffer != NULL && bytesToReadFromClient > 0) {
         if (pBuffer != NULL && bytesToReadFromClient > 0) {
             mal_uint32 framesToReadFromClient = (mal_uint32)bytesToReadFromClient / (pDevice->internalChannels*mal_get_bytes_per_sample(pDevice->internalFormat));
             mal_uint32 framesToReadFromClient = (mal_uint32)bytesToReadFromClient / (pDevice->internalChannels*mal_get_bytes_per_sample(pDevice->internalFormat));
             if (framesToReadFromClient > 0) {
             if (framesToReadFromClient > 0) {
                 mal_device__read_frames_from_client(pDevice, framesToReadFromClient, pBuffer);
                 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);
                 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) {
                 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));
                     mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to write data to the PulseAudio stream.", mal_result_from_pulse(error));
                     return;
                     return;
                 }
                 }
+            } else {
+#ifdef MAL_DEBUG_OUTPUT
+            printf(", framesToReadFromClient=0\n");
+#endif
             }
             }
 
 
             bytesRemaining -= bytesToReadFromClient;
             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_sample_spec ss;
     mal_pa_channel_map cmap;
     mal_pa_channel_map cmap;
     mal_pa_buffer_attr attr;
     mal_pa_buffer_attr attr;
+    mal_pa_stream_flags_t streamFlags;
 
 
     const mal_pa_sample_spec* pActualSS   = NULL;
     const mal_pa_sample_spec* pActualSS   = NULL;
     const mal_pa_channel_map* pActualCMap = 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);
         ((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) {
     if (type == mal_device_type_playback) {
         ss = sinkInfo.sample_spec;
         ss = sinkInfo.sample_spec;
         cmap = sinkInfo.channel_map;
         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;
         ss = sourceInfo.sample_spec;
         cmap = sourceInfo.channel_map;
         cmap = sourceInfo.channel_map;
     }
     }
-#endif
+
 
 
     // Buffer size.
     // Buffer size.
     bufferSizeInFrames = pDevice->bufferSizeInFrames;
     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.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.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];
     char streamName[256];
     if (pConfig->pulse.pStreamName != NULL) {
     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) {
     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 {
     } 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) {
     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.
     // Internal format.
     pActualSS = ((mal_pa_stream_get_sample_spec_proc)pContext->pulse.pa_stream_get_sample_spec)((mal_pa_stream*)pDevice->pulse.pStream);
     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 (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;
         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->bufferSizeInFrames = attr.maxlength / (mal_get_bytes_per_sample(pDevice->internalFormat)*pDevice->internalChannels);
     pDevice->periods = attr.maxlength / attr.tlength;
     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.
     // 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);
     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_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_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_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_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_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");
     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_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_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_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_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_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;
     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_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_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_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_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_write_callback       = (mal_proc)_pa_stream_set_write_callback;
     pContext->pulse.pa_stream_set_read_callback        = (mal_proc)_pa_stream_set_read_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)
 #if defined(MAL_DEBUG_OUTPUT)
     printf("INFO: Output Callback: busNumber=%d, frameCount=%d, mNumberBuffers=%d\n", busNumber, frameCount, pBufferList->mNumberBuffers);
     printf("INFO: Output Callback: busNumber=%d, frameCount=%d, mNumberBuffers=%d\n", busNumber, frameCount, pBufferList->mNumberBuffers);
 #endif
 #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;
     AudioBufferList* pRenderedBufferList = (AudioBufferList*)pDevice->coreaudio.pAudioBufferList;
     mal_assert(pRenderedBufferList);
     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)
 #if defined(MAL_DEBUG_OUTPUT)
     printf("INFO: Input Callback: busNumber=%d, frameCount=%d, mNumberBuffers=%d\n", busNumber, frameCount, pRenderedBufferList->mNumberBuffers);
     printf("INFO: Input Callback: busNumber=%d, frameCount=%d, mNumberBuffers=%d\n", busNumber, frameCount, pRenderedBufferList->mNumberBuffers);
 #endif
 #endif
@@ -14953,16 +15006,58 @@ OSStatus mal_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFlags* pA
         return status;
         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_device* pDevice = (mal_device*)pUserData;
     mal_assert(pDevice != NULL);
     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;
         mal_stop_proc onStop = pDevice->onStop;
         if (onStop) {
         if (onStop) {
             onStop(pDevice);
             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) {
     if (result != MAL_SUCCESS) {
         return result;
         return result;
     }
     }
+    
+    pData->bufferSizeInFramesOut = actualBufferSizeInFrames * pData->periodsOut;
 #else
 #else
     actualBufferSizeInFrames = 4096;
     actualBufferSizeInFrames = 4096;
+    pData->bufferSizeInFramesOut = actualBufferSizeInFrames;
 #endif
 #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:
     // 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_result mal_context_init__coreaudio(mal_context* pContext)
 {
 {
     mal_assert(pContext != NULL);
     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)
 #if !defined(MAL_NO_RUNTIME_LINKING) && !defined(MAL_APPLE_MOBILE)
     pContext->coreaudio.hCoreFoundation = mal_dlopen("CoreFoundation.framework/CoreFoundation");
     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
     // 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.
     // to explicitly clamp this because it will be easy to overflow.
-    mal_uint32 bufferSize = pConfig->bufferSizeInFrames;
+    mal_uint32 bufferSize = pDevice->bufferSizeInFrames;
     if (bufferSize > 32768) {
     if (bufferSize > 32768) {
         bufferSize = 32768;
         bufferSize = 32768;
     } else {
     } 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);
         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) {
         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
     } else
 #endif
 #endif
@@ -19778,10 +19896,12 @@ mal_result mal_device_init__sdl(mal_context* pContext, mal_device_type type, con
             desiredSpec.format  = MAL_AUDIO_S16;
             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);
     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);
                 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;
             pContext->backend = backend;
             return result;
             return result;
         }
         }
@@ -20767,7 +20895,7 @@ mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_devi
 
 
 
 
 #ifdef MAL_DEBUG_OUTPUT
 #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("  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("  Channels:    %d -> %d\n", pDevice->channels, pDevice->internalChannels);
     printf("  Sample Rate: %d -> %d\n", pDevice->sampleRate, pDevice->internalSampleRate);
     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.
         // Asynchronous backends need to be handled differently.
         if (mal_context_is_backend_asynchronous(pDevice->pContext)) {
         if (mal_context_is_backend_asynchronous(pDevice->pContext)) {
-            pDevice->pContext->onDeviceStop(pDevice);
+            result = pDevice->pContext->onDeviceStop(pDevice);
         } else {
         } else {
             // Synchronous backends.
             // 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
 typedef struct
@@ -28296,6 +28510,13 @@ mal_uint64 mal_sine_wave_read_ex(mal_sine_wave* pSineWave, mal_uint64 frameCount
 // REVISION HISTORY
 // 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
 // v0.8.8 - 2018-09-14
 //   - Fix Linux build with the ALSA backend.
 //   - Fix Linux build with the ALSA backend.
 //   - Minor documentation fix.
 //   - Minor documentation fix.