From 157bc3071da89b15f1393475e906e7301698cc8a Mon Sep 17 00:00:00 2001 From: Monroe Linden Date: Tue, 13 Apr 2010 10:10:58 -0700 Subject: Initial implementation of mac_volume_catcher.cpp. --- indra/media_plugins/webkit/CMakeLists.txt | 8 + indra/media_plugins/webkit/mac_volume_catcher.cpp | 275 ++++++++++++++++++++++ 2 files changed, 283 insertions(+) create mode 100644 indra/media_plugins/webkit/mac_volume_catcher.cpp (limited to 'indra/media_plugins/webkit') diff --git a/indra/media_plugins/webkit/CMakeLists.txt b/indra/media_plugins/webkit/CMakeLists.txt index c3a3f8e2b2..4f183cddeb 100644 --- a/indra/media_plugins/webkit/CMakeLists.txt +++ b/indra/media_plugins/webkit/CMakeLists.txt @@ -51,6 +51,14 @@ if (LINUX AND PULSEAUDIO) list(APPEND media_plugin_webkit_LINK_LIBRARIES ${UI_LIBRARIES} # for glib/GTK ) +elseif (DARWIN) + list(APPEND media_plugin_webkit_SOURCE_FILES mac_volume_catcher.cpp) + find_library(CORESERVICES_LIBRARY CoreServices) + find_library(AUDIOUNIT_LIBRARY AudioUnit) + list(APPEND media_plugin_webkit_LINK_LIBRARIES + ${CORESERVICES_LIBRARY} # for Component Manager calls + ${AUDIOUNIT_LIBRARY} # for AudioUnit calls + ) else (LINUX AND PULSEAUDIO) # All other platforms use the dummy volume catcher for now. list(APPEND media_plugin_webkit_SOURCE_FILES dummy_volume_catcher.cpp) diff --git a/indra/media_plugins/webkit/mac_volume_catcher.cpp b/indra/media_plugins/webkit/mac_volume_catcher.cpp new file mode 100644 index 0000000000..03249f9e83 --- /dev/null +++ b/indra/media_plugins/webkit/mac_volume_catcher.cpp @@ -0,0 +1,275 @@ +/** + * @file dummy_volume_catcher.cpp + * @brief A Mac OS X specific hack to control the volume level of all audio channels opened by a process. + * + * @cond + * $LicenseInfo:firstyear=2010&license=viewergpl$ + * + * Copyright (c) 2010, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + * @endcond + */ + +/************************************************************************************************************** + This code works by using CaptureComponent to capture the "Default Output" audio component + (kAudioUnitType_Output/kAudioUnitSubType_DefaultOutput) and delegating all calls to the original component. + It does this just to keep track of all instances of the default output component, so that it can set the + kHALOutputParam_Volume parameter on all of them to adjust the output volume. +**************************************************************************************************************/ + +#include "volume_catcher.h" + +#include +#include +#include + +struct VolumeCatcherStorage; + +class VolumeCatcherImpl +{ +public: + + void setVolume(F32 volume); + void setPan(F32 pan); + void pump(void); + + void setDelegateVolume(ComponentInstance delegate); + + std::list mComponentInstances; + Component mOriginalDefaultOutput; + Component mVolumeAdjuster; + + static VolumeCatcherImpl *getInstance(); +private: + VolumeCatcherImpl(); + ~VolumeCatcherImpl(); + + // The component entry point needs to be able to find the instance. + static VolumeCatcherImpl *sInstance; + + F32 mVolume; + F32 mPan; +}; + +VolumeCatcherImpl *VolumeCatcherImpl::sInstance = NULL;; + +struct VolumeCatcherStorage +{ + ComponentInstance self; + ComponentInstance delegate; +}; + +static ComponentResult volume_catcher_component_entry(ComponentParameters *cp, Handle componentStorage); +static ComponentResult volume_catcher_component_open(VolumeCatcherStorage *storage, ComponentInstance self); +static ComponentResult volume_catcher_component_close(VolumeCatcherStorage *storage, ComponentInstance self); + +VolumeCatcherImpl *VolumeCatcherImpl::getInstance() +{ + if(!sInstance) + { + // The constructor will set up the instance pointer. + new VolumeCatcherImpl; + } + + return sInstance; +} + +VolumeCatcherImpl::VolumeCatcherImpl() +{ + sInstance = this; + + mVolume = 1.0; // default to full volume + mPan = 0.5; // and center pan + + // Register a component which can delegate + + // Capture the default audio output component. + ComponentDescription desc; + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = kAudioUnitSubType_DefaultOutput; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + + // Find the original default output component + mOriginalDefaultOutput = FindNextComponent(NULL, &desc); + + // Register our own output component with the same parameters + mVolumeAdjuster = RegisterComponent(&desc, NewComponentRoutineUPP(volume_catcher_component_entry), 0, NULL, NULL, NULL); + + // Capture the original component, so we always get found instead. + CaptureComponent(mOriginalDefaultOutput, mVolumeAdjuster); + +} + +static ComponentResult volume_catcher_component_entry(ComponentParameters *cp, Handle componentStorage) +{ + ComponentResult result = badComponentSelector; + VolumeCatcherStorage *storage = (VolumeCatcherStorage*)componentStorage; + + switch(cp->what) + { + case kComponentOpenSelect: +// std::cerr << "kComponentOpenSelect" << std::endl; + result = CallComponentFunctionWithStorageProcInfo((Handle)storage, cp, (ProcPtr)volume_catcher_component_open, uppCallComponentOpenProcInfo); + break; + + case kComponentCloseSelect: +// std::cerr << "kComponentCloseSelect" << std::endl; + result = CallComponentFunctionWithStorageProcInfo((Handle)storage, cp, (ProcPtr)volume_catcher_component_close, uppCallComponentCloseProcInfo); + // CallComponentFunctionWithStorageProcInfo + break; + + default: +// std::cerr << "Delegating selector: " << cp->what << " to component instance " << storage->delegate << std::endl; + result = DelegateComponentCall(cp, storage->delegate); + break; + } + + return result; +} + +static ComponentResult volume_catcher_component_open(VolumeCatcherStorage *storage, ComponentInstance self) +{ + ComponentResult result = noErr; + + storage = new VolumeCatcherStorage; + SetComponentInstanceStorage(self, (Handle)storage); + + + VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance(); + + impl->mComponentInstances.push_back(storage); + + storage->self = self; + storage->delegate = NULL; + + result = OpenAComponent(impl->mOriginalDefaultOutput, &(storage->delegate)); + +// std::cerr << "OpenAComponent result = " << result << ", component ref = " << storage->delegate << std::endl; + + impl->setDelegateVolume(storage->delegate); + + return result; +} + +static ComponentResult volume_catcher_component_close(VolumeCatcherStorage *storage, ComponentInstance self) +{ + ComponentResult result = noErr; + + if(storage) + { + if(storage->delegate) + { + CloseComponent(storage->delegate); + storage->delegate = NULL; + } + + VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance(); + impl->mComponentInstances.remove(storage); + delete[] storage; + } + + return result; +} + +VolumeCatcherImpl::~VolumeCatcherImpl() +{ + // We expect to persist until the process exits. This should never be called. + abort(); +} + +void VolumeCatcherImpl::setVolume(F32 volume) +{ + VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance(); + impl->mVolume = volume; + + std::list::iterator iter; + + for(iter = mComponentInstances.begin(); iter != mComponentInstances.end(); ++iter) + { + impl->setDelegateVolume((*iter)->delegate); + } +} + +void VolumeCatcherImpl::setPan(F32 pan) +{ + VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance(); + impl->mPan = pan; + + // TODO: implement this. + // This will probably require adding a "panner" audio unit to the chain somehow. + // There's also a "3d mixer" component that we might be able to use... +} + +void VolumeCatcherImpl::pump(void) +{ +} + +void VolumeCatcherImpl::setDelegateVolume(ComponentInstance delegate) +{ +// std::cerr << "Setting volume on component instance: " << (delegate) << " to " << mVolume << std::endl; + + OSStatus err; + err = AudioUnitSetParameter( + delegate, + kHALOutputParam_Volume, + kAudioUnitScope_Global, + 0, + mVolume, + 0); + + if(err) + { +// std::cerr << " AudioUnitSetParameter returned " << err << std::endl; + } +} + +///////////////////////////////////////////////////// + +VolumeCatcher::VolumeCatcher() +{ + pimpl = VolumeCatcherImpl::getInstance(); +} + +VolumeCatcher::~VolumeCatcher() +{ + // Let the instance persist until exit. +} + +void VolumeCatcher::setVolume(F32 volume) +{ + VolumeCatcherImpl::getInstance()->setVolume(volume); +} + +void VolumeCatcher::setPan(F32 pan) +{ + VolumeCatcherImpl::getInstance()->setPan(pan); +} + +void VolumeCatcher::pump() +{ + VolumeCatcherImpl::getInstance()->pump(); +} + -- cgit v1.2.3 From d7e4fa17e80072d609aaadaf060ee8f51f1069eb Mon Sep 17 00:00:00 2001 From: Monroe Linden Date: Tue, 13 Apr 2010 15:16:03 -0700 Subject: Minor cleanup in mac_volume_catcher.cpp. --- indra/media_plugins/webkit/mac_volume_catcher.cpp | 100 +++++++++++----------- 1 file changed, 49 insertions(+), 51 deletions(-) (limited to 'indra/media_plugins/webkit') diff --git a/indra/media_plugins/webkit/mac_volume_catcher.cpp b/indra/media_plugins/webkit/mac_volume_catcher.cpp index 03249f9e83..9788f10a58 100644 --- a/indra/media_plugins/webkit/mac_volume_catcher.cpp +++ b/indra/media_plugins/webkit/mac_volume_catcher.cpp @@ -52,9 +52,8 @@ public: void setVolume(F32 volume); void setPan(F32 pan); - void pump(void); - void setDelegateVolume(ComponentInstance delegate); + void setInstanceVolume(VolumeCatcherStorage *instance); std::list mComponentInstances; Component mOriginalDefaultOutput; @@ -62,12 +61,14 @@ public: static VolumeCatcherImpl *getInstance(); private: + // This is a singleton class -- both callers and the component implementation should use getInstance() to find the instance. VolumeCatcherImpl(); - ~VolumeCatcherImpl(); - - // The component entry point needs to be able to find the instance. static VolumeCatcherImpl *sInstance; + // The singlar instance of this class is expected to last until the process exits. + // To ensure this, we declare the destructor here but never define it, so any code which attempts to destroy the instance will not link. + ~VolumeCatcherImpl(); + F32 mVolume; F32 mPan; }; @@ -88,8 +89,7 @@ VolumeCatcherImpl *VolumeCatcherImpl::getInstance() { if(!sInstance) { - // The constructor will set up the instance pointer. - new VolumeCatcherImpl; + sInstance = new VolumeCatcherImpl; } return sInstance; @@ -97,14 +97,9 @@ VolumeCatcherImpl *VolumeCatcherImpl::getInstance() VolumeCatcherImpl::VolumeCatcherImpl() { - sInstance = this; - mVolume = 1.0; // default to full volume mPan = 0.5; // and center pan - - // Register a component which can delegate - - // Capture the default audio output component. + ComponentDescription desc; desc.componentType = kAudioUnitType_Output; desc.componentSubType = kAudioUnitSubType_DefaultOutput; @@ -153,23 +148,33 @@ static ComponentResult volume_catcher_component_entry(ComponentParameters *cp, H static ComponentResult volume_catcher_component_open(VolumeCatcherStorage *storage, ComponentInstance self) { ComponentResult result = noErr; + VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance(); storage = new VolumeCatcherStorage; - SetComponentInstanceStorage(self, (Handle)storage); - - VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance(); - - impl->mComponentInstances.push_back(storage); - storage->self = self; storage->delegate = NULL; result = OpenAComponent(impl->mOriginalDefaultOutput, &(storage->delegate)); -// std::cerr << "OpenAComponent result = " << result << ", component ref = " << storage->delegate << std::endl; + if(result != noErr) + { +// std::cerr << "OpenAComponent result = " << result << ", component ref = " << storage->delegate << std::endl; + + // If we failed to open the delagate component, our open is going to fail. Clean things up. + delete storage; + } + else + { + // Success -- set up this component's storage + SetComponentInstanceStorage(self, (Handle)storage); - impl->setDelegateVolume(storage->delegate); + // add this instance to the global list + impl->mComponentInstances.push_back(storage); + + // and set up the initial volume + impl->setInstanceVolume(storage); + } return result; } @@ -194,22 +199,15 @@ static ComponentResult volume_catcher_component_close(VolumeCatcherStorage *stor return result; } -VolumeCatcherImpl::~VolumeCatcherImpl() -{ - // We expect to persist until the process exits. This should never be called. - abort(); -} - void VolumeCatcherImpl::setVolume(F32 volume) { VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance(); impl->mVolume = volume; - - std::list::iterator iter; - - for(iter = mComponentInstances.begin(); iter != mComponentInstances.end(); ++iter) + + // Iterate through all known instances, setting the volume on each. + for(std::list::iterator iter = mComponentInstances.begin(); iter != mComponentInstances.end(); ++iter) { - impl->setDelegateVolume((*iter)->delegate); + impl->setInstanceVolume(*iter); } } @@ -223,23 +221,23 @@ void VolumeCatcherImpl::setPan(F32 pan) // There's also a "3d mixer" component that we might be able to use... } -void VolumeCatcherImpl::pump(void) -{ -} - -void VolumeCatcherImpl::setDelegateVolume(ComponentInstance delegate) +void VolumeCatcherImpl::setInstanceVolume(VolumeCatcherStorage *instance) { -// std::cerr << "Setting volume on component instance: " << (delegate) << " to " << mVolume << std::endl; - - OSStatus err; - err = AudioUnitSetParameter( - delegate, - kHALOutputParam_Volume, - kAudioUnitScope_Global, - 0, - mVolume, - 0); - +// std::cerr << "Setting volume on component instance: " << (instance->delegate) << " to " << mVolume << std::endl; + + OSStatus err = noErr; + + if(instance && instance->delegate) + { + err = AudioUnitSetParameter( + instance->delegate, + kHALOutputParam_Volume, + kAudioUnitScope_Global, + 0, + mVolume, + 0); + } + if(err) { // std::cerr << " AudioUnitSetParameter returned " << err << std::endl; @@ -260,16 +258,16 @@ VolumeCatcher::~VolumeCatcher() void VolumeCatcher::setVolume(F32 volume) { - VolumeCatcherImpl::getInstance()->setVolume(volume); + pimpl->setVolume(volume); } void VolumeCatcher::setPan(F32 pan) { - VolumeCatcherImpl::getInstance()->setPan(pan); + pimpl->setPan(pan); } void VolumeCatcher::pump() { - VolumeCatcherImpl::getInstance()->pump(); + // No periodic tasks are necessary for this implementation. } -- cgit v1.2.3