blob: 38ab8c5019a8039bcdd456999594831fdf0bcbff [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 <stdint.h>
#include <algorithm>
#include <string>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/compiler_specific.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/sparse_histogram.h"
#include "base/numerics/safe_conversions.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "media/audio/audio_manager.h"
#include "media/audio/audio_output_dispatcher_impl.h"
#include "media/audio/audio_output_proxy.h"
#include "media/base/audio_converter.h"
#include "media/base/audio_timestamp_helper.h"
#include "media/base/limits.h"
#include "media/base/sample_rates.h"
namespace media {
class OnMoreDataConverter
: public AudioOutputStream::AudioSourceCallback,
public AudioConverter::InputCallback {
public:
OnMoreDataConverter(const AudioParameters& input_params,
const AudioParameters& output_params,
std::unique_ptr<AudioDebugRecorder> debug_recorder);
~OnMoreDataConverter() override;
// AudioSourceCallback interface.
int OnMoreData(base::TimeDelta delay,
base::TimeTicks delay_timestamp,
int prior_frames_skipped,
AudioBus* dest) override;
void OnError() 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();
bool started() const { return source_callback_ != nullptr; }
bool error_occurred() const { return error_occurred_; }
private:
// AudioConverter::InputCallback implementation.
double ProvideInput(AudioBus* audio_bus, uint32_t frames_delayed) override;
// Ratio of input bytes to output bytes used to correct playback delay with
// regard to buffering and resampling.
const double io_ratio_;
// Source callback.
AudioOutputStream::AudioSourceCallback* source_callback_;
// Last |delay| and |delay_timestamp| received via OnMoreData(). Used to
// correct playback delay in ProvideInput() before calling |source_callback_|.
base::TimeDelta current_delay_;
base::TimeTicks current_delay_timestamp_;
const int input_samples_per_second_;
// Handles resampling, buffering, and channel mixing between input and output
// parameters.
AudioConverter audio_converter_;
// True if OnError() was ever called. Should only be read if the underlying
// stream has been stopped.
bool error_occurred_;
// Information about input and output buffer sizes to be traced.
const int input_buffer_size_;
const int output_buffer_size_;
// For audio debug recordings.
std::unique_ptr<AudioDebugRecorder> debug_recorder_;
DISALLOW_COPY_AND_ASSIGN(OnMoreDataConverter);
};
namespace {
// Record UMA statistics for hardware output configuration.
static void RecordStats(const AudioParameters& output_params) {
UMA_HISTOGRAM_EXACT_LINEAR("Media.HardwareAudioBitsPerChannel",
output_params.bits_per_sample(),
static_cast<int>(limits::kMaxBitsPerSample));
UMA_HISTOGRAM_ENUMERATION(
"Media.HardwareAudioChannelLayout", output_params.channel_layout(),
CHANNEL_LAYOUT_MAX + 1);
UMA_HISTOGRAM_EXACT_LINEAR("Media.HardwareAudioChannelCount",
output_params.channels(),
static_cast<int>(limits::kMaxChannels));
AudioSampleRate asr;
if (ToAudioSampleRate(output_params.sample_rate(), &asr)) {
UMA_HISTOGRAM_ENUMERATION(
"Media.HardwareAudioSamplesPerSecond", asr, kAudioSampleRateMax + 1);
} 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_EXACT_LINEAR("Media.FallbackHardwareAudioBitsPerChannel",
output_params.bits_per_sample(),
static_cast<int>(limits::kMaxBitsPerSample));
UMA_HISTOGRAM_ENUMERATION(
"Media.FallbackHardwareAudioChannelLayout",
output_params.channel_layout(), CHANNEL_LAYOUT_MAX + 1);
UMA_HISTOGRAM_EXACT_LINEAR("Media.FallbackHardwareAudioChannelCount",
output_params.channels(),
static_cast<int>(limits::kMaxChannels));
AudioSampleRate asr;
if (ToAudioSampleRate(output_params.sample_rate(), &asr)) {
UMA_HISTOGRAM_ENUMERATION(
"Media.FallbackHardwareAudioSamplesPerSecond",
asr, kAudioSampleRateMax + 1);
} else {
UMA_HISTOGRAM_COUNTS(
"Media.FallbackHardwareAudioSamplesPerSecondUnexpected",
output_params.sample_rate());
}
}
// Record UMA statistics for input/output rebuffering.
static void RecordRebufferingStats(const AudioParameters& input_params,
const AudioParameters& output_params) {
const int input_buffer_size = input_params.frames_per_buffer();
const int output_buffer_size = output_params.frames_per_buffer();
DCHECK_NE(0, input_buffer_size);
DCHECK_NE(0, output_buffer_size);
// Buffer size mismatch; see Media.Audio.Render.BrowserCallbackRegularity
// histogram for explanation.
int value = 0;
if (input_buffer_size >= output_buffer_size) {
// 0 if input size is a multiple of output size; otherwise -1.
value = (input_buffer_size % output_buffer_size) ? -1 : 0;
} else {
value = (output_buffer_size / input_buffer_size - 1) * 2;
if (output_buffer_size % input_buffer_size) {
// One more callback is issued periodically.
value += 1;
}
}
const int value_cap = (4096 / 128 - 1) * 2 + 1;
if (value > value_cap)
value = value_cap;
switch (input_params.latency_tag()) {
case AudioLatency::LATENCY_EXACT_MS:
UMA_HISTOGRAM_SPARSE_SLOWLY(
"Media.Audio.Render.BrowserCallbackRegularity.LatencyExactMs", value);
return;
case AudioLatency::LATENCY_INTERACTIVE:
UMA_HISTOGRAM_SPARSE_SLOWLY(
"Media.Audio.Render.BrowserCallbackRegularity.LatencyInteractive",
value);
return;
case AudioLatency::LATENCY_RTC:
UMA_HISTOGRAM_SPARSE_SLOWLY(
"Media.Audio.Render.BrowserCallbackRegularity.LatencyRtc", value);
return;
case AudioLatency::LATENCY_PLAYBACK:
UMA_HISTOGRAM_SPARSE_SLOWLY(
"Media.Audio.Render.BrowserCallbackRegularity.LatencyPlayback",
value);
return;
default:
DVLOG(1) << "Latency tag is not set";
}
}
// Only Windows has a high latency output driver that is not the same as the low
// latency path.
#if defined(OS_WIN)
// Converts low latency based |output_params| into high latency appropriate
// output parameters in error situations.
AudioParameters GetFallbackOutputParams(
const AudioParameters& original_output_params) {
DCHECK_EQ(original_output_params.format(),
AudioParameters::AUDIO_PCM_LOW_LATENCY);
// 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;
const int frames_per_buffer = std::max(
original_output_params.frames_per_buffer(), kMinLowLatencyFrameSize);
return AudioParameters(AudioParameters::AUDIO_PCM_LINEAR,
original_output_params.channel_layout(),
original_output_params.sample_rate(),
original_output_params.bits_per_sample(),
frames_per_buffer);
}
#endif
// This enum must match the numbering for
// AudioOutputResamplerOpenLowLatencyStreamResult in enums.xml. Do not reorder
// or remove items, only add new items before OPEN_STREAM_MAX.
enum OpenStreamResult {
OPEN_STREAM_FAIL = 0,
OPEN_STREAM_FALLBACK_TO_FAKE = 1,
OPEN_STREAM_FALLBACK_TO_LINEAR = 2,
OPEN_STREAM_SUCCESS = 3,
OPEN_STREAM_SUBSEQUENT_FALLBACK_TO_FAKE_FAIL = 4,
OPEN_STREAM_SUBSEQUENT_FALLBACK_TO_FAKE_SUCCESS = 5,
OPEN_STREAM_SUBSEQUENT_FALLBACK_TO_LINEAR_FAIL = 6,
OPEN_STREAM_SUBSEQUENT_FALLBACK_TO_LINEAR_SUCCESS = 7,
OPEN_STREAM_SUBSEQUENT_FAIL = 8,
OPEN_STREAM_SUBSEQUENT_SUCCESS = 9,
OPEN_STREAM_MAX = 9,
};
OpenStreamResult GetSubsequentStreamCreationResultBucket(
const AudioParameters& current_params,
bool success) {
switch (current_params.format()) {
case AudioParameters::AUDIO_PCM_LOW_LATENCY:
return success ? OPEN_STREAM_SUBSEQUENT_SUCCESS
: OPEN_STREAM_SUBSEQUENT_FAIL;
case AudioParameters::AUDIO_PCM_LINEAR:
return success ? OPEN_STREAM_SUBSEQUENT_FALLBACK_TO_LINEAR_SUCCESS
: OPEN_STREAM_SUBSEQUENT_FALLBACK_TO_LINEAR_FAIL;
case AudioParameters::AUDIO_FAKE:
return success ? OPEN_STREAM_SUBSEQUENT_FALLBACK_TO_FAKE_SUCCESS
: OPEN_STREAM_SUBSEQUENT_FALLBACK_TO_FAKE_FAIL;
default:
NOTREACHED();
return OPEN_STREAM_FAIL;
}
}
} // namespace
AudioOutputResampler::AudioOutputResampler(
AudioManager* audio_manager,
const AudioParameters& input_params,
const AudioParameters& output_params,
const std::string& output_device_id,
base::TimeDelta close_delay,
const RegisterDebugRecordingSourceCallback&
register_debug_recording_source_callback)
: AudioOutputDispatcher(audio_manager),
close_delay_(close_delay),
input_params_(input_params),
output_params_(output_params),
original_output_params_(output_params),
device_id_(output_device_id),
reinitialize_timer_(FROM_HERE,
close_delay_,
base::Bind(&AudioOutputResampler::Reinitialize,
base::Unretained(this)),
false),
register_debug_recording_source_callback_(
register_debug_recording_source_callback),
weak_factory_(this) {
DCHECK(audio_manager->GetTaskRunner()->BelongsToCurrentThread());
DCHECK(input_params.IsValid());
DCHECK(output_params.IsValid());
DCHECK(output_params_.format() == AudioParameters::AUDIO_PCM_LOW_LATENCY ||
output_params_.format() == AudioParameters::AUDIO_PCM_LINEAR);
DCHECK(register_debug_recording_source_callback_);
// Record UMA statistics for the hardware configuration.
RecordStats(output_params);
}
AudioOutputResampler::~AudioOutputResampler() {
DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
for (const auto& item : callbacks_) {
if (item.second->started())
StopStreamInternal(item);
}
}
void AudioOutputResampler::Reinitialize() {
DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
// We can only reinitialize the dispatcher if it has no active proxies. Check
// if one has been created since the reinitialization timer was started.
if (dispatcher_ && dispatcher_->HasOutputProxies())
return;
DCHECK(callbacks_.empty());
// Log a trace event so we can get feedback in the field when this happens.
TRACE_EVENT0("audio", "AudioOutputResampler::Reinitialize");
output_params_ = original_output_params_;
dispatcher_.reset();
}
std::unique_ptr<AudioOutputDispatcherImpl> AudioOutputResampler::MakeDispatcher(
const std::string& output_device_id,
const AudioParameters& params) {
DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
DCHECK(callbacks_.empty());
return std::make_unique<AudioOutputDispatcherImpl>(
audio_manager(), params, output_device_id, close_delay_);
}
AudioOutputProxy* AudioOutputResampler::CreateStreamProxy() {
DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
return new AudioOutputProxy(weak_factory_.GetWeakPtr());
}
bool AudioOutputResampler::OpenStream() {
DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
bool first_stream = false;
if (!dispatcher_) {
first_stream = true;
// No open streams => no fallback has happened.
DCHECK(original_output_params_.Equals(output_params_));
DCHECK(callbacks_.empty());
dispatcher_ = MakeDispatcher(device_id_, output_params_);
}
if (dispatcher_->OpenStream()) {
// Only record the UMA statistic if we didn't fallback during construction
// and only for the first stream we open.
if (original_output_params_.format() ==
AudioParameters::AUDIO_PCM_LOW_LATENCY) {
if (first_stream)
UMA_HISTOGRAM_BOOLEAN("Media.FallbackToHighLatencyAudioPath", false);
UMA_HISTOGRAM_ENUMERATION(
"Media.AudioOutputResampler.OpenLowLatencyStream",
first_stream
? OPEN_STREAM_SUCCESS
: GetSubsequentStreamCreationResultBucket(output_params_, true),
OPEN_STREAM_MAX + 1);
}
return true;
}
// Fallback is available for low latency streams only.
if (original_output_params_.format() !=
AudioParameters::AUDIO_PCM_LOW_LATENCY) {
return false;
}
// If we have successfully opened a stream previously, there's nothing more to
// be done.
if (!first_stream) {
UMA_HISTOGRAM_ENUMERATION(
"Media.AudioOutputResampler.OpenLowLatencyStream",
GetSubsequentStreamCreationResultBucket(output_params_, false),
OPEN_STREAM_MAX + 1);
return false;
}
// Record UMA statistics about the hardware which triggered the failure so
// we can debug and triage later.
RecordFallbackStats(original_output_params_);
// Only Windows has a high latency output driver that is not the same as the
// low latency path.
#if defined(OS_WIN)
DLOG(ERROR) << "Unable to open audio device in low latency mode. Falling "
<< "back to high latency audio output.";
output_params_ = GetFallbackOutputParams(original_output_params_);
const std::string fallback_device_id = "";
dispatcher_ = MakeDispatcher(fallback_device_id, output_params_);
if (dispatcher_->OpenStream()) {
UMA_HISTOGRAM_ENUMERATION("Media.AudioOutputResampler.OpenLowLatencyStream",
OPEN_STREAM_FALLBACK_TO_LINEAR,
OPEN_STREAM_MAX + 1);
return true;
}
#endif
DLOG(ERROR) << "Unable to open audio device in high latency mode. Falling "
<< "back to fake audio output.";
// Finally fall back to a fake audio output device.
output_params_ = input_params_;
output_params_.set_format(AudioParameters::AUDIO_FAKE);
dispatcher_ = MakeDispatcher(device_id_, output_params_);
if (dispatcher_->OpenStream()) {
UMA_HISTOGRAM_ENUMERATION("Media.AudioOutputResampler.OpenLowLatencyStream",
OPEN_STREAM_FALLBACK_TO_FAKE,
OPEN_STREAM_MAX + 1);
return true;
}
// Resetting the malfunctioning dispatcher.
dispatcher_.reset();
UMA_HISTOGRAM_ENUMERATION("Media.AudioOutputResampler.OpenLowLatencyStream",
OPEN_STREAM_FAIL, OPEN_STREAM_MAX + 1);
return false;
}
bool AudioOutputResampler::StartStream(
AudioOutputStream::AudioSourceCallback* callback,
AudioOutputProxy* stream_proxy) {
DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
DCHECK(dispatcher_);
OnMoreDataConverter* resampler_callback = nullptr;
CallbackMap::iterator it = callbacks_.find(stream_proxy);
if (it == callbacks_.end()) {
// If a register callback has been given, register and pass the returned
// recoder to the converter. Data is fed to same recorder for the lifetime
// of the converter, which is until the stream is closed.
resampler_callback = new OnMoreDataConverter(
input_params_, output_params_,
register_debug_recording_source_callback_.Run(output_params_));
callbacks_[stream_proxy] =
base::WrapUnique<OnMoreDataConverter>(resampler_callback);
} else {
resampler_callback = it->second.get();
}
resampler_callback->Start(callback);
bool result = dispatcher_->StartStream(resampler_callback, stream_proxy);
if (!result)
resampler_callback->Stop();
return result;
}
void AudioOutputResampler::StreamVolumeSet(AudioOutputProxy* stream_proxy,
double volume) {
DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
DCHECK(dispatcher_);
dispatcher_->StreamVolumeSet(stream_proxy, volume);
}
void AudioOutputResampler::StopStream(AudioOutputProxy* stream_proxy) {
DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
CallbackMap::iterator it = callbacks_.find(stream_proxy);
DCHECK(it != callbacks_.end());
StopStreamInternal(*it);
}
void AudioOutputResampler::CloseStream(AudioOutputProxy* stream_proxy) {
DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
DCHECK(dispatcher_);
dispatcher_->CloseStream(stream_proxy);
// We assume that StopStream() is always called prior to CloseStream(), so
// that it is safe to delete the OnMoreDataConverter here.
callbacks_.erase(stream_proxy);
// Start the reinitialization timer if there are no active proxies and we're
// not using the originally requested output parameters. This allows us to
// recover from transient output creation errors.
if (!dispatcher_->HasOutputProxies() && callbacks_.empty() &&
!output_params_.Equals(original_output_params_)) {
reinitialize_timer_.Reset();
}
}
void AudioOutputResampler::StopStreamInternal(
const CallbackMap::value_type& item) {
DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
DCHECK(dispatcher_);
AudioOutputProxy* stream_proxy = item.first;
OnMoreDataConverter* callback = item.second.get();
DCHECK(callback->started());
// Stop the underlying physical stream.
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.
callback->Stop();
// Destroy idle streams if any errors occurred during output; this ensures
// bad streams will not be reused. Note: Errors may occur during the Stop()
// call above.
if (callback->error_occurred())
dispatcher_->CloseAllIdleStreams();
}
OnMoreDataConverter::OnMoreDataConverter(
const AudioParameters& input_params,
const AudioParameters& output_params,
std::unique_ptr<AudioDebugRecorder> debug_recorder)
: io_ratio_(static_cast<double>(input_params.GetBytesPerSecond()) /
output_params.GetBytesPerSecond()),
source_callback_(nullptr),
input_samples_per_second_(input_params.sample_rate()),
audio_converter_(input_params, output_params, false),
error_occurred_(false),
input_buffer_size_(input_params.frames_per_buffer()),
output_buffer_size_(output_params.frames_per_buffer()),
debug_recorder_(std::move(debug_recorder)) {
RecordRebufferingStats(input_params, output_params);
}
OnMoreDataConverter::~OnMoreDataConverter() {
// Ensure Stop() has been called so we don't end up with an AudioOutputStream
// calling back into OnMoreData() after destruction.
CHECK(!source_callback_);
}
void OnMoreDataConverter::Start(
AudioOutputStream::AudioSourceCallback* callback) {
CHECK(!source_callback_);
CHECK(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() {
CHECK(source_callback_);
audio_converter_.RemoveInput(this);
source_callback_ = nullptr;
}
int OnMoreDataConverter::OnMoreData(base::TimeDelta delay,
base::TimeTicks delay_timestamp,
int /* prior_frames_skipped */,
AudioBus* dest) {
TRACE_EVENT2("audio", "OnMoreDataConverter::OnMoreData", "input buffer size",
input_buffer_size_, "output buffer size", output_buffer_size_);
current_delay_ = delay;
current_delay_timestamp_ = delay_timestamp;
audio_converter_.Convert(dest);
if (debug_recorder_)
debug_recorder_->OnData(dest);
// Always return the full number of frames requested, ProvideInput()
// will pad with silence if it wasn't able to acquire enough data.
return dest->frames();
}
double OnMoreDataConverter::ProvideInput(AudioBus* dest,
uint32_t frames_delayed) {
base::TimeDelta new_delay =
current_delay_ + AudioTimestampHelper::FramesToTime(
frames_delayed, input_samples_per_second_);
// Retrieve data from the original callback.
const int frames = source_callback_->OnMoreData(
new_delay, current_delay_timestamp_, 0, dest);
// 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);
return frames > 0 ? 1 : 0;
}
void OnMoreDataConverter::OnError() {
error_occurred_ = true;
source_callback_->OnError();
}
} // namespace media