| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <fidl/fuchsia.device/cpp/wire.h> |
| #include <fidl/fuchsia.hardware.audio/cpp/wire.h> |
| #include <lib/inspect/testing/cpp/zxtest/inspect.h> |
| #include <lib/simple-audio-stream/simple-audio-stream.h> |
| #include <lib/zircon-internal/thread_annotations.h> |
| #include <lib/zx/clock.h> |
| #include <threads.h> |
| #include <zircon/errors.h> |
| |
| #include <set> |
| |
| #include <audio-proto-utils/format-utils.h> |
| #include <zxtest/zxtest.h> |
| |
| #include "src/devices/testing/mock-ddk/mock-device.h" |
| |
| namespace audio { |
| |
| namespace audio_fidl = fuchsia_hardware_audio; |
| |
| audio_fidl::wire::PcmFormat GetDefaultPcmFormat() { |
| audio_fidl::wire::PcmFormat format; |
| format.number_of_channels = 2; |
| format.sample_format = audio_fidl::wire::SampleFormat::kPcmSigned; |
| format.frame_rate = 48000; |
| format.bytes_per_sample = 2; |
| format.valid_bits_per_sample = 16; |
| return format; |
| } |
| |
| fidl::WireSyncClient<audio_fidl::StreamConfig> GetStreamClient( |
| fidl::WireSyncClient<audio_fidl::StreamConfigConnector> client) { |
| auto [stream_channel_local, stream_channel_remote] = |
| fidl::Endpoints<audio_fidl::StreamConfig>::Create(); |
| auto result = client->Connect(std::move(stream_channel_remote)); |
| if (!result.ok()) { |
| return {}; |
| } |
| return fidl::WireSyncClient<audio_fidl::StreamConfig>(std::move(stream_channel_local)); |
| } |
| |
| class MockSimpleAudio : public SimpleAudioStream { |
| public: |
| static constexpr uint32_t kTestFrameRate = 48000; |
| static constexpr uint8_t kTestNumberOfChannels = 2; |
| static constexpr uint32_t kTestDriverTransferBytes = 16; |
| static constexpr int64_t kTestExternalDelay = 123456789; |
| static constexpr uint32_t kTestClockDomain = audio_fidl::wire::kClockDomainExternal; |
| static constexpr uint32_t kTestPositionNotify = 4; |
| static constexpr float kTestGain = 1.2345f; |
| |
| MockSimpleAudio(zx_device_t* parent) : SimpleAudioStream(parent, false /* is input */) {} |
| |
| void PostSetPlugState(bool plugged, zx::duration delay) { |
| async::PostDelayedTask( |
| dispatcher(), |
| [this, plugged]() { |
| ScopedToken t(domain_token()); |
| SetPlugState(plugged); |
| }, |
| delay); |
| } |
| inspect::Inspector& inspect() { return SimpleAudioStream::inspect(); } |
| |
| protected: |
| zx_status_t Init() __TA_REQUIRES(domain_token()) override { |
| fbl::AllocChecker ac; |
| supported_formats_.reserve(1, &ac); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| SimpleAudioStream::SupportedFormat format = {}; |
| format.range.min_channels = kTestNumberOfChannels; |
| format.range.max_channels = kTestNumberOfChannels; |
| format.range.sample_formats = AUDIO_SAMPLE_FORMAT_16BIT; |
| format.range.min_frames_per_second = kTestFrameRate; |
| format.range.max_frames_per_second = kTestFrameRate; |
| format.range.flags = ASF_RANGE_FLAG_FPS_48000_FAMILY; |
| |
| format.frequency_ranges.push_back({ |
| .min_frequency = 40, |
| .max_frequency = 3'000, |
| }); |
| format.frequency_ranges.push_back({ |
| .min_frequency = 3'000, |
| .max_frequency = 25'000, |
| }); |
| |
| supported_formats_.push_back(std::move(format)); |
| |
| external_delay_nsec_ = kTestExternalDelay; |
| driver_transfer_bytes_ = kTestDriverTransferBytes; |
| clock_domain_ = kTestClockDomain; |
| |
| // Set our gain capabilities. |
| cur_gain_state_.cur_gain = 0; |
| cur_gain_state_.cur_mute = false; |
| cur_gain_state_.cur_agc = false; |
| cur_gain_state_.min_gain = 0; |
| cur_gain_state_.max_gain = 100; |
| cur_gain_state_.gain_step = 0; |
| cur_gain_state_.can_mute = true; |
| cur_gain_state_.can_agc = true; |
| |
| SetInitialPlugState(AUDIO_PDNF_CAN_NOTIFY); |
| |
| snprintf(device_name_, sizeof(device_name_), "test-audio-in"); |
| snprintf(mfr_name_, sizeof(mfr_name_), "Bike Sheds, Inc."); |
| snprintf(prod_name_, sizeof(prod_name_), "testy_mctestface"); |
| |
| unique_id_ = AUDIO_STREAM_UNIQUE_ID_BUILTIN_MICROPHONE; |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t SetGain(const audio_proto::SetGainReq& req) __TA_REQUIRES(domain_token()) override { |
| if (req.flags & AUDIO_SGF_GAIN_VALID) { |
| cur_gain_state_.cur_gain = req.gain; |
| } |
| if (req.flags & AUDIO_SGF_AGC_VALID) { |
| cur_gain_state_.cur_agc = req.flags & AUDIO_SGF_AGC; |
| } |
| if (req.flags & AUDIO_SGF_MUTE_VALID) { |
| cur_gain_state_.cur_mute = req.flags & AUDIO_SGF_MUTE; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t ChangeFormat(const audio_proto::StreamSetFmtReq& req) |
| __TA_REQUIRES(domain_token()) override { |
| return ZX_OK; |
| } |
| |
| zx_status_t GetBuffer(const audio_proto::RingBufGetBufferReq& req, uint32_t* out_num_rb_frames, |
| zx::vmo* out_buffer) __TA_REQUIRES(domain_token()) override { |
| zx::vmo rb; |
| *out_num_rb_frames = req.min_ring_buffer_frames; |
| zx::vmo::create(*out_num_rb_frames * 2 * 2, 0, &rb); |
| us_per_notification_ = (req.notifications_per_ring |
| ? (1'000'000 * req.min_ring_buffer_frames) / |
| (MockSimpleAudio::kTestFrameRate * req.notifications_per_ring) |
| : 0); |
| constexpr uint32_t rights = ZX_RIGHT_READ | ZX_RIGHT_WRITE | ZX_RIGHT_MAP | ZX_RIGHT_TRANSFER; |
| return rb.duplicate(rights, out_buffer); |
| } |
| |
| zx_status_t Start(uint64_t* out_start_time) __TA_REQUIRES(domain_token()) override { |
| *out_start_time = zx::clock::get_monotonic().get(); |
| if (us_per_notification_) { |
| notify_timer_.PostDelayed(dispatcher(), zx::usec(us_per_notification_)); |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t Stop() __TA_REQUIRES(domain_token()) override { |
| notify_timer_.Cancel(); |
| return ZX_OK; |
| } |
| zx_status_t ChangeActiveChannels(uint64_t mask, zx_time_t* set_time_out) |
| __TA_REQUIRES(domain_token()) override { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| void ProcessRingNotification() { |
| ScopedToken t(domain_token()); |
| audio_proto::RingBufPositionNotify resp = {}; |
| resp.hdr.cmd = AUDIO_RB_POSITION_NOTIFY; |
| resp.monotonic_time = zx::clock::get_monotonic().get(); |
| resp.ring_buffer_pos = kTestPositionNotify; |
| NotifyPosition(resp); |
| if (us_per_notification_) { |
| notify_timer_.PostDelayed(dispatcher(), zx::usec(us_per_notification_)); |
| } |
| } |
| |
| void ShutdownHook() __TA_REQUIRES(domain_token()) override { Stop(); } |
| |
| private: |
| async::TaskClosureMethod<MockSimpleAudio, &MockSimpleAudio::ProcessRingNotification> notify_timer_ |
| TA_GUARDED(domain_token()){this}; |
| |
| uint64_t us_per_notification_ = 0; // if 0, do not emit position notifications |
| }; |
| |
| class MockSimpleAudioWithExtension : public MockSimpleAudio { |
| public: |
| explicit MockSimpleAudioWithExtension(zx_device_t* parent) : MockSimpleAudio(parent) {} |
| zx_status_t ChangeActiveChannels(uint64_t mask, zx_time_t* set_time_out) override |
| __TA_REQUIRES(domain_token()) { |
| return (mask > (1u << kTestNumberOfChannels) - 1u) ? ZX_ERR_INVALID_ARGS : ZX_OK; |
| } |
| }; |
| |
| class SimpleAudioTest : public inspect::InspectTestHelper, public zxtest::Test { |
| public: |
| void SetUp() override {} |
| |
| void TearDown() override {} |
| |
| fidl::WireSyncClient<audio_fidl::StreamConfigConnector> GetClient(MockSimpleAudio* server) { |
| loop_.StartThread(); |
| auto endpoints = fidl::CreateEndpoints<audio_fidl::StreamConfigConnector>(); |
| fidl::BindServer(loop_.dispatcher(), std::move(endpoints->server), server); |
| return fidl::WireSyncClient<audio_fidl::StreamConfigConnector>(std::move(endpoints->client)); |
| } |
| |
| template <typename T> |
| static void CheckPropertyNotEqual(const inspect::NodeValue& node, std::string property, |
| T not_expected_value) { |
| const T* actual_value = node.get_property<T>(property); |
| EXPECT_TRUE(actual_value); |
| if (!actual_value) { |
| return; |
| } |
| EXPECT_NE(not_expected_value.value(), actual_value->value()); |
| } |
| |
| protected: |
| std::shared_ptr<MockDevice> root_ = MockDevice::FakeRootParent(); |
| async::Loop loop_{&kAsyncLoopConfigNeverAttachToThread}; |
| }; |
| |
| TEST_F(SimpleAudioTest, DdkLifeCycleTest) { |
| auto server = SimpleAudioStream::Create<MockSimpleAudio>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| ddk::SuspendTxn txn(server->zxdev(), 0, false, DEVICE_SUSPEND_REASON_SELECTIVE_SUSPEND); |
| server->DdkSuspend(std::move(txn)); |
| |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, UnbindAndAlsoShutdown) { |
| auto server = SimpleAudioStream::Create<MockSimpleAudio>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| server->DdkAsyncRemove(); |
| server->Shutdown(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, SetAndGetGain) { |
| auto server = SimpleAudioStream::Create<MockSimpleAudio>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| auto stream_client = GetStreamClient(GetClient(server.get())); |
| ASSERT_TRUE(stream_client.is_valid()); |
| { |
| fidl::Arena allocator; |
| audio_fidl::wire::GainState gain_state(allocator); |
| gain_state.set_gain_db(MockSimpleAudio::kTestGain); |
| auto status = stream_client->SetGain(std::move(gain_state)); |
| ASSERT_OK(status.status()); |
| } |
| |
| auto gain_state = stream_client->WatchGainState(); |
| ASSERT_OK(gain_state.status()); |
| ASSERT_EQ(MockSimpleAudio::kTestGain, gain_state.value().gain_state.gain_db()); |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, WatchGainAndCloseStreamBeforeReply) { |
| auto server = SimpleAudioStream::Create<MockSimpleAudio>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| auto stream_client = GetStreamClient(GetClient(server.get())); |
| ASSERT_TRUE(stream_client.is_valid()); |
| { |
| fidl::Arena allocator; |
| audio_fidl::wire::GainState gain_state(allocator); |
| gain_state.set_gain_db(MockSimpleAudio::kTestGain); |
| auto status = stream_client->SetGain(std::move(gain_state)); |
| ASSERT_OK(status.status()); |
| } |
| |
| // One watch for initial reply. |
| auto gain_state = stream_client->WatchGainState(); |
| ASSERT_OK(gain_state.status()); |
| ASSERT_EQ(MockSimpleAudio::kTestGain, gain_state.value().gain_state.gain_db()); |
| |
| // A second watch with no reply since there is no change of gain. |
| auto f = [](void* arg) -> int { |
| auto stream_client = static_cast<fidl::WireSyncClient<audio_fidl::StreamConfig>*>(arg); |
| [[maybe_unused]] auto result = (*stream_client)->WatchGainState(); |
| return 0; |
| }; |
| thrd_t th; |
| ASSERT_OK(thrd_create_with_name(&th, f, &stream_client, "test-thread")); |
| |
| // We want the watch to be started before we reset the channel triggering a deactivation. |
| zx::nanosleep(zx::deadline_after(zx::msec(100))); |
| stream_client.TakeClientEnd().TakeChannel().reset(); |
| |
| int result = -1; |
| thrd_join(th, &result); |
| ASSERT_EQ(result, 0); |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, SetAndGetAgc) { |
| auto server = SimpleAudioStream::Create<MockSimpleAudio>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| auto stream_client = GetStreamClient(GetClient(server.get())); |
| ASSERT_TRUE(stream_client.is_valid()); |
| { |
| fidl::Arena allocator; |
| audio_fidl::wire::GainState gain_state(allocator); |
| gain_state.set_agc_enabled(true); |
| auto status = stream_client->SetGain(gain_state); |
| ASSERT_OK(status.status()); |
| } |
| |
| auto gain_state1 = stream_client->WatchGainState(); |
| ASSERT_OK(gain_state1.status()); |
| ASSERT_TRUE(gain_state1.value().gain_state.agc_enabled()); |
| |
| { |
| fidl::Arena allocator; |
| audio_fidl::wire::GainState gain_state(allocator); |
| gain_state.set_agc_enabled(false); |
| auto status = stream_client->SetGain(gain_state); |
| ASSERT_OK(status.status()); |
| } |
| |
| auto gain_state2 = stream_client->WatchGainState(); |
| ASSERT_OK(gain_state2.status()); |
| ASSERT_FALSE(gain_state2.value().gain_state.agc_enabled()); |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, SetAndGetMute) { |
| auto server = SimpleAudioStream::Create<MockSimpleAudio>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| auto stream_client = GetStreamClient(GetClient(server.get())); |
| ASSERT_TRUE(stream_client.is_valid()); |
| { |
| fidl::Arena allocator; |
| audio_fidl::wire::GainState gain_state(allocator); |
| gain_state.set_muted(true); |
| auto status = stream_client->SetGain(gain_state); |
| ASSERT_OK(status.status()); |
| } |
| |
| auto gain_state1 = stream_client->WatchGainState(); |
| ASSERT_OK(gain_state1.status()); |
| ASSERT_TRUE(gain_state1.value().gain_state.muted()); |
| |
| { |
| fidl::Arena allocator; |
| audio_fidl::wire::GainState gain_state(allocator); |
| gain_state.set_muted(false); |
| auto status = stream_client->SetGain(gain_state); |
| ASSERT_OK(status.status()); |
| } |
| |
| auto gain_state2 = stream_client->WatchGainState(); |
| ASSERT_OK(gain_state2.status()); |
| ASSERT_FALSE(gain_state2.value().gain_state.muted()); |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, SetMuteWhenDisabled) { |
| struct MockSimpleAudioLocal : public MockSimpleAudio { |
| MockSimpleAudioLocal(zx_device_t* parent) : MockSimpleAudio(parent) {} |
| zx_status_t Init() __TA_REQUIRES(domain_token()) override { |
| auto status = MockSimpleAudio::Init(); |
| cur_gain_state_.can_mute = false; |
| return status; |
| } |
| }; |
| auto server = SimpleAudioStream::Create<MockSimpleAudioLocal>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| auto stream_client = GetStreamClient(GetClient(server.get())); |
| ASSERT_TRUE(stream_client.is_valid()); |
| { |
| fidl::Arena allocator; |
| audio_fidl::wire::GainState gain_state(allocator); |
| gain_state.set_muted(true); |
| auto status = stream_client->SetGain(std::move(gain_state)); |
| ASSERT_OK(status.status()); |
| } |
| |
| auto gain_state1 = stream_client->WatchGainState(); |
| ASSERT_OK(gain_state1.status()); |
| ASSERT_FALSE(gain_state1.value().gain_state.has_muted()); |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, Enumerate1) { |
| auto server = SimpleAudioStream::Create<MockSimpleAudio>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| auto stream_client = GetStreamClient(GetClient(server.get())); |
| ASSERT_TRUE(stream_client.is_valid()); |
| |
| auto ret = stream_client->GetSupportedFormats(); |
| auto& supported_formats = ret.value().supported_formats; |
| auto& formats = supported_formats[0].pcm_supported_formats(); |
| ASSERT_EQ(1, formats.channel_sets().count()); |
| ASSERT_EQ(2, formats.channel_sets()[0].attributes().count()); |
| ASSERT_EQ(1, formats.sample_formats().count()); |
| ASSERT_EQ(audio_fidl::wire::SampleFormat::kPcmSigned, formats.sample_formats()[0]); |
| ASSERT_EQ(1, formats.frame_rates().count()); |
| ASSERT_EQ(48000, formats.frame_rates()[0]); |
| ASSERT_EQ(1, formats.bytes_per_sample().count()); |
| ASSERT_EQ(2, formats.bytes_per_sample()[0]); |
| ASSERT_EQ(1, formats.valid_bits_per_sample().count()); |
| ASSERT_EQ(16, formats.valid_bits_per_sample()[0]); |
| |
| auto& channels_attributes = formats.channel_sets()[0].attributes(); |
| ASSERT_EQ(2, channels_attributes.count()); |
| ASSERT_EQ(40, channels_attributes[0].min_frequency()); |
| ASSERT_EQ(3'000, channels_attributes[0].max_frequency()); |
| ASSERT_EQ(3'000, channels_attributes[1].min_frequency()); |
| ASSERT_EQ(25'000, channels_attributes[1].max_frequency()); |
| |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, Enumerate2) { |
| struct MockSimpleAudioLocal : public MockSimpleAudio { |
| MockSimpleAudioLocal(zx_device_t* parent) : MockSimpleAudio(parent) {} |
| zx_status_t Init() __TA_REQUIRES(domain_token()) override { |
| auto status = MockSimpleAudio::Init(); |
| |
| SimpleAudioStream::SupportedFormat format1 = {}; |
| SimpleAudioStream::SupportedFormat format2 = {}; |
| |
| format1.range.min_channels = 2; |
| format1.range.max_channels = 4; |
| format1.range.sample_formats = AUDIO_SAMPLE_FORMAT_24BIT_IN32; |
| format1.range.min_frames_per_second = 48'000; |
| format1.range.max_frames_per_second = 768'000; |
| format1.range.flags = ASF_RANGE_FLAG_FPS_48000_FAMILY; |
| |
| format2.range.min_channels = 1; |
| format2.range.max_channels = 1; |
| format2.range.sample_formats = AUDIO_SAMPLE_FORMAT_32BIT_FLOAT; |
| format2.range.min_frames_per_second = 88'200; |
| format2.range.max_frames_per_second = 88'200; |
| format2.range.flags = |
| ASF_RANGE_FLAG_FPS_CONTINUOUS; // Ok only because min and max fps are equal. |
| |
| supported_formats_ = fbl::Vector<SupportedFormat>{format1, format2}; |
| return status; |
| } |
| }; |
| |
| auto server = SimpleAudioStream::Create<MockSimpleAudioLocal>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| auto stream_client = GetStreamClient(GetClient(server.get())); |
| ASSERT_TRUE(stream_client.is_valid()); |
| |
| auto ret = stream_client->GetSupportedFormats(); |
| auto& supported_formats = ret.value().supported_formats; |
| ASSERT_EQ(2, supported_formats.count()); |
| |
| auto& formats1 = supported_formats[0].pcm_supported_formats(); |
| ASSERT_EQ(3, formats1.channel_sets().count()); |
| ASSERT_EQ(2, formats1.channel_sets()[0].attributes().count()); |
| ASSERT_EQ(3, formats1.channel_sets()[1].attributes().count()); |
| ASSERT_EQ(4, formats1.channel_sets()[2].attributes().count()); |
| ASSERT_EQ(1, formats1.sample_formats().count()); |
| ASSERT_EQ(audio_fidl::wire::SampleFormat::kPcmSigned, formats1.sample_formats()[0]); |
| ASSERT_EQ(5, formats1.frame_rates().count()); |
| std::set<uint32_t> rates1; |
| for (auto& i : formats1.frame_rates()) { |
| rates1.insert(i); |
| } |
| ASSERT_EQ(rates1, std::set<uint32_t>({48'000, 96'000, 192'000, 384'000, 768'000})); |
| ASSERT_EQ(1, formats1.bytes_per_sample().count()); |
| ASSERT_EQ(4, formats1.bytes_per_sample()[0]); |
| ASSERT_EQ(1, formats1.valid_bits_per_sample().count()); |
| ASSERT_EQ(24, formats1.valid_bits_per_sample()[0]); |
| |
| auto& formats2 = supported_formats[1].pcm_supported_formats(); |
| ASSERT_EQ(1, formats2.channel_sets().count()); |
| ASSERT_EQ(1, formats2.channel_sets()[0].attributes().count()); |
| ASSERT_EQ(1, formats2.sample_formats().count()); |
| ASSERT_EQ(audio_fidl::wire::SampleFormat::kPcmFloat, formats2.sample_formats()[0]); |
| ASSERT_EQ(1, formats2.frame_rates().count()); |
| std::set<uint32_t> rates2; |
| for (auto& i : formats2.frame_rates()) { |
| rates2.insert(i); |
| } |
| ASSERT_EQ(rates2, std::set<uint32_t>({88'200})); |
| ASSERT_EQ(1, formats2.bytes_per_sample().count()); |
| ASSERT_EQ(4, formats2.bytes_per_sample()[0]); |
| ASSERT_EQ(1, formats2.valid_bits_per_sample().count()); |
| ASSERT_EQ(32, formats2.valid_bits_per_sample()[0]); |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, EnumerateMultipleRateFamilies) { |
| struct MockSimpleAudioLocal : public MockSimpleAudio { |
| MockSimpleAudioLocal(zx_device_t* parent) : MockSimpleAudio(parent) {} |
| zx_status_t Init() __TA_REQUIRES(domain_token()) override { |
| auto status = MockSimpleAudio::Init(); |
| |
| SimpleAudioStream::SupportedFormat format = {}; |
| |
| format.range.min_channels = 2; |
| format.range.max_channels = 2; |
| format.range.sample_formats = AUDIO_SAMPLE_FORMAT_24BIT_IN32; |
| format.range.min_frames_per_second = 44'100; |
| format.range.max_frames_per_second = 96'000; |
| format.range.flags = ASF_RANGE_FLAG_FPS_48000_FAMILY | ASF_RANGE_FLAG_FPS_44100_FAMILY; |
| |
| supported_formats_ = fbl::Vector<SupportedFormat>{format}; |
| return status; |
| } |
| }; |
| |
| auto server = SimpleAudioStream::Create<MockSimpleAudioLocal>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| auto stream_client = GetStreamClient(GetClient(server.get())); |
| ASSERT_TRUE(stream_client.is_valid()); |
| |
| auto ret = stream_client->GetSupportedFormats(); |
| auto& supported_formats = ret.value().supported_formats; |
| ASSERT_EQ(1, supported_formats.count()); |
| |
| auto& formats = supported_formats[0].pcm_supported_formats(); |
| ASSERT_EQ(1, formats.channel_sets().count()); |
| ASSERT_EQ(2, formats.channel_sets()[0].attributes().count()); |
| ASSERT_EQ(1, formats.sample_formats().count()); |
| ASSERT_EQ(audio_fidl::wire::SampleFormat::kPcmSigned, formats.sample_formats()[0]); |
| ASSERT_EQ(4, formats.frame_rates().count()); |
| // Must enumerate them in ascending order. |
| ASSERT_EQ(formats.frame_rates()[0], 44'100); |
| ASSERT_EQ(formats.frame_rates()[1], 48'000); |
| ASSERT_EQ(formats.frame_rates()[2], 88'200); |
| ASSERT_EQ(formats.frame_rates()[3], 96'000); |
| |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, CreateRingBuffer1) { |
| auto server = SimpleAudioStream::Create<MockSimpleAudio>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| auto stream_client = GetStreamClient(GetClient(server.get())); |
| ASSERT_TRUE(stream_client.is_valid()); |
| |
| auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create(); |
| |
| fidl::Arena allocator; |
| audio_fidl::wire::Format format(allocator); |
| format.set_pcm_format(allocator, GetDefaultPcmFormat()); |
| auto result0 = stream_client->CreateRingBuffer(std::move(format), std::move(remote)); |
| ASSERT_OK(result0.status()); |
| |
| auto result1 = fidl::WireCall(local)->GetProperties(); |
| ASSERT_OK(result1.status()); |
| ASSERT_EQ(result1.value().properties.driver_transfer_bytes(), |
| MockSimpleAudio::kTestDriverTransferBytes); |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, CreateRingBuffer2) { |
| struct MockSimpleAudioLocal : public MockSimpleAudio { |
| MockSimpleAudioLocal(zx_device_t* parent) : MockSimpleAudio(parent) {} |
| zx_status_t Init() __TA_REQUIRES(domain_token()) override { |
| SimpleAudioStream::SupportedFormat format = {}; |
| format.range.min_channels = 1; |
| format.range.max_channels = 4; |
| format.range.sample_formats = |
| AUDIO_SAMPLE_FORMAT_24BIT_IN32 | AUDIO_SAMPLE_FORMAT_FLAG_UNSIGNED; |
| format.range.min_frames_per_second = 22050; |
| format.range.max_frames_per_second = 88200; |
| format.range.flags = ASF_RANGE_FLAG_FPS_44100_FAMILY; |
| supported_formats_.push_back(std::move(format)); |
| return MockSimpleAudio::Init(); |
| } |
| }; |
| auto server = SimpleAudioStream::Create<MockSimpleAudioLocal>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| auto stream_client = GetStreamClient(GetClient(server.get())); |
| ASSERT_TRUE(stream_client.is_valid()); |
| |
| auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create(); |
| |
| audio_fidl::wire::PcmFormat pcm_format; |
| pcm_format.number_of_channels = 4; |
| pcm_format.sample_format = audio_fidl::wire::SampleFormat::kPcmUnsigned; |
| pcm_format.frame_rate = 44100; |
| pcm_format.bytes_per_sample = 4; |
| pcm_format.valid_bits_per_sample = 24; |
| |
| fidl::Arena allocator; |
| audio_fidl::wire::Format format(allocator); |
| format.set_pcm_format(allocator, std::move(pcm_format)); |
| auto result0 = stream_client->CreateRingBuffer(std::move(format), std::move(remote)); |
| ASSERT_OK(result0.status()); |
| |
| auto result1 = fidl::WireCall(local)->GetProperties(); |
| ASSERT_OK(result1.status()); |
| ASSERT_EQ(result1.value().properties.driver_transfer_bytes(), |
| MockSimpleAudio::kTestDriverTransferBytes); |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, SetBadFormat1) { |
| auto server = SimpleAudioStream::Create<MockSimpleAudio>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| auto stream_client = GetStreamClient(GetClient(server.get())); |
| ASSERT_TRUE(stream_client.is_valid()); |
| |
| auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create(); |
| |
| // Define a pretty bad format. |
| audio_fidl::wire::PcmFormat pcm_format; |
| pcm_format.sample_format = audio_fidl::wire::SampleFormat::kPcmSigned; |
| |
| fidl::Arena allocator; |
| audio_fidl::wire::Format format(allocator); |
| format.set_pcm_format(allocator, std::move(pcm_format)); |
| |
| auto result0 = stream_client->CreateRingBuffer(std::move(format), std::move(remote)); |
| ASSERT_EQ(ZX_OK, result0.status()); // CreateRingBuffer is sent successfully. |
| |
| auto result1 = stream_client->GetSupportedFormats(); |
| ASSERT_EQ(ZX_ERR_PEER_CLOSED, result1.status()); // With a bad format we get a channel close. |
| |
| auto result2 = fidl::WireCall(local)->GetProperties(); |
| ASSERT_EQ(ZX_ERR_PEER_CLOSED, result2.status()); // With a bad format we get a channel close. |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, SetBadFormat2) { |
| auto server = SimpleAudioStream::Create<MockSimpleAudio>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| auto stream_client = GetStreamClient(GetClient(server.get())); |
| ASSERT_TRUE(stream_client.is_valid()); |
| |
| auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create(); |
| |
| // Define an almost good format. |
| audio_fidl::wire::PcmFormat pcm_format = GetDefaultPcmFormat(); |
| pcm_format.frame_rate = 48001; // Bad rate. |
| |
| fidl::Arena allocator; |
| audio_fidl::wire::Format format(allocator); |
| format.set_pcm_format(allocator, std::move(pcm_format)); |
| |
| auto result0 = stream_client->CreateRingBuffer(std::move(format), std::move(remote)); |
| ASSERT_EQ(ZX_OK, result0.status()); // CreateRingBuffer is sent successfully. |
| |
| auto result1 = stream_client->GetSupportedFormats(); |
| ASSERT_EQ(ZX_ERR_PEER_CLOSED, result1.status()); // With a bad format we get a channel close. |
| |
| auto result2 = fidl::WireCall(local)->GetProperties(); |
| ASSERT_EQ(ZX_ERR_PEER_CLOSED, result2.status()); // With a bad format we get a channel close. |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, GetIds) { |
| auto server = SimpleAudioStream::Create<MockSimpleAudio>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| auto stream_client = GetStreamClient(GetClient(server.get())); |
| ASSERT_TRUE(stream_client.is_valid()); |
| auto result = stream_client->GetProperties(); |
| ASSERT_OK(result.status()); |
| |
| audio_stream_unique_id_t mic = AUDIO_STREAM_UNIQUE_ID_BUILTIN_MICROPHONE; |
| ASSERT_BYTES_EQ(result.value().properties.unique_id().data(), mic.data, |
| strlen(reinterpret_cast<char*>(mic.data))); |
| ASSERT_BYTES_EQ(result.value().properties.manufacturer().data(), "Bike Sheds, Inc.", |
| strlen("Bike Sheds, Inc.")); |
| ASSERT_EQ(result.value().properties.clock_domain(), MockSimpleAudio::kTestClockDomain); |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, MultipleChannelsPlugDetectState) { |
| auto server = SimpleAudioStream::Create<MockSimpleAudio>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| // We get 2 clients from the one FIDL channel acquired via FidlClient(). |
| fidl::WireSyncClient client_wrap{GetClient(server.get())}; |
| ASSERT_TRUE(client_wrap.is_valid()); |
| auto [stream_channel_local1, stream_channel_remote1] = |
| fidl::Endpoints<audio_fidl::StreamConfig>::Create(); |
| auto result0 = client_wrap->Connect(std::move(stream_channel_remote1)); |
| ASSERT_OK(result0.status()); |
| fidl::WireSyncClient<audio_fidl::StreamConfig> stream_client1(std::move(stream_channel_local1)); |
| auto [stream_channel_local2, stream_channel_remote2] = |
| fidl::Endpoints<audio_fidl::StreamConfig>::Create(); |
| auto result1 = client_wrap->Connect(std::move(stream_channel_remote2)); |
| ASSERT_OK(result1.status()); |
| fidl::WireSyncClient<audio_fidl::StreamConfig> stream_client2(std::move(stream_channel_local2)); |
| |
| auto prop1 = stream_client1->GetProperties(); |
| auto prop2 = stream_client2->GetProperties(); |
| ASSERT_OK(prop1.status()); |
| ASSERT_OK(prop2.status()); |
| |
| ASSERT_EQ(prop1.value().properties.plug_detect_capabilities(), |
| audio_fidl::wire::PlugDetectCapabilities::kCanAsyncNotify); |
| ASSERT_EQ(prop2.value().properties.plug_detect_capabilities(), |
| audio_fidl::wire::PlugDetectCapabilities::kCanAsyncNotify); |
| |
| auto state1 = stream_client1->WatchPlugState(); |
| auto state2 = stream_client2->WatchPlugState(); |
| ASSERT_OK(state1.status()); |
| ASSERT_OK(state2.status()); |
| ASSERT_FALSE(state1.value().plug_state.plugged()); |
| ASSERT_FALSE(state2.value().plug_state.plugged()); |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, WatchPlugDetectAndCloseStreamBeforeReply) { |
| auto server = SimpleAudioStream::Create<MockSimpleAudio>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| // We get 2 clients from the one FIDL channel acquired via FidlClient(). |
| fidl::WireSyncClient client_wrap{GetClient(server.get())}; |
| ASSERT_TRUE(client_wrap.is_valid()); |
| auto [stream_channel_local1, stream_channel_remote1] = |
| fidl::Endpoints<audio_fidl::StreamConfig>::Create(); |
| auto result0 = client_wrap->Connect(std::move(stream_channel_remote1)); |
| ASSERT_OK(result0.status()); |
| fidl::WireSyncClient<audio_fidl::StreamConfig> stream_client1(std::move(stream_channel_local1)); |
| auto [stream_channel_local2, stream_channel_remote2] = |
| fidl::Endpoints<audio_fidl::StreamConfig>::Create(); |
| auto result1 = client_wrap->Connect(std::move(stream_channel_remote2)); |
| ASSERT_OK(result1.status()); |
| fidl::WireSyncClient<audio_fidl::StreamConfig> stream_client2(std::move(stream_channel_local2)); |
| |
| auto prop1 = stream_client1->GetProperties(); |
| auto prop2 = stream_client2->GetProperties(); |
| ASSERT_OK(prop1.status()); |
| ASSERT_OK(prop2.status()); |
| |
| ASSERT_EQ(prop1.value().properties.plug_detect_capabilities(), |
| audio_fidl::wire::PlugDetectCapabilities::kCanAsyncNotify); |
| ASSERT_EQ(prop2.value().properties.plug_detect_capabilities(), |
| audio_fidl::wire::PlugDetectCapabilities::kCanAsyncNotify); |
| |
| // Watch each channel for initial reply. |
| auto state1 = stream_client1->WatchPlugState(); |
| auto state2 = stream_client2->WatchPlugState(); |
| ASSERT_OK(state1.status()); |
| ASSERT_OK(state2.status()); |
| ASSERT_FALSE(state1.value().plug_state.plugged()); |
| ASSERT_FALSE(state2.value().plug_state.plugged()); |
| |
| // Secondary watches with no reply since there is no change of plug detect state. |
| auto f = [](void* arg) -> int { |
| auto stream_client = static_cast<fidl::WireSyncClient<audio_fidl::StreamConfig>*>(arg); |
| [[maybe_unused]] auto result = (*stream_client)->WatchPlugState(); |
| return 0; |
| }; |
| thrd_t th1; |
| ASSERT_OK(thrd_create_with_name(&th1, f, &stream_client1, "test-thread-1")); |
| thrd_t th2; |
| ASSERT_OK(thrd_create_with_name(&th2, f, &stream_client2, "test-thread-2")); |
| |
| // We want the watches to be started before we reset the channels triggering deactivations. |
| zx::nanosleep(zx::deadline_after(zx::msec(100))); |
| stream_client1.TakeClientEnd().TakeChannel().reset(); |
| stream_client2.TakeClientEnd().TakeChannel().reset(); |
| |
| int result = -1; |
| thrd_join(th1, &result); |
| ASSERT_EQ(result, 0); |
| result = -1; |
| thrd_join(th2, &result); |
| ASSERT_EQ(result, 0); |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, MultipleChannelsPlugDetectNotify) { |
| auto server = SimpleAudioStream::Create<MockSimpleAudio>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| fidl::WireSyncClient client_wrap{GetClient(server.get())}; |
| ASSERT_TRUE(client_wrap.is_valid()); |
| auto [stream_channel_local1, stream_channel_remote1] = |
| fidl::Endpoints<audio_fidl::StreamConfig>::Create(); |
| auto result0 = client_wrap->Connect(std::move(stream_channel_remote1)); |
| ASSERT_OK(result0.status()); |
| fidl::WireSyncClient<audio_fidl::StreamConfig> stream_client1(std::move(stream_channel_local1)); |
| auto [stream_channel_local2, stream_channel_remote2] = |
| fidl::Endpoints<audio_fidl::StreamConfig>::Create(); |
| auto result1 = client_wrap->Connect(std::move(stream_channel_remote2)); |
| ASSERT_OK(result1.status()); |
| fidl::WireSyncClient<audio_fidl::StreamConfig> stream_client2(std::move(stream_channel_local2)); |
| auto [stream_channel_local3, stream_channel_remote3] = |
| fidl::Endpoints<audio_fidl::StreamConfig>::Create(); |
| auto result2 = client_wrap->Connect(std::move(stream_channel_remote3)); |
| ASSERT_OK(result2.status()); |
| fidl::WireSyncClient<audio_fidl::StreamConfig> stream_client3(std::move(stream_channel_local3)); |
| |
| auto state1a = stream_client1->WatchPlugState(); |
| auto state2a = stream_client2->WatchPlugState(); |
| auto state3a = stream_client3->WatchPlugState(); |
| ASSERT_OK(state1a.status()); |
| ASSERT_OK(state2a.status()); |
| ASSERT_OK(state3a.status()); |
| ASSERT_FALSE(state1a.value().plug_state.plugged()); |
| ASSERT_FALSE(state2a.value().plug_state.plugged()); |
| ASSERT_FALSE(state3a.value().plug_state.plugged()); |
| |
| server->PostSetPlugState(true, zx::duration(zx::msec(100))); |
| |
| auto state1b = stream_client1->WatchPlugState(); |
| auto state2b = stream_client2->WatchPlugState(); |
| auto state3b = stream_client3->WatchPlugState(); |
| ASSERT_OK(state1b.status()); |
| ASSERT_OK(state2b.status()); |
| ASSERT_OK(state3b.status()); |
| ASSERT_TRUE(state1b.value().plug_state.plugged()); |
| ASSERT_TRUE(state2b.value().plug_state.plugged()); |
| ASSERT_TRUE(state3b.value().plug_state.plugged()); |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, MultipleChannelsGainState) { |
| auto server = SimpleAudioStream::Create<MockSimpleAudio>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| fidl::WireSyncClient client_wrap{GetClient(server.get())}; |
| ASSERT_TRUE(client_wrap.is_valid()); |
| auto [stream_channel_local1, stream_channel_remote1] = |
| fidl::Endpoints<audio_fidl::StreamConfig>::Create(); |
| auto result0 = client_wrap->Connect(std::move(stream_channel_remote1)); |
| ASSERT_OK(result0.status()); |
| fidl::WireSyncClient<audio_fidl::StreamConfig> stream_client1(std::move(stream_channel_local1)); |
| auto [stream_channel_local2, stream_channel_remote2] = |
| fidl::Endpoints<audio_fidl::StreamConfig>::Create(); |
| auto result1 = client_wrap->Connect(std::move(stream_channel_remote2)); |
| ASSERT_OK(result1.status()); |
| fidl::WireSyncClient<audio_fidl::StreamConfig> stream_client2(std::move(stream_channel_local2)); |
| |
| auto state1 = stream_client1->WatchGainState(); |
| auto state2 = stream_client2->WatchGainState(); |
| ASSERT_OK(state1.status()); |
| ASSERT_OK(state2.status()); |
| ASSERT_EQ(0.f, state1.value().gain_state.gain_db()); |
| ASSERT_EQ(0.f, state2.value().gain_state.gain_db()); |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, MultipleChannelsGainStateNotify) { |
| auto server = SimpleAudioStream::Create<MockSimpleAudio>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| fidl::WireSyncClient client_wrap{GetClient(server.get())}; |
| ASSERT_TRUE(client_wrap.is_valid()); |
| auto [stream_channel_local1, stream_channel_remote1] = |
| fidl::Endpoints<audio_fidl::StreamConfig>::Create(); |
| auto result0 = client_wrap->Connect(std::move(stream_channel_remote1)); |
| ASSERT_OK(result0.status()); |
| fidl::WireSyncClient<audio_fidl::StreamConfig> stream_client1(std::move(stream_channel_local1)); |
| auto [stream_channel_local2, stream_channel_remote2] = |
| fidl::Endpoints<audio_fidl::StreamConfig>::Create(); |
| auto result1 = client_wrap->Connect(std::move(stream_channel_remote2)); |
| ASSERT_OK(result1.status()); |
| fidl::WireSyncClient<audio_fidl::StreamConfig> stream_client2(std::move(stream_channel_local2)); |
| auto [stream_channel_local3, stream_channel_remote3] = |
| fidl::Endpoints<audio_fidl::StreamConfig>::Create(); |
| auto result2 = client_wrap->Connect(std::move(stream_channel_remote3)); |
| ASSERT_OK(result2.status()); |
| fidl::WireSyncClient<audio_fidl::StreamConfig> stream_client3(std::move(stream_channel_local3)); |
| |
| auto state1a = stream_client1->WatchGainState(); |
| auto state2a = stream_client2->WatchGainState(); |
| auto state3a = stream_client3->WatchGainState(); |
| ASSERT_OK(state1a.status()); |
| ASSERT_OK(state2a.status()); |
| ASSERT_OK(state3a.status()); |
| ASSERT_EQ(0.f, state1a.value().gain_state.gain_db()); |
| ASSERT_EQ(0.f, state2a.value().gain_state.gain_db()); |
| ASSERT_EQ(0.f, state3a.value().gain_state.gain_db()); |
| |
| auto f = [](void* arg) -> int { |
| zx::nanosleep(zx::deadline_after(zx::msec(100))); |
| auto stream_client = static_cast<fidl::WireSyncClient<audio_fidl::StreamConfig>*>(arg); |
| |
| fidl::Arena allocator; |
| audio_fidl::wire::GainState gain_state(allocator); |
| gain_state.set_muted(false).set_agc_enabled(false).set_gain_db(MockSimpleAudio::kTestGain); |
| auto result = (*stream_client)->SetGain(std::move(gain_state)); |
| if (result.ok()) { |
| return 0; |
| } else { |
| return -1; |
| } |
| }; |
| thrd_t th; |
| ASSERT_OK(thrd_create_with_name(&th, f, &stream_client1, "test-thread")); |
| |
| auto state1b = stream_client1->WatchGainState(); |
| auto state2b = stream_client2->WatchGainState(); |
| auto state3b = stream_client3->WatchGainState(); |
| ASSERT_OK(state1b.status()); |
| ASSERT_OK(state2b.status()); |
| ASSERT_OK(state3b.status()); |
| ASSERT_EQ(MockSimpleAudio::kTestGain, state1b.value().gain_state.gain_db()); |
| ASSERT_EQ(MockSimpleAudio::kTestGain, state2b.value().gain_state.gain_db()); |
| ASSERT_EQ(MockSimpleAudio::kTestGain, state3b.value().gain_state.gain_db()); |
| |
| int result = -1; |
| thrd_join(th, &result); |
| ASSERT_EQ(result, 0); |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, RingBufferTests) { |
| auto server = SimpleAudioStream::Create<MockSimpleAudio>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| auto stream_client = GetStreamClient(GetClient(server.get())); |
| ASSERT_TRUE(stream_client.is_valid()); |
| auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create(); |
| |
| fidl::Arena allocator; |
| audio_fidl::wire::Format format(allocator); |
| auto default_format = GetDefaultPcmFormat(); |
| format.set_pcm_format(allocator, default_format); |
| uint32_t frame_size = default_format.number_of_channels * default_format.bytes_per_sample; |
| |
| auto rb = stream_client->CreateRingBuffer(std::move(format), std::move(remote)); |
| ASSERT_OK(rb.status()); |
| |
| constexpr uint32_t kNumberOfPositionNotifications = 5; |
| // Buffer is set to hold at least 1 second, with kNumberOfPositionNotifications notifications |
| // per ring buffer (i.e. per second) we set the time waiting for the watch below to 200ms+. |
| constexpr uint32_t kMinFrames = MockSimpleAudio::kTestFrameRate; |
| auto vmo = fidl::WireCall(local)->GetVmo(kMinFrames, kNumberOfPositionNotifications); |
| ASSERT_OK(vmo.status()); |
| uint32_t frames_expected = |
| kMinFrames + (MockSimpleAudio::kTestDriverTransferBytes + frame_size - 1) / frame_size; |
| ASSERT_EQ(vmo->value()->num_frames, frames_expected); |
| |
| constexpr uint64_t kSomeActiveChannelsMask = 0xc3; |
| auto active_channels = fidl::WireCall(local)->SetActiveChannels(kSomeActiveChannelsMask); |
| ASSERT_TRUE(active_channels->is_error()); |
| ASSERT_EQ(active_channels->error_value(), ZX_ERR_NOT_SUPPORTED); |
| |
| // Check inspect state. |
| { |
| ASSERT_NO_FATAL_FAILURE(ReadInspect(server->inspect().DuplicateVmo())); |
| auto* simple_audio = hierarchy().GetByPath({"simple_audio_stream"}); |
| ASSERT_TRUE(simple_audio); |
| ASSERT_NO_FATAL_FAILURE( |
| CheckProperty(simple_audio->node(), "state", inspect::StringPropertyValue("created"))); |
| ASSERT_NO_FATAL_FAILURE( |
| CheckProperty(simple_audio->node(), "start_time", inspect::IntPropertyValue(0))); |
| ASSERT_NO_FATAL_FAILURE( |
| CheckProperty(simple_audio->node(), "frames_requested", |
| inspect::UintPropertyValue(MockSimpleAudio::kTestFrameRate))); |
| } |
| |
| auto start = fidl::WireCall(local)->Start(); |
| ASSERT_OK(start.status()); |
| |
| // Check updated inspect state. |
| { |
| ASSERT_NO_FATAL_FAILURE(ReadInspect(server->inspect().DuplicateVmo())); |
| auto* simple_audio = hierarchy().GetByPath({"simple_audio_stream"}); |
| ASSERT_TRUE(simple_audio); |
| ASSERT_NO_FATAL_FAILURE( |
| CheckProperty(simple_audio->node(), "state", inspect::StringPropertyValue("started"))); |
| ASSERT_NO_FATAL_FAILURE( |
| CheckPropertyNotEqual(simple_audio->node(), "start_time", inspect::IntPropertyValue(0))); |
| } |
| |
| auto position = fidl::WireCall(local)->WatchClockRecoveryPositionInfo(); |
| ASSERT_EQ(MockSimpleAudio::kTestPositionNotify, position.value().position_info.position); |
| |
| auto stop = fidl::WireCall(local)->Stop(); |
| ASSERT_OK(stop.status()); |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| // Validate that the library can succeed and fail for SetActiveChannels calls. |
| TEST_F(SimpleAudioTest, SetActiveChannels) { |
| auto server = SimpleAudioStream::Create<MockSimpleAudioWithExtension>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| auto stream_client = GetStreamClient(GetClient(server.get())); |
| ASSERT_TRUE(stream_client.is_valid()); |
| auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create(); |
| |
| fidl::Arena allocator; |
| audio_fidl::wire::Format format(allocator); |
| format.set_pcm_format(allocator, GetDefaultPcmFormat()); |
| |
| auto rb = stream_client->CreateRingBuffer(format, std::move(remote)); |
| ASSERT_OK(rb.status()); |
| |
| auto vmo = fidl::WireCall(local)->GetVmo(MockSimpleAudio::kTestFrameRate, 0); |
| ASSERT_OK(vmo.status()); |
| |
| auto active_channels1 = fidl::WireCall(local)->SetActiveChannels(0x01); |
| ASSERT_TRUE(active_channels1->is_ok()); |
| |
| auto active_channels2 = fidl::WireCall(local)->SetActiveChannels(0x0fff); |
| ASSERT_TRUE(active_channels2->is_error()); |
| ASSERT_EQ(active_channels2->error_value(), ZX_ERR_INVALID_ARGS); |
| |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, RingBufferStartBeforeGetVmo) { |
| auto server = SimpleAudioStream::Create<MockSimpleAudio>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| auto stream_client = GetStreamClient(GetClient(server.get())); |
| ASSERT_TRUE(stream_client.is_valid()); |
| auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create(); |
| |
| fidl::Arena allocator; |
| audio_fidl::wire::Format format(allocator); |
| format.set_pcm_format(allocator, GetDefaultPcmFormat()); |
| |
| auto rb = stream_client->CreateRingBuffer(std::move(format), std::move(remote)); |
| ASSERT_OK(rb.status()); |
| |
| // Start() before GetVmo() must result in channel closure |
| auto start = fidl::WireCall(local)->Start(); |
| ASSERT_EQ(ZX_ERR_PEER_CLOSED, start.status()); // We get a channel close. |
| |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, RingBufferStartWhileStarted) { |
| auto server = SimpleAudioStream::Create<MockSimpleAudio>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| auto stream_client = GetStreamClient(GetClient(server.get())); |
| ASSERT_TRUE(stream_client.is_valid()); |
| auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create(); |
| |
| fidl::Arena allocator; |
| audio_fidl::wire::Format format(allocator); |
| format.set_pcm_format(allocator, GetDefaultPcmFormat()); |
| |
| auto rb = stream_client->CreateRingBuffer(std::move(format), std::move(remote)); |
| ASSERT_OK(rb.status()); |
| |
| auto vmo = fidl::WireCall(local)->GetVmo(MockSimpleAudio::kTestFrameRate, 0); |
| ASSERT_OK(vmo.status()); |
| |
| auto start = fidl::WireCall(local)->Start(); |
| ASSERT_OK(start.status()); |
| |
| // Start() while already started must result in channel closure |
| auto restart = fidl::WireCall(local)->Start(); |
| ASSERT_EQ(ZX_ERR_PEER_CLOSED, restart.status()); // We get a channel close. |
| |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, RingBufferStopBeforeGetVmo) { |
| auto server = SimpleAudioStream::Create<MockSimpleAudio>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| auto stream_client = GetStreamClient(GetClient(server.get())); |
| ASSERT_TRUE(stream_client.is_valid()); |
| auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create(); |
| |
| fidl::Arena allocator; |
| audio_fidl::wire::Format format(allocator); |
| format.set_pcm_format(allocator, GetDefaultPcmFormat()); |
| |
| auto rb = stream_client->CreateRingBuffer(std::move(format), std::move(remote)); |
| ASSERT_OK(rb.status()); |
| |
| // Stop() before GetVmo() must result in channel closure |
| auto stop = fidl::WireCall(local)->Stop(); |
| ASSERT_EQ(ZX_ERR_PEER_CLOSED, stop.status()); // We get a channel close. |
| |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, RingBufferStopWhileStopped) { |
| auto server = SimpleAudioStream::Create<MockSimpleAudio>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| auto stream_client = GetStreamClient(GetClient(server.get())); |
| ASSERT_TRUE(stream_client.is_valid()); |
| auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create(); |
| |
| fidl::Arena allocator; |
| audio_fidl::wire::Format format(allocator); |
| format.set_pcm_format(allocator, GetDefaultPcmFormat()); |
| |
| auto rb = stream_client->CreateRingBuffer(std::move(format), std::move(remote)); |
| ASSERT_OK(rb.status()); |
| |
| auto vmo = fidl::WireCall(local)->GetVmo(MockSimpleAudio::kTestFrameRate, 0); |
| ASSERT_OK(vmo.status()); |
| |
| // We are already stopped, but this should be harmless |
| auto stop = fidl::WireCall(local)->Stop(); |
| ASSERT_OK(stop.status()); |
| // Another stop immediately afterward should also be harmless |
| auto restop = fidl::WireCall(local)->Stop(); |
| ASSERT_OK(restop.status()); |
| |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, WatchPositionAndCloseRingBufferBeforeReply) { |
| auto server = SimpleAudioStream::Create<MockSimpleAudio>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| auto stream_client = GetStreamClient(GetClient(server.get())); |
| ASSERT_TRUE(stream_client.is_valid()); |
| auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create(); |
| |
| fidl::Arena allocator; |
| audio_fidl::wire::Format format(allocator); |
| format.set_pcm_format(allocator, GetDefaultPcmFormat()); |
| |
| auto rb = stream_client->CreateRingBuffer(std::move(format), std::move(remote)); |
| ASSERT_OK(rb.status()); |
| |
| constexpr uint32_t kNumberOfPositionNotifications = 5; |
| // Buffer is set to hold at least 1 second, with kNumberOfPositionNotifications notifications |
| // per ring buffer (i.e. per second) the time waiting before getting a position reply is 200ms+. |
| |
| auto vmo = fidl::WireCall(local)->GetVmo(MockSimpleAudio::kTestFrameRate, |
| kNumberOfPositionNotifications); |
| ASSERT_OK(vmo.status()); |
| |
| auto start = fidl::WireCall(local)->Start(); |
| ASSERT_OK(start.status()); |
| |
| // Watch position notifications. |
| auto f = [](void* arg) -> int { |
| auto ch = static_cast<fidl::ClientEnd<audio_fidl::RingBuffer>*>(arg); |
| [[maybe_unused]] auto result = fidl::WireCall(*ch)->WatchClockRecoveryPositionInfo(); |
| return 0; |
| }; |
| thrd_t th; |
| ASSERT_OK(thrd_create_with_name(&th, f, &local, "test-thread")); |
| |
| // We want the watch to be started before we reset the channel triggering a deactivation. |
| zx::nanosleep(zx::deadline_after(zx::msec(100))); |
| local.reset(); |
| stream_client.TakeClientEnd().TakeChannel().reset(); |
| |
| int result = -1; |
| thrd_join(th, &result); |
| ASSERT_EQ(result, 0); |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, GetDriverTransferBytes) { |
| auto server = SimpleAudioStream::Create<MockSimpleAudio>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| auto stream_client = GetStreamClient(GetClient(server.get())); |
| ASSERT_TRUE(stream_client.is_valid()); |
| |
| auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create(); |
| |
| fidl::Arena allocator; |
| audio_fidl::wire::Format format(allocator); |
| format.set_pcm_format(allocator, GetDefaultPcmFormat()); |
| auto result0 = stream_client->CreateRingBuffer(std::move(format), std::move(remote)); |
| ASSERT_EQ(ZX_OK, result0.status()); // CreateRingBuffer is sent successfully. |
| |
| auto result1 = fidl::WireCall(local)->GetProperties(); |
| ASSERT_OK(result1.status()); |
| ASSERT_EQ(result1.value().properties.driver_transfer_bytes(), |
| MockSimpleAudio::kTestDriverTransferBytes); |
| |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, WatchDelays) { |
| auto server = SimpleAudioStream::Create<MockSimpleAudio>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| auto stream_client = GetStreamClient(GetClient(server.get())); |
| ASSERT_TRUE(stream_client.is_valid()); |
| auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create(); |
| |
| fidl::Arena allocator; |
| audio_fidl::wire::Format format(allocator); |
| format.set_pcm_format(allocator, GetDefaultPcmFormat()); |
| |
| auto rb = stream_client->CreateRingBuffer(std::move(format), std::move(remote)); |
| ASSERT_OK(rb.status()); |
| auto delay_info = fidl::WireCall(local)->WatchDelayInfo(); |
| ASSERT_OK(delay_info.status()); |
| ASSERT_EQ(delay_info->delay_info.internal_delay(), 0); |
| ASSERT_EQ(delay_info->delay_info.external_delay(), MockSimpleAudio::kTestExternalDelay); |
| |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, ClientCloseStreamConfigProtocol) { |
| auto server = SimpleAudioStream::Create<MockSimpleAudio>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| auto stream_client = GetStreamClient(GetClient(server.get())); |
| ASSERT_TRUE(stream_client.is_valid()); |
| |
| // To make sure the 1-way Connect call is completed in the StreamConfigConnector server, |
| // make a 2-way call. Since StreamConfigConnector does not have a 2-way call, we use |
| // StreamConfig synchronously. |
| auto result = stream_client->GetProperties(); |
| ASSERT_OK(result.status()); |
| |
| stream_client.TakeClientEnd().TakeChannel().reset(); |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, ClientCloseRingBufferProtocol) { |
| auto server = SimpleAudioStream::Create<MockSimpleAudio>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| auto stream_client = GetStreamClient(GetClient(server.get())); |
| ASSERT_TRUE(stream_client.is_valid()); |
| |
| auto endpoints = fidl::Endpoints<audio_fidl::RingBuffer>::Create(); |
| |
| fidl::Arena allocator; |
| audio_fidl::wire::Format format(allocator); |
| format.set_pcm_format(allocator, GetDefaultPcmFormat()); |
| |
| auto ret = stream_client->CreateRingBuffer(std::move(format), std::move(endpoints.server)); |
| ASSERT_OK(ret.status()); |
| |
| // To make sure the 1-way Connect call is completed in the StreamConfigConnector server, |
| // make a 2-way call. Since StreamConfigConnector does not have a 2-way call, we use |
| // StreamConfig synchronously. |
| auto result = stream_client->GetProperties(); |
| ASSERT_OK(result.status()); |
| |
| endpoints.client.reset(); |
| |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, ClientCloseStreamConfigProtocolWithARingBufferProtocol) { |
| auto server = SimpleAudioStream::Create<MockSimpleAudio>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| auto stream_client = GetStreamClient(GetClient(server.get())); |
| ASSERT_TRUE(stream_client.is_valid()); |
| |
| auto endpoints = fidl::CreateEndpoints<audio_fidl::RingBuffer>(); |
| ASSERT_OK(endpoints.status_value()); |
| |
| fidl::Arena allocator; |
| audio_fidl::wire::Format format(allocator); |
| format.set_pcm_format(allocator, GetDefaultPcmFormat()); |
| |
| auto ret = stream_client->CreateRingBuffer(std::move(format), std::move(endpoints->server)); |
| ASSERT_OK(ret.status()); |
| |
| // To make sure the 1-way Connect call is completed in the StreamConfigConnector server, |
| // make a 2-way call. Since StreamConfigConnector does not have a 2-way call, we use |
| // StreamConfig synchronously. |
| auto result = stream_client->GetProperties(); |
| ASSERT_OK(result.status()); |
| |
| stream_client = {}; |
| |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| TEST_F(SimpleAudioTest, NonPrivileged) { |
| auto server = SimpleAudioStream::Create<MockSimpleAudio>(root_.get()); |
| ASSERT_NOT_NULL(server); |
| |
| fidl::WireSyncClient client_wrap{GetClient(server.get())}; |
| ASSERT_TRUE(client_wrap.is_valid()); |
| auto [stream_channel_local1, stream_channel_remote1] = |
| fidl::Endpoints<audio_fidl::StreamConfig>::Create(); |
| auto result0 = client_wrap->Connect(std::move(stream_channel_remote1)); |
| ASSERT_OK(result0.status()); |
| fidl::WireSyncClient<audio_fidl::StreamConfig> stream_client1(std::move(stream_channel_local1)); |
| auto [stream_channel_local2, stream_channel_remote2] = |
| fidl::Endpoints<audio_fidl::StreamConfig>::Create(); |
| auto result1 = client_wrap->Connect(std::move(stream_channel_remote2)); |
| ASSERT_OK(result1.status()); |
| fidl::WireSyncClient<audio_fidl::StreamConfig> stream_client2(std::move(stream_channel_local2)); |
| auto [stream_channel_local3, stream_channel_remote3] = |
| fidl::Endpoints<audio_fidl::StreamConfig>::Create(); |
| auto result2 = client_wrap->Connect(std::move(stream_channel_remote3)); |
| ASSERT_OK(result2.status()); |
| fidl::WireSyncClient<audio_fidl::StreamConfig> stream_client3(std::move(stream_channel_local3)); |
| |
| auto endpoints1 = fidl::Endpoints<audio_fidl::RingBuffer>::Create(); |
| |
| { |
| fidl::Arena allocator; |
| audio_fidl::wire::Format format(allocator); |
| format.set_pcm_format(allocator, GetDefaultPcmFormat()); |
| |
| auto ret1 = stream_client1->CreateRingBuffer(std::move(format), std::move(endpoints1.server)); |
| ASSERT_OK(ret1.status()); |
| } |
| fidl::WireSyncClient<audio_fidl::RingBuffer> ringbuffer1(std::move(endpoints1.client)); |
| auto vmo1 = ringbuffer1->GetVmo(MockSimpleAudio::kTestFrameRate, /* notifs_per_sec = */ 0); |
| ASSERT_OK(vmo1.status()); |
| |
| auto endpoints2 = fidl::Endpoints<audio_fidl::RingBuffer>::Create(); |
| |
| { |
| fidl::Arena allocator; |
| audio_fidl::wire::Format format(allocator); |
| format.set_pcm_format(allocator, GetDefaultPcmFormat()); |
| |
| auto ret2 = stream_client2->CreateRingBuffer(std::move(format), std::move(endpoints2.server)); |
| ASSERT_OK(ret2.status()); |
| } |
| fidl::WireSyncClient<audio_fidl::RingBuffer> ringbuffer2(std::move(endpoints2.client)); |
| auto vmo2 = ringbuffer2->GetVmo(MockSimpleAudio::kTestFrameRate, 0); |
| ASSERT_NOT_OK(vmo2.status()); // Non-privileged channel. |
| |
| auto endpoints3 = fidl::Endpoints<audio_fidl::RingBuffer>::Create(); |
| |
| { |
| fidl::Arena allocator; |
| audio_fidl::wire::Format format(allocator); |
| format.set_pcm_format(allocator, GetDefaultPcmFormat()); |
| |
| auto ret3 = stream_client3->CreateRingBuffer(std::move(format), std::move(endpoints3.server)); |
| ASSERT_OK(ret3.status()); |
| } |
| fidl::WireSyncClient<audio_fidl::RingBuffer> ringbuffer3(std::move(endpoints3.client)); |
| auto vmo3 = ringbuffer3->GetVmo(MockSimpleAudio::kTestFrameRate, 0); |
| ASSERT_NOT_OK(vmo3.status()); // Non-privileged channel. |
| |
| loop_.Shutdown(); |
| server->DdkAsyncRemove(); |
| mock_ddk::ReleaseFlaggedDevices(root_.get()); |
| } |
| |
| } // namespace audio |