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  base::SystemMonitor* monitor = base::SystemMonitor::Get();
129  if (monitor)
130    monitor->ProcessDevicesChanged(base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE);
131
132  return S_OK;
133}
134
135STDMETHODIMP AudioDeviceListenerWin::OnDefaultDeviceChanged(
136    EDataFlow flow, ERole role, LPCWSTR new_default_device_id) {
137  // Only listen for console and communication device changes.
138  if ((role != eConsole && role != eCommunications) ||
139      (flow != eRender && flow != eCapture)) {
140    return S_OK;
141  }
142
143  // Grab a pointer to the appropriate ID member.
144  // Note that there are three "?:"'s here to select the right ID.
145  std::string* current_device_id =
146      flow == eRender ? (
147          role == eConsole ?
148              &default_render_device_id_ :
149              &default_communications_render_device_id_
150      ) : (
151          role == eConsole ?
152              &default_capture_device_id_ :
153              &default_communications_capture_device_id_
154      );
155
156  // If no device is now available, |new_default_device_id| will be NULL.
157  std::string new_device_id;
158  if (new_default_device_id)
159    new_device_id = base::WideToUTF8(new_default_device_id);
160
161  VLOG(1) << "OnDefaultDeviceChanged() "
162          << "new_default_device: "
163          << (new_default_device_id ?
164              CoreAudioUtil::GetFriendlyName(new_device_id) : "No device")
165          << ", flow: " << FlowToString(flow)
166          << ", role: " << RoleToString(role);
167
168  // Only fire a state change event if the device has actually changed.
169  // TODO(dalecurtis): This still seems to fire an extra event on my machine for
170  // an unplug event (probably others too); e.g., we get two transitions to a
171  // new default device id.
172  if (new_device_id.compare(*current_device_id) == 0)
173    return S_OK;
174
175  // Store the new id in the member variable (that current_device_id points to).
176  *current_device_id = new_device_id;
177  listener_cb_.Run();
178
179  return S_OK;
180}
181
182}  // namespace media
183