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