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