| // Copyright 2020 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/link_matrix.h" |
| |
| #include <map> |
| #include <ostream> |
| #include <set> |
| #include <unordered_map> |
| #include <utility> |
| |
| namespace media::audio { |
| namespace { |
| |
| using LinkType = std::pair<AudioObject::Type, AudioObject::Type>; |
| constexpr std::array<LinkType, 3> kValidLinks{ |
| LinkType{AudioObject::Type::AudioRenderer, AudioObject::Type::Output}, |
| LinkType{AudioObject::Type::Input, AudioObject::Type::AudioCapturer}, |
| LinkType{AudioObject::Type::Output, AudioObject::Type::AudioCapturer}}; |
| |
| void CheckLinkIsValid(AudioObject* source, AudioObject* dest) { |
| FX_CHECK(source != nullptr); |
| FX_CHECK(dest != nullptr); |
| |
| FX_CHECK(std::any_of(kValidLinks.begin(), kValidLinks.end(), |
| [source_type = source->type(), dest_type = dest->type()](auto pair) { |
| auto [valid_source_type, valid_dest_type] = pair; |
| return source_type == valid_source_type && dest_type == valid_dest_type; |
| })); |
| } |
| |
| } // namespace |
| |
| zx_status_t LinkMatrix::LinkObjects(std::shared_ptr<AudioObject> source, |
| std::shared_ptr<AudioObject> dest, |
| std::shared_ptr<const LoudnessTransform> loudness_transform) |
| FXL_LOCKS_EXCLUDED(lock_) { |
| TRACE_DURATION("audio", "LinkMatrix::LinkObjects"); |
| CheckLinkIsValid(source.get(), dest.get()); |
| |
| auto dest_link_init_result = source->InitializeDestLink(*dest); |
| if (dest_link_init_result.is_error()) { |
| return dest_link_init_result.error(); |
| } |
| auto stream = dest_link_init_result.take_value(); |
| |
| auto source_link_init_result = dest->InitializeSourceLink(*source, stream); |
| if (source_link_init_result.is_error()) { |
| return source_link_init_result.error(); |
| } |
| auto [mixer, mix_domain] = source_link_init_result.take_value(); |
| |
| { |
| std::lock_guard<std::mutex> lock(lock_); |
| DestLinkSet(source.get()).insert(Link(dest, loudness_transform, stream, mixer, mix_domain)); |
| SourceLinkSet(dest.get()).insert(Link(source, loudness_transform, stream, mixer, mix_domain)); |
| } |
| |
| source->OnLinkAdded(); |
| dest->OnLinkAdded(); |
| |
| return ZX_OK; |
| } |
| |
| void LinkMatrix::Unlink(AudioObject& key) FXL_LOCKS_EXCLUDED(lock_) { |
| TRACE_DURATION("audio", "LinkMatrix::Unlink"); |
| std::lock_guard<std::mutex> lock(lock_); |
| |
| auto dest_list = DestLinkSet(&key); |
| std::for_each(dest_list.begin(), dest_list.end(), [this, &key](auto& dest) { |
| auto& sources = SourceLinkSet(dest.key); |
| auto source = sources.find(Link(&key)); |
| if (source == sources.end()) { |
| FX_LOGS(WARNING) << "Trying to unlink " << &key << " -- no source found"; |
| return; |
| } |
| |
| auto dest_object = dest.object.lock(); |
| if (dest_object) { |
| dest_object->CleanupSourceLink(key, source->stream); |
| key.CleanupDestLink(*dest_object); |
| } |
| |
| sources.erase(Link(&key)); |
| }); |
| |
| auto source_list = SourceLinkSet(&key); |
| std::for_each(source_list.begin(), source_list.end(), [this, &key](auto& source) { |
| auto& dests = DestLinkSet(source.key); |
| auto dest = dests.find(Link(&key)); |
| if (dest == dests.end()) { |
| FX_LOGS(WARNING) << "Trying to unlink " << &key << " -- no dest found"; |
| return; |
| } |
| |
| auto source_object = source.object.lock(); |
| if (source_object) { |
| source_object->CleanupDestLink(key); |
| key.CleanupSourceLink(*source_object, dest->stream); |
| } |
| |
| dests.erase(Link(&key)); |
| }); |
| |
| sources_.erase(&key); |
| dests_.erase(&key); |
| } |
| |
| void LinkMatrix::ForEachDestLink(const AudioObject& object, fit::function<void(LinkHandle)> f) |
| FXL_LOCKS_EXCLUDED(lock_) { |
| TRACE_DURATION("audio", "LinkMatrix::ForEachDestLink"); |
| std::lock_guard<std::mutex> lock(lock_); |
| |
| for (auto& link : DestLinkSet(&object)) { |
| TRACE_DURATION("audio", "LinkMatrix::ForEachDestLink.link"); |
| if (auto ptr = link.object.lock()) { |
| f(LinkHandle{.object = ptr, |
| .loudness_transform = link.loudness_transform, |
| .stream = link.stream, |
| .mixer = link.mixer, |
| .mix_domain = link.mix_domain}); |
| } |
| } |
| } |
| |
| void LinkMatrix::ForEachSourceLink(const AudioObject& object, fit::function<void(LinkHandle)> f) |
| FXL_LOCKS_EXCLUDED(lock_) { |
| TRACE_DURATION("audio", "LinkMatrix::ForEachSourceLink"); |
| std::lock_guard<std::mutex> lock(lock_); |
| |
| for (auto& link : SourceLinkSet(&object)) { |
| TRACE_DURATION("audio", "LinkMatrix::ForEachSourceLink.link"); |
| if (auto ptr = link.object.lock()) { |
| f(LinkHandle{.object = ptr, |
| .loudness_transform = link.loudness_transform, |
| .stream = link.stream, |
| .mixer = link.mixer, |
| .mix_domain = link.mix_domain}); |
| } |
| } |
| } |
| |
| size_t LinkMatrix::DestLinkCount(const AudioObject& object) { |
| std::lock_guard<std::mutex> lock(lock_); |
| |
| return DestLinkSet(&object).size(); |
| } |
| |
| size_t LinkMatrix::SourceLinkCount(const AudioObject& object) { |
| std::lock_guard<std::mutex> lock(lock_); |
| |
| return SourceLinkSet(&object).size(); |
| } |
| |
| void LinkMatrix::DestLinks(const AudioObject& object, std::vector<LinkHandle>* store) |
| FXL_LOCKS_EXCLUDED(lock_) { |
| TRACE_DURATION("audio", "LinkMatrix::DestLinks"); |
| std::lock_guard<std::mutex> lock(lock_); |
| |
| OnlyStrongLinks(DestLinkSet(&object), store); |
| } |
| |
| void LinkMatrix::SourceLinks(const AudioObject& object, std::vector<LinkHandle>* store) |
| FXL_LOCKS_EXCLUDED(lock_) { |
| TRACE_DURATION("audio", "LinkMatrix::SourceLinks"); |
| std::lock_guard<std::mutex> lock(lock_); |
| |
| OnlyStrongLinks(SourceLinkSet(&object), store); |
| } |
| |
| bool LinkMatrix::AreLinked(const AudioObject& source, AudioObject& dest) FXL_LOCKS_EXCLUDED(lock_) { |
| TRACE_DURATION("audio", "LinkMatrix::AreLinked"); |
| std::lock_guard<std::mutex> lock(lock_); |
| |
| auto dests = DestLinkSet(&source); |
| bool forward_linked = std::any_of(dests.begin(), dests.end(), |
| [&dest](auto candidate) { return candidate.key == &dest; }); |
| |
| auto sources = SourceLinkSet(&dest); |
| bool backward_linked = std::any_of(sources.begin(), sources.end(), [&source](auto candidate) { |
| return candidate.key == &source; |
| }); |
| FX_CHECK(forward_linked == backward_linked) |
| << "Routing inconsistency " << (forward_linked ? "forward" : "backward") << "-linked but not " |
| << (forward_linked ? "backward" : "forward") << "-linked"; |
| |
| return forward_linked; |
| } |
| |
| std::string UsageStrFromPair(const AudioObject* source, const AudioObject* dest) { |
| if (source && (source->is_audio_capturer() || source->is_audio_renderer())) { |
| return source->usage() ? source->usage()->ToString() : "Unknown source usage"; |
| } |
| return (dest && dest->usage()) ? dest->usage()->ToString() : "Unknown dest usage"; |
| } |
| |
| std::ostream& operator<<(std::ostream& out, const AudioObject* object) { |
| out << static_cast<const void*>(object) << " "; |
| if (object) { |
| if (object->is_audio_capturer() || object->is_audio_renderer()) { |
| out << "(" << object->usage()->ToString() << ")"; |
| } else { |
| out << "(" << object->type() << ")"; |
| } |
| } |
| return out; |
| } |
| |
| void LinkMatrix::DisplayCurrentRouting() { |
| std::lock_guard<std::mutex> lock(lock_); |
| |
| FX_LOGS(INFO) << "******************************************************************************"; |
| FX_LOGS(INFO) << "Per-source routing:"; |
| std::ostringstream out; |
| for (const auto& [source, dests_for_source] : dests_) { |
| out << " " << source << " -> {"; |
| for (const auto& dest : dests_for_source) { |
| FX_LOGS(INFO) << out.str(); |
| std::ostringstream().swap(out); |
| out << " " << dest.key << ","; |
| } |
| FX_LOGS(INFO) << out.str() << " }"; |
| std::ostringstream().swap(out); |
| } |
| FX_LOGS(INFO) << "Per-dest routing:"; |
| for (const auto& [dest, sources_for_dest] : sources_) { |
| out << " { "; |
| for (const auto& source : sources_for_dest) { |
| out << source.key << ","; |
| FX_LOGS(INFO) << out.str(); |
| std::ostringstream().swap(out); |
| out << " "; |
| } |
| FX_LOGS(INFO) << out.str() << " } -> " << dest; |
| std::ostringstream().swap(out); |
| } |
| FX_LOGS(INFO) << "******************************************************************************"; |
| } |
| |
| void LinkMatrix::OnlyStrongLinks(LinkSet& link_set, std::vector<LinkHandle>* store) { |
| TRACE_DURATION("audio", "LinkMatrix::OnlyStrongLinks"); |
| store->clear(); |
| for (const auto& link : link_set) { |
| if (auto ptr = link.object.lock()) { |
| store->push_back(LinkHandle{.object = ptr, |
| .loudness_transform = link.loudness_transform, |
| .stream = link.stream, |
| .mixer = link.mixer, |
| .mix_domain = link.mix_domain}); |
| } |
| } |
| } |
| |
| LinkMatrix::LinkSet& LinkMatrix::SourceLinkSet(const AudioObject* object) FXL_REQUIRE(lock_) { |
| if (sources_.find(object) == sources_.end()) { |
| sources_.insert({object, {}}); |
| } |
| return sources_[object]; |
| } |
| |
| LinkMatrix::LinkSet& LinkMatrix::DestLinkSet(const AudioObject* object) FXL_REQUIRE(lock_) { |
| if (dests_.find(object) == dests_.end()) { |
| dests_.insert({object, {}}); |
| } |
| |
| return dests_[object]; |
| } |
| |
| } // namespace media::audio |