Commit 8adce03d authored by David Reid's avatar David Reid

Core Audio: Refactoring in preparation for full-duplex.

parent d59b08d4
...@@ -35,15 +35,16 @@ int main(int argc, char** argv) ...@@ -35,15 +35,16 @@ int main(int argc, char** argv)
return -2; return -2;
} }
mal_device_config config = mal_device_config_init( mal_device_config config = mal_device_config_init(mal_device_type_playback);
decoder.outputFormat, config.playback.pDeviceID = NULL;
decoder.outputChannels, config.format = decoder.outputFormat;
decoder.outputSampleRate, config.channels = decoder.outputChannels;
on_send_frames_to_device, config.sampleRate = decoder.outputSampleRate;
&decoder); config.dataCallback = on_send_frames_to_device;
config.pUserData = &decoder;
mal_device device; mal_device device;
if (mal_device_init(NULL, mal_device_type_playback, NULL, &config, &device) != MAL_SUCCESS) { if (mal_device_init(NULL, &config, &device) != MAL_SUCCESS) {
printf("Failed to open playback device.\n"); printf("Failed to open playback device.\n");
mal_decoder_uninit(&decoder); mal_decoder_uninit(&decoder);
return -3; return -3;
......
...@@ -1859,6 +1859,8 @@ struct mal_context ...@@ -1859,6 +1859,8 @@ struct mal_context
mal_proc AudioUnitSetProperty; mal_proc AudioUnitSetProperty;
mal_proc AudioUnitInitialize; mal_proc AudioUnitInitialize;
mal_proc AudioUnitRender; mal_proc AudioUnitRender;
/*AudioComponent*/ mal_ptr component;
} coreaudio; } coreaudio;
#endif #endif
#ifdef MAL_SUPPORT_SNDIO #ifdef MAL_SUPPORT_SNDIO
...@@ -2023,7 +2025,6 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device ...@@ -2023,7 +2025,6 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device
mal_bool32 usingDefaultBufferSize : 1; mal_bool32 usingDefaultBufferSize : 1;
mal_bool32 usingDefaultPeriods : 1; mal_bool32 usingDefaultPeriods : 1;
mal_bool32 isOwnerOfContext : 1; // When set to true, uninitializing the device will also uninitialize the context. Set to true when NULL is passed into mal_device_init(). mal_bool32 isOwnerOfContext : 1; // When set to true, uninitializing the device will also uninitialize the context. Set to true when NULL is passed into mal_device_init().
mal_bool32 isDefaultDevice : 1; // Used to determine if the backend should try reinitializing if the default device is unplugged.
mal_format internalFormat; mal_format internalFormat;
mal_uint32 internalChannels; mal_uint32 internalChannels;
mal_uint32 internalSampleRate; mal_uint32 internalSampleRate;
...@@ -2164,12 +2165,16 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device ...@@ -2164,12 +2165,16 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device
#ifdef MAL_SUPPORT_COREAUDIO #ifdef MAL_SUPPORT_COREAUDIO
struct struct
{ {
mal_uint32 deviceObjectID; mal_uint32 deviceObjectIDPlayback;
/*AudioComponent*/ mal_ptr component; // <-- Can this be per-context? mal_uint32 deviceObjectIDCapture;
/*AudioUnit*/ mal_ptr audioUnit; /*AudioUnit*/ mal_ptr audioUnitPlayback;
/*AudioUnit*/ mal_ptr audioUnitCapture;
/*AudioBufferList**/ mal_ptr pAudioBufferList; // Only used for input devices. /*AudioBufferList**/ mal_ptr pAudioBufferList; // Only used for input devices.
mal_event stopEvent; mal_event stopEvent;
mal_bool32 isSwitchingDevice; /* <-- Set to true when the default device has changed and mini_al is in the process of switching. */ mal_bool32 isDefaultPlaybackDevice;
mal_bool32 isDefaultCaptureDevice;
mal_bool32 isSwitchingPlaybackDevice; /* <-- Set to true when the default device has changed and mini_al is in the process of switching. */
mal_bool32 isSwitchingCaptureDevice; /* <-- Set to true when the default device has changed and mini_al is in the process of switching. */
} coreaudio; } coreaudio;
#endif #endif
#ifdef MAL_SUPPORT_SNDIO #ifdef MAL_SUPPORT_SNDIO
...@@ -2507,7 +2512,7 @@ mal_uint32 mal_device_get_buffer_size_in_bytes(mal_device* pDevice); ...@@ -2507,7 +2512,7 @@ mal_uint32 mal_device_get_buffer_size_in_bytes(mal_device* pDevice);
// Helper function for initializing a mal_context_config object. // Helper function for initializing a mal_context_config object.
mal_context_config mal_context_config_init(); mal_context_config mal_context_config_init(void);
// Initializes a device config. // Initializes a device config.
// //
...@@ -4768,7 +4773,7 @@ mal_uint32 mal_get_format_priority_index(mal_format format) // Lower = better. ...@@ -4768,7 +4773,7 @@ mal_uint32 mal_get_format_priority_index(mal_format format) // Lower = better.
return (mal_uint32)-1; return (mal_uint32)-1;
} }
void mal_device__post_init_setup(mal_device* pDevice); void mal_device__post_init_setup(mal_device* pDevice, mal_device_type deviceType);
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// //
...@@ -14450,7 +14455,7 @@ typedef OSStatus (* mal_AudioUnitRender_proc)(AudioUnit inUnit, AudioUnitRenderA ...@@ -14450,7 +14455,7 @@ typedef OSStatus (* mal_AudioUnitRender_proc)(AudioUnit inUnit, AudioUnitRenderA
#define MAL_COREAUDIO_OUTPUT_BUS 0 #define MAL_COREAUDIO_OUTPUT_BUS 0
#define MAL_COREAUDIO_INPUT_BUS 1 #define MAL_COREAUDIO_INPUT_BUS 1
mal_result mal_device_reinit_internal__coreaudio(mal_device* pDevice, mal_bool32 disposePreviousAudioUnit); mal_result mal_device_reinit_internal__coreaudio(mal_device* pDevice, mal_device_type deviceType, mal_bool32 disposePreviousAudioUnit);
// Core Audio // Core Audio
...@@ -15714,7 +15719,12 @@ void mal_device_uninit__coreaudio(mal_device* pDevice) ...@@ -15714,7 +15719,12 @@ void mal_device_uninit__coreaudio(mal_device* pDevice)
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
mal_assert(mal_device__get_state(pDevice) == MAL_STATE_UNINITIALIZED); mal_assert(mal_device__get_state(pDevice) == MAL_STATE_UNINITIALIZED);
((mal_AudioComponentInstanceDispose_proc)pDevice->pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); if (pDevice->coreaudio.audioUnitCapture != NULL) {
((mal_AudioComponentInstanceDispose_proc)pDevice->pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnitCapture);
}
if (pDevice->coreaudio.audioUnitPlayback != NULL) {
((mal_AudioComponentInstanceDispose_proc)pDevice->pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnitPlayback);
}
if (pDevice->coreaudio.pAudioBufferList) { if (pDevice->coreaudio.pAudioBufferList) {
mal_free(pDevice->coreaudio.pAudioBufferList); mal_free(pDevice->coreaudio.pAudioBufferList);
...@@ -15821,7 +15831,7 @@ OSStatus mal_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFlags* pA ...@@ -15821,7 +15831,7 @@ OSStatus mal_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFlags* pA
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
OSStatus status = ((mal_AudioUnitRender_proc)pDevice->pContext->coreaudio.AudioUnitRender)((AudioUnit)pDevice->coreaudio.audioUnit, pActionFlags, pTimeStamp, busNumber, frameCount, pRenderedBufferList); OSStatus status = ((mal_AudioUnitRender_proc)pDevice->pContext->coreaudio.AudioUnitRender)((AudioUnit)pDevice->coreaudio.audioUnitCapture, pActionFlags, pTimeStamp, busNumber, frameCount, pRenderedBufferList);
if (status != noErr) { if (status != noErr) {
#if defined(MAL_DEBUG_OUTPUT) #if defined(MAL_DEBUG_OUTPUT)
printf(" ERROR: AudioUnitRender() failed with %d\n", status); printf(" ERROR: AudioUnitRender() failed with %d\n", status);
...@@ -15898,7 +15908,7 @@ void on_start_stop__coreaudio(void* pUserData, AudioUnit audioUnit, AudioUnitPro ...@@ -15898,7 +15908,7 @@ void on_start_stop__coreaudio(void* pUserData, AudioUnit audioUnit, AudioUnitPro
// AudioUnitGetProprty (called below) and AudioComponentInstanceDispose (called in mal_device_uninit) // 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 // 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. // 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) { if (mal_device__get_state(pDevice) == MAL_STATE_UNINITIALIZED || mal_device__get_state(pDevice) == MAL_STATE_STOPPING || mal_device__get_state(pDevice) == MAL_STATE_STOPPED) {
mal_stop_proc onStop = pDevice->onStop; mal_stop_proc onStop = pDevice->onStop;
if (onStop) { if (onStop) {
onStop(pDevice); onStop(pDevice);
...@@ -15920,12 +15930,14 @@ void on_start_stop__coreaudio(void* pUserData, AudioUnit audioUnit, AudioUnitPro ...@@ -15920,12 +15930,14 @@ void on_start_stop__coreaudio(void* pUserData, AudioUnit audioUnit, AudioUnitPro
// 2) When the device is changed via the default device change notification, this will be called _after_ the switch. // 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. // 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) { if (((audioUnit == pDevice->coreaudio.audioUnitPlayback) && pDevice->coreaudio.isDefaultPlaybackDevice) ||
((audioUnit == pDevice->coreaudio.audioUnitCapture) && pDevice->coreaudio.isDefaultCaptureDevice)) {
// It looks like the device is switching through an external event, such as the user unplugging the device or changing the default device // 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 // 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 // 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!). // hasn't!).
if (pDevice->coreaudio.isSwitchingDevice) { if (((audioUnit == pDevice->coreaudio.audioUnitPlayback) && pDevice->coreaudio.isSwitchingPlaybackDevice) ||
((audioUnit == pDevice->coreaudio.audioUnitCapture) && pDevice->coreaudio.isSwitchingCaptureDevice)) {
return; return;
} }
...@@ -15947,40 +15959,58 @@ void on_start_stop__coreaudio(void* pUserData, AudioUnit audioUnit, AudioUnitPro ...@@ -15947,40 +15959,58 @@ void on_start_stop__coreaudio(void* pUserData, AudioUnit audioUnit, AudioUnitPro
} }
#if defined(MAL_APPLE_DESKTOP) #if defined(MAL_APPLE_DESKTOP)
OSStatus mal_default_output_device_changed__coreaudio(AudioObjectID objectID, UInt32 addressCount, const AudioObjectPropertyAddress* pAddresses, void* pUserData) OSStatus mal_default_device_changed__coreaudio(AudioObjectID objectID, UInt32 addressCount, const AudioObjectPropertyAddress* pAddresses, void* pUserData)
{ {
(void)objectID; (void)objectID;
mal_device* pDevice = (mal_device*)pUserData; mal_device* pDevice = (mal_device*)pUserData;
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
if (pDevice->isDefaultDevice) {
// Not sure if I really need to check this, but it makes me feel better. // Not sure if I really need to check this, but it makes me feel better.
if (addressCount == 0) { if (addressCount == 0) {
return noErr; return noErr;
} }
if ((pDevice->type == mal_device_type_playback && pAddresses[0].mSelector == kAudioHardwarePropertyDefaultOutputDevice) || if (pAddresses[0].mSelector == kAudioHardwarePropertyDefaultOutputDevice) {
(pDevice->type == mal_device_type_capture && pAddresses[0].mSelector == kAudioHardwarePropertyDefaultInputDevice)) { pDevice->coreaudio.isSwitchingPlaybackDevice = MAL_TRUE;
#ifdef MAL_DEBUG_OUTPUT mal_result reinitResult = mal_device_reinit_internal__coreaudio(pDevice, mal_device_type_playback, MAL_TRUE);
printf("Device Changed: addressCount=%d, pAddresses[0].mElement=%d\n", addressCount, pAddresses[0].mElement); pDevice->coreaudio.isSwitchingPlaybackDevice = MAL_FALSE;
#endif
pDevice->coreaudio.isSwitchingDevice = MAL_TRUE;
mal_result reinitResult = mal_device_reinit_internal__coreaudio(pDevice, MAL_TRUE);
pDevice->coreaudio.isSwitchingDevice = MAL_FALSE;
if (reinitResult == MAL_SUCCESS) { if (reinitResult == MAL_SUCCESS) {
mal_device__post_init_setup(pDevice); mal_device__post_init_setup(pDevice, mal_device_type_playback);
// Make sure we resume the device if applicable. // Restart the device if required. If this fails we need to stop the device entirely.
if (mal_device__get_state(pDevice) == MAL_STATE_STARTED) { if (mal_device__get_state(pDevice) == MAL_STATE_STARTED) {
mal_result startResult = pDevice->pContext->onDeviceStart(pDevice); OSStatus status = ((mal_AudioOutputUnitStart_proc)pDevice->pContext->coreaudio.AudioOutputUnitStart)((AudioUnit)pDevice->coreaudio.audioUnitPlayback);
if (startResult != MAL_SUCCESS) { if (status != noErr) {
if (pDevice->type == mal_device_type_duplex) {
((mal_AudioOutputUnitStop_proc)pDevice->pContext->coreaudio.AudioOutputUnitStop)((AudioUnit)pDevice->coreaudio.audioUnitCapture);
}
mal_device__set_state(pDevice, MAL_STATE_STOPPED); mal_device__set_state(pDevice, MAL_STATE_STOPPED);
} }
} }
} }
} }
if (pAddresses[0].mSelector == kAudioHardwarePropertyDefaultInputDevice) {
pDevice->coreaudio.isSwitchingPlaybackDevice = MAL_TRUE;
mal_result reinitResult = mal_device_reinit_internal__coreaudio(pDevice, mal_device_type_capture, MAL_TRUE);
pDevice->coreaudio.isSwitchingPlaybackDevice = MAL_FALSE;
if (reinitResult == MAL_SUCCESS) {
mal_device__post_init_setup(pDevice, mal_device_type_capture);
// Restart the device if required. If this fails we need to stop the device entirely.
if (mal_device__get_state(pDevice) == MAL_STATE_STARTED) {
OSStatus status = ((mal_AudioOutputUnitStart_proc)pDevice->pContext->coreaudio.AudioOutputUnitStart)((AudioUnit)pDevice->coreaudio.audioUnitCapture);
if (status != noErr) {
if (pDevice->type == mal_device_type_duplex) {
((mal_AudioOutputUnitStop_proc)pDevice->pContext->coreaudio.AudioOutputUnitStop)((AudioUnit)pDevice->coreaudio.audioUnitPlayback);
}
mal_device__set_state(pDevice, MAL_STATE_STOPPED);
}
}
}
} }
return noErr; return noErr;
...@@ -16002,6 +16032,7 @@ typedef struct ...@@ -16002,6 +16032,7 @@ typedef struct
mal_bool32 usingDefaultSampleRate; mal_bool32 usingDefaultSampleRate;
mal_bool32 usingDefaultChannelMap; mal_bool32 usingDefaultChannelMap;
mal_share_mode shareMode; mal_share_mode shareMode;
mal_bool32 registerStopEvent;
// Output. // Output.
#if defined(MAL_APPLE_DESKTOP) #if defined(MAL_APPLE_DESKTOP)
...@@ -16019,7 +16050,7 @@ typedef struct ...@@ -16019,7 +16050,7 @@ typedef struct
char deviceName[256]; char deviceName[256];
} mal_device_init_internal_data__coreaudio; } mal_device_init_internal_data__coreaudio;
mal_result mal_device_init_internal__coreaudio(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pkDeviceID, mal_device_init_internal_data__coreaudio* pData, void* pDevice_DoNotReference) /* <-- pDevice is typed as void* intentionally so as to avoid accidentally referencing it. */ mal_result mal_device_init_internal__coreaudio(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, mal_device_init_internal_data__coreaudio* pData, void* pDevice_DoNotReference) /* <-- pDevice is typed as void* intentionally so as to avoid accidentally referencing it. */
{ {
/* This API should only be used for a single device type: playback or capture. No full-duplex mode. */ /* This API should only be used for a single device type: playback or capture. No full-duplex mode. */
if (deviceType == mal_device_type_duplex) { if (deviceType == mal_device_type_duplex) {
...@@ -16058,26 +16089,8 @@ mal_result mal_device_init_internal__coreaudio(mal_context* pContext, mal_device ...@@ -16058,26 +16089,8 @@ mal_result mal_device_init_internal__coreaudio(mal_context* pContext, mal_device
} }
// Audio component.
AudioComponentDescription desc;
desc.componentType = kAudioUnitType_Output;
#if defined(MAL_APPLE_DESKTOP)
desc.componentSubType = kAudioUnitSubType_HALOutput;
#else
desc.componentSubType = kAudioUnitSubType_RemoteIO;
#endif
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
pData->component = ((mal_AudioComponentFindNext_proc)pContext->coreaudio.AudioComponentFindNext)(NULL, &desc);
if (pData->component == NULL) {
return MAL_FAILED_TO_INIT_BACKEND;
}
// Audio unit. // Audio unit.
OSStatus status = ((mal_AudioComponentInstanceNew_proc)pContext->coreaudio.AudioComponentInstanceNew)(pData->component, (AudioUnit*)&pData->audioUnit); OSStatus status = ((mal_AudioComponentInstanceNew_proc)pContext->coreaudio.AudioComponentInstanceNew)(pContext->coreaudio.component, (AudioUnit*)&pData->audioUnit);
if (status != noErr) { if (status != noErr) {
return mal_result_from_OSStatus(status); return mal_result_from_OSStatus(status);
} }
...@@ -16326,11 +16339,13 @@ mal_result mal_device_init_internal__coreaudio(mal_context* pContext, mal_device ...@@ -16326,11 +16339,13 @@ mal_result mal_device_init_internal__coreaudio(mal_context* pContext, mal_device
} }
// We need to listen for stop events. // We need to listen for stop events.
if (pData->registerStopEvent) {
status = ((mal_AudioUnitAddPropertyListener_proc)pContext->coreaudio.AudioUnitAddPropertyListener)(pData->audioUnit, kAudioOutputUnitProperty_IsRunning, on_start_stop__coreaudio, pDevice_DoNotReference); status = ((mal_AudioUnitAddPropertyListener_proc)pContext->coreaudio.AudioUnitAddPropertyListener)(pData->audioUnit, kAudioOutputUnitProperty_IsRunning, on_start_stop__coreaudio, pDevice_DoNotReference);
if (status != noErr) { if (status != noErr) {
((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); ((mal_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit);
return mal_result_from_OSStatus(status); return mal_result_from_OSStatus(status);
} }
}
// Initialize the audio unit. // Initialize the audio unit.
status = ((mal_AudioUnitInitialize_proc)pContext->coreaudio.AudioUnitInitialize)(pData->audioUnit); status = ((mal_AudioUnitInitialize_proc)pContext->coreaudio.AudioUnitInitialize)(pData->audioUnit);
...@@ -16355,49 +16370,86 @@ mal_result mal_device_init_internal__coreaudio(mal_context* pContext, mal_device ...@@ -16355,49 +16370,86 @@ mal_result mal_device_init_internal__coreaudio(mal_context* pContext, mal_device
return result; return result;
} }
mal_result mal_device_reinit_internal__coreaudio(mal_device* pDevice, mal_bool32 disposePreviousAudioUnit) mal_result mal_device_reinit_internal__coreaudio(mal_device* pDevice, mal_device_type deviceType, mal_bool32 disposePreviousAudioUnit)
{ {
mal_device_init_internal_data__coreaudio data; /* This should only be called for playback or capture, not duplex. */
data.formatIn = pDevice->format; if (deviceType == mal_device_type_duplex) {
data.channelsIn = pDevice->channels; return MAL_INVALID_ARGS;
data.sampleRateIn = pDevice->sampleRate;
mal_copy_memory(data.channelMapIn, pDevice->channelMap, sizeof(pDevice->channelMap));
data.bufferSizeInFramesIn = pDevice->bufferSizeInFrames;
data.bufferSizeInMillisecondsIn = pDevice->bufferSizeInMilliseconds;
data.periodsIn = pDevice->periods;
data.usingDefaultFormat = pDevice->usingDefaultFormat;
data.usingDefaultChannels = pDevice->usingDefaultChannels;
data.usingDefaultSampleRate = pDevice->usingDefaultSampleRate;
data.usingDefaultChannelMap = pDevice->usingDefaultChannelMap;
data.shareMode = pDevice->initConfig.shareMode;
mal_result result = mal_device_init_internal__coreaudio(pDevice->pContext, pDevice->type, NULL, NULL, &data, (void*)pDevice);
if (result != MAL_SUCCESS) {
return result;
} }
// We have successfully initialized the new objects. We now need to uninitialize the previous objects and re-set them. mal_device_init_internal_data__coreaudio data;
if (deviceType == mal_device_type_capture) {
data.formatIn = pDevice->capture.format;
data.channelsIn = pDevice->capture.channels;
data.sampleRateIn = pDevice->capture.sampleRate;
mal_copy_memory(data.channelMapIn, pDevice->capture.channelMap, sizeof(pDevice->capture.channelMap));
data.usingDefaultFormat = pDevice->capture.usingDefaultFormat;
data.usingDefaultChannels = pDevice->capture.usingDefaultChannels;
data.usingDefaultSampleRate = pDevice->capture.usingDefaultSampleRate;
data.usingDefaultChannelMap = pDevice->capture.usingDefaultChannelMap;
data.shareMode = pDevice->capture.shareMode;
data.registerStopEvent = MAL_TRUE;
if (disposePreviousAudioUnit) { if (disposePreviousAudioUnit) {
pDevice->pContext->onDeviceStop(pDevice); ((mal_AudioOutputUnitStop_proc)pDevice->pContext->coreaudio.AudioOutputUnitStop)((AudioUnit)pDevice->coreaudio.audioUnitCapture);
((mal_AudioComponentInstanceDispose_proc)pDevice->pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); ((mal_AudioComponentInstanceDispose_proc)pDevice->pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnitCapture);
} }
if (pDevice->coreaudio.pAudioBufferList) { if (pDevice->coreaudio.pAudioBufferList) {
mal_free(pDevice->coreaudio.pAudioBufferList); mal_free(pDevice->coreaudio.pAudioBufferList);
} }
#if defined(MAL_APPLE_DESKTOP) #if defined(MAL_APPLE_DESKTOP)
pDevice->coreaudio.deviceObjectID = (mal_uint32)data.deviceObjectID; pDevice->coreaudio.deviceObjectIDCapture = (mal_uint32)data.deviceObjectID;
#endif #endif
pDevice->coreaudio.component = (mal_ptr)data.component; pDevice->coreaudio.audioUnitCapture = (mal_ptr)data.audioUnit;
pDevice->coreaudio.audioUnit = (mal_ptr)data.audioUnit;
pDevice->coreaudio.pAudioBufferList = (mal_ptr)data.pAudioBufferList; pDevice->coreaudio.pAudioBufferList = (mal_ptr)data.pAudioBufferList;
}
if (deviceType == mal_device_type_playback) {
data.formatIn = pDevice->playback.format;
data.channelsIn = pDevice->playback.channels;
data.sampleRateIn = pDevice->playback.sampleRate;
mal_copy_memory(data.channelMapIn, pDevice->playback.channelMap, sizeof(pDevice->playback.channelMap));
data.usingDefaultFormat = pDevice->playback.usingDefaultFormat;
data.usingDefaultChannels = pDevice->playback.usingDefaultChannels;
data.usingDefaultSampleRate = pDevice->playback.usingDefaultSampleRate;
data.usingDefaultChannelMap = pDevice->playback.usingDefaultChannelMap;
data.shareMode = pDevice->playback.shareMode;
data.registerStopEvent = (pDevice->type != mal_device_type_duplex);
pDevice->internalFormat = data.formatOut; if (disposePreviousAudioUnit) {
pDevice->internalChannels = data.channelsOut; ((mal_AudioOutputUnitStop_proc)pDevice->pContext->coreaudio.AudioOutputUnitStop)((AudioUnit)pDevice->coreaudio.audioUnitPlayback);
pDevice->internalSampleRate = data.sampleRateOut; ((mal_AudioComponentInstanceDispose_proc)pDevice->pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnitPlayback);
mal_copy_memory(pDevice->internalChannelMap, data.channelMapOut, sizeof(data.channelMapOut)); }
pDevice->bufferSizeInFrames = data.bufferSizeInFramesOut;
pDevice->periods = data.periodsOut; #if defined(MAL_APPLE_DESKTOP)
pDevice->coreaudio.deviceObjectIDPlayback = (mal_uint32)data.deviceObjectID;
#endif
pDevice->coreaudio.audioUnitPlayback = (mal_ptr)data.audioUnit;
}
data.bufferSizeInFramesIn = pDevice->bufferSizeInFrames;
data.periodsIn = pDevice->periods;
mal_result result = mal_device_init_internal__coreaudio(pDevice->pContext, deviceType, NULL, &data, (void*)pDevice);
if (result != MAL_SUCCESS) {
return result;
}
// TEMP. BACKWARDS COMPATIBILITY.
if (pDevice->type == mal_device_type_capture || pDevice->type == mal_device_type_duplex) {
pDevice->internalFormat = pDevice->capture.internalFormat;
pDevice->internalChannels = pDevice->capture.internalChannels;
pDevice->internalSampleRate = pDevice->capture.internalSampleRate;
mal_copy_memory(pDevice->internalChannelMap, pDevice->capture.channelMap, sizeof(pDevice->capture.channelMap));
pDevice->bufferSizeInFrames = pDevice->capture.internalBufferSizeInFrames;
pDevice->periods = pDevice->capture.internalPeriods;
} else {
pDevice->internalFormat = pDevice->playback.internalFormat;
pDevice->internalChannels = pDevice->playback.internalChannels;
pDevice->internalSampleRate = pDevice->playback.internalSampleRate;
mal_copy_memory(pDevice->internalChannelMap, pDevice->playback.channelMap, sizeof(pDevice->playback.channelMap));
pDevice->bufferSizeInFrames = pDevice->playback.internalBufferSizeInFrames;
pDevice->periods = pDevice->playback.internalPeriods;
}
return MAL_SUCCESS; return MAL_SUCCESS;
} }
...@@ -16412,59 +16464,135 @@ mal_result mal_device_init__coreaudio(mal_context* pContext, const mal_device_co ...@@ -16412,59 +16464,135 @@ mal_result mal_device_init__coreaudio(mal_context* pContext, const mal_device_co
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
/* No exclusive mode with the Core Audio backend for now. */ /* No exclusive mode with the Core Audio backend for now. */
if (pConfig->shareMode == mal_share_mode_exclusive) { if (((pConfig->deviceType == mal_device_type_capture || pConfig->deviceType == mal_device_type_duplex) && pConfig->capture.shareMode == mal_share_mode_exclusive) ||
((pConfig->deviceType == mal_device_type_playback || pConfig->deviceType == mal_device_type_duplex) && pConfig->playback.shareMode == mal_share_mode_exclusive)) {
return MAL_SHARE_MODE_NOT_SUPPORTED; return MAL_SHARE_MODE_NOT_SUPPORTED;
} }
/* Capture needs to be initialized first. */
if (pConfig->deviceType == mal_device_type_capture || pConfig->deviceType == mal_device_type_duplex) {
mal_device_init_internal_data__coreaudio data; mal_device_init_internal_data__coreaudio data;
data.formatIn = pDevice->format; data.formatIn = pConfig->capture.format;
data.channelsIn = pDevice->channels; data.channelsIn = pConfig->capture.channels;
data.sampleRateIn = pDevice->sampleRate; data.sampleRateIn = pConfig->sampleRate;
mal_copy_memory(data.channelMapIn, pDevice->channelMap, sizeof(pDevice->channelMap)); mal_copy_memory(data.channelMapIn, pConfig->capture.channelMap, sizeof(pConfig->capture.channelMap));
data.bufferSizeInFramesIn = pDevice->bufferSizeInFrames; data.usingDefaultFormat = pDevice->capture.usingDefaultFormat;
data.bufferSizeInMillisecondsIn = pDevice->bufferSizeInMilliseconds; data.usingDefaultChannels = pDevice->capture.usingDefaultChannels;
data.periodsIn = pDevice->periods; data.usingDefaultSampleRate = pDevice->capture.usingDefaultSampleRate;
data.usingDefaultFormat = pDevice->usingDefaultFormat; data.usingDefaultChannelMap = pDevice->capture.usingDefaultChannelMap;
data.usingDefaultChannels = pDevice->usingDefaultChannels; data.shareMode = pConfig->capture.shareMode;
data.usingDefaultSampleRate = pDevice->usingDefaultSampleRate; data.bufferSizeInFramesIn = pConfig->bufferSizeInFrames;
data.usingDefaultChannelMap = pDevice->usingDefaultChannelMap; data.bufferSizeInMillisecondsIn = pConfig->bufferSizeInMilliseconds;
data.shareMode = pDevice->initConfig.shareMode;
mal_result result = mal_device_init_internal__coreaudio(pDevice->pContext, pConfig->deviceType, pConfig->playback.pDeviceID, pConfig->capture.pDeviceID, &data, (void*)pDevice); mal_result result = mal_device_init_internal__coreaudio(pDevice->pContext, mal_device_type_capture, pConfig->capture.pDeviceID, &data, (void*)pDevice);
if (result != MAL_SUCCESS) { if (result != MAL_SUCCESS) {
return result; return result;
} }
// We have successfully initialized the new objects. We now need to uninitialize the previous objects and re-set them. pDevice->coreaudio.isDefaultCaptureDevice = (pConfig->capture.pDeviceID == NULL);
pDevice->pContext->onDeviceStop(pDevice); #if defined(MAL_APPLE_DESKTOP)
((mal_AudioComponentInstanceDispose_proc)pDevice->pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnit); pDevice->coreaudio.deviceObjectIDCapture = (mal_uint32)data.deviceObjectID;
#endif
pDevice->coreaudio.audioUnitCapture = (mal_ptr)data.audioUnit;
pDevice->coreaudio.pAudioBufferList = (mal_ptr)data.pAudioBufferList;
pDevice->capture.internalFormat = data.formatOut;
pDevice->capture.internalChannels = data.channelsOut;
pDevice->capture.internalSampleRate = data.sampleRateOut;
mal_copy_memory(pDevice->capture.internalChannelMap, data.channelMapOut, sizeof(data.channelMapOut));
pDevice->capture.internalBufferSizeInFrames = data.bufferSizeInFramesOut;
pDevice->capture.internalPeriods = data.periodsOut;
// TODO: This needs to be made global.
#if defined(MAL_APPLE_DESKTOP)
// If we are using the default device we'll need to listen for changes to the system's default device so we can seemlessly
// switch the device in the background.
if (pConfig->capture.pDeviceID == NULL) {
AudioObjectPropertyAddress propAddress;
propAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
propAddress.mScope = kAudioObjectPropertyScopeGlobal;
propAddress.mElement = kAudioObjectPropertyElementMaster;
((mal_AudioObjectAddPropertyListener_proc)pDevice->pContext->coreaudio.AudioObjectAddPropertyListener)(kAudioObjectSystemObject, &propAddress, &mal_default_device_changed__coreaudio, pDevice);
}
#endif
}
/* Playback. */
if (pConfig->deviceType == mal_device_type_playback || pConfig->deviceType == mal_device_type_duplex) {
mal_device_init_internal_data__coreaudio data;
data.formatIn = pConfig->playback.format;
data.channelsIn = pConfig->playback.channels;
data.sampleRateIn = pConfig->sampleRate;
mal_copy_memory(data.channelMapIn, pConfig->playback.channelMap, sizeof(pConfig->playback.channelMap));
data.usingDefaultFormat = pDevice->playback.usingDefaultFormat;
data.usingDefaultChannels = pDevice->playback.usingDefaultChannels;
data.usingDefaultSampleRate = pDevice->playback.usingDefaultSampleRate;
data.usingDefaultChannelMap = pDevice->playback.usingDefaultChannelMap;
data.shareMode = pConfig->playback.shareMode;
/* In full-duplex mode we want the playback buffer to be the same size as the capture buffer. */
if (pConfig->deviceType == mal_device_type_duplex) {
data.bufferSizeInFramesIn = pDevice->capture.internalBufferSizeInFrames;
data.periodsIn = pDevice->capture.internalPeriods;
} else {
data.bufferSizeInFramesIn = pConfig->bufferSizeInFrames;
data.bufferSizeInMillisecondsIn = pConfig->bufferSizeInMilliseconds;
data.periodsIn = pConfig->periods;
}
mal_result result = mal_device_init_internal__coreaudio(pDevice->pContext, mal_device_type_playback, pConfig->playback.pDeviceID, &data, (void*)pDevice);
if (result != MAL_SUCCESS) {
if (pConfig->deviceType == mal_device_type_duplex) {
((mal_AudioComponentInstanceDispose_proc)pDevice->pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnitCapture);
if (pDevice->coreaudio.pAudioBufferList) { if (pDevice->coreaudio.pAudioBufferList) {
mal_free(pDevice->coreaudio.pAudioBufferList); mal_free(pDevice->coreaudio.pAudioBufferList);
} }
}
return result;
}
#if defined(MAL_APPLE_DESKTOP) pDevice->coreaudio.isDefaultPlaybackDevice = (pConfig->playback.pDeviceID == NULL);
pDevice->coreaudio.deviceObjectID = (mal_uint32)data.deviceObjectID; #if defined(MAL_APPLE_DESKTOP)
#endif pDevice->coreaudio.deviceObjectIDPlayback = (mal_uint32)data.deviceObjectID;
pDevice->coreaudio.component = (mal_ptr)data.component; #endif
pDevice->coreaudio.audioUnit = (mal_ptr)data.audioUnit; pDevice->coreaudio.audioUnitPlayback = (mal_ptr)data.audioUnit;
pDevice->coreaudio.pAudioBufferList = (mal_ptr)data.pAudioBufferList;
pDevice->internalFormat = data.formatOut; pDevice->playback.internalFormat = data.formatOut;
pDevice->internalChannels = data.channelsOut; pDevice->playback.internalChannels = data.channelsOut;
pDevice->internalSampleRate = data.sampleRateOut; pDevice->playback.internalSampleRate = data.sampleRateOut;
mal_copy_memory(pDevice->internalChannelMap, data.channelMapOut, sizeof(data.channelMapOut)); mal_copy_memory(pDevice->playback.internalChannelMap, data.channelMapOut, sizeof(data.channelMapOut));
pDevice->bufferSizeInFrames = data.bufferSizeInFramesOut; pDevice->playback.internalBufferSizeInFrames = data.bufferSizeInFramesOut;
pDevice->periods = data.periodsOut; pDevice->playback.internalPeriods = data.periodsOut;
#if defined(MAL_APPLE_DESKTOP) // TODO: This needs to be made global.
#if defined(MAL_APPLE_DESKTOP)
// If we are using the default device we'll need to listen for changes to the system's default device so we can seemlessly // If we are using the default device we'll need to listen for changes to the system's default device so we can seemlessly
// switch the device in the background. // switch the device in the background.
if (pConfig->playback.pDeviceID == NULL) {
AudioObjectPropertyAddress propAddress; AudioObjectPropertyAddress propAddress;
propAddress.mSelector = (deviceType == mal_device_type_playback) ? kAudioHardwarePropertyDefaultOutputDevice : kAudioHardwarePropertyDefaultInputDevice; propAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
propAddress.mScope = kAudioObjectPropertyScopeGlobal; propAddress.mScope = kAudioObjectPropertyScopeGlobal;
propAddress.mElement = kAudioObjectPropertyElementMaster; propAddress.mElement = kAudioObjectPropertyElementMaster;
((mal_AudioObjectAddPropertyListener_proc)pDevice->pContext->coreaudio.AudioObjectAddPropertyListener)(kAudioObjectSystemObject, &propAddress, &mal_default_output_device_changed__coreaudio, pDevice); ((mal_AudioObjectAddPropertyListener_proc)pDevice->pContext->coreaudio.AudioObjectAddPropertyListener)(kAudioObjectSystemObject, &propAddress, &mal_default_device_changed__coreaudio, pDevice);
#endif }
#endif
}
if (pDevice->type == mal_device_type_capture || pDevice->type == mal_device_type_duplex) {
pDevice->internalFormat = pDevice->capture.internalFormat;
pDevice->internalChannels = pDevice->capture.internalChannels;
pDevice->internalSampleRate = pDevice->capture.internalSampleRate;
mal_copy_memory(pDevice->internalChannelMap, pDevice->capture.channelMap, sizeof(pDevice->capture.channelMap));
pDevice->bufferSizeInFrames = pDevice->capture.internalBufferSizeInFrames;
pDevice->periods = pDevice->capture.internalPeriods;
} else {
pDevice->internalFormat = pDevice->playback.internalFormat;
pDevice->internalChannels = pDevice->playback.internalChannels;
pDevice->internalSampleRate = pDevice->playback.internalSampleRate;
mal_copy_memory(pDevice->internalChannelMap, pDevice->playback.channelMap, sizeof(pDevice->playback.channelMap));
pDevice->bufferSizeInFrames = pDevice->playback.internalBufferSizeInFrames;
pDevice->periods = pDevice->playback.internalPeriods;
}
/* /*
When stopping the device, a callback is called on another thread. We need to wait for this callback When stopping the device, a callback is called on another thread. We need to wait for this callback
...@@ -16480,10 +16608,22 @@ mal_result mal_device_start__coreaudio(mal_device* pDevice) ...@@ -16480,10 +16608,22 @@ mal_result mal_device_start__coreaudio(mal_device* pDevice)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
OSStatus status = ((mal_AudioOutputUnitStart_proc)pDevice->pContext->coreaudio.AudioOutputUnitStart)((AudioUnit)pDevice->coreaudio.audioUnit); if (pDevice->type == mal_device_type_capture || pDevice->type == mal_device_type_duplex) {
OSStatus status = ((mal_AudioOutputUnitStart_proc)pDevice->pContext->coreaudio.AudioOutputUnitStart)((AudioUnit)pDevice->coreaudio.audioUnitCapture);
if (status != noErr) { if (status != noErr) {
return mal_result_from_OSStatus(status); return mal_result_from_OSStatus(status);
} }
}
if (pDevice->type == mal_device_type_playback || pDevice->type == mal_device_type_duplex) {
OSStatus status = ((mal_AudioOutputUnitStart_proc)pDevice->pContext->coreaudio.AudioOutputUnitStart)((AudioUnit)pDevice->coreaudio.audioUnitPlayback);
if (status != noErr) {
if (pDevice->type == mal_device_type_duplex) {
((mal_AudioOutputUnitStop_proc)pDevice->pContext->coreaudio.AudioOutputUnitStop)((AudioUnit)pDevice->coreaudio.audioUnitCapture);
}
return mal_result_from_OSStatus(status);
}
}
return MAL_SUCCESS; return MAL_SUCCESS;
} }
...@@ -16492,10 +16632,19 @@ mal_result mal_device_stop__coreaudio(mal_device* pDevice) ...@@ -16492,10 +16632,19 @@ mal_result mal_device_stop__coreaudio(mal_device* pDevice)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
OSStatus status = ((mal_AudioOutputUnitStop_proc)pDevice->pContext->coreaudio.AudioOutputUnitStop)((AudioUnit)pDevice->coreaudio.audioUnit); if (pDevice->type == mal_device_type_capture || pDevice->type == mal_device_type_duplex) {
OSStatus status = ((mal_AudioOutputUnitStop_proc)pDevice->pContext->coreaudio.AudioOutputUnitStop)((AudioUnit)pDevice->coreaudio.audioUnitCapture);
if (status != noErr) {
return mal_result_from_OSStatus(status);
}
}
if (pDevice->type == mal_device_type_playback || pDevice->type == mal_device_type_duplex) {
OSStatus status = ((mal_AudioOutputUnitStop_proc)pDevice->pContext->coreaudio.AudioOutputUnitStop)((AudioUnit)pDevice->coreaudio.audioUnitPlayback);
if (status != noErr) { if (status != noErr) {
return mal_result_from_OSStatus(status); return mal_result_from_OSStatus(status);
} }
}
/* We need to wait for the callback to finish before returning. */ /* We need to wait for the callback to finish before returning. */
mal_event_wait(&pDevice->coreaudio.stopEvent); mal_event_wait(&pDevice->coreaudio.stopEvent);
...@@ -16626,6 +16775,28 @@ mal_result mal_context_init__coreaudio(mal_context* pContext) ...@@ -16626,6 +16775,28 @@ mal_result mal_context_init__coreaudio(mal_context* pContext)
pContext->onDeviceStart = mal_device_start__coreaudio; pContext->onDeviceStart = mal_device_start__coreaudio;
pContext->onDeviceStop = mal_device_stop__coreaudio; pContext->onDeviceStop = mal_device_stop__coreaudio;
// Audio component.
AudioComponentDescription desc;
desc.componentType = kAudioUnitType_Output;
#if defined(MAL_APPLE_DESKTOP)
desc.componentSubType = kAudioUnitSubType_HALOutput;
#else
desc.componentSubType = kAudioUnitSubType_RemoteIO;
#endif
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
pContext->coreaudio.component = ((mal_AudioComponentFindNext_proc)pContext->coreaudio.AudioComponentFindNext)(NULL, &desc);
if (pContext->coreaudio.component == NULL) {
#if !defined(MAL_NO_RUNTIME_LINKING) && !defined(MAL_APPLE_MOBILE)
mal_dlclose(pContext->coreaudio.hAudioUnit);
mal_dlclose(pContext->coreaudio.hCoreAudio);
mal_dlclose(pContext->coreaudio.hCoreFoundation);
#endif
return MAL_FAILED_TO_INIT_BACKEND;
}
return MAL_SUCCESS; return MAL_SUCCESS;
} }
#endif // Core Audio #endif // Core Audio
...@@ -20279,10 +20450,15 @@ mal_bool32 mal__is_channel_map_valid(const mal_channel* channelMap, mal_uint32 c ...@@ -20279,10 +20450,15 @@ mal_bool32 mal__is_channel_map_valid(const mal_channel* channelMap, mal_uint32 c
} }
void mal_device__post_init_setup(mal_device* pDevice) void mal_device__post_init_setup(mal_device* pDevice, mal_device_type deviceType)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
// TODO: This needs to be updated to support full duplex:
// - When deviceType = mal_device_type_capture then update the capture PCM converter.
// - When deviceType = mal_device_type_playback then update the playback PCM converter.
// - When deviceType = mal_device_type_duplex then update both the capture and playback PCM converters.
// Make sure the internal channel map was set correctly by the backend. If it's not valid, just fall back to defaults. // Make sure the internal channel map was set correctly by the backend. If it's not valid, just fall back to defaults.
if (!mal_channel_map_valid(pDevice->internalChannels, pDevice->internalChannelMap)) { if (!mal_channel_map_valid(pDevice->internalChannels, pDevice->internalChannelMap)) {
mal_get_standard_channel_map(mal_standard_channel_map_default, pDevice->internalChannels, pDevice->internalChannelMap); mal_get_standard_channel_map(mal_standard_channel_map_default, pDevice->internalChannels, pDevice->internalChannelMap);
...@@ -20978,13 +21154,6 @@ mal_result mal_device_init(mal_context* pContext, const mal_device_config* pConf ...@@ -20978,13 +21154,6 @@ mal_result mal_device_init(mal_context* pContext, const mal_device_config* pConf
} }
} }
/* TODO: This is only used in Core Audio. Move this to the Core Audio backend. Also, this logic does not handle full-duplex devices properly */
if (config.playback.pDeviceID == NULL || config.capture.pDeviceID == NULL) {
pDevice->isDefaultDevice = MAL_TRUE;
}
// When passing in 0 for the format/channels/rate/chmap it means the device will be using whatever is chosen by the backend. If everything is set // When passing in 0 for the format/channels/rate/chmap it means the device will be using whatever is chosen by the backend. If everything is set
// to defaults it means the format conversion pipeline will run on a fast path where data transfer is just passed straight through to the backend. // to defaults it means the format conversion pipeline will run on a fast path where data transfer is just passed straight through to the backend.
if (config.format == mal_format_unknown) { if (config.format == mal_format_unknown) {
...@@ -21062,7 +21231,7 @@ mal_result mal_device_init(mal_context* pContext, const mal_device_config* pConf ...@@ -21062,7 +21231,7 @@ mal_result mal_device_init(mal_context* pContext, const mal_device_config* pConf
return MAL_NO_BACKEND; // The error message will have been posted with mal_post_error() by the source of the error so don't bother calling it here. return MAL_NO_BACKEND; // The error message will have been posted with mal_post_error() by the source of the error so don't bother calling it here.
} }
mal_device__post_init_setup(pDevice); mal_device__post_init_setup(pDevice, pConfig->deviceType);
// If the backend did not fill out a name for the device, try a generic method. // If the backend did not fill out a name for the device, try a generic method.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment