blob: fdae2f72cd1494b2639546844b5286c924d5ff91 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/media/audio/drivers/tests/test_base.h"
#include <fcntl.h>
#include <fuchsia/component/cpp/fidl.h>
#include <fuchsia/hardware/audio/cpp/fidl.h>
#include <fuchsia/logger/cpp/fidl.h>
#include <fuchsia/media/cpp/fidl.h>
#include <lib/fdio/directory.h>
#include <lib/fidl/cpp/enum.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/time.h>
#include <sys/types.h>
#include <algorithm>
#include <cstddef>
#include <cstring>
#include <string>
#include <fbl/unique_fd.h>
#include <gtest/gtest.h>
#include "src/media/audio/drivers/tests/audio_device_enumerator_stub.h"
inline constexpr bool kAllowCompositeDriverUnsupportedRingBufferFormats = true;
namespace media::audio::drivers::test {
using component_testing::ChildRef;
using component_testing::ParentRef;
using component_testing::Protocol;
using component_testing::RealmBuilder;
using component_testing::RealmRoot;
using component_testing::Route;
// Device discovery is done once at binary open; a fresh FIDL channel is used for each test.
// TODO(b/301003578): When virtual audio is DFv2, remove DFv1-specific trampoline support.
void TestBase::SetUp() {
media::audio::test::TestFixture::SetUp();
auto& entry = device_entry();
if (entry.isA2DP()) {
ConnectToBluetoothDevice();
} else {
switch (entry.driver_type) {
case DriverType::Codec:
CreateCodecFromChannel(
ConnectWithTrampoline<fuchsia::hardware::audio::Codec,
fuchsia::hardware::audio::CodecConnectorPtr>(device_entry()));
break;
case DriverType::Composite:
// Use DFv1-specific trampoline Connector API for the virtual composite driver (DFv1),
// for all other non-virtual composite drivers (DFv2) do not use the trampoline.
if (entry.device_type == DeviceType::Virtual) {
CreateCompositeFromChannel(
ConnectWithTrampoline<fuchsia::hardware::audio::Composite,
fuchsia::hardware::audio::CompositeConnectorPtr>(
device_entry()));
} else {
CreateCompositeFromChannel(
Connect<fuchsia::hardware::audio::CompositePtr>(device_entry()));
}
break;
case DriverType::Dai:
CreateDaiFromChannel(
ConnectWithTrampoline<fuchsia::hardware::audio::Dai,
fuchsia::hardware::audio::DaiConnectorPtr>(device_entry()));
break;
case DriverType::StreamConfigInput:
[[fallthrough]];
case DriverType::StreamConfigOutput:
CreateStreamConfigFromChannel(
ConnectWithTrampoline<fuchsia::hardware::audio::StreamConfig,
fuchsia::hardware::audio::StreamConfigConnectorPtr>(
device_entry()));
break;
}
}
}
void TestBase::TearDown() {
if (device_entry().isCodec()) {
codec_.Unbind();
} else if (device_entry().isComposite()) {
composite_.Unbind();
} else if (device_entry().isDai()) {
dai_.Unbind();
} else if (device_entry().isStreamConfig()) {
stream_config_.Unbind();
}
if (realm_.has_value()) {
// We're about to shut down the realm; unbind to unhook the error handler.
audio_binder_.Unbind();
bool complete = false;
realm_.value().Teardown(
[&complete](fit::result<fuchsia::component::Error> result) { complete = true; });
RunLoopUntil([&complete]() { return complete; });
}
// Audio drivers can have multiple StreamConfig channels open, but only one can be 'privileged':
// the one that can in turn create a RingBuffer channel. Each test case starts from scratch,
// opening and closing channels. If we create a StreamConfig channel before the previous one is
// cleared, a new StreamConfig channel will not be privileged and Admin tests will fail.
//
// When disconnecting a StreamConfig, there's no signal to wait on before proceeding (potentially
// immediately executing other tests); insert a 10-ms wait (needing >3.5ms was never observed).
zx::nanosleep(zx::deadline_after(zx::msec(10)));
TestFixture::TearDown();
}
void TestBase::ConnectToBluetoothDevice() {
std::unique_ptr<AudioDeviceEnumeratorStub> audio_device_enumerator_impl =
std::make_unique<AudioDeviceEnumeratorStub>();
auto audio_device_enumerator_impl_ptr = audio_device_enumerator_impl.get();
auto builder = RealmBuilder::Create();
// The component binding must live as long as the Realm, so std::move the
// unique_ptr into the component function.
builder.AddLocalChild(
"audio-device-enumerator",
[audio_device_enumerator_impl = std::move(audio_device_enumerator_impl)]() mutable {
// Note: This lambda does not create a new instance,
// so the component can only be started once.
return std::move(audio_device_enumerator_impl);
});
builder.AddChild("audio-device-output-harness", "#meta/audio-device-output-harness.cm");
builder.AddRoute(Route{.capabilities = {Protocol{fuchsia::media::AudioDeviceEnumerator::Name_}},
.source = ChildRef{"audio-device-enumerator"},
.targets = {ChildRef{"audio-device-output-harness"}}});
builder.AddRoute(Route{.capabilities = {Protocol{fuchsia::logger::LogSink::Name_}},
.source = ParentRef{},
.targets = {ChildRef{"audio-device-output-harness"}}});
builder.AddRoute(Route{
.capabilities = {Protocol{.name = fuchsia::component::Binder::Name_, .as = "audio-binder"}},
.source = ChildRef{"audio-device-output-harness"},
.targets = {ParentRef{}}});
realm_ = builder.Build();
ASSERT_EQ(ZX_OK,
realm_->component().Connect("audio-binder", audio_binder_.NewRequest().TakeChannel()));
audio_binder_.set_error_handler([](zx_status_t status) {
FAIL() << "audio-device-output-harness exited: " << zx_status_get_string(status);
});
// Wait for the Bluetooth harness to AddDeviceByChannel, then pass it on
RunLoopUntil([impl = audio_device_enumerator_impl_ptr]() {
return impl->channel_available() || HasFailure();
});
CreateStreamConfigFromChannel(audio_device_enumerator_impl_ptr->TakeChannel());
}
// Given this device_entry, use its channel to open the device.
template <typename DeviceType, typename ConnectorType>
fidl::InterfaceHandle<DeviceType> TestBase::ConnectWithTrampoline(const DeviceEntry& device_entry) {
auto connector = Connect<ConnectorType>(device_entry);
fidl::InterfaceHandle<DeviceType> client;
fidl::InterfaceRequest<DeviceType> server = client.NewRequest();
connector->Connect(std::move(server));
auto channel = client.TakeChannel();
FX_LOGS(TRACE) << "Successfully opened devnode '" << device_entry.filename << "' for audio "
<< driver_type();
return fidl::InterfaceHandle<DeviceType>(std::move(channel));
}
template <typename DeviceType>
DeviceType TestBase::Connect(const DeviceEntry& device_entry) {
DeviceType device;
ZX_ASSERT(device_entry.dir.index() == 1u);
ZX_ASSERT(fdio_service_connect_at(std::get<1>(device_entry.dir).channel()->get(),
device_entry.filename.c_str(),
device.NewRequest().TakeChannel().release()) == ZX_OK);
device.set_error_handler([this](zx_status_t status) {
FAIL() << status << "Err " << status << ", failed to open channel for audio " << driver_type();
});
return std::move(device);
}
void TestBase::CreateCodecFromChannel(
fidl::InterfaceHandle<fuchsia::hardware::audio::Codec> channel) {
codec_ = channel.Bind();
// If no device was enumerated, don't waste further time.
if (!codec_.is_bound()) {
FAIL() << "Failed to get codec channel for this device";
__UNREACHABLE;
}
AddErrorHandler(codec_, "Codec");
}
void TestBase::CreateCompositeFromChannel(
fidl::InterfaceHandle<fuchsia::hardware::audio::Composite> channel) {
composite_ = channel.Bind();
// If no device was enumerated, don't waste further time.
if (!composite_.is_bound()) {
FAIL() << "Failed to get composite channel for this device";
__UNREACHABLE;
}
AddErrorHandler(composite_, "Composite");
}
void TestBase::CreateDaiFromChannel(fidl::InterfaceHandle<fuchsia::hardware::audio::Dai> channel) {
dai_ = channel.Bind();
// If no device was enumerated, don't waste further time.
if (!dai_.is_bound()) {
FAIL() << "Failed to get DAI channel for this device";
__UNREACHABLE;
}
AddErrorHandler(dai_, "DAI");
}
void TestBase::CreateStreamConfigFromChannel(
fidl::InterfaceHandle<fuchsia::hardware::audio::StreamConfig> channel) {
stream_config_ = channel.Bind();
// If no device was enumerated, don't waste further time.
if (!stream_config_.is_bound()) {
FAIL() << "Failed to get stream channel for this device";
__UNREACHABLE;
}
AddErrorHandler(stream_config_, "StreamConfig");
}
// Request that the driver return the format ranges that it supports.
void TestBase::RetrieveDaiFormats() {
if (device_entry().isCodec()) {
codec()->GetDaiFormats(AddCallback(
"Codec::GetDaiFormats",
[this](fuchsia::hardware::audio::Codec_GetDaiFormats_Result result) {
ASSERT_FALSE(result.is_err());
auto& supported_dai_formats = result.response().formats;
EXPECT_FALSE(supported_dai_formats.empty());
for (size_t i = 0; i < supported_dai_formats.size(); ++i) {
SCOPED_TRACE(testing::Message() << "Codec supported_dai_formats[" << i << "]");
dai_formats_.push_back(std::move(supported_dai_formats[i]));
}
}));
} else if (device_entry().isComposite()) {
RequestTopologies();
// If there is a dai id, request the DAI formats for this interconnect.
if (dai_id_.has_value()) {
composite()->GetDaiFormats(
dai_id_.value(),
AddCallback("Composite::GetDaiFormats",
[this](fuchsia::hardware::audio::Composite_GetDaiFormats_Result result) {
ASSERT_FALSE(result.is_err());
auto& supported_formats = result.response().dai_formats;
EXPECT_FALSE(supported_formats.empty());
for (size_t i = 0; i < supported_formats.size(); ++i) {
SCOPED_TRACE(testing::Message()
<< "Composite supported_formats[" << i << "]");
dai_formats_.push_back(std::move(supported_formats[i]));
}
}));
} else {
// "No DAI" is also valid (Composite can replace StreamConfig); do nothing in that case.
return;
}
} else if (device_entry().isDai()) {
dai()->GetDaiFormats(AddCallback(
"Dai::GetDaiFormats", [this](fuchsia::hardware::audio::Dai_GetDaiFormats_Result result) {
ASSERT_FALSE(result.is_err());
auto& supported_dai_formats = result.response().dai_formats;
EXPECT_FALSE(supported_dai_formats.empty());
for (size_t i = 0; i < supported_dai_formats.size(); ++i) {
SCOPED_TRACE(testing::Message() << "DAI supported_dai_formats[" << i << "]");
dai_formats_.push_back(std::move(supported_dai_formats[i]));
}
}));
} else {
return; // StreamConfig, nothing to do
}
ExpectCallbacks();
ValidateDaiFormatSets(dai_formats());
if (!HasFailure()) {
SetMinMaxDaiFormats();
}
}
// Fail if the returned formats are not complete, unique and within ranges.
// Consider defining FIDL constants for min/max channels, frame_rate, bits_per_slot/sample, etc.
void TestBase::ValidateDaiFormatSets(
const std::vector<fuchsia::hardware::audio::DaiSupportedFormats>& dai_format_sets) {
ASSERT_FALSE(dai_format_sets.empty()) << "No dai formats returned";
for (size_t i = 0; i < dai_format_sets.size(); ++i) {
SCOPED_TRACE(testing::Message() << "dai_format[" << i << "]");
auto& format_set = dai_format_sets[i];
ASSERT_FALSE(format_set.number_of_channels.empty());
// Ensure number_of_channels are unique.
for (size_t j = 0; j < format_set.number_of_channels.size(); ++j) {
EXPECT_GT(format_set.number_of_channels[j], 0u);
EXPECT_LE(format_set.number_of_channels[j], 256u);
for (size_t k = j + 1; k < format_set.number_of_channels.size(); ++k) {
EXPECT_NE(format_set.number_of_channels[j], format_set.number_of_channels[k])
<< "number_of_channels[" << j << "] must not equal number_of_channels[" << k << "]";
}
}
ASSERT_FALSE(format_set.sample_formats.empty());
// Ensure sample_formats are unique.
for (size_t j = 0; j < format_set.sample_formats.size(); ++j) {
for (size_t k = j + 1; k < format_set.sample_formats.size(); ++k) {
EXPECT_NE(static_cast<uint16_t>(fidl::ToUnderlying(format_set.sample_formats[j])),
static_cast<uint16_t>(fidl::ToUnderlying(format_set.sample_formats[k])))
<< "sample_formats[" << j << "] must not equal sample_formats[" << k << "]";
}
}
ASSERT_FALSE(format_set.frame_formats.empty());
// Ensure frame_formats are unique.
for (size_t j = 0; j < format_set.frame_formats.size(); ++j) {
auto& format_1 = format_set.frame_formats[j];
for (size_t k = j + 1; k < format_set.frame_formats.size(); ++k) {
auto& format_2 = format_set.frame_formats[k];
if (format_1.Which() != format_2.Which()) {
continue;
}
if (format_1.is_frame_format_standard()) {
if (format_1.frame_format_standard() != format_2.frame_format_standard()) {
continue;
}
} else {
if (format_1.frame_format_custom().left_justified !=
format_2.frame_format_custom().left_justified ||
format_1.frame_format_custom().sclk_on_raising !=
format_2.frame_format_custom().sclk_on_raising ||
format_1.frame_format_custom().frame_sync_sclks_offset !=
format_2.frame_format_custom().frame_sync_sclks_offset ||
format_1.frame_format_custom().frame_sync_size !=
format_2.frame_format_custom().frame_sync_size) {
continue;
}
}
FAIL() << "frame_formats[" << j << "] must not equal frame_formats[" << k << "]";
__UNREACHABLE;
}
}
ASSERT_FALSE(format_set.frame_rates.empty());
// Ensure frame_rates are unique and ascending.
for (size_t j = 0; j < format_set.frame_rates.size(); ++j) {
EXPECT_GE(format_set.frame_rates[j], 1'000u);
EXPECT_LE(format_set.frame_rates[j], 768'000u);
if (j > 0) {
EXPECT_LT(format_set.frame_rates[j - 1], format_set.frame_rates[j])
<< "frame_rates[" << j - 1 << "] must be less than frame_rates[" << j << "]";
}
}
ASSERT_FALSE(format_set.bits_per_slot.empty());
// Ensure bits_per_slot are unique and ascending.
uint8_t max_bits_per_slot = 0;
for (size_t j = 0; j < format_set.bits_per_slot.size(); ++j) {
EXPECT_GT(static_cast<uint16_t>(format_set.bits_per_slot[j]), 0u);
EXPECT_LE(static_cast<uint16_t>(format_set.bits_per_slot[j]), 64u);
max_bits_per_slot = std::max(max_bits_per_slot, format_set.bits_per_slot[j]);
if (j > 0) {
EXPECT_LT(static_cast<uint16_t>(format_set.bits_per_slot[j - 1]),
static_cast<uint16_t>(format_set.bits_per_slot[j]))
<< "bits_per_slot[" << j - 1 << "] must be less than bits_per_slot[" << j << "]";
}
}
ASSERT_FALSE(format_set.bits_per_sample.empty());
// Ensure bits_per_sample are unique and ascending.
for (size_t j = 0; j < format_set.bits_per_sample.size(); ++j) {
EXPECT_GT(static_cast<uint16_t>(format_set.bits_per_sample[j]), 0u);
EXPECT_LE(static_cast<uint16_t>(format_set.bits_per_sample[j]),
static_cast<uint16_t>(max_bits_per_slot));
if (j > 0) {
EXPECT_LT(static_cast<uint16_t>(format_set.bits_per_sample[j - 1]),
static_cast<uint16_t>(format_set.bits_per_sample[j]))
<< "bits_per_sample[" << j - 1 << "] must be less than bits_per_sample[" << j << "]";
}
}
}
}
// For debugging purposes
void TestBase::LogDaiFormatSets(
const std::vector<fuchsia::hardware::audio::DaiSupportedFormats>& dai_format_sets,
std::string tag) {
FX_LOGS(INFO) << tag << ": dai_format_sets[" << dai_format_sets.size() << "]";
for (auto i = 0u; i < dai_format_sets.size(); ++i) {
auto& format_set = dai_format_sets[i];
FX_LOGS(INFO) << tag << ": [" << i << "] number_of_channels["
<< format_set.number_of_channels.size() << "]";
for (auto num : format_set.number_of_channels) {
FX_LOGS(INFO) << tag << ": " << num;
}
FX_LOGS(INFO) << tag << ": sample_formats [" << format_set.sample_formats.size()
<< "]";
for (auto fmt : format_set.sample_formats) {
FX_LOGS(INFO) << tag << ": " << fmt;
}
FX_LOGS(INFO) << tag << ": frame_formats [" << format_set.frame_formats.size()
<< "]";
for (auto& fmt : format_set.frame_formats) {
fuchsia::hardware::audio::DaiFrameFormat dai_frame_format = {};
EXPECT_EQ(fuchsia::hardware::audio::Clone(fmt, &dai_frame_format), ZX_OK);
FX_LOGS(INFO) << tag << ": " << std::move(dai_frame_format);
}
FX_LOGS(INFO) << tag << ": frame_rates [" << format_set.frame_rates.size() << "]";
for (auto rate : format_set.frame_rates) {
FX_LOGS(INFO) << tag << ": " << rate;
}
FX_LOGS(INFO) << tag << ": bits_per_slot [" << format_set.bits_per_slot.size()
<< "]";
for (auto bits : format_set.bits_per_slot) {
FX_LOGS(INFO) << tag << ": " << static_cast<uint16_t>(bits);
}
FX_LOGS(INFO) << tag << ": bits_per_sample [" << format_set.bits_per_sample.size()
<< "]";
for (auto bits : format_set.bits_per_sample) {
FX_LOGS(INFO) << tag << ": " << static_cast<uint16_t>(bits);
}
}
}
void TestBase::SetMinMaxDaiFormats() {
for (size_t i = 0; i < dai_formats_.size(); ++i) {
auto& format_set = dai_formats_[i];
fuchsia::hardware::audio::DaiSampleFormat sample_format = format_set.sample_formats[0];
fuchsia::hardware::audio::DaiFrameFormat frame_format;
if (format_set.frame_formats[0].is_frame_format_standard()) {
frame_format.set_frame_format_standard(format_set.frame_formats[0].frame_format_standard());
} else {
frame_format.set_frame_format_custom(format_set.frame_formats[0].frame_format_custom());
}
uint32_t min_number_of_channels = ~0, max_number_of_channels = 0;
for (auto& number_of_channels : format_set.number_of_channels) {
if (number_of_channels < min_number_of_channels) {
min_number_of_channels = number_of_channels;
}
if (number_of_channels > max_number_of_channels) {
max_number_of_channels = number_of_channels;
}
}
uint8_t min_bits_per_slot = ~0, max_bits_per_slot = 0;
for (auto& bits_per_slot : format_set.bits_per_slot) {
if (bits_per_slot < min_bits_per_slot) {
min_bits_per_slot = bits_per_slot;
}
if (bits_per_slot > max_bits_per_slot) {
max_bits_per_slot = bits_per_slot;
}
}
uint8_t min_bits_per_sample = ~0, max_bits_per_sample = 0;
for (auto& bits_per_sample : format_set.bits_per_sample) {
if (bits_per_sample < min_bits_per_sample) {
min_bits_per_sample = bits_per_sample;
}
if (bits_per_sample > max_bits_per_sample) {
max_bits_per_sample = bits_per_sample;
}
}
uint32_t min_frame_rate = ~0, max_frame_rate = 0;
for (auto& frame_rate : format_set.frame_rates) {
if (frame_rate < min_frame_rate) {
min_frame_rate = frame_rate;
}
if (frame_rate > max_frame_rate) {
max_frame_rate = frame_rate;
}
}
// Save, if less than min.
auto bit_rate = min_number_of_channels * min_bits_per_sample * min_frame_rate;
if (i == 0 || bit_rate < min_dai_format_->number_of_channels *
min_dai_format_->bits_per_sample * min_dai_format_->frame_rate) {
min_dai_format_ = fuchsia::hardware::audio::DaiFormat{
.number_of_channels = min_number_of_channels,
.channels_to_use_bitmask = (1u << min_number_of_channels) - 1u,
.sample_format = sample_format,
.frame_format = std::move(frame_format),
.frame_rate = min_frame_rate,
.bits_per_slot = min_bits_per_slot,
.bits_per_sample = min_bits_per_sample,
};
}
// Save, if more than max.
bit_rate = max_number_of_channels * max_bits_per_sample * max_frame_rate;
if (i == 0 || bit_rate > max_dai_format_->number_of_channels *
max_dai_format_->bits_per_sample * max_dai_format_->frame_rate) {
max_dai_format_ = fuchsia::hardware::audio::DaiFormat{
.number_of_channels = max_number_of_channels,
.channels_to_use_bitmask = (1u << max_number_of_channels) - 1u,
.sample_format = sample_format,
.frame_format = std::move(frame_format),
.frame_rate = max_frame_rate,
.bits_per_slot = max_bits_per_slot,
.bits_per_sample = max_bits_per_sample,
};
}
}
}
void TestBase::GetMinDaiFormat(fuchsia::hardware::audio::DaiFormat& min_dai_format_out) {
if (!min_dai_format_) {
RetrieveDaiFormats();
}
if (dai_formats_.empty()) {
GTEST_SKIP() << "*** this audio device returns no DaiFormats. Skipping this test. ***";
__UNREACHABLE;
}
EXPECT_EQ(fuchsia::hardware::audio::Clone(*min_dai_format_, &min_dai_format_out), ZX_OK);
}
void TestBase::GetMaxDaiFormat(fuchsia::hardware::audio::DaiFormat& max_dai_format_out) {
if (!max_dai_format_) {
RetrieveDaiFormats();
}
if (dai_formats_.empty()) {
GTEST_SKIP() << "*** this audio device returns no DaiFormats. Skipping this test. ***";
__UNREACHABLE;
}
EXPECT_EQ(fuchsia::hardware::audio::Clone(*max_dai_format_, &max_dai_format_out), ZX_OK);
}
void TestBase::ValidateDaiFormat(const fuchsia::hardware::audio::DaiFormat& dai_format) {
EXPECT_GT(dai_format.bits_per_sample, 0u);
EXPECT_LE(dai_format.bits_per_sample, sizeof(double) * 8u);
EXPECT_GE(dai_format.bits_per_slot, dai_format.bits_per_sample);
EXPECT_LE(dai_format.bits_per_slot, sizeof(double) * 8u);
EXPECT_GE(dai_format.frame_rate, 1'000u);
EXPECT_LE(dai_format.frame_rate, 768'000u);
EXPECT_GT(dai_format.number_of_channels, 0u);
EXPECT_LE(dai_format.number_of_channels, 64u);
EXPECT_GT(dai_format.channels_to_use_bitmask, 0u);
}
// For debugging purposes
void TestBase::LogDaiFormat(const fuchsia::hardware::audio::DaiFormat& format,
const std::string& tag) {
fuchsia::hardware::audio::DaiFrameFormat dai_frame_format = {};
EXPECT_EQ(fuchsia::hardware::audio::Clone(format.frame_format, &dai_frame_format), ZX_OK);
FX_LOGS(INFO) << tag << ": " << format.frame_rate << ", " << format.sample_format << ", "
<< std::move(dai_frame_format) << ", " << std::dec
<< static_cast<uint16_t>(format.bits_per_sample) << "-in-"
<< static_cast<uint16_t>(format.bits_per_slot) << ", " << format.number_of_channels
<< "-chan (0x" << std::hex << format.channels_to_use_bitmask << ")";
}
const std::vector<fuchsia::hardware::audio::DaiSupportedFormats>& TestBase::dai_formats() const {
return dai_formats_;
}
// Request that the driver return the format ranges that it supports.
void TestBase::RetrieveRingBufferFormats() {
if (device_entry().isCodec()) {
return; // Codec, nothing to do
}
if (device_entry().isComposite()) {
RequestTopologies();
// If ring_buffer_id_ is set, then a ring-buffer element exists for this composite device.
// Retrieve the supported ring buffer formats for that node.
if (ring_buffer_id_.has_value()) {
composite()->GetRingBufferFormats(
ring_buffer_id_.value(),
AddCallback("GetRingBufferFormats", [this](fuchsia::hardware::audio::
Composite_GetRingBufferFormats_Result
result) {
if (!device_entry().isVirtual() && !device_entry().isA2DP() &&
kAllowCompositeDriverUnsupportedRingBufferFormats) {
if (result.is_err() &&
(result.err() == fuchsia::hardware::audio::DriverError::NOT_SUPPORTED)) {
GTEST_SKIP()
<< "*** this audio device is known to not (yet) support GetRingBufferFormats(). Skipping this test. ***";
__UNREACHABLE;
}
}
ASSERT_FALSE(result.is_err()) << static_cast<int32_t>(result.err());
auto& supported_formats = result.response().ring_buffer_formats;
EXPECT_FALSE(supported_formats.empty());
for (size_t i = 0; i < supported_formats.size(); ++i) {
SCOPED_TRACE(testing::Message() << "Composite supported_formats[" << i << "]");
ASSERT_TRUE(supported_formats[i].has_pcm_supported_formats());
auto& format_set = *supported_formats[i].mutable_pcm_supported_formats();
ring_buffer_pcm_formats_.push_back(std::move(format_set));
}
}));
} else {
// "No ring buffer" is valid (Composite can replace Codec); do nothing in that case.
return;
}
} else if (device_entry().isDai()) {
dai()->GetRingBufferFormats(
AddCallback("GetRingBufferFormats",
[this](fuchsia::hardware::audio::Dai_GetRingBufferFormats_Result result) {
ASSERT_FALSE(result.is_err());
auto& supported_rb_formats = result.response().ring_buffer_formats;
EXPECT_FALSE(supported_rb_formats.empty());
for (size_t i = 0; i < supported_rb_formats.size(); ++i) {
SCOPED_TRACE(testing::Message() << "DAI supported_rb_formats[" << i << "]");
ASSERT_TRUE(supported_rb_formats[i].has_pcm_supported_formats());
auto& format_set = *supported_rb_formats[i].mutable_pcm_supported_formats();
ring_buffer_pcm_formats_.push_back(std::move(format_set));
}
}));
} else if (device_entry().isStreamConfig()) {
stream_config()->GetSupportedFormats(AddCallback(
"GetSupportedFormats",
[this](std::vector<fuchsia::hardware::audio::SupportedFormats> supported_formats) {
EXPECT_FALSE(supported_formats.empty());
for (size_t i = 0; i < supported_formats.size(); ++i) {
SCOPED_TRACE(testing::Message() << "StreamConfig supported_formats[" << i << "]");
ASSERT_TRUE(supported_formats[i].has_pcm_supported_formats());
auto& format_set = *supported_formats[i].mutable_pcm_supported_formats();
ring_buffer_pcm_formats_.push_back(std::move(format_set));
}
}));
} else {
FAIL() << "unknown driver type";
__UNREACHABLE;
}
ExpectCallbacks();
ValidateRingBufferFormatSets(ring_buffer_pcm_formats());
if (!HasFailure()) {
SetMinMaxRingBufferFormats();
}
}
// Fail if the returned formats are not complete, unique and within ranges.
void TestBase::ValidateRingBufferFormatSets(
const std::vector<fuchsia::hardware::audio::PcmSupportedFormats>& rb_format_sets) {
for (size_t i = 0; i < rb_format_sets.size(); ++i) {
SCOPED_TRACE(testing::Message() << "ring_buffer_pcm_format[" << i << "]");
auto& format_set = rb_format_sets[i];
ASSERT_TRUE(format_set.has_channel_sets());
ASSERT_TRUE(format_set.has_sample_formats());
ASSERT_TRUE(format_set.has_bytes_per_sample());
ASSERT_TRUE(format_set.has_valid_bits_per_sample());
ASSERT_TRUE(format_set.has_frame_rates());
ASSERT_FALSE(format_set.channel_sets().empty());
ASSERT_FALSE(format_set.sample_formats().empty());
ASSERT_FALSE(format_set.bytes_per_sample().empty());
ASSERT_FALSE(format_set.valid_bits_per_sample().empty());
ASSERT_FALSE(format_set.frame_rates().empty());
EXPECT_LE(format_set.channel_sets().size(), fuchsia::hardware::audio::MAX_COUNT_CHANNEL_SETS);
EXPECT_LE(format_set.sample_formats().size(),
fuchsia::hardware::audio::MAX_COUNT_SUPPORTED_SAMPLE_FORMATS);
EXPECT_LE(format_set.bytes_per_sample().size(),
fuchsia::hardware::audio::MAX_COUNT_SUPPORTED_BYTES_PER_SAMPLE);
EXPECT_LE(format_set.valid_bits_per_sample().size(),
fuchsia::hardware::audio::MAX_COUNT_SUPPORTED_VALID_BITS_PER_SAMPLE);
EXPECT_LE(format_set.frame_rates().size(), fuchsia::hardware::audio::MAX_COUNT_SUPPORTED_RATES);
for (size_t j = 0; j < format_set.channel_sets().size(); ++j) {
SCOPED_TRACE(testing::Message() << "channel_set[" << j << "]");
auto& channel_set = format_set.channel_sets()[j];
ASSERT_TRUE(channel_set.has_attributes());
ASSERT_FALSE(channel_set.attributes().empty());
EXPECT_LE(channel_set.attributes().size(),
fuchsia::hardware::audio::MAX_COUNT_CHANNELS_IN_RING_BUFFER);
// Ensure each `ChannelSet` contains a unique number of channels.
for (size_t k = j + 1; k < format_set.channel_sets().size(); ++k) {
size_t other_channel_set_size = format_set.channel_sets()[k].attributes().size();
EXPECT_NE(channel_set.attributes().size(), other_channel_set_size)
<< "same channel count as channel_set[" << k << "]: " << other_channel_set_size;
}
for (size_t k = 0; k < channel_set.attributes().size(); ++k) {
SCOPED_TRACE(testing::Message() << "attributes[" << k << "]");
auto& attribs = channel_set.attributes()[k];
// Ensure channel_set.attributes are within the required range.
if (attribs.has_min_frequency()) {
EXPECT_LT(attribs.min_frequency(), fuchsia::media::MAX_PCM_FRAMES_PER_SECOND);
}
if (attribs.has_max_frequency()) {
EXPECT_GT(attribs.max_frequency(), fuchsia::media::MIN_PCM_FRAMES_PER_SECOND);
EXPECT_LE(attribs.max_frequency(), fuchsia::media::MAX_PCM_FRAMES_PER_SECOND);
if (attribs.has_min_frequency()) {
EXPECT_LE(attribs.min_frequency(), attribs.max_frequency());
}
}
}
}
// Ensure sample_formats are unique.
for (size_t j = 0; j < format_set.sample_formats().size(); ++j) {
for (size_t k = j + 1; k < format_set.sample_formats().size(); ++k) {
EXPECT_NE(static_cast<uint16_t>(fidl::ToUnderlying(format_set.sample_formats()[j])),
static_cast<uint16_t>(fidl::ToUnderlying(format_set.sample_formats()[k])))
<< "sample_formats[" << j << "] ("
<< static_cast<uint16_t>(fidl::ToUnderlying(format_set.sample_formats()[j]))
<< ") must not equal sample_formats[" << k << "] ("
<< static_cast<uint16_t>(fidl::ToUnderlying(format_set.sample_formats()[k])) << ")";
}
}
// Ensure bytes_per_sample are unique and listed in ascending order.
uint16_t max_bytes = 0;
for (size_t j = 0; j < format_set.bytes_per_sample().size(); ++j) {
EXPECT_GT(format_set.bytes_per_sample()[j], 0)
<< "bytes_per_sample[" << j << "] ("
<< static_cast<uint16_t>(format_set.bytes_per_sample()[j])
<< ") must be greater than zero";
if (j > 0) {
EXPECT_GT(format_set.bytes_per_sample()[j], format_set.bytes_per_sample()[j - 1])
<< "bytes_per_sample[" << j << "] ("
<< static_cast<uint16_t>(format_set.bytes_per_sample()[j])
<< ") must exceed bytes_per_sample[" << j - 1 << "] ("
<< static_cast<uint16_t>(format_set.bytes_per_sample()[j - 1]) << ")";
}
max_bytes = std::max(max_bytes, static_cast<uint16_t>(format_set.bytes_per_sample()[j]));
}
// Ensure valid_bits_per_sample are unique and listed in ascending order.
for (size_t j = 0; j < format_set.valid_bits_per_sample().size(); ++j) {
EXPECT_GT(format_set.valid_bits_per_sample()[j], 0)
<< "valid_bits_per_sample[" << j << "] ("
<< static_cast<uint16_t>(format_set.valid_bits_per_sample()[j])
<< ") must be greater than zero";
if (j > 0) {
EXPECT_GT(format_set.valid_bits_per_sample()[j], format_set.valid_bits_per_sample()[j - 1])
<< "valid_bits_per_sample[" << j << "] ("
<< static_cast<uint16_t>(format_set.valid_bits_per_sample()[j])
<< ") must exceed than valid_bits_per_sample[" << j - 1 << "] ("
<< static_cast<uint16_t>(format_set.valid_bits_per_sample()[j - 1]) << ")";
}
EXPECT_LE(format_set.valid_bits_per_sample()[j], max_bytes * 8)
<< "valid_bits_per_sample[" << j << "] ("
<< static_cast<uint16_t>(format_set.valid_bits_per_sample()[j])
<< ") must fit into the maximum bytes_per_sample (" << max_bytes << ")";
}
// Ensure frame_rates are in range and unique and listed in ascending order.
for (size_t j = 0; j < format_set.frame_rates().size(); ++j) {
EXPECT_GE(format_set.frame_rates()[j], fuchsia::media::MIN_PCM_FRAMES_PER_SECOND)
<< "frame_rates[" << j << "] (" << format_set.frame_rates()[j]
<< ") cannot be less than MIN_PCM_FRAMES_PER_SECOND ("
<< fuchsia::media::MIN_PCM_FRAMES_PER_SECOND << ")";
EXPECT_LE(format_set.frame_rates()[j], fuchsia::media::MAX_PCM_FRAMES_PER_SECOND)
<< "frame_rates[" << j << "] (" << format_set.frame_rates()[j]
<< ") cannot exceed MAX_PCM_FRAMES_PER_SECOND ("
<< fuchsia::media::MAX_PCM_FRAMES_PER_SECOND << ")";
if (j > 0) {
EXPECT_GT(format_set.frame_rates()[j], format_set.frame_rates()[j - 1])
<< "frame_rates[" << j << "] (" << format_set.frame_rates()[j]
<< ") must exceed frame_rates[" << j - 1 << "] (" << format_set.frame_rates()[j - 1]
<< ")";
}
}
}
}
void TestBase::SetMinMaxRingBufferFormats() {
for (size_t i = 0; i < ring_buffer_pcm_formats_.size(); ++i) {
SCOPED_TRACE(testing::Message() << "pcm_format[" << i << "]");
size_t min_chans = ~0, max_chans = 0;
uint8_t min_bytes_per_sample = ~0, max_bytes_per_sample = 0;
uint8_t min_valid_bits_per_sample = ~0, max_valid_bits_per_sample = 0;
uint32_t min_frame_rate = ~0, max_frame_rate = 0;
auto& format_set = ring_buffer_pcm_formats_[i];
fuchsia::hardware::audio::SampleFormat sample_format = format_set.sample_formats()[0];
for (auto& channel_set : format_set.channel_sets()) {
if (channel_set.attributes().size() < min_chans) {
min_chans = channel_set.attributes().size();
}
if (channel_set.attributes().size() > max_chans) {
max_chans = channel_set.attributes().size();
}
}
for (auto& bytes_per_sample : format_set.bytes_per_sample()) {
if (bytes_per_sample < min_bytes_per_sample) {
min_bytes_per_sample = bytes_per_sample;
}
if (bytes_per_sample > max_bytes_per_sample) {
max_bytes_per_sample = bytes_per_sample;
}
}
for (auto& valid_bits : format_set.valid_bits_per_sample()) {
if (valid_bits < min_valid_bits_per_sample) {
min_valid_bits_per_sample = valid_bits;
}
if (valid_bits > max_valid_bits_per_sample) {
max_valid_bits_per_sample = valid_bits;
}
}
for (auto& frame_rate : format_set.frame_rates()) {
if (frame_rate < min_frame_rate) {
min_frame_rate = frame_rate;
}
if (frame_rate > max_frame_rate) {
max_frame_rate = frame_rate;
}
}
// Save, if less than min.
auto bit_rate = min_chans * min_bytes_per_sample * min_frame_rate;
if (i == 0 || bit_rate < static_cast<size_t>(min_ring_buffer_format_.number_of_channels) *
min_ring_buffer_format_.bytes_per_sample *
min_ring_buffer_format_.frame_rate) {
min_ring_buffer_format_ = {
.number_of_channels = static_cast<uint8_t>(min_chans),
.sample_format = sample_format,
.bytes_per_sample = min_bytes_per_sample,
.valid_bits_per_sample = min_valid_bits_per_sample,
.frame_rate = min_frame_rate,
};
}
// Save, if more than max.
bit_rate = max_chans * max_bytes_per_sample * max_frame_rate;
if (i == 0 || bit_rate > static_cast<size_t>(max_ring_buffer_format_.number_of_channels) *
max_ring_buffer_format_.bytes_per_sample *
max_ring_buffer_format_.frame_rate) {
max_ring_buffer_format_ = {
.number_of_channels = static_cast<uint8_t>(max_chans),
.sample_format = sample_format,
.bytes_per_sample = max_bytes_per_sample,
.valid_bits_per_sample = max_valid_bits_per_sample,
.frame_rate = max_frame_rate,
};
}
}
}
void TestBase::ValidateRingBufferFormat(const fuchsia::hardware::audio::PcmFormat& rb_format) {
EXPECT_GT(rb_format.bytes_per_sample, 0u);
EXPECT_LE(rb_format.bytes_per_sample, sizeof(double));
EXPECT_GT(rb_format.frame_rate, fuchsia::media::MIN_PCM_FRAMES_PER_SECOND);
EXPECT_LE(rb_format.frame_rate, fuchsia::media::MAX_PCM_FRAMES_PER_SECOND);
EXPECT_GT(rb_format.number_of_channels, 0u);
EXPECT_LE(rb_format.number_of_channels,
fuchsia::hardware::audio::MAX_COUNT_CHANNELS_IN_RING_BUFFER);
EXPECT_GT(rb_format.valid_bits_per_sample, 0u);
EXPECT_LE(rb_format.valid_bits_per_sample, sizeof(double) * 8u);
}
void TestBase::LogRingBufferFormat(const fuchsia::hardware::audio::PcmFormat& format,
const std::string& tag) {
FX_LOGS(INFO) << tag << ": " << format.frame_rate << ", smp_fmt "
<< static_cast<int>(format.sample_format) << ", "
<< static_cast<uint16_t>(format.valid_bits_per_sample) << "-in-"
<< format.bytes_per_sample * 8u << ", "
<< static_cast<uint16_t>(format.number_of_channels) << "-chan";
}
const fuchsia::hardware::audio::PcmFormat& TestBase::min_ring_buffer_format() const {
return min_ring_buffer_format_;
}
const fuchsia::hardware::audio::PcmFormat& TestBase::max_ring_buffer_format() const {
return max_ring_buffer_format_;
}
const std::vector<fuchsia::hardware::audio::PcmSupportedFormats>&
TestBase::ring_buffer_pcm_formats() const {
return ring_buffer_pcm_formats_;
}
void TestBase::SignalProcessingConnect() {
if (sp_.is_bound()) {
return; // Already connected.
}
fidl::InterfaceHandle<fuchsia::hardware::audio::signalprocessing::SignalProcessing> sp_client;
fidl::InterfaceRequest<fuchsia::hardware::audio::signalprocessing::SignalProcessing> sp_server =
sp_client.NewRequest();
composite()->SignalProcessingConnect(std::move(sp_server));
sp_ = sp_client.Bind();
}
// First retrieve the element list. If signalprocessing is not supported, exit early;
// otherwise save the ID of a RING_BUFFER element, and the ID of a DAI_INTERCONNECT element.
// We will use these IDs later, when performing Dai-specific and RingBuffer-specific checks.
void TestBase::RequestTopologies() {
SignalProcessingConnect();
zx_status_t status = ZX_OK;
ring_buffer_id_.reset();
dai_id_.reset();
sp_->GetElements(AddCallback(
"signalprocessing::Reader::GetElements",
[this,
&status](fuchsia::hardware::audio::signalprocessing::Reader_GetElements_Result result) {
status = result.is_err() ? result.err() : ZX_OK;
if (status == ZX_OK) {
elements_ = std::move(result.response().processing_elements);
for (auto& element : elements_) {
if (element.type() ==
fuchsia::hardware::audio::signalprocessing::ElementType::RING_BUFFER) {
ring_buffer_id_.emplace(element.id()); // Override any previous.
} else if (element.type() ==
fuchsia::hardware::audio::signalprocessing::ElementType::DAI_INTERCONNECT) {
dai_id_.emplace(element.id()); // Override any previous.
}
}
}
}));
ExpectCallbacks();
// Either we get elements or the API method is not supported.
ASSERT_TRUE(status == ZX_OK || status == ZX_ERR_NOT_SUPPORTED);
// We don't check for topologies if GetElements is not supported.
if (status == ZX_ERR_NOT_SUPPORTED) {
return;
}
// If supported, GetElements must return at least one element.
ASSERT_TRUE(!elements_.empty());
sp_->GetTopologies(AddCallback(
"signalprocessing::Reader::GetTopologies",
[this](fuchsia::hardware::audio::signalprocessing::Reader_GetTopologies_Result result) {
ASSERT_FALSE(result.is_err());
topologies_ = std::move(result.response().topologies);
}));
ExpectCallbacks();
// We only call GetTopologies if we have elements, so we must have at least one topology.
ASSERT_FALSE(topologies_.empty());
}
} // namespace media::audio::drivers::test