Home | History | Annotate | Download | only in win
      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 
     16 using base::win::ScopedCoMem;
     17 
     18 namespace media {
     19 
     20 static std::string FlowToString(EDataFlow flow) {
     21   return (flow == eRender) ? "eRender" : "eConsole";
     22 }
     23 
     24 static 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 
     33 static 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 
     54 AudioDeviceListenerWin::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 
     80 AudioDeviceListenerWin::~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 
     90 STDMETHODIMP_(ULONG) AudioDeviceListenerWin::AddRef() {
     91   return 1;
     92 }
     93 
     94 STDMETHODIMP_(ULONG) AudioDeviceListenerWin::Release() {
     95   return 1;
     96 }
     97 
     98 STDMETHODIMP 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 
    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
    113   return S_OK;
    114 }
    115 
    116 STDMETHODIMP AudioDeviceListenerWin::OnDeviceAdded(LPCWSTR device_id) {
    117   // We don't care when devices are added.
    118   return S_OK;
    119 }
    120 
    121 STDMETHODIMP AudioDeviceListenerWin::OnDeviceRemoved(LPCWSTR device_id) {
    122   // We don't care when devices are removed.
    123   return S_OK;
    124 }
    125 
    126 STDMETHODIMP 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 
    135 STDMETHODIMP 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       role == eRender ? (
    147           flow == eConsole ?
    148               &default_render_device_id_ :
    149               &default_communications_render_device_id_
    150       ) : (
    151           flow == 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