| // 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. |
| |
| #ifndef SRC_MEDIA_AUDIO_SERVICES_DEVICE_REGISTRY_DEVICE_UNITTEST_H_ |
| #define SRC_MEDIA_AUDIO_SERVICES_DEVICE_REGISTRY_DEVICE_UNITTEST_H_ |
| |
| #include <fidl/fuchsia.audio.device/cpp/common_types.h> |
| #include <fidl/fuchsia.audio.device/cpp/natural_types.h> |
| #include <zircon/errors.h> |
| #include <zircon/types.h> |
| |
| #include <memory> |
| #include <optional> |
| #include <sstream> |
| #include <unordered_map> |
| |
| #include <gtest/gtest.h> |
| |
| #include "src/lib/testing/loop_fixture/test_loop_fixture.h" |
| #include "src/media/audio/lib/clock/clock.h" |
| #include "src/media/audio/services/device_registry/basic_types.h" |
| #include "src/media/audio/services/device_registry/control_notify.h" |
| #include "src/media/audio/services/device_registry/device.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_device_presence_watcher.h" |
| #include "src/media/audio/services/device_registry/testing/fake_stream_config.h" |
| |
| namespace media_audio { |
| |
| static constexpr bool kLogDeviceTestNotifyResponses = false; |
| |
| // Test class to verify the driver initialization/configuration sequence. |
| class DeviceTestBase : public gtest::TestLoopFixture { |
| static inline const std::string_view kClassName = "DeviceTestBase"; |
| |
| public: |
| void SetUp() override { |
| notify_ = std::make_shared<NotifyStub>(*this); |
| fake_device_presence_watcher_ = std::make_shared<FakeDevicePresenceWatcher>(); |
| } |
| void TearDown() override { fake_device_presence_watcher_.reset(); } |
| |
| protected: |
| static fuchsia_audio_device::Info GetDeviceInfo(const std::shared_ptr<Device>& device) { |
| return *device->info(); |
| } |
| |
| static std::shared_ptr<Clock> device_clock(const std::shared_ptr<Device>& device) { |
| return device->device_clock_; |
| } |
| |
| static bool HasError(const std::shared_ptr<Device>& device) { |
| return device->state_ == Device::State::Error; |
| } |
| static bool IsInitializing(const std::shared_ptr<Device>& device) { |
| return device->state_ == Device::State::DeviceInitializing; |
| } |
| static bool IsInitialized(const std::shared_ptr<Device>& device) { |
| return device->state_ == Device::State::DeviceInitialized; |
| } |
| static bool IsControlled(const std::shared_ptr<Device>& device) { |
| return (device->GetControlNotify() != nullptr); |
| } |
| |
| static bool HasRingBuffer(const std::shared_ptr<Device>& device, ElementId element_id) { |
| return device->ring_buffer_map_.find(element_id) != device->ring_buffer_map_.end(); |
| } |
| |
| static bool RingBufferIsCreatingOrStopped(const std::shared_ptr<Device>& device, |
| ElementId element_id) { |
| auto match = device->ring_buffer_map_.find(element_id); |
| |
| return (HasRingBuffer(device, element_id) && |
| (match->second.ring_buffer_state == Device::RingBufferState::Creating || |
| match->second.ring_buffer_state == Device::RingBufferState::Stopped)); |
| } |
| static bool RingBufferIsOperational(const std::shared_ptr<Device>& device, ElementId element_id) { |
| auto match = device->ring_buffer_map_.find(element_id); |
| |
| return (HasRingBuffer(device, element_id) && |
| (match->second.ring_buffer_state == Device::RingBufferState::Stopped || |
| match->second.ring_buffer_state == Device::RingBufferState::Started)); |
| } |
| static bool RingBufferIsStopped(const std::shared_ptr<Device>& device, ElementId element_id) { |
| auto match = device->ring_buffer_map_.find(element_id); |
| |
| return (HasRingBuffer(device, element_id) && |
| match->second.ring_buffer_state == Device::RingBufferState::Stopped); |
| } |
| static bool RingBufferIsStarted(const std::shared_ptr<Device>& device, ElementId element_id) { |
| auto match = device->ring_buffer_map_.find(element_id); |
| |
| return (HasRingBuffer(device, element_id) && |
| match->second.ring_buffer_state == Device::RingBufferState::Started); |
| } |
| |
| // Accessor for a Device private member. |
| static const std::optional<fuchsia_hardware_audio::DelayInfo>& DeviceDelayInfo( |
| const std::shared_ptr<Device>& device, ElementId element_id) { |
| return device->ring_buffer_map_.find(element_id)->second.delay_info; |
| } |
| |
| class NotifyStub : public std::enable_shared_from_this<NotifyStub>, public ControlNotify { |
| static inline const std::string_view kClassName = "DeviceTestBase::NotifyStub"; |
| |
| public: |
| explicit NotifyStub(DeviceTestBase& parent) : parent_(parent) {} |
| virtual ~NotifyStub() = default; |
| |
| bool AddObserver(const std::shared_ptr<Device>& device) { |
| return device->AddObserver(shared_from_this()); |
| } |
| bool SetControl(const std::shared_ptr<Device>& device) { |
| return device->SetControl(shared_from_this()); |
| } |
| static bool DropControl(const std::shared_ptr<Device>& device) { return device->DropControl(); } |
| |
| // ObserverNotify |
| // |
| void DeviceIsRemoved() final { ADR_LOG_OBJECT(kLogDeviceTestNotifyResponses); } |
| void DeviceHasError() final { ADR_LOG_OBJECT(kLogDeviceTestNotifyResponses); } |
| void GainStateChanged(const fuchsia_audio_device::GainState& new_gain_state) final { |
| ADR_LOG_OBJECT(kLogDeviceTestNotifyResponses); |
| gain_state_ = new_gain_state; |
| } |
| void PlugStateChanged(const fuchsia_audio_device::PlugState& new_plug_state, |
| zx::time plug_change_time) final { |
| ADR_LOG_OBJECT(kLogDeviceTestNotifyResponses); |
| plug_state_ = std::make_pair(new_plug_state, plug_change_time); |
| } |
| void TopologyChanged(TopologyId topology_id) final { |
| ADR_LOG_OBJECT(kLogDeviceTestNotifyResponses) << "(topology_id " << topology_id << ")"; |
| topology_id_ = topology_id; |
| } |
| void ElementStateChanged( |
| ElementId element_id, |
| fuchsia_hardware_audio_signalprocessing::ElementState element_state) final { |
| ADR_LOG_OBJECT(kLogDeviceTestNotifyResponses) << "(element_id " << element_id << ")"; |
| element_states_.insert({element_id, element_state}); |
| } |
| |
| // ControlNotify |
| // |
| void DeviceDroppedRingBuffer(ElementId element_id) final { |
| ADR_LOG_OBJECT(kLogDeviceTestNotifyResponses) << "(element_id " << element_id << ")"; |
| } |
| void DelayInfoChanged(ElementId element_id, |
| const fuchsia_audio_device::DelayInfo& new_delay_info) final { |
| ADR_LOG_OBJECT(kLogDeviceTestNotifyResponses) << "(element_id " << element_id << ")"; |
| delay_infos_.insert_or_assign(element_id, new_delay_info); |
| } |
| void DaiFormatChanged( |
| ElementId element_id, const std::optional<fuchsia_hardware_audio::DaiFormat>& dai_format, |
| const std::optional<fuchsia_hardware_audio::CodecFormatInfo>& codec_format_info) final { |
| ADR_LOG_OBJECT(kLogDeviceTestNotifyResponses) << "(element_id " << element_id << ")"; |
| dai_format_errors_.erase(element_id); |
| |
| codec_format_infos_.erase(element_id); |
| if (dai_format.has_value()) { |
| LogDaiFormat(dai_format); |
| LogCodecFormatInfo(codec_format_info); |
| dai_formats_.insert_or_assign(element_id, *dai_format); |
| if (codec_format_info.has_value()) { |
| codec_format_infos_.insert({element_id, *codec_format_info}); |
| } |
| } else { |
| dai_formats_.insert_or_assign(element_id, std::nullopt); |
| } |
| } |
| void DaiFormatNotSet(ElementId element_id, const fuchsia_hardware_audio::DaiFormat& dai_format, |
| fuchsia_audio_device::ControlSetDaiFormatError error) final { |
| ADR_LOG_OBJECT(kLogDeviceTestNotifyResponses) |
| << "(element_id " << element_id << ", " << error << ")"; |
| dai_format_errors_.insert_or_assign(element_id, error); |
| } |
| |
| void CodecStarted(const zx::time& start_time) final { |
| ADR_LOG_OBJECT(kLogDeviceTestNotifyResponses) << "(" << start_time.get() << ")"; |
| codec_start_failed_ = false; |
| codec_start_time_ = start_time; |
| codec_stop_time_.reset(); |
| } |
| void CodecNotStarted() final { |
| ADR_LOG_OBJECT(kLogDeviceTestNotifyResponses); |
| codec_start_failed_ = true; |
| } |
| void CodecStopped(const zx::time& stop_time) final { |
| ADR_LOG_OBJECT(kLogDeviceTestNotifyResponses) << "(" << stop_time.get() << ")"; |
| codec_stop_failed_ = false; |
| codec_stop_time_ = stop_time; |
| codec_start_time_.reset(); |
| } |
| void CodecNotStopped() final { |
| ADR_LOG_OBJECT(kLogDeviceTestNotifyResponses); |
| codec_stop_failed_ = true; |
| } |
| |
| // control and access internal state, for validating that correct responses were received. |
| // |
| // For testing purposes, reset internal state so we detect new Notify calls (including errors). |
| void clear_dai_formats() { |
| dai_formats_.clear(); |
| dai_format_errors_.clear(); |
| codec_format_infos_.clear(); |
| } |
| void clear_dai_format(ElementId element_id) { |
| dai_formats_.erase(element_id); |
| dai_format_errors_.erase(element_id); |
| codec_format_infos_.erase(element_id); |
| } |
| // If Codec/Start and Stop is added to Composite, then move these into a map like DaiFormat is. |
| void clear_codec_start_stop() { |
| codec_start_time_.reset(); |
| codec_stop_time_ = zx::time::infinite_past(); |
| codec_start_failed_ = false; |
| codec_stop_failed_ = false; |
| } |
| bool codec_is_started() { |
| FX_CHECK(codec_start_time_.has_value() != codec_stop_time_.has_value()); |
| return codec_start_time_.has_value(); |
| } |
| bool codec_is_stopped() { |
| FX_CHECK(codec_start_time_.has_value() != codec_stop_time_.has_value()); |
| return codec_stop_time_.has_value(); |
| } |
| |
| const std::optional<fuchsia_audio_device::GainState>& gain_state() const { return gain_state_; } |
| std::optional<fuchsia_audio_device::GainState>& gain_state() { return gain_state_; } |
| |
| const std::optional<std::pair<fuchsia_audio_device::PlugState, zx::time>>& plug_state() const { |
| return plug_state_; |
| } |
| std::optional<std::pair<fuchsia_audio_device::PlugState, zx::time>>& plug_state() { |
| return plug_state_; |
| } |
| |
| std::optional<fuchsia_audio_device::DelayInfo> delay_info( |
| ElementId element_id = fuchsia_audio_device::kDefaultRingBufferElementId) const { |
| auto delay_match = delay_infos_.find(element_id); |
| if (delay_match == delay_infos_.end()) { |
| return std::nullopt; |
| } |
| return delay_match->second; |
| } |
| void clear_delay_info( |
| ElementId element_id = fuchsia_audio_device::kDefaultRingBufferElementId) { |
| delay_infos_.erase(element_id); |
| } |
| void clear_delay_infos() { delay_infos_.clear(); } |
| |
| std::optional<fuchsia_hardware_audio::DaiFormat> dai_format( |
| ElementId element_id = fuchsia_audio_device::kDefaultDaiInterconnectElementId) { |
| if (dai_formats_.find(element_id) == dai_formats_.end()) { |
| return std::nullopt; |
| } |
| return dai_formats_.at(element_id); |
| } |
| std::optional<fuchsia_hardware_audio::CodecFormatInfo> codec_format_info(ElementId element_id) { |
| if (codec_format_infos_.find(element_id) == codec_format_infos_.end()) { |
| return std::nullopt; |
| } |
| return codec_format_infos_.at(element_id); |
| } |
| const std::unordered_map<ElementId, std::optional<fuchsia_hardware_audio::DaiFormat>>& |
| dai_formats() { |
| return dai_formats_; |
| } |
| const std::unordered_map<ElementId, fuchsia_hardware_audio::CodecFormatInfo>& |
| codec_format_infos() { |
| return codec_format_infos_; |
| } |
| const std::unordered_map<ElementId, fuchsia_audio_device::ControlSetDaiFormatError>& |
| dai_format_errors() { |
| return dai_format_errors_; |
| } |
| |
| std::optional<zx::time>& codec_start_time() { return codec_start_time_; } |
| bool codec_start_failed() const { return codec_start_failed_; } |
| std::optional<zx::time>& codec_stop_time() { return codec_stop_time_; } |
| bool codec_stop_failed() const { return codec_stop_failed_; } |
| |
| const std::unordered_map<ElementId, fuchsia_hardware_audio_signalprocessing::ElementState>& |
| element_states() const { |
| return element_states_; |
| } |
| void clear_element_states() { element_states_.clear(); } |
| |
| std::optional<TopologyId> topology_id() const { return topology_id_; } |
| void clear_topology_id() { topology_id_.reset(); } |
| |
| private: |
| [[maybe_unused]] DeviceTestBase& parent_; |
| std::optional<fuchsia_audio_device::GainState> gain_state_; |
| std::optional<std::pair<fuchsia_audio_device::PlugState, zx::time>> plug_state_; |
| std::unordered_map<ElementId, fuchsia_audio_device::DelayInfo> delay_infos_; |
| |
| std::unordered_map<ElementId, std::optional<fuchsia_hardware_audio::DaiFormat>> dai_formats_; |
| std::unordered_map<ElementId, fuchsia_audio_device::ControlSetDaiFormatError> |
| dai_format_errors_; |
| std::unordered_map<ElementId, fuchsia_hardware_audio::CodecFormatInfo> codec_format_infos_; |
| |
| std::optional<zx::time> codec_start_time_; |
| std::optional<zx::time> codec_stop_time_{zx::time::infinite_past()}; |
| bool codec_start_failed_ = false; |
| bool codec_stop_failed_ = false; |
| |
| std::optional<TopologyId> topology_id_; |
| std::unordered_map<ElementId, fuchsia_hardware_audio_signalprocessing::ElementState> |
| element_states_; |
| }; |
| |
| static uint8_t ExpectFormatMatch(const std::shared_ptr<Device>& device, ElementId element_id, |
| fuchsia_audio::SampleType sample_type, uint32_t channel_count, |
| uint32_t rate) { |
| std::stringstream stream; |
| stream << "Expected format match: [" << sample_type << " " << channel_count << "-channel " |
| << rate << " hz]"; |
| SCOPED_TRACE(stream.str()); |
| const auto& match = |
| device->SupportedDriverFormatForClientFormat(element_id, {{ |
| .sample_type = sample_type, |
| .channel_count = channel_count, |
| .frames_per_second = rate, |
| }}); |
| EXPECT_TRUE(match); |
| return match->pcm_format()->valid_bits_per_sample(); |
| } |
| |
| static void ExpectNoFormatMatch(const std::shared_ptr<Device>& device, ElementId element_id, |
| fuchsia_audio::SampleType sample_type, uint32_t channel_count, |
| uint32_t rate) { |
| std::stringstream stream; |
| stream << "Unexpected format match: [" << sample_type << " " << channel_count << "-channel " |
| << rate << " hz]"; |
| SCOPED_TRACE(stream.str()); |
| const auto& match = |
| device->SupportedDriverFormatForClientFormat(element_id, {{ |
| .sample_type = sample_type, |
| .channel_count = channel_count, |
| .frames_per_second = rate, |
| }}); |
| EXPECT_FALSE(match); |
| } |
| |
| // A consolidated notify recipient for tests (ObserverNotify and ControlNotify). |
| std::shared_ptr<NotifyStub> notify() { return notify_; } |
| std::shared_ptr<FakeDevicePresenceWatcher> device_presence_watcher() { |
| return fake_device_presence_watcher_; |
| } |
| |
| bool AddObserver(const std::shared_ptr<Device>& device) { return notify()->AddObserver(device); } |
| bool SetControl(const std::shared_ptr<Device>& device) { return notify()->SetControl(device); } |
| bool DropControl(const std::shared_ptr<Device>& device) { return notify()->DropControl(device); } |
| |
| static bool device_plugged_state(const std::shared_ptr<Device>& device) { |
| return *device->plug_state_->plugged(); |
| } |
| |
| static ElementId ring_buffer_element_id() { return kRingBufferElementId; } |
| static ElementId dai_element_id() { return kDaiElementId; } |
| |
| private: |
| static constexpr ElementId kRingBufferElementId = |
| fuchsia_audio_device::kDefaultRingBufferElementId; |
| static constexpr ElementId kDaiElementId = fuchsia_audio_device::kDefaultDaiInterconnectElementId; |
| |
| static constexpr zx::duration kCommandTimeout = zx::sec(0); |
| |
| std::shared_ptr<NotifyStub> notify_; |
| |
| // Receives "OnInitCompletion", "DeviceHasError", "DeviceIsRemoved" notifications from Devices. |
| std::shared_ptr<FakeDevicePresenceWatcher> fake_device_presence_watcher_; |
| }; |
| |
| class CodecTest : public DeviceTestBase { |
| protected: |
| std::shared_ptr<FakeCodec> MakeFakeCodecInput() { return MakeFakeCodec(true); } |
| std::shared_ptr<FakeCodec> MakeFakeCodecOutput() { return MakeFakeCodec(false); } |
| std::shared_ptr<FakeCodec> MakeFakeCodecNoDirection() { return MakeFakeCodec(std::nullopt); } |
| |
| std::shared_ptr<Device> InitializeDeviceForFakeCodec(const std::shared_ptr<FakeCodec>& driver) { |
| auto codec_client_end = driver->Enable(); |
| EXPECT_TRUE(codec_client_end.is_valid()); |
| auto device = |
| Device::Create(std::weak_ptr<FakeDevicePresenceWatcher>(device_presence_watcher()), |
| dispatcher(), "Device name", fuchsia_audio_device::DeviceType::kCodec, |
| fuchsia_audio_device::DriverClient::WithCodec(std::move(codec_client_end))); |
| |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(IsInitializing(device)) << "Device is initializing"; |
| |
| return device; |
| } |
| |
| private: |
| std::shared_ptr<FakeCodec> MakeFakeCodec(std::optional<bool> is_input = false) { |
| auto codec_endpoints = fidl::Endpoints<fuchsia_hardware_audio::Codec>::Create(); |
| auto fake_codec = std::make_shared<FakeCodec>( |
| codec_endpoints.server.TakeChannel(), codec_endpoints.client.TakeChannel(), dispatcher()); |
| fake_codec->set_is_input(is_input); |
| return fake_codec; |
| } |
| }; |
| |
| class CompositeTest : public DeviceTestBase { |
| protected: |
| static inline const std::string_view kClassName = "CompositeTest"; |
| |
| static const std::vector< |
| std::pair<ElementId, std::vector<fuchsia_hardware_audio::SupportedFormats>>>& |
| ElementDriverRingBufferFormatSets(const std::shared_ptr<Device>& device) { |
| return device->element_driver_ring_buffer_format_sets_; |
| } |
| |
| static const std::unordered_map<ElementId, ElementRecord>& signal_processing_elements( |
| const std::shared_ptr<Device>& device) { |
| return device->sig_proc_element_map_; |
| } |
| |
| std::shared_ptr<FakeComposite> MakeFakeComposite() { |
| auto composite_endpoints = fidl::CreateEndpoints<fuchsia_hardware_audio::Composite>(); |
| EXPECT_TRUE(composite_endpoints.is_ok()); |
| auto fake_composite = |
| std::make_shared<FakeComposite>(composite_endpoints->server.TakeChannel(), |
| composite_endpoints->client.TakeChannel(), dispatcher()); |
| return fake_composite; |
| } |
| |
| std::shared_ptr<Device> InitializeDeviceForFakeComposite( |
| const std::shared_ptr<FakeComposite>& driver) { |
| auto composite_client_end = driver->Enable(); |
| EXPECT_TRUE(composite_client_end.is_valid()); |
| auto device = Device::Create( |
| std::weak_ptr<FakeDevicePresenceWatcher>(device_presence_watcher()), dispatcher(), |
| "Device name", fuchsia_audio_device::DeviceType::kComposite, |
| fuchsia_audio_device::DriverClient::WithComposite(std::move(composite_client_end))); |
| |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(IsInitializing(device)) << "Device is initializing"; |
| |
| return device; |
| } |
| |
| void TestCreateRingBuffer(const std::shared_ptr<Device>& device, ElementId element_id, |
| const fuchsia_hardware_audio::Format& safe_format); |
| |
| bool ExpectDaiFormatMatches(ElementId dai_element_id, |
| const fuchsia_hardware_audio::DaiFormat& dai_format) { |
| auto format_match = notify()->dai_formats().find(dai_element_id); |
| if (format_match == notify()->dai_formats().end()) { |
| ADR_WARN_METHOD() << "Dai element " << dai_element_id << " not found"; |
| return false; |
| } |
| if (!format_match->second.has_value()) { |
| ADR_WARN_METHOD() << "Dai format not set for element " << dai_element_id; |
| return false; |
| } |
| if (*format_match->second != dai_format) { |
| ADR_WARN_METHOD() << "Dai format for element " << dai_element_id << " is not the expected"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool ExpectDaiFormatError(ElementId element_id, |
| fuchsia_audio_device::ControlSetDaiFormatError expected_error) { |
| auto error_match = notify()->dai_format_errors().find(element_id); |
| if (error_match == notify()->dai_format_errors().end()) { |
| ADR_WARN_METHOD() << "No dai format errors for element " << element_id; |
| return false; |
| } |
| |
| if (error_match->second != expected_error) { |
| ADR_WARN_METHOD() << "For element " << element_id << ", expected error " << expected_error |
| << " but instead received " << error_match->second; |
| return false; |
| } |
| return true; |
| } |
| }; |
| |
| class StreamConfigTest : public DeviceTestBase { |
| protected: |
| static inline const fuchsia_hardware_audio::Format kDefaultRingBufferFormat{{ |
| .pcm_format = fuchsia_hardware_audio::PcmFormat{{ |
| .number_of_channels = 2, |
| .sample_format = fuchsia_hardware_audio::SampleFormat::kPcmSigned, |
| .bytes_per_sample = 2, |
| .valid_bits_per_sample = static_cast<uint8_t>(16), |
| .frame_rate = 48000, |
| }}, |
| }}; |
| |
| std::shared_ptr<FakeStreamConfig> MakeFakeStreamConfigInput() { |
| return MakeFakeStreamConfig(true); |
| } |
| std::shared_ptr<FakeStreamConfig> MakeFakeStreamConfigOutput() { |
| return MakeFakeStreamConfig(false); |
| } |
| |
| std::shared_ptr<Device> InitializeDeviceForFakeStreamConfig( |
| const std::shared_ptr<FakeStreamConfig>& driver) { |
| auto device_type = *driver->is_input() ? fuchsia_audio_device::DeviceType::kInput |
| : fuchsia_audio_device::DeviceType::kOutput; |
| auto stream_config_client_end = driver->Enable(); |
| auto device = Device::Create( |
| std::weak_ptr<FakeDevicePresenceWatcher>(device_presence_watcher()), dispatcher(), |
| "Device name", device_type, |
| fuchsia_audio_device::DriverClient::WithStreamConfig(std::move(stream_config_client_end))); |
| |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(IsInitializing(device)); |
| |
| return device; |
| } |
| |
| static fuchsia_hardware_audio::GainState device_gain_state( |
| const std::shared_ptr<Device>& device) { |
| return *device->gain_state_; |
| } |
| |
| static bool SetDeviceGain(const std::shared_ptr<Device>& device, |
| fuchsia_hardware_audio::GainState new_state) { |
| return device->SetGain(new_state); |
| } |
| |
| void ConnectToRingBufferAndExpectValidClient(const std::shared_ptr<Device>& device, |
| ElementId element_id) { |
| EXPECT_EQ( |
| device->ConnectRingBufferFidl( |
| element_id, {{ |
| fuchsia_hardware_audio::PcmFormat{{ |
| .number_of_channels = 2, |
| .sample_format = fuchsia_hardware_audio::SampleFormat::kPcmSigned, |
| .bytes_per_sample = 2, |
| .valid_bits_per_sample = static_cast<uint8_t>(16), |
| .frame_rate = 48000, |
| }}, |
| }}), |
| fuchsia_audio_device::ControlCreateRingBufferError(0)); |
| |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(device->ring_buffer_map_.find(element_id)->second.ring_buffer_client.has_value() && |
| device->ring_buffer_map_.find(element_id)->second.ring_buffer_client->is_valid()); |
| } |
| |
| void GetRingBufferProperties(const std::shared_ptr<Device>& device, ElementId element_id) { |
| ASSERT_TRUE(RingBufferIsCreatingOrStopped(device, element_id)); |
| |
| device->RetrieveRingBufferProperties(element_id); |
| |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(device->ring_buffer_map_.find(element_id)->second.ring_buffer_properties); |
| EXPECT_TRUE(RingBufferIsCreatingOrStopped(device, element_id)); |
| } |
| |
| void RetrieveDelayInfoAndExpect(const std::shared_ptr<Device>& device, ElementId element_id, |
| std::optional<int64_t> internal_delay, |
| std::optional<int64_t> external_delay) { |
| ASSERT_TRUE(RingBufferIsOperational(device, element_id)); |
| device->RetrieveDelayInfo(element_id); |
| |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(device->ring_buffer_map_.find(element_id)->second.delay_info); |
| EXPECT_EQ( |
| device->ring_buffer_map_.find(element_id)->second.delay_info->internal_delay().value_or(0), |
| internal_delay.value_or(0)); |
| EXPECT_EQ( |
| device->ring_buffer_map_.find(element_id)->second.delay_info->external_delay().value_or(0), |
| external_delay.value_or(0)); |
| } |
| |
| void GetDriverVmoAndExpectValid(const std::shared_ptr<Device>& device, ElementId element_id) { |
| ASSERT_TRUE(RingBufferIsCreatingOrStopped(device, element_id)); |
| |
| device->GetVmo(element_id, 2000, 0); |
| |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(device->VmoReceived(element_id)); |
| EXPECT_TRUE(RingBufferIsCreatingOrStopped(device, element_id)); |
| } |
| |
| void SetInitialActiveChannelsAndExpect(const std::shared_ptr<Device>& device, |
| ElementId element_id, uint64_t expected_bitmask) { |
| ASSERT_TRUE(RingBufferIsCreatingOrStopped(device, element_id)); |
| const auto now = zx::clock::get_monotonic(); |
| auto& ring_buffer = device->ring_buffer_map_.find(element_id)->second; |
| bool callback_received = false; |
| |
| auto succeeded = device->SetActiveChannels( |
| element_id, expected_bitmask, |
| [&ring_buffer, &callback_received](zx::result<zx::time> result) { |
| EXPECT_TRUE(result.is_ok()) << result.status_string(); |
| ASSERT_TRUE(ring_buffer.active_channels_set_time); |
| EXPECT_EQ(result.value(), *ring_buffer.active_channels_set_time); |
| callback_received = true; |
| }); |
| |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(succeeded); |
| ASSERT_TRUE(ring_buffer.active_channels_set_time); |
| EXPECT_GT(*ring_buffer.active_channels_set_time, now); |
| ExpectActiveChannels(device, element_id, expected_bitmask); |
| } |
| |
| static void ExpectActiveChannels(const std::shared_ptr<Device>& device, ElementId element_id, |
| uint64_t expected_bitmask) { |
| EXPECT_EQ(device->ring_buffer_map_.find(element_id)->second.active_channels_bitmask, |
| expected_bitmask); |
| } |
| |
| void ExpectRingBufferReady(const std::shared_ptr<Device>& device, ElementId element_id) { |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(IsInitialized(device)); |
| EXPECT_TRUE(RingBufferIsStopped(device, element_id)); |
| } |
| |
| void StartAndExpectValid(const std::shared_ptr<Device>& device, ElementId element_id) { |
| ASSERT_TRUE(IsInitialized(device)); |
| ASSERT_TRUE(RingBufferIsStopped(device, element_id)); |
| const auto now = zx::clock::get_monotonic().get(); |
| auto& ring_buffer = device->ring_buffer_map_.find(element_id)->second; |
| |
| device->StartRingBuffer(element_id, [&ring_buffer](zx::result<zx::time> result) { |
| EXPECT_TRUE(result.is_ok()) << result.status_string(); |
| ASSERT_TRUE(ring_buffer.start_time); |
| EXPECT_EQ(result.value(), *ring_buffer.start_time); |
| }); |
| |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(ring_buffer.start_time); |
| EXPECT_GT(ring_buffer.start_time->get(), now); |
| EXPECT_TRUE(RingBufferIsStarted(device, element_id)); |
| } |
| |
| void StopAndExpectValid(const std::shared_ptr<Device>& device, ElementId element_id) { |
| ASSERT_TRUE(RingBufferIsStarted(device, element_id)); |
| |
| device->StopRingBuffer(element_id, [](zx_status_t result) { EXPECT_EQ(result, ZX_OK); }); |
| |
| RunLoopUntilIdle(); |
| auto& ring_buffer = device->ring_buffer_map_.find(element_id)->second; |
| ASSERT_FALSE(ring_buffer.start_time); |
| EXPECT_TRUE(RingBufferIsStopped(device, element_id)); |
| } |
| |
| private: |
| std::shared_ptr<FakeStreamConfig> MakeFakeStreamConfig(bool is_input = false) { |
| auto stream_config_endpoints = fidl::Endpoints<fuchsia_hardware_audio::StreamConfig>::Create(); |
| auto fake_stream = std::make_shared<FakeStreamConfig>( |
| stream_config_endpoints.server.TakeChannel(), stream_config_endpoints.client.TakeChannel(), |
| dispatcher()); |
| fake_stream->set_is_input(is_input); |
| return fake_stream; |
| } |
| }; |
| |
| } // namespace media_audio |
| |
| #endif // SRC_MEDIA_AUDIO_SERVICES_DEVICE_REGISTRY_DEVICE_UNITTEST_H_ |