/* * Copyright (C) 2013-2019 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #import "config.h" #import "AudioSessionMac.h" #if USE(AUDIO_SESSION) && PLATFORM(MAC) #import "FloatConversion.h" #import "Logging.h" #import "NotImplemented.h" #import #import #import #import #import namespace WebCore { static AudioDeviceID defaultDevice() { AudioDeviceID deviceID = kAudioDeviceUnknown; UInt32 infoSize = sizeof(deviceID); AudioObjectPropertyAddress defaultOutputDeviceAddress = { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, #if HAVE(AUDIO_OBJECT_PROPERTY_ELEMENT_MAIN) kAudioObjectPropertyElementMain #else ALLOW_DEPRECATED_DECLARATIONS_BEGIN kAudioObjectPropertyElementMaster ALLOW_DEPRECATED_DECLARATIONS_END #endif }; OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &defaultOutputDeviceAddress, 0, 0, &infoSize, (void*)&deviceID); if (result) return 0; // error return deviceID; } #if ENABLE(ROUTING_ARBITRATION) static std::optional isPlayingToBluetoothOverride; static float defaultDeviceTransportIsBluetooth() { if (isPlayingToBluetoothOverride) return *isPlayingToBluetoothOverride; static const AudioObjectPropertyAddress audioDeviceTransportTypeProperty = { kAudioDevicePropertyTransportType, kAudioObjectPropertyScopeGlobal, #if HAVE(AUDIO_OBJECT_PROPERTY_ELEMENT_MAIN) kAudioObjectPropertyElementMain #else ALLOW_DEPRECATED_DECLARATIONS_BEGIN kAudioObjectPropertyElementMaster, ALLOW_DEPRECATED_DECLARATIONS_END #endif }; UInt32 transportType = kAudioDeviceTransportTypeUnknown; UInt32 transportSize = sizeof(transportType); if (AudioObjectGetPropertyData(defaultDevice(), &audioDeviceTransportTypeProperty, 0, 0, &transportSize, &transportType)) return false; return transportType == kAudioDeviceTransportTypeBluetooth || transportType == kAudioDeviceTransportTypeBluetoothLE; } #endif void AudioSessionMac::addSampleRateObserverIfNeeded() const { if (m_hasSampleRateObserver) return; m_hasSampleRateObserver = true; AudioObjectPropertyAddress nominalSampleRateAddress = { kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal, #if HAVE(AUDIO_OBJECT_PROPERTY_ELEMENT_MAIN) kAudioObjectPropertyElementMain #else ALLOW_DEPRECATED_DECLARATIONS_BEGIN kAudioObjectPropertyElementMaster ALLOW_DEPRECATED_DECLARATIONS_END #endif }; AudioObjectAddPropertyListener(defaultDevice(), &nominalSampleRateAddress, handleSampleRateChange, const_cast(this)); } OSStatus AudioSessionMac::handleSampleRateChange(AudioObjectID device, UInt32, const AudioObjectPropertyAddress* sampleRateAddress, void* inClientData) { ASSERT(inClientData); if (!inClientData) return noErr; auto* session = static_cast(inClientData); Float64 nominalSampleRate; UInt32 nominalSampleRateSize = sizeof(Float64); OSStatus result = AudioObjectGetPropertyData(device, sampleRateAddress, 0, 0, &nominalSampleRateSize, (void*)&nominalSampleRate); if (result) return result; session->m_sampleRate = narrowPrecisionToFloat(nominalSampleRate); callOnMainThread([session] { session->handleSampleRateChange(); }); return noErr; } void AudioSessionMac::handleSampleRateChange() const { m_configurationChangeObservers.forEach([this](auto& observer) { observer.sampleRateDidChange(*this); }); } void AudioSessionMac::addBufferSizeObserverIfNeeded() const { if (m_hasBufferSizeObserver) return; m_hasBufferSizeObserver = true; AudioObjectPropertyAddress bufferSizeAddress = { kAudioDevicePropertyBufferFrameSize, kAudioObjectPropertyScopeGlobal, #if HAVE(AUDIO_OBJECT_PROPERTY_ELEMENT_MAIN) kAudioObjectPropertyElementMain #else ALLOW_DEPRECATED_DECLARATIONS_BEGIN kAudioObjectPropertyElementMaster ALLOW_DEPRECATED_DECLARATIONS_END #endif }; AudioObjectAddPropertyListener(defaultDevice(), &bufferSizeAddress, handleBufferSizeChange, const_cast(this)); } OSStatus AudioSessionMac::handleBufferSizeChange(AudioObjectID device, UInt32, const AudioObjectPropertyAddress* bufferSizeAddress, void* inClientData) { ASSERT(inClientData); if (!inClientData) return noErr; auto* session = static_cast(inClientData); UInt32 bufferSize; UInt32 bufferSizeSize = sizeof(bufferSize); OSStatus result = AudioObjectGetPropertyData(device, bufferSizeAddress, 0, 0, &bufferSizeSize, &bufferSize); if (result) return result; session->m_bufferSize = bufferSize; callOnMainThread([session] { session->handleBufferSizeChange(); }); return noErr; } void AudioSessionMac::handleBufferSizeChange() const { m_configurationChangeObservers.forEach([this](auto& observer) { observer.bufferSizeDidChange(*this); }); } void AudioSessionMac::audioOutputDeviceChanged() { #if ENABLE(ROUTING_ARBITRATION) if (!m_playingToBluetooth || *m_playingToBluetooth == defaultDeviceTransportIsBluetooth()) return; m_playingToBluetooth = std::nullopt; #endif } void AudioSessionMac::setIsPlayingToBluetoothOverride(std::optional value) { #if ENABLE(ROUTING_ARBITRATION) isPlayingToBluetoothOverride = value; #else UNUSED_PARAM(value); #endif } void AudioSessionMac::setCategory(CategoryType category, RouteSharingPolicy policy) { #if ENABLE(ROUTING_ARBITRATION) bool playingToBluetooth = defaultDeviceTransportIsBluetooth(); if (category == m_category && m_playingToBluetooth && *m_playingToBluetooth == playingToBluetooth) return; m_category = category; m_policy = policy; if (m_setupArbitrationOngoing) { RELEASE_LOG_ERROR(Media, "AudioSessionMac::setCategory() - a beginArbitrationWithCategory is still ongoing"); return; } if (!m_routingArbitrationClient) return; if (m_inRoutingArbitration) { m_inRoutingArbitration = false; m_routingArbitrationClient->leaveRoutingAbritration(); } if (category == CategoryType::AmbientSound || category == CategoryType::SoloAmbientSound || category == CategoryType::AudioProcessing || category == CategoryType::None) return; using RoutingArbitrationError = AudioSessionRoutingArbitrationClient::RoutingArbitrationError; using DefaultRouteChanged = AudioSessionRoutingArbitrationClient::DefaultRouteChanged; m_playingToBluetooth = playingToBluetooth; m_setupArbitrationOngoing = true; m_routingArbitrationClient->beginRoutingArbitrationWithCategory(m_category, [this] (RoutingArbitrationError error, DefaultRouteChanged defaultRouteChanged) { m_setupArbitrationOngoing = false; if (error != RoutingArbitrationError::None) { RELEASE_LOG_ERROR(Media, "AudioSessionMac::setCategory() - beginArbitrationWithCategory:%s failed with error %s", convertEnumerationToString(m_category).ascii().data(), convertEnumerationToString(error).ascii().data()); return; } m_inRoutingArbitration = true; // FIXME: Do we need to reset sample rate and buffer size for the new default device? if (defaultRouteChanged == DefaultRouteChanged::Yes) LOG(Media, "AudioSessionMac::setCategory() - defaultRouteChanged!"); }); #else m_category = category; m_policy = policy; #endif } float AudioSessionMac::sampleRate() const { if (!m_sampleRate) { addSampleRateObserverIfNeeded(); Float64 nominalSampleRate; UInt32 nominalSampleRateSize = sizeof(Float64); AudioObjectPropertyAddress nominalSampleRateAddress = { kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal, #if HAVE(AUDIO_OBJECT_PROPERTY_ELEMENT_MAIN) kAudioObjectPropertyElementMain #else ALLOW_DEPRECATED_DECLARATIONS_BEGIN kAudioObjectPropertyElementMaster ALLOW_DEPRECATED_DECLARATIONS_END #endif }; OSStatus result = AudioObjectGetPropertyData(defaultDevice(), &nominalSampleRateAddress, 0, 0, &nominalSampleRateSize, (void*)&nominalSampleRate); if (result != noErr) { RELEASE_LOG_ERROR(Media, "AudioSessionMac::sampleRate() - AudioObjectGetPropertyData() failed with error %d", result); return 44100; } m_sampleRate = narrowPrecisionToFloat(nominalSampleRate); if (!*m_sampleRate) { RELEASE_LOG_ERROR(Media, "AudioSessionMac::sampleRate() - AudioObjectGetPropertyData() return an invalid sample rate"); m_sampleRate = 44100; } handleSampleRateChange(); } return *m_sampleRate; } size_t AudioSessionMac::bufferSize() const { if (m_bufferSize) return *m_bufferSize; addBufferSizeObserverIfNeeded(); UInt32 bufferSize; UInt32 bufferSizeSize = sizeof(bufferSize); AudioObjectPropertyAddress bufferSizeAddress = { kAudioDevicePropertyBufferFrameSize, kAudioObjectPropertyScopeGlobal, #if HAVE(AUDIO_OBJECT_PROPERTY_ELEMENT_MAIN) kAudioObjectPropertyElementMain #else ALLOW_DEPRECATED_DECLARATIONS_BEGIN kAudioObjectPropertyElementMaster ALLOW_DEPRECATED_DECLARATIONS_END #endif }; OSStatus result = AudioObjectGetPropertyData(defaultDevice(), &bufferSizeAddress, 0, 0, &bufferSizeSize, &bufferSize); if (result) return 0; m_bufferSize = bufferSize; return bufferSize; } size_t AudioSessionMac::numberOfOutputChannels() const { notImplemented(); return 0; } size_t AudioSessionMac::maximumNumberOfOutputChannels() const { AudioObjectPropertyAddress sizeAddress = { kAudioDevicePropertyStreamConfiguration, kAudioObjectPropertyScopeOutput, #if HAVE(AUDIO_OBJECT_PROPERTY_ELEMENT_MAIN) kAudioObjectPropertyElementMain #else ALLOW_DEPRECATED_DECLARATIONS_BEGIN kAudioObjectPropertyElementMaster ALLOW_DEPRECATED_DECLARATIONS_END #endif }; UInt32 size = 0; OSStatus result = AudioObjectGetPropertyDataSize(defaultDevice(), &sizeAddress, 0, 0, &size); if (result || !size) return 0; auto listMemory = makeUniqueArray(size); auto* audioBufferList = reinterpret_cast(listMemory.get()); result = AudioObjectGetPropertyData(defaultDevice(), &sizeAddress, 0, 0, &size, audioBufferList); if (result) return 0; size_t channels = 0; for (UInt32 i = 0; i < audioBufferList->mNumberBuffers; ++i) channels += audioBufferList->mBuffers[i].mNumberChannels; return channels; } bool AudioSessionMac::tryToSetActiveInternal(bool) { notImplemented(); return true; } String AudioSessionMac::routingContextUID() const { return emptyString(); } size_t AudioSessionMac::preferredBufferSize() const { return bufferSize(); } void AudioSessionMac::setPreferredBufferSize(size_t bufferSize) { if (m_bufferSize == bufferSize) return; AudioValueRange bufferSizeRange = {0, 0}; UInt32 bufferSizeRangeSize = sizeof(AudioValueRange); AudioObjectPropertyAddress bufferSizeRangeAddress = { kAudioDevicePropertyBufferFrameSizeRange, kAudioObjectPropertyScopeGlobal, #if HAVE(AUDIO_OBJECT_PROPERTY_ELEMENT_MAIN) kAudioObjectPropertyElementMain #else ALLOW_DEPRECATED_DECLARATIONS_BEGIN kAudioObjectPropertyElementMaster ALLOW_DEPRECATED_DECLARATIONS_END #endif }; OSStatus result = AudioObjectGetPropertyData(defaultDevice(), &bufferSizeRangeAddress, 0, 0, &bufferSizeRangeSize, &bufferSizeRange); if (result) return; size_t minBufferSize = static_cast(bufferSizeRange.mMinimum); size_t maxBufferSize = static_cast(bufferSizeRange.mMaximum); UInt32 bufferSizeOut = std::min(maxBufferSize, std::max(minBufferSize, bufferSize)); AudioObjectPropertyAddress preferredBufferSizeAddress = { kAudioDevicePropertyBufferFrameSize, kAudioObjectPropertyScopeGlobal, #if HAVE(AUDIO_OBJECT_PROPERTY_ELEMENT_MAIN) kAudioObjectPropertyElementMain #else ALLOW_DEPRECATED_DECLARATIONS_BEGIN kAudioObjectPropertyElementMaster ALLOW_DEPRECATED_DECLARATIONS_END #endif }; result = AudioObjectSetPropertyData(defaultDevice(), &preferredBufferSizeAddress, 0, 0, sizeof(bufferSizeOut), (void*)&bufferSizeOut); if (!result) { m_bufferSize = bufferSizeOut; handleBufferSizeChange(); } #if !LOG_DISABLED if (result) LOG(Media, "AudioSessionMac::setPreferredBufferSize(%zu) - failed with error %d", bufferSize, static_cast(result)); else LOG(Media, "AudioSessionMac::setPreferredBufferSize(%zu)", bufferSize); #endif } bool AudioSessionMac::isMuted() const { UInt32 mute = 0; UInt32 muteSize = sizeof(mute); AudioObjectPropertyAddress muteAddress = { kAudioDevicePropertyMute, kAudioDevicePropertyScopeOutput, #if HAVE(AUDIO_OBJECT_PROPERTY_ELEMENT_MAIN) kAudioObjectPropertyElementMain #else ALLOW_DEPRECATED_DECLARATIONS_BEGIN kAudioObjectPropertyElementMaster ALLOW_DEPRECATED_DECLARATIONS_END #endif }; AudioObjectGetPropertyData(defaultDevice(), &muteAddress, 0, nullptr, &muteSize, &mute); switch (mute) { case 0: return false; case 1: return true; default: ASSERT_NOT_REACHED(); return false; } } static OSStatus handleMutePropertyChange(AudioObjectID, UInt32, const AudioObjectPropertyAddress*, void* inClientData) { callOnMainThread([inClientData] { reinterpret_cast(inClientData)->handleMutedStateChange(); }); return noErr; } void AudioSessionMac::handleMutedStateChange() { bool isCurrentlyMuted = isMuted(); if (m_lastMutedState && *m_lastMutedState == isCurrentlyMuted) return; m_lastMutedState = isCurrentlyMuted; m_configurationChangeObservers.forEach([this](auto& observer) { observer.hardwareMutedStateDidChange(*this); }); } void AudioSessionMac::addConfigurationChangeObserver(ConfigurationChangeObserver& observer) { m_configurationChangeObservers.add(observer); if (m_configurationChangeObservers.computeSize() > 1) return; AudioObjectPropertyAddress muteAddress = { kAudioDevicePropertyMute, kAudioDevicePropertyScopeOutput, #if HAVE(AUDIO_OBJECT_PROPERTY_ELEMENT_MAIN) kAudioObjectPropertyElementMain #else ALLOW_DEPRECATED_DECLARATIONS_BEGIN kAudioObjectPropertyElementMaster ALLOW_DEPRECATED_DECLARATIONS_END #endif }; AudioObjectAddPropertyListener(defaultDevice(), &muteAddress, handleMutePropertyChange, this); } void AudioSessionMac::removeConfigurationChangeObserver(ConfigurationChangeObserver& observer) { if (m_configurationChangeObservers.computeSize() == 1) { AudioObjectPropertyAddress muteAddress = { kAudioDevicePropertyMute, kAudioDevicePropertyScopeOutput, #if HAVE(AUDIO_OBJECT_PROPERTY_ELEMENT_MAIN) kAudioObjectPropertyElementMain #else ALLOW_DEPRECATED_DECLARATIONS_BEGIN kAudioObjectPropertyElementMaster ALLOW_DEPRECATED_DECLARATIONS_END #endif }; AudioObjectRemovePropertyListener(defaultDevice(), &muteAddress, handleMutePropertyChange, this); } m_configurationChangeObservers.remove(observer); } } #endif // USE(AUDIO_SESSION) && PLATFORM(MAC)