mac_volume_catcher.cpp 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. /**
  2. * @file mac_volume_catcher.cpp
  3. * @brief A Mac OS X specific hack to control the volume level of all audio channels opened by a process.
  4. *
  5. * @cond
  6. * $LicenseInfo:firstyear=2010&license=viewerlgpl$
  7. * Second Life Viewer Source Code
  8. * Copyright (C) 2010, Linden Research, Inc.
  9. *
  10. * This library is free software; you can redistribute it and/or
  11. * modify it under the terms of the GNU Lesser General Public
  12. * License as published by the Free Software Foundation;
  13. * version 2.1 of the License only.
  14. *
  15. * This library is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  18. * Lesser General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU Lesser General Public
  21. * License along with this library; if not, write to the Free Software
  22. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  23. *
  24. * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
  25. * $/LicenseInfo$
  26. * @endcond
  27. */
  28. /**************************************************************************************************************
  29. This code works by using CaptureComponent to capture the "Default Output" audio component
  30. (kAudioUnitType_Output/kAudioUnitSubType_DefaultOutput) and delegating all calls to the original component.
  31. It does this just to keep track of all instances of the default output component, so that it can set the
  32. kHALOutputParam_Volume parameter on all of them to adjust the output volume.
  33. **************************************************************************************************************/
  34. #include "volume_catcher.h"
  35. #include <list>
  36. #include <QuickTime/QuickTime.h>
  37. #include <AudioUnit/AudioUnit.h>
  38. struct VolumeCatcherStorage;
  39. class VolumeCatcherImpl
  40. {
  41. public:
  42. void setVolume(F32 volume);
  43. void setPan(F32 pan);
  44. void setInstanceVolume(VolumeCatcherStorage *instance);
  45. std::list<VolumeCatcherStorage*> mComponentInstances;
  46. Component mOriginalDefaultOutput;
  47. Component mVolumeAdjuster;
  48. static VolumeCatcherImpl *getInstance();
  49. private:
  50. // This is a singleton class -- both callers and the component implementation should use getInstance() to find the instance.
  51. VolumeCatcherImpl();
  52. static VolumeCatcherImpl *sInstance;
  53. // The singlar instance of this class is expected to last until the process exits.
  54. // To ensure this, we declare the destructor here but never define it, so any code which attempts to destroy the instance will not link.
  55. ~VolumeCatcherImpl();
  56. F32 mVolume;
  57. F32 mPan;
  58. };
  59. VolumeCatcherImpl *VolumeCatcherImpl::sInstance = NULL;;
  60. struct VolumeCatcherStorage
  61. {
  62. ComponentInstance self;
  63. ComponentInstance delegate;
  64. };
  65. static ComponentResult volume_catcher_component_entry(ComponentParameters *cp, Handle componentStorage);
  66. static ComponentResult volume_catcher_component_open(VolumeCatcherStorage *storage, ComponentInstance self);
  67. static ComponentResult volume_catcher_component_close(VolumeCatcherStorage *storage, ComponentInstance self);
  68. VolumeCatcherImpl *VolumeCatcherImpl::getInstance()
  69. {
  70. if(!sInstance)
  71. {
  72. sInstance = new VolumeCatcherImpl;
  73. }
  74. return sInstance;
  75. }
  76. VolumeCatcherImpl::VolumeCatcherImpl()
  77. {
  78. mVolume = 1.0; // default to full volume
  79. mPan = 0.0; // and center pan
  80. ComponentDescription desc;
  81. desc.componentType = kAudioUnitType_Output;
  82. desc.componentSubType = kAudioUnitSubType_DefaultOutput;
  83. desc.componentManufacturer = kAudioUnitManufacturer_Apple;
  84. desc.componentFlags = 0;
  85. desc.componentFlagsMask = 0;
  86. // Find the original default output component
  87. mOriginalDefaultOutput = FindNextComponent(NULL, &desc);
  88. // Register our own output component with the same parameters
  89. mVolumeAdjuster = RegisterComponent(&desc, NewComponentRoutineUPP(volume_catcher_component_entry), 0, NULL, NULL, NULL);
  90. // Capture the original component, so we always get found instead.
  91. CaptureComponent(mOriginalDefaultOutput, mVolumeAdjuster);
  92. }
  93. static ComponentResult volume_catcher_component_entry(ComponentParameters *cp, Handle componentStorage)
  94. {
  95. ComponentResult result = badComponentSelector;
  96. VolumeCatcherStorage *storage = (VolumeCatcherStorage*)componentStorage;
  97. switch(cp->what)
  98. {
  99. case kComponentOpenSelect:
  100. // std::cerr << "kComponentOpenSelect" << std::endl;
  101. result = CallComponentFunctionWithStorageProcInfo((Handle)storage, cp, (ProcPtr)volume_catcher_component_open, uppCallComponentOpenProcInfo);
  102. break;
  103. case kComponentCloseSelect:
  104. // std::cerr << "kComponentCloseSelect" << std::endl;
  105. result = CallComponentFunctionWithStorageProcInfo((Handle)storage, cp, (ProcPtr)volume_catcher_component_close, uppCallComponentCloseProcInfo);
  106. // CallComponentFunctionWithStorageProcInfo
  107. break;
  108. default:
  109. // std::cerr << "Delegating selector: " << cp->what << " to component instance " << storage->delegate << std::endl;
  110. result = DelegateComponentCall(cp, storage->delegate);
  111. break;
  112. }
  113. return result;
  114. }
  115. static ComponentResult volume_catcher_component_open(VolumeCatcherStorage *storage, ComponentInstance self)
  116. {
  117. ComponentResult result = noErr;
  118. VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance();
  119. storage = new VolumeCatcherStorage;
  120. storage->self = self;
  121. storage->delegate = NULL;
  122. result = OpenAComponent(impl->mOriginalDefaultOutput, &(storage->delegate));
  123. if(result != noErr)
  124. {
  125. // std::cerr << "OpenAComponent result = " << result << ", component ref = " << storage->delegate << std::endl;
  126. // If we failed to open the delagate component, our open is going to fail. Clean things up.
  127. delete storage;
  128. }
  129. else
  130. {
  131. // Success -- set up this component's storage
  132. SetComponentInstanceStorage(self, (Handle)storage);
  133. // add this instance to the global list
  134. impl->mComponentInstances.push_back(storage);
  135. // and set up the initial volume
  136. impl->setInstanceVolume(storage);
  137. }
  138. return result;
  139. }
  140. static ComponentResult volume_catcher_component_close(VolumeCatcherStorage *storage, ComponentInstance self)
  141. {
  142. ComponentResult result = noErr;
  143. if(storage)
  144. {
  145. if(storage->delegate)
  146. {
  147. CloseComponent(storage->delegate);
  148. storage->delegate = NULL;
  149. }
  150. VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance();
  151. impl->mComponentInstances.remove(storage);
  152. delete[] storage;
  153. }
  154. return result;
  155. }
  156. void VolumeCatcherImpl::setVolume(F32 volume)
  157. {
  158. VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance();
  159. impl->mVolume = volume;
  160. // Iterate through all known instances, setting the volume on each.
  161. for(std::list<VolumeCatcherStorage*>::iterator iter = mComponentInstances.begin(); iter != mComponentInstances.end(); ++iter)
  162. {
  163. impl->setInstanceVolume(*iter);
  164. }
  165. }
  166. void VolumeCatcherImpl::setPan(F32 pan)
  167. {
  168. VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance();
  169. impl->mPan = pan;
  170. // TODO: implement this.
  171. // This will probably require adding a "panner" audio unit to the chain somehow.
  172. // There's also a "3d mixer" component that we might be able to use...
  173. }
  174. void VolumeCatcherImpl::setInstanceVolume(VolumeCatcherStorage *instance)
  175. {
  176. // std::cerr << "Setting volume on component instance: " << (instance->delegate) << " to " << mVolume << std::endl;
  177. OSStatus err = noErr;
  178. if(instance && instance->delegate)
  179. {
  180. err = AudioUnitSetParameter(
  181. instance->delegate,
  182. kHALOutputParam_Volume,
  183. kAudioUnitScope_Global,
  184. 0,
  185. mVolume,
  186. 0);
  187. }
  188. if(err)
  189. {
  190. // std::cerr << " AudioUnitSetParameter returned " << err << std::endl;
  191. }
  192. }
  193. /////////////////////////////////////////////////////
  194. VolumeCatcher::VolumeCatcher()
  195. {
  196. pimpl = VolumeCatcherImpl::getInstance();
  197. }
  198. VolumeCatcher::~VolumeCatcher()
  199. {
  200. // Let the instance persist until exit.
  201. }
  202. void VolumeCatcher::setVolume(F32 volume)
  203. {
  204. pimpl->setVolume(volume);
  205. }
  206. void VolumeCatcher::setPan(F32 pan)
  207. {
  208. pimpl->setPan(pan);
  209. }
  210. void VolumeCatcher::pump()
  211. {
  212. // No periodic tasks are necessary for this implementation.
  213. }