From c3fcb643635c4635261df7c88557565eeecbd7e6 Mon Sep 17 00:00:00 2001 From: David Reid Date: Thu, 29 Nov 2018 13:20:15 +1000 Subject: [PATCH] Core Audio: Fix some issues with channel maps. --- mini_al.h | 234 +++++++++++++++++++++++++++++------------------------- 1 file changed, 128 insertions(+), 106 deletions(-) diff --git a/mini_al.h b/mini_al.h index 5a6102a4..7aff081c 100644 --- a/mini_al.h +++ b/mini_al.h @@ -13939,6 +13939,80 @@ mal_result mal_format_from_AudioStreamBasicDescription(const AudioStreamBasicDes return MAL_FORMAT_NOT_SUPPORTED; } +mal_result mal_get_channel_map_from_AudioChannelLayout(AudioChannelLayout* pChannelLayout, mal_channel channelMap[MAL_MAX_CHANNELS]) +{ + mal_assert(pChannelLayout != NULL); + + if (pChannelLayout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions) { + for (UInt32 iChannel = 0; iChannel < pChannelLayout->mNumberChannelDescriptions; ++iChannel) { + channelMap[iChannel] = mal_channel_from_AudioChannelLabel(pChannelLayout->mChannelDescriptions[iChannel].mChannelLabel); + } + } else +#if 0 + if (pChannelLayout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelBitmap) { + // This is the same kind of system that's used by Windows audio APIs. + UInt32 iChannel = 0; + AudioChannelBitmap bitmap = pChannelLayout->mChannelBitmap; + for (UInt32 iBit = 0; iBit < 32; ++iBit) { + AudioChannelBitmap bit = bitmap & (1 << iBit); + if (bit != 0) { + channelMap[iChannel++] = mal_channel_from_AudioChannelBit(bit); + } + } + } else +#endif + { + // Need to use the tag to determine the channel map. For now I'm just assuming a default channel map, but later on this should + // be updated to determine the mapping based on the tag. + UInt32 channelCount = AudioChannelLayoutTag_GetNumberOfChannels(pChannelLayout->mChannelLayoutTag); + switch (pChannelLayout->mChannelLayoutTag) + { + case kAudioChannelLayoutTag_Mono: + case kAudioChannelLayoutTag_Stereo: + case kAudioChannelLayoutTag_StereoHeadphones: + case kAudioChannelLayoutTag_MatrixStereo: + case kAudioChannelLayoutTag_MidSide: + case kAudioChannelLayoutTag_XY: + case kAudioChannelLayoutTag_Binaural: + case kAudioChannelLayoutTag_Ambisonic_B_Format: + { + mal_get_standard_channel_map(mal_standard_channel_map_default, channelCount, channelMap); + } break; + + case kAudioChannelLayoutTag_Octagonal: + { + channelMap[7] = MAL_CHANNEL_SIDE_RIGHT; + channelMap[6] = MAL_CHANNEL_SIDE_LEFT; + } // Intentional fallthrough. + case kAudioChannelLayoutTag_Hexagonal: + { + channelMap[5] = MAL_CHANNEL_BACK_CENTER; + } // Intentional fallthrough. + case kAudioChannelLayoutTag_Pentagonal: + { + channelMap[4] = MAL_CHANNEL_FRONT_CENTER; + } // Intentional fallghrough. + case kAudioChannelLayoutTag_Quadraphonic: + { + channelMap[3] = MAL_CHANNEL_BACK_RIGHT; + channelMap[2] = MAL_CHANNEL_BACK_LEFT; + channelMap[1] = MAL_CHANNEL_RIGHT; + channelMap[0] = MAL_CHANNEL_LEFT; + } break; + + // TODO: Add support for more tags here. + + default: + { + mal_get_standard_channel_map(mal_standard_channel_map_default, channelCount, channelMap); + } break; + } + } + + return MAL_SUCCESS; +} + + #if defined(MAL_APPLE_DESKTOP) mal_result mal_get_device_object_ids__coreaudio(mal_context* pContext, UInt32* pDeviceCount, AudioObjectID** ppDeviceObjectIDs) // NOTE: Free the returned buffer with mal_free(). { @@ -14178,79 +14252,6 @@ mal_result mal_get_AudioObject_channel_count(mal_context* pContext, AudioObjectI return MAL_SUCCESS; } -mal_result mal_get_channel_map_from_AudioChannelLayout(AudioChannelLayout* pChannelLayout, mal_channel channelMap[MAL_MAX_CHANNELS]) -{ - mal_assert(pChannelLayout != NULL); - - if (pChannelLayout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions) { - for (UInt32 iChannel = 0; iChannel < pChannelLayout->mNumberChannelDescriptions; ++iChannel) { - channelMap[iChannel] = mal_channel_from_AudioChannelLabel(pChannelLayout->mChannelDescriptions[iChannel].mChannelLabel); - } - } else -#if 0 - if (pChannelLayout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelBitmap) { - // This is the same kind of system that's used by Windows audio APIs. - UInt32 iChannel = 0; - AudioChannelBitmap bitmap = pChannelLayout->mChannelBitmap; - for (UInt32 iBit = 0; iBit < 32; ++iBit) { - AudioChannelBitmap bit = bitmap & (1 << iBit); - if (bit != 0) { - channelMap[iChannel++] = mal_channel_from_AudioChannelBit(bit); - } - } - } else -#endif - { - // Need to use the tag to determine the channel map. For now I'm just assuming a default channel map, but later on this should - // be updated to determine the mapping based on the tag. - UInt32 channelCount = AudioChannelLayoutTag_GetNumberOfChannels(pChannelLayout->mChannelLayoutTag); - switch (pChannelLayout->mChannelLayoutTag) - { - case kAudioChannelLayoutTag_Mono: - case kAudioChannelLayoutTag_Stereo: - case kAudioChannelLayoutTag_StereoHeadphones: - case kAudioChannelLayoutTag_MatrixStereo: - case kAudioChannelLayoutTag_MidSide: - case kAudioChannelLayoutTag_XY: - case kAudioChannelLayoutTag_Binaural: - case kAudioChannelLayoutTag_Ambisonic_B_Format: - { - mal_get_standard_channel_map(mal_standard_channel_map_default, channelCount, channelMap); - } break; - - case kAudioChannelLayoutTag_Octagonal: - { - channelMap[7] = MAL_CHANNEL_SIDE_RIGHT; - channelMap[6] = MAL_CHANNEL_SIDE_LEFT; - } // Intentional fallthrough. - case kAudioChannelLayoutTag_Hexagonal: - { - channelMap[5] = MAL_CHANNEL_BACK_CENTER; - } // Intentional fallthrough. - case kAudioChannelLayoutTag_Pentagonal: - { - channelMap[4] = MAL_CHANNEL_FRONT_CENTER; - } // Intentional fallghrough. - case kAudioChannelLayoutTag_Quadraphonic: - { - channelMap[3] = MAL_CHANNEL_BACK_RIGHT; - channelMap[2] = MAL_CHANNEL_BACK_LEFT; - channelMap[1] = MAL_CHANNEL_RIGHT; - channelMap[0] = MAL_CHANNEL_LEFT; - } break; - - // TODO: Add support for more tags here. - - default: - { - mal_get_standard_channel_map(mal_standard_channel_map_default, channelCount, channelMap); - } break; - } - } - - return MAL_SUCCESS; -} - mal_result mal_get_AudioObject_channel_map(mal_context* pContext, AudioObjectID deviceObjectID, mal_device_type deviceType, mal_channel channelMap[MAL_MAX_CHANNELS]) { mal_assert(pContext != NULL); @@ -14271,37 +14272,6 @@ mal_result mal_get_AudioObject_channel_map(mal_context* pContext, AudioObjectID return result; } -mal_result mal_get_AudioUnit_channel_map(mal_context* pContext, AudioUnit audioUnit, mal_device_type deviceType, mal_channel channelMap[MAL_MAX_CHANNELS]) -{ - mal_assert(pContext != NULL); - - UInt32 channelLayoutSize; - OSStatus status = ((mal_AudioUnitGetPropertyInfo_proc)pContext->coreaudio.AudioUnitGetPropertyInfo)(audioUnit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Output, MAL_COREAUDIO_OUTPUT_BUS, &channelLayoutSize, NULL); - if (status != noErr) { - return mal_result_from_OSStatus(status); - } - - AudioChannelLayout* pChannelLayout = (AudioChannelLayout*)mal_malloc(channelLayoutSize); - if (pChannelLayout == NULL) { - return MAL_OUT_OF_MEMORY; - } - - status = ((mal_AudioUnitGetProperty_proc)pContext->coreaudio.AudioUnitGetProperty)(audioUnit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Output, MAL_COREAUDIO_OUTPUT_BUS, pChannelLayout, &channelLayoutSize); - if (status != noErr) { - mal_free(pChannelLayout); - return mal_result_from_OSStatus(status); - } - - mal_result result = mal_get_channel_map_from_AudioChannelLayout(pChannelLayout, channelMap); - if (result != MAL_SUCCESS) { - mal_free(pChannelLayout); - return result; - } - - mal_free(pChannelLayout); - return MAL_SUCCESS; -} - mal_result mal_get_AudioObject_sample_rates(mal_context* pContext, AudioObjectID deviceObjectID, mal_device_type deviceType, UInt32* pSampleRateRangesCount, AudioValueRange** ppSampleRateRanges) // NOTE: Free the returned pointer with mal_free(). { mal_assert(pContext != NULL); @@ -14716,7 +14686,46 @@ mal_result mal_find_best_format__coreaudio(mal_context* pContext, AudioObjectID } #endif +mal_result mal_get_AudioUnit_channel_map(mal_context* pContext, AudioUnit audioUnit, mal_device_type deviceType, mal_channel channelMap[MAL_MAX_CHANNELS]) +{ + mal_assert(pContext != NULL); + + AudioUnitScope deviceScope; + AudioUnitElement deviceBus; + if (deviceType == mal_device_type_playback) { + deviceScope = kAudioUnitScope_Output; + deviceBus = MAL_COREAUDIO_OUTPUT_BUS; + } else { + deviceScope = kAudioUnitScope_Input; + deviceBus = MAL_COREAUDIO_INPUT_BUS; + } + + UInt32 channelLayoutSize; + OSStatus status = ((mal_AudioUnitGetPropertyInfo_proc)pContext->coreaudio.AudioUnitGetPropertyInfo)(audioUnit, kAudioUnitProperty_AudioChannelLayout, deviceScope, deviceBus, &channelLayoutSize, NULL); + if (status != noErr) { + return mal_result_from_OSStatus(status); + } + + AudioChannelLayout* pChannelLayout = (AudioChannelLayout*)mal_malloc(channelLayoutSize); + if (pChannelLayout == NULL) { + return MAL_OUT_OF_MEMORY; + } + + status = ((mal_AudioUnitGetProperty_proc)pContext->coreaudio.AudioUnitGetProperty)(audioUnit, kAudioUnitProperty_AudioChannelLayout, deviceScope, deviceBus, pChannelLayout, &channelLayoutSize); + if (status != noErr) { + mal_free(pChannelLayout); + return mal_result_from_OSStatus(status); + } + + mal_result result = mal_get_channel_map_from_AudioChannelLayout(pChannelLayout, channelMap); + if (result != MAL_SUCCESS) { + mal_free(pChannelLayout); + return result; + } + mal_free(pChannelLayout); + return MAL_SUCCESS; +} mal_bool32 mal_context_is_device_id_equal__coreaudio(mal_context* pContext, const mal_device_id* pID0, const mal_device_id* pID1) { @@ -15425,11 +15434,24 @@ mal_result mal_device_init_internal__coreaudio(mal_context* pContext, mal_device } - // Internal channel map. + // Internal channel map. This is weird in my testing. If I use the AudioObject to get the + // channel map, the channel descriptions are set to "Unknown" for some reason. To work around + // this it looks like retrieving it from the AudioUnit will work. However, and this is where + // it gets weird, it doesn't seem to work with capture devices, nor at all on iOS... Therefore + // I'm going to fall back to a default assumption in these cases. #if defined(MAL_APPLE_DESKTOP) result = mal_get_AudioUnit_channel_map(pContext, pData->audioUnit, deviceType, pData->channelMapOut); if (result != MAL_SUCCESS) { - return result; + #if 0 + // Try falling back to the channel map from the AudioObject. + result = mal_get_AudioObject_channel_map(pContext, deviceObjectID, deviceType, pData->channelMapOut); + if (result != MAL_SUCCESS) { + return result; + } + #else + // Fall back to default assumptions. + mal_get_standard_channel_map(mal_standard_channel_map_default, pData->channelsOut, pData->channelMapOut); + #endif } #else // TODO: Figure out how to get the channel map using AVAudioSession.