blob: cdf7bec9061afad95fe28fb598aae87acea04a67 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/audio/win/audio_device_listener_win.h"
#include <Audioclient.h>
#include "base/logging.h"
#include "base/strings/utf_string_conversions.h"
#include "base/system/system_monitor.h"
#include "base/time/default_tick_clock.h"
#include "base/win/scoped_co_mem.h"
#include "base/win/windows_version.h"
#include "media/audio/win/core_audio_util_win.h"
using base::win::ScopedCoMem;
namespace media {
static std::string FlowToString(EDataFlow flow) {
return flow == eRender ? "eRender" : "eCapture";
}
static std::string RoleToString(ERole role) {
switch (role) {
case eConsole:
return "eConsole";
case eMultimedia:
return "eMultimedia";
case eCommunications:
return "eCommunications";
default:
return "undefined";
}
}
AudioDeviceListenerWin::AudioDeviceListenerWin(
base::RepeatingClosure listener_cb)
: listener_cb_(std::move(listener_cb)),
tick_clock_(base::DefaultTickClock::GetInstance()) {
// CreateDeviceEnumerator can fail on some installations of Windows such
// as "Windows Server 2008 R2" where the desktop experience isn't available.
auto device_enumerator = CoreAudioUtil::CreateDeviceEnumerator();
if (!device_enumerator) {
DLOG(ERROR) << "Failed to create device enumeration.";
return;
}
HRESULT hr = device_enumerator->RegisterEndpointNotificationCallback(this);
if (FAILED(hr)) {
DLOG(ERROR) << "RegisterEndpointNotificationCallback failed: " << std::hex
<< hr;
return;
}
device_enumerator_ = device_enumerator;
}
AudioDeviceListenerWin::~AudioDeviceListenerWin() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (!device_enumerator_)
return;
HRESULT hr = device_enumerator_->UnregisterEndpointNotificationCallback(this);
DLOG_IF(ERROR, FAILED(hr)) << "UnregisterEndpointNotificationCallback() "
<< "failed: " << std::hex << hr;
}
ULONG AudioDeviceListenerWin::AddRef() {
return 1;
}
ULONG AudioDeviceListenerWin::Release() {
return 1;
}
HRESULT AudioDeviceListenerWin::QueryInterface(REFIID iid, void** object) {
if (iid == IID_IUnknown || iid == __uuidof(IMMNotificationClient)) {
*object = static_cast<IMMNotificationClient*>(this);
return S_OK;
}
*object = nullptr;
return E_NOINTERFACE;
}
HRESULT AudioDeviceListenerWin::OnPropertyValueChanged(LPCWSTR device_id,
const PROPERTYKEY key) {
// Property changes are handled by IAudioSessionControl listeners hung off of
// each WASAPIAudioOutputStream() since not all property changes make it to
// this method and those that do are spammed 10s of times.
return S_OK;
}
HRESULT AudioDeviceListenerWin::OnDeviceAdded(LPCWSTR device_id) {
// We don't care when devices are added.
return S_OK;
}
HRESULT AudioDeviceListenerWin::OnDeviceRemoved(LPCWSTR device_id) {
// We don't care when devices are removed.
return S_OK;
}
HRESULT AudioDeviceListenerWin::OnDeviceStateChanged(LPCWSTR device_id,
DWORD new_state) {
if (auto* monitor = base::SystemMonitor::Get())
monitor->ProcessDevicesChanged(base::SystemMonitor::DEVTYPE_AUDIO);
return S_OK;
}
HRESULT AudioDeviceListenerWin::OnDefaultDeviceChanged(
EDataFlow flow,
ERole role,
LPCWSTR new_default_device_id) {
// Only listen for console and communication device changes.
if ((role != eConsole && role != eCommunications) ||
(flow != eRender && flow != eCapture)) {
return S_OK;
}
// If no device is now available, |new_default_device_id| will be NULL.
std::string new_device_id;
if (new_default_device_id)
new_device_id = base::WideToUTF8(new_default_device_id);
// Only output device changes should be forwarded. Do not attempt to filter
// changes based on device id since some devices may not change their device
// id and instead trigger some internal flow change: http://crbug.com/506712
//
// We rate limit device changes to avoid a single device change causing back
// to back changes for eCommunications and eConsole; this is worth doing as
// it provides a substantially faster resumption of playback.
bool did_run_listener_cb = false;
const base::TimeTicks now = tick_clock_->NowTicks();
if (flow == eRender && (now - last_device_change_time_ > kDeviceChangeLimit ||
new_device_id.compare(last_device_id_) != 0)) {
last_device_change_time_ = now;
last_device_id_ = new_device_id;
listener_cb_.Run();
did_run_listener_cb = true;
}
if (auto* monitor = base::SystemMonitor::Get())
monitor->ProcessDevicesChanged(base::SystemMonitor::DEVTYPE_AUDIO);
DVLOG(1) << "OnDefaultDeviceChanged() "
<< "new_default_device: "
<< (new_default_device_id
? CoreAudioUtil::GetFriendlyName(new_device_id, flow, role)
: "no device")
<< ", flow: " << FlowToString(flow)
<< ", role: " << RoleToString(role)
<< ", notified manager: " << (did_run_listener_cb ? "Yes" : "No");
return S_OK;
}
} // namespace media