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/win/core_audio_util_win.h" 15 16 using base::win::ScopedCoMem; 17 18 namespace media { 19 20 static std::string FlowToString(EDataFlow flow) { 21 return (flow == eRender) ? "eRender" : "eConsole"; 22 } 23 24 static std::string RoleToString(ERole role) { 25 switch (role) { 26 case eConsole: return "eConsole"; 27 case eMultimedia: return "eMultimedia"; 28 case eCommunications: return "eCommunications"; 29 default: return "undefined"; 30 } 31 } 32 33 static std::string GetDeviceId(EDataFlow flow, 34 ERole role) { 35 ScopedComPtr<IMMDevice> device = 36 CoreAudioUtil::CreateDefaultDevice(flow, role); 37 if (!device) { 38 // Most probable reason for ending up here is that all audio devices are 39 // disabled or unplugged. 40 DVLOG(1) << "CoreAudioUtil::CreateDefaultDevice failed. No device?"; 41 return std::string(); 42 } 43 44 AudioDeviceName device_name; 45 HRESULT hr = CoreAudioUtil::GetDeviceName(device, &device_name); 46 if (FAILED(hr)) { 47 DVLOG(1) << "Failed to retrieve the device id: " << std::hex << hr; 48 return std::string(); 49 } 50 51 return device_name.unique_id; 52 } 53 54 AudioDeviceListenerWin::AudioDeviceListenerWin(const base::Closure& listener_cb) 55 : listener_cb_(listener_cb) { 56 CHECK(CoreAudioUtil::IsSupported()); 57 58 ScopedComPtr<IMMDeviceEnumerator> device_enumerator( 59 CoreAudioUtil::CreateDeviceEnumerator()); 60 if (!device_enumerator) 61 return; 62 63 HRESULT hr = device_enumerator->RegisterEndpointNotificationCallback(this); 64 if (FAILED(hr)) { 65 LOG(ERROR) << "RegisterEndpointNotificationCallback failed: " 66 << std::hex << hr; 67 return; 68 } 69 70 device_enumerator_ = device_enumerator; 71 72 default_render_device_id_ = GetDeviceId(eRender, eConsole); 73 default_capture_device_id_ = GetDeviceId(eCapture, eConsole); 74 default_communications_render_device_id_ = 75 GetDeviceId(eRender, eCommunications); 76 default_communications_capture_device_id_ = 77 GetDeviceId(eCapture, eCommunications); 78 } 79 80 AudioDeviceListenerWin::~AudioDeviceListenerWin() { 81 DCHECK(thread_checker_.CalledOnValidThread()); 82 if (device_enumerator_) { 83 HRESULT hr = 84 device_enumerator_->UnregisterEndpointNotificationCallback(this); 85 LOG_IF(ERROR, FAILED(hr)) << "UnregisterEndpointNotificationCallback() " 86 << "failed: " << std::hex << hr; 87 } 88 } 89 90 STDMETHODIMP_(ULONG) AudioDeviceListenerWin::AddRef() { 91 return 1; 92 } 93 94 STDMETHODIMP_(ULONG) AudioDeviceListenerWin::Release() { 95 return 1; 96 } 97 98 STDMETHODIMP AudioDeviceListenerWin::QueryInterface(REFIID iid, void** object) { 99 if (iid == IID_IUnknown || iid == __uuidof(IMMNotificationClient)) { 100 *object = static_cast<IMMNotificationClient*>(this); 101 return S_OK; 102 } 103 104 *object = NULL; 105 return E_NOINTERFACE; 106 } 107 108 STDMETHODIMP AudioDeviceListenerWin::OnPropertyValueChanged( 109 LPCWSTR device_id, const PROPERTYKEY key) { 110 // TODO(dalecurtis): We need to handle changes for the current default device 111 // here. It's tricky because this method may be called many (20+) times for 112 // a single change like sample rate. http://crbug.com/153056 113 return S_OK; 114 } 115 116 STDMETHODIMP AudioDeviceListenerWin::OnDeviceAdded(LPCWSTR device_id) { 117 // We don't care when devices are added. 118 return S_OK; 119 } 120 121 STDMETHODIMP AudioDeviceListenerWin::OnDeviceRemoved(LPCWSTR device_id) { 122 // We don't care when devices are removed. 123 return S_OK; 124 } 125 126 STDMETHODIMP AudioDeviceListenerWin::OnDeviceStateChanged(LPCWSTR device_id, 127 DWORD new_state) { 128 base::SystemMonitor* monitor = base::SystemMonitor::Get(); 129 if (monitor) 130 monitor->ProcessDevicesChanged(base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE); 131 132 return S_OK; 133 } 134 135 STDMETHODIMP AudioDeviceListenerWin::OnDefaultDeviceChanged( 136 EDataFlow flow, ERole role, LPCWSTR new_default_device_id) { 137 // Only listen for console and communication device changes. 138 if ((role != eConsole && role != eCommunications) || 139 (flow != eRender && flow != eCapture)) { 140 return S_OK; 141 } 142 143 // Grab a pointer to the appropriate ID member. 144 // Note that there are three "?:"'s here to select the right ID. 145 std::string* current_device_id = 146 flow == eRender ? ( 147 role == eConsole ? 148 &default_render_device_id_ : 149 &default_communications_render_device_id_ 150 ) : ( 151 role == eConsole ? 152 &default_capture_device_id_ : 153 &default_communications_capture_device_id_ 154 ); 155 156 // If no device is now available, |new_default_device_id| will be NULL. 157 std::string new_device_id; 158 if (new_default_device_id) 159 new_device_id = base::WideToUTF8(new_default_device_id); 160 161 VLOG(1) << "OnDefaultDeviceChanged() " 162 << "new_default_device: " 163 << (new_default_device_id ? 164 CoreAudioUtil::GetFriendlyName(new_device_id) : "No device") 165 << ", flow: " << FlowToString(flow) 166 << ", role: " << RoleToString(role); 167 168 // Only fire a state change event if the device has actually changed. 169 // TODO(dalecurtis): This still seems to fire an extra event on my machine for 170 // an unplug event (probably others too); e.g., we get two transitions to a 171 // new default device id. 172 if (new_device_id.compare(*current_device_id) == 0) 173 return S_OK; 174 175 // Store the new id in the member variable (that current_device_id points to). 176 *current_device_id = new_device_id; 177 listener_cb_.Run(); 178 179 return S_OK; 180 } 181 182 } // namespace media 183