summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordalecurtis@google.com <dalecurtis@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2012-10-26 01:06:19 +0000
committerdalecurtis@google.com <dalecurtis@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2012-10-26 01:06:19 +0000
commit0b4125c58738e6fc1339a5ffffc4fb4e644d9e5a (patch)
tree34ba89c2298bb884f6b2c412b65f6fdd88d42409
parent89b4d959a338177544b7d32fc3a02022c605b602 (diff)
downloadchromium_src-0b4125c58738e6fc1339a5ffffc4fb4e644d9e5a.zip
chromium_src-0b4125c58738e6fc1339a5ffffc4fb4e644d9e5a.tar.gz
chromium_src-0b4125c58738e6fc1339a5ffffc4fb4e644d9e5a.tar.bz2
Handle audio device changes on Windows.
Uses the new AudioDeviceListener framework to notify of device changes. Handles only default device changes at the moment, e.g., not manually changing the sample rate, etc on a current default device. This all works well enough that I can connect / disconnect remote desktop sessions with and without audio and everything continues to play seamlessly and in sync! BUG=153056 TEST=Unplug... Plug... Unplug! Plug! Review URL: https://codereview.chromium.org/11233023 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@164236 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--content/renderer/media/audio_renderer_mixer_manager.h4
-rw-r--r--media/audio/audio_manager_base.cc74
-rw-r--r--media/audio/audio_manager_base.h5
-rw-r--r--media/audio/audio_parameters.cc22
-rw-r--r--media/audio/audio_parameters.h19
-rw-r--r--media/audio/audio_parameters_unittest.cc5
-rw-r--r--media/audio/win/audio_device_listener_win.cc145
-rw-r--r--media/audio/win/audio_device_listener_win.h62
-rw-r--r--media/audio/win/audio_device_listener_win_unittest.cc93
-rw-r--r--media/audio/win/audio_low_latency_output_win.cc15
-rw-r--r--media/audio/win/audio_low_latency_output_win.h5
-rw-r--r--media/audio/win/audio_manager_win.cc25
-rw-r--r--media/audio/win/audio_manager_win.h17
-rw-r--r--media/media.gyp3
14 files changed, 407 insertions, 87 deletions
diff --git a/content/renderer/media/audio_renderer_mixer_manager.h b/content/renderer/media/audio_renderer_mixer_manager.h
index 37f432b4..dba3ba8 100644
--- a/content/renderer/media/audio_renderer_mixer_manager.h
+++ b/content/renderer/media/audio_renderer_mixer_manager.h
@@ -61,8 +61,8 @@ class CONTENT_EXPORT AudioRendererMixerManager {
media::AudioRendererMixer* mixer;
int ref_count;
};
- typedef std::map<media::AudioParameters, AudioRendererMixerReference,
- media::AudioParameters::Compare> AudioRendererMixerMap;
+ typedef std::map<media::AudioParameters,
+ AudioRendererMixerReference> AudioRendererMixerMap;
AudioRendererMixerMap mixers_;
base::Lock mixers_lock_;
diff --git a/media/audio/audio_manager_base.cc b/media/audio/audio_manager_base.cc
index 1beee2f..6b1fa92 100644
--- a/media/audio/audio_manager_base.cc
+++ b/media/audio/audio_manager_base.cc
@@ -160,41 +160,53 @@ AudioOutputStream* AudioManagerBase::MakeAudioOutputStreamProxy(
#else
DCHECK(message_loop_->BelongsToCurrentThread());
- AudioOutputDispatchersMap::iterator it = output_dispatchers_.find(params);
+ bool use_audio_output_resampler =
+ !CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kDisableAudioOutputResampler) &&
+ params.format() == AudioParameters::AUDIO_PCM_LOW_LATENCY;
+
+ // If we're not using AudioOutputResampler our output parameters are the same
+ // as our input parameters.
+ AudioParameters output_params = params;
+ if (use_audio_output_resampler) {
+ output_params = GetPreferredLowLatencyOutputStreamParameters(params);
+
+ // Ensure we only pass on valid output parameters.
+ if (!output_params.IsValid()) {
+ // We've received invalid audio output parameters, so switch to a mock
+ // output device based on the input parameters. This may happen if the OS
+ // provided us junk values for the hardware configuration.
+ LOG(ERROR) << "Invalid audio output parameters received; using fake "
+ << "audio path. Channels: " << output_params.channels() << ", "
+ << "Sample Rate: " << output_params.sample_rate() << ", "
+ << "Bits Per Sample: " << output_params.bits_per_sample()
+ << ", Frames Per Buffer: "
+ << output_params.frames_per_buffer();
+
+ // Tell the AudioManager to create a fake output device.
+ output_params = AudioParameters(
+ AudioParameters::AUDIO_FAKE, params.channel_layout(),
+ params.sample_rate(), params.bits_per_sample(),
+ params.frames_per_buffer());
+ }
+ }
+
+ std::pair<AudioParameters, AudioParameters> dispatcher_key =
+ std::make_pair(params, output_params);
+ AudioOutputDispatchersMap::iterator it =
+ output_dispatchers_.find(dispatcher_key);
if (it != output_dispatchers_.end())
return new AudioOutputProxy(it->second);
base::TimeDelta close_delay =
base::TimeDelta::FromSeconds(kStreamCloseDelaySeconds);
- const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
- if (!cmd_line->HasSwitch(switches::kDisableAudioOutputResampler) &&
- params.format() == AudioParameters::AUDIO_PCM_LOW_LATENCY) {
- AudioParameters output_params =
- GetPreferredLowLatencyOutputStreamParameters(params);
-
- // Ensure we only pass on valid output parameters.
- if (output_params.IsValid()) {
- scoped_refptr<AudioOutputDispatcher> dispatcher =
- new AudioOutputResampler(this, params, output_params, close_delay);
- output_dispatchers_[params] = dispatcher;
- return new AudioOutputProxy(dispatcher);
- }
-
- // We've received invalid audio output parameters, so switch to a mock
- // output device based on the input parameters. This may happen if the OS
- // provided us junk values for the hardware configuration.
- LOG(ERROR) << "Invalid audio output parameters received; using fake audio "
- << "path. Channels: " << output_params.channels() << ", "
- << "Sample Rate: " << output_params.sample_rate() << ", "
- << "Bits Per Sample: " << output_params.bits_per_sample()
- << ", Frames Per Buffer: " << output_params.frames_per_buffer();
-
- // Passing AUDIO_FAKE tells the AudioManager to create a fake output device.
- output_params = AudioParameters(
- AudioParameters::AUDIO_FAKE, params.channel_layout(),
- params.sample_rate(), params.bits_per_sample(),
- params.frames_per_buffer());
+ if (use_audio_output_resampler &&
+ output_params.format() != AudioParameters::AUDIO_FAKE) {
+ scoped_refptr<AudioOutputDispatcher> dispatcher =
+ new AudioOutputResampler(this, params, output_params, close_delay);
+ output_dispatchers_[dispatcher_key] = dispatcher;
+ return new AudioOutputProxy(dispatcher);
}
#if defined(ENABLE_AUDIO_MIXER)
@@ -204,14 +216,14 @@ AudioOutputStream* AudioManagerBase::MakeAudioOutputStreamProxy(
if (cmd_line->HasSwitch(switches::kEnableAudioMixer)) {
scoped_refptr<AudioOutputDispatcher> dispatcher =
new AudioOutputMixer(this, params, close_delay);
- output_dispatchers_[params] = dispatcher;
+ output_dispatchers_[dispatcher_key] = dispatcher;
return new AudioOutputProxy(dispatcher);
}
#endif
scoped_refptr<AudioOutputDispatcher> dispatcher =
new AudioOutputDispatcherImpl(this, params, close_delay);
- output_dispatchers_[params] = dispatcher;
+ output_dispatchers_[dispatcher_key] = dispatcher;
return new AudioOutputProxy(dispatcher);
#endif // defined(OS_IOS)
}
diff --git a/media/audio/audio_manager_base.h b/media/audio/audio_manager_base.h
index b14452f..7372c61 100644
--- a/media/audio/audio_manager_base.h
+++ b/media/audio/audio_manager_base.h
@@ -7,6 +7,7 @@
#include <map>
#include <string>
+#include <utility>
#include "base/atomic_ref_count.h"
#include "base/compiler_specific.h"
@@ -104,8 +105,8 @@ class MEDIA_EXPORT AudioManagerBase : public AudioManager {
// TODO(dalecurtis): This must change to map both input and output parameters
// to a single dispatcher, otherwise on a device state change we'll just get
// the exact same invalid dispatcher.
- typedef std::map<AudioParameters, scoped_refptr<AudioOutputDispatcher>,
- AudioParameters::Compare>
+ typedef std::map<std::pair<AudioParameters, AudioParameters>,
+ scoped_refptr<AudioOutputDispatcher> >
AudioOutputDispatchersMap;
// Shuts down the audio thread and releases all the audio output dispatchers
diff --git a/media/audio/audio_parameters.cc b/media/audio/audio_parameters.cc
index eaa0607..0d9263ff5 100644
--- a/media/audio/audio_parameters.cc
+++ b/media/audio/audio_parameters.cc
@@ -66,26 +66,4 @@ int AudioParameters::GetBytesPerFrame() const {
return channels_ * bits_per_sample_ / 8;
}
-bool AudioParameters::Compare::operator()(
- const AudioParameters& a,
- const AudioParameters& b) const {
- if (a.format_ < b.format_)
- return true;
- if (a.format_ > b.format_)
- return false;
- if (a.channels_ < b.channels_)
- return true;
- if (a.channels_ > b.channels_)
- return false;
- if (a.sample_rate_ < b.sample_rate_)
- return true;
- if (a.sample_rate_ > b.sample_rate_)
- return false;
- if (a.bits_per_sample_ < b.bits_per_sample_)
- return true;
- if (a.bits_per_sample_ > b.bits_per_sample_)
- return false;
- return a.frames_per_buffer_ < b.frames_per_buffer_;
-}
-
} // namespace media
diff --git a/media/audio/audio_parameters.h b/media/audio/audio_parameters.h
index be904f4..05e368e 100644
--- a/media/audio/audio_parameters.h
+++ b/media/audio/audio_parameters.h
@@ -26,12 +26,6 @@ struct MEDIA_EXPORT AudioInputBuffer {
class MEDIA_EXPORT AudioParameters {
public:
- // Compare is useful when AudioParameters is used as a key in std::map.
- class MEDIA_EXPORT Compare {
- public:
- bool operator()(const AudioParameters& a, const AudioParameters& b) const;
- };
-
enum Format {
AUDIO_PCM_LINEAR = 0, // PCM is 'raw' amplitude samples.
AUDIO_PCM_LOW_LATENCY, // Linear PCM, low latency requested.
@@ -85,6 +79,19 @@ class MEDIA_EXPORT AudioParameters {
// |channel_layout|.
};
+// Comparison is useful when AudioParameters is used with std structures.
+inline bool operator<(const AudioParameters& a, const AudioParameters& b) {
+ if (a.format() != b.format())
+ return a.format() < b.format();
+ if (a.channels() != b.channels())
+ return a.channels() < b.channels();
+ if (a.sample_rate() != b.sample_rate())
+ return a.sample_rate() < b.sample_rate();
+ if (a.bits_per_sample() != b.bits_per_sample())
+ return a.bits_per_sample() < b.bits_per_sample();
+ return a.frames_per_buffer() < b.frames_per_buffer();
+}
+
} // namespace media
#endif // MEDIA_AUDIO_AUDIO_PARAMETERS_H_
diff --git a/media/audio/audio_parameters_unittest.cc b/media/audio/audio_parameters_unittest.cc
index 1a1140b..fd42e14 100644
--- a/media/audio/audio_parameters_unittest.cc
+++ b/media/audio/audio_parameters_unittest.cc
@@ -154,15 +154,14 @@ TEST(AudioParameters, Compare) {
CHANNEL_LAYOUT_STEREO, 2000, 16, 200),
};
- AudioParameters::Compare target;
for (size_t i = 0; i < arraysize(values); ++i) {
for (size_t j = 0; j < arraysize(values); ++j) {
SCOPED_TRACE("i=" + base::IntToString(i) + " j=" + base::IntToString(j));
- EXPECT_EQ(i < j, target(values[i], values[j]));
+ EXPECT_EQ(i < j, values[i] < values[j]);
}
// Verify that a value is never less than itself.
- EXPECT_FALSE(target(values[i], values[i]));
+ EXPECT_FALSE(values[i] < values[i]);
}
}
diff --git a/media/audio/win/audio_device_listener_win.cc b/media/audio/win/audio_device_listener_win.cc
new file mode 100644
index 0000000..588852e
--- /dev/null
+++ b/media/audio/win/audio_device_listener_win.cc
@@ -0,0 +1,145 @@
+// 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/utf_string_conversions.h"
+#include "base/win/scoped_co_mem.h"
+#include "base/win/windows_version.h"
+#include "media/audio/audio_util.h"
+
+using base::win::ScopedCoMem;
+
+namespace media {
+
+// TODO(henrika): Move to CoreAudioUtil class.
+static ScopedComPtr<IMMDeviceEnumerator> CreateDeviceEnumerator() {
+ ScopedComPtr<IMMDeviceEnumerator> device_enumerator;
+ HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
+ NULL,
+ CLSCTX_INPROC_SERVER,
+ __uuidof(IMMDeviceEnumerator),
+ device_enumerator.ReceiveVoid());
+ DLOG_IF(ERROR, FAILED(hr)) << "CoCreateInstance(IMMDeviceEnumerator): "
+ << std::hex << hr;
+ return device_enumerator;
+}
+
+AudioDeviceListenerWin::AudioDeviceListenerWin(const base::Closure& listener_cb)
+ : listener_cb_(listener_cb) {
+ CHECK(media::IsWASAPISupported());
+
+ device_enumerator_ = CreateDeviceEnumerator();
+ if (!device_enumerator_)
+ return;
+
+ HRESULT hr = device_enumerator_->RegisterEndpointNotificationCallback(this);
+ if (FAILED(hr)) {
+ DLOG(ERROR) << "RegisterEndpointNotificationCallback failed: "
+ << std::hex << hr;
+ device_enumerator_ = NULL;
+ return;
+ }
+
+ ScopedComPtr<IMMDevice> endpoint_render_device;
+ hr = device_enumerator_->GetDefaultAudioEndpoint(
+ eRender, eConsole, endpoint_render_device.Receive());
+ // This will fail if there are no audio devices currently plugged in, so we
+ // still want to keep our endpoint registered.
+ if (FAILED(hr)) {
+ DVLOG(1) << "GetDefaultAudioEndpoint() failed. No devices? Error: "
+ << std::hex << hr;
+ return;
+ }
+
+ ScopedCoMem<WCHAR> render_device_id;
+ hr = endpoint_render_device->GetId(&render_device_id);
+ if (FAILED(hr)) {
+ DLOG(ERROR) << "GetId() failed: " << std::hex << hr;
+ return;
+ }
+
+ default_render_device_id_ = WideToUTF8(static_cast<WCHAR*>(render_device_id));
+ DVLOG(1) << "Default render device: " << default_render_device_id_;
+}
+
+AudioDeviceListenerWin::~AudioDeviceListenerWin() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (device_enumerator_) {
+ HRESULT hr =
+ device_enumerator_->UnregisterEndpointNotificationCallback(this);
+ DLOG_IF(ERROR, FAILED(hr)) << "UnregisterEndpointNotificationCallback() "
+ << "failed: " << std::hex << hr;
+ }
+}
+
+STDMETHODIMP_(ULONG) AudioDeviceListenerWin::AddRef() {
+ return 1;
+}
+
+STDMETHODIMP_(ULONG) AudioDeviceListenerWin::Release() {
+ return 1;
+}
+
+STDMETHODIMP AudioDeviceListenerWin::QueryInterface(REFIID iid, void** object) {
+ if (iid == IID_IUnknown || iid == __uuidof(IMMNotificationClient)) {
+ *object = static_cast<IMMNotificationClient*>(this);
+ return S_OK;
+ }
+
+ *object = NULL;
+ return E_NOINTERFACE;
+}
+
+STDMETHODIMP AudioDeviceListenerWin::OnPropertyValueChanged(
+ LPCWSTR device_id, const PROPERTYKEY key) {
+ // TODO(dalecurtis): We need to handle changes for the current default device
+ // here. It's tricky because this method may be called many (20+) times for
+ // a single change like sample rate. http://crbug.com/153056
+ return S_OK;
+}
+
+STDMETHODIMP AudioDeviceListenerWin::OnDeviceAdded(LPCWSTR device_id) {
+ // We don't care when devices are added.
+ return S_OK;
+}
+
+STDMETHODIMP AudioDeviceListenerWin::OnDeviceRemoved(LPCWSTR device_id) {
+ // We don't care when devices are removed.
+ return S_OK;
+}
+
+STDMETHODIMP AudioDeviceListenerWin::OnDeviceStateChanged(LPCWSTR device_id,
+ DWORD new_state) {
+ return S_OK;
+}
+
+STDMETHODIMP AudioDeviceListenerWin::OnDefaultDeviceChanged(
+ EDataFlow flow, ERole role, LPCWSTR new_default_device_id) {
+ // Only listen for output device changes right now...
+ if (flow != eConsole && role != eRender)
+ 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 = WideToUTF8(new_default_device_id);
+
+ // Only fire a state change event if the device has actually changed.
+ // TODO(dalecurtis): This still seems to fire an extra event on my machine for
+ // an unplug event (probably others too); e.g., we get two transitions to a
+ // new default device id.
+ if (new_device_id.compare(default_render_device_id_) == 0)
+ return S_OK;
+
+ default_render_device_id_ = new_device_id;
+ listener_cb_.Run();
+
+ return S_OK;
+}
+
+} // namespace media
diff --git a/media/audio/win/audio_device_listener_win.h b/media/audio/win/audio_device_listener_win.h
new file mode 100644
index 0000000..d2c38b3
--- /dev/null
+++ b/media/audio/win/audio_device_listener_win.h
@@ -0,0 +1,62 @@
+// 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.
+
+#ifndef MEDIA_AUDIO_WIN_AUDIO_DEVICE_LISTENER_WIN_H_
+#define MEDIA_AUDIO_WIN_AUDIO_DEVICE_LISTENER_WIN_H_
+
+#include <MMDeviceAPI.h>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/threading/thread_checker.h"
+#include "base/win/scoped_comptr.h"
+#include "media/base/media_export.h"
+
+using base::win::ScopedComPtr;
+
+namespace media {
+
+// IMMNotificationClient implementation for listening for default device changes
+// and forwarding to AudioManagerWin so it can notify downstream clients. Only
+// output (eRender) device changes are supported currently. WASAPI support is
+// required to construct this object. Must be constructed and destructed on a
+// single COM initialized thread.
+// TODO(henrika): Refactor based on upcoming CoreAudioUtil class for windows.
+// TODO(dalecurtis, henrika): Support input device changes.
+class MEDIA_EXPORT AudioDeviceListenerWin : public IMMNotificationClient {
+ public:
+ // The listener callback will be called from a system level multimedia thread,
+ // thus the callee must be thread safe. |listener| is a permanent callback
+ // and must outlive AudioDeviceListenerWin.
+ explicit AudioDeviceListenerWin(const base::Closure& listener_cb);
+ virtual ~AudioDeviceListenerWin();
+
+ private:
+ friend class AudioDeviceListenerWinTest;
+
+ // IMMNotificationClient implementation.
+ STDMETHOD_(ULONG, AddRef)();
+ STDMETHOD_(ULONG, Release)();
+ STDMETHOD(QueryInterface)(REFIID iid, void** object);
+ STDMETHOD(OnPropertyValueChanged)(LPCWSTR device_id, const PROPERTYKEY key);
+ STDMETHOD(OnDeviceAdded)(LPCWSTR device_id);
+ STDMETHOD(OnDeviceRemoved)(LPCWSTR device_id);
+ STDMETHOD(OnDeviceStateChanged)(LPCWSTR device_id, DWORD new_state);
+ STDMETHOD(OnDefaultDeviceChanged)(EDataFlow flow, ERole role,
+ LPCWSTR new_default_device_id);
+
+ base::Closure listener_cb_;
+ ScopedComPtr<IMMDeviceEnumerator> device_enumerator_;
+ std::string default_render_device_id_;
+
+ // AudioDeviceListenerWin must be constructed and destructed on one thread.
+ base::ThreadChecker thread_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioDeviceListenerWin);
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_WIN_AUDIO_DEVICE_LISTENER_WIN_H_
diff --git a/media/audio/win/audio_device_listener_win_unittest.cc b/media/audio/win/audio_device_listener_win_unittest.cc
new file mode 100644
index 0000000..c717674
--- /dev/null
+++ b/media/audio/win/audio_device_listener_win_unittest.cc
@@ -0,0 +1,93 @@
+// 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 <string>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/win/scoped_com_initializer.h"
+#include "base/utf_string_conversions.h"
+#include "media/audio/audio_manager.h"
+#include "media/audio/win/audio_device_listener_win.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::win::ScopedCOMInitializer;
+
+namespace media {
+
+static const char* kNoDevice = "";
+static const char* kFirstTestDevice = "test_device_0";
+static const char* kSecondTestDevice = "test_device_1";
+
+class AudioDeviceListenerWinTest : public testing::Test {
+ public:
+ AudioDeviceListenerWinTest()
+ : com_init_(ScopedCOMInitializer::kMTA) {
+ }
+
+ virtual void SetUp() {
+ output_device_listener_.reset(new AudioDeviceListenerWin(base::Bind(
+ &AudioDeviceListenerWinTest::OnDeviceChange, base::Unretained(this))));
+ }
+
+ // Simulate a device change where no output devices are available.
+ bool SimulateNullDefaultOutputDeviceChange() {
+ return output_device_listener_->OnDefaultDeviceChanged(
+ static_cast<EDataFlow>(eConsole), static_cast<ERole>(eRender),
+ NULL) == S_OK;
+ }
+
+ bool SimulateDefaultOutputDeviceChange(const char* new_device_id) {
+ return output_device_listener_->OnDefaultDeviceChanged(
+ static_cast<EDataFlow>(eConsole), static_cast<ERole>(eRender),
+ ASCIIToWide(new_device_id).c_str()) == S_OK;
+ }
+
+ void SetOutputDeviceId(std::string new_device_id) {
+ output_device_listener_->default_render_device_id_ = new_device_id;
+ }
+
+ MOCK_METHOD0(OnDeviceChange, void());
+
+ private:
+ ScopedCOMInitializer com_init_;
+ scoped_ptr<AudioDeviceListenerWin> output_device_listener_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioDeviceListenerWinTest);
+};
+
+// Simulate a device change events and ensure we get the right callbacks.
+TEST_F(AudioDeviceListenerWinTest, OutputDeviceChange) {
+ SetOutputDeviceId(kNoDevice);
+ EXPECT_CALL(*this, OnDeviceChange()).Times(1);
+ ASSERT_TRUE(SimulateDefaultOutputDeviceChange(kFirstTestDevice));
+
+ testing::Mock::VerifyAndClear(this);
+ EXPECT_CALL(*this, OnDeviceChange()).Times(1);
+ ASSERT_TRUE(SimulateDefaultOutputDeviceChange(kSecondTestDevice));
+
+ // The second device event should be ignored since the device id has not
+ // changed.
+ ASSERT_TRUE(SimulateDefaultOutputDeviceChange(kSecondTestDevice));
+}
+
+// Ensure that null output device changes don't crash. Simulates the situation
+// where we have no output devices.
+TEST_F(AudioDeviceListenerWinTest, NullOutputDeviceChange) {
+ SetOutputDeviceId(kNoDevice);
+ EXPECT_CALL(*this, OnDeviceChange()).Times(0);
+ ASSERT_TRUE(SimulateNullDefaultOutputDeviceChange());
+
+ testing::Mock::VerifyAndClear(this);
+ EXPECT_CALL(*this, OnDeviceChange()).Times(1);
+ ASSERT_TRUE(SimulateDefaultOutputDeviceChange(kFirstTestDevice));
+
+ testing::Mock::VerifyAndClear(this);
+ EXPECT_CALL(*this, OnDeviceChange()).Times(1);
+ ASSERT_TRUE(SimulateNullDefaultOutputDeviceChange());
+}
+
+} // namespace media
diff --git a/media/audio/win/audio_low_latency_output_win.cc b/media/audio/win/audio_low_latency_output_win.cc
index 966f077..c353cb8 100644
--- a/media/audio/win/audio_low_latency_output_win.cc
+++ b/media/audio/win/audio_low_latency_output_win.cc
@@ -292,13 +292,8 @@ bool WASAPIAudioOutputStream::Open() {
return false;
}
- // Register this client as an IMMNotificationClient implementation.
- // Only OnDefaultDeviceChanged() and OnDeviceStateChanged() and are
- // non-trivial.
- hr = device_enumerator_->RegisterEndpointNotificationCallback(this);
-
opened_ = true;
- return SUCCEEDED(hr);
+ return true;
}
void WASAPIAudioOutputStream::Start(AudioSourceCallback* callback) {
@@ -411,14 +406,6 @@ void WASAPIAudioOutputStream::Close() {
// It is also valid to call Close() after Start() has been called.
Stop();
- if (opened_ && device_enumerator_) {
- // De-register the IMMNotificationClient callback interface.
- HRESULT hr = device_enumerator_->UnregisterEndpointNotificationCallback(
- this);
- DLOG_IF(ERROR, FAILED(hr)) << "Failed to disable device notifications: "
- << std::hex << hr;
- }
-
// Inform the audio manager that we have been closed. This will cause our
// destruction.
manager_->ReleaseOutputStream(this);
diff --git a/media/audio/win/audio_low_latency_output_win.h b/media/audio/win/audio_low_latency_output_win.h
index b973831..f48daac 100644
--- a/media/audio/win/audio_low_latency_output_win.h
+++ b/media/audio/win/audio_low_latency_output_win.h
@@ -158,8 +158,9 @@ namespace media {
class AudioManagerWin;
// AudioOutputStream implementation using Windows Core Audio APIs.
-// The IMMNotificationClient interface enables device event notifications
-// related to changes in the status of an audio endpoint device.
+// TODO(henrika): Remove IMMNotificationClient implementation now that we have
+// AudioDeviceListenerWin; currently just disabled since extraction is extremely
+// advanced.
class MEDIA_EXPORT WASAPIAudioOutputStream
: public IMMNotificationClient,
public AudioOutputStream,
diff --git a/media/audio/win/audio_manager_win.cc b/media/audio/win/audio_manager_win.cc
index 4449d150d1..aa324bc 100644
--- a/media/audio/win/audio_manager_win.cc
+++ b/media/audio/win/audio_manager_win.cc
@@ -10,15 +10,18 @@
#include <mmsystem.h>
#include <setupapi.h>
-#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/file_path.h"
#include "base/memory/scoped_ptr.h"
+#include "base/message_loop.h"
#include "base/path_service.h"
#include "base/process_util.h"
#include "base/string_number_conversions.h"
#include "base/string_util.h"
#include "media/audio/audio_util.h"
+#include "media/audio/win/audio_device_listener_win.h"
#include "media/audio/win/audio_low_latency_input_win.h"
#include "media/audio/win/audio_low_latency_output_win.h"
#include "media/audio/win/audio_manager_win.h"
@@ -26,6 +29,7 @@
#include "media/audio/win/device_enumeration_win.h"
#include "media/audio/win/wavein_input_win.h"
#include "media/audio/win/waveout_output_win.h"
+#include "media/base/bind_to_loop.h"
#include "media/base/limits.h"
#include "media/base/media_switches.h"
@@ -112,6 +116,10 @@ AudioManagerWin::AudioManagerWin() {
}
AudioManagerWin::~AudioManagerWin() {
+ // It's safe to post a task here since Shutdown() will wait for all tasks to
+ // complete before returning.
+ GetMessageLoop()->PostTask(FROM_HERE, base::Bind(
+ &AudioManagerWin::DestructOnAudioThread, base::Unretained(this)));
Shutdown();
}
@@ -123,6 +131,21 @@ bool AudioManagerWin::HasAudioInputDevices() {
return (::waveInGetNumDevs() != 0);
}
+void AudioManagerWin::InitializeOnAudioThread() {
+ // AudioDeviceListenerWin must be initialized on a COM thread and should only
+ // be used if WASAPI / Core Audio is supported.
+ if (media::IsWASAPISupported()) {
+ output_device_listener_.reset(new AudioDeviceListenerWin(BindToLoop(
+ GetMessageLoop(), base::Bind(
+ &AudioManagerWin::NotifyAllOutputDeviceChangeListeners,
+ base::Unretained(this)))));
+ }
+}
+
+void AudioManagerWin::DestructOnAudioThread() {
+ output_device_listener_.reset();
+}
+
string16 AudioManagerWin::GetAudioInputDeviceModel() {
// Get the default audio capture device and its device interface name.
DWORD device_id = 0;
diff --git a/media/audio/win/audio_manager_win.h b/media/audio/win/audio_manager_win.h
index 6a4efcb..4046577 100644
--- a/media/audio/win/audio_manager_win.h
+++ b/media/audio/win/audio_manager_win.h
@@ -5,22 +5,21 @@
#ifndef MEDIA_AUDIO_WIN_AUDIO_MANAGER_WIN_H_
#define MEDIA_AUDIO_WIN_AUDIO_MANAGER_WIN_H_
-#include <windows.h>
#include <string>
-#include "base/basictypes.h"
-#include "base/compiler_specific.h"
-#include "base/gtest_prod_util.h"
#include "media/audio/audio_manager_base.h"
namespace media {
+class AudioDeviceListenerWin;
+
// Windows implementation of the AudioManager singleton. This class is internal
// to the audio output and only internal users can call methods not exposed by
// the AudioManager class.
class MEDIA_EXPORT AudioManagerWin : public AudioManagerBase {
public:
AudioManagerWin();
+
// Implementation of AudioManager.
virtual bool HasAudioOutputDevices() OVERRIDE;
virtual bool HasAudioInputDevices() OVERRIDE;
@@ -45,6 +44,9 @@ class MEDIA_EXPORT AudioManagerWin : public AudioManagerBase {
protected:
virtual ~AudioManagerWin();
+ // Implementation of AudioManager.
+ virtual void InitializeOnAudioThread() OVERRIDE;
+
private:
enum EnumerationType {
kUninitializedEnumeration = 0,
@@ -70,6 +72,13 @@ class MEDIA_EXPORT AudioManagerWin : public AudioManagerBase {
const AudioParameters& params,
const std::string& device_id);
+ // |output_device_listener_| must be destructed on the same COM thread it was
+ // initialized on.
+ void DestructOnAudioThread();
+
+ // Listen for output device changes.
+ scoped_ptr<AudioDeviceListenerWin> output_device_listener_;
+
DISALLOW_COPY_AND_ASSIGN(AudioManagerWin);
};
diff --git a/media/media.gyp b/media/media.gyp
index 3db10df..dec45ee 100644
--- a/media/media.gyp
+++ b/media/media.gyp
@@ -134,6 +134,8 @@
'audio/scoped_loop_observer.h',
'audio/simple_sources.cc',
'audio/simple_sources.h',
+ 'audio/win/audio_device_listener_win.cc',
+ 'audio/win/audio_device_listener_win.h',
'audio/win/audio_low_latency_input_win.cc',
'audio/win/audio_low_latency_input_win.h',
'audio/win/audio_low_latency_output_win.cc',
@@ -613,6 +615,7 @@
'audio/mac/audio_low_latency_input_mac_unittest.cc',
'audio/mac/audio_output_mac_unittest.cc',
'audio/simple_sources_unittest.cc',
+ 'audio/win/audio_device_listener_win_unittest.cc',
'audio/win/audio_low_latency_input_win_unittest.cc',
'audio/win/audio_low_latency_output_win_unittest.cc',
'audio/win/audio_output_win_unittest.cc',