audio_device_listener_win.cc revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
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 = base::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