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/audio_util.h"
     15 #include "media/audio/win/core_audio_util_win.h"
     16 
     17 using base::win::ScopedCoMem;
     18 
     19 namespace media {
     20 
     21 static std::string FlowToString(EDataFlow flow) {
     22   return (flow == eRender) ? "eRender" : "eConsole";
     23 }
     24 
     25 static 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 
     34 AudioDeviceListenerWin::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 
     70 AudioDeviceListenerWin::~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 
     80 STDMETHODIMP_(ULONG) AudioDeviceListenerWin::AddRef() {
     81   return 1;
     82 }
     83 
     84 STDMETHODIMP_(ULONG) AudioDeviceListenerWin::Release() {
     85   return 1;
     86 }
     87 
     88 STDMETHODIMP 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 
     98 STDMETHODIMP 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 
    106 STDMETHODIMP AudioDeviceListenerWin::OnDeviceAdded(LPCWSTR device_id) {
    107   // We don't care when devices are added.
    108   return S_OK;
    109 }
    110 
    111 STDMETHODIMP AudioDeviceListenerWin::OnDeviceRemoved(LPCWSTR device_id) {
    112   // We don't care when devices are removed.
    113   return S_OK;
    114 }
    115 
    116 STDMETHODIMP 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 
    128 STDMETHODIMP 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