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
+132 -162
View File
@@ -35551,118 +35551,30 @@ static void ma_AVAudioSessionPortDescription_to_device_info(AVAudioSessionPortDe
}
#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) {
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)
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)
{
ma_result result;
ma_device_info deviceInfo;
MA_ASSERT(pContext != NULL);
#if defined(MA_APPLE_DESKTOP)
/* Desktop */
{
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;
}
MA_ZERO_OBJECT(&deviceInfo);
/* Default. */
if (deviceObjectID == defaultDeviceObjectID) {
pDeviceInfo->isDefault = MA_TRUE;
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. */
{
/*
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
@@ -35671,24 +35583,26 @@ 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
sample rate.
*/
pDeviceInfo->nativeDataFormatCount = 0;
/* Formats. */
{
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 result;
return MA_TRUE; /* Failed to get channel count. */
}
/* Formats. */
result = ma_get_AudioObject_stream_descriptions(pContext, deviceObjectID, deviceType, &streamDescriptionCount, &pStreamDescriptions);
if (result != MA_SUCCESS) {
return result;
return MA_TRUE;
}
for (iStreamDescription = 0; iStreamDescription < streamDescriptionCount; ++iStreamDescription) {
@@ -35696,6 +35610,8 @@ static ma_result ma_context_get_device_info__coreaudio(ma_context* pContext, ma_
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) {
@@ -35723,7 +35639,7 @@ static ma_result ma_context_get_device_info__coreaudio(ma_context* pContext, ma_
/* Sample Rates */
result = ma_get_AudioObject_sample_rates(pContext, deviceObjectID, deviceType, &sampleRateRangeCount, &pSampleRateRanges);
if (result != MA_SUCCESS) {
return result;
return MA_TRUE; /* Failed to retrieve sample rates. */
}
/*
@@ -35736,13 +35652,13 @@ static ma_result ma_context_get_device_info__coreaudio(ma_context* pContext, ma_
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;
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 (pDeviceInfo->nativeDataFormatCount >= ma_countof(pDeviceInfo->nativeDataFormats)) {
if (deviceInfo.nativeDataFormatCount >= ma_countof(deviceInfo.nativeDataFormats)) {
break; /* No more room for any more formats. */
}
}
@@ -35751,18 +35667,21 @@ static ma_result ma_context_get_device_info__coreaudio(ma_context* pContext, ma_
ma_free(pSampleRateRanges, &pContext->allocationCallbacks);
if (pDeviceInfo->nativeDataFormatCount >= ma_countof(pDeviceInfo->nativeDataFormats)) {
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
/* 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_device_info deviceInfo;
AudioComponentDescription desc;
AudioComponent component;
AudioUnit audioUnit;
@@ -35772,41 +35691,19 @@ static ma_result ma_context_get_device_info__coreaudio(ma_context* pContext, ma_
AudioStreamBasicDescription bestFormat;
UInt32 propSize;
/* We want to ensure we use a consistent device name to device enumeration. */
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;
}
}
}
MA_ZERO_OBJECT(&deviceInfo);
if (!found) {
return MA_DOES_NOT_EXIST;
}
} 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);
}
}
/* ID and Name. */
ma_AVAudioSessionPortDescription_to_device_info(pPortDesc, &deviceInfo);
/*
Default.
I'm not sure how to do this. I'm going to just assume it's the default device for now. Advice welcome!
*/
deviceInfo.isDefault = MA_TRUE;
/* Data Format. */
/*
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
@@ -35820,12 +35717,12 @@ static ma_result ma_context_get_device_info__coreaudio(ma_context* pContext, ma_
component = pContextStateCoreAudio->AudioComponentFindNext(NULL, &desc);
if (component == NULL) {
return MA_FAILED_TO_INIT_BACKEND;
return MA_TRUE;
}
status = pContextStateCoreAudio->AudioComponentInstanceNew(component, &audioUnit);
if (status != noErr) {
return ma_result_from_OSStatus(status);
return MA_TRUE;
}
formatScope = (deviceType == ma_device_type_playback) ? kAudioUnitScope_Input : kAudioUnitScope_Output;
@@ -35835,21 +35732,21 @@ static ma_result ma_context_get_device_info__coreaudio(ma_context* pContext, ma_
status = pContextStateCoreAudio->AudioUnitGetProperty(audioUnit, kAudioUnitProperty_StreamFormat, formatScope, formatElement, &bestFormat, &propSize);
if (status != noErr) {
pContextStateCoreAudio->AudioComponentInstanceDispose(audioUnit);
return ma_result_from_OSStatus(status);
return MA_TRUE;
}
pContextStateCoreAudio->AudioComponentInstanceDispose(audioUnit);
audioUnit = NULL;
/* Only a single format is being reported for iOS. */
pDeviceInfo->nativeDataFormatCount = 1;
deviceInfo.nativeDataFormatCount = 1;
result = ma_format_from_AudioStreamBasicDescription(&bestFormat, &pDeviceInfo->nativeDataFormats[0].format);
result = ma_format_from_AudioStreamBasicDescription(&bestFormat, &deviceInfo.nativeDataFormats[0].format);
if (result != MA_SUCCESS) {
return result;
return MA_TRUE;
}
pDeviceInfo->nativeDataFormats[0].channels = bestFormat.mChannelsPerFrame;
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
@@ -35859,12 +35756,85 @@ static ma_result ma_context_get_device_info__coreaudio(ma_context* pContext, ma_
AVAudioSession* pAudioSession = [AVAudioSession sharedInstance];
MA_ASSERT(pAudioSession != NULL);
pDeviceInfo->nativeDataFormats[0].sampleRate = (ma_uint32)pAudioSession.sampleRate;
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) {
return result;
}
for (iDevice = 0; iDevice < deviceCount; ++iDevice) {
AudioObjectID deviceObjectID = pDeviceObjectIDs[iDevice];
if (ma_does_AudioObject_support_playback(pContext, deviceObjectID)) {
ma_bool32 cbResult = ma_context_enumerate_device_by_AudioObjectID__coreaudio(pContext, deviceObjectID, defaultDeviceObjectIDPlayback, ma_device_type_playback, callback, pUserData);
if (cbResult == MA_FALSE) {
break;
}
}
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
(void)pDeviceInfo; /* Unused. */
return MA_SUCCESS;
}
@@ -37134,7 +37104,7 @@ static ma_device_backend_vtable ma_gDeviceBackendVTable_CoreAudio =
ma_context_init__coreaudio,
ma_context_uninit__coreaudio,
ma_context_enumerate_devices__coreaudio,
ma_context_get_device_info__coreaudio,
NULL,
ma_device_init__coreaudio,
ma_device_uninit__coreaudio,
ma_device_start__coreaudio,