1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "media/audio/win/audio_device_listener_win.h" 6 7 #include <Audioclient.h> 8 9 #include "base/logging.h" 10 #include "base/strings/utf_string_conversions.h" 11 #include "base/system_monitor/system_monitor.h" 12 #include "base/win/scoped_co_mem.h" 13 #include "base/win/windows_version.h" 14 #include "media/audio/audio_util.h" 15 #include "media/audio/win/core_audio_util_win.h" 16 17 using base::win::ScopedCoMem; 18 19 namespace media { 20 21 static std::string FlowToString(EDataFlow flow) { 22 return (flow == eRender) ? "eRender" : "eConsole"; 23 } 24 25 static std::string RoleToString(ERole role) { 26 switch (role) { 27 case eConsole: return "eConsole"; 28 case eMultimedia: return "eMultimedia"; 29 case eCommunications: return "eCommunications"; 30 default: return "undefined"; 31 } 32 } 33 34 AudioDeviceListenerWin::AudioDeviceListenerWin(const base::Closure& listener_cb) 35 : listener_cb_(listener_cb) { 36 CHECK(CoreAudioUtil::IsSupported()); 37 38 ScopedComPtr<IMMDeviceEnumerator> device_enumerator( 39 CoreAudioUtil::CreateDeviceEnumerator()); 40 if (!device_enumerator) 41 return; 42 43 HRESULT hr = device_enumerator->RegisterEndpointNotificationCallback(this); 44 if (FAILED(hr)) { 45 LOG(ERROR) << "RegisterEndpointNotificationCallback failed: " 46 << std::hex << hr; 47 return; 48 } 49 50 device_enumerator_ = device_enumerator; 51 52 ScopedComPtr<IMMDevice> device = 53 CoreAudioUtil::CreateDefaultDevice(eRender, eConsole); 54 if (!device) { 55 // Most probable reason for ending up here is that all audio devices are 56 // disabled or unplugged. 57 VLOG(1) << "CoreAudioUtil::CreateDefaultDevice failed. No device?"; 58 return; 59 } 60 61 AudioDeviceName device_name; 62 hr = CoreAudioUtil::GetDeviceName(device, &device_name); 63 if (FAILED(hr)) { 64 VLOG(1) << "Failed to retrieve the device id: " << std::hex << hr; 65 return; 66 } 67 default_render_device_id_ = device_name.unique_id; 68 } 69 70 AudioDeviceListenerWin::~AudioDeviceListenerWin() { 71 DCHECK(thread_checker_.CalledOnValidThread()); 72 if (device_enumerator_) { 73 HRESULT hr = 74 device_enumerator_->UnregisterEndpointNotificationCallback(this); 75 LOG_IF(ERROR, FAILED(hr)) << "UnregisterEndpointNotificationCallback() " 76 << "failed: " << std::hex << hr; 77 } 78 } 79 80 STDMETHODIMP_(ULONG) AudioDeviceListenerWin::AddRef() { 81 return 1; 82 } 83 84 STDMETHODIMP_(ULONG) AudioDeviceListenerWin::Release() { 85 return 1; 86 } 87 88 STDMETHODIMP AudioDeviceListenerWin::QueryInterface(REFIID iid, void** object) { 89 if (iid == IID_IUnknown || iid == __uuidof(IMMNotificationClient)) { 90 *object = static_cast<IMMNotificationClient*>(this); 91 return S_OK; 92 } 93 94 *object = NULL; 95 return E_NOINTERFACE; 96 } 97 98 STDMETHODIMP AudioDeviceListenerWin::OnPropertyValueChanged( 99 LPCWSTR device_id, const PROPERTYKEY key) { 100 // TODO(dalecurtis): We need to handle changes for the current default device 101 // here. It's tricky because this method may be called many (20+) times for 102 // a single change like sample rate. http://crbug.com/153056 103 return S_OK; 104 } 105 106 STDMETHODIMP AudioDeviceListenerWin::OnDeviceAdded(LPCWSTR device_id) { 107 // We don't care when devices are added. 108 return S_OK; 109 } 110 111 STDMETHODIMP AudioDeviceListenerWin::OnDeviceRemoved(LPCWSTR device_id) { 112 // We don't care when devices are removed. 113 return S_OK; 114 } 115 116 STDMETHODIMP AudioDeviceListenerWin::OnDeviceStateChanged(LPCWSTR device_id, 117 DWORD new_state) { 118 if (new_state != DEVICE_STATE_ACTIVE && new_state != DEVICE_STATE_NOTPRESENT) 119 return S_OK; 120 121 base::SystemMonitor* monitor = base::SystemMonitor::Get(); 122 if (monitor) 123 monitor->ProcessDevicesChanged(base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE); 124 125 return S_OK; 126 } 127 128 STDMETHODIMP AudioDeviceListenerWin::OnDefaultDeviceChanged( 129 EDataFlow flow, ERole role, LPCWSTR new_default_device_id) { 130 // Only listen for output device changes right now... 131 if (flow != eConsole && role != eRender) 132 return S_OK; 133 134 // If no device is now available, |new_default_device_id| will be NULL. 135 std::string new_device_id; 136 if (new_default_device_id) 137 new_device_id = WideToUTF8(new_default_device_id); 138 139 VLOG(1) << "OnDefaultDeviceChanged() " 140 << "new_default_device: " 141 << (new_default_device_id ? 142 CoreAudioUtil::GetFriendlyName(new_device_id) : "No device") 143 << ", flow: " << FlowToString(flow) 144 << ", role: " << RoleToString(role); 145 146 // Only fire a state change event if the device has actually changed. 147 // TODO(dalecurtis): This still seems to fire an extra event on my machine for 148 // an unplug event (probably others too); e.g., we get two transitions to a 149 // new default device id. 150 if (new_device_id.compare(default_render_device_id_) == 0) 151 return S_OK; 152 153 default_render_device_id_ = new_device_id; 154 listener_cb_.Run(); 155 156 return S_OK; 157 } 158 159 } // namespace media 160