// Copyright (C) 2025 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qmacosaudiodatautils_p.h" QT_BEGIN_NAMESPACE namespace QCoreAudioUtils { #define ENUM_CASE(NAME) \ case NAME: return QStringView(QT_UNICODE_LITERAL(QT_STRINGIFY(NAME))) #define DUPLICATE_ENUM_CASE(NAME) static_assert(true, "force semicolon") QStringView audioPropertySelectorToString(AudioObjectPropertySelector selector) { switch (selector) { // AudioObject properties ENUM_CASE(kAudioObjectPropertyBaseClass ); ENUM_CASE(kAudioObjectPropertyClass ); ENUM_CASE(kAudioObjectPropertyOwner ); ENUM_CASE(kAudioObjectPropertyName ); ENUM_CASE(kAudioObjectPropertyModelName ); ENUM_CASE(kAudioObjectPropertyManufacturer ); ENUM_CASE(kAudioObjectPropertyElementName ); ENUM_CASE(kAudioObjectPropertyElementCategoryName); ENUM_CASE(kAudioObjectPropertyElementNumberName ); ENUM_CASE(kAudioObjectPropertyOwnedObjects ); ENUM_CASE(kAudioObjectPropertyIdentify ); ENUM_CASE(kAudioObjectPropertySerialNumber ); ENUM_CASE(kAudioObjectPropertyFirmwareVersion ); ENUM_CASE(kAudioObjectPropertySelectorWildcard ); // AudioDevice properties ENUM_CASE(kAudioDevicePropertyConfigurationApplication ); ENUM_CASE(kAudioDevicePropertyDeviceUID ); ENUM_CASE(kAudioDevicePropertyModelUID ); ENUM_CASE(kAudioDevicePropertyTransportType ); ENUM_CASE(kAudioDevicePropertyRelatedDevices ); ENUM_CASE(kAudioDevicePropertyClockDomain ); ENUM_CASE(kAudioDevicePropertyDeviceIsAlive ); ENUM_CASE(kAudioDevicePropertyDeviceIsRunning ); ENUM_CASE(kAudioDevicePropertyDeviceCanBeDefaultDevice ); ENUM_CASE(kAudioDevicePropertyDeviceCanBeDefaultSystemDevice); ENUM_CASE(kAudioDevicePropertyLatency ); ENUM_CASE(kAudioDevicePropertyStreams ); ENUM_CASE(kAudioObjectPropertyControlList ); ENUM_CASE(kAudioDevicePropertySafetyOffset ); ENUM_CASE(kAudioDevicePropertyNominalSampleRate ); ENUM_CASE(kAudioDevicePropertyAvailableNominalSampleRates ); ENUM_CASE(kAudioDevicePropertyIcon ); ENUM_CASE(kAudioDevicePropertyIsHidden ); ENUM_CASE(kAudioDevicePropertyPreferredChannelsForStereo ); ENUM_CASE(kAudioDevicePropertyPreferredChannelLayout ); // AudioClockDevice properties ENUM_CASE(kAudioClockDevicePropertyDeviceUID ); DUPLICATE_ENUM_CASE(kAudioClockDevicePropertyTransportType ); DUPLICATE_ENUM_CASE(kAudioClockDevicePropertyClockDomain ); DUPLICATE_ENUM_CASE(kAudioClockDevicePropertyDeviceIsAlive ); DUPLICATE_ENUM_CASE(kAudioClockDevicePropertyDeviceIsRunning ); DUPLICATE_ENUM_CASE(kAudioClockDevicePropertyLatency ); DUPLICATE_ENUM_CASE(kAudioClockDevicePropertyControlList ); DUPLICATE_ENUM_CASE(kAudioClockDevicePropertyNominalSampleRate ); DUPLICATE_ENUM_CASE(kAudioClockDevicePropertyAvailableNominalSampleRates); // AudioEndPointDevice properties ENUM_CASE(kAudioEndPointDevicePropertyComposition ); ENUM_CASE(kAudioEndPointDevicePropertyEndPointList); ENUM_CASE(kAudioEndPointDevicePropertyIsPrivate ); // AudioStream properties ENUM_CASE(kAudioStreamPropertyIsActive ); ENUM_CASE(kAudioStreamPropertyDirection ); ENUM_CASE(kAudioStreamPropertyTerminalType ); ENUM_CASE(kAudioStreamPropertyStartingChannel ); ENUM_CASE(kAudioStreamPropertyVirtualFormat ); ENUM_CASE(kAudioStreamPropertyAvailableVirtualFormats ); ENUM_CASE(kAudioStreamPropertyPhysicalFormat ); ENUM_CASE(kAudioStreamPropertyAvailablePhysicalFormats); default: Q_UNREACHABLE_RETURN(u""); } } QStringView audioPropertyScopeToString(AudioObjectPropertyScope scope) { switch (scope) { ENUM_CASE(kAudioObjectPropertyScopeGlobal ); ENUM_CASE(kAudioObjectPropertyScopeInput ); ENUM_CASE(kAudioObjectPropertyScopeOutput ); ENUM_CASE(kAudioObjectPropertyScopePlayThrough); ENUM_CASE(kAudioObjectPropertyScopeWildcard ); default: Q_UNREACHABLE_RETURN(u""); } } QStringView audioPropertyElementToString(AudioObjectPropertyElement element) { switch (element) { ENUM_CASE(kAudioObjectPropertyElementMain ); ENUM_CASE(kAudioObjectPropertyElementWildcard); default: Q_UNREACHABLE_RETURN(u""); } } #undef ENUM_CASE #undef DUPLICATE_ENUM_CASE } AudioObjectPropertyAddress QCoreAudioUtils::makePropertyAddress( AudioObjectPropertySelector selector, QAudioDevice::Mode mode, AudioObjectPropertyElement element) { return { selector, mode == QAudioDevice::Input ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, element }; } bool QCoreAudioUtils::getAudioPropertyRaw(AudioObjectID objectID, const AudioObjectPropertyAddress &address, QSpan destination, bool warnIfMissing) { UInt32 readBytes = destination.size(); const auto res = AudioObjectGetPropertyData(objectID, &address, 0, nullptr, &readBytes, destination.data()); if (res != noErr) { if (warnIfMissing) printUnableToReadWarning(objectID, address, "Err:", res); } else if (readBytes != destination.size()) { if (warnIfMissing) printUnableToReadWarning(objectID, address, "Data size", readBytes, "VS", destination.size(), "expected"); } else { return true; } return false; } QByteArray QCoreAudioUtils::readPersistentDeviceId( AudioDeviceID device, QAudioDevice::Mode mode) { const AudioObjectPropertyAddress propertyAddress = makePropertyAddress( kAudioDevicePropertyDeviceUID, mode); const std::optional name = getAudioProperty(device, propertyAddress); if (name) return QString{*name}.toUtf8(); return QByteArray(); } std::optional QCoreAudioUtils::findAudioDeviceId( const QByteArray &id, QAudioDevice::Mode mode) { if (id.isEmpty() || mode == QAudioDevice::Mode::Null) return std::nullopt; // Iterate over all the devices connected and find the one matching our ID and return the AudioDeviceID. const AudioObjectPropertyAddress audioDevicesPropertyAddress = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain }; const std::optional> audioDevicesOpt = getAudioPropertyList( kAudioObjectSystemObject, audioDevicesPropertyAddress); if (audioDevicesOpt.has_value()) { const std::vector &audioDevices = audioDevicesOpt.value(); const AudioObjectPropertyAddress audioDeviceStreamFormatPropertyAddress = makePropertyAddress( kAudioDevicePropertyStreamFormat, mode); for (const AudioDeviceID &device : audioDevices) { // Ignore devices that don't have any audio formats we can use. const std::optional audioStreamOpt = getAudioProperty( device, audioDeviceStreamFormatPropertyAddress); // Check that these devices have the same unique-id. In which case, we found the // correct CoreAudioID. if (audioStreamOpt.has_value() && readPersistentDeviceId(device, mode) == id) return device; } } return std::nullopt; } std::optional QCoreAudioUtils::findAudioDeviceId( const QAudioDevice &device) { return findAudioDeviceId(device.id(), device.mode()); } QT_END_NAMESPACE