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.
5 #include "media/audio/win/audio_device_listener_win.h"
7 #include <Audioclient.h>
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"
16 using base::win::ScopedCoMem;
20 static std::string FlowToString(EDataFlow flow) {
21 return (flow == eRender) ? "eRender" : "eConsole";
24 static std::string RoleToString(ERole role) {
26 case eConsole: return "eConsole";
27 case eMultimedia: return "eMultimedia";
28 case eCommunications: return "eCommunications";
29 default: return "undefined";
33 static std::string GetDeviceId(EDataFlow flow,
35 ScopedComPtr<IMMDevice> device =
36 CoreAudioUtil::CreateDefaultDevice(flow, role);
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?";
44 AudioDeviceName device_name;
45 HRESULT hr = CoreAudioUtil::GetDeviceName(device, &device_name);
47 DVLOG(1) << "Failed to retrieve the device id: " << std::hex << hr;
51 return device_name.unique_id;
54 AudioDeviceListenerWin::AudioDeviceListenerWin(const base::Closure& listener_cb)
55 : listener_cb_(listener_cb) {
56 CHECK(CoreAudioUtil::IsSupported());
58 ScopedComPtr<IMMDeviceEnumerator> device_enumerator(
59 CoreAudioUtil::CreateDeviceEnumerator());
60 if (!device_enumerator)
63 HRESULT hr = device_enumerator->RegisterEndpointNotificationCallback(this);
65 LOG(ERROR) << "RegisterEndpointNotificationCallback failed: "
70 device_enumerator_ = device_enumerator;
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);
80 AudioDeviceListenerWin::~AudioDeviceListenerWin() {
81 DCHECK(thread_checker_.CalledOnValidThread());
82 if (device_enumerator_) {
84 device_enumerator_->UnregisterEndpointNotificationCallback(this);
85 LOG_IF(ERROR, FAILED(hr)) << "UnregisterEndpointNotificationCallback() "
86 << "failed: " << std::hex << hr;
90 STDMETHODIMP_(ULONG) AudioDeviceListenerWin::AddRef() {
94 STDMETHODIMP_(ULONG) AudioDeviceListenerWin::Release() {
98 STDMETHODIMP AudioDeviceListenerWin::QueryInterface(REFIID iid, void** object) {
99 if (iid == IID_IUnknown || iid == __uuidof(IMMNotificationClient)) {
100 *object = static_cast<IMMNotificationClient*>(this);
105 return E_NOINTERFACE;
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
116 STDMETHODIMP AudioDeviceListenerWin::OnDeviceAdded(LPCWSTR device_id) {
117 // We don't care when devices are added.
121 STDMETHODIMP AudioDeviceListenerWin::OnDeviceRemoved(LPCWSTR device_id) {
122 // We don't care when devices are removed.
126 STDMETHODIMP AudioDeviceListenerWin::OnDeviceStateChanged(LPCWSTR device_id,
128 if (new_state != DEVICE_STATE_ACTIVE && new_state != DEVICE_STATE_NOTPRESENT)
131 base::SystemMonitor* monitor = base::SystemMonitor::Get();
133 monitor->ProcessDevicesChanged(base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE);
138 STDMETHODIMP 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)) {
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 =
151 &default_render_device_id_ :
152 &default_communications_render_device_id_
155 &default_capture_device_id_ :
156 &default_communications_capture_device_id_
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);
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);
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)
178 // Store the new id in the member variable (that current_device_id points to).
179 *current_device_id = new_device_id;