blob: b364b0b0531883b359c25db3fcf50995ef6b11d9 [file] [log] [blame]
// Copyright 2019 The Fuchsia 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 "src/media/audio/audio_core/route_graph.h"
#include <algorithm>
#include "src/media/audio/audio_core/audio_driver.h"
#include "src/media/audio/lib/logging/logging.h"
namespace media::audio {
namespace {
std::array<fuchsia::media::AudioRenderUsage, fuchsia::media::RENDER_USAGE_COUNT> kRenderUsages = {
fuchsia::media::AudioRenderUsage::BACKGROUND, fuchsia::media::AudioRenderUsage::MEDIA,
fuchsia::media::AudioRenderUsage::INTERRUPTION, fuchsia::media::AudioRenderUsage::SYSTEM_AGENT,
fuchsia::media::AudioRenderUsage::COMMUNICATION};
} // namespace
RouteGraph::RouteGraph(const RoutingConfig& routing_config) : routing_config_(routing_config) {
static_assert(fidl::ToUnderlying(fuchsia::media::AudioRenderUsage::BACKGROUND) == 0);
static_assert(fidl::ToUnderlying(fuchsia::media::AudioRenderUsage::MEDIA) == 1);
static_assert(fidl::ToUnderlying(fuchsia::media::AudioRenderUsage::INTERRUPTION) == 2);
static_assert(fidl::ToUnderlying(fuchsia::media::AudioRenderUsage::SYSTEM_AGENT) == 3);
static_assert(fidl::ToUnderlying(fuchsia::media::AudioRenderUsage::COMMUNICATION) == 4);
static_assert(fuchsia::media::RENDER_USAGE_COUNT == 5);
static_assert(fidl::ToUnderlying(fuchsia::media::AudioCaptureUsage::BACKGROUND) == 0);
static_assert(fidl::ToUnderlying(fuchsia::media::AudioCaptureUsage::FOREGROUND) == 1);
static_assert(fidl::ToUnderlying(fuchsia::media::AudioCaptureUsage::SYSTEM_AGENT) == 2);
static_assert(fidl::ToUnderlying(fuchsia::media::AudioCaptureUsage::COMMUNICATION) == 3);
static_assert(fuchsia::media::CAPTURE_USAGE_COUNT == 4);
}
RouteGraph::~RouteGraph() {
if (throttle_release_fence_) {
throttle_release_fence_->complete_ok();
}
}
void RouteGraph::SetThrottleOutput(ThreadingModel* threading_model,
fbl::RefPtr<AudioOutput> throttle_output) {
fit::bridge<void, void> bridge;
threading_model->FidlDomain().ScheduleTask(bridge.consumer.promise().then(
[throttle_output](fit::result<void, void>& _) { return throttle_output->Shutdown(); }));
threading_model->FidlDomain().executor()->schedule_task(
throttle_output->Startup().or_else([throttle_output](zx_status_t& error) {
FX_PLOGS(ERROR, error) << "Failed to initialize the throttle output";
return throttle_output->Shutdown();
}));
throttle_release_fence_ = {std::move(bridge.completer)};
throttle_output_ = throttle_output;
AddOutput(throttle_output_.get());
}
void RouteGraph::AddOutput(AudioDevice* output) {
AUD_VLOG(TRACE) << "Added output device to route graph: " << output;
outputs_.push_front(output);
UpdateGraphForDeviceChange();
}
void RouteGraph::RemoveOutput(AudioDevice* output) {
AUD_VLOG(TRACE) << "Removing output device from graph: " << output;
auto it = std::find(outputs_.begin(), outputs_.end(), output);
if (it == outputs_.end()) {
FX_LOGS(WARNING) << "Attempted to remove unregistered output device from the route graph.";
return;
}
outputs_.erase(it);
UpdateGraphForDeviceChange();
}
void RouteGraph::AddInput(AudioDevice* input) {
AUD_VLOG(TRACE) << "Added input device to route graph: " << input;
inputs_.push_front(input);
UpdateGraphForDeviceChange();
}
void RouteGraph::RemoveInput(AudioDevice* input) {
AUD_VLOG(TRACE) << "Removing input device to route graph: " << input;
auto it = std::find(inputs_.begin(), inputs_.end(), input);
if (it == inputs_.end()) {
FX_LOGS(WARNING) << "Attempted to remove unregistered input device from the route graph.";
return;
}
inputs_.erase(it);
UpdateGraphForDeviceChange();
}
void RouteGraph::AddRenderer(fbl::RefPtr<AudioObject> renderer) {
FX_DCHECK(throttle_output_);
FX_DCHECK(renderer->is_audio_renderer());
AUD_VLOG(TRACE) << "Adding renderer route graph: " << renderer.get();
renderers_.insert({renderer.get(), RoutableOwnedObject{renderer, {}}});
}
void RouteGraph::SetRendererRoutingProfile(AudioObject* renderer, RoutingProfile profile) {
FX_DCHECK(renderer->is_audio_renderer());
FX_DCHECK(renderer->format_valid() || !profile.routable)
<< "AudioRenderer without PCM format was added to route graph";
AUD_VLOG(TRACE) << "Setting renderer route profile: " << renderer;
auto it = renderers_.find(renderer);
if (it == renderers_.end()) {
FX_LOGS(WARNING) << "Tried to set routing policy for an unregistered renderer.";
return;
}
it->second.profile = std::move(profile);
if (!it->second.profile.routable || !it->second.profile.usage.is_render_usage()) {
it->second.ref->Unlink();
return;
}
AudioDevice* output = OutputForUsage(it->second.profile.usage);
if (it->second.ref->has_link_to(output)) {
return;
}
it->second.ref->Unlink();
if (!output) {
FX_LOGS(WARNING) << "Tried to route AudioRenderer, but no output for the given usage exist.";
return;
}
AudioObject::LinkObjects(it->second.ref, fbl::RefPtr(output));
}
void RouteGraph::RemoveRenderer(AudioObject* renderer) {
FX_DCHECK(renderer->is_audio_renderer());
AUD_VLOG(TRACE) << "Removing renderer from route graph: " << renderer;
renderer->Unlink();
renderers_.erase(renderer);
}
void RouteGraph::AddCapturer(fbl::RefPtr<AudioObject> capturer) {
FX_DCHECK(capturer->is_audio_capturer());
AUD_VLOG(TRACE) << "Adding capturer to route graph: " << capturer.get();
capturers_.insert({capturer.get(), RoutableOwnedObject{capturer, {}}});
}
void RouteGraph::SetCapturerRoutingProfile(AudioObject* capturer, RoutingProfile profile) {
FX_DCHECK(capturer->is_audio_capturer());
AUD_VLOG(TRACE) << "Setting capturer route profile: " << capturer;
auto it = capturers_.find(capturer);
if (it == capturers_.end()) {
FX_LOGS(WARNING) << "Tried to set routing policy for an unregistered renderer.";
return;
}
it->second.profile = std::move(profile);
if (!it->second.profile.routable || !it->second.profile.usage.is_capture_usage()) {
it->second.ref->Unlink();
return;
}
if (it->second.ref->has_link_to(targets_.capture)) {
return;
}
it->second.ref->Unlink();
if (!targets_.capture) {
FX_LOGS(WARNING) << "Tried to route AudioCapturer, but no inputs exist.";
return;
}
AudioObject::LinkObjects(fbl::RefPtr(targets_.capture), it->second.ref);
}
void RouteGraph::RemoveCapturer(AudioObject* capturer) {
FX_DCHECK(capturer->is_audio_capturer());
AUD_VLOG(TRACE) << "Removing capturer from route graph: " << capturer;
capturer->Unlink();
capturers_.erase(capturer);
}
// TODO(39627): Only accept capturers of loopback type.
void RouteGraph::AddLoopbackCapturer(fbl::RefPtr<AudioObject> loopback_capturer) {
FX_DCHECK(loopback_capturer->is_audio_capturer());
AUD_VLOG(TRACE) << "Adding loopback capturer to route graph: " << loopback_capturer.get();
loopback_capturers_.insert({loopback_capturer.get(), RoutableOwnedObject{loopback_capturer, {}}});
}
// TODO(39627): Only accept capturers of loopback type.
void RouteGraph::SetLoopbackCapturerRoutingProfile(AudioObject* loopback_capturer,
RoutingProfile profile) {
FX_DCHECK(loopback_capturer->is_audio_capturer());
AUD_VLOG(TRACE) << "Setting loopback capturer route profile: " << loopback_capturer;
auto it = loopback_capturers_.find(loopback_capturer);
if (it == loopback_capturers_.end()) {
FX_LOGS(WARNING) << "Tried to set routing policy for an unregistered renderer.";
return;
}
it->second.profile = std::move(profile);
if (!it->second.profile.routable || !it->second.profile.usage.is_capture_usage()) {
it->second.ref->Unlink();
return;
}
if (it->second.ref->has_link_to(targets_.loopback)) {
return;
}
it->second.ref->Unlink();
if (!targets_.loopback) {
FX_LOGS(WARNING) << "Tried to route loopback AudioCapturer, but no outputs exist.";
return;
}
AudioObject::LinkObjects(fbl::RefPtr(targets_.loopback), it->second.ref);
}
// TODO(39627): Only accept capturers of loopback type.
void RouteGraph::RemoveLoopbackCapturer(AudioObject* loopback_capturer) {
FX_DCHECK(loopback_capturer->is_audio_capturer());
AUD_VLOG(TRACE) << "Setting loopback capturer from route graph: " << loopback_capturer;
loopback_capturer->Unlink();
loopback_capturers_.erase(loopback_capturer);
}
void RouteGraph::UpdateGraphForDeviceChange() {
auto [targets, unlink_command] = CalculateTargets();
targets_ = targets;
Unlink(unlink_command);
if (std::any_of(unlink_command.renderers.begin(), unlink_command.renderers.end(),
[](auto unlink) { return unlink; })) {
std::for_each(renderers_.begin(), renderers_.end(), [this](auto& renderer) {
AudioDevice* output;
if (!renderer.second.profile.routable ||
!(output = OutputForUsage(renderer.second.profile.usage)) ||
renderer.second.ref->dest_link_count() > 0) {
return;
}
AudioObject::LinkObjects(renderer.second.ref, fbl::RefPtr(output));
});
}
if (unlink_command.loopback_capturers && targets_.loopback) {
std::for_each(loopback_capturers_.begin(), loopback_capturers_.end(),
[target = targets_.loopback](auto& loopback_capturer) {
if (!loopback_capturer.second.profile.routable ||
!loopback_capturer.second.profile.usage.is_capture_usage()) {
return;
}
FX_DCHECK(loopback_capturer.second.ref->source_link_count() == 0);
AudioObject::LinkObjects(fbl::RefPtr(target), loopback_capturer.second.ref);
});
}
if (unlink_command.capturers && targets_.capture) {
std::for_each(capturers_.begin(), capturers_.end(),
[target = targets_.capture](auto& capturer) {
if (!capturer.second.profile.routable ||
!capturer.second.profile.usage.is_capture_usage()) {
return;
}
FX_DCHECK(capturer.second.ref->source_link_count() == 0);
AudioObject::LinkObjects(fbl::RefPtr(target), capturer.second.ref);
});
}
}
std::pair<RouteGraph::Targets, RouteGraph::UnlinkCommand> RouteGraph::CalculateTargets() const {
// We generate a new set of targets.
// We generate an unlink command to unlink anything linked to a target which has changed.
std::array<AudioDevice*, fuchsia::media::RENDER_USAGE_COUNT> new_render_targets = {};
std::array<bool, fuchsia::media::RENDER_USAGE_COUNT> unlink_renderers = {};
for (const auto& usage : kRenderUsages) {
const auto idx = fidl::ToUnderlying(usage);
auto it = std::find_if(outputs_.begin(), outputs_.end(), [&usage, this](auto output) {
if (output == throttle_output_.get()) {
return false;
}
const auto& device_id = output->driver()->persistent_unique_id();
auto device_profile = routing_config_.device_profile(device_id);
return device_profile.supports_usage(usage);
});
if (it != outputs_.end()) {
new_render_targets[idx] = *it;
} else {
new_render_targets[idx] = throttle_output_.get();
}
unlink_renderers[idx] = new_render_targets[idx];
}
auto new_loopback_target = [this]() {
auto it = std::find_if(outputs_.begin(), outputs_.end(), [this](auto output) {
if (output == throttle_output_.get()) {
return false;
}
const auto& device_id = output->driver()->persistent_unique_id();
auto device_profile = routing_config_.device_profile(device_id);
return device_profile.eligible_for_loopback();
});
return it == outputs_.end() ? nullptr : *it;
}();
auto new_capture_target = inputs_.empty() ? nullptr : inputs_.front();
return {Targets{.render = new_render_targets,
.loopback = new_loopback_target,
.capture = new_capture_target},
UnlinkCommand{.renderers = unlink_renderers,
.loopback_capturers = new_loopback_target != targets_.loopback,
.capturers = new_capture_target != targets_.capture}};
}
void RouteGraph::Unlink(UnlinkCommand unlink_command) {
std::for_each(renderers_.begin(), renderers_.end(), [&unlink_command](auto& renderer) {
if (renderer.second.profile.usage.is_render_usage() &&
unlink_command
.renderers[fidl::ToUnderlying(renderer.second.profile.usage.render_usage())]) {
renderer.second.ref->Unlink();
}
});
if (unlink_command.loopback_capturers) {
std::for_each(loopback_capturers_.begin(), loopback_capturers_.end(),
[](auto& loopback_capturer) { loopback_capturer.second.ref->Unlink(); });
}
if (unlink_command.capturers) {
std::for_each(capturers_.begin(), capturers_.end(),
[](auto& capturer) { capturer.second.ref->Unlink(); });
}
}
AudioDevice* RouteGraph::OutputForUsage(const fuchsia::media::Usage& usage) const {
if (!usage.is_render_usage()) {
return nullptr;
}
auto idx = fidl::ToUnderlying(usage.render_usage());
return targets_.render[idx];
}
} // namespace media::audio