blob: 8215c387897e80de81ac2ee1a9d464f9468c560d [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 "third_party/blink/renderer/modules/media/audio/audio_renderer_mixer_manager.h"
#include <algorithm>
#include <limits>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "build/build_config.h"
#include "media/audio/audio_device_description.h"
#include "media/base/audio_renderer_mixer.h"
#include "media/base/audio_renderer_mixer_input.h"
#include "third_party/blink/public/web/modules/media/audio/web_audio_device_factory.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
namespace {
// Calculate mixer output parameters based on mixer input parameters and
// hardware parameters for audio output.
media::AudioParameters GetMixerOutputParams(
const media::AudioParameters& input_params,
const media::AudioParameters& hardware_params,
media::AudioLatency::LatencyType latency) {
// For a compressed bitstream, no audio post processing is allowed, hence the
// output parameters should be the same as input parameters.
if (input_params.IsBitstreamFormat())
return input_params;
int output_sample_rate, preferred_output_buffer_size;
if (!hardware_params.IsValid() ||
hardware_params.format() == media::AudioParameters::AUDIO_FAKE) {
// With fake or invalid hardware params, don't waste cycles on resampling.
output_sample_rate = input_params.sample_rate();
preferred_output_buffer_size = 0; // Let media::AudioLatency() choose.
} else if (media::AudioLatency::IsResamplingPassthroughSupported(latency)) {
// Certain platforms don't require us to resample to a single rate for low
// latency, so again, don't waste cycles on resampling.
output_sample_rate = input_params.sample_rate();
// For playback, prefer the input params buffer size unless the hardware
// needs something even larger (say for Bluetooth devices).
if (latency == media::AudioLatency::LATENCY_PLAYBACK) {
preferred_output_buffer_size =
std::max(input_params.frames_per_buffer(),
hardware_params.frames_per_buffer());
} else {
preferred_output_buffer_size = hardware_params.frames_per_buffer();
}
} else {
// Otherwise, always resample and rebuffer to the hardware parameters.
output_sample_rate = hardware_params.sample_rate();
preferred_output_buffer_size = hardware_params.frames_per_buffer();
}
int output_buffer_size = 0;
// Adjust output buffer size according to the latency requirement.
switch (latency) {
case media::AudioLatency::LATENCY_INTERACTIVE:
output_buffer_size = media::AudioLatency::GetInteractiveBufferSize(
hardware_params.frames_per_buffer());
break;
case media::AudioLatency::LATENCY_RTC:
output_buffer_size = media::AudioLatency::GetRtcBufferSize(
output_sample_rate, preferred_output_buffer_size);
break;
case media::AudioLatency::LATENCY_PLAYBACK:
output_buffer_size = media::AudioLatency::GetHighLatencyBufferSize(
output_sample_rate, preferred_output_buffer_size);
break;
case media::AudioLatency::LATENCY_EXACT_MS:
// TODO(olka): add support when WebAudio requires it.
default:
NOTREACHED();
}
DCHECK_NE(output_buffer_size, 0);
media::AudioParameters params(input_params.format(),
input_params.channel_layout(),
output_sample_rate, output_buffer_size);
// Use the actual channel count when the channel layout is "DISCRETE".
if (input_params.channel_layout() == media::CHANNEL_LAYOUT_DISCRETE)
params.set_channels_for_discrete(input_params.channels());
// Specify the effects info the passed to the browser side.
params.set_effects(input_params.effects());
// Specify the latency info to be passed to the browser side.
params.set_latency_tag(latency);
return params;
}
} // namespace
namespace blink {
AudioRendererMixerManager::AudioRendererMixerManager(
CreateSinkCB create_sink_cb)
: create_sink_cb_(std::move(create_sink_cb)) {
DCHECK(create_sink_cb_);
}
AudioRendererMixerManager::~AudioRendererMixerManager() {
// References to AudioRendererMixers may be owned by garbage collected
// objects. During process shutdown they may be leaked, so, transitively,
// |mixers_| may leak (i.e., may be non-empty at this time) as well.
}
// static
AudioRendererMixerManager& AudioRendererMixerManager::GetInstance() {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
AudioRendererMixerManager, instance,
(base::BindRepeating(&WebAudioDeviceFactory::NewAudioRendererMixerSink)));
return instance;
}
scoped_refptr<media::AudioRendererMixerInput>
AudioRendererMixerManager::CreateInput(
const blink::LocalFrameToken& source_frame_token,
const base::UnguessableToken& session_id,
const std::string& device_id,
media::AudioLatency::LatencyType latency) {
// AudioRendererMixerManager lives on the renderer thread and is destroyed on
// renderer thread destruction, so it's safe to pass its pointer to a mixer
// input.
//
// TODO(olka, grunell): |session_id| is always empty, delete since
// NewAudioRenderingMixingStrategy didn't ship, https://crbug.com/870836.
DCHECK(session_id.is_empty());
return base::MakeRefCounted<media::AudioRendererMixerInput>(
this, source_frame_token.value(), device_id, latency);
}
media::AudioRendererMixer* AudioRendererMixerManager::GetMixer(
const blink::LocalFrameToken& source_frame_token,
const media::AudioParameters& input_params,
media::AudioLatency::LatencyType latency,
const media::OutputDeviceInfo& sink_info,
scoped_refptr<media::AudioRendererSink> sink) {
// Ownership of the sink must be given to GetMixer().
DCHECK(sink->HasOneRef());
DCHECK_EQ(sink_info.device_status(), media::OUTPUT_DEVICE_STATUS_OK);
const MixerKey key(source_frame_token, input_params, latency,
sink_info.device_id());
base::AutoLock auto_lock(mixers_lock_);
auto it = mixers_.find(key);
if (it != mixers_.end()) {
auto new_count = ++it->second.ref_count;
CHECK(new_count != std::numeric_limits<decltype(new_count)>::max());
DVLOG(1) << "Reusing mixer: " << it->second.mixer;
// Sink will now be released unused, but still must be stopped.
//
// TODO(dalecurtis): Is it worth caching this sink instead for a future
// GetSink() call? We should experiment with a few top sites. We can't just
// drop in AudioRendererSinkCache here since it doesn't reuse sinks once
// they've been vended externally to the class.
sink->Stop();
return it->second.mixer;
}
const media::AudioParameters& mixer_output_params =
GetMixerOutputParams(input_params, sink_info.output_params(), latency);
media::AudioRendererMixer* mixer =
new media::AudioRendererMixer(mixer_output_params, std::move(sink));
mixers_[key] = {mixer, 1};
DVLOG(1) << __func__ << " mixer: " << mixer << " latency: " << latency
<< "\n input: " << input_params.AsHumanReadableString()
<< "\noutput: " << mixer_output_params.AsHumanReadableString();
return mixer;
}
scoped_refptr<media::AudioRendererSink> AudioRendererMixerManager::GetSink(
const blink::LocalFrameToken& source_frame_token,
const std::string& device_id) {
return create_sink_cb_.Run(
source_frame_token,
media::AudioSinkParameters(base::UnguessableToken(), device_id));
}
media::AudioRendererMixer* AudioRendererMixerManager::GetMixer(
const base::UnguessableToken& source_frame_token,
const media::AudioParameters& input_params,
media::AudioLatency::LatencyType latency,
const media::OutputDeviceInfo& sink_info,
scoped_refptr<media::AudioRendererSink> sink) {
// Ownership of the sink must be given to GetMixer().
DCHECK(sink->HasOneRef());
// Forward to the strongly typed version. We move the |sink| as GetMixer
// expects to be the sole owner at this point.
DCHECK(source_frame_token);
return GetMixer(blink::LocalFrameToken(source_frame_token), input_params,
latency, sink_info, std::move(sink));
}
void AudioRendererMixerManager::ReturnMixer(media::AudioRendererMixer* mixer) {
base::AutoLock auto_lock(mixers_lock_);
auto it = std::find_if(
mixers_.begin(), mixers_.end(),
[mixer](const std::pair<MixerKey, AudioRendererMixerReference>& val) {
return val.second.mixer == mixer;
});
DCHECK(it != mixers_.end());
// Only remove the mixer if AudioRendererMixerManager is the last owner.
it->second.ref_count--;
if (it->second.ref_count == 0) {
delete it->second.mixer;
mixers_.erase(it);
}
}
scoped_refptr<media::AudioRendererSink> AudioRendererMixerManager::GetSink(
const base::UnguessableToken& source_frame_token,
const std::string& device_id) {
// Forward to the strongly typed version.
DCHECK(source_frame_token);
return GetSink(blink::LocalFrameToken(source_frame_token), device_id);
}
AudioRendererMixerManager::MixerKey::MixerKey(
const blink::LocalFrameToken& source_frame_token,
const media::AudioParameters& params,
media::AudioLatency::LatencyType latency,
const std::string& device_id)
: source_frame_token(source_frame_token),
params(params),
latency(latency),
device_id(device_id) {}
AudioRendererMixerManager::MixerKey::MixerKey(const MixerKey& other) = default;
AudioRendererMixerManager::MixerKey::~MixerKey() = default;
} // namespace blink