blob: 3d43c5b05da1c93eaa162fdd2a38bb17d8647c68 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/audio/output_device_mixer_manager.h"
#include "base/check.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/trace_event/trace_event.h"
#include "media/audio/audio_io.h"
#include "media/audio/audio_manager.h"
#include "media/base/audio_latency.h"
#include "services/audio/audio_manager_power_user.h"
#include "services/audio/device_listener_output_stream.h"
#include "services/audio/output_device_mixer.h"
namespace {
const char kNormalizedDefaultDeviceId[] = "";
// Helper function which returns a consistent representation of the default
// device ID.
std::string NormalizeIfDefault(const std::string& device_id) {
return media::AudioDeviceDescription::IsDefaultDevice(device_id)
? kNormalizedDefaultDeviceId
: device_id;
}
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
// Aligned with AudioOutputDeviceMixerManagerStreamCreation enum.
enum class StreamCreation {
kUnmixable,
kFallbackToUnmixable,
kUsingNewMixer,
kUsingExistingMixer,
kMaxValue = kUsingExistingMixer,
};
} // namespace
namespace audio {
OutputDeviceMixerManager::OutputDeviceMixerManager(
media::AudioManager* audio_manager,
OutputDeviceMixer::CreateCallback create_mixer_callback)
: audio_manager_(audio_manager),
current_default_device_id_(
AudioManagerPowerUser(audio_manager_).GetDefaultOutputDeviceID()),
current_communication_device_id_(AudioManagerPowerUser(audio_manager_)
.GetCommunicationsOutputDeviceID()),
create_mixer_callback_(std::move(create_mixer_callback)),
device_change_weak_ptr_factory_(this) {
// This should be a static_assert, but there is no compile time way to run
// AudioDeviceDescription::IsDefaultDevice().
DCHECK(media::AudioDeviceDescription::IsDefaultDevice(
kNormalizedDefaultDeviceId));
}
OutputDeviceMixerManager::~OutputDeviceMixerManager() {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
}
media::AudioOutputStream* OutputDeviceMixerManager::MakeOutputStream(
const std::string& device_id,
const media::AudioParameters& params,
base::OnceClosure close_stream_on_device_change) {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
OutputDeviceMixer* mixer = nullptr;
StreamCreation stream_creation = StreamCreation::kUnmixable;
if (params.format() == media::AudioParameters::AUDIO_PCM_LOW_LATENCY) {
std::string mixer_device_id = ToMixerDeviceId(device_id);
mixer = FindMixer(mixer_device_id);
if (mixer) {
stream_creation = StreamCreation::kUsingExistingMixer;
} else {
mixer = AddMixer(mixer_device_id);
stream_creation = mixer ? StreamCreation::kUsingNewMixer
: StreamCreation::kFallbackToUnmixable;
}
}
base::UmaHistogramEnumeration(
"Media.Audio.OutputDeviceMixerManager.StreamCreation", stream_creation);
if (mixer) {
return mixer->MakeMixableStream(params,
std::move(close_stream_on_device_change));
}
DLOG(WARNING) << "Making unmixable output stream";
return CreateDeviceListenerStream(std::move(close_stream_on_device_change),
device_id, params);
}
void OutputDeviceMixerManager::OnDeviceChange() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("audio"),
"OutputDeviceMixerManager::OnDeviceChange");
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
current_default_device_id_ =
AudioManagerPowerUser(audio_manager_).GetDefaultOutputDeviceID();
current_communication_device_id_ =
AudioManagerPowerUser(audio_manager_).GetCommunicationsOutputDeviceID();
// Invalidate WeakPtrs, cancelling any pending device change callbacks
// generated by the same device change event.
device_change_weak_ptr_factory_.InvalidateWeakPtrs();
OutputDeviceMixers old_mixers;
output_device_mixers_.swap(old_mixers);
// Do not call StopListening(), as |old_mixers| are being destroyed anyways.
for (auto&& mixer : old_mixers)
mixer->ProcessDeviceChange();
}
void OutputDeviceMixerManager::StartNewListener(
ReferenceOutput::Listener* listener,
const std::string& listener_device_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
DCHECK(IsNormalizedIfDefault(listener_device_id));
DCHECK(!listener_registration_.contains(listener));
listener_registration_[listener] = listener_device_id;
OutputDeviceMixer* mixer = FindMixer(ToMixerDeviceId(listener_device_id));
if (!mixer)
return;
mixer->StartListening(listener);
}
void OutputDeviceMixerManager::StartListening(
ReferenceOutput::Listener* listener,
const std::string& output_device_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
std::string listener_device_id = NormalizeIfDefault(output_device_id);
if (!listener_registration_.contains(listener)) {
StartNewListener(listener, listener_device_id);
return;
}
std::string registered_listener_device_id =
listener_registration_.at(listener);
if (ToMixerDeviceId(registered_listener_device_id) !=
ToMixerDeviceId(listener_device_id)) {
// |listener| is listening to a completely different mixer.
StopListening(listener);
StartNewListener(listener, listener_device_id);
return;
}
// |listener| is listening to the right mixer, but we might need to update
// its registration (e.g. when switching between
// |current_default_device_id_| and kNormalizedDefaultId, or
// |current_communications_device_id_| and kCommunicationsDeviceId).
if (registered_listener_device_id != listener_device_id)
listener_registration_[listener] = listener_device_id;
}
void OutputDeviceMixerManager::StopListening(
ReferenceOutput::Listener* listener) {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
const std::string listener_device_id = listener_registration_.at(listener);
listener_registration_.erase(listener);
OutputDeviceMixer* mixer = FindMixer(ToMixerDeviceId(listener_device_id));
// The mixer was never created, because there was no playback to that device
// (possibly after a device device change). Listening never started, so there
// is nothing to stop.
if (!mixer)
return;
mixer->StopListening(listener);
}
std::string OutputDeviceMixerManager::ToMixerDeviceId(
const std::string& device_id) {
if (media::AudioDeviceDescription::IsDefaultDevice(device_id))
return kNormalizedDefaultDeviceId;
DCHECK(!device_id.empty());
if (device_id == current_default_device_id_)
return kNormalizedDefaultDeviceId;
// It's possible for |current_communication_device_id_| and
// |current_default_device_id_| to match. In that case, replace the
// communications mixer device ID with the default mixer device ID.
// Similarly, replace "communications" with kNormalizedDefaultDeviceId when
// |current_communication_device_id_| is unsupported/unconfigured.
if (device_id == media::AudioDeviceDescription::kCommunicationsDeviceId &&
(current_communication_device_id_.empty() ||
current_communication_device_id_ == current_default_device_id_)) {
return kNormalizedDefaultDeviceId;
}
if (device_id == current_communication_device_id_)
return media::AudioDeviceDescription::kCommunicationsDeviceId;
return device_id;
}
OutputDeviceMixer* OutputDeviceMixerManager::FindMixer(
const std::string& device_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
DCHECK_EQ(ToMixerDeviceId(device_id), device_id);
for (const auto& mixer : output_device_mixers_) {
if (mixer->device_id() == device_id)
return mixer.get();
}
return nullptr;
}
OutputDeviceMixer* OutputDeviceMixerManager::AddMixer(
const std::string& device_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
DCHECK_EQ(ToMixerDeviceId(device_id), device_id);
DCHECK(!FindMixer(device_id));
media::AudioParameters output_params =
AudioManagerPowerUser(audio_manager_)
.GetOutputStreamParameters(device_id);
if (!output_params.IsValid()) {
LOG(ERROR) << "Adding OutputDeviceMixer failed: invalid output parameters";
return nullptr;
}
output_params.set_frames_per_buffer(media::AudioLatency::GetRtcBufferSize(
output_params.sample_rate(), output_params.frames_per_buffer()));
// TODO(crbug/1295658): Temporary work around. Mix all audio as stereo and
// rely on the system channel mapping.
if (output_params.channel_layout() == media::CHANNEL_LAYOUT_DISCRETE &&
output_params.channels() >= 2) {
output_params.Reset(
output_params.format(), media::ChannelLayoutConfig::Stereo(),
output_params.sample_rate(), output_params.frames_per_buffer());
}
// base::Unretained(this) is safe here, because |output_device_mixers_|
// are owned by |this|.
std::unique_ptr<OutputDeviceMixer> output_device_mixer =
create_mixer_callback_.Run(
device_id, output_params,
base::BindRepeating(&OutputDeviceMixerManager::CreateMixerOwnedStream,
base::Unretained(this)),
audio_manager_->GetTaskRunner());
// The |device_id| might no longer be valid, e.g. if a device was unplugged.
if (!output_device_mixer) {
LOG(ERROR) << "Adding OutputDeviceMixer failed: creation error";
return nullptr;
}
auto* mixer = output_device_mixer.get();
output_device_mixers_.push_back(std::move(output_device_mixer));
// Attach any interested listeners.
for (auto&& listener_device_kvp : listener_registration_) {
if (ToMixerDeviceId(listener_device_kvp.second) == mixer->device_id())
mixer->StartListening(listener_device_kvp.first);
}
return mixer;
}
base::OnceClosure OutputDeviceMixerManager::GetOnDeviceChangeCallback() {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
return base::BindOnce(&OutputDeviceMixerManager::OnDeviceChange,
device_change_weak_ptr_factory_.GetWeakPtr());
}
media::AudioOutputStream* OutputDeviceMixerManager::CreateMixerOwnedStream(
const std::string& device_id,
const media::AudioParameters& params) {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
return CreateDeviceListenerStream(GetOnDeviceChangeCallback(), device_id,
params);
}
media::AudioOutputStream* OutputDeviceMixerManager::CreateDeviceListenerStream(
base::OnceClosure on_device_change_callback,
const std::string& device_id,
const media::AudioParameters& params) {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
media::AudioOutputStream* stream =
audio_manager_->MakeAudioOutputStreamProxy(params, device_id);
if (!stream) {
LOG(ERROR) << "Stream proxy limit reached";
return nullptr;
}
// If this stream is created via CreateMixerOwnedStream(),
// |on_device_change_callback| will call OnDeviceChange(), cancel pending
// calls to OnDeviceChange(), and release all mixer owned streams.
//
// If we are directly creating this stream, |on_device_change_callback| will
// synchronously close the returned stream.
return new DeviceListenerOutputStream(audio_manager_, stream,
std::move(on_device_change_callback));
}
bool OutputDeviceMixerManager::IsNormalizedIfDefault(
const std::string& device_id) {
return device_id == kNormalizedDefaultDeviceId ||
!media::AudioDeviceDescription::IsDefaultDevice(device_id);
}
} // namespace audio