blob: 4734e40b801d3f7660213ef02c60186e96367ba9 [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/audio_output_resampler.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/message_loop.h"
#include "base/metrics/histogram.h"
#include "base/time.h"
#include "build/build_config.h"
#include "media/audio/audio_io.h"
#include "media/audio/audio_output_dispatcher_impl.h"
#include "media/audio/audio_output_proxy.h"
#include "media/audio/audio_util.h"
#include "media/audio/sample_rates.h"
#include "media/base/audio_converter.h"
#include "media/base/limits.h"
#include "media/base/media_switches.h"
#if defined(OS_WIN)
#include "media/audio/win/core_audio_util_win.h"
#endif
namespace media {
class OnMoreDataConverter
: public AudioOutputStream::AudioSourceCallback,
public AudioConverter::InputCallback {
public:
OnMoreDataConverter(const AudioParameters& input_params,
const AudioParameters& output_params);
virtual ~OnMoreDataConverter();
// AudioSourceCallback interface.
virtual int OnMoreData(AudioBus* dest,
AudioBuffersState buffers_state) OVERRIDE;
virtual int OnMoreIOData(AudioBus* source,
AudioBus* dest,
AudioBuffersState buffers_state) OVERRIDE;
virtual void OnError(AudioOutputStream* stream, int code) OVERRIDE;
virtual void WaitTillDataReady() OVERRIDE;
// Sets |source_callback_|. If this is not a new object, then Stop() must be
// called before Start().
void Start(AudioOutputStream::AudioSourceCallback* callback);
// Clears |source_callback_| and flushes the resampler.
void Stop();
private:
// AudioConverter::InputCallback implementation.
virtual double ProvideInput(AudioBus* audio_bus,
base::TimeDelta buffer_delay) OVERRIDE;
// Ratio of input bytes to output bytes used to correct playback delay with
// regard to buffering and resampling.
double io_ratio_;
// Source callback and associated lock.
base::Lock source_lock_;
AudioOutputStream::AudioSourceCallback* source_callback_;
// |source| passed to OnMoreIOData() which should be passed downstream.
AudioBus* source_bus_;
// Last AudioBuffersState object received via OnMoreData(), used to correct
// playback delay by ProvideInput() and passed on to |source_callback_|.
AudioBuffersState current_buffers_state_;
const int input_bytes_per_second_;
// Handles resampling, buffering, and channel mixing between input and output
// parameters.
AudioConverter audio_converter_;
// If we're using WaveOut on Windows' we always have to wait for DataReady()
// before calling |source_callback_|.
bool waveout_wait_hack_;
DISALLOW_COPY_AND_ASSIGN(OnMoreDataConverter);
};
// Record UMA statistics for hardware output configuration.
static void RecordStats(const AudioParameters& output_params) {
UMA_HISTOGRAM_ENUMERATION(
"Media.HardwareAudioBitsPerChannel", output_params.bits_per_sample(),
limits::kMaxBitsPerSample);
UMA_HISTOGRAM_ENUMERATION(
"Media.HardwareAudioChannelLayout", output_params.channel_layout(),
CHANNEL_LAYOUT_MAX);
UMA_HISTOGRAM_ENUMERATION(
"Media.HardwareAudioChannelCount", output_params.channels(),
limits::kMaxChannels);
AudioSampleRate asr = media::AsAudioSampleRate(output_params.sample_rate());
if (asr != kUnexpectedAudioSampleRate) {
UMA_HISTOGRAM_ENUMERATION(
"Media.HardwareAudioSamplesPerSecond", asr, kUnexpectedAudioSampleRate);
} else {
UMA_HISTOGRAM_COUNTS(
"Media.HardwareAudioSamplesPerSecondUnexpected",
output_params.sample_rate());
}
}
// Record UMA statistics for hardware output configuration after fallback.
static void RecordFallbackStats(const AudioParameters& output_params) {
UMA_HISTOGRAM_BOOLEAN("Media.FallbackToHighLatencyAudioPath", true);
UMA_HISTOGRAM_ENUMERATION(
"Media.FallbackHardwareAudioBitsPerChannel",
output_params.bits_per_sample(), limits::kMaxBitsPerSample);
UMA_HISTOGRAM_ENUMERATION(
"Media.FallbackHardwareAudioChannelLayout",
output_params.channel_layout(), CHANNEL_LAYOUT_MAX);
UMA_HISTOGRAM_ENUMERATION(
"Media.FallbackHardwareAudioChannelCount",
output_params.channels(), limits::kMaxChannels);
AudioSampleRate asr = media::AsAudioSampleRate(output_params.sample_rate());
if (asr != kUnexpectedAudioSampleRate) {
UMA_HISTOGRAM_ENUMERATION(
"Media.FallbackHardwareAudioSamplesPerSecond",
asr, kUnexpectedAudioSampleRate);
} else {
UMA_HISTOGRAM_COUNTS(
"Media.FallbackHardwareAudioSamplesPerSecondUnexpected",
output_params.sample_rate());
}
}
// Converts low latency based |output_params| into high latency appropriate
// output parameters in error situations.
static AudioParameters SetupFallbackParams(
const AudioParameters& input_params, const AudioParameters& output_params) {
// Choose AudioParameters appropriate for opening the device in high latency
// mode. |kMinLowLatencyFrameSize| is arbitrarily based on Pepper Flash's
// MAXIMUM frame size for low latency.
static const int kMinLowLatencyFrameSize = 2048;
int frames_per_buffer = std::min(
std::max(input_params.frames_per_buffer(), kMinLowLatencyFrameSize),
static_cast<int>(
GetHighLatencyOutputBufferSize(input_params.sample_rate())));
return AudioParameters(
AudioParameters::AUDIO_PCM_LINEAR, input_params.channel_layout(),
input_params.sample_rate(), input_params.bits_per_sample(),
frames_per_buffer);
}
AudioOutputResampler::AudioOutputResampler(AudioManager* audio_manager,
const AudioParameters& input_params,
const AudioParameters& output_params,
const base::TimeDelta& close_delay)
: AudioOutputDispatcher(audio_manager, input_params),
close_delay_(close_delay),
output_params_(output_params),
streams_opened_(false) {
DCHECK(input_params.IsValid());
DCHECK(output_params.IsValid());
DCHECK_EQ(output_params_.format(), AudioParameters::AUDIO_PCM_LOW_LATENCY);
// Record UMA statistics for the hardware configuration.
RecordStats(output_params);
Initialize();
}
AudioOutputResampler::~AudioOutputResampler() {
DCHECK(callbacks_.empty());
}
void AudioOutputResampler::Initialize() {
DCHECK(!streams_opened_);
DCHECK(callbacks_.empty());
dispatcher_ = new AudioOutputDispatcherImpl(
audio_manager_, output_params_, close_delay_);
}
bool AudioOutputResampler::OpenStream() {
DCHECK_EQ(MessageLoop::current(), message_loop_);
if (dispatcher_->OpenStream()) {
// Only record the UMA statistic if we didn't fallback during construction
// and only for the first stream we open.
if (!streams_opened_ &&
output_params_.format() == AudioParameters::AUDIO_PCM_LOW_LATENCY) {
UMA_HISTOGRAM_BOOLEAN("Media.FallbackToHighLatencyAudioPath", false);
}
streams_opened_ = true;
return true;
}
// If we've already tried to open the stream in high latency mode or we've
// successfully opened a stream previously, there's nothing more to be done.
if (output_params_.format() == AudioParameters::AUDIO_PCM_LINEAR ||
streams_opened_ || !callbacks_.empty()) {
return false;
}
DCHECK_EQ(output_params_.format(), AudioParameters::AUDIO_PCM_LOW_LATENCY);
if (CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableAudioFallback)) {
LOG(ERROR) << "Open failed and automatic fallback to high latency audio "
<< "path is disabled, aborting.";
return false;
}
DLOG(ERROR) << "Unable to open audio device in low latency mode. Falling "
<< "back to high latency audio output.";
// Record UMA statistics about the hardware which triggered the failure so
// we can debug and triage later.
RecordFallbackStats(output_params_);
output_params_ = SetupFallbackParams(params_, output_params_);
Initialize();
// Retry, if this fails, there's nothing left to do but report the error back.
return dispatcher_->OpenStream();
}
bool AudioOutputResampler::StartStream(
AudioOutputStream::AudioSourceCallback* callback,
AudioOutputProxy* stream_proxy) {
DCHECK_EQ(MessageLoop::current(), message_loop_);
OnMoreDataConverter* resampler_callback = NULL;
CallbackMap::iterator it = callbacks_.find(stream_proxy);
if (it == callbacks_.end()) {
resampler_callback = new OnMoreDataConverter(params_, output_params_);
callbacks_[stream_proxy] = resampler_callback;
} else {
resampler_callback = it->second;
}
resampler_callback->Start(callback);
return dispatcher_->StartStream(resampler_callback, stream_proxy);
}
void AudioOutputResampler::StreamVolumeSet(AudioOutputProxy* stream_proxy,
double volume) {
DCHECK_EQ(MessageLoop::current(), message_loop_);
dispatcher_->StreamVolumeSet(stream_proxy, volume);
}
void AudioOutputResampler::StopStream(AudioOutputProxy* stream_proxy) {
DCHECK_EQ(MessageLoop::current(), message_loop_);
dispatcher_->StopStream(stream_proxy);
// Now that StopStream() has completed the underlying physical stream should
// be stopped and no longer calling OnMoreData(), making it safe to Stop() the
// OnMoreDataConverter.
CallbackMap::iterator it = callbacks_.find(stream_proxy);
if (it != callbacks_.end())
it->second->Stop();
}
void AudioOutputResampler::CloseStream(AudioOutputProxy* stream_proxy) {
DCHECK_EQ(MessageLoop::current(), message_loop_);
dispatcher_->CloseStream(stream_proxy);
// We assume that StopStream() is always called prior to CloseStream(), so
// that it is safe to delete the OnMoreDataConverter here.
CallbackMap::iterator it = callbacks_.find(stream_proxy);
if (it != callbacks_.end()) {
delete it->second;
callbacks_.erase(it);
}
}
void AudioOutputResampler::Shutdown() {
DCHECK_EQ(MessageLoop::current(), message_loop_);
// No AudioOutputProxy objects should hold a reference to us when we get
// to this stage.
DCHECK(HasOneRef()) << "Only the AudioManager should hold a reference";
dispatcher_->Shutdown();
DCHECK(callbacks_.empty());
}
OnMoreDataConverter::OnMoreDataConverter(const AudioParameters& input_params,
const AudioParameters& output_params)
: source_callback_(NULL),
source_bus_(NULL),
input_bytes_per_second_(input_params.GetBytesPerSecond()),
audio_converter_(input_params, output_params, false),
waveout_wait_hack_(false) {
io_ratio_ =
static_cast<double>(input_params.GetBytesPerSecond()) /
output_params.GetBytesPerSecond();
// TODO(dalecurtis): We should require all render side clients to use a
// buffer size that's a multiple of the hardware buffer size scaled by the
// request_sample_rate / hw_sample_rate. Doing so ensures each hardware
// request for audio data results in only a single render side callback and
// would allow us to remove this hack. See http://crbug.com/162207.
#if defined(OS_WIN)
waveout_wait_hack_ =
output_params.format() == AudioParameters::AUDIO_PCM_LINEAR ||
!CoreAudioUtil::IsSupported();
#endif
}
OnMoreDataConverter::~OnMoreDataConverter() {}
void OnMoreDataConverter::Start(
AudioOutputStream::AudioSourceCallback* callback) {
base::AutoLock auto_lock(source_lock_);
DCHECK(!source_callback_);
source_callback_ = callback;
// While AudioConverter can handle multiple inputs, we're using it only with
// a single input currently. Eventually this may be the basis for a browser
// side mixer.
audio_converter_.AddInput(this);
}
void OnMoreDataConverter::Stop() {
base::AutoLock auto_lock(source_lock_);
source_callback_ = NULL;
audio_converter_.RemoveInput(this);
}
int OnMoreDataConverter::OnMoreData(AudioBus* dest,
AudioBuffersState buffers_state) {
return OnMoreIOData(NULL, dest, buffers_state);
}
int OnMoreDataConverter::OnMoreIOData(AudioBus* source,
AudioBus* dest,
AudioBuffersState buffers_state) {
base::AutoLock auto_lock(source_lock_);
// While we waited for |source_lock_| the callback might have been cleared.
if (!source_callback_) {
dest->Zero();
return dest->frames();
}
source_bus_ = source;
current_buffers_state_ = buffers_state;
audio_converter_.Convert(dest);
// Always return the full number of frames requested, ProvideInput_Locked()
// will pad with silence if it wasn't able to acquire enough data.
return dest->frames();
}
double OnMoreDataConverter::ProvideInput(AudioBus* dest,
base::TimeDelta buffer_delay) {
source_lock_.AssertAcquired();
// Adjust playback delay to include |buffer_delay|.
// TODO(dalecurtis): Stop passing bytes around, it doesn't make sense since
// AudioBus is just float data. Use TimeDelta instead.
AudioBuffersState new_buffers_state;
new_buffers_state.pending_bytes =
io_ratio_ * (current_buffers_state_.total_bytes() +
buffer_delay.InSecondsF() * input_bytes_per_second_);
if (waveout_wait_hack_)
source_callback_->WaitTillDataReady();
// Retrieve data from the original callback.
int frames = source_callback_->OnMoreIOData(
source_bus_, dest, new_buffers_state);
// |source_bus_| should only be provided once.
// TODO(dalecurtis, crogers): This is not a complete fix. If ProvideInput()
// is called multiple times, we need to do something more clever here.
source_bus_ = NULL;
// Zero any unfilled frames if anything was filled, otherwise we'll just
// return a volume of zero and let AudioConverter drop the output.
if (frames > 0 && frames < dest->frames())
dest->ZeroFramesPartial(frames, dest->frames() - frames);
// TODO(dalecurtis): Return the correct volume here.
return frames > 0 ? 1 : 0;
}
void OnMoreDataConverter::OnError(AudioOutputStream* stream, int code) {
base::AutoLock auto_lock(source_lock_);
if (source_callback_)
source_callback_->OnError(stream, code);
}
void OnMoreDataConverter::WaitTillDataReady() {
base::AutoLock auto_lock(source_lock_);
if (source_callback_)
source_callback_->WaitTillDataReady();
}
} // namespace media