Commit 9973884f authored by Dale Curtis's avatar Dale Curtis Committed by Commit Bot

Reland: "Support audio channel count and sample rate changes on Windows."

Includes one line fix in audio_device_listener_win.cc to use the
DCHECK_CALLED_ON_VALID_THREAD() macro.

----- [ Original Description ] -----

This fixes a TODO in AudioDeviceListenerWin on listening for
property value changes... by listening for them in a completely
different way.

Unfortunately forcing mono audio for accessibility doesn't
generate IMMNotificationClient events, so we need to instead
use the IAudioSessionEvents interface, but that can only be
hung off a fully initialized IAudioClient... which is only
done during WASAPI audio output setup.

So the original TODO is fixed by adding a 1-to-1 path for device
changes (in addition to the 1-to-many path). We still need both
paths since just switching your default device won't generate an
IAudioSessionEvent notification, only an IMMNotificationClient
event. Additionally fake and WaveOut based devices don't have a
IAudioSessionEvents interface, so still need the old path.

Misc other cleanups:
- Expands the AudioDeviceListenerWin unit tests.
- Fixes broken --force-wave-audio switch in AudioManagerWin.

BUG=1020006
TEST=property changes, mono audio changes, default changes work.
TBR=henrika, tguilbert

Change-Id: I95f5f439982a68d2b509db10978a570fb98081b5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1931408Reviewed-by: default avatarDale Curtis <dalecurtis@chromium.org>
Reviewed-by: default avatarThomas Guilbert <tguilbert@chromium.org>
Commit-Queue: Dale Curtis <dalecurtis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#718374}
parent 86244992
...@@ -195,6 +195,8 @@ source_set("audio") { ...@@ -195,6 +195,8 @@ source_set("audio") {
"win/audio_low_latency_output_win.h", "win/audio_low_latency_output_win.h",
"win/audio_manager_win.cc", "win/audio_manager_win.cc",
"win/audio_manager_win.h", "win/audio_manager_win.h",
"win/audio_session_event_listener_win.cc",
"win/audio_session_event_listener_win.h",
"win/avrt_wrapper_win.cc", "win/avrt_wrapper_win.cc",
"win/avrt_wrapper_win.h", "win/avrt_wrapper_win.h",
"win/core_audio_util_win.cc", "win/core_audio_util_win.cc",
...@@ -441,6 +443,7 @@ source_set("unit_tests") { ...@@ -441,6 +443,7 @@ source_set("unit_tests") {
"win/audio_low_latency_input_win_unittest.cc", "win/audio_low_latency_input_win_unittest.cc",
"win/audio_low_latency_output_win_unittest.cc", "win/audio_low_latency_output_win_unittest.cc",
"win/audio_output_win_unittest.cc", "win/audio_output_win_unittest.cc",
"win/audio_session_event_listener_win_unittest.cc",
"win/core_audio_util_win_unittest.cc", "win/core_audio_util_win_unittest.cc",
"win/device_enumeration_win_unittest.cc", "win/device_enumeration_win_unittest.cc",
] ]
......
...@@ -19,32 +19,38 @@ using base::win::ScopedCoMem; ...@@ -19,32 +19,38 @@ using base::win::ScopedCoMem;
namespace media { namespace media {
static std::string FlowToString(EDataFlow flow) { static std::string FlowToString(EDataFlow flow) {
return (flow == eRender) ? "eRender" : "eConsole"; return flow == eRender ? "eRender" : "eConsole";
} }
static std::string RoleToString(ERole role) { static std::string RoleToString(ERole role) {
switch (role) { switch (role) {
case eConsole: return "eConsole"; case eConsole:
case eMultimedia: return "eMultimedia"; return "eConsole";
case eCommunications: return "eCommunications"; case eMultimedia:
default: return "undefined"; return "eMultimedia";
case eCommunications:
return "eCommunications";
default:
return "undefined";
} }
} }
AudioDeviceListenerWin::AudioDeviceListenerWin(const base::Closure& listener_cb) AudioDeviceListenerWin::AudioDeviceListenerWin(
: listener_cb_(listener_cb), base::RepeatingClosure listener_cb)
: listener_cb_(std::move(listener_cb)),
tick_clock_(base::DefaultTickClock::GetInstance()) { tick_clock_(base::DefaultTickClock::GetInstance()) {
// CreateDeviceEnumerator can fail on some installations of Windows such // CreateDeviceEnumerator can fail on some installations of Windows such
// as "Windows Server 2008 R2" where the desktop experience isn't available. // as "Windows Server 2008 R2" where the desktop experience isn't available.
Microsoft::WRL::ComPtr<IMMDeviceEnumerator> device_enumerator( auto device_enumerator = CoreAudioUtil::CreateDeviceEnumerator();
CoreAudioUtil::CreateDeviceEnumerator()); if (!device_enumerator) {
if (!device_enumerator.Get()) DLOG(ERROR) << "Failed to create device enumeration.";
return; return;
}
HRESULT hr = device_enumerator->RegisterEndpointNotificationCallback(this); HRESULT hr = device_enumerator->RegisterEndpointNotificationCallback(this);
if (FAILED(hr)) { if (FAILED(hr)) {
LOG(ERROR) << "RegisterEndpointNotificationCallback failed: " DLOG(ERROR) << "RegisterEndpointNotificationCallback failed: " << std::hex
<< std::hex << hr; << hr;
return; return;
} }
...@@ -52,13 +58,13 @@ AudioDeviceListenerWin::AudioDeviceListenerWin(const base::Closure& listener_cb) ...@@ -52,13 +58,13 @@ AudioDeviceListenerWin::AudioDeviceListenerWin(const base::Closure& listener_cb)
} }
AudioDeviceListenerWin::~AudioDeviceListenerWin() { AudioDeviceListenerWin::~AudioDeviceListenerWin() {
DCHECK(thread_checker_.CalledOnValidThread()); DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (device_enumerator_.Get()) { if (!device_enumerator_)
HRESULT hr = return;
device_enumerator_->UnregisterEndpointNotificationCallback(this);
LOG_IF(ERROR, FAILED(hr)) << "UnregisterEndpointNotificationCallback() " HRESULT hr = device_enumerator_->UnregisterEndpointNotificationCallback(this);
<< "failed: " << std::hex << hr; DLOG_IF(ERROR, FAILED(hr)) << "UnregisterEndpointNotificationCallback() "
} << "failed: " << std::hex << hr;
} }
STDMETHODIMP_(ULONG) AudioDeviceListenerWin::AddRef() { STDMETHODIMP_(ULONG) AudioDeviceListenerWin::AddRef() {
...@@ -75,15 +81,16 @@ STDMETHODIMP AudioDeviceListenerWin::QueryInterface(REFIID iid, void** object) { ...@@ -75,15 +81,16 @@ STDMETHODIMP AudioDeviceListenerWin::QueryInterface(REFIID iid, void** object) {
return S_OK; return S_OK;
} }
*object = NULL; *object = nullptr;
return E_NOINTERFACE; return E_NOINTERFACE;
} }
STDMETHODIMP AudioDeviceListenerWin::OnPropertyValueChanged( STDMETHODIMP AudioDeviceListenerWin::OnPropertyValueChanged(
LPCWSTR device_id, const PROPERTYKEY key) { LPCWSTR device_id,
// TODO(dalecurtis): We need to handle changes for the current default device const PROPERTYKEY key) {
// here. It's tricky because this method may be called many (20+) times for // Property changes are handled by IAudioSessionControl listeners hung off of
// a single change like sample rate. http://crbug.com/153056 // each WASAPIAudioOutputStream() since not all property changes make it to
// this method and those that do are spammed 10s of times.
return S_OK; return S_OK;
} }
...@@ -99,15 +106,15 @@ STDMETHODIMP AudioDeviceListenerWin::OnDeviceRemoved(LPCWSTR device_id) { ...@@ -99,15 +106,15 @@ STDMETHODIMP AudioDeviceListenerWin::OnDeviceRemoved(LPCWSTR device_id) {
STDMETHODIMP AudioDeviceListenerWin::OnDeviceStateChanged(LPCWSTR device_id, STDMETHODIMP AudioDeviceListenerWin::OnDeviceStateChanged(LPCWSTR device_id,
DWORD new_state) { DWORD new_state) {
base::SystemMonitor* monitor = base::SystemMonitor::Get(); if (auto* monitor = base::SystemMonitor::Get())
if (monitor)
monitor->ProcessDevicesChanged(base::SystemMonitor::DEVTYPE_AUDIO); monitor->ProcessDevicesChanged(base::SystemMonitor::DEVTYPE_AUDIO);
return S_OK; return S_OK;
} }
STDMETHODIMP AudioDeviceListenerWin::OnDefaultDeviceChanged( STDMETHODIMP AudioDeviceListenerWin::OnDefaultDeviceChanged(
EDataFlow flow, ERole role, LPCWSTR new_default_device_id) { EDataFlow flow,
ERole role,
LPCWSTR new_default_device_id) {
// Only listen for console and communication device changes. // Only listen for console and communication device changes.
if ((role != eConsole && role != eCommunications) || if ((role != eConsole && role != eCommunications) ||
(flow != eRender && flow != eCapture)) { (flow != eRender && flow != eCapture)) {
...@@ -128,16 +135,13 @@ STDMETHODIMP AudioDeviceListenerWin::OnDefaultDeviceChanged( ...@@ -128,16 +135,13 @@ STDMETHODIMP AudioDeviceListenerWin::OnDefaultDeviceChanged(
// it provides a substantially faster resumption of playback. // it provides a substantially faster resumption of playback.
bool did_run_listener_cb = false; bool did_run_listener_cb = false;
const base::TimeTicks now = tick_clock_->NowTicks(); const base::TimeTicks now = tick_clock_->NowTicks();
if (flow == eRender && if (flow == eRender && now - last_device_change_time_ > kDeviceChangeLimit) {
now - last_device_change_time_ >
base::TimeDelta::FromMilliseconds(kDeviceChangeLimitMs)) {
last_device_change_time_ = now; last_device_change_time_ = now;
listener_cb_.Run(); listener_cb_.Run();
did_run_listener_cb = true; did_run_listener_cb = true;
} }
base::SystemMonitor* monitor = base::SystemMonitor::Get(); if (auto* monitor = base::SystemMonitor::Get())
if (monitor)
monitor->ProcessDevicesChanged(base::SystemMonitor::DEVTYPE_AUDIO); monitor->ProcessDevicesChanged(base::SystemMonitor::DEVTYPE_AUDIO);
DVLOG(1) << "OnDefaultDeviceChanged() " DVLOG(1) << "OnDefaultDeviceChanged() "
......
...@@ -32,38 +32,38 @@ namespace media { ...@@ -32,38 +32,38 @@ namespace media {
class MEDIA_EXPORT AudioDeviceListenerWin : public IMMNotificationClient { class MEDIA_EXPORT AudioDeviceListenerWin : public IMMNotificationClient {
public: public:
// The listener callback will be called from a system level multimedia thread, // The listener callback will be called from a system level multimedia thread,
// thus the callee must be thread safe. |listener| is a permanent callback // thus the callee must be thread safe. |listener_cb| is a permanent callback
// and must outlive AudioDeviceListenerWin. // and must outlive AudioDeviceListenerWin.
explicit AudioDeviceListenerWin(const base::Closure& listener_cb); explicit AudioDeviceListenerWin(base::RepeatingClosure listener_cb);
virtual ~AudioDeviceListenerWin(); virtual ~AudioDeviceListenerWin();
private: private:
friend class AudioDeviceListenerWinTest; friend class AudioDeviceListenerWinTest;
// Minimum allowed time between device change notifications. // Minimum allowed time between device change notifications.
static const int kDeviceChangeLimitMs = 250; static constexpr base::TimeDelta kDeviceChangeLimit =
base::TimeDelta::FromMilliseconds(250);
// IMMNotificationClient implementation. // IMMNotificationClient implementation.
STDMETHOD_(ULONG, AddRef)() override; STDMETHOD_(ULONG, AddRef)() override;
STDMETHOD_(ULONG, Release)() override; STDMETHOD_(ULONG, Release)() override;
STDMETHOD(QueryInterface)(REFIID iid, void** object) override; STDMETHOD(QueryInterface)(REFIID iid, void** object) override;
STDMETHOD(OnPropertyValueChanged)(LPCWSTR device_id, STDMETHOD(OnPropertyValueChanged)
const PROPERTYKEY key) override; (LPCWSTR device_id, const PROPERTYKEY key) override;
STDMETHOD(OnDeviceAdded)(LPCWSTR device_id) override; STDMETHOD(OnDeviceAdded)(LPCWSTR device_id) override;
STDMETHOD(OnDeviceRemoved)(LPCWSTR device_id) override; STDMETHOD(OnDeviceRemoved)(LPCWSTR device_id) override;
STDMETHOD(OnDeviceStateChanged)(LPCWSTR device_id, DWORD new_state) override; STDMETHOD(OnDeviceStateChanged)(LPCWSTR device_id, DWORD new_state) override;
STDMETHOD(OnDefaultDeviceChanged)(EDataFlow flow, STDMETHOD(OnDefaultDeviceChanged)
ERole role, (EDataFlow flow, ERole role, LPCWSTR new_default_device_id) override;
LPCWSTR new_default_device_id) override;
base::Closure listener_cb_; const base::RepeatingClosure listener_cb_;
Microsoft::WRL::ComPtr<IMMDeviceEnumerator> device_enumerator_; Microsoft::WRL::ComPtr<IMMDeviceEnumerator> device_enumerator_;
// Used to rate limit device change events. // Used to rate limit device change events.
base::TimeTicks last_device_change_time_; base::TimeTicks last_device_change_time_;
// AudioDeviceListenerWin must be constructed and destructed on one thread. // AudioDeviceListenerWin must be constructed and destructed on one thread.
base::ThreadChecker thread_checker_; THREAD_CHECKER(thread_checker_);
const base::TickClock* tick_clock_; const base::TickClock* tick_clock_;
......
...@@ -11,7 +11,9 @@ ...@@ -11,7 +11,9 @@
#include "base/bind_helpers.h" #include "base/bind_helpers.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/system/system_monitor.h"
#include "base/test/simple_test_tick_clock.h" #include "base/test/simple_test_tick_clock.h"
#include "base/test/task_environment.h"
#include "base/win/scoped_com_initializer.h" #include "base/win/scoped_com_initializer.h"
#include "media/audio/audio_manager.h" #include "media/audio/audio_manager.h"
#include "media/audio/audio_unittest_util.h" #include "media/audio/audio_unittest_util.h"
...@@ -23,51 +25,62 @@ using base::win::ScopedCOMInitializer; ...@@ -23,51 +25,62 @@ using base::win::ScopedCOMInitializer;
namespace media { namespace media {
static const char kFirstTestDevice[] = "test_device_0"; constexpr char kFirstTestDevice[] = "test_device_0";
static const char kSecondTestDevice[] = "test_device_1"; constexpr char kSecondTestDevice[] = "test_device_1";
class AudioDeviceListenerWinTest : public testing::Test { class AudioDeviceListenerWinTest
: public testing::Test,
public base::SystemMonitor::DevicesChangedObserver {
public: public:
AudioDeviceListenerWinTest() { AudioDeviceListenerWinTest() {
DCHECK(com_init_.Succeeded()); DCHECK(com_init_.Succeeded());
}
virtual void SetUp() {
if (!CoreAudioUtil::IsSupported()) if (!CoreAudioUtil::IsSupported())
return; return;
output_device_listener_.reset(new AudioDeviceListenerWin(base::Bind( system_monitor_.AddDevicesChangedObserver(this);
&AudioDeviceListenerWinTest::OnDeviceChange, base::Unretained(this))));
output_device_listener_ = std::make_unique<AudioDeviceListenerWin>(
base::BindRepeating(&AudioDeviceListenerWinTest::OnDeviceChange,
base::Unretained(this)));
tick_clock_.Advance(base::TimeDelta::FromSeconds(12345)); tick_clock_.Advance(base::TimeDelta::FromSeconds(12345));
output_device_listener_->tick_clock_ = &tick_clock_; output_device_listener_->tick_clock_ = &tick_clock_;
} }
~AudioDeviceListenerWinTest() override {
system_monitor_.RemoveDevicesChangedObserver(this);
}
void AdvanceLastDeviceChangeTime() { void AdvanceLastDeviceChangeTime() {
tick_clock_.Advance(base::TimeDelta::FromMilliseconds( tick_clock_.Advance(AudioDeviceListenerWin::kDeviceChangeLimit +
AudioDeviceListenerWin::kDeviceChangeLimitMs + 1)); base::TimeDelta::FromMilliseconds(1));
} }
// Simulate a device change where no output devices are available. // Simulate a device change where no output devices are available.
bool SimulateNullDefaultOutputDeviceChange() { bool SimulateNullDefaultOutputDeviceChange() {
return output_device_listener_->OnDefaultDeviceChanged( auto result = output_device_listener_->OnDefaultDeviceChanged(
static_cast<EDataFlow>(eConsole), static_cast<ERole>(eRender), static_cast<EDataFlow>(eConsole), static_cast<ERole>(eRender), nullptr);
NULL) == S_OK; task_environment_.RunUntilIdle();
return result == S_OK;
} }
bool SimulateDefaultOutputDeviceChange(const char* new_device_id) { bool SimulateDefaultOutputDeviceChange(const char* new_device_id) {
return output_device_listener_->OnDefaultDeviceChanged( auto result = output_device_listener_->OnDefaultDeviceChanged(
static_cast<EDataFlow>(eConsole), static_cast<ERole>(eRender), static_cast<EDataFlow>(eConsole), static_cast<ERole>(eRender),
base::ASCIIToUTF16(new_device_id).c_str()) == S_OK; base::ASCIIToUTF16(new_device_id).c_str());
task_environment_.RunUntilIdle();
return result == S_OK;
} }
MOCK_METHOD0(OnDeviceChange, void()); MOCK_METHOD0(OnDeviceChange, void());
MOCK_METHOD1(OnDevicesChanged, void(base::SystemMonitor::DeviceType));
private: private:
ScopedCOMInitializer com_init_; ScopedCOMInitializer com_init_;
std::unique_ptr<AudioDeviceListenerWin> output_device_listener_; base::test::TaskEnvironment task_environment_;
base::SystemMonitor system_monitor_;
base::SimpleTestTickClock tick_clock_; base::SimpleTestTickClock tick_clock_;
std::unique_ptr<AudioDeviceListenerWin> output_device_listener_;
DISALLOW_COPY_AND_ASSIGN(AudioDeviceListenerWinTest); DISALLOW_COPY_AND_ASSIGN(AudioDeviceListenerWinTest);
}; };
...@@ -77,14 +90,22 @@ TEST_F(AudioDeviceListenerWinTest, OutputDeviceChange) { ...@@ -77,14 +90,22 @@ TEST_F(AudioDeviceListenerWinTest, OutputDeviceChange) {
ABORT_AUDIO_TEST_IF_NOT(CoreAudioUtil::IsSupported()); ABORT_AUDIO_TEST_IF_NOT(CoreAudioUtil::IsSupported());
EXPECT_CALL(*this, OnDeviceChange()).Times(1); EXPECT_CALL(*this, OnDeviceChange()).Times(1);
EXPECT_CALL(*this, OnDevicesChanged(base::SystemMonitor::DEVTYPE_AUDIO))
.Times(1);
ASSERT_TRUE(SimulateDefaultOutputDeviceChange(kFirstTestDevice)); ASSERT_TRUE(SimulateDefaultOutputDeviceChange(kFirstTestDevice));
testing::Mock::VerifyAndClear(this); testing::Mock::VerifyAndClear(this);
AdvanceLastDeviceChangeTime(); AdvanceLastDeviceChangeTime();
EXPECT_CALL(*this, OnDeviceChange()).Times(1); EXPECT_CALL(*this, OnDeviceChange()).Times(1);
EXPECT_CALL(*this, OnDevicesChanged(base::SystemMonitor::DEVTYPE_AUDIO))
.Times(1);
ASSERT_TRUE(SimulateDefaultOutputDeviceChange(kSecondTestDevice)); ASSERT_TRUE(SimulateDefaultOutputDeviceChange(kSecondTestDevice));
// The second device event should be ignored since it occurs too soon. // Since it occurs too soon, the second device event shouldn't call
// OnDeviceChange(), but it should notify OnDevicesChanged().
EXPECT_CALL(*this, OnDeviceChange()).Times(0);
EXPECT_CALL(*this, OnDevicesChanged(base::SystemMonitor::DEVTYPE_AUDIO))
.Times(1);
ASSERT_TRUE(SimulateDefaultOutputDeviceChange(kSecondTestDevice)); ASSERT_TRUE(SimulateDefaultOutputDeviceChange(kSecondTestDevice));
} }
...@@ -94,16 +115,22 @@ TEST_F(AudioDeviceListenerWinTest, NullOutputDeviceChange) { ...@@ -94,16 +115,22 @@ TEST_F(AudioDeviceListenerWinTest, NullOutputDeviceChange) {
ABORT_AUDIO_TEST_IF_NOT(CoreAudioUtil::IsSupported()); ABORT_AUDIO_TEST_IF_NOT(CoreAudioUtil::IsSupported());
EXPECT_CALL(*this, OnDeviceChange()).Times(1); EXPECT_CALL(*this, OnDeviceChange()).Times(1);
EXPECT_CALL(*this, OnDevicesChanged(base::SystemMonitor::DEVTYPE_AUDIO))
.Times(1);
ASSERT_TRUE(SimulateNullDefaultOutputDeviceChange()); ASSERT_TRUE(SimulateNullDefaultOutputDeviceChange());
testing::Mock::VerifyAndClear(this); testing::Mock::VerifyAndClear(this);
AdvanceLastDeviceChangeTime(); AdvanceLastDeviceChangeTime();
EXPECT_CALL(*this, OnDeviceChange()).Times(1); EXPECT_CALL(*this, OnDeviceChange()).Times(1);
EXPECT_CALL(*this, OnDevicesChanged(base::SystemMonitor::DEVTYPE_AUDIO))
.Times(1);
ASSERT_TRUE(SimulateDefaultOutputDeviceChange(kFirstTestDevice)); ASSERT_TRUE(SimulateDefaultOutputDeviceChange(kFirstTestDevice));
// Since it occurs too soon, the second device event shouldn't call
// OnDeviceChange(), but it should notify OnDevicesChanged().
testing::Mock::VerifyAndClear(this); testing::Mock::VerifyAndClear(this);
AdvanceLastDeviceChangeTime(); EXPECT_CALL(*this, OnDevicesChanged(base::SystemMonitor::DEVTYPE_AUDIO))
EXPECT_CALL(*this, OnDeviceChange()).Times(1); .Times(1);
ASSERT_TRUE(SimulateNullDefaultOutputDeviceChange()); ASSERT_TRUE(SimulateNullDefaultOutputDeviceChange());
} }
......
...@@ -5,10 +5,12 @@ ...@@ -5,10 +5,12 @@
#include "media/audio/win/audio_low_latency_output_win.h" #include "media/audio/win/audio_low_latency_output_win.h"
#include <Functiondiscoverykeys_devpkey.h> #include <Functiondiscoverykeys_devpkey.h>
#include <audiopolicy.h>
#include <objbase.h> #include <objbase.h>
#include <climits> #include <climits>
#include "base/callback.h"
#include "base/command_line.h" #include "base/command_line.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/metrics/histogram.h" #include "base/metrics/histogram.h"
...@@ -20,9 +22,11 @@ ...@@ -20,9 +22,11 @@
#include "base/win/scoped_propvariant.h" #include "base/win/scoped_propvariant.h"
#include "media/audio/audio_device_description.h" #include "media/audio/audio_device_description.h"
#include "media/audio/win/audio_manager_win.h" #include "media/audio/win/audio_manager_win.h"
#include "media/audio/win/audio_session_event_listener_win.h"
#include "media/audio/win/avrt_wrapper_win.h" #include "media/audio/win/avrt_wrapper_win.h"
#include "media/audio/win/core_audio_util_win.h" #include "media/audio/win/core_audio_util_win.h"
#include "media/base/audio_sample_types.h" #include "media/base/audio_sample_types.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/limits.h" #include "media/base/limits.h"
#include "media/base/media_switches.h" #include "media/base/media_switches.h"
...@@ -32,7 +36,8 @@ using base::win::ScopedCoMem; ...@@ -32,7 +36,8 @@ using base::win::ScopedCoMem;
namespace media { namespace media {
// static // static
AUDCLNT_SHAREMODE WASAPIAudioOutputStream::GetShareMode() { AUDCLNT_SHAREMODE
WASAPIAudioOutputStream::GetShareMode() {
const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess(); const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
if (cmd_line->HasSwitch(switches::kEnableExclusiveAudio)) if (cmd_line->HasSwitch(switches::kEnableExclusiveAudio))
return AUDCLNT_SHAREMODE_EXCLUSIVE; return AUDCLNT_SHAREMODE_EXCLUSIVE;
...@@ -245,6 +250,11 @@ bool WASAPIAudioOutputStream::Open() { ...@@ -245,6 +250,11 @@ bool WASAPIAudioOutputStream::Open() {
return false; return false;
} }
session_listener_ = std::make_unique<AudioSessionEventListener>(
audio_client_.Get(), BindToCurrentLoop(base::BindOnce(
&WASAPIAudioOutputStream::OnDeviceChanged,
weak_factory_.GetWeakPtr())));
opened_ = true; opened_ = true;
return true; return true;
} }
...@@ -260,6 +270,14 @@ void WASAPIAudioOutputStream::Start(AudioSourceCallback* callback) { ...@@ -260,6 +270,14 @@ void WASAPIAudioOutputStream::Start(AudioSourceCallback* callback) {
return; return;
} }
// Since a device change may occur between Open() and Start() we need to
// signal the change once we have a |callback|. It's okay if this ends up
// being delivered multiple times.
if (device_changed_) {
callback->OnError(AudioSourceCallback::ErrorType::kDeviceChange);
return;
}
// Ensure that the endpoint buffer is prepared with silence. Also serves as // Ensure that the endpoint buffer is prepared with silence. Also serves as
// a sanity check for the IAudioClient and IAudioRenderClient which may have // a sanity check for the IAudioClient and IAudioRenderClient which may have
// been invalidated by Windows since the last Stop() call. // been invalidated by Windows since the last Stop() call.
...@@ -355,6 +373,8 @@ void WASAPIAudioOutputStream::Close() { ...@@ -355,6 +373,8 @@ void WASAPIAudioOutputStream::Close() {
DVLOG(1) << "WASAPIAudioOutputStream::Close()"; DVLOG(1) << "WASAPIAudioOutputStream::Close()";
DCHECK_EQ(GetCurrentThreadId(), creating_thread_id_); DCHECK_EQ(GetCurrentThreadId(), creating_thread_id_);
session_listener_.reset();
// It is valid to call Close() before calling open or Start(). // It is valid to call Close() before calling open or Start().
// It is also valid to call Close() after Start() has been called. // It is also valid to call Close() after Start() has been called.
Stop(); Stop();
...@@ -734,4 +754,10 @@ void WASAPIAudioOutputStream::ReportAndResetStats() { ...@@ -734,4 +754,10 @@ void WASAPIAudioOutputStream::ReportAndResetStats() {
largest_glitch_ = base::TimeDelta(); largest_glitch_ = base::TimeDelta();
} }
void WASAPIAudioOutputStream::OnDeviceChanged() {
device_changed_ = true;
if (source_)
source_->OnError(AudioSourceCallback::ErrorType::kDeviceChange);
}
} // namespace media } // namespace media
...@@ -104,6 +104,7 @@ ...@@ -104,6 +104,7 @@
#include "base/compiler_specific.h" #include "base/compiler_specific.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/threading/platform_thread.h" #include "base/threading/platform_thread.h"
#include "base/threading/simple_thread.h" #include "base/threading/simple_thread.h"
#include "base/win/scoped_co_mem.h" #include "base/win/scoped_co_mem.h"
...@@ -116,6 +117,7 @@ ...@@ -116,6 +117,7 @@
namespace media { namespace media {
class AudioManagerWin; class AudioManagerWin;
class AudioSessionEventListener;
// AudioOutputStream implementation using Windows Core Audio APIs. // AudioOutputStream implementation using Windows Core Audio APIs.
class MEDIA_EXPORT WASAPIAudioOutputStream : class MEDIA_EXPORT WASAPIAudioOutputStream :
...@@ -174,6 +176,9 @@ class MEDIA_EXPORT WASAPIAudioOutputStream : ...@@ -174,6 +176,9 @@ class MEDIA_EXPORT WASAPIAudioOutputStream :
// Reports audio stream glitch stats and resets them to their initial values. // Reports audio stream glitch stats and resets them to their initial values.
void ReportAndResetStats(); void ReportAndResetStats();
// Called by AudioSessionEventListener() when a device change occurs.
void OnDeviceChanged();
// Contains the thread ID of the creating thread. // Contains the thread ID of the creating thread.
const base::PlatformThreadId creating_thread_id_; const base::PlatformThreadId creating_thread_id_;
...@@ -262,6 +267,14 @@ class MEDIA_EXPORT WASAPIAudioOutputStream : ...@@ -262,6 +267,14 @@ class MEDIA_EXPORT WASAPIAudioOutputStream :
Microsoft::WRL::ComPtr<IAudioClock> audio_clock_; Microsoft::WRL::ComPtr<IAudioClock> audio_clock_;
bool device_changed_ = false;
std::unique_ptr<AudioSessionEventListener> session_listener_;
// Since AudioSessionEventListener needs to posts tasks back to the audio
// thread, it's possible to end up in a state where that task would execute
// after destruction of this class -- so use a WeakPtr to cancel safely.
base::WeakPtrFactory<WASAPIAudioOutputStream> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(WASAPIAudioOutputStream); DISALLOW_COPY_AND_ASSIGN(WASAPIAudioOutputStream);
}; };
......
...@@ -56,15 +56,15 @@ DEFINE_GUID(AM_KSCATEGORY_AUDIO, ...@@ -56,15 +56,15 @@ DEFINE_GUID(AM_KSCATEGORY_AUDIO,
namespace media { namespace media {
// Maximum number of output streams that can be open simultaneously. // Maximum number of output streams that can be open simultaneously.
static const int kMaxOutputStreams = 50; constexpr int kMaxOutputStreams = 50;
// Up to 8 channels can be passed to the driver. This should work, given the // Up to 8 channels can be passed to the driver. This should work, given the
// right drivers, but graceful error handling is needed. // right drivers, but graceful error handling is needed.
static const int kWinMaxChannels = 8; constexpr int kWinMaxChannels = 8;
// Buffer size to use for input and output stream when a proper size can't be // Buffer size to use for input and output stream when a proper size can't be
// determined from the system // determined from the system
static const int kFallbackBufferSize = 2048; constexpr int kFallbackBufferSize = 2048;
static int NumberOfWaveOutBuffers() { static int NumberOfWaveOutBuffers() {
// Use the user provided buffer count if provided. // Use the user provided buffer count if provided.
...@@ -131,9 +131,10 @@ void AudioManagerWin::InitializeOnAudioThread() { ...@@ -131,9 +131,10 @@ void AudioManagerWin::InitializeOnAudioThread() {
DCHECK(GetTaskRunner()->BelongsToCurrentThread()); DCHECK(GetTaskRunner()->BelongsToCurrentThread());
// AudioDeviceListenerWin must be initialized on a COM thread. // AudioDeviceListenerWin must be initialized on a COM thread.
output_device_listener_.reset(new AudioDeviceListenerWin(BindToCurrentLoop( output_device_listener_ = std::make_unique<AudioDeviceListenerWin>(
base::Bind(&AudioManagerWin::NotifyAllOutputDeviceChangeListeners, BindToCurrentLoop(base::BindRepeating(
base::Unretained(this))))); &AudioManagerWin::NotifyAllOutputDeviceChangeListeners,
base::Unretained(this))));
} }
void AudioManagerWin::GetAudioDeviceNamesImpl(bool input, void AudioManagerWin::GetAudioDeviceNamesImpl(bool input,
...@@ -205,7 +206,7 @@ AudioOutputStream* AudioManagerWin::MakeLinearOutputStream( ...@@ -205,7 +206,7 @@ AudioOutputStream* AudioManagerWin::MakeLinearOutputStream(
const LogCallback& log_callback) { const LogCallback& log_callback) {
DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format()); DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
if (params.channels() > kWinMaxChannels) if (params.channels() > kWinMaxChannels)
return NULL; return nullptr;
return new PCMWaveOutAudioOutputStream(this, params, NumberOfWaveOutBuffers(), return new PCMWaveOutAudioOutputStream(this, params, NumberOfWaveOutBuffers(),
WAVE_MAPPER); WAVE_MAPPER);
...@@ -222,7 +223,13 @@ AudioOutputStream* AudioManagerWin::MakeLowLatencyOutputStream( ...@@ -222,7 +223,13 @@ AudioOutputStream* AudioManagerWin::MakeLowLatencyOutputStream(
const LogCallback& log_callback) { const LogCallback& log_callback) {
DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format()); DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
if (params.channels() > kWinMaxChannels) if (params.channels() > kWinMaxChannels)
return NULL; return nullptr;
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kForceWaveAudio)) {
DLOG(WARNING) << "Forcing usage of Windows WaveXxx APIs";
return nullptr;
}
// Pass an empty string to indicate that we want the default device // Pass an empty string to indicate that we want the default device
// since we consistently only check for an empty string in // since we consistently only check for an empty string in
......
// Copyright 2019 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_session_event_listener_win.h"
namespace media {
AudioSessionEventListener::AudioSessionEventListener(
IAudioClient* client,
base::OnceClosure device_change_cb)
: device_change_cb_(std::move(device_change_cb)) {
DCHECK(device_change_cb_);
HRESULT hr = client->GetService(IID_PPV_ARGS(&audio_session_control_));
if (FAILED(hr)) {
DLOG(ERROR) << "Failed to get IAudioSessionControl service.";
return;
}
audio_session_control_->RegisterAudioSessionNotification(this);
}
AudioSessionEventListener::~AudioSessionEventListener() {
if (!audio_session_control_)
return;
HRESULT hr = audio_session_control_->UnregisterAudioSessionNotification(this);
DLOG_IF(ERROR, FAILED(hr))
<< "UnregisterAudioSessionNotification() failed: " << std::hex << hr;
}
STDMETHODIMP_(ULONG) AudioSessionEventListener::AddRef() {
return 1; // Class is owned in Chromium code and should have no outside refs.
}
STDMETHODIMP_(ULONG) AudioSessionEventListener::Release() {
return 1; // Class is owned in Chromium code and should have no outside refs.
}
STDMETHODIMP AudioSessionEventListener::QueryInterface(REFIID iid,
void** object) {
if (iid == IID_IUnknown || iid == __uuidof(IAudioSessionEvents)) {
*object = static_cast<IAudioSessionEvents*>(this);
return S_OK;
}
*object = nullptr;
return E_NOINTERFACE;
}
STDMETHODIMP AudioSessionEventListener::OnChannelVolumeChanged(
DWORD channel_count,
float new_channel_volume_array[],
DWORD changed_channel,
LPCGUID event_context) {
return S_OK;
}
STDMETHODIMP
AudioSessionEventListener::OnDisplayNameChanged(LPCWSTR new_display_name,
LPCGUID event_context) {
return S_OK;
}
STDMETHODIMP AudioSessionEventListener::OnGroupingParamChanged(
LPCGUID new_grouping_param,
LPCGUID event_context) {
return S_OK;
}
STDMETHODIMP AudioSessionEventListener::OnIconPathChanged(
LPCWSTR new_icon_path,
LPCGUID event_context) {
return S_OK;
}
STDMETHODIMP AudioSessionEventListener::OnSessionDisconnected(
AudioSessionDisconnectReason disconnect_reason) {
DVLOG(1) << __func__ << ": " << disconnect_reason;
if (device_change_cb_)
std::move(device_change_cb_).Run();
return S_OK;
}
STDMETHODIMP AudioSessionEventListener::OnSimpleVolumeChanged(
float new_volume,
BOOL new_mute,
LPCGUID event_context) {
return S_OK;
}
STDMETHODIMP AudioSessionEventListener::OnStateChanged(
AudioSessionState new_state) {
return S_OK;
}
} // namespace media
// Copyright 2019 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.
#ifndef MEDIA_AUDIO_WIN_AUDIO_SESSION_EVENT_LISTENER_WIN_H_
#define MEDIA_AUDIO_WIN_AUDIO_SESSION_EVENT_LISTENER_WIN_H_
#include <audiopolicy.h>
#include <wrl/client.h>
#include "base/callback.h"
#include "media/base/media_export.h"
namespace media {
class MEDIA_EXPORT AudioSessionEventListener : public IAudioSessionEvents {
public:
// Calls RegisterAudioSessionNotification() on |client| and calls
// |device_change_cb| when OnSessionDisconnected() is called.
//
// Since the IAudioClient session is dead after the disconnection, we use a
// OnceCallback. The delivery of this notification is fatal to the |client|.
AudioSessionEventListener(IAudioClient* client,
base::OnceClosure device_change_cb);
virtual ~AudioSessionEventListener();
private:
friend class AudioSessionEventListenerTest;
STDMETHOD_(ULONG, AddRef)() override;
STDMETHOD_(ULONG, Release)() override;
STDMETHOD(QueryInterface)(REFIID iid, void** object) override;
// IAudioSessionEvents implementation.
STDMETHOD(OnChannelVolumeChanged)
(DWORD channel_count,
float new_channel_volume_array[],
DWORD changed_channel,
LPCGUID event_context) override;
STDMETHOD(OnDisplayNameChanged)
(LPCWSTR new_display_name, LPCGUID event_context) override;
STDMETHOD(OnGroupingParamChanged)
(LPCGUID new_grouping_param, LPCGUID event_context) override;
STDMETHOD(OnIconPathChanged)
(LPCWSTR new_icon_path, LPCGUID event_context) override;
STDMETHOD(OnSessionDisconnected)
(AudioSessionDisconnectReason disconnect_reason) override;
STDMETHOD(OnSimpleVolumeChanged)
(float new_volume, BOOL new_mute, LPCGUID event_context) override;
STDMETHOD(OnStateChanged)(AudioSessionState new_state) override;
base::OnceClosure device_change_cb_;
Microsoft::WRL::ComPtr<IAudioSessionControl> audio_session_control_;
};
} // namespace media
#endif // MEDIA_AUDIO_WIN_AUDIO_SESSION_EVENT_LISTENER_WIN_H_
// Copyright 2019 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_session_event_listener_win.h"
#include <memory>
#include "base/bind_helpers.h"
#include "base/win/scoped_com_initializer.h"
#include "media/audio/audio_unittest_util.h"
#include "media/audio/win/core_audio_util_win.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
static bool DevicesAvailable() {
return media::CoreAudioUtil::IsSupported() &&
media::CoreAudioUtil::NumberOfActiveDevices(eRender) > 0;
}
} // namespace
namespace media {
class AudioSessionEventListenerTest : public testing::Test {
public:
AudioSessionEventListenerTest() {
if (!DevicesAvailable())
return;
audio_client_ =
CoreAudioUtil::CreateClient(std::string(), eRender, eConsole);
CHECK(audio_client_);
// AudioClient must be initialized to succeed in getting the session.
WAVEFORMATEXTENSIBLE format;
EXPECT_TRUE(SUCCEEDED(
CoreAudioUtil::GetSharedModeMixFormat(audio_client_.Get(), &format)));
// Perform a shared-mode initialization without event-driven buffer
// handling.
uint32_t endpoint_buffer_size = 0;
HRESULT hr = CoreAudioUtil::SharedModeInitialize(
audio_client_.Get(), &format, nullptr, 0, &endpoint_buffer_size,
nullptr);
EXPECT_TRUE(SUCCEEDED(hr));
listener_ = std::make_unique<AudioSessionEventListener>(
audio_client_.Get(),
base::BindOnce(&AudioSessionEventListenerTest::OnSessionDisconnected,
base::Unretained(this)));
}
~AudioSessionEventListenerTest() override = default;
void SimulateSessionDisconnected() {
listener_->OnSessionDisconnected(DisconnectReasonDeviceRemoval);
}
MOCK_METHOD0(OnSessionDisconnected, void());
protected:
base::win::ScopedCOMInitializer com_init_;
Microsoft::WRL::ComPtr<IAudioClient> audio_client_;
std::unique_ptr<AudioSessionEventListener> listener_;
};
TEST_F(AudioSessionEventListenerTest, Works) {
ABORT_AUDIO_TEST_IF_NOT(DevicesAvailable());
EXPECT_CALL(*this, OnSessionDisconnected());
SimulateSessionDisconnected();
// Calling it again shouldn't crash, but also shouldn't make any more calls.
EXPECT_CALL(*this, OnSessionDisconnected()).Times(0);
SimulateSessionDisconnected();
}
} // namespace media
...@@ -436,9 +436,13 @@ void OutputController::LogAudioPowerLevel(const char* call_name) { ...@@ -436,9 +436,13 @@ void OutputController::LogAudioPowerLevel(const char* call_name) {
} }
void OutputController::OnError(ErrorType type) { void OutputController::OnError(ErrorType type) {
// TODO(dalecurtis): Handle type == ErrorType::kDeviceChange errors without if (type == ErrorType::kDeviceChange) {
// delay. task_runner_->PostTask(FROM_HERE,
// base::BindOnce(&OutputController::OnDeviceChange,
weak_this_for_stream_));
return;
}
// Handle error on the audio controller thread. We defer errors for one // Handle error on the audio controller thread. We defer errors for one
// second in case they are the result of a device change; delay chosen to // second in case they are the result of a device change; delay chosen to
// exceed duration of device changes which take a few hundred milliseconds. // exceed duration of device changes which take a few hundred milliseconds.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment