blob: 0b5105975dcb6bf2262c22d462568919dedfdbd6 [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 <gmock/gmock.h>
#include "src/media/audio/audio_core/audio_device_manager.h"
#include "src/media/audio/audio_core/audio_driver.h"
#include "src/media/audio/audio_core/format.h"
#include "src/media/audio/audio_core/testing/fake_audio_driver.h"
#include "src/media/audio/audio_core/testing/fake_audio_renderer.h"
#include "src/media/audio/audio_core/testing/threading_model_fixture.h"
#include "src/media/audio/audio_core/usage_settings.h"
#include "src/media/audio/lib/logging/logging.h"
using testing::IsEmpty;
using testing::UnorderedElementsAreArray;
namespace media::audio {
namespace {
class FakeAudioObject : public AudioObject {
public:
static std::unique_ptr<FakeAudioObject> FakeRenderer(bool valid_format = true,
RenderUsage usage = RenderUsage::MEDIA) {
return std::make_unique<FakeAudioObject>(AudioObject::Type::AudioRenderer, valid_format,
StreamUsage::WithRenderUsage(std::move(usage)));
}
static std::unique_ptr<FakeAudioObject> FakeCapturer(
CaptureUsage usage = CaptureUsage::FOREGROUND) {
return std::make_unique<FakeAudioObject>(AudioObject::Type::AudioCapturer,
/*valid_format=*/true,
StreamUsage::WithCaptureUsage(std::move(usage)));
}
FakeAudioObject(AudioObject::Type object_type, bool valid_format, StreamUsage usage)
: AudioObject(object_type) {
if (valid_format) {
auto format_result = Format::Create({
.sample_format = fuchsia::media::AudioSampleFormat::FLOAT,
.channels = 1,
.frames_per_second = 48000,
});
FX_CHECK(format_result.is_ok());
format_ = {format_result.take_value()};
}
usage_ = std::move(usage);
}
std::optional<Format> format() const override { return format_; }
std::optional<StreamUsage> usage() const override { return {usage_}; }
void OnLinkAdded() override { total_links_formed_++; }
uint32_t total_links_formed() { return total_links_formed_; }
private:
std::optional<Format> format_;
StreamUsage usage_;
uint32_t total_links_formed_ = 0;
};
// TODO(39532): Remove; use a real output class with fake hardware.
class FakeAudioOutput : public AudioOutput {
public:
static std::shared_ptr<FakeAudioOutput> Create(ThreadingModel* threading_model,
DeviceRegistry* device_registry,
LinkMatrix* link_matrix) {
return std::make_shared<FakeAudioOutput>(threading_model, device_registry, link_matrix);
}
FakeAudioOutput(ThreadingModel* threading_model, DeviceRegistry* device_registry,
LinkMatrix* link_matrix)
: AudioOutput(threading_model, device_registry, link_matrix) {}
void ApplyGainLimits(fuchsia::media::AudioGainInfo* in_out_info, uint32_t set_flags) override {}
void OnWakeup() override {}
std::optional<MixStage::FrameSpan> StartMixJob(zx::time process_start) override {
return std::nullopt;
}
void FinishMixJob(const MixStage::FrameSpan& span, float* buffer) override {}
fit::result<std::shared_ptr<ReadableStream>, zx_status_t> InitializeDestLink(
const AudioObject& dest) override {
return fit::ok(nullptr);
}
};
static const DeviceConfig kConfigNoPolicy = DeviceConfig();
class RouteGraphTest : public testing::ThreadingModelFixture {
public:
RouteGraphTest() : RouteGraphTest(kConfigNoPolicy) {}
RouteGraphTest(const DeviceConfig& device_config)
: ThreadingModelFixture(
ProcessConfig(VolumeCurve::DefaultForMinGain(VolumeCurve::kDefaultGainForMinVolume),
device_config, ThermalConfig({}))) {}
struct FakeOutputAndDriver {
std::shared_ptr<FakeAudioOutput> output;
std::unique_ptr<testing::FakeAudioDriverV1> fake_driver;
};
FakeOutputAndDriver OutputWithDeviceId(const audio_stream_unique_id_t& device_id) {
auto output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
zx::channel c1, c2;
ZX_ASSERT(ZX_OK == zx::channel::create(0, &c1, &c2));
auto fake_driver = std::make_unique<testing::FakeAudioDriverV1>(
std::move(c1), threading_model().FidlDomain().dispatcher());
fake_driver->set_stream_unique_id(device_id);
ZX_ASSERT(ZX_OK == output->driver()->Init(std::move(c2)));
fake_driver->Start();
output->driver()->GetDriverInfo();
RunLoopUntilIdle();
return {output, std::move(fake_driver)};
}
std::vector<AudioObject*> SourceLinks(const AudioObject& object) {
std::vector<LinkMatrix::LinkHandle> handles;
context().link_matrix().SourceLinks(object, &handles);
std::vector<AudioObject*> links;
std::transform(handles.begin(), handles.end(), std::back_inserter(links),
[](auto handle) { return handle.object.get(); });
return links;
}
std::vector<AudioObject*> DestLinks(const AudioObject& object) {
std::vector<LinkMatrix::LinkHandle> handles;
context().link_matrix().DestLinks(object, &handles);
std::vector<AudioObject*> links;
std::transform(handles.begin(), handles.end(), std::back_inserter(links),
[](auto handle) { return handle.object.get(); });
return links;
}
RouteGraph& under_test_ = context().route_graph();
};
TEST_F(RouteGraphTest, RenderersAreUnlinkedWhenHaveNoRoutingProfile) {
auto renderer = FakeAudioObject::FakeRenderer();
auto* renderer_raw = renderer.get();
under_test_.AddRenderer(std::move(renderer));
EXPECT_THAT(DestLinks(*renderer_raw), IsEmpty());
}
TEST_F(RouteGraphTest, RenderersRouteToLastPluggedOutput) {
auto renderer = FakeAudioObject::FakeRenderer();
auto* renderer_raw = renderer.get();
under_test_.AddRenderer(std::move(renderer));
under_test_.SetRendererRoutingProfile(
*renderer_raw, {.routable = true, .usage = StreamUsage::WithRenderUsage(RenderUsage::MEDIA)});
auto first_output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
under_test_.AddDevice(first_output.get());
EXPECT_THAT(DestLinks(*renderer_raw), UnorderedElementsAreArray({first_output.get()}));
auto later_output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
under_test_.AddDevice(later_output.get());
EXPECT_THAT(DestLinks(*renderer_raw), UnorderedElementsAreArray({later_output.get()}));
}
TEST_F(RouteGraphTest, RenderersFallbackWhenOutputRemoved) {
auto renderer = FakeAudioObject::FakeRenderer();
auto* renderer_raw = renderer.get();
under_test_.AddRenderer(std::move(renderer));
under_test_.SetRendererRoutingProfile(
*renderer_raw, {.routable = true, .usage = StreamUsage::WithRenderUsage(RenderUsage::MEDIA)});
auto first_output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
auto later_output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
under_test_.AddDevice(first_output.get());
under_test_.AddDevice(later_output.get());
under_test_.RemoveDevice(later_output.get());
EXPECT_THAT(DestLinks(*renderer_raw),
UnorderedElementsAreArray(std::vector<AudioObject*>{first_output.get()}));
under_test_.RemoveDevice(first_output.get());
EXPECT_THAT(DestLinks(*renderer_raw),
UnorderedElementsAreArray(std::vector<AudioObject*>{context().throttle_output()}));
}
TEST_F(RouteGraphTest, RemovingNonLastOutputDoesNotRerouteRenderers) {
auto renderer = FakeAudioObject::FakeRenderer();
auto* renderer_raw = renderer.get();
under_test_.AddRenderer(std::move(renderer));
under_test_.SetRendererRoutingProfile(
*renderer_raw, {.routable = true, .usage = StreamUsage::WithRenderUsage(RenderUsage::MEDIA)});
auto first_output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
auto second_output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
auto last_output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
under_test_.AddDevice(first_output.get());
under_test_.AddDevice(second_output.get());
under_test_.AddDevice(last_output.get());
under_test_.RemoveDevice(second_output.get());
EXPECT_THAT(DestLinks(*renderer_raw), UnorderedElementsAreArray({last_output.get()}));
under_test_.RemoveDevice(first_output.get());
EXPECT_THAT(DestLinks(*renderer_raw), UnorderedElementsAreArray({last_output.get()}));
}
TEST_F(RouteGraphTest, RenderersPickUpLastPluggedOutputWhenRoutable) {
auto first_output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
under_test_.AddDevice(first_output.get());
auto renderer = FakeAudioObject::FakeRenderer();
auto* renderer_raw = renderer.get();
under_test_.AddRenderer(std::move(renderer));
EXPECT_THAT(DestLinks(*renderer_raw), IsEmpty());
under_test_.SetRendererRoutingProfile(
*renderer_raw, {.routable = true, .usage = StreamUsage::WithRenderUsage(RenderUsage::MEDIA)});
EXPECT_THAT(DestLinks(*renderer_raw), UnorderedElementsAreArray({first_output.get()}));
}
TEST_F(RouteGraphTest, RenderersAreRemoved) {
auto renderer = FakeAudioObject::FakeRenderer();
auto* renderer_raw = renderer.get();
under_test_.AddRenderer(std::move(renderer));
under_test_.SetRendererRoutingProfile(
*renderer_raw, {.routable = true, .usage = StreamUsage::WithRenderUsage(RenderUsage::MEDIA)});
// Now the renderer should have 3 references:
// 1. Ours in this test.
// 2. The RouteGraph's.
// 3. The ThrottleOutput's (because they are linked).
EXPECT_THAT(DestLinks(*renderer_raw), UnorderedElementsAreArray({context().throttle_output()}));
under_test_.RemoveRenderer(*renderer_raw);
auto output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
under_test_.AddDevice(output.get());
EXPECT_THAT(SourceLinks(*output), IsEmpty());
}
TEST_F(RouteGraphTest, CapturersAreUnlinkedWhenHaveNoRoutingProfile) {
auto capturer = FakeAudioObject::FakeCapturer();
auto* capturer_raw = capturer.get();
under_test_.AddCapturer(std::move(capturer));
EXPECT_THAT(SourceLinks(*capturer_raw), IsEmpty());
}
TEST_F(RouteGraphTest, CapturersRouteToLastPluggedInput) {
auto capturer = FakeAudioObject::FakeCapturer();
auto* capturer_raw = capturer.get();
under_test_.AddCapturer(std::move(capturer));
under_test_.SetCapturerRoutingProfile(
*capturer_raw,
{.routable = true, .usage = StreamUsage::WithCaptureUsage(CaptureUsage::SYSTEM_AGENT)});
auto first_input = AudioInput::Create(zx::channel(), &threading_model(),
&context().device_manager(), &context().link_matrix());
under_test_.AddDevice(first_input.get());
EXPECT_THAT(SourceLinks(*capturer_raw), UnorderedElementsAreArray({first_input.get()}));
auto later_input = AudioInput::Create(zx::channel(), &threading_model(),
&context().device_manager(), &context().link_matrix());
under_test_.AddDevice(later_input.get());
EXPECT_THAT(SourceLinks(*capturer_raw), UnorderedElementsAreArray({later_input.get()}));
}
TEST_F(RouteGraphTest, CapturersFallbackWhenInputRemoved) {
auto capturer = FakeAudioObject::FakeCapturer();
auto* capturer_raw = capturer.get();
under_test_.AddCapturer(std::move(capturer));
under_test_.SetCapturerRoutingProfile(
*capturer_raw,
{.routable = true, .usage = StreamUsage::WithCaptureUsage(CaptureUsage::SYSTEM_AGENT)});
auto first_input = AudioInput::Create(zx::channel(), &threading_model(),
&context().device_manager(), &context().link_matrix());
auto later_input = AudioInput::Create(zx::channel(), &threading_model(),
&context().device_manager(), &context().link_matrix());
under_test_.AddDevice(first_input.get());
under_test_.AddDevice(later_input.get());
under_test_.RemoveDevice(later_input.get());
EXPECT_THAT(SourceLinks(*capturer_raw), UnorderedElementsAreArray({first_input.get()}));
under_test_.RemoveDevice(first_input.get());
EXPECT_THAT(SourceLinks(*capturer_raw), IsEmpty());
}
TEST_F(RouteGraphTest, RemovingNonLastInputDoesNotRerouteCapturers) {
auto capturer = FakeAudioObject::FakeCapturer();
auto* capturer_raw = capturer.get();
under_test_.AddCapturer(std::move(capturer));
under_test_.SetCapturerRoutingProfile(
*capturer_raw,
{.routable = true, .usage = StreamUsage::WithCaptureUsage(CaptureUsage::SYSTEM_AGENT)});
auto first_input = AudioInput::Create(zx::channel(), &threading_model(),
&context().device_manager(), &context().link_matrix());
auto second_input = AudioInput::Create(zx::channel(), &threading_model(),
&context().device_manager(), &context().link_matrix());
auto last_input = AudioInput::Create(zx::channel(), &threading_model(),
&context().device_manager(), &context().link_matrix());
under_test_.AddDevice(first_input.get());
under_test_.AddDevice(second_input.get());
under_test_.AddDevice(last_input.get());
under_test_.RemoveDevice(first_input.get());
EXPECT_THAT(SourceLinks(*capturer_raw), UnorderedElementsAreArray({last_input.get()}));
under_test_.RemoveDevice(second_input.get());
EXPECT_THAT(SourceLinks(*capturer_raw), UnorderedElementsAreArray({last_input.get()}));
}
TEST_F(RouteGraphTest, CapturersPickUpLastPluggedInputWhenRoutable) {
auto first_input = AudioInput::Create(zx::channel(), &threading_model(),
&context().device_manager(), &context().link_matrix());
under_test_.AddDevice(first_input.get());
auto later_input = AudioInput::Create(zx::channel(), &threading_model(),
&context().device_manager(), &context().link_matrix());
under_test_.AddDevice(later_input.get());
auto capturer = FakeAudioObject::FakeCapturer();
auto* capturer_raw = capturer.get();
under_test_.AddCapturer(std::move(capturer));
EXPECT_THAT(SourceLinks(*capturer_raw), IsEmpty());
under_test_.SetCapturerRoutingProfile(
*capturer_raw,
{.routable = true, .usage = StreamUsage::WithCaptureUsage(CaptureUsage::SYSTEM_AGENT)});
EXPECT_THAT(SourceLinks(*capturer_raw), UnorderedElementsAreArray({later_input.get()}));
}
TEST_F(RouteGraphTest, CapturersAreRemoved) {
auto capturer = FakeAudioObject::FakeCapturer();
auto* capturer_raw = capturer.get();
under_test_.AddCapturer(std::move(capturer));
under_test_.SetCapturerRoutingProfile(
*capturer_raw,
{.routable = true, .usage = StreamUsage::WithCaptureUsage(CaptureUsage::SYSTEM_AGENT)});
auto input = AudioInput::Create(zx::channel(), &threading_model(), &context().device_manager(),
&context().link_matrix());
under_test_.AddDevice(input.get());
EXPECT_THAT(DestLinks(*input), UnorderedElementsAreArray({capturer_raw}));
under_test_.RemoveCapturer(*capturer_raw);
EXPECT_THAT(DestLinks(*input), IsEmpty());
}
TEST_F(RouteGraphTest, LoopbackCapturersRouteToLastPluggedOutput) {
auto capturer = FakeAudioObject::FakeCapturer();
auto* capturer_raw = capturer.get();
under_test_.AddCapturer(std::move(capturer));
under_test_.SetCapturerRoutingProfile(
*capturer_raw,
{.routable = true, .usage = StreamUsage::WithCaptureUsage(CaptureUsage::LOOPBACK)});
auto first_output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
under_test_.AddDevice(first_output.get());
EXPECT_THAT(SourceLinks(*capturer_raw), UnorderedElementsAreArray({first_output.get()}));
auto later_output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
under_test_.AddDevice(later_output.get());
EXPECT_THAT(SourceLinks(*capturer_raw), UnorderedElementsAreArray({later_output.get()}));
}
TEST_F(RouteGraphTest, LoopbackCapturersFallbackWhenOutputRemoved) {
auto capturer = FakeAudioObject::FakeCapturer();
auto* capturer_raw = capturer.get();
under_test_.AddCapturer(std::move(capturer));
under_test_.SetCapturerRoutingProfile(
*capturer_raw,
{.routable = true, .usage = StreamUsage::WithCaptureUsage(CaptureUsage::LOOPBACK)});
auto first_output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
auto later_output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
under_test_.AddDevice(first_output.get());
under_test_.AddDevice(later_output.get());
under_test_.RemoveDevice(later_output.get());
EXPECT_THAT(SourceLinks(*capturer_raw), UnorderedElementsAreArray({first_output.get()}));
under_test_.RemoveDevice(first_output.get());
EXPECT_THAT(SourceLinks(*capturer_raw), IsEmpty());
}
TEST_F(RouteGraphTest, RemovingNonLastOutputDoesNotRerouteLoopbackCapturers) {
auto capturer = FakeAudioObject::FakeCapturer();
auto* capturer_raw = capturer.get();
under_test_.AddCapturer(std::move(capturer));
under_test_.SetCapturerRoutingProfile(
*capturer_raw,
{.routable = true, .usage = StreamUsage::WithCaptureUsage(CaptureUsage::LOOPBACK)});
auto first_output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
auto second_output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
auto last_output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
under_test_.AddDevice(first_output.get());
under_test_.AddDevice(second_output.get());
under_test_.AddDevice(last_output.get());
under_test_.RemoveDevice(second_output.get());
EXPECT_THAT(SourceLinks(*capturer_raw), UnorderedElementsAreArray({last_output.get()}));
under_test_.RemoveDevice(first_output.get());
EXPECT_THAT(SourceLinks(*capturer_raw), UnorderedElementsAreArray({last_output.get()}));
}
TEST_F(RouteGraphTest, LoopbackCapturersPickUpLastPluggedOutputWhenRoutable) {
auto first_output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
under_test_.AddDevice(first_output.get());
auto later_output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
under_test_.AddDevice(later_output.get());
auto capturer = FakeAudioObject::FakeCapturer();
auto* capturer_raw = capturer.get();
under_test_.AddCapturer(std::move(capturer));
EXPECT_THAT(SourceLinks(*capturer_raw), IsEmpty());
under_test_.SetCapturerRoutingProfile(
*capturer_raw,
{.routable = true, .usage = StreamUsage::WithCaptureUsage(CaptureUsage::LOOPBACK)});
EXPECT_THAT(SourceLinks(*capturer_raw), UnorderedElementsAreArray({later_output.get()}));
}
TEST_F(RouteGraphTest, LoopbackCapturersAreRemoved) {
auto capturer = FakeAudioObject::FakeCapturer();
auto* capturer_raw = capturer.get();
under_test_.AddCapturer(std::move(capturer));
under_test_.SetCapturerRoutingProfile(
*capturer_raw,
{.routable = true, .usage = StreamUsage::WithCaptureUsage(CaptureUsage::LOOPBACK)});
auto output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
under_test_.AddDevice(output.get());
EXPECT_THAT(DestLinks(*output), UnorderedElementsAreArray({capturer_raw}));
under_test_.RemoveCapturer(*capturer_raw);
EXPECT_THAT(DestLinks(*output), IsEmpty());
}
TEST_F(RouteGraphTest, OutputRouteCategoriesDoNotAffectEachOther) {
auto output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
under_test_.AddDevice(output.get());
auto capturer = FakeAudioObject::FakeCapturer();
auto* capturer_raw = capturer.get();
under_test_.AddCapturer(std::move(capturer));
EXPECT_THAT(SourceLinks(*capturer_raw), IsEmpty());
auto renderer = FakeAudioObject::FakeRenderer();
auto* renderer_raw = renderer.get();
under_test_.AddRenderer(std::move(renderer));
under_test_.SetRendererRoutingProfile(
*renderer_raw, {.routable = true, .usage = StreamUsage::WithRenderUsage(RenderUsage::MEDIA)});
EXPECT_THAT(SourceLinks(*capturer_raw), IsEmpty());
EXPECT_THAT(DestLinks(*renderer_raw),
UnorderedElementsAreArray(std::vector<AudioObject*>{output.get()}));
under_test_.SetCapturerRoutingProfile(
*capturer_raw,
{.routable = true, .usage = StreamUsage::WithCaptureUsage(CaptureUsage::LOOPBACK)});
EXPECT_THAT(SourceLinks(*capturer_raw), UnorderedElementsAreArray({output.get()}));
EXPECT_THAT(DestLinks(*renderer_raw),
UnorderedElementsAreArray(std::vector<AudioObject*>{output.get()}));
}
TEST_F(RouteGraphTest, InputRouteCategoriesDoNotAffectOutputs) {
auto output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
under_test_.AddDevice(output.get());
auto first_input = AudioInput::Create(zx::channel(), &threading_model(),
&context().device_manager(), &context().link_matrix());
under_test_.AddDevice(first_input.get());
auto capturer = FakeAudioObject::FakeCapturer();
auto* capturer_raw = capturer.get();
under_test_.AddCapturer(std::move(capturer));
under_test_.SetCapturerRoutingProfile(
*capturer_raw,
{.routable = true, .usage = StreamUsage::WithCaptureUsage(CaptureUsage::SYSTEM_AGENT)});
EXPECT_THAT(SourceLinks(*capturer_raw), UnorderedElementsAreArray({first_input.get()}));
auto renderer = FakeAudioObject::FakeRenderer();
auto* renderer_raw = renderer.get();
under_test_.AddRenderer(std::move(renderer));
under_test_.SetRendererRoutingProfile(
*renderer_raw, {.routable = true, .usage = StreamUsage::WithRenderUsage(RenderUsage::MEDIA)});
EXPECT_THAT(DestLinks(*renderer_raw),
UnorderedElementsAreArray(std::vector<AudioObject*>{output.get()}));
EXPECT_THAT(SourceLinks(*capturer_raw), UnorderedElementsAreArray({first_input.get()}));
}
TEST_F(RouteGraphTest, DoesNotRouteUnroutableRenderer) {
auto output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
under_test_.AddDevice(output.get());
auto renderer = FakeAudioObject::FakeRenderer();
auto* renderer_raw = renderer.get();
under_test_.AddRenderer(std::move(renderer));
EXPECT_THAT(DestLinks(*renderer_raw), IsEmpty());
under_test_.SetRendererRoutingProfile(
*renderer_raw,
{.routable = false, .usage = StreamUsage::WithRenderUsage(RenderUsage::MEDIA)});
auto second_output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
under_test_.AddDevice(second_output.get());
EXPECT_THAT(DestLinks(*renderer_raw), IsEmpty());
}
TEST_F(RouteGraphTest, DoesNotRouteUnroutableCapturer) {
auto input = AudioInput::Create(zx::channel(), &threading_model(), &context().device_manager(),
&context().link_matrix());
under_test_.AddDevice(input.get());
auto capturer = FakeAudioObject::FakeCapturer();
auto* capturer_raw = capturer.get();
under_test_.AddCapturer(std::move(capturer));
EXPECT_THAT(SourceLinks(*capturer_raw), IsEmpty());
under_test_.SetCapturerRoutingProfile(
*capturer_raw,
{.routable = false, .usage = StreamUsage::WithCaptureUsage(CaptureUsage::SYSTEM_AGENT)});
auto second_input = AudioInput::Create(zx::channel(), &threading_model(),
&context().device_manager(), &context().link_matrix());
under_test_.AddDevice(second_input.get());
EXPECT_THAT(SourceLinks(*capturer_raw), IsEmpty());
}
TEST_F(RouteGraphTest, DoesNotRouteUnroutableLoopbackCapturer) {
auto output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
under_test_.AddDevice(output.get());
auto loopback_capturer = FakeAudioObject::FakeCapturer();
auto* loopback_capturer_raw = loopback_capturer.get();
under_test_.AddCapturer(std::move(loopback_capturer));
EXPECT_THAT(SourceLinks(*loopback_capturer_raw), IsEmpty());
under_test_.SetCapturerRoutingProfile(
*loopback_capturer_raw,
{.routable = false, .usage = StreamUsage::WithCaptureUsage(CaptureUsage::LOOPBACK)});
auto second_output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
under_test_.AddDevice(second_output.get());
EXPECT_THAT(SourceLinks(*loopback_capturer_raw), IsEmpty());
}
TEST_F(RouteGraphTest, AcceptsUnroutableRendererWithInvalidFormat) {
auto renderer = FakeAudioObject::FakeRenderer(/*valid_format=*/false);
auto* renderer_raw = renderer.get();
under_test_.AddRenderer(std::move(renderer));
under_test_.SetRendererRoutingProfile(
*renderer_raw,
{.routable = false, .usage = StreamUsage::WithRenderUsage(RenderUsage::MEDIA)});
// Passes by not crashing.
}
TEST_F(RouteGraphTest, UnroutesNewlyUnRoutableRenderer) {
auto output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
under_test_.AddDevice(output.get());
auto renderer = FakeAudioObject::FakeRenderer();
auto* renderer_raw = renderer.get();
under_test_.AddRenderer(std::move(renderer));
EXPECT_THAT(DestLinks(*renderer_raw), IsEmpty());
under_test_.SetRendererRoutingProfile(
*renderer_raw, {.routable = true, .usage = StreamUsage::WithRenderUsage(RenderUsage::MEDIA)});
under_test_.SetRendererRoutingProfile(
*renderer_raw,
{.routable = false, .usage = StreamUsage::WithRenderUsage(RenderUsage::MEDIA)});
EXPECT_THAT(DestLinks(*renderer_raw), IsEmpty());
}
TEST_F(RouteGraphTest, UnroutesNewlyUnRoutableCapturer) {
auto input = AudioInput::Create(zx::channel(), &threading_model(), &context().device_manager(),
&context().link_matrix());
under_test_.AddDevice(input.get());
auto capturer = FakeAudioObject::FakeCapturer();
auto* capturer_raw = capturer.get();
under_test_.AddCapturer(std::move(capturer));
EXPECT_THAT(SourceLinks(*capturer_raw), IsEmpty());
under_test_.SetCapturerRoutingProfile(
*capturer_raw,
{.routable = true, .usage = StreamUsage::WithCaptureUsage(CaptureUsage::SYSTEM_AGENT)});
under_test_.SetCapturerRoutingProfile(
*capturer_raw,
{.routable = false, .usage = StreamUsage::WithCaptureUsage(CaptureUsage::SYSTEM_AGENT)});
EXPECT_THAT(SourceLinks(*capturer_raw), IsEmpty());
}
TEST_F(RouteGraphTest, UnroutesNewlyUnRoutableLoopbackCapturer) {
auto output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
under_test_.AddDevice(output.get());
auto loopback_capturer = FakeAudioObject::FakeCapturer();
auto* loopback_capturer_raw = loopback_capturer.get();
under_test_.AddCapturer(std::move(loopback_capturer));
EXPECT_THAT(SourceLinks(*loopback_capturer_raw), IsEmpty());
under_test_.SetCapturerRoutingProfile(
*loopback_capturer_raw,
{.routable = true, .usage = StreamUsage::WithCaptureUsage(CaptureUsage::LOOPBACK)});
under_test_.SetCapturerRoutingProfile(
*loopback_capturer_raw,
{.routable = false, .usage = StreamUsage::WithCaptureUsage(CaptureUsage::LOOPBACK)});
EXPECT_THAT(SourceLinks(*loopback_capturer_raw), IsEmpty());
}
const audio_stream_unique_id_t kSupportsAllDeviceId = audio_stream_unique_id_t{.data = {0x33}};
const audio_stream_unique_id_t kUnconfiguredDeviceId = audio_stream_unique_id_t{.data = {0x45}};
static const DeviceConfig kConfigWithMediaExternalRoutingPolicy = DeviceConfig(
/*profiles=*/{{{kSupportsAllDeviceId},
DeviceConfig::OutputDeviceProfile(
/*eligible_for_loopback=*/true,
/*output_usage_support_set=*/
StreamUsageSetFromRenderUsages(kFidlRenderUsages))}},
/*default=*/
{DeviceConfig::OutputDeviceProfile(
/*eligible_for_loopback=*/true,
/*output_usage_support_set=*/{StreamUsage::WithRenderUsage(RenderUsage::MEDIA)})},
{}, {});
class RouteGraphWithMediaExternalPolicyTest : public RouteGraphTest {
public:
RouteGraphWithMediaExternalPolicyTest() : RouteGraphTest(kConfigWithMediaExternalRoutingPolicy) {}
};
TEST_F(RouteGraphWithMediaExternalPolicyTest, MediaRoutesToLastPluggedSupportedDevice) {
auto output_and_driver = OutputWithDeviceId(kSupportsAllDeviceId);
auto output = output_and_driver.output.get();
under_test_.AddDevice(output);
auto renderer = FakeAudioObject::FakeRenderer();
auto* renderer_raw = renderer.get();
under_test_.AddRenderer(std::move(renderer));
under_test_.SetRendererRoutingProfile(
*renderer_raw, {.routable = true, .usage = StreamUsage::WithRenderUsage(RenderUsage::MEDIA)});
EXPECT_THAT(DestLinks(*renderer_raw), UnorderedElementsAreArray({output}));
auto unconfigured_output_and_driver = OutputWithDeviceId(kUnconfiguredDeviceId);
auto unconfigured_output = unconfigured_output_and_driver.output.get();
under_test_.AddDevice(unconfigured_output);
EXPECT_THAT(DestLinks(*renderer_raw), UnorderedElementsAreArray({unconfigured_output}));
}
TEST_F(RouteGraphWithMediaExternalPolicyTest, InterruptionDoesNotRouteToUnsupportedDevice) {
auto output_and_driver = OutputWithDeviceId(kSupportsAllDeviceId);
auto output = output_and_driver.output.get();
under_test_.AddDevice(output);
auto renderer = FakeAudioObject::FakeRenderer();
auto* renderer_raw = renderer.get();
under_test_.AddRenderer(std::move(renderer));
under_test_.SetRendererRoutingProfile(
*renderer_raw,
{.routable = true, .usage = StreamUsage::WithRenderUsage(RenderUsage::INTERRUPTION)});
EXPECT_THAT(DestLinks(*renderer_raw), UnorderedElementsAreArray({output}));
auto unconfigured_output_and_driver = OutputWithDeviceId(kUnconfiguredDeviceId);
auto unconfigured_output = unconfigured_output_and_driver.output.get();
under_test_.AddDevice(unconfigured_output);
EXPECT_THAT(DestLinks(*renderer_raw), UnorderedElementsAreArray({output}));
}
const audio_stream_unique_id_t kSupportsLoopbackDeviceId = audio_stream_unique_id_t{.data = {0x7a}};
static const DeviceConfig kConfigWithExternNonLoopbackDevicePolicy = DeviceConfig(
/*profiles=*/{{{kSupportsAllDeviceId},
DeviceConfig::OutputDeviceProfile(
/*eligible_for_loopback=*/true,
/*output_usage_support_set=*/
StreamUsageSetFromRenderUsages(kFidlRenderUsages))},
{{kSupportsLoopbackDeviceId},
DeviceConfig::OutputDeviceProfile(
/*eligible_for_loopback=*/true,
/*output_usage_support_set=*/
{StreamUsage::WithRenderUsage(RenderUsage::BACKGROUND)})}},
/*default=*/
{DeviceConfig::OutputDeviceProfile(
/*eligible_for_loopback=*/false,
/*output_usage_support_set=*/
{StreamUsageSetFromRenderUsages(kFidlRenderUsages)})},
{}, {});
class RouteGraphWithExternalNonLoopbackDeviceTest : public RouteGraphTest {
public:
RouteGraphWithExternalNonLoopbackDeviceTest()
: RouteGraphTest(kConfigWithExternNonLoopbackDevicePolicy) {}
};
TEST_F(RouteGraphWithExternalNonLoopbackDeviceTest, LoopbackRoutesToLastPluggedSupported) {
auto output_and_driver = OutputWithDeviceId(kSupportsAllDeviceId);
auto output = output_and_driver.output.get();
under_test_.AddDevice(output);
auto capturer = FakeAudioObject::FakeCapturer();
auto* capturer_raw = capturer.get();
under_test_.AddCapturer(std::move(capturer));
under_test_.SetCapturerRoutingProfile(
*capturer_raw,
{.routable = true, .usage = StreamUsage::WithCaptureUsage(CaptureUsage::LOOPBACK)});
EXPECT_THAT(SourceLinks(*capturer_raw), UnorderedElementsAreArray({output}));
auto second_output_and_driver = OutputWithDeviceId(kSupportsLoopbackDeviceId);
auto second_output = second_output_and_driver.output.get();
under_test_.AddDevice(second_output);
EXPECT_THAT(SourceLinks(*capturer_raw), UnorderedElementsAreArray({second_output}));
}
TEST_F(RouteGraphWithExternalNonLoopbackDeviceTest, LoopbackDoesNotRouteToUnsupportedDevice) {
auto output_and_driver = OutputWithDeviceId(kSupportsAllDeviceId);
auto output = output_and_driver.output.get();
under_test_.AddDevice(output);
auto capturer = FakeAudioObject::FakeCapturer();
auto* capturer_raw = capturer.get();
under_test_.AddCapturer(std::move(capturer));
under_test_.SetCapturerRoutingProfile(
*capturer_raw,
{.routable = true, .usage = StreamUsage::WithCaptureUsage(CaptureUsage::LOOPBACK)});
EXPECT_THAT(SourceLinks(*capturer_raw), UnorderedElementsAreArray({output}));
auto second_output_and_driver = OutputWithDeviceId(kUnconfiguredDeviceId);
auto second_output = second_output_and_driver.output.get();
under_test_.AddDevice(second_output);
EXPECT_THAT(SourceLinks(*capturer_raw), UnorderedElementsAreArray({output}));
}
TEST_F(RouteGraphTest, DoesNotUnlinkRendererNotInGraph) {
auto renderer = std::shared_ptr<FakeAudioObject>(FakeAudioObject::FakeRenderer().release());
auto output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
context().link_matrix().LinkObjects(renderer, output, std::make_shared<NoOpLoudnessTransform>());
EXPECT_THAT(DestLinks(*renderer), UnorderedElementsAreArray({output.get()}));
under_test_.RemoveRenderer(*renderer);
EXPECT_THAT(DestLinks(*renderer), UnorderedElementsAreArray({output.get()}));
}
TEST_F(RouteGraphTest, DoesNotUnlinkCapturerNotInGraph) {
auto capturer = std::shared_ptr<FakeAudioObject>(FakeAudioObject::FakeCapturer().release());
auto input = AudioInput::Create(zx::channel(), &threading_model(), &context().device_manager(),
&context().link_matrix());
context().link_matrix().LinkObjects(input, capturer, std::make_shared<NoOpLoudnessTransform>());
EXPECT_THAT(SourceLinks(*capturer), UnorderedElementsAreArray({input.get()}));
under_test_.RemoveCapturer(*capturer);
EXPECT_THAT(SourceLinks(*capturer), UnorderedElementsAreArray({input.get()}));
}
TEST_F(RouteGraphTest, DoesNotUnlinkLoopbackCapturerNotInGraph) {
auto loopback_capturer =
std::shared_ptr<FakeAudioObject>(FakeAudioObject::FakeCapturer().release());
auto output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
context().link_matrix().LinkObjects(output, loopback_capturer,
std::make_shared<NoOpLoudnessTransform>());
EXPECT_THAT(SourceLinks(*loopback_capturer), UnorderedElementsAreArray({output.get()}));
under_test_.RemoveCapturer(*loopback_capturer);
EXPECT_THAT(SourceLinks(*loopback_capturer), UnorderedElementsAreArray({output.get()}));
}
TEST_F(RouteGraphTest, DoesNotRelinkRendererIfRouteDoesNotChange) {
auto renderer_owned = FakeAudioObject::FakeRenderer();
auto renderer = renderer_owned.get();
auto output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
const RoutingProfile kRoutableProfile{
.routable = true,
.usage = StreamUsage::WithRenderUsage(RenderUsage::MEDIA),
};
// Add and confirm an initial link has been made.
under_test_.AddDevice(output.get());
under_test_.AddRenderer(std::move(renderer_owned));
under_test_.SetRendererRoutingProfile(*renderer, kRoutableProfile);
EXPECT_THAT(DestLinks(*renderer), UnorderedElementsAreArray({output.get()}));
EXPECT_EQ(1u, renderer->total_links_formed());
// Now set the same profile. We should see no change in our routes or link counts.
under_test_.SetRendererRoutingProfile(*renderer, kRoutableProfile);
EXPECT_THAT(DestLinks(*renderer), UnorderedElementsAreArray({output.get()}));
EXPECT_EQ(1u, renderer->total_links_formed());
}
TEST_F(RouteGraphTest, DoesNotRelinkCapturerIfRouteDoesNotChange) {
auto capturer_owned = FakeAudioObject::FakeCapturer();
auto capturer = capturer_owned.get();
auto input = AudioInput::Create(zx::channel(), &threading_model(), &context().device_manager(),
&context().link_matrix());
const RoutingProfile kRoutableProfile{
.routable = true,
.usage = StreamUsage::WithCaptureUsage(CaptureUsage::BACKGROUND),
};
// Add and confirm an initial link has been made.
under_test_.AddDevice(input.get());
under_test_.AddCapturer(std::move(capturer_owned));
under_test_.SetCapturerRoutingProfile(*capturer, kRoutableProfile);
EXPECT_THAT(SourceLinks(*capturer), UnorderedElementsAreArray({input.get()}));
EXPECT_EQ(1u, capturer->total_links_formed());
// Now set the same profile. We should see no change in our routes or link counts.
under_test_.SetCapturerRoutingProfile(*capturer, kRoutableProfile);
EXPECT_THAT(SourceLinks(*capturer), UnorderedElementsAreArray({input.get()}));
EXPECT_EQ(1u, capturer->total_links_formed());
}
TEST_F(RouteGraphTest, DoesNotRelinkCapturerIfUnroutedDeviceIsAdded) {
auto capturer_owned = FakeAudioObject::FakeCapturer();
auto capturer = capturer_owned.get();
auto input = AudioInput::Create(zx::channel(), &threading_model(), &context().device_manager(),
&context().link_matrix());
const RoutingProfile kRoutableProfile{
.routable = true,
.usage = StreamUsage::WithCaptureUsage(CaptureUsage::BACKGROUND),
};
// Add and confirm an initial link has been made.
under_test_.AddDevice(input.get());
under_test_.AddCapturer(std::move(capturer_owned));
under_test_.SetCapturerRoutingProfile(*capturer, kRoutableProfile);
EXPECT_THAT(SourceLinks(*capturer), UnorderedElementsAreArray({input.get()}));
EXPECT_EQ(1u, capturer->total_links_formed());
// Now add an unrelated device. We should see no link activity on our capturer.
auto output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
under_test_.AddDevice(output.get());
EXPECT_THAT(SourceLinks(*capturer), UnorderedElementsAreArray({input.get()}));
EXPECT_EQ(1u, capturer->total_links_formed());
}
TEST_F(RouteGraphTest, DoesNotRelinkRendererIfUnroutedDeviceIsAdded) {
auto renderer_owned = FakeAudioObject::FakeRenderer();
auto renderer = renderer_owned.get();
auto output = FakeAudioOutput::Create(&threading_model(), &context().device_manager(),
&context().link_matrix());
const RoutingProfile kRoutableProfile{
.routable = true,
.usage = StreamUsage::WithRenderUsage(RenderUsage::MEDIA),
};
// Add and confirm an initial link has been made.
under_test_.AddDevice(output.get());
under_test_.AddRenderer(std::move(renderer_owned));
under_test_.SetRendererRoutingProfile(*renderer, kRoutableProfile);
EXPECT_THAT(DestLinks(*renderer), UnorderedElementsAreArray({output.get()}));
EXPECT_EQ(1u, renderer->total_links_formed());
// Now set the same profile. We should see no change in our routes or link counts.
auto input = AudioInput::Create(zx::channel(), &threading_model(), &context().device_manager(),
&context().link_matrix());
under_test_.AddDevice(input.get());
EXPECT_THAT(DestLinks(*renderer), UnorderedElementsAreArray({output.get()}));
EXPECT_EQ(1u, renderer->total_links_formed());
}
} // namespace
} // namespace media::audio