| // 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.audio/cpp/natural_types.h> |
| #include <fidl/fuchsia.hardware.audio/cpp/fidl.h> |
| #include <lib/fidl/cpp/enum.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/logging.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 { |
| |
| using ::testing::Optional; |
| |
| ///////////////////// |
| // 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 codec_endpoints = fidl::CreateEndpoints<fuchsia_hardware_audio::Codec>(); |
| ASSERT_TRUE(codec_endpoints.is_ok()); |
| |
| auto fake_driver2 = std::make_shared<FakeCodec>( |
| codec_endpoints->server.TakeChannel(), codec_endpoints->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 composite_endpoints = fidl::CreateEndpoints<fuchsia_hardware_audio::Composite>(); |
| ASSERT_TRUE(composite_endpoints.is_ok()); |
| |
| auto fake_driver2 = |
| std::make_shared<FakeComposite>(composite_endpoints->server.TakeChannel(), |
| composite_endpoints->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"; } |
| |
| ///////////////////// |
| // 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 stream_config_endpoints = fidl::CreateEndpoints<fuchsia_hardware_audio::StreamConfig>(); |
| ASSERT_TRUE(stream_config_endpoints.is_ok()); |
| |
| auto fake_driver2 = std::make_shared<FakeStreamConfig>( |
| stream_config_endpoints->server.TakeChannel(), stream_config_endpoints->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 |