audio_device_listener_win.cc revision 5f1c94371a64b3196d4be9466099bb892df9b88e
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 16using base::win::ScopedCoMem; 17 18namespace media { 19 20static std::string FlowToString(EDataFlow flow) { 21 return (flow == eRender) ? "eRender" : "eConsole"; 22} 23 24static 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 33static 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 54AudioDeviceListenerWin::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 80AudioDeviceListenerWin::~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 90STDMETHODIMP_(ULONG) AudioDeviceListenerWin::AddRef() { 91 return 1; 92} 93 94STDMETHODIMP_(ULONG) AudioDeviceListenerWin::Release() { 95 return 1; 96} 97 98STDMETHODIMP 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 108STDMETHODIMP 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 116STDMETHODIMP AudioDeviceListenerWin::OnDeviceAdded(LPCWSTR device_id) { 117 // We don't care when devices are added. 118 return S_OK; 119} 120 121STDMETHODIMP AudioDeviceListenerWin::OnDeviceRemoved(LPCWSTR device_id) { 122 // We don't care when devices are removed. 123 return S_OK; 124} 125 126STDMETHODIMP AudioDeviceListenerWin::OnDeviceStateChanged(LPCWSTR device_id, 127 DWORD new_state) { 128 if (new_state != DEVICE_STATE_ACTIVE && new_state != DEVICE_STATE_NOTPRESENT) 129 return S_OK; 130 131 base::SystemMonitor* monitor = base::SystemMonitor::Get(); 132 if (monitor) 133 monitor->ProcessDevicesChanged(base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE); 134 135 return S_OK; 136} 137 138STDMETHODIMP AudioDeviceListenerWin::OnDefaultDeviceChanged( 139 EDataFlow flow, ERole role, LPCWSTR new_default_device_id) { 140 // Only listen for console and communication device changes. 141 if ((role != eConsole && role != eCommunications) || 142 (flow != eRender && flow != eCapture)) { 143 return S_OK; 144 } 145 146 // Grab a pointer to the appropriate ID member. 147 // Note that there are three "?:"'s here to select the right ID. 148 std::string* current_device_id = 149 flow == eRender ? ( 150 role == eConsole ? 151 &default_render_device_id_ : 152 &default_communications_render_device_id_ 153 ) : ( 154 role == eConsole ? 155 &default_capture_device_id_ : 156 &default_communications_capture_device_id_ 157 ); 158 159 // If no device is now available, |new_default_device_id| will be NULL. 160 std::string new_device_id; 161 if (new_default_device_id) 162 new_device_id = base::WideToUTF8(new_default_device_id); 163 164 VLOG(1) << "OnDefaultDeviceChanged() " 165 << "new_default_device: " 166 << (new_default_device_id ? 167 CoreAudioUtil::GetFriendlyName(new_device_id) : "No device") 168 << ", flow: " << FlowToString(flow) 169 << ", role: " << RoleToString(role); 170 171 // Only fire a state change event if the device has actually changed. 172 // TODO(dalecurtis): This still seems to fire an extra event on my machine for 173 // an unplug event (probably others too); e.g., we get two transitions to a 174 // new default device id. 175 if (new_device_id.compare(*current_device_id) == 0) 176 return S_OK; 177 178 // Store the new id in the member variable (that current_device_id points to). 179 *current_device_id = new_device_id; 180 listener_cb_.Run(); 181 182 return S_OK; 183} 184 185} // namespace media 186