blob: 830dee885dbe8d17ad1ef8517cc86f388ebd2ef0 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fuchsia/device/llcpp/fidl.h>
#include <fuchsia/hardware/audio/llcpp/fidl.h>
#include <lib/fake_ddk/fake_ddk.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 <set>
#include <audio-proto-utils/format-utils.h>
#include <audio-utils/audio-output.h>
#include <zxtest/zxtest.h>
namespace audio {
using ::llcpp::fuchsia::hardware::audio::Device;
class MockSimpleAudio : public SimpleAudioStream {
public:
static constexpr uint32_t kTestFrameRate = 48000;
static constexpr uint8_t kTestNumberOfChannels = 2;
static constexpr uint32_t kTestFifoDepth = 16;
static constexpr uint32_t kTestPositionNotify = 4;
MockSimpleAudio(zx_device_t* parent) : SimpleAudioStream(parent, false /* is input */) {}
zx_status_t PostSetPlugState(bool plugged) {
async::PostTask(dispatcher(), [this, plugged]() {
ScopedToken t(domain_token());
SimpleAudioStream::SetPlugState(plugged);
});
return ZX_OK;
}
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;
}
audio_stream_format_range_t range;
range.min_channels = kTestNumberOfChannels;
range.max_channels = kTestNumberOfChannels;
range.sample_formats = AUDIO_SAMPLE_FORMAT_16BIT;
range.min_frames_per_second = kTestFrameRate;
range.max_frames_per_second = kTestFrameRate;
range.flags = ASF_RANGE_FLAG_FPS_48000_FAMILY;
supported_formats_.push_back(range);
fifo_depth_ = kTestFifoDepth;
// 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 = false;
cur_gain_state_.can_agc = false;
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;
clock_domain_ = 0;
return ZX_OK;
}
zx_status_t SetGain(const audio_proto::SetGainReq& req) __TA_REQUIRES(domain_token()) override {
cur_gain_state_.cur_gain = req.gain;
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_ = 1'000 * MockSimpleAudio::kTestFrameRate / *out_num_rb_frames * 1'000 /
req.notifications_per_ring;
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();
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;
}
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);
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};
uint32_t us_per_notification_ = 0;
};
class Bind;
class Bind : public fake_ddk::Bind {
public:
int total_children() const { return total_children_; }
zx_status_t DeviceAdd(zx_driver_t* drv, zx_device_t* parent, device_add_args_t* args,
zx_device_t** out) override {
if (parent == fake_ddk::kFakeParent) {
*out = fake_ddk::kFakeDevice;
add_called_ = true;
} else if (parent == fake_ddk::kFakeDevice) {
*out = kFakeChild;
children_++;
total_children_++;
} else {
*out = kUnknownDevice;
bad_parent_ = false;
}
return ZX_OK;
}
zx_status_t DeviceRemove(zx_device_t* device) override {
if (device == fake_ddk::kFakeDevice) {
remove_called_ = true;
} else if (device == kFakeChild) {
// Check that all children are removed before the parent is removed.
if (!remove_called_) {
children_--;
}
} else {
bad_device_ = true;
}
return ZX_OK;
}
bool IsRemoved() { return remove_called_; }
bool Ok() {
return ((children_ == 0) && add_called_ && remove_called_ && !bad_parent_ && !bad_device_);
}
private:
zx_device_t* kFakeChild = reinterpret_cast<zx_device_t*>(0x1234);
zx_device_t* kUnknownDevice = reinterpret_cast<zx_device_t*>(0x5678);
int total_children_ = 0;
int children_ = 0;
bool bad_parent_ = false;
bool bad_device_ = false;
bool add_called_ = false;
bool remove_called_ = false;
};
TEST(SimpleAudioTest, DdkLifeCycleTest) {
Bind tester;
auto server = audio::SimpleAudioStream::Create<audio::MockSimpleAudio>(fake_ddk::kFakeParent);
ASSERT_NOT_NULL(server);
ddk::SuspendTxn txn(server->zxdev(), 0, false, DEVICE_SUSPEND_REASON_SELECTIVE_SUSPEND);
server->DdkSuspendNew(std::move(txn));
EXPECT_FALSE(tester.IsRemoved());
server->DdkUnbindDeprecated();
EXPECT_TRUE(tester.Ok());
}
TEST(SimpleAudioTest, SetAndGetGain) {
fake_ddk::Bind tester;
auto server = audio::SimpleAudioStream::Create<audio::MockSimpleAudio>(fake_ddk::kFakeParent);
ASSERT_NOT_NULL(server);
Device::SyncClient client(std::move(tester.FidlClient()));
Device::ResultOf::GetChannel channel_wrap = client.GetChannel();
ASSERT_EQ(channel_wrap.status(), ZX_OK);
// After we get the channel we use audio::utils serialization until we convert to FIDL.
auto channel_client = audio::utils::AudioOutput::Create(1);
channel_client->SetStreamChannel(std::move(channel_wrap->channel));
auto gain = 1.2345f;
channel_client->SetGain(gain);
audio_stream_cmd_get_gain_resp gain_state;
channel_client->GetGain(&gain_state);
ASSERT_EQ(gain_state.cur_gain, gain);
}
TEST(SimpleAudioTest, EnumerateMultipleRates) {
struct EnumerateRates : public MockSimpleAudio {
EnumerateRates(zx_device_t* parent) : MockSimpleAudio(parent) {}
zx_status_t Init() __TA_REQUIRES(domain_token()) override {
auto status = MockSimpleAudio::Init();
audio_stream_format_range_t range;
range.min_channels = kTestNumberOfChannels;
range.max_channels = kTestNumberOfChannels;
range.sample_formats = AUDIO_SAMPLE_FORMAT_16BIT;
range.min_frames_per_second = 48000;
range.max_frames_per_second = 768000;
range.flags = ASF_RANGE_FLAG_FPS_48000_FAMILY;
supported_formats_ = fbl::Vector<audio_stream_format_range_t>{range};
return status;
}
};
fake_ddk::Bind tester;
auto server = audio::SimpleAudioStream::Create<EnumerateRates>(fake_ddk::kFakeParent);
ASSERT_NOT_NULL(server);
Device::SyncClient client(std::move(tester.FidlClient()));
Device::ResultOf::GetChannel channel_wrap = client.GetChannel();
ASSERT_EQ(channel_wrap.status(), ZX_OK);
// After we get the channel we use audio::utils serialization until we convert to FIDL.
auto channel_client = audio::utils::AudioOutput::Create(1);
channel_client->SetStreamChannel(std::move(channel_wrap->channel));
fbl::Vector<audio_stream_format_range_t> ranges;
channel_client->GetSupportedFormats(&ranges);
ASSERT_EQ(1, ranges.size());
ASSERT_EQ(MockSimpleAudio::kTestNumberOfChannels, ranges[0].min_channels);
ASSERT_EQ(MockSimpleAudio::kTestNumberOfChannels, ranges[0].max_channels);
ASSERT_EQ(AUDIO_SAMPLE_FORMAT_16BIT, ranges[0].sample_formats);
ASSERT_EQ(48000, ranges[0].min_frames_per_second);
ASSERT_EQ(768000, ranges[0].max_frames_per_second);
ASSERT_EQ(ASF_RANGE_FLAG_FPS_48000_FAMILY, ranges[0].flags);
audio::utils::FrameRateEnumerator enumerator(ranges[0]);
std::set<uint32_t> rates;
for (uint32_t rate : enumerator) {
rates.insert(rate);
}
ASSERT_EQ(5, rates.size());
ASSERT_EQ(rates, std::set<uint32_t>({48'000, 96'000, 192'000, 384'000, 768'000}));
}
TEST(SimpleAudioTest, GetIds) {
fake_ddk::Bind tester;
auto server = audio::SimpleAudioStream::Create<audio::MockSimpleAudio>(fake_ddk::kFakeParent);
ASSERT_NOT_NULL(server);
Device::SyncClient client(std::move(tester.FidlClient()));
Device::ResultOf::GetChannel channel_wrap = client.GetChannel();
ASSERT_EQ(channel_wrap.status(), ZX_OK);
// After we get the channel we use audio::utils serialization until we convert to FIDL.
auto channel_client = audio::utils::AudioOutput::Create(1);
channel_client->SetStreamChannel(std::move(channel_wrap->channel));
audio_stream_cmd_get_unique_id_resp_t id = {};
ASSERT_OK(channel_client->GetUniqueId(&id));
audio_stream_unique_id_t mic = AUDIO_STREAM_UNIQUE_ID_BUILTIN_MICROPHONE;
ASSERT_BYTES_EQ(id.unique_id.data, mic.data, strlen(reinterpret_cast<char*>(mic.data)) + 1);
audio_stream_cmd_get_string_resp_t str = {};
ASSERT_OK(channel_client->GetString(AUDIO_STREAM_STR_ID_MANUFACTURER, &str));
ASSERT_BYTES_EQ(str.str, "Bike Sheds, Inc.", strlen("Bike Sheds, Inc.") + 1);
}
TEST(SimpleAudioTest, GetClockDomain) {
fake_ddk::Bind tester;
auto server = audio::SimpleAudioStream::Create<audio::MockSimpleAudio>(fake_ddk::kFakeParent);
ASSERT_NOT_NULL(server);
Device::SyncClient client(std::move(tester.FidlClient()));
Device::ResultOf::GetChannel channel_wrap = client.GetChannel();
ASSERT_EQ(channel_wrap.status(), ZX_OK);
// After we get the channel we use audio::utils serialization until we convert to FIDL.
auto channel_client = audio::utils::AudioOutput::Create(1);
channel_client->SetStreamChannel(std::move(channel_wrap->channel));
audio_stream_cmd_get_clock_domain_resp_t resp = {};
ASSERT_OK(channel_client->GetClockDomain(&resp));
ASSERT_EQ(resp.clock_domain, 0);
}
TEST(SimpleAudioTest, MultipleChannelsPlugDetectState) {
fake_ddk::Bind tester;
auto server = audio::SimpleAudioStream::Create<audio::MockSimpleAudio>(fake_ddk::kFakeParent);
ASSERT_NOT_NULL(server);
Device::SyncClient client(std::move(tester.FidlClient()));
// We get 2 channels from the one FIDL channel acquired via FidlClient() using GetChannel.
Device::ResultOf::GetChannel channel_wrap1 = client.GetChannel();
ASSERT_EQ(channel_wrap1.status(), ZX_OK);
Device::ResultOf::GetChannel channel_wrap2 = client.GetChannel();
ASSERT_EQ(channel_wrap2.status(), ZX_OK);
// After we get the channel we use audio::utils serialization until we convert to FIDL.
auto channel_client1 = audio::utils::AudioOutput::Create(1);
auto channel_client2 = audio::utils::AudioOutput::Create(2);
channel_client1->SetStreamChannel(std::move(channel_wrap1->channel));
channel_client2->SetStreamChannel(std::move(channel_wrap2->channel));
audio_stream_cmd_plug_detect_resp resp = {};
channel_client1->GetPlugState(&resp, false);
ASSERT_EQ(resp.flags, AUDIO_PDNF_CAN_NOTIFY);
channel_client2->GetPlugState(&resp, true);
ASSERT_EQ(resp.flags, AUDIO_PDNF_CAN_NOTIFY);
}
TEST(SimpleAudioTest, MultipleChannelsPlugDetectNotify) {
fake_ddk::Bind tester;
auto server = audio::SimpleAudioStream::Create<audio::MockSimpleAudio>(fake_ddk::kFakeParent);
ASSERT_NOT_NULL(server);
Device::SyncClient client(std::move(tester.FidlClient()));
// We get multiple channels from the one FIDL channel acquired via FidlClient() using
// GetChannel.
Device::ResultOf::GetChannel channel_wrap1 = client.GetChannel();
Device::ResultOf::GetChannel channel_wrap2 = client.GetChannel();
Device::ResultOf::GetChannel channel_wrap3 = client.GetChannel();
ASSERT_EQ(channel_wrap1.status(), ZX_OK);
ASSERT_EQ(channel_wrap2.status(), ZX_OK);
ASSERT_EQ(channel_wrap3.status(), ZX_OK);
// After we get the channel we use audio::utils serialization until we convert to FIDL.
auto channel_client1 = audio::utils::AudioOutput::Create(1);
auto channel_client2 = audio::utils::AudioOutput::Create(1);
auto channel_client3 = audio::utils::AudioOutput::Create(1);
channel_client1->SetStreamChannel(std::move(channel_wrap1->channel));
channel_client2->SetStreamChannel(std::move(channel_wrap2->channel));
channel_client3->SetStreamChannel(std::move(channel_wrap3->channel));
// Create threads to wait for notifications on them.
auto f = [](void* arg) -> int {
audio::utils::AudioOutput* channel_client = static_cast<audio::utils::AudioOutput*>(arg);
bool client_notified = false;
auto cb = [&client_notified](bool plug_state, zx_time_t plug_time) -> bool {
client_notified = plug_state;
return false; // Stop monitoring.
};
utils::AudioDeviceStream::PlugMonitorCallback monitor = cb;
channel_client->PlugMonitor(30, &monitor);
return client_notified ? 0 : 1;
};
audio_stream_cmd_plug_detect_resp_t resp = {};
// GetPlugState() enables notifications now, so the channel message from SetPlugState is ready
// when PlugMonitor is run. GetPlugState is a blocking call.
channel_client1->GetPlugState(&resp, true);
channel_client2->GetPlugState(&resp, true);
channel_client3->GetPlugState(&resp, true);
server->PostSetPlugState(true);
thrd_t thread1, thread2, thread3;
ASSERT_OK(thrd_create_with_name(&thread1, f, channel_client1.get(), "test-thread-1"));
ASSERT_OK(thrd_create_with_name(&thread2, f, channel_client2.get(), "test-thread-2"));
ASSERT_OK(thrd_create_with_name(&thread3, f, channel_client3.get(), "test-thread-3"));
int result = -1;
thrd_join(thread1, &result);
ASSERT_EQ(result, 0);
result = -1;
thrd_join(thread2, &result);
ASSERT_EQ(result, 0);
result = -1;
thrd_join(thread3, &result);
ASSERT_EQ(result, 0);
}
TEST(SimpleAudioTest, RingBufferTests) {
fake_ddk::Bind tester;
auto server = audio::SimpleAudioStream::Create<audio::MockSimpleAudio>(fake_ddk::kFakeParent);
ASSERT_NOT_NULL(server);
Device::SyncClient client(std::move(tester.FidlClient()));
Device::ResultOf::GetChannel channel_wrap = client.GetChannel();
ASSERT_EQ(channel_wrap.status(), ZX_OK);
// After we get the channel we use audio::utils serialization until we convert to FIDL.
auto channel_client = audio::utils::AudioOutput::Create(1);
channel_client->SetStreamChannel(std::move(channel_wrap->channel));
audio_sample_format_t format = AUDIO_SAMPLE_FORMAT_16BIT;
ASSERT_OK(channel_client->SetFormat(MockSimpleAudio::kTestFrameRate,
MockSimpleAudio::kTestNumberOfChannels, format));
constexpr uint32_t kNumberOfPositionNotifications = 5;
// Buffer is set to hold 1 second, with 10 x kNumberOfPositionNotifications notifications
// per ring buffer (i.e. per second) we limit the time waiting in the loop below to ~100ms.
ASSERT_OK(channel_client->GetBuffer(MockSimpleAudio::kTestFrameRate,
kNumberOfPositionNotifications * 10));
ASSERT_EQ(channel_client->fifo_depth(), MockSimpleAudio::kTestFifoDepth);
ASSERT_OK(channel_client->StartRingBuffer());
audio_rb_position_notify_t pos_notif = {};
uint32_t bytes_read = 0;
zx_signals_t signals = {};
for (size_t i = 0; i < kNumberOfPositionNotifications; ++i) {
ASSERT_OK(channel_client->BorrowRingBufferChannel()->wait_one(ZX_CHANNEL_READABLE,
zx::time::infinite(), &signals));
ASSERT_OK(channel_client->BorrowRingBufferChannel()->read(
0, &pos_notif, nullptr, sizeof(pos_notif), 0, &bytes_read, nullptr));
ASSERT_EQ(pos_notif.ring_buffer_pos, MockSimpleAudio::kTestPositionNotify);
}
ASSERT_OK(channel_client->StopRingBuffer());
}
} // namespace audio