Skip to content

Commit

Permalink
Handle audio device changes on Windows.
Browse files Browse the repository at this point in the history
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
  • Loading branch information
dalecurtis@google.com committed Oct 26, 2012
1 parent 89b4d95 commit 0b4125c
Show file tree
Hide file tree
Showing 14 changed files with 407 additions and 87 deletions.
4 changes: 2 additions & 2 deletions content/renderer/media/audio_renderer_mixer_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_;

Expand Down
74 changes: 43 additions & 31 deletions media/audio/audio_manager_base.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
}
Expand Down
5 changes: 3 additions & 2 deletions media/audio/audio_manager_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include <map>
#include <string>
#include <utility>

#include "base/atomic_ref_count.h"
#include "base/compiler_specific.h"
Expand Down Expand Up @@ -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
Expand Down
22 changes: 0 additions & 22 deletions media/audio/audio_parameters.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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
19 changes: 13 additions & 6 deletions media/audio/audio_parameters.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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_
5 changes: 2 additions & 3 deletions media/audio/audio_parameters_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
}
}

Expand Down
145 changes: 145 additions & 0 deletions media/audio/win/audio_device_listener_win.cc
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 0b4125c

Please sign in to comment.