* WASAPI: Fix a bug in duplex mode when the capture and playback devices have different native sample rates.
* AAudio: Add support for automatic stream routing.
* iOS: The interruption_began notification now automatically calls `ma_device_stop()`. This allows `ma_device_start()` to work as expected when called from interruption_ended.
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from playback device in preparation for writing to the device.");
exitLoop = MA_TRUE;
break;
}
/* Now just copy the data over to the output buffer. */
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for writing to the device.");
exitLoop = MA_TRUE;
break;
}
/* Overrun detection. */
if ((flagsCapture & MA_AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) != 0) {
Exeriment: If we get an overrun it probably means we're straddling the end of the buffer. In order to prevent a never-ending sequence of glitches let's experiment
by dropping every frame until we're left with only a single period. To do this we just keep retrieving and immediately releasing buffers until we're down to the
last period.
*/
if (framesAvailableCapture >= pDevice->wasapi.actualPeriodSizeInFramesCapture) {
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for writing to the device.");
exitLoop = MA_TRUE;
break;
}
} else {
pMappedDeviceBufferCapture = NULL;
mappedDeviceBufferSizeInFramesCapture = 0;
}
} while (framesAvailableCapture > periodSizeInFramesCapture);
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to release internal buffer from capture device after reading from the device.");
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to release internal buffer from playback device after writing to the device.");
/* Prevent a deadlock. If we don't clamp against the actual buffer size we'll never end up starting the playback device which will result in a deadlock. */
if (startThreshold > pDevice->wasapi.actualPeriodSizeInFramesPlayback) {
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for writing to the device.");
exitLoop = MA_TRUE;
break;
}
/* Overrun detection. */
if ((flagsCapture & MA_AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) != 0) {
Exeriment: If we get an overrun it probably means we're straddling the end of the buffer. In order to prevent a never-ending sequence of glitches let's experiment
by dropping every frame until we're left with only a single period. To do this we just keep retrieving and immediately releasing buffers until we're down to the
last period.
*/
if (framesAvailableCapture >= pDevice->wasapi.actualPeriodSizeInFramesCapture) {
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for writing to the device.");
exitLoop = MA_TRUE;
break;
}
} else {
pMappedDeviceBufferCapture = NULL;
mappedDeviceBufferSizeInFramesCapture = 0;
}
} while (framesAvailableCapture > periodSizeInFramesCapture);
pMappedDeviceBufferCapture = NULL; /* <-- Important. Not doing this can result in an error once we leave this loop because it will use this to know whether or not a final ReleaseBuffer() needs to be called. */
mappedDeviceBufferSizeInFramesCapture = 0;
if (FAILED(hr)) {
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to release internal buffer from capture device after reading from the device.");
exitLoop = MA_TRUE;
break;
}
}
} break;
/*
If we were unable to process the entire requested frame count, but we still have a mapped buffer,
there's a good chance either an error occurred or the device was stopped mid-read. In this case
we'll need to make sure the buffer is released.
*/
if (totalFramesProcessed < frameCount && pDevice->wasapi.pMappedBufferCapture != NULL) {
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from playback device in preparation for writing to the device.");
exitLoop = MA_TRUE;
break;
}
/*
We're going to do this in a similar way to capture. We'll first check if the cached data pointer
is valid, and if so, read from that. Otherwise We will call IAudioRenderClient_GetBuffer() with
a requested buffer size equal to our actual period size. If it returns AUDCLNT_E_BUFFER_TOO_LARGE
it means we need to wait for some data to become available.
*/
if (pDevice->wasapi.pMappedBufferPlayback != NULL) {
/* We still have some space available in the mapped data buffer. Write to it. */
ma_uint32 framesToProcessNow = framesRemaining;
if (framesToProcessNow > (pDevice->wasapi.mappedBufferPlaybackCap - pDevice->wasapi.mappedBufferPlaybackLen)) {
pMappedDeviceBufferPlayback = NULL; /* <-- Important. Not doing this can result in an error once we leave this loop because it will use this to know whether or not a final ReleaseBuffer() needs to be called. */
mappedDeviceBufferSizeInFramesPlayback = 0;
/* If the data buffer has been fully consumed we need to release it. */
if (pDevice->wasapi.mappedBufferPlaybackLen == pDevice->wasapi.mappedBufferPlaybackCap) {
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to release internal buffer from playback device after writing to the device.");
exitLoop = MA_TRUE;
break;
/*
In exclusive mode we need to wait here. Exclusive mode is weird because GetBuffer() never
seems to return AUDCLNT_E_BUFFER_TOO_LARGE, which is what we normally use to determine
whether or not we need to wait for more data.
*/
if (pDevice->playback.shareMode == ma_share_mode_exclusive) {
if (WaitForSingleObject(pDevice->wasapi.hEventPlayback, MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS) != WAIT_OBJECT_0) {
/* Not enough data available. We need to wait for more. */
if (WaitForSingleObject(pDevice->wasapi.hEventPlayback, MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS) != WAIT_OBJECT_0) {
break; /* Wait failed. Probably timed out. */
}
} else {
/* Some error occurred. We'll need to abort. */
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from playback device in preparation for writing to the device.");
break;
}
} break;
default: return MA_INVALID_ARGS;
}
}
/* Here is where the device needs to be stopped. */
/* We create the device on the JavaScript side and reference it using an index. We use this to make it possible to reference the device between JavaScript and C. */
ma_device__set_state(pDevice, ma_device_state_started); /* <-- Set this before signaling the event so that the state is always guaranteed to be good after ma_device_start() has returned. */
/*HANDLE*/ma_handlehEventPlayback;/* Auto reset. Initialized to signaled. */
/*HANDLE*/ma_handlehEventCapture;/* Auto reset. Initialized to unsignaled. */
ma_uint32actualPeriodSizeInFramesPlayback;/* Value from GetBufferSize(). internalPeriodSizeInFrames is not set to the _actual_ buffer size when low-latency shared mode is being used due to the way the IAudioClient3 API works. */
ma_uint32actualPeriodSizeInFramesCapture;
ma_uint32actualBufferSizeInFramesPlayback;/* Value from GetBufferSize(). internalPeriodSizeInFrames is not set to the _actual_ buffer size when low-latency shared mode is being used due to the way the IAudioClient3 API works. */
ma_uint32actualBufferSizeInFramesCapture;
ma_uint32originalPeriodSizeInFrames;
ma_uint32originalPeriodSizeInMilliseconds;
ma_uint32originalPeriods;
ma_performance_profileoriginalPerformanceProfile;
ma_uint32periodSizeInFramesPlayback;
ma_uint32periodSizeInFramesCapture;
void*pMappedBufferCapture;
ma_uint32mappedBufferCaptureCap;
ma_uint32mappedBufferCaptureLen;
void*pMappedBufferPlayback;
ma_uint32mappedBufferPlaybackCap;
ma_uint32mappedBufferPlaybackLen;
MA_ATOMIC(4,ma_bool32)isStartedCapture;/* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */
MA_ATOMIC(4,ma_bool32)isStartedPlayback;/* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */
ma_bool8noAutoConvertSRC;/* When set to true, disables the use of AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM. */
...
...
@@ -4624,6 +4630,11 @@ then be set directly on the structure. Below are the members of the `ma_device_c
AAudio only. Explicitly sets the type of recording your program will be doing. When left
unset, the input preset will be left unchanged.
aaudio.noAutoStartAfterReroute
AAudio only. Controls whether or not the device should be automatically restarted after a
stream reroute. When set to false (default) the device will be restarted automatically;
otherwise the device will be stopped.
Once initialized, the device's config is immutable. If you need to change the config you will need to initialize a new device.