blob: 2fbf93e199bd547cb2305848972d4372d30c40fa [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 "src/media/audio/audio_core/driver_output.h"
#include <lib/fzl/vmo-mapper.h>
#include <zircon/status.h>
#include <fbl/ref_ptr.h>
#include <fbl/span.h>
#include <gmock/gmock.h>
#include "src/media/audio/audio_core/audio_device_manager.h"
#include "src/media/audio/audio_core/audio_driver.h"
#include "src/media/audio/audio_core/loudness_transform.h"
#include "src/media/audio/audio_core/testing/fake_audio_driver.h"
#include "src/media/audio/audio_core/testing/fake_audio_renderer.h"
#include "src/media/audio/audio_core/testing/threading_model_fixture.h"
#include "src/media/audio/lib/effects_loader/testing/test_effects.h"
#include "src/media/audio/lib/format/driver_format.h"
#include "src/media/audio/lib/logging/logging.h"
using testing::Each;
using testing::Eq;
using testing::FloatEq;
namespace media::audio {
namespace {
constexpr size_t kRingBufferSizeBytes = 8 * PAGE_SIZE;
constexpr zx::duration kExpectedMixInterval =
DriverOutput::kDefaultHighWaterNsec - DriverOutput::kDefaultLowWaterNsec;
constexpr zx::duration kBeyondSubmittedPackets = zx::sec(1);
const fuchsia::media::AudioStreamType kDefaultStreamType{
.sample_format = fuchsia::media::AudioSampleFormat::FLOAT,
.channels = 2,
.frames_per_second = 48000,
};
} // namespace
class DriverOutputTest : public testing::ThreadingModelFixture {
protected:
void SetUp() override {
ThreadingModelFixture::SetUp();
zx::channel c1, c2;
ASSERT_EQ(ZX_OK, zx::channel::create(0, &c1, &c2));
driver_ = std::make_unique<testing::FakeAudioDriverV1>(
std::move(c1), threading_model().FidlDomain().dispatcher());
ASSERT_NE(driver_, nullptr);
output_ = std::make_shared<DriverOutput>("", &threading_model(), &context().device_manager(),
std::move(c2), &context().link_matrix(),
context().process_config().default_volume_curve());
ASSERT_NE(output_, nullptr);
ring_buffer_mapper_ = driver_->CreateRingBuffer(kRingBufferSizeBytes);
ASSERT_NE(ring_buffer_mapper_.start(), nullptr);
}
// Use len = -1 for the end of the buffer.
template <typename T>
fbl::Span<T> RingBufferSlice(size_t first, ssize_t maybe_len) const {
static_assert(kRingBufferSizeBytes % sizeof(T) == 0);
T* array = static_cast<T*>(ring_buffer_mapper_.start());
size_t len = maybe_len >= 0 ? maybe_len : (kRingBufferSizeBytes / sizeof(T)) - first;
FX_CHECK((first + len) * sizeof(T) <= kRingBufferSizeBytes);
return {&array[first], len};
}
template <typename T>
std::array<T, kRingBufferSizeBytes / sizeof(T)>& RingBuffer() const {
static_assert(kRingBufferSizeBytes % sizeof(T) == 0);
return reinterpret_cast<std::array<T, kRingBufferSizeBytes / sizeof(T)>&>(
static_cast<T*>(ring_buffer_mapper_.start())[0]);
}
// Updates the driver to advertise the given format. This will be the only audio format that the
// driver exposes.
void ConfigureDriverForSampleFormat(uint8_t chans, uint32_t sample_rate,
audio_sample_format_t sample_format, uint16_t flags) {
driver_->set_formats({{
.sample_formats = sample_format,
.min_frames_per_second = sample_rate,
.max_frames_per_second = sample_rate,
.min_channels = chans,
.max_channels = chans,
.flags = flags,
}});
}
VolumeCurve volume_curve_ = VolumeCurve::DefaultForMinGain(Gain::kMinGainDb);
std::unique_ptr<testing::FakeAudioDriverV1> driver_;
std::shared_ptr<AudioOutput> output_;
fzl::VmoMapper ring_buffer_mapper_;
zx::vmo ring_buffer_;
};
// Simple sanity test that the DriverOutput properly initializes the driver.
TEST_F(DriverOutputTest, DriverOutputStartsDriver) {
driver_->Start();
// Fill the ring buffer with some bytes so we can detect if we've written to the buffer.
auto rb_bytes = RingBuffer<uint8_t>();
memset(rb_bytes.data(), 0xff, rb_bytes.size());
// Setup our driver to advertise support for only 16-bit/2-channel/48khz audio.
constexpr uint8_t kSupportedChannels = 2;
constexpr uint32_t kSupportedSampleRate = 48000;
constexpr audio_sample_format_t kSupportedSampleFormat = AUDIO_SAMPLE_FORMAT_16BIT;
ConfigureDriverForSampleFormat(kSupportedChannels, kSupportedSampleRate, kSupportedSampleFormat,
ASF_RANGE_FLAG_FPS_48000_FAMILY);
// Startup the DriverOutput. We expect it's completed some basic initialization of the driver.
threading_model().FidlDomain().ScheduleTask(output_->Startup());
RunLoopUntilIdle();
EXPECT_TRUE(driver_->is_running());
// Verify the DriverOutput has requested a ring buffer with the correct format type. Since we only
// published support for a single format above, there's only one possible solution here.
auto selected_format = output_->format();
EXPECT_TRUE(selected_format);
EXPECT_EQ(kSupportedSampleRate, selected_format->frames_per_second());
EXPECT_EQ(kSupportedChannels, selected_format->channels());
audio_sample_format_t selected_sample_format;
ASSERT_TRUE(AudioSampleFormatToDriverSampleFormat(selected_format->sample_format(),
&selected_sample_format));
EXPECT_EQ(kSupportedSampleFormat, selected_sample_format);
// We expect the driver has filled the buffer with silence. For 16-bit/2-channel audio, we can
// represent each frame as a single uint32_t.
const uint32_t kSilentFrame = 0;
EXPECT_THAT(RingBuffer<float>(), Each(FloatEq(kSilentFrame)));
threading_model().FidlDomain().ScheduleTask(output_->Shutdown());
RunLoopUntilIdle();
}
// Test fix for fxbug.dev/47251
TEST_F(DriverOutputTest, HandlePlugDetectBeforeStartResponse) {
// Fill the ring buffer with some bytes so we can detect if we've written to the buffer.
auto rb_bytes = RingBuffer<uint8_t>();
memset(rb_bytes.data(), 0xff, rb_bytes.size());
// Setup our driver to advertise support for only 16-bit/2-channel/48khz audio.
constexpr uint8_t kSupportedChannels = 2;
constexpr uint32_t kSupportedSampleRate = 48000;
constexpr audio_sample_format_t kSupportedSampleFormat = AUDIO_SAMPLE_FORMAT_16BIT;
ConfigureDriverForSampleFormat(kSupportedChannels, kSupportedSampleRate, kSupportedSampleFormat,
ASF_RANGE_FLAG_FPS_48000_FAMILY);
driver_->set_plugged(true);
driver_->set_hardwired(false);
// Startup the DriverOutput. We expect it's completed some basic initialization of the driver.
context().device_manager().AddDevice(output_);
RunLoopUntilIdle();
// We want to step through some driver commands to that we can send the plug detect message before
// the ring buffer is started.
while (true) {
auto result = driver_->Step();
RunLoopUntilIdle();
if (!result.is_ok()) {
ASSERT_EQ(result.error(), ZX_ERR_SHOULD_WAIT)
<< "Command filed " << zx_status_get_string(result.error());
break;
}
}
// |AUDIO_RB_CMD_GET_BUFFER| comes right before |START|, so we'll stop processing those messages
// then.
while (true) {
auto result = driver_->StepRingBuffer();
ASSERT_TRUE(result.is_ok()) << "Command failed " << zx_status_get_string(result.error());
RunLoopUntilIdle();
if (result.value() == AUDIO_RB_CMD_GET_BUFFER) {
break;
}
}
// Now process the main channel again. This will process any plug detect messages.
while (true) {
auto result = driver_->Step();
RunLoopUntilIdle();
if (!result.is_ok()) {
ASSERT_EQ(result.error(), ZX_ERR_SHOULD_WAIT)
<< "Command filed " << zx_status_get_string(result.error());
break;
}
}
// Now add a renderer. We expect it to not yet be linked because the ring buffer hasn't completed
// the |AUDIO_RB_CMD_START| message yet.
auto renderer = testing::FakeAudioRenderer::Create(
dispatcher(), std::make_optional<Format>(Format::Create(kDefaultStreamType).take_value()),
fuchsia::media::AudioRenderUsage::MEDIA, &context().link_matrix());
context().route_graph().AddRenderer(renderer);
context().route_graph().SetRendererRoutingProfile(
*renderer, {.routable = true, .usage = StreamUsage::WithRenderUsage(RenderUsage::MEDIA)});
RunLoopUntilIdle();
// Since the output is not started, we should not have linked the renderer yet.
ASSERT_FALSE(context().link_matrix().AreLinked(*renderer, *output_));
// Now finish starting the ring buffer and confirm the link has been made to our renderer.
auto result = driver_->StepRingBuffer();
RunLoopUntilIdle();
ASSERT_TRUE(result.is_ok());
ASSERT_EQ(result.value(), AUDIO_RB_CMD_START);
result = driver_->StepRingBuffer();
ASSERT_FALSE(result.is_ok());
ASSERT_TRUE(context().link_matrix().AreLinked(*renderer, *output_));
threading_model().FidlDomain().ScheduleTask(output_->Shutdown());
RunLoopUntilIdle();
}
TEST_F(DriverOutputTest, RendererOutput) {
driver_->Start();
// Setup our driver to advertise support for a single format.
constexpr uint8_t kSupportedChannels = 2;
constexpr uint32_t kSupportedSampleRate = 48000;
constexpr audio_sample_format_t kSupportedSampleFormat = AUDIO_SAMPLE_FORMAT_16BIT;
ConfigureDriverForSampleFormat(kSupportedChannels, kSupportedSampleRate, kSupportedSampleFormat,
ASF_RANGE_FLAG_FPS_48000_FAMILY);
threading_model().FidlDomain().ScheduleTask(output_->Startup());
RunLoopUntilIdle();
EXPECT_TRUE(driver_->is_running());
auto renderer = testing::FakeAudioRenderer::CreateWithDefaultFormatInfo(dispatcher(),
&context().link_matrix());
context().link_matrix().LinkObjects(renderer, output_,
std::make_shared<MappedLoudnessTransform>(volume_curve_));
renderer->EnqueueAudioPacket(0.5, zx::msec(5));
renderer->EnqueueAudioPacket(0.5, zx::msec(5));
// Only these first two packets will be mixed -- we'll stop before mixing the third.
bool packet3_released = false;
renderer->EnqueueAudioPacket(-1.0, zx::msec(5), [&packet3_released] { packet3_released = true; });
// Run the loop to just before the mix should occur, to validate we mix on the correct interval.
RunLoopFor(kExpectedMixInterval - zx::nsec(1));
const uint32_t kSilentFrame = 0;
EXPECT_THAT(RingBuffer<uint32_t>(), Each(Eq(kSilentFrame)));
// Now run for that last instant and expect a mix has occurred.
RunLoopFor(zx::nsec(1));
// Expect 3 sections of the ring:
// [0, first_non_silent_frame) - Silence (corresponds to the mix lead time).
// [first_non_silent_frame, first_silent_frame) - Non silent samples (corresponds to 0.5
// samples provided by the renderer, which is 0x4000 in uint16).
// [first_silent_frame, ring_buffer.size()) - Silence again (we did not provide any data to
// mix at this point in the ring buffer).
const uint32_t kNonSilentFrame = 0x40004000;
const uint32_t kMixWindowFrames = 480;
size_t first_non_silent_frame =
(kSupportedSampleRate * output_->presentation_delay().to_nsecs()) / 1'000'000'000;
size_t first_silent_frame = first_non_silent_frame + kMixWindowFrames;
EXPECT_THAT(RingBufferSlice<uint32_t>(0, first_non_silent_frame), Each(Eq(kSilentFrame)));
EXPECT_THAT(RingBufferSlice<uint32_t>(first_non_silent_frame, kMixWindowFrames),
Each(Eq(kNonSilentFrame)));
EXPECT_THAT(RingBufferSlice<uint32_t>(first_silent_frame, -1), Each(Eq(kSilentFrame)));
EXPECT_FALSE(packet3_released);
// Play out any remaining packets, so the slab_allocator won't assert on debug builds.
RunLoopFor(kBeyondSubmittedPackets);
threading_model().FidlDomain().ScheduleTask(output_->Shutdown());
RunLoopUntilIdle();
}
TEST_F(DriverOutputTest, MixAtExpectedInterval) {
driver_->Start();
// Setup our driver to advertise support for a single format.
constexpr uint8_t kSupportedChannels = 2;
constexpr uint32_t kSupportedSampleRate = 48000;
constexpr audio_sample_format_t kSupportedSampleFormat = AUDIO_SAMPLE_FORMAT_16BIT;
// 5ms at our chosen sample rate.
constexpr uint32_t kFifoDepth = 240;
constexpr zx::duration kExternalDelay = zx::usec(47376);
driver_->set_fifo_depth(kFifoDepth);
driver_->set_external_delay(kExternalDelay);
ConfigureDriverForSampleFormat(kSupportedChannels, kSupportedSampleRate, kSupportedSampleFormat,
ASF_RANGE_FLAG_FPS_48000_FAMILY);
threading_model().FidlDomain().ScheduleTask(output_->Startup());
RunLoopUntilIdle();
EXPECT_TRUE(driver_->is_running());
auto renderer = testing::FakeAudioRenderer::CreateWithDefaultFormatInfo(dispatcher(),
&context().link_matrix());
context().link_matrix().LinkObjects(renderer, output_,
std::make_shared<MappedLoudnessTransform>(volume_curve_));
renderer->EnqueueAudioPacket(0.875, kExpectedMixInterval);
renderer->EnqueueAudioPacket(-0.875, kExpectedMixInterval);
// We'll have 4 sections in our ring buffer:
// * Silence during the initial lead time.
// * 10ms of frames that contain 0.875 float data.
// * 10ms of frames that contain -0.875 float data.
// * Silence during the rest of the ring.
const uint32_t kSilentFrame = 0;
const uint32_t kPostiveFrame = 0x70007000;
const uint32_t kNegativeFrame = 0x90009000;
const uint32_t kMixWindowFrames = 480;
// Renderer clients need to provide packets early, by the amount presentation_delay.
// Audio data will be mixed into the ring buffer, offset by exactly that amount EXCEPT the
// external_delay component, which is a post-interconnect delay.
size_t first_positive_frame =
(kSupportedSampleRate * (output_->presentation_delay() - kExternalDelay).to_nsecs()) /
1'000'000'000;
size_t first_negative_frame = first_positive_frame + kMixWindowFrames;
size_t first_silent_frame = first_negative_frame + kMixWindowFrames;
// Run until just before the expected first mix. Expect the ring buffer to be empty.
RunLoopFor(kExpectedMixInterval - zx::nsec(1));
EXPECT_THAT(RingBuffer<uint32_t>(), Each(Eq(kSilentFrame)));
// Now expect the first mix, which adds the 0.875 samples
RunLoopFor(zx::nsec(1));
EXPECT_THAT(RingBufferSlice<uint32_t>(0, first_positive_frame), Each(Eq(kSilentFrame)));
EXPECT_THAT(RingBufferSlice<uint32_t>(first_positive_frame, kMixWindowFrames),
Each(Eq(kPostiveFrame)));
EXPECT_THAT(RingBufferSlice<uint32_t>(first_negative_frame, -1), Each(Eq(kSilentFrame)));
// Run until just before the next mix interval. Expect the ring to be unchanged.
RunLoopFor(kExpectedMixInterval - zx::nsec(1));
EXPECT_THAT(RingBufferSlice<uint32_t>(0, first_positive_frame), Each(Eq(kSilentFrame)));
EXPECT_THAT(RingBufferSlice<uint32_t>(first_positive_frame, kMixWindowFrames),
Each(Eq(kPostiveFrame)));
EXPECT_THAT(RingBufferSlice<uint32_t>(first_negative_frame, -1), Each(Eq(kSilentFrame)));
// Now run the second mix. Expect the additional negative frames to be added to the ring.
RunLoopFor(zx::nsec(1));
EXPECT_THAT(RingBufferSlice<uint32_t>(0, first_positive_frame), Each(Eq(kSilentFrame)));
EXPECT_THAT(RingBufferSlice<uint32_t>(first_positive_frame, kMixWindowFrames),
Each(Eq(kPostiveFrame)));
EXPECT_THAT(RingBufferSlice<uint32_t>(first_negative_frame, kMixWindowFrames),
Each(Eq(kNegativeFrame)));
EXPECT_THAT(RingBufferSlice<uint32_t>(first_silent_frame, -1), Each(Eq(kSilentFrame)));
threading_model().FidlDomain().ScheduleTask(output_->Shutdown());
RunLoopUntilIdle();
}
TEST_F(DriverOutputTest, WriteSilenceToRingWhenMuted) {
driver_->Start();
// Setup our driver to advertise support for a single format.
constexpr uint8_t kSupportedChannels = 2;
constexpr uint32_t kSupportedSampleRate = 48000;
constexpr audio_sample_format_t kSupportedSampleFormat = AUDIO_SAMPLE_FORMAT_16BIT;
// 5ms at our chosen sample rate.
constexpr uint32_t kFifoDepth = 240;
constexpr zx::duration kExternalDelay = zx::usec(47376);
driver_->set_fifo_depth(kFifoDepth);
driver_->set_external_delay(kExternalDelay);
ConfigureDriverForSampleFormat(kSupportedChannels, kSupportedSampleRate, kSupportedSampleFormat,
ASF_RANGE_FLAG_FPS_48000_FAMILY);
threading_model().FidlDomain().ScheduleTask(output_->Startup());
RunLoopUntilIdle();
EXPECT_TRUE(driver_->is_running());
// Mute the output.
fuchsia::media::AudioGainInfo gain_info;
gain_info.flags = fuchsia::media::AudioGainInfoFlags::MUTE;
output_->SetGainInfo(gain_info, fuchsia::media::AudioGainValidFlags::MUTE_VALID);
RunLoopUntilIdle();
// Create an add a renderer. We enqueue some audio in this renderer, however we'll expect the
// ring to only contain silence since the output is muted.
auto renderer = testing::FakeAudioRenderer::CreateWithDefaultFormatInfo(dispatcher(),
&context().link_matrix());
context().link_matrix().LinkObjects(renderer, output_,
std::make_shared<MappedLoudnessTransform>(volume_curve_));
bool packet1_released = false;
bool packet2_released = false;
renderer->EnqueueAudioPacket(1.0, kExpectedMixInterval,
[&packet1_released] { packet1_released = true; });
renderer->EnqueueAudioPacket(-1.0, kExpectedMixInterval,
[&packet2_released] { packet2_released = true; });
// Fill the ring buffer with some bytes so we can detect if we've written to the buffer.
memset(ring_buffer_mapper_.start(), 0xff, kRingBufferSizeBytes);
const uint32_t kMixWindowFrames = 480;
const uint32_t kSilentFrame = 0;
const uint32_t kInitialFrame = UINT32_MAX;
// Renderer clients need to provide packets early, by the amount presentation_delay.
// Audio data will be mixed into the ring buffer, offset by exactly that amount EXCEPT the
// external_delay component, which is a post-interconnect delay.
size_t first_silent_frame =
(kSupportedSampleRate * (output_->presentation_delay() - kExternalDelay).to_nsecs()) /
1'000'000'000;
size_t num_silent_frames = kMixWindowFrames * 2;
// Run loop to consume all the frames from the renderer.
RunLoopFor(kExpectedMixInterval);
RunLoopFor(kExpectedMixInterval);
EXPECT_THAT(RingBufferSlice<uint32_t>(0, first_silent_frame), Each(Eq(kInitialFrame)));
EXPECT_THAT(RingBufferSlice<uint32_t>(first_silent_frame, num_silent_frames),
Each(Eq(kSilentFrame)));
EXPECT_THAT(RingBufferSlice<uint32_t>(first_silent_frame + num_silent_frames, -1),
Each(Eq(kInitialFrame)));
// We expect to have mixed these packets, but we want to hold onto them until the corresponding
// frames would have been played back.
EXPECT_FALSE(packet1_released || packet2_released);
// Run the loop for |presentation_delay| to verify we release our packets. We add
// |kExpectedMixInterval| - |zx::nsec(1)| to ensure we run the next |Process()| after this lead
// time has elapsed.
RunLoopFor((output_->presentation_delay() + kExpectedMixInterval - zx::nsec(1)));
EXPECT_TRUE(packet1_released);
EXPECT_TRUE(packet2_released);
threading_model().FidlDomain().ScheduleTask(output_->Shutdown());
RunLoopUntilIdle();
}
class DriverV2OutputTest : public testing::ThreadingModelFixture {
protected:
static constexpr uint32_t kRequestedDeviceRate = 48000;
static constexpr uint16_t kRequestedDeviceChannels = 4;
static PipelineConfig CreatePipelineConfig() {
PipelineConfig config;
config.mutable_root().name = "default";
config.mutable_root().input_streams = {
RenderUsage::BACKGROUND, RenderUsage::MEDIA, RenderUsage::INTERRUPTION,
RenderUsage::SYSTEM_AGENT, RenderUsage::COMMUNICATION,
};
config.mutable_root().output_rate = kRequestedDeviceRate;
config.mutable_root().output_channels = kRequestedDeviceChannels / 2;
config.mutable_root().loopback = true;
config.mutable_root().effects = {{
.lib_name = testing::kTestEffectsModuleName,
.effect_name = "rechannel",
.instance_name = "1:2 upchannel",
.effect_config = "",
.output_channels = kRequestedDeviceChannels,
}};
return config;
}
DriverV2OutputTest()
: ThreadingModelFixture(
ProcessConfig::Builder()
.AddDeviceProfile(
{std::nullopt,
DeviceConfig::OutputDeviceProfile(
/* eligible_for_loopback */ true,
StreamUsageSetFromRenderUsages(kFidlRenderUsages),
VolumeCurve::DefaultForMinGain(VolumeCurve::kDefaultGainForMinVolume),
/* independent_volume_control */ false, CreatePipelineConfig(),
/* driver_gain_db */ 0.0)})
.SetDefaultVolumeCurve(
VolumeCurve::DefaultForMinGain(VolumeCurve::kDefaultGainForMinVolume))
.Build()) {}
void SetUp() override {
ThreadingModelFixture::SetUp();
zx::channel c1, c2;
ASSERT_EQ(ZX_OK, zx::channel::create(0, &c1, &c2));
driver_ = std::make_unique<testing::FakeAudioDriverV2>(
std::move(c1), threading_model().FidlDomain().dispatcher());
ASSERT_NE(driver_, nullptr);
driver_->Start();
fidl::InterfaceHandle<fuchsia::hardware::audio::StreamConfig> stream_config = {};
stream_config.set_channel(std::move(c2));
output_ = std::make_shared<DriverOutput>("", &threading_model(), &context().device_manager(),
std::move(stream_config), &context().link_matrix(),
context().process_config().default_volume_curve());
ASSERT_NE(output_, nullptr);
ring_buffer_mapper_ = driver_->CreateRingBuffer(kRingBufferSizeBytes);
ASSERT_NE(ring_buffer_mapper_.start(), nullptr);
// Add a rechannel effect.
test_effects_.AddEffect("rechannel")
.WithChannelization(FUCHSIA_AUDIO_EFFECTS_CHANNELS_ANY, FUCHSIA_AUDIO_EFFECTS_CHANNELS_ANY)
.WithAction(TEST_EFFECTS_ACTION_ADD, 1.0);
}
// Use len = -1 for the end of the buffer.
template <typename T>
fbl::Span<T> RingBufferSlice(size_t first, ssize_t maybe_len) const {
static_assert(kRingBufferSizeBytes % sizeof(T) == 0);
T* array = static_cast<T*>(ring_buffer_mapper_.start());
size_t len = maybe_len >= 0 ? maybe_len : (kRingBufferSizeBytes / sizeof(T)) - first;
FX_CHECK((first + len) * sizeof(T) <= kRingBufferSizeBytes);
return {&array[first], len};
}
template <typename T>
std::array<T, kRingBufferSizeBytes / sizeof(T)>& RingBuffer() const {
static_assert(kRingBufferSizeBytes % sizeof(T) == 0);
return reinterpret_cast<std::array<T, kRingBufferSizeBytes / sizeof(T)>&>(
static_cast<T*>(ring_buffer_mapper_.start())[0]);
}
// Updates the driver to advertise the given format. This will be the only audio format that the
// driver exposes.
void ConfigureDriverForSampleFormat(fuchsia::hardware::audio::PcmFormat sample_format) {
fuchsia::hardware::audio::PcmSupportedFormats formats = {};
formats.number_of_channels.push_back(sample_format.number_of_channels);
formats.sample_formats.push_back(sample_format.sample_format);
formats.bytes_per_sample.push_back(sample_format.bytes_per_sample);
formats.valid_bits_per_sample.push_back(sample_format.valid_bits_per_sample);
formats.frame_rates.push_back(sample_format.frame_rate);
ConfigureDriverForSampleFormats(std::move(formats));
}
void ConfigureDriverForSampleFormats(fuchsia::hardware::audio::PcmSupportedFormats formats) {
driver_->set_formats(std::move(formats));
}
VolumeCurve volume_curve_ = VolumeCurve::DefaultForMinGain(Gain::kMinGainDb);
std::unique_ptr<testing::FakeAudioDriverV2> driver_;
std::shared_ptr<DriverOutput> output_;
fzl::VmoMapper ring_buffer_mapper_;
zx::vmo ring_buffer_;
testing::TestEffectsModule test_effects_ = testing::TestEffectsModule::Open();
};
// Simple sanity test that the DriverOutput properly initializes the driver.
TEST_F(DriverV2OutputTest, DriverOutputStartsDriver) {
// Fill the ring buffer with some bytes so we can detect if we've written to the buffer.
auto rb_bytes = RingBuffer<uint8_t>();
memset(rb_bytes.data(), 0xff, rb_bytes.size());
// Setup our driver to advertise support for only 24-bit/2-channel/48khz audio.
fuchsia::hardware::audio::PcmFormat supportedSampleFormat = {};
supportedSampleFormat.sample_format = fuchsia::hardware::audio::SampleFormat::PCM_SIGNED;
supportedSampleFormat.bytes_per_sample = 4;
supportedSampleFormat.valid_bits_per_sample = 24;
supportedSampleFormat.number_of_channels = 2;
supportedSampleFormat.frame_rate = 48000;
ConfigureDriverForSampleFormat(supportedSampleFormat);
// Startup the DriverOutput. We expect it's completed some basic initialization of the driver.
threading_model().FidlDomain().ScheduleTask(output_->Startup());
RunLoopUntilIdle();
EXPECT_TRUE(driver_->is_running());
// Verify the DriverOutput has requested a ring buffer with the correct format type. Since we only
// published support for a single format above, there's only one possible solution here.
auto selected_format = driver_->selected_format();
EXPECT_TRUE(selected_format);
auto& selected = selected_format.value();
EXPECT_EQ(selected.sample_format, supportedSampleFormat.sample_format);
EXPECT_EQ(selected.bytes_per_sample, supportedSampleFormat.bytes_per_sample);
EXPECT_EQ(selected.valid_bits_per_sample, supportedSampleFormat.valid_bits_per_sample);
EXPECT_EQ(selected.number_of_channels, supportedSampleFormat.number_of_channels);
EXPECT_EQ(selected.frame_rate, supportedSampleFormat.frame_rate);
// We expect the driver has filled the buffer with silence. For 16-bit/2-channel audio, we can
// represent each frame as a single uint32_t.
const uint32_t kSilentFrame = 0;
EXPECT_THAT(RingBuffer<float>(), Each(FloatEq(kSilentFrame)));
threading_model().FidlDomain().ScheduleTask(output_->Shutdown());
RunLoopUntilIdle();
}
TEST_F(DriverV2OutputTest, RendererOutput) {
// Setup our driver to advertise support for a single format.
fuchsia::hardware::audio::PcmFormat supportedSampleFormat = {};
supportedSampleFormat.sample_format = fuchsia::hardware::audio::SampleFormat::PCM_SIGNED;
supportedSampleFormat.bytes_per_sample = 2;
supportedSampleFormat.valid_bits_per_sample = 16;
supportedSampleFormat.number_of_channels = 2;
supportedSampleFormat.frame_rate = 48000;
ConfigureDriverForSampleFormat(supportedSampleFormat);
threading_model().FidlDomain().ScheduleTask(output_->Startup());
RunLoopUntilIdle();
EXPECT_TRUE(driver_->is_running());
auto renderer = testing::FakeAudioRenderer::CreateWithDefaultFormatInfo(dispatcher(),
&context().link_matrix());
context().link_matrix().LinkObjects(renderer, output_,
std::make_shared<MappedLoudnessTransform>(volume_curve_));
renderer->EnqueueAudioPacket(-0.5, zx::msec(5));
renderer->EnqueueAudioPacket(-0.5, zx::msec(5));
// Only the first two packets will be mixed; we'll stop before mixing the third.
bool packet3_released = false;
renderer->EnqueueAudioPacket(0.8765, zx::msec(5),
[&packet3_released] { packet3_released = true; });
// Run the loop for just before we expect the mix to occur to validate we're mixing on the correct
// interval.
RunLoopFor(kExpectedMixInterval - zx::nsec(1));
const uint32_t kSilentFrame = 0;
EXPECT_THAT(RingBuffer<uint32_t>(), Each(Eq(kSilentFrame)));
// Now run for that last instant and expect a mix has occurred.
RunLoopFor(zx::nsec(1));
// Expect 3 sections of the ring:
// [0, first_non_silent_frame) - Silence (corresponds to the mix lead time).
// [first_non_silent_frame, first_silent_frame) - Non silent samples (corresponds to -0.5
// samples provided by renderer: 0xC000 in int16; 0xC000C000 for entire frame as uint32).
// [first_silent_frame, ring_buffer.size()) - Silence again (we did not provide any data to
// mix at this point in the ring buffer).
const uint32_t kNonSilentFrame = 0xC000C000;
const uint32_t kMixWindowFrames = 480;
size_t first_non_silent_frame =
(supportedSampleFormat.frame_rate * output_->presentation_delay().to_nsecs()) / 1'000'000'000;
size_t first_silent_frame = first_non_silent_frame + kMixWindowFrames;
EXPECT_THAT(RingBufferSlice<uint32_t>(0, first_non_silent_frame), Each(Eq(kSilentFrame)));
EXPECT_THAT(RingBufferSlice<uint32_t>(first_non_silent_frame, kMixWindowFrames),
Each(Eq(kNonSilentFrame)));
EXPECT_THAT(RingBufferSlice<uint32_t>(first_silent_frame, -1), Each(Eq(kSilentFrame)));
EXPECT_FALSE(packet3_released);
// Play out any remaining packets, so the slab_allocator won't assert on debug builds.
RunLoopFor(kBeyondSubmittedPackets);
threading_model().FidlDomain().ScheduleTask(output_->Shutdown());
RunLoopUntilIdle();
}
TEST_F(DriverV2OutputTest, MixAtExpectedInterval) {
// Setup our driver to advertise support for a single format.
fuchsia::hardware::audio::PcmFormat supportedSampleFormat = {};
supportedSampleFormat.sample_format = fuchsia::hardware::audio::SampleFormat::PCM_SIGNED;
supportedSampleFormat.bytes_per_sample = 2;
supportedSampleFormat.valid_bits_per_sample = 16;
supportedSampleFormat.number_of_channels = 2;
supportedSampleFormat.frame_rate = 48000;
// 5ms at our chosen sample rate.
constexpr uint32_t kFifoDepth = 240;
constexpr zx::duration kExternalDelay = zx::usec(47376);
driver_->set_fifo_depth(kFifoDepth);
driver_->set_external_delay(kExternalDelay);
ConfigureDriverForSampleFormat(supportedSampleFormat);
threading_model().FidlDomain().ScheduleTask(output_->Startup());
RunLoopUntilIdle();
EXPECT_TRUE(driver_->is_running());
auto renderer = testing::FakeAudioRenderer::CreateWithDefaultFormatInfo(dispatcher(),
&context().link_matrix());
context().link_matrix().LinkObjects(renderer, output_,
std::make_shared<MappedLoudnessTransform>(volume_curve_));
renderer->EnqueueAudioPacket(0.75, kExpectedMixInterval);
renderer->EnqueueAudioPacket(-0.75, kExpectedMixInterval);
// We'll have 4 sections in our ring buffer:
// * Silence during the initial lead time.
// * 10ms of frames that contain 0.75 float data.
// * 10ms of frames that contain -0.75 float data.
// * Silence during the rest of the ring.
const uint32_t kSilentFrame = 0;
const uint32_t kPostiveFrame = 0x60006000;
const uint32_t kNegativeFrame = 0xA000A000;
const uint32_t kMixWindowFrames = 480;
// Renderer clients need to provide packets early, by the amount presentation_delay.
// Audio data will be mixed into the ring buffer, offset by exactly that amount EXCEPT the
// external_delay component, which is a post-interconnect delay.
size_t first_positive_frame = (supportedSampleFormat.frame_rate *
(output_->presentation_delay() - kExternalDelay).to_nsecs()) /
1'000'000'000;
size_t first_negative_frame = first_positive_frame + kMixWindowFrames;
size_t first_silent_frame = first_negative_frame + kMixWindowFrames;
// Run until just before the expected first mix. Expect the ring buffer to be empty.
RunLoopFor(kExpectedMixInterval - zx::nsec(1));
EXPECT_THAT(RingBuffer<uint32_t>(), Each(Eq(kSilentFrame)));
// Now expect the first mix, which adds the positive samples
RunLoopFor(zx::nsec(1));
EXPECT_THAT(RingBufferSlice<uint32_t>(0, first_positive_frame), Each(Eq(kSilentFrame)));
EXPECT_THAT(RingBufferSlice<uint32_t>(first_positive_frame, kMixWindowFrames),
Each(Eq(kPostiveFrame)));
EXPECT_THAT(RingBufferSlice<uint32_t>(first_negative_frame, -1), Each(Eq(kSilentFrame)));
// Run until just before the next mix interval. Expect the ring to be unchanged.
RunLoopFor(kExpectedMixInterval - zx::nsec(1));
EXPECT_THAT(RingBufferSlice<uint32_t>(0, first_positive_frame), Each(Eq(kSilentFrame)));
EXPECT_THAT(RingBufferSlice<uint32_t>(first_positive_frame, kMixWindowFrames),
Each(Eq(kPostiveFrame)));
EXPECT_THAT(RingBufferSlice<uint32_t>(first_negative_frame, -1), Each(Eq(kSilentFrame)));
// Now run the second mix. Expect the additional negative frames to be added to the ring.
RunLoopFor(zx::nsec(1));
EXPECT_THAT(RingBufferSlice<uint32_t>(0, first_positive_frame), Each(Eq(kSilentFrame)));
EXPECT_THAT(RingBufferSlice<uint32_t>(first_positive_frame, kMixWindowFrames),
Each(Eq(kPostiveFrame)));
EXPECT_THAT(RingBufferSlice<uint32_t>(first_negative_frame, kMixWindowFrames),
Each(Eq(kNegativeFrame)));
EXPECT_THAT(RingBufferSlice<uint32_t>(first_silent_frame, -1), Each(Eq(kSilentFrame)));
threading_model().FidlDomain().ScheduleTask(output_->Shutdown());
RunLoopUntilIdle();
}
TEST_F(DriverV2OutputTest, WriteSilenceToRingWhenMuted) {
// Setup our driver to advertise support for a single format.
fuchsia::hardware::audio::PcmFormat supportedSampleFormat = {};
supportedSampleFormat.sample_format = fuchsia::hardware::audio::SampleFormat::PCM_SIGNED;
supportedSampleFormat.bytes_per_sample = 2;
supportedSampleFormat.valid_bits_per_sample = 16;
supportedSampleFormat.number_of_channels = 2;
supportedSampleFormat.frame_rate = 48000;
ConfigureDriverForSampleFormat(supportedSampleFormat);
// 5ms at our chosen sample rate.
constexpr uint32_t kFifoDepth = 240;
constexpr zx::duration kExternalDelay = zx::usec(47376);
driver_->set_fifo_depth(kFifoDepth);
driver_->set_external_delay(kExternalDelay);
threading_model().FidlDomain().ScheduleTask(output_->Startup());
RunLoopUntilIdle();
EXPECT_TRUE(driver_->is_running());
// Mute the output.
fuchsia::media::AudioGainInfo gain_info;
gain_info.flags = fuchsia::media::AudioGainInfoFlags::MUTE;
output_->SetGainInfo(gain_info, fuchsia::media::AudioGainValidFlags::MUTE_VALID);
RunLoopUntilIdle();
// Create an add a renderer. We enqueue some audio in this renderer, however we'll expect the
// ring to only contain silence since the output is muted.
auto renderer = testing::FakeAudioRenderer::CreateWithDefaultFormatInfo(dispatcher(),
&context().link_matrix());
context().link_matrix().LinkObjects(renderer, output_,
std::make_shared<MappedLoudnessTransform>(volume_curve_));
bool packet1_released = false;
bool packet2_released = false;
renderer->EnqueueAudioPacket(1.0, kExpectedMixInterval,
[&packet1_released] { packet1_released = true; });
renderer->EnqueueAudioPacket(-1.0, kExpectedMixInterval,
[&packet2_released] { packet2_released = true; });
// Fill the ring buffer with some bytes so we can detect if we've written to the buffer.
memset(ring_buffer_mapper_.start(), 0xff, kRingBufferSizeBytes);
const uint32_t kMixWindowFrames = 480;
const uint32_t kSilentFrame = 0;
const uint32_t kInitialFrame = UINT32_MAX;
// Renderer clients need to provide packets early, by the amount presentation_delay.
// Audio data will be mixed into the ring buffer, offset by exactly that amount EXCEPT the
// external_delay component, which is a post-interconnect delay.
size_t first_silent_frame = (supportedSampleFormat.frame_rate *
(output_->presentation_delay() - kExternalDelay).to_nsecs()) /
1'000'000'000;
size_t num_silent_frames = kMixWindowFrames * 2;
// Run loop to consume all the frames from the renderer.
RunLoopFor(kExpectedMixInterval);
RunLoopFor(kExpectedMixInterval);
EXPECT_THAT(RingBufferSlice<uint32_t>(0, first_silent_frame), Each(Eq(kInitialFrame)));
EXPECT_THAT(RingBufferSlice<uint32_t>(first_silent_frame, num_silent_frames),
Each(Eq(kSilentFrame)));
EXPECT_THAT(RingBufferSlice<uint32_t>(first_silent_frame + num_silent_frames, -1),
Each(Eq(kInitialFrame)));
// We expect to have mixed these packets, but we want to hold onto them until the corresponding
// frames would have been played back.
EXPECT_FALSE(packet1_released || packet2_released);
// Run the loop for |presentation_delay| to verify we release our packets. We add
// |kExpectedMixInterval| - |zx::nsec(1)| to ensure we run the next |Process()| after this lead
// time has elapsed.
RunLoopFor((output_->presentation_delay() + kExpectedMixInterval - zx::nsec(1)));
EXPECT_TRUE(packet1_released);
EXPECT_TRUE(packet2_released);
threading_model().FidlDomain().ScheduleTask(output_->Shutdown());
RunLoopUntilIdle();
}
TEST_F(DriverV2OutputTest, SelectRateAndChannelizationFromDeviceConfig) {
// Setup our driver to advertise support for a single format.
fuchsia::hardware::audio::PcmSupportedFormats formats = {};
formats.sample_formats.push_back(fuchsia::hardware::audio::SampleFormat::PCM_SIGNED);
formats.bytes_per_sample.push_back(2);
formats.valid_bits_per_sample.push_back(16);
// Support the requested rate/channelization from the pipeline config, but also support additional
// rates and channelizations.
formats.number_of_channels.push_back(kRequestedDeviceChannels / 2);
formats.number_of_channels.push_back(kRequestedDeviceChannels);
formats.number_of_channels.push_back(kRequestedDeviceChannels * 2);
formats.frame_rates.push_back(kRequestedDeviceRate / 2);
formats.frame_rates.push_back(kRequestedDeviceRate);
formats.frame_rates.push_back(kRequestedDeviceRate * 2);
ConfigureDriverForSampleFormats(std::move(formats));
threading_model().FidlDomain().ScheduleTask(output_->Startup());
RunLoopUntilIdle();
EXPECT_TRUE(driver_->is_running());
// Expect the pipeline to include the 1:2 upchannel effect.
EXPECT_EQ(1u, output_->pipeline_config().root().effects.size());
EXPECT_EQ(output_->pipeline_config().root().output_channels, kRequestedDeviceChannels / 2);
EXPECT_EQ(output_->pipeline_config().root().output_rate, kRequestedDeviceRate);
EXPECT_EQ(output_->pipeline_config().channels(), kRequestedDeviceChannels);
EXPECT_EQ(output_->pipeline_config().frames_per_second(), kRequestedDeviceRate);
}
TEST_F(DriverV2OutputTest, UseBestAvailableSampleRateAndChannelization) {
// Setup our driver to advertise support for a single format.
fuchsia::hardware::audio::PcmSupportedFormats formats = {};
formats.sample_formats.push_back(fuchsia::hardware::audio::SampleFormat::PCM_SIGNED);
formats.bytes_per_sample.push_back(2);
formats.valid_bits_per_sample.push_back(16);
// Support the requested channelization but not the requested sample rate.
static constexpr uint32_t kSupportedFrameRate = kRequestedDeviceRate / 2;
static constexpr uint16_t kSupportedChannels = kRequestedDeviceChannels / 2;
formats.number_of_channels.push_back(kSupportedChannels);
formats.frame_rates.push_back(kSupportedFrameRate);
ConfigureDriverForSampleFormats(std::move(formats));
threading_model().FidlDomain().ScheduleTask(output_->Startup());
RunLoopUntilIdle();
EXPECT_TRUE(driver_->is_running());
// If the device does not meet our requirements, then we don't attempt to use the rechannel effect
// and just rely on our root mix stage to meet the channelization required.
EXPECT_TRUE(output_->pipeline_config().root().effects.empty());
EXPECT_EQ(output_->pipeline_config().root().output_channels, kSupportedChannels);
EXPECT_EQ(output_->pipeline_config().root().output_rate, kSupportedFrameRate);
EXPECT_EQ(output_->pipeline_config().channels(), kSupportedChannels);
EXPECT_EQ(output_->pipeline_config().frames_per_second(), kSupportedFrameRate);
}
} // namespace media::audio