blob: c20c6f48b826981795bcb7cd9cd516ca200d0d71 [file] [log] [blame]
// Copyright 2022 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/services/device_registry/device.h"
#include <fidl/fuchsia.audio.device/cpp/common_types.h>
#include <fidl/fuchsia.audio/cpp/common_types.h>
#include <fidl/fuchsia.hardware.audio.signalprocessing/cpp/common_types.h>
#include <fidl/fuchsia.hardware.audio.signalprocessing/cpp/natural_types.h>
#include <fidl/fuchsia.hardware.audio/cpp/fidl.h>
#include <lib/zx/clock.h>
#include <zircon/errors.h>
#include <optional>
#include <sstream>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/media/audio/services/device_registry/common_unittest.h"
#include "src/media/audio/services/device_registry/device_unittest.h"
#include "src/media/audio/services/device_registry/testing/fake_codec.h"
#include "src/media/audio/services/device_registry/testing/fake_composite.h"
#include "src/media/audio/services/device_registry/testing/fake_composite_ring_buffer.h"
#include "src/media/audio/services/device_registry/testing/fake_stream_config.h"
#include "src/media/audio/services/device_registry/validate.h"
namespace media_audio {
/////////////////////
// Codec tests
//
// Verify that a fake codec is initialized successfully.
TEST_F(CodecTest, Initialization) {
auto fake_driver = MakeFakeCodecOutput();
auto device = InitializeDeviceForFakeCodec(fake_driver);
EXPECT_TRUE(IsInitialized(device));
EXPECT_EQ(device_presence_watcher()->ready_devices().size(), 1u);
EXPECT_EQ(device_presence_watcher()->error_devices().size(), 0u);
EXPECT_EQ(device_presence_watcher()->on_ready_count(), 1u);
EXPECT_EQ(device_presence_watcher()->on_error_count(), 0u);
EXPECT_EQ(device_presence_watcher()->on_removal_count(), 0u);
EXPECT_TRUE(device->is_codec());
EXPECT_TRUE(device->has_codec_properties());
EXPECT_TRUE(device->checked_for_signalprocessing());
EXPECT_TRUE(device->has_health_state());
EXPECT_TRUE(device->dai_format_sets_retrieved());
EXPECT_TRUE(device->has_plug_state());
EXPECT_FALSE(device->supports_signalprocessing());
EXPECT_TRUE(device->info().has_value());
}
TEST_F(CodecTest, InitializationNoDirection) {
auto fake_driver = MakeFakeCodecNoDirection();
auto device = InitializeDeviceForFakeCodec(fake_driver);
EXPECT_TRUE(IsInitialized(device));
EXPECT_EQ(device_presence_watcher()->ready_devices().size(), 1u);
EXPECT_EQ(device_presence_watcher()->error_devices().size(), 0u);
EXPECT_EQ(device_presence_watcher()->on_ready_count(), 1u);
EXPECT_EQ(device_presence_watcher()->on_error_count(), 0u);
EXPECT_EQ(device_presence_watcher()->on_removal_count(), 0u);
ASSERT_TRUE(device->info().has_value());
EXPECT_FALSE(device->info()->is_input().has_value());
}
// Verify that a fake codec is initialized to the expected default values.
TEST_F(CodecTest, DeviceInfo) {
auto fake_driver = MakeFakeCodecOutput();
auto device = InitializeDeviceForFakeCodec(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_EQ(device_presence_watcher()->ready_devices().size(), 1u);
ASSERT_EQ(device_presence_watcher()->on_ready_count(), 1u);
ASSERT_TRUE(device->info().has_value());
auto info = *device->info();
EXPECT_TRUE(info.token_id().has_value());
ASSERT_TRUE(info.device_type().has_value());
EXPECT_EQ(*info.device_type(), fuchsia_audio_device::DeviceType::kCodec);
ASSERT_TRUE(info.device_name().has_value());
EXPECT_TRUE(!info.device_name()->empty());
ASSERT_TRUE(info.manufacturer().has_value());
EXPECT_EQ(*info.manufacturer(), FakeCodec::kDefaultManufacturer);
ASSERT_TRUE(info.product().has_value());
EXPECT_EQ(*info.product(), FakeCodec::kDefaultProduct);
ASSERT_TRUE(info.unique_instance_id().has_value());
EXPECT_EQ(*info.unique_instance_id(), FakeCodec::kDefaultUniqueInstanceId);
ASSERT_TRUE(info.is_input().has_value());
EXPECT_FALSE(info.is_input().value());
EXPECT_FALSE(info.ring_buffer_format_sets().has_value());
ASSERT_TRUE(info.dai_format_sets().has_value());
ASSERT_FALSE(info.dai_format_sets()->empty());
ASSERT_TRUE(info.dai_format_sets()->at(0).format_sets().has_value());
EXPECT_EQ(*info.dai_format_sets()->at(0).format_sets(), FakeCodec::kDefaultDaiFormatSets);
EXPECT_FALSE(info.gain_caps().has_value());
ASSERT_TRUE(info.plug_detect_caps().has_value());
EXPECT_EQ(*info.plug_detect_caps(), FakeCodec::kDefaultPlugCaps);
EXPECT_FALSE(info.clock_domain().has_value());
EXPECT_FALSE(info.signal_processing_elements().has_value());
EXPECT_FALSE(info.signal_processing_topologies().has_value());
}
TEST_F(CodecTest, DistinctTokenIds) {
auto fake_driver = MakeFakeCodecOutput();
auto device = InitializeDeviceForFakeCodec(fake_driver);
ASSERT_TRUE(IsInitialized(device));
// Set up a second, entirely distinct fake device.
auto [client, server] = fidl::Endpoints<fuchsia_hardware_audio::Codec>::Create();
auto fake_driver2 =
std::make_shared<FakeCodec>(server.TakeChannel(), client.TakeChannel(), dispatcher());
fake_driver2->set_is_input(true);
auto device2 = InitializeDeviceForFakeCodec(fake_driver2);
EXPECT_TRUE(IsInitialized(device2));
EXPECT_NE(device->token_id(), device2->token_id());
EXPECT_EQ(device_presence_watcher()->ready_devices().size(), 2u);
EXPECT_EQ(device_presence_watcher()->error_devices().size(), 0u);
}
// Verify that a driver's dropping the Codec causes a DeviceIsRemoved notification.
TEST_F(CodecTest, Disconnect) {
auto fake_driver = MakeFakeCodecNoDirection();
auto device = InitializeDeviceForFakeCodec(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_EQ(device_presence_watcher()->ready_devices().size(), 1u);
ASSERT_EQ(device_presence_watcher()->error_devices().size(), 0u);
ASSERT_EQ(device_presence_watcher()->on_ready_count(), 1u);
ASSERT_EQ(device_presence_watcher()->on_error_count(), 0u);
ASSERT_EQ(device_presence_watcher()->on_removal_count(), 0u);
fake_driver->DropCodec();
RunLoopUntilIdle();
EXPECT_EQ(device_presence_watcher()->ready_devices().size(), 0u);
EXPECT_EQ(device_presence_watcher()->error_devices().size(), 0u);
EXPECT_EQ(device_presence_watcher()->on_ready_count(), 1u);
EXPECT_EQ(device_presence_watcher()->on_error_count(), 0u);
EXPECT_EQ(device_presence_watcher()->on_removal_count(), 1u);
EXPECT_EQ(device_presence_watcher()->on_removal_from_ready_count(), 1u);
}
// Verify that a GetHealthState response of an empty struct is considered "healthy".
TEST_F(CodecTest, EmptyHealthResponse) {
auto fake_driver = MakeFakeCodecOutput();
fake_driver->set_health_state(std::nullopt);
auto device = InitializeDeviceForFakeCodec(fake_driver);
EXPECT_TRUE(IsInitialized(device));
EXPECT_EQ(device_presence_watcher()->ready_devices().size(), 1u);
EXPECT_EQ(device_presence_watcher()->error_devices().size(), 0u);
}
TEST_F(CodecTest, GetClock) {
auto fake_driver = MakeFakeCodecNoDirection();
auto device = InitializeDeviceForFakeCodec(fake_driver);
ASSERT_TRUE(IsInitialized(device));
auto clock = device->GetReadOnlyClock();
ASSERT_TRUE(clock.is_error());
EXPECT_EQ(clock.error_value(), ZX_ERR_NOT_SUPPORTED);
}
TEST_F(CodecTest, Observer) {
auto fake_driver = MakeFakeCodecNoDirection();
auto device = InitializeDeviceForFakeCodec(fake_driver);
ASSERT_TRUE(IsInitialized(device));
EXPECT_TRUE(AddObserver(device));
}
TEST_F(CodecTest, Control) {
auto fake_driver = MakeFakeCodecOutput();
auto device = InitializeDeviceForFakeCodec(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(SetControl(device));
EXPECT_TRUE(DropControl(device));
}
// This tests whether a Codec driver notifies Observers of initial gain state. (It shouldn't.)
TEST_F(CodecTest, InitialGainState) {
auto fake_driver = MakeFakeCodecInput();
auto device = InitializeDeviceForFakeCodec(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(AddObserver(device));
RunLoopUntilIdle();
EXPECT_FALSE(notify()->gain_state().has_value())
<< "ObserverNotify was notified of initial GainState";
}
// This tests the driver's ability to inform its ObserverNotify of initial plug state.
TEST_F(CodecTest, InitialPlugState) {
auto fake_driver = MakeFakeCodecNoDirection();
auto device = InitializeDeviceForFakeCodec(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(AddObserver(device));
RunLoopUntilIdle();
EXPECT_TRUE(device_plugged_state(device));
ASSERT_TRUE(notify()->plug_state());
EXPECT_EQ(notify()->plug_state()->first, fuchsia_audio_device::PlugState::kPlugged);
EXPECT_EQ(notify()->plug_state()->second.get(), zx::time::infinite_past().get());
}
// This tests the driver's ability to originate plug changes, such as from jack detection. It also
// validates that plug notifications are delivered through ControlNotify (not just ObserverNotify).
TEST_F(CodecTest, DynamicPlugUpdate) {
auto fake_driver = MakeFakeCodecOutput();
auto device = InitializeDeviceForFakeCodec(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(SetControl(device));
RunLoopUntilIdle();
EXPECT_TRUE(device_plugged_state(device));
ASSERT_TRUE(notify()->plug_state());
EXPECT_EQ(notify()->plug_state()->first, fuchsia_audio_device::PlugState::kPlugged);
EXPECT_EQ(notify()->plug_state()->second.get(), zx::time::infinite_past().get());
notify()->plug_state().reset();
auto unplug_time = zx::clock::get_monotonic();
fake_driver->InjectUnpluggedAt(unplug_time);
RunLoopUntilIdle();
EXPECT_FALSE(device_plugged_state(device));
ASSERT_TRUE(notify()->plug_state());
EXPECT_EQ(notify()->plug_state()->first, fuchsia_audio_device::PlugState::kUnplugged);
EXPECT_EQ(notify()->plug_state()->second.get(), unplug_time.get());
}
TEST_F(CodecTest, GetDaiFormats) {
auto fake_driver = MakeFakeCodecInput();
auto device = InitializeDeviceForFakeCodec(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_FALSE(IsControlled(device));
ASSERT_TRUE(AddObserver(device));
bool received_get_dai_formats_callback = false;
std::vector<fuchsia_hardware_audio::DaiSupportedFormats> dai_formats;
device->RetrieveDaiFormatSets(
dai_element_id(),
[&received_get_dai_formats_callback, &dai_formats](
ElementId element_id,
const std::vector<fuchsia_hardware_audio::DaiSupportedFormats>& formats) {
EXPECT_EQ(element_id, fuchsia_audio_device::kDefaultDaiInterconnectElementId);
received_get_dai_formats_callback = true;
for (auto& dai_format_set : formats) {
dai_formats.push_back(dai_format_set);
}
});
RunLoopUntilIdle();
ASSERT_TRUE(received_get_dai_formats_callback);
EXPECT_TRUE(ValidateDaiFormatSets(dai_formats));
}
// SetDaiFormat is tested (here and in CodecWarningTest) against all states and error cases:
// States: First set, format-change, no-change. ControlNotify::DaiFormatChanged received.
// SetDaiFormat stops Codec; ControlNotify::CodecStopped received if was started.
// Errors: StreamConfig type; Device has error, not controlled; invalid format; unsupported format.
//
// SetDaiFormat - use the first supported format
TEST_F(CodecTest, SetDaiFormat) {
auto fake_driver = MakeFakeCodecNoDirection();
auto device = InitializeDeviceForFakeCodec(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(SetControl(device));
ASSERT_TRUE(IsControlled(device));
bool received_get_dai_formats_callback = false;
std::vector<fuchsia_hardware_audio::DaiSupportedFormats> dai_formats;
device->RetrieveDaiFormatSets(
dai_element_id(),
[&received_get_dai_formats_callback, &dai_formats](
ElementId element_id,
const std::vector<fuchsia_hardware_audio::DaiSupportedFormats>& formats) {
EXPECT_EQ(element_id, fuchsia_audio_device::kDefaultDaiInterconnectElementId);
received_get_dai_formats_callback = true;
for (auto& dai_format_set : formats) {
dai_formats.push_back(dai_format_set);
}
});
RunLoopUntilIdle();
ASSERT_TRUE(received_get_dai_formats_callback);
ASSERT_FALSE(notify()->dai_format());
device->SetDaiFormat(dai_element_id(), SafeDaiFormatFromDaiFormatSets(dai_formats));
// check that notify received dai_format and codec_format_info
RunLoopUntilIdle();
EXPECT_TRUE(notify()->dai_format());
EXPECT_TRUE(ValidateDaiFormat(*notify()->dai_format()));
EXPECT_TRUE(notify()->codec_format_info(dai_element_id()));
EXPECT_TRUE(ValidateCodecFormatInfo(*notify()->codec_format_info(dai_element_id())));
}
// Start and Stop
TEST_F(CodecTest, InitiallyStopped) {
auto fake_driver = MakeFakeCodecOutput();
auto device = InitializeDeviceForFakeCodec(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(SetControl(device));
ASSERT_TRUE(IsControlled(device));
bool received_get_dai_formats_callback = false;
std::vector<fuchsia_hardware_audio::DaiSupportedFormats> dai_formats;
device->RetrieveDaiFormatSets(
dai_element_id(),
[&received_get_dai_formats_callback, &dai_formats](
ElementId element_id,
const std::vector<fuchsia_hardware_audio::DaiSupportedFormats>& formats) {
EXPECT_EQ(element_id, fuchsia_audio_device::kDefaultDaiInterconnectElementId);
received_get_dai_formats_callback = true;
for (auto& dai_format_set : formats) {
dai_formats.push_back(dai_format_set);
}
});
RunLoopUntilIdle();
ASSERT_TRUE(received_get_dai_formats_callback);
device->SetDaiFormat(dai_element_id(), SafeDaiFormatFromDaiFormatSets(dai_formats));
RunLoopUntilIdle();
ASSERT_TRUE(notify()->dai_format());
ASSERT_TRUE(notify()->codec_is_stopped());
// This should do nothing since we are already stopped.
EXPECT_TRUE(device->CodecStop());
RunLoopUntilIdle();
EXPECT_TRUE(notify()->codec_is_stopped());
// This demonstrates that there was no change as a result of the Stop call.
EXPECT_EQ(notify()->codec_stop_time()->get(), zx::time::infinite_past().get());
}
TEST_F(CodecTest, Start) {
auto fake_driver = MakeFakeCodecInput();
auto device = InitializeDeviceForFakeCodec(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(SetControl(device));
std::vector<fuchsia_hardware_audio::DaiSupportedFormats> dai_formats;
device->RetrieveDaiFormatSets(
dai_element_id(),
[&dai_formats](ElementId element_id,
const std::vector<fuchsia_hardware_audio::DaiSupportedFormats>& formats) {
EXPECT_EQ(element_id, fuchsia_audio_device::kDefaultDaiInterconnectElementId);
dai_formats.push_back(formats[0]);
});
RunLoopUntilIdle();
device->SetDaiFormat(dai_element_id(), SafeDaiFormatFromDaiFormatSets(dai_formats));
RunLoopUntilIdle();
ASSERT_TRUE(notify()->dai_format());
ASSERT_TRUE(notify()->codec_is_stopped());
auto time_before_start = zx::clock::get_monotonic();
EXPECT_TRUE(device->CodecStart());
RunLoopUntilIdle();
EXPECT_TRUE(notify()->codec_is_started());
EXPECT_GT(notify()->codec_start_time()->get(), time_before_start.get());
}
TEST_F(CodecTest, SetDaiFormatChange) {
auto fake_driver = MakeFakeCodecNoDirection();
auto device = InitializeDeviceForFakeCodec(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(SetControl(device));
std::vector<fuchsia_hardware_audio::DaiSupportedFormats> dai_formats;
device->RetrieveDaiFormatSets(
dai_element_id(),
[&dai_formats](ElementId element_id,
const std::vector<fuchsia_hardware_audio::DaiSupportedFormats>& formats) {
EXPECT_EQ(element_id, fuchsia_audio_device::kDefaultDaiInterconnectElementId);
dai_formats.push_back(formats[0]);
});
RunLoopUntilIdle();
device->SetDaiFormat(dai_element_id(), SafeDaiFormatFromDaiFormatSets(dai_formats));
RunLoopUntilIdle();
ASSERT_TRUE(notify()->dai_format());
ASSERT_TRUE(device->CodecStart());
RunLoopUntilIdle();
ASSERT_TRUE(notify()->codec_is_started());
auto dai_format2 = SecondDaiFormatFromDaiFormatSets(dai_formats);
auto time_before_format_change = zx::clock::get_monotonic();
// Change the DaiFormat
device->SetDaiFormat(dai_element_id(), dai_format2);
// Expect codec to be stopped, and format to be changed.
RunLoopUntilIdle();
EXPECT_TRUE(notify()->codec_is_stopped());
EXPECT_GT(notify()->codec_stop_time()->get(), time_before_format_change.get());
EXPECT_TRUE(notify()->dai_format());
EXPECT_EQ(*notify()->dai_format(), dai_format2);
EXPECT_TRUE(notify()->codec_format_info(dai_element_id()));
}
TEST_F(CodecTest, SetDaiFormatNoChange) {
auto fake_driver = MakeFakeCodecNoDirection();
auto device = InitializeDeviceForFakeCodec(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(SetControl(device));
std::vector<fuchsia_hardware_audio::DaiSupportedFormats> dai_formats;
device->RetrieveDaiFormatSets(
dai_element_id(),
[&dai_formats](ElementId element_id,
const std::vector<fuchsia_hardware_audio::DaiSupportedFormats>& formats) {
EXPECT_EQ(element_id, fuchsia_audio_device::kDefaultDaiInterconnectElementId);
dai_formats.push_back(formats[0]);
});
RunLoopUntilIdle();
auto safe_format = SafeDaiFormatFromDaiFormatSets(dai_formats);
device->SetDaiFormat(dai_element_id(), safe_format);
RunLoopUntilIdle();
ASSERT_TRUE(notify()->dai_format());
ASSERT_TRUE(device->CodecStart());
RunLoopUntilIdle();
ASSERT_TRUE(notify()->codec_is_started());
auto time_after_started = zx::clock::get_monotonic();
// Clear out our notify's DaiFormat so we can detect a new notification of the same DaiFormat.
notify()->clear_dai_format(dai_element_id());
device->SetDaiFormat(dai_element_id(), safe_format);
RunLoopUntilIdle();
// We do not expect this to reset our Start state.
EXPECT_TRUE(notify()->codec_is_started());
EXPECT_LT(notify()->codec_start_time()->get(), time_after_started.get());
// We do not expect to be notified of a format change -- even a "change" to the same format.
EXPECT_FALSE(notify()->dai_format());
}
TEST_F(CodecTest, StartStop) {
auto fake_driver = MakeFakeCodecNoDirection();
auto device = InitializeDeviceForFakeCodec(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(SetControl(device));
std::vector<fuchsia_hardware_audio::DaiSupportedFormats> dai_formats;
device->RetrieveDaiFormatSets(
dai_element_id(),
[&dai_formats](ElementId element_id,
const std::vector<fuchsia_hardware_audio::DaiSupportedFormats>& formats) {
EXPECT_EQ(element_id, fuchsia_audio_device::kDefaultDaiInterconnectElementId);
dai_formats.push_back(formats[0]);
});
RunLoopUntilIdle();
device->SetDaiFormat(dai_element_id(), SafeDaiFormatFromDaiFormatSets(dai_formats));
RunLoopUntilIdle();
ASSERT_TRUE(notify()->dai_format());
ASSERT_TRUE(notify()->codec_is_stopped());
ASSERT_TRUE(device->CodecStart());
RunLoopUntilIdle();
auto time_before_stop = zx::clock::get_monotonic();
ASSERT_TRUE(notify()->codec_is_started());
ASSERT_TRUE(device->CodecStop());
RunLoopUntilIdle();
EXPECT_TRUE(notify()->codec_is_stopped());
EXPECT_GT(notify()->codec_stop_time()->get(), time_before_stop.get());
}
// Start when already started: no notification; old start_time.
TEST_F(CodecTest, StartStart) {
auto fake_driver = MakeFakeCodecOutput();
auto device = InitializeDeviceForFakeCodec(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(SetControl(device));
std::vector<fuchsia_hardware_audio::DaiSupportedFormats> dai_formats;
device->RetrieveDaiFormatSets(
dai_element_id(),
[&dai_formats](ElementId element_id,
const std::vector<fuchsia_hardware_audio::DaiSupportedFormats>& formats) {
EXPECT_EQ(element_id, fuchsia_audio_device::kDefaultDaiInterconnectElementId);
dai_formats.push_back(formats[0]);
});
RunLoopUntilIdle();
device->SetDaiFormat(dai_element_id(), SafeDaiFormatFromDaiFormatSets(dai_formats));
RunLoopUntilIdle();
ASSERT_TRUE(notify()->dai_format());
ASSERT_TRUE(notify()->codec_is_stopped());
ASSERT_TRUE(device->CodecStart());
RunLoopUntilIdle();
ASSERT_TRUE(notify()->codec_is_started());
auto previous_start_time = *notify()->codec_start_time();
ASSERT_TRUE(device->CodecStart());
// Should not get a new notification here.
RunLoopUntilIdle();
EXPECT_TRUE(notify()->codec_is_started());
EXPECT_EQ(notify()->codec_start_time()->get(), previous_start_time.get());
}
// Stop when already stopped: no notification; old stop_time.
TEST_F(CodecTest, StopStop) {
auto fake_driver = MakeFakeCodecInput();
auto device = InitializeDeviceForFakeCodec(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(SetControl(device));
std::vector<fuchsia_hardware_audio::DaiSupportedFormats> dai_formats;
device->RetrieveDaiFormatSets(
dai_element_id(),
[&dai_formats](ElementId element_id,
const std::vector<fuchsia_hardware_audio::DaiSupportedFormats>& formats) {
EXPECT_EQ(element_id, fuchsia_audio_device::kDefaultDaiInterconnectElementId);
dai_formats.push_back(formats[0]);
});
RunLoopUntilIdle();
device->SetDaiFormat(dai_element_id(), SafeDaiFormatFromDaiFormatSets(dai_formats));
RunLoopUntilIdle();
ASSERT_TRUE(notify()->dai_format());
ASSERT_TRUE(notify()->codec_is_stopped());
// First Start the Codec so we can explicitly transition into the Stop state.
ASSERT_TRUE(device->CodecStart());
RunLoopUntilIdle();
ASSERT_TRUE(notify()->codec_is_started());
ASSERT_TRUE(device->CodecStop());
RunLoopUntilIdle();
ASSERT_TRUE(notify()->codec_is_stopped());
auto previous_stop_time = *notify()->codec_stop_time();
// Since we are already stopped, this should have no effect.
EXPECT_TRUE(device->CodecStop());
RunLoopUntilIdle();
EXPECT_TRUE(notify()->codec_is_stopped());
// This demonstrates that there was no change as a result of the Stop call.
EXPECT_EQ(notify()->codec_stop_time()->get(), previous_stop_time.get());
}
// Reset stops the Codec and resets DaiFormat, Elements and Topology
TEST_F(CodecTest, Reset) {
auto fake_driver = MakeFakeCodecNoDirection();
auto device = InitializeDeviceForFakeCodec(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(SetControl(device));
std::vector<fuchsia_hardware_audio::DaiSupportedFormats> dai_formats;
device->RetrieveDaiFormatSets(
dai_element_id(),
[&dai_formats](ElementId element_id,
const std::vector<fuchsia_hardware_audio::DaiSupportedFormats>& formats) {
EXPECT_EQ(element_id, fuchsia_audio_device::kDefaultDaiInterconnectElementId);
dai_formats.push_back(formats[0]);
});
RunLoopUntilIdle();
device->SetDaiFormat(dai_element_id(), SafeDaiFormatFromDaiFormatSets(dai_formats));
RunLoopUntilIdle();
ASSERT_TRUE(notify()->dai_format());
ASSERT_TRUE(notify()->codec_format_info(dai_element_id()));
ASSERT_TRUE(notify()->codec_is_stopped());
ASSERT_TRUE(device->CodecStart());
// TODO(https://fxbug.dev/323270827): implement signalprocessing for Codec (topology, gain).
// When implemented in FakeCodec, set a signalprocessing Topology, and change a signalprocessing
// Element, and observe that both of these changes are reverted by the call to Reset.
RunLoopUntilIdle();
EXPECT_TRUE(notify()->codec_is_started());
// Verify that the signalprocessing Topology has in fact changed.
// Verify that the signalprocessing Element has in fact changed.
auto time_before_reset = zx::clock::get_monotonic();
EXPECT_TRUE(device->Reset());
RunLoopUntilIdle();
EXPECT_TRUE(notify()->codec_is_stopped());
// We were notified that the Codec stopped.
EXPECT_GT(notify()->codec_stop_time()->get(), time_before_reset.get());
// We were notified that the Codec reset its DaiFormat (none is now set).
EXPECT_FALSE(notify()->dai_format());
EXPECT_FALSE(notify()->codec_format_info(dai_element_id()));
// Observe the signalprocessing Elements - were they reset?
// Observe the signalprocessing Topology - was it reset?
}
/////////////////////
// Composite tests
//
// Verify that a fake composite is initialized successfully.
TEST_F(CompositeTest, Initialization) {
auto fake_driver = MakeFakeComposite();
auto device = InitializeDeviceForFakeComposite(fake_driver);
EXPECT_TRUE(IsInitialized(device));
EXPECT_EQ(device_presence_watcher()->ready_devices().size(), 1u);
EXPECT_EQ(device_presence_watcher()->error_devices().size(), 0u);
EXPECT_EQ(device_presence_watcher()->on_ready_count(), 1u);
EXPECT_EQ(device_presence_watcher()->on_error_count(), 0u);
EXPECT_EQ(device_presence_watcher()->on_removal_count(), 0u);
EXPECT_TRUE(device->is_composite());
ASSERT_TRUE(device->info().has_value());
EXPECT_FALSE(device->info()->is_input().has_value());
}
// Verify that a fake composite is initialized to the expected default values.
TEST_F(CompositeTest, DeviceInfo) {
auto fake_driver = MakeFakeComposite();
auto device = InitializeDeviceForFakeComposite(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_EQ(device_presence_watcher()->ready_devices().size(), 1u);
ASSERT_EQ(device_presence_watcher()->on_ready_count(), 1u);
ASSERT_TRUE(device->info().has_value());
auto info = *device->info();
EXPECT_TRUE(info.token_id().has_value());
ASSERT_TRUE(info.device_type().has_value());
EXPECT_EQ(*info.device_type(), fuchsia_audio_device::DeviceType::kComposite);
ASSERT_TRUE(info.device_name().has_value());
EXPECT_FALSE(info.device_name()->empty());
ASSERT_TRUE(info.manufacturer().has_value());
EXPECT_EQ(*info.manufacturer(), FakeComposite::kDefaultManufacturer);
ASSERT_TRUE(info.product().has_value());
EXPECT_EQ(*info.product(), FakeComposite::kDefaultProduct);
ASSERT_TRUE(info.unique_instance_id().has_value());
EXPECT_EQ(*info.unique_instance_id(), FakeComposite::kDefaultUniqueInstanceId);
EXPECT_FALSE(info.is_input().has_value());
ASSERT_TRUE(info.ring_buffer_format_sets().has_value());
ASSERT_FALSE(info.ring_buffer_format_sets()->empty());
ASSERT_EQ(info.ring_buffer_format_sets()->size(), 2u);
ASSERT_TRUE(info.ring_buffer_format_sets()->at(0).element_id().has_value());
EXPECT_EQ(*info.ring_buffer_format_sets()->at(0).element_id(), FakeComposite::kSourceRbElementId);
ASSERT_TRUE(info.ring_buffer_format_sets()->at(0).format_sets().has_value());
ASSERT_FALSE(info.ring_buffer_format_sets()->at(0).format_sets()->empty());
EXPECT_EQ(info.ring_buffer_format_sets()->at(0).format_sets()->size(), 1u);
auto format_set0 = info.ring_buffer_format_sets()->at(0).format_sets()->at(0);
ASSERT_TRUE(format_set0.frame_rates().has_value());
EXPECT_THAT(*format_set0.frame_rates(),
testing::ElementsAreArray(FakeComposite::kDefaultRbFrameRates2));
ASSERT_TRUE(format_set0.channel_sets().has_value());
ASSERT_FALSE(format_set0.channel_sets()->empty());
EXPECT_EQ(format_set0.channel_sets()->size(), 1u);
ASSERT_TRUE(format_set0.channel_sets()->at(0).attributes().has_value());
ASSERT_FALSE(format_set0.channel_sets()->at(0).attributes()->empty());
EXPECT_EQ(format_set0.channel_sets()->at(0).attributes()->size(), 1u);
ASSERT_FALSE(format_set0.channel_sets()->at(0).attributes()->at(0).max_frequency().has_value());
ASSERT_TRUE(format_set0.channel_sets()->at(0).attributes()->at(0).min_frequency().has_value());
EXPECT_EQ(*format_set0.channel_sets()->at(0).attributes()->at(0).min_frequency(),
FakeComposite::kDefaultRbChannelAttributeMinFrequency2);
ASSERT_TRUE(info.ring_buffer_format_sets()->at(1).element_id().has_value());
EXPECT_EQ(*info.ring_buffer_format_sets()->at(1).element_id(), FakeComposite::kDestRbElementId);
ASSERT_TRUE(info.ring_buffer_format_sets()->at(1).format_sets().has_value());
ASSERT_FALSE(info.ring_buffer_format_sets()->at(1).format_sets()->empty());
EXPECT_EQ(info.ring_buffer_format_sets()->at(1).format_sets()->size(), 1u);
auto format_set1 = info.ring_buffer_format_sets()->at(1).format_sets()->at(0);
ASSERT_TRUE(format_set1.frame_rates().has_value());
EXPECT_THAT(*format_set1.frame_rates(),
testing::ElementsAreArray(FakeComposite::kDefaultRbFrameRates));
ASSERT_TRUE(format_set1.channel_sets().has_value());
ASSERT_FALSE(format_set1.channel_sets()->empty());
EXPECT_EQ(format_set1.channel_sets()->size(), 1u);
ASSERT_TRUE(format_set1.channel_sets()->at(0).attributes().has_value());
ASSERT_FALSE(format_set1.channel_sets()->at(0).attributes()->empty());
EXPECT_EQ(format_set1.channel_sets()->at(0).attributes()->size(), 1u);
ASSERT_TRUE(format_set1.channel_sets()->at(0).attributes()->at(0).max_frequency().has_value());
EXPECT_EQ(*format_set1.channel_sets()->at(0).attributes()->at(0).max_frequency(),
FakeComposite::kDefaultRbChannelAttributeMaxFrequency);
ASSERT_TRUE(format_set1.channel_sets()->at(0).attributes()->at(0).min_frequency().has_value());
EXPECT_EQ(*format_set1.channel_sets()->at(0).attributes()->at(0).min_frequency(),
FakeComposite::kDefaultRbChannelAttributeMinFrequency);
ASSERT_TRUE(info.dai_format_sets().has_value());
ASSERT_FALSE(info.dai_format_sets()->empty());
ASSERT_EQ(info.dai_format_sets()->size(), 2u);
ASSERT_TRUE(info.dai_format_sets()->at(0).element_id().has_value());
EXPECT_EQ(*info.dai_format_sets()->at(0).element_id(), FakeComposite::kDestDaiElementId);
ASSERT_TRUE(info.dai_format_sets()->at(0).format_sets().has_value());
EXPECT_EQ(info.dai_format_sets()->at(0).format_sets()->size(), 1u);
EXPECT_THAT(info.dai_format_sets()->at(0).format_sets()->at(0).number_of_channels(),
testing::ElementsAreArray(FakeComposite::kDefaultDaiNumberOfChannelsSet2));
EXPECT_THAT(info.dai_format_sets()->at(0).format_sets()->at(0).frame_rates(),
testing::ElementsAreArray(FakeComposite::kDefaultDaiFrameRates2));
EXPECT_THAT(info.dai_format_sets()->at(0).format_sets()->at(0).bits_per_slot(),
testing::ElementsAreArray(FakeComposite::kDefaultDaiBitsPerSlotSet2));
EXPECT_THAT(info.dai_format_sets()->at(0).format_sets()->at(0).bits_per_sample(),
testing::ElementsAreArray(FakeComposite::kDefaultDaiBitsPerSampleSet2));
EXPECT_THAT(info.dai_format_sets()->at(0).format_sets()->at(0).frame_formats(),
testing::ElementsAreArray(FakeComposite::kDefaultDaiFrameFormatsSet2));
EXPECT_THAT(info.dai_format_sets()->at(0).format_sets()->at(0).sample_formats(),
testing::ElementsAreArray(FakeComposite::kDefaultDaiSampleFormatsSet2));
ASSERT_TRUE(info.dai_format_sets()->at(1).element_id().has_value());
EXPECT_EQ(*info.dai_format_sets()->at(1).element_id(), FakeComposite::kSourceDaiElementId);
ASSERT_TRUE(info.dai_format_sets()->at(1).format_sets().has_value());
EXPECT_EQ(info.dai_format_sets()->at(1).format_sets()->size(), 1u);
EXPECT_THAT(info.dai_format_sets()->at(1).format_sets()->at(0).number_of_channels(),
testing::ElementsAreArray(FakeComposite::kDefaultDaiNumberOfChannelsSet));
EXPECT_THAT(info.dai_format_sets()->at(1).format_sets()->at(0).frame_rates(),
testing::ElementsAreArray(FakeComposite::kDefaultDaiFrameRates));
EXPECT_THAT(info.dai_format_sets()->at(1).format_sets()->at(0).bits_per_slot(),
testing::ElementsAreArray(FakeComposite::kDefaultDaiBitsPerSlotSet));
EXPECT_THAT(info.dai_format_sets()->at(1).format_sets()->at(0).bits_per_sample(),
testing::ElementsAreArray(FakeComposite::kDefaultDaiBitsPerSampleSet));
EXPECT_THAT(info.dai_format_sets()->at(1).format_sets()->at(0).frame_formats(),
testing::ElementsAreArray(FakeComposite::kDefaultDaiFrameFormatsSet));
EXPECT_THAT(info.dai_format_sets()->at(1).format_sets()->at(0).sample_formats(),
testing::ElementsAreArray(FakeComposite::kDefaultDaiSampleFormatsSet));
EXPECT_FALSE(info.gain_caps().has_value());
EXPECT_FALSE(info.plug_detect_caps().has_value());
EXPECT_TRUE(info.clock_domain().has_value());
ASSERT_TRUE(info.signal_processing_elements().has_value());
ASSERT_TRUE(info.signal_processing_topologies().has_value());
EXPECT_FALSE(info.signal_processing_elements()->empty());
EXPECT_FALSE(info.signal_processing_topologies()->empty());
}
TEST_F(CompositeTest, DistinctTokenIds) {
auto fake_driver = MakeFakeComposite();
auto device = InitializeDeviceForFakeComposite(fake_driver);
ASSERT_TRUE(IsInitialized(device));
// Set up a second, entirely distinct fake device.
auto [client, server] = fidl::Endpoints<fuchsia_hardware_audio::Composite>::Create();
auto fake_driver2 =
std::make_shared<FakeComposite>(server.TakeChannel(), client.TakeChannel(), dispatcher());
auto device2 = InitializeDeviceForFakeComposite(fake_driver2);
EXPECT_TRUE(IsInitialized(device2));
EXPECT_NE(device->token_id(), device2->token_id());
EXPECT_EQ(device_presence_watcher()->ready_devices().size(), 2u);
EXPECT_EQ(device_presence_watcher()->error_devices().size(), 0u);
}
// Verify that a driver's dropping the Composite causes a DeviceIsRemoved notification.
TEST_F(CompositeTest, Disconnect) {
auto fake_driver = MakeFakeComposite();
auto device = InitializeDeviceForFakeComposite(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_EQ(device_presence_watcher()->ready_devices().size(), 1u);
ASSERT_EQ(device_presence_watcher()->error_devices().size(), 0u);
ASSERT_EQ(device_presence_watcher()->on_ready_count(), 1u);
ASSERT_EQ(device_presence_watcher()->on_error_count(), 0u);
ASSERT_EQ(device_presence_watcher()->on_removal_count(), 0u);
fake_driver->DropComposite();
RunLoopUntilIdle();
EXPECT_EQ(device_presence_watcher()->ready_devices().size(), 0u);
EXPECT_EQ(device_presence_watcher()->error_devices().size(), 0u);
EXPECT_EQ(device_presence_watcher()->on_ready_count(), 1u);
EXPECT_EQ(device_presence_watcher()->on_error_count(), 0u);
EXPECT_EQ(device_presence_watcher()->on_removal_count(), 1u);
EXPECT_EQ(device_presence_watcher()->on_removal_from_ready_count(), 1u);
}
// Verify that a GetHealthState response of an empty struct is considered "healthy".
TEST_F(CompositeTest, EmptyHealthResponse) {
auto fake_driver = MakeFakeComposite();
fake_driver->set_health_state(std::nullopt);
auto device = InitializeDeviceForFakeComposite(fake_driver);
EXPECT_TRUE(IsInitialized(device));
EXPECT_EQ(device_presence_watcher()->ready_devices().size(), 1u);
EXPECT_EQ(device_presence_watcher()->error_devices().size(), 0u);
}
TEST_F(CompositeTest, DefaultClock) {
auto fake_driver = MakeFakeComposite();
auto device = InitializeDeviceForFakeComposite(fake_driver);
ASSERT_TRUE(IsInitialized(device));
EXPECT_EQ(device_clock(device)->domain(), fuchsia_hardware_audio::kClockDomainMonotonic);
EXPECT_TRUE(device_clock(device)->IdenticalToMonotonicClock());
EXPECT_FALSE(device_clock(device)->adjustable());
EXPECT_EQ(device_presence_watcher()->ready_devices().size(), 1u);
EXPECT_EQ(device_presence_watcher()->error_devices().size(), 0u);
}
TEST_F(CompositeTest, ClockInOtherDomain) {
const uint32_t kNonMonotonicClockDomain = fuchsia_hardware_audio::kClockDomainMonotonic + 1;
auto fake_driver = MakeFakeComposite();
fake_driver->set_clock_domain(kNonMonotonicClockDomain);
auto device = InitializeDeviceForFakeComposite(fake_driver);
ASSERT_TRUE(IsInitialized(device));
EXPECT_EQ(device_clock(device)->domain(), kNonMonotonicClockDomain);
EXPECT_TRUE(device_clock(device)->IdenticalToMonotonicClock());
EXPECT_TRUE(device_clock(device)->adjustable());
EXPECT_EQ(device_presence_watcher()->ready_devices().size(), 1u);
EXPECT_EQ(device_presence_watcher()->error_devices().size(), 0u);
}
TEST_F(CompositeTest, Observer) {
auto fake_driver = MakeFakeComposite();
auto device = InitializeDeviceForFakeComposite(fake_driver);
ASSERT_TRUE(IsInitialized(device));
EXPECT_TRUE(AddObserver(device));
}
// This tests the driver's ability to inform its ObserverNotify of initial signalprocessing state.
TEST_F(CompositeTest, InitialSignalProcessingForObserver) {
auto fake_driver = MakeFakeComposite();
auto device = InitializeDeviceForFakeComposite(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(AddObserver(device));
RunLoopUntilIdle();
EXPECT_TRUE(notify()->topology_id().has_value())
<< "ObserverNotify was not notified of initial TopologyId";
EXPECT_FALSE(notify()->element_states().empty());
ASSERT_TRUE(device->info().has_value());
ASSERT_TRUE(device->info()->signal_processing_elements().has_value());
EXPECT_FALSE(device->info()->signal_processing_elements()->empty());
EXPECT_EQ(notify()->element_states().size(),
device->info()->signal_processing_elements()->size());
}
TEST_F(CompositeTest, Control) {
auto fake_driver = MakeFakeComposite();
auto device = InitializeDeviceForFakeComposite(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(SetControl(device));
EXPECT_TRUE(DropControl(device));
}
// This tests the driver's ability to inform its ControlNotify of initial signalprocessing state.
TEST_F(CompositeTest, InitialSignalProcessingForControl) {
auto fake_driver = MakeFakeComposite();
auto device = InitializeDeviceForFakeComposite(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(SetControl(device));
RunLoopUntilIdle();
EXPECT_TRUE(notify()->topology_id().has_value())
<< "ObserverNotify was not notified of initial TopologyId";
EXPECT_FALSE(notify()->element_states().empty());
ASSERT_TRUE(device->info().has_value());
ASSERT_TRUE(device->info()->signal_processing_elements().has_value());
EXPECT_FALSE(device->info()->signal_processing_elements()->empty());
EXPECT_EQ(notify()->element_states().size(),
device->info()->signal_processing_elements()->size());
}
// This tests whether a Composite driver notifies Observers of initial gain state. (It shouldn't.)
TEST_F(CompositeTest, NoInitialGainState) {
auto fake_driver = MakeFakeComposite();
auto device = InitializeDeviceForFakeComposite(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(AddObserver(device));
RunLoopUntilIdle();
EXPECT_FALSE(notify()->gain_state().has_value())
<< "ObserverNotify was notified of initial GainState";
}
// This tests whether a Composite driver notifies Observers of initial plug state. (It shouldn't.)
TEST_F(CompositeTest, NoInitialPlugState) {
auto fake_driver = MakeFakeComposite();
auto device = InitializeDeviceForFakeComposite(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(AddObserver(device));
RunLoopUntilIdle();
EXPECT_FALSE(notify()->plug_state().has_value())
<< "ObserverNotify was notified of initial PlugState";
}
// Verify setting initial DAI format, with ControlNotify receiving appropriate notification.
TEST_F(CompositeTest, SetDaiFormat) {
auto fake_driver = MakeFakeComposite();
auto device = InitializeDeviceForFakeComposite(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(SetControl(device));
auto dai_format_sets_by_element = device->dai_format_sets();
ASSERT_FALSE(dai_format_sets_by_element.empty());
for (auto dai_element_id : device->dai_endpoint_ids()) {
notify()->clear_dai_formats();
auto safe_format =
SafeDaiFormatFromElementDaiFormatSets(dai_element_id, dai_format_sets_by_element);
device->SetDaiFormat(dai_element_id, safe_format);
RunLoopUntilIdle();
// check that notify received dai_format for this element_id
EXPECT_TRUE(ExpectDaiFormatMatches(dai_element_id, safe_format));
EXPECT_TRUE(notify()->codec_format_infos().empty());
EXPECT_TRUE(notify()->dai_format_errors().empty());
}
}
// After a DAI format is set, setting it to the SAME format should have no effect.
TEST_F(CompositeTest, SetDaiFormatNoChange) {
auto fake_driver = MakeFakeComposite();
auto device = InitializeDeviceForFakeComposite(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(SetControl(device));
auto dai_format_sets_by_element = device->dai_format_sets();
ASSERT_FALSE(dai_format_sets_by_element.empty());
for (auto dai_element_id : device->dai_endpoint_ids()) {
notify()->clear_dai_formats();
auto safe_format =
SafeDaiFormatFromElementDaiFormatSets(dai_element_id, dai_format_sets_by_element);
device->SetDaiFormat(dai_element_id, safe_format);
RunLoopUntilIdle();
auto format_match = notify()->dai_formats().find(dai_element_id);
ASSERT_NE(format_match, notify()->dai_formats().end());
ASSERT_TRUE(format_match->second.has_value());
ASSERT_EQ(format_match->second, safe_format);
ASSERT_TRUE(notify()->codec_format_infos().empty());
ASSERT_TRUE(notify()->dai_format_errors().empty());
notify()->clear_dai_format(dai_element_id);
device->SetDaiFormat(dai_element_id, safe_format);
RunLoopUntilIdle();
ASSERT_FALSE(notify()->dai_format_errors().empty());
auto error_match = notify()->dai_format_errors().find(dai_element_id);
ASSERT_NE(error_match, notify()->dai_format_errors().end());
EXPECT_EQ(error_match->second, fuchsia_audio_device::ControlSetDaiFormatError(0));
format_match = notify()->dai_formats().find(dai_element_id);
EXPECT_EQ(format_match, notify()->dai_formats().end());
EXPECT_TRUE(notify()->codec_format_infos().empty());
}
}
// After a DAI format is set, CHANGING it should generate the appropriate notifications.
TEST_F(CompositeTest, SetDaiFormatChange) {
auto fake_driver = MakeFakeComposite();
auto device = InitializeDeviceForFakeComposite(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(SetControl(device));
auto dai_format_sets_by_element = device->dai_format_sets();
ASSERT_FALSE(dai_format_sets_by_element.empty());
for (auto dai_element_id : device->dai_endpoint_ids()) {
notify()->clear_dai_formats();
auto safe_format =
SafeDaiFormatFromElementDaiFormatSets(dai_element_id, dai_format_sets_by_element);
auto safe_format2 =
SecondDaiFormatFromElementDaiFormatSets(dai_element_id, dai_format_sets_by_element);
device->SetDaiFormat(dai_element_id, safe_format);
RunLoopUntilIdle();
auto format_match = notify()->dai_formats().find(dai_element_id);
ASSERT_NE(format_match, notify()->dai_formats().end());
ASSERT_TRUE(format_match->second.has_value());
EXPECT_TRUE(ValidateDaiFormat(format_match->second.value()));
EXPECT_TRUE(notify()->codec_format_infos().empty());
EXPECT_TRUE(notify()->dai_format_errors().empty());
notify()->clear_dai_format(dai_element_id);
device->SetDaiFormat(dai_element_id, safe_format2);
RunLoopUntilIdle();
// SetDaiFormat with no-change should cause a DaiElement to emit a not-set notification with 0.
format_match = notify()->dai_formats().find(dai_element_id);
EXPECT_TRUE(ExpectDaiFormatMatches(dai_element_id, safe_format2));
EXPECT_TRUE(notify()->codec_format_infos().empty());
EXPECT_TRUE(notify()->dai_format_errors().empty());
}
}
// Reset stops/releases all RingBuffers and resets all Dais / Elements / Topology.
TEST_F(CompositeTest, Reset) {
auto fake_driver = MakeFakeComposite();
auto device = InitializeDeviceForFakeComposite(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(SetControl(device));
// Set DAI formats on every DAI endpoint.
auto dai_format_sets_by_element = device->dai_format_sets();
// ASSERT_FALSE(dai_format_sets_by_element.empty());
ASSERT_EQ(device->dai_endpoint_ids().size(), dai_format_sets_by_element.size());
for (auto dai_element_id : device->dai_endpoint_ids()) {
auto safe_format =
SafeDaiFormatFromElementDaiFormatSets(dai_element_id, dai_format_sets_by_element);
device->SetDaiFormat(dai_element_id, safe_format);
RunLoopUntilIdle();
ASSERT_TRUE(ExpectDaiFormatMatches(dai_element_id, safe_format));
}
// Create RingBuffers on every RingBuffer endpoint.
auto ring_buffer_format_sets_by_element = ElementDriverRingBufferFormatSets(device);
// ASSERT_FALSE(ring_buffer_format_sets_by_element.empty());
ASSERT_EQ(device->ring_buffer_endpoint_ids().size(), ring_buffer_format_sets_by_element.size());
for (auto element_id : device->ring_buffer_endpoint_ids()) {
fake_driver->ReserveRingBufferSize(element_id, 4096);
auto safe_format = SafeDriverRingBufferFormatFromElementDriverRingBufferFormatSets(
element_id, ring_buffer_format_sets_by_element);
std::stringstream stream;
stream << "Validating CreateRingBuffer on element_id " << element_id << " with format "
<< *safe_format.pcm_format();
SCOPED_TRACE(stream.str());
uint32_t requested_ring_buffer_bytes = 2000;
auto callback_received = false;
Device::RingBufferInfo rb_info;
ASSERT_TRUE(device->CreateRingBuffer(
element_id, safe_format, requested_ring_buffer_bytes,
[&callback_received](const fit::result<fuchsia_audio_device::ControlCreateRingBufferError,
Device::RingBufferInfo>& result) {
callback_received = true;
EXPECT_TRUE(result.is_ok());
}));
RunLoopUntilIdle();
ASSERT_TRUE(callback_received);
}
EXPECT_EQ(FakeCompositeRingBuffer::count(), device->ring_buffer_endpoint_ids().size());
EXPECT_TRUE(device->Reset());
RunLoopUntilIdle();
// Reset should cause every DaiElement to emit DaiFormatChanged(id, std::nullopt, std::nullopt).
// So notify()->dai_formats() should contain an entry for each Dai element, of value std::nullopt.
EXPECT_EQ(notify()->dai_formats().size(), device->dai_endpoint_ids().size());
for (const auto& [element_id, format] : notify()->dai_formats()) {
EXPECT_FALSE(format.has_value()) << "{element_id " << element_id << "}";
}
EXPECT_TRUE(notify()->codec_format_infos().empty());
EXPECT_TRUE(notify()->dai_format_errors().empty());
// Expect any RingBuffers to drop, eventually. Our Control should still be valid though.
EXPECT_EQ(FakeCompositeRingBuffer::count(), 0u);
// Expect ElementState notifications, when implemented.
// Also expect (maybe) a TopologyChanged notification
}
// RingBuffer test cases
//
// Creating a RingBuffer should succeed.
void CompositeTest::TestCreateRingBuffer(const std::shared_ptr<Device>& device,
ElementId element_id,
const fuchsia_hardware_audio::Format& safe_format) {
std::stringstream stream;
stream << "Validating CreateRingBuffer on element_id " << element_id << " with format "
<< *safe_format.pcm_format();
SCOPED_TRACE(stream.str());
uint32_t requested_ring_buffer_bytes = 4000;
auto callback_received = false;
Device::RingBufferInfo rb_info;
EXPECT_TRUE(device->CreateRingBuffer(
element_id, safe_format, requested_ring_buffer_bytes,
[&callback_received, &rb_info](
fit::result<fuchsia_audio_device::ControlCreateRingBufferError, Device::RingBufferInfo>
result) {
callback_received = true;
ASSERT_TRUE(result.is_ok());
rb_info = std::move(result.value());
}));
RunLoopUntilIdle();
ASSERT_TRUE(callback_received);
ASSERT_TRUE(rb_info.properties.turn_on_delay().has_value());
EXPECT_GE(*rb_info.properties.turn_on_delay(), 0);
ASSERT_TRUE(rb_info.properties.valid_bits_per_sample().has_value());
EXPECT_LE(*rb_info.properties.valid_bits_per_sample(),
safe_format.pcm_format()->bytes_per_sample() * 8);
ASSERT_TRUE(rb_info.ring_buffer.buffer().has_value());
EXPECT_GT(rb_info.ring_buffer.buffer()->size(), requested_ring_buffer_bytes);
EXPECT_TRUE(rb_info.ring_buffer.buffer()->vmo().is_valid());
ASSERT_TRUE(rb_info.ring_buffer.consumer_bytes().has_value());
EXPECT_GT(*rb_info.ring_buffer.consumer_bytes(), 0u);
ASSERT_TRUE(rb_info.ring_buffer.producer_bytes().has_value());
EXPECT_GT(*rb_info.ring_buffer.producer_bytes(), 0u);
EXPECT_TRUE(*rb_info.ring_buffer.consumer_bytes() >= requested_ring_buffer_bytes ||
*rb_info.ring_buffer.producer_bytes() >= requested_ring_buffer_bytes);
ASSERT_TRUE(rb_info.ring_buffer.format()->channel_count().has_value());
EXPECT_EQ(*rb_info.ring_buffer.format()->channel_count(),
safe_format.pcm_format()->number_of_channels());
ASSERT_TRUE(rb_info.ring_buffer.format()->sample_type().has_value());
ASSERT_TRUE(rb_info.ring_buffer.format()->frames_per_second().has_value());
EXPECT_EQ(*rb_info.ring_buffer.format()->frames_per_second(),
safe_format.pcm_format()->frame_rate());
ASSERT_TRUE(rb_info.ring_buffer.reference_clock().has_value());
EXPECT_TRUE(rb_info.ring_buffer.reference_clock()->is_valid());
ASSERT_TRUE(rb_info.ring_buffer.reference_clock_domain().has_value());
EXPECT_EQ(*rb_info.ring_buffer.reference_clock_domain(),
fuchsia_hardware_audio::kClockDomainMonotonic);
// Now client can drop its RingBuffer connection.
device->DropRingBuffer(element_id);
}
TEST_F(CompositeTest, CreateRingBuffers) {
auto fake_driver = MakeFakeComposite();
auto device = InitializeDeviceForFakeComposite(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(SetControl(device));
auto ring_buffer_format_sets_by_element = ElementDriverRingBufferFormatSets(device);
ASSERT_FALSE(ring_buffer_format_sets_by_element.empty());
ASSERT_EQ(device->ring_buffer_endpoint_ids().size(), ring_buffer_format_sets_by_element.size());
for (auto element_id : device->ring_buffer_endpoint_ids()) {
fake_driver->ReserveRingBufferSize(element_id, 8192);
auto safe_format = SafeDriverRingBufferFormatFromElementDriverRingBufferFormatSets(
element_id, ring_buffer_format_sets_by_element);
TestCreateRingBuffer(device, element_id, safe_format);
}
}
// Tests on the Composite RingBuffer interface
//
// Maybe, rather than testing the entire CreateRingBuffer meta-method, we should test the individual
// component-level methods: ConnectRingBufferFidl, RetrieveRingBufferProperties, GetVmo.
// Verify that we get DeviceDroppedRingBuffer notifications
TEST_F(CompositeTest, DeviceDroppedRingBuffer) {
auto fake_driver = MakeFakeComposite();
auto device = InitializeDeviceForFakeComposite(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(SetControl(device));
auto ring_buffer_format_sets_by_element = ElementDriverRingBufferFormatSets(device);
ASSERT_FALSE(ring_buffer_format_sets_by_element.empty());
ASSERT_EQ(device->ring_buffer_endpoint_ids().size(), ring_buffer_format_sets_by_element.size());
constexpr uint32_t reserved_ring_buffer_bytes = 8192;
constexpr uint32_t requested_ring_buffer_bytes = 4000;
for (auto element_id : device->ring_buffer_endpoint_ids()) {
SCOPED_TRACE(std::string("Testing DropRingBuffer: element_id ") + std::to_string(element_id));
fake_driver->ReserveRingBufferSize(element_id, reserved_ring_buffer_bytes);
auto safe_format = SafeDriverRingBufferFormatFromElementDriverRingBufferFormatSets(
element_id, ring_buffer_format_sets_by_element);
auto callback_received = false;
Device::RingBufferInfo rb_info;
ASSERT_TRUE(device->CreateRingBuffer(
element_id, safe_format, requested_ring_buffer_bytes,
[&callback_received](const fit::result<fuchsia_audio_device::ControlCreateRingBufferError,
Device::RingBufferInfo>& result) {
callback_received = true;
EXPECT_TRUE(result.is_ok());
}));
RunLoopUntilIdle();
ASSERT_TRUE(callback_received);
ASSERT_TRUE(RingBufferIsStopped(device, element_id));
fake_driver->DropRingBuffer(element_id);
RunLoopUntilIdle();
EXPECT_FALSE(HasRingBuffer(device, element_id));
EXPECT_EQ(device_presence_watcher()->ready_devices().size(), 1u);
EXPECT_EQ(device_presence_watcher()->error_devices().size(), 0u);
}
}
// Verify Start and Stop
TEST_F(CompositeTest, RingBufferStartAndStop) {
auto fake_driver = MakeFakeComposite();
auto device = InitializeDeviceForFakeComposite(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(SetControl(device));
auto ring_buffer_format_sets_by_element = ElementDriverRingBufferFormatSets(device);
ASSERT_FALSE(ring_buffer_format_sets_by_element.empty());
ASSERT_EQ(device->ring_buffer_endpoint_ids().size(), ring_buffer_format_sets_by_element.size());
constexpr uint32_t reserved_ring_buffer_bytes = 8192;
constexpr uint32_t requested_ring_buffer_bytes = 4000;
for (auto element_id : device->ring_buffer_endpoint_ids()) {
SCOPED_TRACE(std::string("Testing RingBuffer Start/Stop: element_id ") +
std::to_string(element_id));
fake_driver->ReserveRingBufferSize(element_id, reserved_ring_buffer_bytes);
auto safe_format = SafeDriverRingBufferFormatFromElementDriverRingBufferFormatSets(
element_id, ring_buffer_format_sets_by_element);
auto callback_received = false;
Device::RingBufferInfo rb_info;
ASSERT_TRUE(device->CreateRingBuffer(
element_id, safe_format, requested_ring_buffer_bytes,
[&callback_received](const fit::result<fuchsia_audio_device::ControlCreateRingBufferError,
Device::RingBufferInfo>& result) {
callback_received = true;
EXPECT_TRUE(result.is_ok());
}));
RunLoopUntilIdle();
ASSERT_TRUE(callback_received);
zx::time before_start = zx::clock::get_monotonic();
zx::time start_time;
callback_received = false;
device->StartRingBuffer(element_id,
[&callback_received, &start_time](zx::result<zx::time> result) {
callback_received = true;
EXPECT_TRUE(result.is_ok());
start_time = result.value();
EXPECT_LT(start_time.get(), zx::clock::get_monotonic().get());
});
RunLoopUntilIdle();
ASSERT_TRUE(callback_received);
EXPECT_GT(start_time.get(), before_start.get());
callback_received = false;
device->StopRingBuffer(element_id, [&callback_received](zx_status_t status) {
callback_received = true;
EXPECT_EQ(status, ZX_OK);
});
RunLoopUntilIdle();
EXPECT_TRUE(callback_received);
}
}
// Verify SetActiveChannels -- for a driver that supports it.
TEST_F(CompositeTest, SetActiveChannelsSupported) {
auto fake_driver = MakeFakeComposite();
auto device = InitializeDeviceForFakeComposite(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(SetControl(device));
auto ring_buffer_format_sets_by_element = ElementDriverRingBufferFormatSets(device);
ASSERT_FALSE(ring_buffer_format_sets_by_element.empty());
ASSERT_EQ(device->ring_buffer_endpoint_ids().size(), ring_buffer_format_sets_by_element.size());
constexpr uint32_t reserved_ring_buffer_bytes = 8192;
constexpr uint32_t requested_ring_buffer_bytes = 4000;
for (auto element_id : device->ring_buffer_endpoint_ids()) {
SCOPED_TRACE(std::string("Testing SetActiveChannels (supported): element_id ") +
std::to_string(element_id));
fake_driver->EnableActiveChannelsSupport(element_id);
fake_driver->ReserveRingBufferSize(element_id, reserved_ring_buffer_bytes);
auto safe_format = SafeDriverRingBufferFormatFromElementDriverRingBufferFormatSets(
element_id, ring_buffer_format_sets_by_element);
auto callback_received = false;
Device::RingBufferInfo rb_info;
ASSERT_TRUE(device->CreateRingBuffer(
element_id, safe_format, requested_ring_buffer_bytes,
[&callback_received](const fit::result<fuchsia_audio_device::ControlCreateRingBufferError,
Device::RingBufferInfo>& result) {
callback_received = true;
EXPECT_TRUE(result.is_ok());
}));
RunLoopUntilIdle();
ASSERT_TRUE(callback_received);
uint64_t channel_bitmask = (1U << safe_format.pcm_format()->number_of_channels()) - 2;
zx::time set_time;
callback_received = false;
zx::time before_set = zx::clock::get_monotonic();
auto succeeded = device->SetActiveChannels(
element_id, channel_bitmask, [&callback_received, &set_time](zx::result<zx::time> result) {
callback_received = true;
EXPECT_TRUE(result.is_ok());
set_time = result.value();
EXPECT_LT(set_time.get(), zx::clock::get_monotonic().get());
});
RunLoopUntilIdle();
ASSERT_TRUE(succeeded);
ASSERT_TRUE(callback_received);
EXPECT_GT(set_time.get(), before_set.get());
// Use the same channel_bitmask value -- it should succeed, but set_time should be unchanged.
zx::time set_time2;
callback_received = false;
zx::time before_set2 = zx::clock::get_monotonic();
succeeded = device->SetActiveChannels(
element_id, channel_bitmask, [&callback_received, &set_time2](zx::result<zx::time> result) {
callback_received = true;
EXPECT_TRUE(result.is_ok());
set_time2 = result.value();
});
RunLoopUntilIdle();
EXPECT_TRUE(succeeded);
EXPECT_TRUE(callback_received);
EXPECT_LT(set_time2.get(), before_set2.get());
EXPECT_EQ(set_time.get(), set_time2.get())
<< "set_time should only change if the channel bitmask does (still " << std::hex
<< channel_bitmask << ")";
}
}
// Verify SetActiveChannels -- for a driver that does not support it.
TEST_F(CompositeTest, SetActiveChannelsUnsupported) {
auto fake_driver = MakeFakeComposite();
auto device = InitializeDeviceForFakeComposite(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(SetControl(device));
auto ring_buffer_format_sets_by_element = ElementDriverRingBufferFormatSets(device);
ASSERT_FALSE(ring_buffer_format_sets_by_element.empty());
ASSERT_EQ(device->ring_buffer_endpoint_ids().size(), ring_buffer_format_sets_by_element.size());
constexpr uint32_t reserved_ring_buffer_bytes = 8192;
constexpr uint32_t requested_ring_buffer_bytes = 4000;
for (auto element_id : device->ring_buffer_endpoint_ids()) {
SCOPED_TRACE(std::string("Testing SetActiveChannels (unsupported): element_id ") +
std::to_string(element_id));
fake_driver->DisableActiveChannelsSupport(element_id);
fake_driver->ReserveRingBufferSize(element_id, reserved_ring_buffer_bytes);
auto safe_format = SafeDriverRingBufferFormatFromElementDriverRingBufferFormatSets(
element_id, ring_buffer_format_sets_by_element);
auto callback_received = false;
Device::RingBufferInfo rb_info;
ASSERT_TRUE(device->CreateRingBuffer(
element_id, safe_format, requested_ring_buffer_bytes,
[&callback_received](const fit::result<fuchsia_audio_device::ControlCreateRingBufferError,
Device::RingBufferInfo>& result) {
callback_received = true;
EXPECT_TRUE(result.is_ok());
}));
RunLoopUntilIdle();
ASSERT_TRUE(callback_received);
uint64_t channel_bitmask = (1U << safe_format.pcm_format()->number_of_channels()) - 2;
callback_received = false;
// During CreateRingBuffer, SetActiveChannels is called, so Device already knows if RingBuffer
// supports this method. In this case it does NOT (DisableActiveChannelsSupport above), so we
// expect the method to return false (meaning it did NOT call the driver), without no callback.
auto succeeded = device->SetActiveChannels(
element_id, channel_bitmask, [&callback_received](zx::result<zx::time> result) {
callback_received = true;
FAIL() << "Unexpected response to SetActiveChannels";
});
RunLoopUntilIdle();
EXPECT_FALSE(succeeded);
EXPECT_FALSE(callback_received);
}
}
// Verify that we get DelayInfo change notifications
TEST_F(CompositeTest, WatchDelayInfoInitial) {
auto fake_driver = MakeFakeComposite();
auto device = InitializeDeviceForFakeComposite(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(SetControl(device));
auto ring_buffer_format_sets_by_element = ElementDriverRingBufferFormatSets(device);
ASSERT_FALSE(ring_buffer_format_sets_by_element.empty());
ASSERT_EQ(device->ring_buffer_endpoint_ids().size(), ring_buffer_format_sets_by_element.size());
constexpr uint32_t reserved_ring_buffer_bytes = 8192;
constexpr uint32_t requested_ring_buffer_bytes = 4000;
for (auto element_id : device->ring_buffer_endpoint_ids()) {
SCOPED_TRACE(std::string("Testing RingBuffer initial DelayInfo: element_id ") +
std::to_string(element_id));
fake_driver->ReserveRingBufferSize(element_id, reserved_ring_buffer_bytes);
auto safe_format = SafeDriverRingBufferFormatFromElementDriverRingBufferFormatSets(
element_id, ring_buffer_format_sets_by_element);
auto created_ring_buffer = false;
Device::RingBufferInfo rb_info;
ASSERT_TRUE(device->CreateRingBuffer(
element_id, safe_format, requested_ring_buffer_bytes,
[&created_ring_buffer](const fit::result<fuchsia_audio_device::ControlCreateRingBufferError,
Device::RingBufferInfo>& result) {
created_ring_buffer = true;
EXPECT_TRUE(result.is_ok());
}));
RunLoopUntilIdle();
ASSERT_TRUE(created_ring_buffer);
// Verify that the Device received the expected values from the driver.
ASSERT_TRUE(DeviceDelayInfo(device, element_id));
ASSERT_TRUE(DeviceDelayInfo(device, element_id)->internal_delay());
EXPECT_EQ(*DeviceDelayInfo(device, element_id)->internal_delay(),
FakeCompositeRingBuffer::kDefaultInternalDelay->get());
EXPECT_TRUE(!DeviceDelayInfo(device, element_id)->external_delay().has_value() &&
!FakeCompositeRingBuffer::kDefaultExternalDelay.has_value());
// Verify that the ControlNotify was sent the expected values.
ASSERT_TRUE(notify()->delay_info(element_id))
<< "ControlNotify was not notified of initial delay info";
ASSERT_TRUE(notify()->delay_info(element_id)->internal_delay());
EXPECT_EQ(*notify()->delay_info(element_id)->internal_delay(),
FakeCompositeRingBuffer::kDefaultInternalDelay->get());
EXPECT_TRUE(!notify()->delay_info(element_id)->external_delay().has_value() &&
!FakeCompositeRingBuffer::kDefaultExternalDelay.has_value());
}
}
// Dynamic delay updates
TEST_F(CompositeTest, WatchDelayInfoUpdate) {
auto fake_driver = MakeFakeComposite();
auto device = InitializeDeviceForFakeComposite(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(SetControl(device));
auto ring_buffer_format_sets_by_element = ElementDriverRingBufferFormatSets(device);
ASSERT_FALSE(ring_buffer_format_sets_by_element.empty());
ASSERT_EQ(device->ring_buffer_endpoint_ids().size(), ring_buffer_format_sets_by_element.size());
constexpr uint32_t reserved_ring_buffer_bytes = 8192;
constexpr uint32_t requested_ring_buffer_bytes = 4000;
for (auto element_id : device->ring_buffer_endpoint_ids()) {
SCOPED_TRACE(std::string("Testing RingBuffer initial DelayInfo: element_id ") +
std::to_string(element_id));
fake_driver->ReserveRingBufferSize(element_id, reserved_ring_buffer_bytes);
auto safe_format = SafeDriverRingBufferFormatFromElementDriverRingBufferFormatSets(
element_id, ring_buffer_format_sets_by_element);
auto created_ring_buffer = false;
Device::RingBufferInfo rb_info;
ASSERT_TRUE(device->CreateRingBuffer(
element_id, safe_format, requested_ring_buffer_bytes,
[&created_ring_buffer](const fit::result<fuchsia_audio_device::ControlCreateRingBufferError,
Device::RingBufferInfo>& result) {
created_ring_buffer = true;
EXPECT_TRUE(result.is_ok());
}));
RunLoopUntilIdle();
ASSERT_TRUE(created_ring_buffer);
ASSERT_TRUE(DeviceDelayInfo(device, element_id));
ASSERT_TRUE(notify()->delay_info(element_id))
<< "ControlNotify was not notified of initial delay info";
}
notify()->clear_delay_infos();
for (auto element_id : device->ring_buffer_endpoint_ids()) {
EXPECT_FALSE(notify()->delay_info(element_id))
<< "ControlNotify was not cleared for element_id " << element_id;
// For each element, inject int_delay [`element_id` nsec] and ext_delay [`element_id` usec].
fake_driver->InjectDelayUpdate(element_id, zx::nsec(static_cast<int64_t>(element_id)),
zx::usec(static_cast<int64_t>(element_id)));
}
RunLoopUntilIdle();
for (auto element_id : device->ring_buffer_endpoint_ids()) {
// Ensure the Device received the updated values from the driver.
ASSERT_TRUE(DeviceDelayInfo(device, element_id));
ASSERT_TRUE(DeviceDelayInfo(device, element_id)->internal_delay());
ASSERT_TRUE(DeviceDelayInfo(device, element_id)->external_delay());
EXPECT_EQ(*DeviceDelayInfo(device, element_id)->internal_delay(), ZX_NSEC(element_id));
EXPECT_EQ(*DeviceDelayInfo(device, element_id)->external_delay(), ZX_USEC(element_id));
// Ensure that ControlNotify received these updates as well.
ASSERT_TRUE(notify()->delay_info(element_id))
<< "ControlNotify was not notified with updated delay info";
ASSERT_TRUE(notify()->delay_info(element_id)->internal_delay());
ASSERT_TRUE(notify()->delay_info(element_id)->external_delay());
EXPECT_EQ(*notify()->delay_info(element_id)->internal_delay(), ZX_NSEC(element_id));
EXPECT_EQ(*notify()->delay_info(element_id)->external_delay(), ZX_USEC(element_id));
}
}
//// This is needed to support hardware where the audio device is NOT in kClockDomainMonotonic.
//// Until then, it is not a high priority.
// TEST_F(CompositeTest, PositionNotifications) { FAIL() << "NOT YET IMPLEMENTED"; }
// Signalprocessing test cases
//
// Ensure that we captured the full contents of FakeComposite signalprocessing functionality,
// specifically the signalprocessing elements.
TEST_F(CompositeTest, GetElements) {
auto fake_driver = MakeFakeComposite();
auto device = InitializeDeviceForFakeComposite(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(device->info().has_value());
ASSERT_TRUE(device->info()->signal_processing_elements().has_value());
auto& elements = device->info()->signal_processing_elements();
ASSERT_TRUE(elements.has_value());
ASSERT_EQ(elements->size(), FakeComposite::kElements.size());
// Get all the `has_value` checks done in automated fashion upfront.
for (const auto& element : *elements) {
ASSERT_TRUE(element.id().has_value());
ASSERT_TRUE(elements->at(0).type().has_value());
ASSERT_TRUE(element.description().has_value());
ASSERT_TRUE(element.can_disable().has_value());
if (element.type() == fuchsia_hardware_audio_signalprocessing::ElementType::kEndpoint) {
ASSERT_TRUE(element.type_specific().has_value());
ASSERT_TRUE(element.type_specific()->endpoint().has_value());
ASSERT_TRUE(element.type_specific()->endpoint()->type().has_value());
ASSERT_TRUE(element.type_specific()->endpoint()->plug_detect_capabilities().has_value());
}
}
EXPECT_EQ(elements->at(0).id(), FakeComposite::kSourceDaiElementId);
EXPECT_EQ(elements->at(1).id(), FakeComposite::kDestDaiElementId);
EXPECT_EQ(elements->at(0).type(),
fuchsia_hardware_audio_signalprocessing::ElementType::kEndpoint);
EXPECT_EQ(elements->at(1).type(),
fuchsia_hardware_audio_signalprocessing::ElementType::kEndpoint);
EXPECT_FALSE(elements->at(0).can_disable().value());
EXPECT_FALSE(elements->at(1).can_disable().value());
EXPECT_EQ(*elements->at(0).description(), *FakeComposite::kSourceDaiElement.description());
EXPECT_EQ(*elements->at(1).description(), *FakeComposite::kDestDaiElement.description());
EXPECT_EQ(*elements->at(0).type_specific()->endpoint()->type(),
fuchsia_hardware_audio_signalprocessing::EndpointType::kDaiInterconnect);
EXPECT_EQ(*elements->at(1).type_specific()->endpoint()->type(),
fuchsia_hardware_audio_signalprocessing::EndpointType::kDaiInterconnect);
EXPECT_EQ(elements->at(0).type_specific()->endpoint()->plug_detect_capabilities(),
fuchsia_hardware_audio_signalprocessing::PlugDetectCapabilities::kCanAsyncNotify);
EXPECT_EQ(elements->at(1).type_specific()->endpoint()->plug_detect_capabilities(),
fuchsia_hardware_audio_signalprocessing::PlugDetectCapabilities::kCanAsyncNotify);
EXPECT_EQ(elements->at(2).id(), FakeComposite::kSourceRbElementId);
EXPECT_EQ(elements->at(3).id(), FakeComposite::kDestRbElementId);
EXPECT_EQ(elements->at(2).type(),
fuchsia_hardware_audio_signalprocessing::ElementType::kEndpoint);
EXPECT_EQ(elements->at(3).type(),
fuchsia_hardware_audio_signalprocessing::ElementType::kEndpoint);
EXPECT_FALSE(elements->at(2).can_disable().value());
EXPECT_FALSE(elements->at(3).can_disable().value());
EXPECT_EQ(*elements->at(2).description(), *FakeComposite::kSourceRbElement.description());
EXPECT_EQ(*elements->at(3).description(), *FakeComposite::kDestRbElement.description());
EXPECT_EQ(*elements->at(2).type_specific()->endpoint()->type(),
fuchsia_hardware_audio_signalprocessing::EndpointType::kRingBuffer);
EXPECT_EQ(*elements->at(3).type_specific()->endpoint()->type(),
fuchsia_hardware_audio_signalprocessing::EndpointType::kRingBuffer);
EXPECT_EQ(elements->at(2).type_specific()->endpoint()->plug_detect_capabilities(),
fuchsia_hardware_audio_signalprocessing::PlugDetectCapabilities::kHardwired);
EXPECT_EQ(elements->at(3).type_specific()->endpoint()->plug_detect_capabilities(),
fuchsia_hardware_audio_signalprocessing::PlugDetectCapabilities::kHardwired);
EXPECT_EQ(elements->at(4).id(), FakeComposite::kMuteElementId);
EXPECT_EQ(elements->at(4).type(), fuchsia_hardware_audio_signalprocessing::ElementType::kMute);
EXPECT_TRUE(elements->at(4).can_disable().value());
EXPECT_EQ(*elements->at(4).description(), *FakeComposite::kMuteElement.description());
EXPECT_FALSE(elements->at(4).type_specific().has_value());
}
// Ensure that we captured the full contents of FakeComposite signalprocessing functionality,
// specifically the signalprocessing topologies.
TEST_F(CompositeTest, GetTopologies) {
auto fake_driver = MakeFakeComposite();
auto device = InitializeDeviceForFakeComposite(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(device->info().has_value());
ASSERT_TRUE(device->info()->signal_processing_elements().has_value());
auto& topologies = device->info()->signal_processing_topologies();
ASSERT_EQ(topologies->size(), FakeComposite::kTopologies.size());
ASSERT_TRUE(topologies->at(0).id().has_value());
EXPECT_EQ(*topologies->at(0).id(), FakeComposite::kInputOnlyTopologyId);
ASSERT_TRUE(topologies->at(0).processing_elements_edge_pairs().has_value());
EXPECT_EQ(topologies->at(0).processing_elements_edge_pairs()->size(), 1u);
EXPECT_EQ(topologies->at(0).processing_elements_edge_pairs()->at(0).processing_element_id_from(),
FakeComposite::kSourceDaiElementId);
EXPECT_EQ(topologies->at(0).processing_elements_edge_pairs()->at(0).processing_element_id_to(),
FakeComposite::kDestRbElementId);
ASSERT_TRUE(topologies->at(1).id().has_value());
EXPECT_EQ(*topologies->at(1).id(), FakeComposite::kFullDuplexTopologyId);
ASSERT_TRUE(topologies->at(1).processing_elements_edge_pairs().has_value());
EXPECT_EQ(topologies->at(1).processing_elements_edge_pairs()->size(), 2u);
EXPECT_EQ(topologies->at(1).processing_elements_edge_pairs()->at(0).processing_element_id_from(),
FakeComposite::kSourceDaiElementId);
EXPECT_EQ(topologies->at(1).processing_elements_edge_pairs()->at(0).processing_element_id_to(),
FakeComposite::kDestRbElementId);
EXPECT_EQ(topologies->at(1).processing_elements_edge_pairs()->at(1).processing_element_id_from(),
FakeComposite::kSourceRbElementId);
EXPECT_EQ(topologies->at(1).processing_elements_edge_pairs()->at(1).processing_element_id_to(),
FakeComposite::kDestDaiElementId);
ASSERT_TRUE(topologies->at(2).id().has_value());
EXPECT_EQ(*topologies->at(2).id(), FakeComposite::kOutputOnlyTopologyId);
ASSERT_TRUE(topologies->at(2).processing_elements_edge_pairs().has_value());
EXPECT_EQ(topologies->at(2).processing_elements_edge_pairs()->size(), 1u);
EXPECT_EQ(topologies->at(2).processing_elements_edge_pairs()->at(0).processing_element_id_from(),
FakeComposite::kSourceRbElementId);
EXPECT_EQ(topologies->at(2).processing_elements_edge_pairs()->at(0).processing_element_id_to(),
FakeComposite::kDestDaiElementId);
}
// Ensure that we captured the full contents of FakeComposite signalprocessing functionality,
// specifically the initial signalprocessing state.
TEST_F(CompositeTest, WatchElementStateInitial) {
auto fake_driver = MakeFakeComposite();
auto device = InitializeDeviceForFakeComposite(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(AddObserver(device));
const auto& states = notify()->element_states();
ASSERT_EQ(states.size(), FakeComposite::kElements.size());
auto state = states.find(FakeComposite::kSourceDaiElementId)->second;
ASSERT_TRUE(state.enabled().has_value());
ASSERT_TRUE(state.latency().has_value());
ASSERT_TRUE(state.type_specific().has_value());
ASSERT_TRUE(state.vendor_specific_data().has_value());
EXPECT_TRUE(*state.enabled());
ASSERT_EQ(state.latency()->Which(),
fuchsia_hardware_audio_signalprocessing::Latency::Tag::kLatencyTime);
ASSERT_TRUE(state.latency()->latency_time().has_value());
EXPECT_EQ(state.latency()->latency_time().value(),
FakeComposite::kSourceDaiElementLatency.latency_time().value());
ASSERT_EQ(state.type_specific()->Which(),
fuchsia_hardware_audio_signalprocessing::TypeSpecificElementState::Tag::kEndpoint);
const auto& endpt_state1 = state.type_specific()->endpoint();
ASSERT_TRUE(endpt_state1.has_value());
ASSERT_TRUE(endpt_state1->plug_state().has_value());
ASSERT_TRUE(endpt_state1->plug_state()->plugged().has_value());
EXPECT_TRUE(*endpt_state1->plug_state()->plugged());
EXPECT_EQ(*endpt_state1->plug_state()->plug_state_time(), ZX_TIME_INFINITE_PAST);
ASSERT_EQ(state.vendor_specific_data()->size(), 8u);
EXPECT_EQ(state.vendor_specific_data()->at(0), 1u);
EXPECT_EQ(state.vendor_specific_data()->at(7), 8u);
state = states.find(FakeComposite::kDestDaiElementId)->second;
ASSERT_TRUE(state.enabled().has_value());
ASSERT_TRUE(state.latency().has_value());
ASSERT_TRUE(state.type_specific().has_value());
ASSERT_TRUE(state.vendor_specific_data().has_value());
EXPECT_TRUE(*state.enabled());
ASSERT_EQ(state.latency()->Which(),
fuchsia_hardware_audio_signalprocessing::Latency::Tag::kLatencyTime);
ASSERT_TRUE(state.latency()->latency_time().has_value());
EXPECT_EQ(state.latency()->latency_time().value(),
FakeComposite::kDestDaiElementLatency.latency_time().value());
ASSERT_EQ(state.type_specific()->Which(),
fuchsia_hardware_audio_signalprocessing::TypeSpecificElementState::Tag::kEndpoint);
const auto& endpt_state2 = state.type_specific()->endpoint();
ASSERT_TRUE(endpt_state2.has_value());
ASSERT_TRUE(endpt_state2->plug_state().has_value());
ASSERT_TRUE(endpt_state2->plug_state()->plugged().has_value());
EXPECT_TRUE(*endpt_state2->plug_state()->plugged());
EXPECT_EQ(*endpt_state2->plug_state()->plug_state_time(), ZX_TIME_INFINITE_PAST);
ASSERT_EQ(state.vendor_specific_data()->size(), 9u);
EXPECT_EQ(state.vendor_specific_data()->at(0), 8u);
EXPECT_EQ(state.vendor_specific_data()->at(8), 0u);
state = states.find(FakeComposite::kSourceRbElementId)->second;
ASSERT_TRUE(state.enabled().has_value());
ASSERT_TRUE(state.latency().has_value());
ASSERT_TRUE(state.type_specific().has_value());
EXPECT_FALSE(state.vendor_specific_data().has_value());
EXPECT_TRUE(*state.enabled());
ASSERT_EQ(state.latency()->Which(),
fuchsia_hardware_audio_signalprocessing::Latency::Tag::kLatencyFrames);
ASSERT_TRUE(states.find(FakeComposite::kSourceRbElementId)
->second.latency()
->latency_frames()
.has_value());
EXPECT_EQ(state.latency()->latency_frames().value(),
FakeComposite::kSourceRbElementLatency.latency_frames().value());
ASSERT_EQ(state.type_specific()->Which(),
fuchsia_hardware_audio_signalprocessing::TypeSpecificElementState::Tag::kEndpoint);
const auto& endpt_state3 = state.type_specific()->endpoint();
ASSERT_TRUE(endpt_state3.has_value());
ASSERT_TRUE(endpt_state3->plug_state().has_value());
ASSERT_TRUE(endpt_state3->plug_state()->plugged().has_value());
EXPECT_TRUE(*endpt_state3->plug_state()->plugged());
EXPECT_EQ(*endpt_state3->plug_state()->plug_state_time(), ZX_TIME_INFINITE_PAST);
state = states.find(FakeComposite::kDestRbElementId)->second;
ASSERT_TRUE(state.enabled().has_value());
ASSERT_TRUE(state.latency().has_value());
ASSERT_TRUE(state.type_specific().has_value());
EXPECT_FALSE(state.vendor_specific_data().has_value());
EXPECT_TRUE(*state.enabled());
ASSERT_EQ(state.latency()->Which(),
fuchsia_hardware_audio_signalprocessing::Latency::Tag::kLatencyFrames);
ASSERT_TRUE(state.latency()->latency_frames().has_value());
EXPECT_EQ(state.latency()->latency_frames().value(),
FakeComposite::kDestRbElementLatency.latency_frames().value());
ASSERT_EQ(state.type_specific()->Which(),
fuchsia_hardware_audio_signalprocessing::TypeSpecificElementState::Tag::kEndpoint);
const auto& endpt_state4 = state.type_specific()->endpoint();
ASSERT_TRUE(endpt_state4.has_value());
ASSERT_TRUE(endpt_state4->plug_state().has_value());
ASSERT_TRUE(endpt_state4->plug_state()->plugged().has_value());
EXPECT_TRUE(*endpt_state4->plug_state()->plugged());
EXPECT_EQ(*endpt_state4->plug_state()->plug_state_time(), ZX_TIME_INFINITE_PAST);
state = states.find(FakeComposite::kMuteElementId)->second;
ASSERT_TRUE(state.enabled().has_value());
EXPECT_FALSE(state.latency().has_value());
EXPECT_FALSE(state.type_specific().has_value());
EXPECT_FALSE(state.vendor_specific_data().has_value());
EXPECT_FALSE(*state.enabled());
}
TEST_F(CompositeTest, WatchElementStateUpdate) {
auto fake_driver = MakeFakeComposite();
auto device = InitializeDeviceForFakeComposite(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(AddObserver(device));
auto& elements = *device->info()->signal_processing_elements();
const auto& states = notify()->element_states();
ASSERT_EQ(states.size(), FakeComposite::kElements.size());
// Determine which states we can inject change into.
std::unordered_map<ElementId, fuchsia_hardware_audio_signalprocessing::ElementState>
element_states_to_inject;
auto plug_change_time_to_inject = zx::clock::get_monotonic();
for (const auto& element : elements) {
auto element_id = *element.id();
auto match_state = states.find(element_id);
ASSERT_NE(match_state, states.end());
auto state = match_state->second;
// Handle the Mute node
if (element.type() == fuchsia_hardware_audio_signalprocessing::ElementType::kMute &&
element.can_disable().value_or(false)) {
// By configuration, our Mute starts disabled (we enable it as our ElementState change).
ASSERT_TRUE(state.enabled().has_value());
EXPECT_FALSE(*state.enabled());
element_states_to_inject.insert_or_assign(
element_id, fuchsia_hardware_audio_signalprocessing::ElementState{{.enabled = true}});
continue;
}
// Then weed out any non-pluggable elements.
if (element.type() != fuchsia_hardware_audio_signalprocessing::ElementType::kEndpoint ||
!element.type_specific().has_value() || !element.type_specific()->endpoint().has_value() ||
element.type_specific()->endpoint()->plug_detect_capabilities() !=
fuchsia_hardware_audio_signalprocessing::PlugDetectCapabilities::kCanAsyncNotify) {
continue;
}
if (!state.type_specific().has_value() || !state.type_specific()->endpoint().has_value() ||
!state.type_specific()->endpoint()->plug_state().has_value() ||
!state.type_specific()->endpoint()->plug_state()->plugged().has_value() ||
!state.type_specific()->endpoint()->plug_state()->plug_state_time().has_value()) {
continue;
}
auto was_plugged = state.type_specific()->endpoint()->plug_state()->plugged();
auto new_state = fuchsia_hardware_audio_signalprocessing::ElementState{{
.type_specific =
fuchsia_hardware_audio_signalprocessing::TypeSpecificElementState::WithEndpoint(
fuchsia_hardware_audio_signalprocessing::EndpointElementState{{
fuchsia_hardware_audio_signalprocessing::PlugState{{
!was_plugged,
plug_change_time_to_inject.get(),
}},
}}),
.enabled = true,
.latency =
fuchsia_hardware_audio_signalprocessing::Latency::WithLatencyTime(ZX_USEC(element_id)),
.vendor_specific_data = {{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
'D', 'E', 'F', 'Z'}}, // 'Z' is located at byte [16].
}};
ASSERT_EQ(new_state.vendor_specific_data()->size(), 17u) << "Test configuration error";
element_states_to_inject.insert_or_assign(element_id, new_state);
}
if (element_states_to_inject.empty()) {
GTEST_SKIP()
<< "No element states can be changed, so dynamic element_state change cannot be tested";
}
notify()->clear_element_states();
// Inject the changes.
for (const auto& [element_id, element_state] : element_states_to_inject) {
fake_driver->InjectElementStateChange(element_id, element_state);
}
RunLoopUntilIdle();
EXPECT_EQ(element_states_to_inject.size(), notify()->element_states().size());
for (const auto& [element_id, state_received] : notify()->element_states()) {
// Compare to actual static values we know.
if (element_id == FakeComposite::kMuteElementId) {
EXPECT_FALSE(state_received.type_specific().has_value());
ASSERT_TRUE(state_received.enabled().has_value());
EXPECT_FALSE(state_received.latency().has_value());
EXPECT_FALSE(state_received.vendor_specific_data().has_value());
EXPECT_EQ(state_received.enabled(), true);
} else {
ASSERT_TRUE(state_received.type_specific().has_value());
ASSERT_TRUE(state_received.type_specific()->endpoint().has_value());
ASSERT_TRUE(state_received.type_specific()->endpoint()->plug_state().has_value());
ASSERT_TRUE(state_received.type_specific()->endpoint()->plug_state()->plugged().has_value());
ASSERT_TRUE(
state_received.type_specific()->endpoint()->plug_state()->plug_state_time().has_value());
EXPECT_EQ(*state_received.type_specific()->endpoint()->plug_state()->plug_state_time(),
plug_change_time_to_inject.get());
ASSERT_TRUE(state_received.enabled().has_value());
EXPECT_EQ(state_received.enabled(), true);
ASSERT_TRUE(state_received.latency().has_value());
ASSERT_EQ(state_received.latency()->Which(),
fuchsia_hardware_audio_signalprocessing::Latency::Tag::kLatencyTime);
EXPECT_EQ(state_received.latency()->latency_time().value(), ZX_USEC(element_id));
ASSERT_TRUE(state_received.vendor_specific_data().has_value());
ASSERT_EQ(state_received.vendor_specific_data()->size(), 17u);
EXPECT_EQ(state_received.vendor_specific_data()->at(16), 'Z');
}
// Compare to what we injected.
ASSERT_FALSE(element_states_to_inject.find(element_id) == element_states_to_inject.end())
<< "WatchElementState response received for unknown element_id " << element_id;
const auto& state_injected = element_states_to_inject.find(element_id)->second;
EXPECT_EQ(state_received, state_injected);
}
}
TEST_F(CompositeTest, WatchTopologyInitial) {
auto fake_driver = MakeFakeComposite();
fake_driver->InjectTopologyChange(FakeComposite::kFullDuplexTopologyId);
auto device = InitializeDeviceForFakeComposite(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(AddObserver(device));
RunLoopUntilIdle();
ASSERT_TRUE(notify()->topology_id().has_value());
EXPECT_EQ(*notify()->topology_id(), FakeComposite::kFullDuplexTopologyId);
}
TEST_F(CompositeTest, WatchTopologyUpdate) {
auto fake_driver = MakeFakeComposite();
fake_driver->InjectTopologyChange(FakeComposite::kFullDuplexTopologyId);
auto device = InitializeDeviceForFakeComposite(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(AddObserver(device));
RunLoopUntilIdle();
ASSERT_TRUE(notify()->topology_id().has_value());
EXPECT_EQ(*notify()->topology_id(), FakeComposite::kFullDuplexTopologyId);
notify()->clear_topology_id();
RunLoopUntilIdle();
EXPECT_FALSE(notify()->topology_id().has_value());
fake_driver->InjectTopologyChange(FakeComposite::kInputOnlyTopologyId);
RunLoopUntilIdle();
ASSERT_TRUE(notify()->topology_id().has_value());
EXPECT_EQ(*notify()->topology_id(), FakeComposite::kInputOnlyTopologyId);
}
TEST_F(CompositeTest, SetTopology) {
auto fake_driver = MakeFakeComposite();
auto device = InitializeDeviceForFakeComposite(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(SetControl(device));
RunLoopUntilIdle();
ASSERT_TRUE(notify()->topology_id().has_value());
TopologyId current_topology_id = *notify()->topology_id();
TopologyId topology_id_to_set = 0;
if (device->topology_ids().size() < 2) {
GTEST_SKIP() << "Not enough topologies to run this test case";
}
for (auto t : device->topology_ids()) {
if (t != current_topology_id) {
topology_id_to_set = t;
break;
}
}
EXPECT_EQ(device->SetTopology(topology_id_to_set), ZX_OK);
RunLoopUntilIdle();
ASSERT_TRUE(notify()->topology_id().has_value());
EXPECT_EQ(*notify()->topology_id(), topology_id_to_set);
}
TEST_F(CompositeTest, SetElementState) {
auto fake_driver = MakeFakeComposite();
auto device = InitializeDeviceForFakeComposite(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(SetControl(device));
RunLoopUntilIdle();
ASSERT_TRUE(notify()->element_states().find(FakeComposite::kMuteElementId) !=
notify()->element_states().end());
notify()->clear_element_states();
fuchsia_hardware_audio_signalprocessing::ElementState state{{.enabled = true}};
EXPECT_EQ(device->SetElementState(FakeComposite::kMuteElementId, state), ZX_OK);
RunLoopUntilIdle();
ASSERT_FALSE(notify()->element_states().find(FakeComposite::kMuteElementId) ==
notify()->element_states().end());
auto new_state = notify()->element_states().find(FakeComposite::kMuteElementId)->second;
ASSERT_TRUE(new_state.enabled().has_value());
EXPECT_EQ(*new_state.enabled(), true);
EXPECT_FALSE(new_state.latency().has_value());
EXPECT_FALSE(new_state.type_specific().has_value());
EXPECT_FALSE(new_state.vendor_specific_data().has_value());
}
/////////////////////
// StreamConfig tests
//
// Verify that a fake stream_config with default values is initialized successfully.
TEST_F(StreamConfigTest, Initialization) {
auto device = InitializeDeviceForFakeStreamConfig(MakeFakeStreamConfigOutput());
EXPECT_TRUE(IsInitialized(device));
EXPECT_EQ(device_presence_watcher()->ready_devices().size(), 1u);
EXPECT_EQ(device_presence_watcher()->error_devices().size(), 0u);
EXPECT_EQ(device_presence_watcher()->on_ready_count(), 1u);
EXPECT_EQ(device_presence_watcher()->on_error_count(), 0u);
EXPECT_EQ(device_presence_watcher()->on_removal_count(), 0u);
EXPECT_EQ(device->device_type(), fuchsia_audio_device::DeviceType::kOutput);
EXPECT_TRUE(device->has_stream_config_properties());
EXPECT_TRUE(device->checked_for_signalprocessing());
EXPECT_TRUE(device->has_health_state());
EXPECT_TRUE(device->ring_buffer_format_sets_retrieved());
EXPECT_TRUE(device->has_plug_state());
EXPECT_TRUE(device->has_gain_state());
EXPECT_FALSE(device->supports_signalprocessing());
EXPECT_TRUE(device->info().has_value());
}
TEST_F(StreamConfigTest, DeviceInfo) {
auto fake_driver = MakeFakeStreamConfigOutput();
auto device = InitializeDeviceForFakeStreamConfig(fake_driver);
ASSERT_TRUE(IsInitialized(device));
auto info = GetDeviceInfo(device);
EXPECT_TRUE(info.token_id());
EXPECT_TRUE(info.device_type());
EXPECT_EQ(*info.device_type(), fuchsia_audio_device::DeviceType::kOutput);
EXPECT_TRUE(info.device_name());
// manufacturer is optional, but it can't be an empty string
EXPECT_TRUE(!info.manufacturer().has_value() || !info.manufacturer()->empty());
// product is optional, but it can't be an empty string
EXPECT_TRUE(!info.product().has_value() || !info.product()->empty());
// unique_instance_id is optional
EXPECT_TRUE(info.gain_caps());
EXPECT_TRUE(info.plug_detect_caps());
EXPECT_TRUE(info.clock_domain());
EXPECT_EQ(*info.clock_domain(), fuchsia_hardware_audio::kClockDomainMonotonic);
}
TEST_F(StreamConfigTest, DistinctTokenIds) {
auto fake_driver = MakeFakeStreamConfigOutput();
auto device = InitializeDeviceForFakeStreamConfig(fake_driver);
ASSERT_TRUE(IsInitialized(device));
// Set up a second, entirely distinct fake device.
auto [client, server] = fidl::Endpoints<fuchsia_hardware_audio::StreamConfig>::Create();
auto fake_driver2 =
std::make_shared<FakeStreamConfig>(server.TakeChannel(), client.TakeChannel(), dispatcher());
fake_driver2->set_is_input(true);
auto device2 = InitializeDeviceForFakeStreamConfig(fake_driver2);
EXPECT_TRUE(IsInitialized(device2));
EXPECT_NE(device->token_id(), device2->token_id());
EXPECT_EQ(device_presence_watcher()->ready_devices().size(), 2u);
EXPECT_EQ(device_presence_watcher()->error_devices().size(), 0u);
}
// Verify that a driver's dropping the StreamConfig causes a DeviceIsRemoved notification.
TEST_F(StreamConfigTest, Disconnect) {
auto fake_driver = MakeFakeStreamConfigOutput();
auto device = InitializeDeviceForFakeStreamConfig(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_EQ(device_presence_watcher()->ready_devices().size(), 1u);
ASSERT_EQ(device_presence_watcher()->error_devices().size(), 0u);
ASSERT_EQ(device_presence_watcher()->on_ready_count(), 1u);
ASSERT_EQ(device_presence_watcher()->on_error_count(), 0u);
ASSERT_EQ(device_presence_watcher()->on_removal_count(), 0u);
fake_driver->DropStreamConfig();
RunLoopUntilIdle();
EXPECT_EQ(device_presence_watcher()->ready_devices().size(), 0u);
EXPECT_EQ(device_presence_watcher()->error_devices().size(), 0u);
EXPECT_EQ(device_presence_watcher()->on_ready_count(), 1u);
EXPECT_EQ(device_presence_watcher()->on_error_count(), 0u);
EXPECT_EQ(device_presence_watcher()->on_removal_count(), 1u);
EXPECT_EQ(device_presence_watcher()->on_removal_from_ready_count(), 1u);
}
// Verify that a GetHealthState response of an empty struct is considered "healthy".
TEST_F(StreamConfigTest, EmptyHealthResponse) {
auto fake_driver = MakeFakeStreamConfigInput();
fake_driver->set_health_state(std::nullopt);
auto device = InitializeDeviceForFakeStreamConfig(fake_driver);
EXPECT_TRUE(IsInitialized(device));
EXPECT_EQ(device_presence_watcher()->ready_devices().size(), 1u);
EXPECT_EQ(device_presence_watcher()->error_devices().size(), 0u);
}
TEST_F(StreamConfigTest, DefaultClock) {
auto fake_driver = MakeFakeStreamConfigInput();
auto device = InitializeDeviceForFakeStreamConfig(fake_driver);
ASSERT_TRUE(IsInitialized(device));
EXPECT_EQ(device_clock(device)->domain(), fuchsia_hardware_audio::kClockDomainMonotonic);
EXPECT_TRUE(device_clock(device)->IdenticalToMonotonicClock());
EXPECT_FALSE(device_clock(device)->adjustable());
EXPECT_EQ(device_presence_watcher()->ready_devices().size(), 1u);
EXPECT_EQ(device_presence_watcher()->error_devices().size(), 0u);
}
TEST_F(StreamConfigTest, ClockInOtherDomain) {
const uint32_t kNonMonotonicClockDomain = fuchsia_hardware_audio::kClockDomainMonotonic + 1;
auto fake_driver = MakeFakeStreamConfigInput();
fake_driver->set_clock_domain(kNonMonotonicClockDomain);
auto device = InitializeDeviceForFakeStreamConfig(fake_driver);
ASSERT_TRUE(IsInitialized(device));
EXPECT_EQ(device_clock(device)->domain(), kNonMonotonicClockDomain);
EXPECT_TRUE(device_clock(device)->IdenticalToMonotonicClock());
EXPECT_TRUE(device_clock(device)->adjustable());
EXPECT_EQ(device_presence_watcher()->ready_devices().size(), 1u);
EXPECT_EQ(device_presence_watcher()->error_devices().size(), 0u);
}
TEST_F(StreamConfigTest, SupportedDriverFormatForClientFormat) {
auto fake_driver = MakeFakeStreamConfigOutput();
fake_driver->set_frame_rates(0, {48000, 48001});
fake_driver->set_valid_bits_per_sample(0, {12, 15, 20});
fake_driver->set_bytes_per_sample(0, {2, 4});
auto device = InitializeDeviceForFakeStreamConfig(fake_driver);
ASSERT_TRUE(IsInitialized(device));
auto valid_bits = ExpectFormatMatch(device, ring_buffer_element_id(),
fuchsia_audio::SampleType::kInt16, 2, 48001);
EXPECT_EQ(valid_bits, 15u);
valid_bits = ExpectFormatMatch(device, ring_buffer_element_id(),
fuchsia_audio::SampleType::kInt32, 2, 48000);
EXPECT_EQ(valid_bits, 20u);
}
TEST_F(StreamConfigTest, Observer) {
auto fake_driver = MakeFakeStreamConfigInput();
auto device = InitializeDeviceForFakeStreamConfig(fake_driver);
ASSERT_TRUE(IsInitialized(device));
EXPECT_TRUE(AddObserver(device));
}
TEST_F(StreamConfigTest, Control) {
auto fake_driver = MakeFakeStreamConfigOutput();
auto device = InitializeDeviceForFakeStreamConfig(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(SetControl(device));
EXPECT_TRUE(DropControl(device));
}
// This tests the driver's ability to inform its ObserverNotify of initial gain state.
TEST_F(StreamConfigTest, InitialGainState) {
auto fake_driver = MakeFakeStreamConfigInput();
auto device = InitializeDeviceForFakeStreamConfig(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(AddObserver(device));
RunLoopUntilIdle();
auto gain_state = device_gain_state(device);
EXPECT_EQ(*gain_state.gain_db(), 0.0f);
EXPECT_FALSE(*gain_state.muted());
EXPECT_FALSE(*gain_state.agc_enabled());
ASSERT_TRUE(notify()->gain_state()) << "ObserverNotify was not notified of initial gain state";
ASSERT_TRUE(notify()->gain_state()->gain_db());
EXPECT_EQ(*notify()->gain_state()->gain_db(), 0.0f);
EXPECT_FALSE(notify()->gain_state()->muted().value_or(false));
EXPECT_FALSE(notify()->gain_state()->agc_enabled().value_or(false));
}
// This tests the driver's ability to originate gain changes, such as from hardware buttons. It
// also validates that gain notifications are delivered through ControlNotify (not just
// ObserverNotify).
TEST_F(StreamConfigTest, DynamicGainUpdate) {
auto fake_driver = MakeFakeStreamConfigOutput();
auto device = InitializeDeviceForFakeStreamConfig(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(SetControl(device));
RunLoopUntilIdle();
auto gain_state = device_gain_state(device);
EXPECT_EQ(*gain_state.gain_db(), 0.0f);
EXPECT_FALSE(*gain_state.muted());
EXPECT_FALSE(*gain_state.agc_enabled());
EXPECT_EQ(*notify()->gain_state()->gain_db(), 0.0f);
EXPECT_FALSE(notify()->gain_state()->muted().value_or(false));
EXPECT_FALSE(notify()->gain_state()->agc_enabled().value_or(false));
notify()->gain_state().reset();
constexpr float kNewGainDb = -2.0f;
fake_driver->InjectGainChange({{
.muted = true,
.agc_enabled = true,
.gain_db = kNewGainDb,
}});
RunLoopUntilIdle();
gain_state = device_gain_state(device);
EXPECT_EQ(*gain_state.gain_db(), kNewGainDb);
EXPECT_TRUE(*gain_state.muted());
EXPECT_TRUE(*gain_state.agc_enabled());
ASSERT_TRUE(notify()->gain_state());
ASSERT_TRUE(notify()->gain_state()->gain_db());
ASSERT_TRUE(notify()->gain_state()->muted());
ASSERT_TRUE(notify()->gain_state()->agc_enabled());
EXPECT_EQ(*notify()->gain_state()->gain_db(), kNewGainDb);
EXPECT_TRUE(*notify()->gain_state()->muted());
EXPECT_TRUE(*notify()->gain_state()->agc_enabled());
}
// This tests the ability to set gain to the driver, such as from GUI volume controls.
TEST_F(StreamConfigTest, SetGain) {
auto fake_driver = MakeFakeStreamConfigInput();
auto device = InitializeDeviceForFakeStreamConfig(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(SetControl(device));
RunLoopUntilIdle();
auto gain_state = device_gain_state(device);
EXPECT_EQ(*gain_state.gain_db(), 0.0f);
EXPECT_FALSE(*gain_state.muted());
EXPECT_FALSE(*gain_state.agc_enabled());
ASSERT_TRUE(notify()->gain_state()) << "ObserverNotify was not notified of initial gain state";
EXPECT_EQ(*notify()->gain_state()->gain_db(), 0.0f);
EXPECT_FALSE(notify()->gain_state()->muted().value_or(false));
EXPECT_FALSE(notify()->gain_state()->agc_enabled().value_or(false));
notify()->gain_state().reset();
constexpr float kNewGainDb = -2.0f;
EXPECT_TRUE(SetDeviceGain(device, {{
.muted = true,
.agc_enabled = true,
.gain_db = kNewGainDb,
}}));
RunLoopUntilIdle();
gain_state = device_gain_state(device);
EXPECT_EQ(*gain_state.gain_db(), kNewGainDb);
EXPECT_TRUE(*gain_state.muted());
EXPECT_TRUE(*gain_state.agc_enabled());
ASSERT_TRUE(notify()->gain_state() && notify()->gain_state()->gain_db() &&
notify()->gain_state()->muted() && notify()->gain_state()->agc_enabled());
EXPECT_EQ(*notify()->gain_state()->gain_db(), kNewGainDb);
EXPECT_TRUE(notify()->gain_state()->muted().value_or(false)); // Must be present and true.
EXPECT_TRUE(notify()->gain_state()->agc_enabled().value_or(false)); // Must be present and true.
}
// This tests the driver's ability to inform its ObserverNotify of initial plug state.
TEST_F(StreamConfigTest, InitialPlugState) {
auto fake_driver = MakeFakeStreamConfigOutput();
auto device = InitializeDeviceForFakeStreamConfig(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(AddObserver(device));
RunLoopUntilIdle();
EXPECT_TRUE(device_plugged_state(device));
ASSERT_TRUE(notify()->plug_state());
EXPECT_EQ(notify()->plug_state()->first, fuchsia_audio_device::PlugState::kPlugged);
EXPECT_EQ(notify()->plug_state()->second.get(), zx::time(0).get());
}
TEST_F(StreamConfigTest, DynamicPlugUpdate) {
auto fake_driver = MakeFakeStreamConfigInput();
auto device = InitializeDeviceForFakeStreamConfig(fake_driver);
ASSERT_TRUE(IsInitialized(device));
ASSERT_TRUE(SetControl(device));
RunLoopUntilIdle();
EXPECT_TRUE(device_plugged_state(device));
ASSERT_TRUE(notify()->plug_state());
EXPECT_EQ(notify()->plug_state()->first, fuchsia_audio_device::PlugState::kPlugged);
EXPECT_EQ(notify()->plug_state()->second.get(), zx::time(0).get());
notify()->plug_state().reset();
auto unplug_time = zx::clock::get_monotonic();
fake_driver->InjectUnpluggedAt(unplug_time);
RunLoopUntilIdle();
EXPECT_FALSE(device_plugged_state(device));
ASSERT_TRUE(notify()->plug_state());
EXPECT_EQ(notify()->plug_state()->first, fuchsia_audio_device::PlugState::kUnplugged);
EXPECT_EQ(notify()->plug_state()->second.get(), unplug_time.get());
}
// Verify that Device can open the driver's RingBuffer FIDL channel.
TEST_F(StreamConfigTest, CreateRingBuffer) {
auto fake_driver = MakeFakeStreamConfigInput();
auto device = InitializeDeviceForFakeStreamConfig(fake_driver);
ASSERT_TRUE(IsInitialized(device));
fake_driver->AllocateRingBuffer(8192);
ASSERT_TRUE(SetControl(device));
auto connected_to_ring_buffer_fidl = device->CreateRingBuffer(
ring_buffer_element_id(), kDefaultRingBufferFormat, 2000,
[](const fit::result<fuchsia_audio_device::ControlCreateRingBufferError,
Device::RingBufferInfo>& result) {
ASSERT_TRUE(result.is_ok()) << fidl::ToUnderlying(result.error_value());
auto& info = result.value();
ASSERT_TRUE(info.ring_buffer.buffer());
EXPECT_GT(info.ring_buffer.buffer()->size(), 2000u);
EXPECT_TRUE(info.ring_buffer.format());
EXPECT_TRUE(info.ring_buffer.producer_bytes());
EXPECT_TRUE(info.ring_buffer.consumer_bytes());
EXPECT_TRUE(info.ring_buffer.reference_clock());
});
EXPECT_TRUE(connected_to_ring_buffer_fidl);
}
TEST_F(StreamConfigTest, RingBufferProperties) {
auto fake_driver = MakeFakeStreamConfigOutput();
auto device = InitializeDeviceForFakeStreamConfig(fake_driver);
ASSERT_TRUE(IsInitialized(device));
fake_driver->AllocateRingBuffer(8192);
ASSERT_TRUE(SetControl(device));
ConnectToRingBufferAndExpectValidClient(device, ring_buffer_element_id());
GetRingBufferProperties(device, ring_buffer_element_id());
}
// TODO(https://fxbug.dev/42069012): Unittest CalculateRequiredRingBufferSizes
TEST_F(StreamConfigTest, RingBufferGetVmo) {
auto fake_driver = MakeFakeStreamConfigInput();
auto device = InitializeDeviceForFakeStreamConfig(fake_driver);
ASSERT_TRUE(IsInitialized(device));
fake_driver->AllocateRingBuffer(8192);
ASSERT_TRUE(SetControl(device));
ConnectToRingBufferAndExpectValidClient(device, ring_buffer_element_id());
GetDriverVmoAndExpectValid(device, ring_buffer_element_id());
}
TEST_F(StreamConfigTest, BasicStartAndStop) {
auto fake_driver = MakeFakeStreamConfigOutput();
auto device = InitializeDeviceForFakeStreamConfig(fake_driver);
ASSERT_TRUE(IsInitialized(device));
fake_driver->AllocateRingBuffer(8192);
ASSERT_TRUE(SetControl(device));
auto connected_to_ring_buffer_fidl = device->CreateRingBuffer(
ring_buffer_element_id(), kDefaultRingBufferFormat, 2000,
[](const fit::result<fuchsia_audio_device::ControlCreateRingBufferError,
Device::RingBufferInfo>& result) {
ASSERT_TRUE(result.is_ok());
auto& info = result.value();
ASSERT_TRUE(info.ring_buffer.buffer());
EXPECT_GT(info.ring_buffer.buffer()->size(), 2000u);
EXPECT_TRUE(info.ring_buffer.format());
EXPECT_TRUE(info.ring_buffer.producer_bytes());
EXPECT_TRUE(info.ring_buffer.consumer_bytes());
EXPECT_TRUE(info.ring_buffer.reference_clock());
});
EXPECT_TRUE(connected_to_ring_buffer_fidl);
ExpectRingBufferReady(device, ring_buffer_element_id());
StartAndExpectValid(device, ring_buffer_element_id());
StopAndExpectValid(device, ring_buffer_element_id());
}
TEST_F(StreamConfigTest, WatchDelayInfoInitial) {
auto fake_driver = MakeFakeStreamConfigInput();
auto device = InitializeDeviceForFakeStreamConfig(fake_driver);
fake_driver->AllocateRingBuffer(8192);
ASSERT_TRUE(SetControl(device));
auto created_ring_buffer = false;
auto connected_to_ring_buffer_fidl = device->CreateRingBuffer(
ring_buffer_element_id(), kDefaultRingBufferFormat, 2000,
[&created_ring_buffer](const fit::result<fuchsia_audio_device::ControlCreateRingBufferError,
Device::RingBufferInfo>& result) {
EXPECT_TRUE(result.is_ok());
created_ring_buffer = true;
});
EXPECT_TRUE(connected_to_ring_buffer_fidl);
RunLoopUntilIdle();
// Verify that the Device received the expected values from the driver.
EXPECT_TRUE(created_ring_buffer);
ASSERT_TRUE(DeviceDelayInfo(device, ring_buffer_element_id()));
ASSERT_TRUE(DeviceDelayInfo(device, ring_buffer_element_id())->internal_delay());
EXPECT_FALSE(DeviceDelayInfo(device, ring_buffer_element_id())->external_delay());
EXPECT_EQ(*DeviceDelayInfo(device, ring_buffer_element_id())->internal_delay(), 0);
// Verify that the ControlNotify was sent the expected values.
ASSERT_TRUE(notify()->delay_info()) << "ControlNotify was not notified of initial delay info";
ASSERT_TRUE(notify()->delay_info()->internal_delay());
EXPECT_FALSE(notify()->delay_info()->external_delay());
EXPECT_EQ(*notify()->delay_info()->internal_delay(), 0);
}
TEST_F(StreamConfigTest, WatchDelayInfoUpdate) {
auto fake_driver = MakeFakeStreamConfigOutput();
auto device = InitializeDeviceForFakeStreamConfig(fake_driver);
fake_driver->AllocateRingBuffer(8192);
ASSERT_TRUE(SetControl(device));
auto created_ring_buffer = false;
auto connected_to_ring_buffer_fidl = device->CreateRingBuffer(
ring_buffer_element_id(), kDefaultRingBufferFormat, 2000,
[&created_ring_buffer](const fit::result<fuchsia_audio_device::ControlCreateRingBufferError,
Device::RingBufferInfo>& result) {
EXPECT_TRUE(result.is_ok());
created_ring_buffer = true;
});
RunLoopUntilIdle();
EXPECT_TRUE(connected_to_ring_buffer_fidl);
EXPECT_TRUE(created_ring_buffer);
ASSERT_TRUE(notify()->delay_info()) << "ControlNotify was not notified of initial delay info";
ASSERT_TRUE(notify()->delay_info()->internal_delay());
EXPECT_FALSE(notify()->delay_info()->external_delay());
EXPECT_EQ(*notify()->delay_info()->internal_delay(), 0);
notify()->clear_delay_info();
RunLoopUntilIdle();
EXPECT_FALSE(notify()->delay_info());
fake_driver->InjectDelayUpdate(zx::nsec(123'456), zx::nsec(654'321));
RunLoopUntilIdle();
ASSERT_TRUE(DeviceDelayInfo(device, ring_buffer_element_id()));
ASSERT_TRUE(DeviceDelayInfo(device, ring_buffer_element_id())->internal_delay());
ASSERT_TRUE(DeviceDelayInfo(device, ring_buffer_element_id())->external_delay());
EXPECT_EQ(*DeviceDelayInfo(device, ring_buffer_element_id())->internal_delay(), 123'456);
EXPECT_EQ(*DeviceDelayInfo(device, ring_buffer_element_id())->external_delay(), 654'321);
ASSERT_TRUE(notify()->delay_info()) << "ControlNotify was not notified with updated delay info";
ASSERT_TRUE(notify()->delay_info()->internal_delay());
ASSERT_TRUE(notify()->delay_info()->external_delay());
EXPECT_EQ(*notify()->delay_info()->internal_delay(), 123'456);
EXPECT_EQ(*notify()->delay_info()->external_delay(), 654'321);
}
TEST_F(StreamConfigTest, ReportsThatItSupportsSetActiveChannels) {
auto fake_driver = MakeFakeStreamConfigInput();
auto device = InitializeDeviceForFakeStreamConfig(fake_driver);
ASSERT_TRUE(IsInitialized(device));
fake_driver->AllocateRingBuffer(8192);
ASSERT_TRUE(SetControl(device));
auto connected_to_ring_buffer_fidl = device->CreateRingBuffer(
ring_buffer_element_id(), kDefaultRingBufferFormat, 2000,
[](const fit::result<fuchsia_audio_device::ControlCreateRingBufferError,
Device::RingBufferInfo>& result) {
ASSERT_TRUE(result.is_ok());
auto& info = result.value();
ASSERT_TRUE(info.ring_buffer.buffer());
EXPECT_GT(info.ring_buffer.buffer()->size(), 2000u);
EXPECT_TRUE(info.ring_buffer.format());
EXPECT_TRUE(info.ring_buffer.producer_bytes());
EXPECT_TRUE(info.ring_buffer.consumer_bytes());
EXPECT_TRUE(info.ring_buffer.reference_clock());
});
EXPECT_TRUE(connected_to_ring_buffer_fidl);
ExpectRingBufferReady(device, ring_buffer_element_id());
EXPECT_TRUE(device->supports_set_active_channels(ring_buffer_element_id()).value_or(false));
}
TEST_F(StreamConfigTest, ReportsThatItDoesNotSupportSetActiveChannels) {
auto fake_driver = MakeFakeStreamConfigOutput();
auto device = InitializeDeviceForFakeStreamConfig(fake_driver);
fake_driver->set_active_channels_supported(false);
ASSERT_TRUE(IsInitialized(device));
fake_driver->AllocateRingBuffer(8192);
ASSERT_TRUE(SetControl(device));
auto connected_to_ring_buffer_fidl = device->CreateRingBuffer(
ring_buffer_element_id(), kDefaultRingBufferFormat, 2000,
[](const fit::result<fuchsia_audio_device::ControlCreateRingBufferError,
Device::RingBufferInfo>& result) {
ASSERT_TRUE(result.is_ok());
auto& info = result.value();
ASSERT_TRUE(info.ring_buffer.buffer());
EXPECT_GT(info.ring_buffer.buffer()->size(), 2000u);
EXPECT_TRUE(info.ring_buffer.format());
EXPECT_TRUE(info.ring_buffer.producer_bytes());
EXPECT_TRUE(info.ring_buffer.consumer_bytes());
EXPECT_TRUE(info.ring_buffer.reference_clock());
});
EXPECT_TRUE(connected_to_ring_buffer_fidl);
ExpectRingBufferReady(device, ring_buffer_element_id());
EXPECT_FALSE(device->supports_set_active_channels(ring_buffer_element_id()).value_or(true));
}
TEST_F(StreamConfigTest, SetActiveChannels) {
auto fake_driver = MakeFakeStreamConfigInput();
auto device = InitializeDeviceForFakeStreamConfig(fake_driver);
ASSERT_TRUE(IsInitialized(device));
fake_driver->AllocateRingBuffer(8192);
fake_driver->set_active_channels_supported(true);
ASSERT_TRUE(SetControl(device));
ConnectToRingBufferAndExpectValidClient(device, ring_buffer_element_id());
ExpectActiveChannels(device, ring_buffer_element_id(), 0x0003);
SetInitialActiveChannelsAndExpect(device, ring_buffer_element_id(), 0x0002);
}
// TODO(https://fxbug.dev/42069012): SetActiveChannel no change => no callback (no set_time
// change).
} // namespace media_audio