blob: 6a197033d753e1ad2626ae4146e75c090665b4c6 [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 <lib/syslog/cpp/macros.h>
#include <algorithm>
#include <sstream>
#include "src/media/audio/audio_core/audio_driver.h"
#include "src/media/audio/audio_core/logging_flags.h"
namespace media::audio {
namespace {
// TODO(fxbug.dev/55132): Remove this workaround. Just 64000 would still support the range needed.
static constexpr int32_t kMinUltrasoundRate = 96000;
bool DeviceConfigurationSupportsUsage(AudioDevice* device, StreamUsage usage) {
if (usage != StreamUsage::WithRenderUsage(RenderUsage::ULTRASOUND) &&
usage != StreamUsage::WithCaptureUsage(CaptureUsage::ULTRASOUND)) {
return true;
}
FX_DCHECK(device->format());
auto device_rate = device->format()->frames_per_second();
return device_rate >= kMinUltrasoundRate;
}
} // namespace
RouteGraph::RouteGraph(LinkMatrix* link_matrix) : link_matrix_(*link_matrix) {
FX_DCHECK(link_matrix);
}
RouteGraph::~RouteGraph() {
if (throttle_release_fence_) {
throttle_release_fence_->complete_ok();
}
}
void RouteGraph::SetThrottleOutput(ThreadingModel* threading_model,
std::shared_ptr<AudioOutput> throttle_output) {
fpromise::bridge<void, void> bridge;
threading_model->FidlDomain().ScheduleTask(bridge.consumer.promise().then(
[throttle_output](fpromise::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;
AddDeviceToRoutes(throttle_output_.get());
}
void RouteGraph::AddDeviceToRoutes(AudioDevice* device) {
TRACE_DURATION("audio", "RouteGraph::AddDeviceToRoutes");
// Add device, sorted with most-recently-plugged devices first. Use a stable sort so that ties are
// broken by most-recently-added device, which helps make unit tests deterministic.
devices_.push_front(device);
std::stable_sort(devices_.begin(), devices_.end(), [](AudioDevice* a, AudioDevice* b) {
if (a->plugged() != b->plugged()) {
return a->plugged();
}
return a->plug_time() > b->plug_time();
});
if constexpr (kLogRoutingChanges) {
FX_LOGS(INFO) << "Added device " << device << " (" << (device->is_input() ? "input" : "output")
<< ") to route graph";
DisplayDevices();
}
UpdateGraphForDeviceChange();
}
void RouteGraph::RemoveDeviceFromRoutes(AudioDevice* device) {
TRACE_DURATION("audio", "RouteGraph::RemoveDeviceFromRoutes");
if constexpr (kLogRoutingChanges) {
FX_LOGS(INFO) << "Removing device " << device << " ("
<< (device->is_input() ? "input" : "output") << ") from route graph";
}
auto it = std::find(devices_.begin(), devices_.end(), device);
if (it == devices_.end()) {
FX_LOGS(WARNING) << "Attempted to remove unregistered device from the route graph.";
return;
}
// Unlink the device (don't just tell its sources/dests to Unlink) so LinkMatrix fully removes it.
link_matrix_.Unlink(*device);
devices_.erase(it);
if constexpr (kLogRoutingChanges) {
DisplayDevices();
}
UpdateGraphForDeviceChange();
}
bool RouteGraph::ContainsDevice(const AudioDevice* device) {
return (std::find(devices_.begin(), devices_.end(), device) != devices_.end());
}
void RouteGraph::AddRenderer(std::shared_ptr<AudioObject> renderer) {
TRACE_DURATION("audio", "RouteGraph::AddRenderer");
FX_DCHECK(throttle_output_);
FX_DCHECK(renderer->is_audio_renderer());
if constexpr (kLogRoutingChanges) {
FX_LOGS(INFO) << "Adding renderer " << renderer.get() << " (" << renderer->usage()->ToString()
<< ") to route graph";
}
renderers_.insert({renderer.get(), RoutableOwnedObject{std::move(renderer), {}}});
if constexpr (kLogRoutingChanges) {
DisplayRenderers();
}
}
void RouteGraph::SetRendererRoutingProfile(const AudioObject& renderer, RoutingProfile profile) {
TRACE_DURATION("audio", "RouteGraph::SetRendererRoutingProfile");
FX_DCHECK(renderer.is_audio_renderer());
FX_LOGS(DEBUG) << "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()) {
link_matrix_.Unlink(*it->second.ref);
return;
}
auto output = TargetForUsage(it->second.profile.usage);
if (!output.is_linkable()) {
FX_LOGS(WARNING) << "Tried to route AudioRenderer, but no device available for usage "
<< it->second.profile.usage.ToString();
link_matrix_.Unlink(*it->second.ref);
return;
}
if (link_matrix_.AreLinked(*it->second.ref, *output.device)) {
return;
}
link_matrix_.Unlink(*it->second.ref);
link_matrix_.LinkObjects(it->second.ref, output.device->shared_from_this(), output.transform);
if constexpr (kLogRoutingChanges) {
FX_LOGS(INFO) << "Setting renderer route profile: " << &renderer;
DisplayRenderers();
link_matrix_.DisplayCurrentRouting();
}
}
void RouteGraph::RemoveRenderer(const AudioObject& renderer) {
TRACE_DURATION("audio", "RouteGraph::RemoveRenderer");
FX_DCHECK(renderer.is_audio_renderer());
auto it = renderers_.find(&renderer);
if (it == renderers_.end()) {
FX_LOGS(INFO) << "Renderer " << &renderer << " was not present in graph.";
return;
}
link_matrix_.Unlink(*it->second.ref);
renderers_.erase(it);
if constexpr (kLogRoutingChanges) {
FX_LOGS(INFO) << "Removed renderer from route graph: " << &renderer << " ("
<< renderer.usage()->ToString() << ")";
DisplayRenderers();
link_matrix_.DisplayCurrentRouting();
}
}
void RouteGraph::AddCapturer(std::shared_ptr<AudioObject> capturer) {
TRACE_DURATION("audio", "RouteGraph::AddCapturer");
FX_DCHECK(capturer->is_audio_capturer());
if constexpr (kLogRoutingChanges) {
FX_LOGS(INFO) << "Adding capturer " << capturer.get() << " (" << capturer->usage()->ToString()
<< ") to route graph";
}
capturers_.insert({capturer.get(), RoutableOwnedObject{std::move(capturer), {}}});
if constexpr (kLogRoutingChanges) {
DisplayCapturers();
}
}
void RouteGraph::SetCapturerRoutingProfile(const AudioObject& capturer, RoutingProfile profile) {
TRACE_DURATION("audio", "RouteGraph::SetCapturerRoutingProfile");
FX_DCHECK(capturer.is_audio_capturer());
FX_LOGS(DEBUG) << "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 capturer.";
return;
}
it->second.profile = std::move(profile);
if (!it->second.profile.routable || !it->second.profile.usage.is_capture_usage()) {
link_matrix_.Unlink(*it->second.ref);
return;
}
auto target = TargetForUsage(it->second.profile.usage);
if (!target.is_linkable()) {
FX_LOGS(WARNING) << "Tried to route AudioCapturer, but no device available for usage "
<< it->second.profile.usage.ToString();
link_matrix_.Unlink(*it->second.ref);
return;
}
if (link_matrix_.AreLinked(*target.device, *it->second.ref)) {
return;
}
link_matrix_.Unlink(*it->second.ref);
link_matrix_.LinkObjects(target.device->shared_from_this(), it->second.ref, target.transform);
if constexpr (kLogRoutingChanges) {
FX_LOGS(INFO) << "Setting capturer route profile: " << &capturer;
DisplayCapturers();
link_matrix_.DisplayCurrentRouting();
}
}
void RouteGraph::RemoveCapturer(const AudioObject& capturer) {
TRACE_DURATION("audio", "RouteGraph::RemoveCapturer");
FX_DCHECK(capturer.is_audio_capturer());
auto it = capturers_.find(&capturer);
if (it == capturers_.end()) {
FX_LOGS(WARNING) << "Capturer " << &capturer << " was not present in graph.";
return;
}
link_matrix_.Unlink(*it->second.ref);
capturers_.erase(it);
if constexpr (kLogRoutingChanges) {
FX_LOGS(INFO) << "Removed capturer " << &capturer << " (" << capturer.usage()->ToString()
<< ") from route graph";
DisplayCapturers();
link_matrix_.DisplayCurrentRouting();
}
}
void RouteGraph::UpdateGraphForDeviceChange() {
TRACE_DURATION("audio", "RouteGraph::UpdateGraphForDeviceChange");
auto [targets, unlink_command] = CalculateTargets();
targets_ = targets;
Unlink(unlink_command);
{
TRACE_DURATION("audio", "RouteGraph::UpdateGraphForDeviceChange.renderers");
std::for_each(renderers_.begin(), renderers_.end(), [this](auto& renderer) {
Target target;
if (!renderer.second.profile.routable ||
!((target = TargetForUsage(renderer.second.profile.usage)).is_linkable()) ||
link_matrix_.DestLinkCount(*renderer.second.ref) > 0u) {
return;
}
link_matrix_.LinkObjects(renderer.second.ref, target.device->shared_from_this(),
target.transform);
});
}
{
TRACE_DURATION("audio", "RouteGraph::UpdateGraphForDeviceChange.capturers");
std::for_each(capturers_.begin(), capturers_.end(), [this](auto& capturer) {
Target target;
if (!capturer.second.profile.routable ||
!((target = TargetForUsage(capturer.second.profile.usage)).is_linkable()) ||
link_matrix_.SourceLinkCount(*capturer.second.ref) > 0u) {
return;
}
link_matrix_.LinkObjects(target.device->shared_from_this(), capturer.second.ref,
target.transform);
});
}
if constexpr (kLogRoutingChanges) {
DisplayRenderers();
DisplayCapturers();
DisplayDevices();
link_matrix_.DisplayCurrentRouting();
}
}
std::pair<RouteGraph::Targets, RouteGraph::UnlinkCommand> RouteGraph::CalculateTargets() const {
TRACE_DURATION("audio", "RouteGraph::CalculateTargets");
// We generate a new set of targets.
// We generate an unlink command to unlink anything linked to a target which has changed.
Targets new_targets = {};
UnlinkCommand unlink;
for (const auto& usage : kStreamUsages) {
const auto idx = HashStreamUsage(usage);
new_targets[idx] = [this, usage]() {
for (auto device : devices_) {
if (device == throttle_output_.get()) {
continue;
}
if (device->profile().supports_usage(usage) &&
DeviceConfigurationSupportsUsage(device, usage)) {
return Target(device, device->profile().loudness_transform());
}
}
if (usage.is_render_usage()) {
return Target(throttle_output_.get(), throttle_output_->profile().loudness_transform());
} else {
return Target();
}
}();
unlink[idx] = targets_[idx].device != new_targets[idx].device;
}
return {new_targets, unlink};
}
void RouteGraph::Unlink(UnlinkCommand unlink_command) {
TRACE_DURATION("audio", "RouteGraph::Unlink");
std::for_each(renderers_.begin(), renderers_.end(), [this, &unlink_command](auto& renderer) {
auto usage = renderer.second.profile.usage;
if (!usage.is_empty() && unlink_command[HashStreamUsage(usage)]) {
link_matrix_.Unlink(*renderer.second.ref);
}
});
std::for_each(capturers_.begin(), capturers_.end(), [this, &unlink_command](auto& capturer) {
auto usage = capturer.second.profile.usage;
if (!usage.is_empty() && unlink_command[HashStreamUsage(usage)]) {
link_matrix_.Unlink(*capturer.second.ref);
}
});
}
RouteGraph::Target RouteGraph::TargetForUsage(const StreamUsage& usage) const {
if (usage.is_empty()) {
return Target();
}
return targets_[HashStreamUsage(usage)];
}
// The API is formed to return more than one output as the target for a RenderUsage, but the current
// audio_core implementation only routes to one output per usage.
std::unordered_set<AudioDevice*> RouteGraph::TargetsForRenderUsage(const RenderUsage& usage) {
auto target = targets_[HashStreamUsage(StreamUsage::WithRenderUsage(usage))];
if (!target.is_linkable()) {
FX_LOGS(ERROR) << __FUNCTION__ << " (" << RenderUsageToString(usage)
<< ") target is not linkable";
return {};
}
if constexpr (kLogIdlePolicyCounts) {
FX_LOGS(INFO) << __FUNCTION__ << " (" << RenderUsageToString(usage) << ") returning "
<< target.device;
}
return {target.device};
}
std::shared_ptr<LoudnessTransform> RouteGraph::LoudnessTransformForUsage(const StreamUsage& usage) {
return TargetForUsage(usage).transform;
}
void RouteGraph::DisplayRenderers() {
std::stringstream stream;
stream << "Renderers:";
if (renderers_.empty()) {
stream << " <empty>";
} else {
for (const auto& renderer : renderers_) {
stream << " " << renderer.first;
}
}
FX_LOGS(INFO) << stream.str();
}
void RouteGraph::DisplayCapturers() {
std::stringstream stream;
stream << "Capturers:";
if (capturers_.empty()) {
stream << " <empty>";
} else {
for (const auto& capturer : capturers_) {
stream << " " << capturer.first;
}
}
FX_LOGS(INFO) << stream.str();
std::stringstream().swap(stream);
stream << "Loopbacks:";
if (loopback_capturers_.empty()) {
stream << " <empty>";
} else {
for (const auto& capturer : loopback_capturers_) {
stream << " " << capturer.first;
}
}
FX_LOGS(INFO) << stream.str();
}
void RouteGraph::DisplayDevices() {
std::stringstream stream;
stream << "Devices:";
for (const auto& device : devices_) {
stream << " " << device;
}
FX_LOGS(INFO) << stream.str();
}
} // namespace media::audio