Core Audio: Include format information in device enumeration.

This commit is contained in:
David Reid
2025-07-20 18:23:31 +10:00
parent e49fc58584
commit c62dc472eb
+231 -261
View File
@@ -35551,118 +35551,30 @@ static void ma_AVAudioSessionPortDescription_to_device_info(AVAudioSessionPortDe
} }
#endif #endif
static ma_result ma_context_enumerate_devices__coreaudio(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData)
{
#if defined(MA_APPLE_DESKTOP) #if defined(MA_APPLE_DESKTOP)
UInt32 deviceCount; static ma_bool32 ma_context_enumerate_device_by_AudioObjectID__coreaudio(ma_context* pContext, AudioObjectID deviceObjectID, AudioObjectID defaultDeviceObjectID, ma_device_type deviceType, ma_enum_devices_callback_proc callback, void* pUserData)
AudioObjectID* pDeviceObjectIDs;
AudioObjectID defaultDeviceObjectIDPlayback;
AudioObjectID defaultDeviceObjectIDCapture;
ma_result result;
UInt32 iDevice;
ma_find_default_AudioObjectID(pContext, ma_device_type_playback, &defaultDeviceObjectIDPlayback); /* OK if this fails. */
ma_find_default_AudioObjectID(pContext, ma_device_type_capture, &defaultDeviceObjectIDCapture); /* OK if this fails. */
result = ma_get_device_object_ids__coreaudio(pContext, &deviceCount, &pDeviceObjectIDs);
if (result != MA_SUCCESS) {
return result;
}
for (iDevice = 0; iDevice < deviceCount; ++iDevice) {
AudioObjectID deviceObjectID = pDeviceObjectIDs[iDevice];
ma_device_info info;
MA_ZERO_OBJECT(&info);
if (ma_get_AudioObject_uid(pContext, deviceObjectID, sizeof(info.id.coreaudio), info.id.coreaudio) != MA_SUCCESS) {
continue;
}
if (ma_get_AudioObject_name(pContext, deviceObjectID, sizeof(info.name), info.name) != MA_SUCCESS) {
continue;
}
if (ma_does_AudioObject_support_playback(pContext, deviceObjectID)) {
if (deviceObjectID == defaultDeviceObjectIDPlayback) {
info.isDefault = MA_TRUE;
}
if (!callback(ma_device_type_playback, &info, pUserData)) {
break;
}
}
if (ma_does_AudioObject_support_capture(pContext, deviceObjectID)) {
if (deviceObjectID == defaultDeviceObjectIDCapture) {
info.isDefault = MA_TRUE;
}
if (!callback(ma_device_type_capture, &info, pUserData)) {
break;
}
}
}
ma_free(pDeviceObjectIDs, &pContext->allocationCallbacks);
#else
ma_device_info info;
NSArray *pInputs = [[[AVAudioSession sharedInstance] currentRoute] inputs];
NSArray *pOutputs = [[[AVAudioSession sharedInstance] currentRoute] outputs];
for (AVAudioSessionPortDescription* pPortDesc in pOutputs) {
ma_AVAudioSessionPortDescription_to_device_info(pPortDesc, &info);
if (!callback(ma_device_type_playback, &info, pUserData)) {
return MA_SUCCESS;
}
}
for (AVAudioSessionPortDescription* pPortDesc in pInputs) {
ma_AVAudioSessionPortDescription_to_device_info(pPortDesc, &info);
if (!callback(ma_device_type_capture, &info, pUserData)) {
return MA_SUCCESS;
}
}
#endif
return MA_SUCCESS;
}
static ma_result ma_context_get_device_info__coreaudio(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo)
{ {
ma_result result; ma_device_info deviceInfo;
MA_ASSERT(pContext != NULL); MA_ZERO_OBJECT(&deviceInfo);
#if defined(MA_APPLE_DESKTOP) /* Default. */
/* Desktop */ if (deviceObjectID == defaultDeviceObjectID) {
deviceInfo.isDefault = MA_TRUE;
}
/* ID. */
if (ma_get_AudioObject_uid(pContext, deviceObjectID, sizeof(deviceInfo.id.coreaudio), deviceInfo.id.coreaudio) != MA_SUCCESS) {
return MA_TRUE;
}
/* Name. */
if (ma_get_AudioObject_name(pContext, deviceObjectID, sizeof(deviceInfo.name), deviceInfo.name) != MA_SUCCESS) {
return MA_TRUE;
}
/* Data Format. */
{ {
AudioObjectID deviceObjectID;
AudioObjectID defaultDeviceObjectID;
UInt32 streamDescriptionCount;
AudioStreamRangedDescription* pStreamDescriptions;
UInt32 iStreamDescription;
UInt32 sampleRateRangeCount;
AudioValueRange* pSampleRateRanges;
ma_find_default_AudioObjectID(pContext, deviceType, &defaultDeviceObjectID); /* OK if this fails. */
result = ma_find_AudioObjectID(pContext, deviceType, pDeviceID, &deviceObjectID);
if (result != MA_SUCCESS) {
return result;
}
result = ma_get_AudioObject_uid(pContext, deviceObjectID, sizeof(pDeviceInfo->id.coreaudio), pDeviceInfo->id.coreaudio);
if (result != MA_SUCCESS) {
return result;
}
result = ma_get_AudioObject_name(pContext, deviceObjectID, sizeof(pDeviceInfo->name), pDeviceInfo->name);
if (result != MA_SUCCESS) {
return result;
}
if (deviceObjectID == defaultDeviceObjectID) {
pDeviceInfo->isDefault = MA_TRUE;
}
/* /*
There could be a large number of permutations here. Fortunately there is only a single channel count There could be a large number of permutations here. Fortunately there is only a single channel count
being reported which reduces this quite a bit. For sample rates we're only reporting those that are being reported which reduces this quite a bit. For sample rates we're only reporting those that are
@@ -35671,200 +35583,258 @@ static ma_result ma_context_get_device_info__coreaudio(ma_context* pContext, ma_
if some driver performs software data conversion and therefore reports every possible format and if some driver performs software data conversion and therefore reports every possible format and
sample rate. sample rate.
*/ */
pDeviceInfo->nativeDataFormatCount = 0; ma_result result;
ma_format uniqueFormats[ma_format_count];
ma_uint32 uniqueFormatCount = 0;
ma_uint32 channels;
UInt32 streamDescriptionCount;
AudioStreamRangedDescription* pStreamDescriptions;
UInt32 iStreamDescription;
deviceInfo.nativeDataFormatCount = 0;
/* Channels. */
result = ma_get_AudioObject_channel_count(pContext, deviceObjectID, deviceType, &channels);
if (result != MA_SUCCESS) {
return MA_TRUE; /* Failed to get channel count. */
}
/* Formats. */ /* Formats. */
{ result = ma_get_AudioObject_stream_descriptions(pContext, deviceObjectID, deviceType, &streamDescriptionCount, &pStreamDescriptions);
ma_format uniqueFormats[ma_format_count]; if (result != MA_SUCCESS) {
ma_uint32 uniqueFormatCount = 0; return MA_TRUE;
ma_uint32 channels; }
/* Channels. */ for (iStreamDescription = 0; iStreamDescription < streamDescriptionCount; ++iStreamDescription) {
result = ma_get_AudioObject_channel_count(pContext, deviceObjectID, deviceType, &channels); ma_format format;
ma_bool32 hasFormatBeenHandled = MA_FALSE;
ma_uint32 iOutputFormat;
ma_uint32 iSampleRate;
UInt32 sampleRateRangeCount;
AudioValueRange* pSampleRateRanges;
result = ma_format_from_AudioStreamBasicDescription(&pStreamDescriptions[iStreamDescription].mFormat, &format);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
return result; continue;
} }
/* Formats. */ MA_ASSERT(format != ma_format_unknown);
result = ma_get_AudioObject_stream_descriptions(pContext, deviceObjectID, deviceType, &streamDescriptionCount, &pStreamDescriptions);
if (result != MA_SUCCESS) { /* Make sure the format isn't already in the output list. */
return result; for (iOutputFormat = 0; iOutputFormat < uniqueFormatCount; ++iOutputFormat) {
if (uniqueFormats[iOutputFormat] == format) {
hasFormatBeenHandled = MA_TRUE;
break;
}
} }
for (iStreamDescription = 0; iStreamDescription < streamDescriptionCount; ++iStreamDescription) { /* If we've already handled this format just skip it. */
ma_format format; if (hasFormatBeenHandled) {
ma_bool32 hasFormatBeenHandled = MA_FALSE; continue;
ma_uint32 iOutputFormat; }
ma_uint32 iSampleRate;
result = ma_format_from_AudioStreamBasicDescription(&pStreamDescriptions[iStreamDescription].mFormat, &format); uniqueFormats[uniqueFormatCount] = format;
if (result != MA_SUCCESS) { uniqueFormatCount += 1;
continue;
}
MA_ASSERT(format != ma_format_unknown); /* Sample Rates */
result = ma_get_AudioObject_sample_rates(pContext, deviceObjectID, deviceType, &sampleRateRangeCount, &pSampleRateRanges);
if (result != MA_SUCCESS) {
return MA_TRUE; /* Failed to retrieve sample rates. */
}
/* Make sure the format isn't already in the output list. */ /*
for (iOutputFormat = 0; iOutputFormat < uniqueFormatCount; ++iOutputFormat) { Annoyingly Core Audio reports a sample rate range. We just get all the standard rates that are
if (uniqueFormats[iOutputFormat] == format) { between this range.
hasFormatBeenHandled = MA_TRUE; */
break; for (iSampleRate = 0; iSampleRate < sampleRateRangeCount; ++iSampleRate) {
} ma_uint32 iStandardSampleRate;
} for (iStandardSampleRate = 0; iStandardSampleRate < ma_countof(g_maStandardSampleRatePriorities); iStandardSampleRate += 1) {
ma_uint32 standardSampleRate = g_maStandardSampleRatePriorities[iStandardSampleRate];
if (standardSampleRate >= pSampleRateRanges[iSampleRate].mMinimum && standardSampleRate <= pSampleRateRanges[iSampleRate].mMaximum) {
/* We have a new data format. Add it to the list. */
deviceInfo.nativeDataFormats[deviceInfo.nativeDataFormatCount].format = format;
deviceInfo.nativeDataFormats[deviceInfo.nativeDataFormatCount].channels = channels;
deviceInfo.nativeDataFormats[deviceInfo.nativeDataFormatCount].sampleRate = standardSampleRate;
deviceInfo.nativeDataFormats[deviceInfo.nativeDataFormatCount].flags = 0;
deviceInfo.nativeDataFormatCount += 1;
/* If we've already handled this format just skip it. */ if (deviceInfo.nativeDataFormatCount >= ma_countof(deviceInfo.nativeDataFormats)) {
if (hasFormatBeenHandled) { break; /* No more room for any more formats. */
continue;
}
uniqueFormats[uniqueFormatCount] = format;
uniqueFormatCount += 1;
/* Sample Rates */
result = ma_get_AudioObject_sample_rates(pContext, deviceObjectID, deviceType, &sampleRateRangeCount, &pSampleRateRanges);
if (result != MA_SUCCESS) {
return result;
}
/*
Annoyingly Core Audio reports a sample rate range. We just get all the standard rates that are
between this range.
*/
for (iSampleRate = 0; iSampleRate < sampleRateRangeCount; ++iSampleRate) {
ma_uint32 iStandardSampleRate;
for (iStandardSampleRate = 0; iStandardSampleRate < ma_countof(g_maStandardSampleRatePriorities); iStandardSampleRate += 1) {
ma_uint32 standardSampleRate = g_maStandardSampleRatePriorities[iStandardSampleRate];
if (standardSampleRate >= pSampleRateRanges[iSampleRate].mMinimum && standardSampleRate <= pSampleRateRanges[iSampleRate].mMaximum) {
/* We have a new data format. Add it to the list. */
pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].format = format;
pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].channels = channels;
pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].sampleRate = standardSampleRate;
pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].flags = 0;
pDeviceInfo->nativeDataFormatCount += 1;
if (pDeviceInfo->nativeDataFormatCount >= ma_countof(pDeviceInfo->nativeDataFormats)) {
break; /* No more room for any more formats. */
}
} }
} }
} }
ma_free(pSampleRateRanges, &pContext->allocationCallbacks);
if (pDeviceInfo->nativeDataFormatCount >= ma_countof(pDeviceInfo->nativeDataFormats)) {
break; /* No more room for any more formats. */
}
} }
ma_free(pStreamDescriptions, &pContext->allocationCallbacks); ma_free(pSampleRateRanges, &pContext->allocationCallbacks);
if (deviceInfo.nativeDataFormatCount >= ma_countof(deviceInfo.nativeDataFormats)) {
break; /* No more room for any more formats. */
}
} }
ma_free(pStreamDescriptions, &pContext->allocationCallbacks);
} }
return callback(deviceType, &deviceInfo, pUserData);
}
#else #else
/* Mobile */ static ma_bool32 ma_context_enumerate_device_by_AVAudioSessionPortDescription__coreaudio(ma_context* pContext, AVAudioSessionPortDescription* pPortDesc, ma_device_type deviceType, ma_enum_devices_callback_proc callback, void* pUserData)
{ {
ma_context_state_coreaudio* pContextStateCoreAudio = ma_context_get_backend_state__coreaudio(pContext); ma_context_state_coreaudio* pContextStateCoreAudio = ma_context_get_backend_state__coreaudio(pContext);
AudioComponentDescription desc; ma_device_info deviceInfo;
AudioComponent component; AudioComponentDescription desc;
AudioUnit audioUnit; AudioComponent component;
OSStatus status; AudioUnit audioUnit;
AudioUnitScope formatScope; OSStatus status;
AudioUnitElement formatElement; AudioUnitScope formatScope;
AudioStreamBasicDescription bestFormat; AudioUnitElement formatElement;
UInt32 propSize; AudioStreamBasicDescription bestFormat;
UInt32 propSize;
/* We want to ensure we use a consistent device name to device enumeration. */ MA_ZERO_OBJECT(&deviceInfo);
if (pDeviceID != NULL && pDeviceID->coreaudio[0] != '\0') {
ma_bool32 found = MA_FALSE;
if (deviceType == ma_device_type_playback) {
NSArray *pOutputs = [[[AVAudioSession sharedInstance] currentRoute] outputs];
for (AVAudioSessionPortDescription* pPortDesc in pOutputs) {
if (strcmp(pDeviceID->coreaudio, [pPortDesc.UID UTF8String]) == 0) {
ma_AVAudioSessionPortDescription_to_device_info(pPortDesc, pDeviceInfo);
found = MA_TRUE;
break;
}
}
} else {
NSArray *pInputs = [[[AVAudioSession sharedInstance] currentRoute] inputs];
for (AVAudioSessionPortDescription* pPortDesc in pInputs) {
if (strcmp(pDeviceID->coreaudio, [pPortDesc.UID UTF8String]) == 0) {
ma_AVAudioSessionPortDescription_to_device_info(pPortDesc, pDeviceInfo);
found = MA_TRUE;
break;
}
}
}
if (!found) { /* ID and Name. */
return MA_DOES_NOT_EXIST; ma_AVAudioSessionPortDescription_to_device_info(pPortDesc, &deviceInfo);
}
} else {
if (deviceType == ma_device_type_playback) {
ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1);
} else {
ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1);
}
}
/*
Default.
/* I'm not sure how to do this. I'm going to just assume it's the default device for now. Advice welcome!
Retrieving device information is more annoying on mobile than desktop. For simplicity I'm locking this down to whatever format is */
reported on a temporary I/O unit. The problem, however, is that this doesn't return a value for the sample rate which we need to deviceInfo.isDefault = MA_TRUE;
retrieve from the AVAudioSession shared instance.
*/
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_RemoteIO;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
component = pContextStateCoreAudio->AudioComponentFindNext(NULL, &desc); /* Data Format. */
if (component == NULL) { /*
return MA_FAILED_TO_INIT_BACKEND; Retrieving device information is more annoying on mobile than desktop. For simplicity I'm locking this down to whatever format is
} reported on a temporary I/O unit. The problem, however, is that this doesn't return a value for the sample rate which we need to
retrieve from the AVAudioSession shared instance.
*/
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_RemoteIO;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
status = pContextStateCoreAudio->AudioComponentInstanceNew(component, &audioUnit); component = pContextStateCoreAudio->AudioComponentFindNext(NULL, &desc);
if (status != noErr) { if (component == NULL) {
return ma_result_from_OSStatus(status); return MA_TRUE;
} }
formatScope = (deviceType == ma_device_type_playback) ? kAudioUnitScope_Input : kAudioUnitScope_Output; status = pContextStateCoreAudio->AudioComponentInstanceNew(component, &audioUnit);
formatElement = (deviceType == ma_device_type_playback) ? MA_COREAUDIO_OUTPUT_BUS : MA_COREAUDIO_INPUT_BUS; if (status != noErr) {
return MA_TRUE;
}
propSize = sizeof(bestFormat); formatScope = (deviceType == ma_device_type_playback) ? kAudioUnitScope_Input : kAudioUnitScope_Output;
status = pContextStateCoreAudio->AudioUnitGetProperty(audioUnit, kAudioUnitProperty_StreamFormat, formatScope, formatElement, &bestFormat, &propSize); formatElement = (deviceType == ma_device_type_playback) ? MA_COREAUDIO_OUTPUT_BUS : MA_COREAUDIO_INPUT_BUS;
if (status != noErr) {
pContextStateCoreAudio->AudioComponentInstanceDispose(audioUnit);
return ma_result_from_OSStatus(status);
}
propSize = sizeof(bestFormat);
status = pContextStateCoreAudio->AudioUnitGetProperty(audioUnit, kAudioUnitProperty_StreamFormat, formatScope, formatElement, &bestFormat, &propSize);
if (status != noErr) {
pContextStateCoreAudio->AudioComponentInstanceDispose(audioUnit); pContextStateCoreAudio->AudioComponentInstanceDispose(audioUnit);
audioUnit = NULL; return MA_TRUE;
}
/* Only a single format is being reported for iOS. */ pContextStateCoreAudio->AudioComponentInstanceDispose(audioUnit);
pDeviceInfo->nativeDataFormatCount = 1; audioUnit = NULL;
result = ma_format_from_AudioStreamBasicDescription(&bestFormat, &pDeviceInfo->nativeDataFormats[0].format); /* Only a single format is being reported for iOS. */
deviceInfo.nativeDataFormatCount = 1;
result = ma_format_from_AudioStreamBasicDescription(&bestFormat, &deviceInfo.nativeDataFormats[0].format);
if (result != MA_SUCCESS) {
return MA_TRUE;
}
deviceInfo.nativeDataFormats[0].channels = bestFormat.mChannelsPerFrame;
/*
It looks like Apple are wanting to push the whole AVAudioSession thing. Thus, we need to use that to determine device settings. To do
this we just get the shared instance and inspect.
*/
@autoreleasepool {
AVAudioSession* pAudioSession = [AVAudioSession sharedInstance];
MA_ASSERT(pAudioSession != NULL);
deviceInfo.nativeDataFormats[0].sampleRate = (ma_uint32)pAudioSession.sampleRate;
}
return callback(deviceType, &deviceInfo, pUserData);
}
#endif
static ma_result ma_context_enumerate_devices__coreaudio(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData)
{
#if defined(MA_APPLE_DESKTOP)
{
UInt32 deviceCount;
AudioObjectID* pDeviceObjectIDs;
AudioObjectID defaultDeviceObjectIDPlayback;
AudioObjectID defaultDeviceObjectIDCapture;
ma_result result;
UInt32 iDevice;
ma_find_default_AudioObjectID(pContext, ma_device_type_playback, &defaultDeviceObjectIDPlayback); /* OK if this fails. */
ma_find_default_AudioObjectID(pContext, ma_device_type_capture, &defaultDeviceObjectIDCapture); /* OK if this fails. */
result = ma_get_device_object_ids__coreaudio(pContext, &deviceCount, &pDeviceObjectIDs);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
return result; return result;
} }
pDeviceInfo->nativeDataFormats[0].channels = bestFormat.mChannelsPerFrame; for (iDevice = 0; iDevice < deviceCount; ++iDevice) {
AudioObjectID deviceObjectID = pDeviceObjectIDs[iDevice];
/* if (ma_does_AudioObject_support_playback(pContext, deviceObjectID)) {
It looks like Apple are wanting to push the whole AVAudioSession thing. Thus, we need to use that to determine device settings. To do ma_bool32 cbResult = ma_context_enumerate_device_by_AudioObjectID__coreaudio(pContext, deviceObjectID, defaultDeviceObjectIDPlayback, ma_device_type_playback, callback, pUserData);
this we just get the shared instance and inspect. if (cbResult == MA_FALSE) {
*/ break;
@autoreleasepool { }
AVAudioSession* pAudioSession = [AVAudioSession sharedInstance]; }
MA_ASSERT(pAudioSession != NULL);
pDeviceInfo->nativeDataFormats[0].sampleRate = (ma_uint32)pAudioSession.sampleRate; if (ma_does_AudioObject_support_capture(pContext, deviceObjectID)) {
ma_bool32 cbResult = ma_context_enumerate_device_by_AudioObjectID__coreaudio(pContext, deviceObjectID, defaultDeviceObjectIDCapture, ma_device_type_capture, callback, pUserData);
if (cbResult == MA_FALSE) {
break;
}
}
}
ma_free(pDeviceObjectIDs, &pContext->allocationCallbacks);
}
#else
{
NSArray *pInputs = [[[AVAudioSession sharedInstance] currentRoute] inputs];
NSArray *pOutputs = [[[AVAudioSession sharedInstance] currentRoute] outputs];
for (AVAudioSessionPortDescription* pPortDesc in pOutputs) {
ma_AVAudioSessionPortDescription_to_device_info(pPortDesc, &info);
/* I'm not sure how to check for default devices. I'm just going to assume the first one is the default. */
if (pPortDesc == pOutputs[0]) {
info.isDefault = MA_TRUE;
}
if (!callback(ma_device_type_playback, &info, pUserData)) {
return MA_SUCCESS;
}
}
for (AVAudioSessionPortDescription* pPortDesc in pInputs) {
ma_AVAudioSessionPortDescription_to_device_info(pPortDesc, &info);
/* I'm not sure how to check for default devices. I'm just going to assume the first one is the default. */
if (pPortDesc == pInputs[0]) {
info.isDefault = MA_TRUE;
}
if (!callback(ma_device_type_capture, &info, pUserData)) {
return MA_SUCCESS;
}
} }
} }
#endif #endif
(void)pDeviceInfo; /* Unused. */
return MA_SUCCESS; return MA_SUCCESS;
} }
@@ -37134,7 +37104,7 @@ static ma_device_backend_vtable ma_gDeviceBackendVTable_CoreAudio =
ma_context_init__coreaudio, ma_context_init__coreaudio,
ma_context_uninit__coreaudio, ma_context_uninit__coreaudio,
ma_context_enumerate_devices__coreaudio, ma_context_enumerate_devices__coreaudio,
ma_context_get_device_info__coreaudio, NULL,
ma_device_init__coreaudio, ma_device_init__coreaudio,
ma_device_uninit__coreaudio, ma_device_uninit__coreaudio,
ma_device_start__coreaudio, ma_device_start__coreaudio,