blob: e8dd633b1cc788a5f593743100c46fb9495ed482 [file] [log] [blame]
// Copyright 2018 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/media/cpp/fidl.h>
#include <fuchsia/virtualaudio/cpp/fidl.h>
#include <lib/fzl/vmo-mapper.h>
#include <lib/sys/cpp/service_directory.h>
#include <lib/zx/clock.h>
#include <fbl/algorithm.h>
#include "src/lib/fxl/logging.h"
#include "src/media/audio/lib/test/audio_test_base.h"
namespace media::audio::test {
//
// AudioLoopbackTest
//
// Base Class for testing simple playback and capture with loopback.
class AudioLoopbackTest : public media::audio::test::AudioTestBase {
protected:
static constexpr int32_t kSampleRate = 8000;
static constexpr int kChannelCount = 1;
static constexpr int kSampleSeconds = 1;
static constexpr int16_t kInitialCaptureData = 0x7fff;
static constexpr unsigned int kMaxNumRenderers = 16;
static constexpr int16_t kPlaybackData[] = {0x1000, 0xfff, -0x2345, -0x0123, 0x100, 0xff,
-0x234, -0x04b7, 0x0310, 0x0def, -0x0101, -0x2020,
0x1357, 0x1324, 0x0135, 0x0132};
static std::shared_ptr<sys::ServiceDirectory> service_directory_;
static void SetUpTestSuite();
static void TearDownTestSuite();
void SetUp() override;
void TearDown() override;
void SetUpVirtualAudioOutput();
void SetUpRenderer(unsigned int index, int16_t data);
void CleanUpRenderer(unsigned int index);
void SetUpCapturer(int16_t data);
void TestLoopback(unsigned int num_renderers);
fuchsia::media::AudioRendererPtr audio_renderer_[kMaxNumRenderers];
fzl::VmoMapper payload_buffer_[kMaxNumRenderers];
size_t playback_size_[kMaxNumRenderers];
size_t playback_sample_size_[kMaxNumRenderers];
fuchsia::media::AudioCapturerPtr audio_capturer_;
fzl::VmoMapper capture_buffer_;
size_t capture_sample_size_;
size_t capture_frames_;
size_t capture_size_;
fuchsia::media::AudioDeviceEnumeratorPtr audio_dev_enum_;
uint64_t virtual_audio_output_token_;
fuchsia::media::AudioSyncPtr audio_sync_;
fuchsia::virtualaudio::OutputSyncPtr virtual_audio_output_sync_;
};
// static
std::shared_ptr<sys::ServiceDirectory> AudioLoopbackTest::service_directory_ = nullptr;
// static
void AudioLoopbackTest::SetUpTestSuite() {
async::Loop loop(&kAsyncLoopConfigAttachToThread);
if (!service_directory_) {
service_directory_ = sys::ComponentContext::Create()->svc();
}
ASSERT_NE(service_directory_, nullptr);
// Disable device settings files
fuchsia::media::AudioCoreSyncPtr audio_core_sync;
service_directory_->Connect(audio_core_sync.NewRequest());
ASSERT_EQ(ZX_OK, audio_core_sync->EnableDeviceSettings(false));
// Ensure that virtualaudio is enabled, before testing commences.
fuchsia::virtualaudio::ControlSyncPtr control_sync;
service_directory_->Connect(control_sync.NewRequest());
ASSERT_EQ(ZX_OK, control_sync->Enable());
}
// static
void AudioLoopbackTest::TearDownTestSuite() {
async::Loop loop(&kAsyncLoopConfigAttachToThread);
ASSERT_NE(service_directory_, nullptr);
// Ensure that virtualaudio is disabled, by the time we leave.
fuchsia::virtualaudio::ControlSyncPtr control_sync;
service_directory_->Connect(control_sync.NewRequest());
ASSERT_EQ(ZX_OK, control_sync->Disable());
media::audio::test::AudioTestBase::TearDownTestSuite();
}
// AudioLoopbackTest implementation
//
void AudioLoopbackTest::SetUp() {
AudioTestBase::SetUp();
ASSERT_NE(service_directory_, nullptr);
service_directory_->Connect(audio_dev_enum_.NewRequest());
ASSERT_TRUE(audio_dev_enum_.is_bound());
audio_dev_enum_.set_error_handler(ErrorHandler());
SetUpVirtualAudioOutput();
audio_dev_enum_.events().OnDeviceAdded =
CompletionCallback([](fuchsia::media::AudioDeviceInfo unused) {
ASSERT_TRUE(false) << "Audio device added while test was running";
});
audio_dev_enum_.events().OnDeviceRemoved = CompletionCallback([this](uint64_t token) {
ASSERT_FALSE(token == virtual_audio_output_token_)
<< "Audio device removed while test was running";
});
audio_dev_enum_.events().OnDefaultDeviceChanged =
CompletionCallback([](uint64_t old_default_token, uint64_t new_default_token) {
ASSERT_TRUE(false) << "Default route changed while test was running.";
});
service_directory_->Connect(audio_sync_.NewRequest());
audio_sync_->SetSystemGain(0.0f);
audio_sync_->SetSystemMute(false);
}
void AudioLoopbackTest::TearDown() {
audio_capturer_.Unbind();
for (auto& renderer : audio_renderer_) {
renderer.Unbind();
}
bool removed = false;
audio_dev_enum_.events().OnDeviceRemoved =
CompletionCallback([&removed, want_token = virtual_audio_output_token_](uint64_t token) {
if (token == want_token) {
removed = true;
}
});
audio_dev_enum_.events().OnDeviceAdded = nullptr;
audio_dev_enum_.events().OnDefaultDeviceChanged = nullptr;
// Remove our virtual audio output device
if (virtual_audio_output_sync_.is_bound()) {
zx_status_t status = virtual_audio_output_sync_->Remove();
ASSERT_EQ(status, ZX_OK) << "Failed to remove virtual audio output";
virtual_audio_output_sync_.Unbind();
}
ExpectCondition([&removed]() { return removed; });
EXPECT_TRUE(audio_dev_enum_.is_bound());
EXPECT_TRUE(audio_sync_.is_bound());
AudioTestBase::TearDown();
}
// SetUpVirtualAudioOutput
//
// For loopback tests, setup the required audio output, using virtualaudio.
void AudioLoopbackTest::SetUpVirtualAudioOutput() {
std::string dev_uuid_read = "4a41494a4a41494a4a41494a4a41494a";
std::array<uint8_t, 16> dev_uuid{0x4a, 0x41, 0x49, 0x4a, 0x4a, 0x41, 0x49, 0x4a,
0x4a, 0x41, 0x49, 0x4a, 0x4a, 0x41, 0x49, 0x4a};
// Connect to audio device enumerator to handle device topology changes during
// test execution.
audio_dev_enum_.events().OnDeviceAdded = [this,
dev_uuid_read](fuchsia::media::AudioDeviceInfo dev) {
if (dev.unique_id == dev_uuid_read) {
virtual_audio_output_token_ = dev.token_id;
}
};
uint64_t default_dev = 0;
audio_dev_enum_.events().OnDefaultDeviceChanged = [&default_dev](uint64_t old_default_token,
uint64_t new_default_token) {
default_dev = new_default_token;
};
// Ensure that that our connection to the device enumerator has completed
// enumerating the audio devices if any exist before we add ours. This serves
// as a synchronization point to make sure audio_core has our OnDeviceAdded
// and OnDefaultDeviceChanged callbacks registered before we trigger the
// device add. Without this call, the add for the virtual output may be
// picked up and processed in the device_manager in audio_core before it's
// added our listener for events.
audio_dev_enum_->GetDevices(
CompletionCallback([](std::vector<fuchsia::media::AudioDeviceInfo> devices) {}));
ExpectCallback();
// Loopback capture requires an active audio output. Use virtualaudio to add a virtual output.
service_directory_->Connect(virtual_audio_output_sync_.NewRequest());
// Create an output device using default settings, save it while tests run.
auto status = virtual_audio_output_sync_->SetUniqueId(dev_uuid);
ASSERT_EQ(status, ZX_OK) << "Failed to set virtual audio output uuid";
status = virtual_audio_output_sync_->Add();
ASSERT_EQ(status, ZX_OK) << "Failed to add virtual audio output";
// Wait for OnDeviceAdded and OnDefaultDeviceChanged callback. These will
// both need to have happened for the new device to be used for the test.
ExpectCondition([this, &default_dev]() {
return virtual_audio_output_token_ != 0 && default_dev == virtual_audio_output_token_;
});
ASSERT_EQ(virtual_audio_output_token_, default_dev)
<< "Timed out waiting for audio_core to make the virtual audio output "
"the default.";
}
// SetUpRenderer
//
// For loopback tests, setup the first audio_renderer interface.
void AudioLoopbackTest::SetUpRenderer(unsigned int index, int16_t data) {
static_assert(kMaxNumRenderers == fbl::count_of(kPlaybackData),
"Incorrect amount of kPlaybackData specified");
FXL_CHECK(index < kMaxNumRenderers) << "Renderer index too high";
int16_t* buffer;
zx::vmo payload_vmo;
audio_sync_->CreateAudioRenderer(audio_renderer_[index].NewRequest());
ASSERT_TRUE(audio_renderer_[index].is_bound());
audio_renderer_[index].set_error_handler(ErrorHandler());
fuchsia::media::AudioStreamType format;
format.sample_format = fuchsia::media::AudioSampleFormat::SIGNED_16;
format.channels = kChannelCount;
format.frames_per_second = kSampleRate;
playback_sample_size_[index] = sizeof(int16_t);
playback_size_[index] =
format.frames_per_second * format.channels * playback_sample_size_[index] * kSampleSeconds;
zx_status_t status = payload_buffer_[index].CreateAndMap(
playback_size_[index], ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, nullptr, &payload_vmo,
ZX_RIGHT_READ | ZX_RIGHT_MAP | ZX_RIGHT_TRANSFER);
ASSERT_EQ(status, ZX_OK) << "Renderer VmoMapper:::CreateAndMap(" << index << ") failed - "
<< status;
buffer = reinterpret_cast<int16_t*>(payload_buffer_[index].start());
for (int32_t i = 0; i < kSampleRate * kSampleSeconds; i++)
buffer[i] = data;
audio_renderer_[index]->SetPcmStreamType(format);
audio_renderer_[index]->AddPayloadBuffer(0, std::move(payload_vmo));
// All audio renderers, by default, are set to 0 dB unity gain (passthru).
}
// CleanUpRenderer
//
// Flush the output and free the vmo that was used by Renderer1.
void AudioLoopbackTest::CleanUpRenderer(unsigned int index) {
FXL_CHECK(index < kMaxNumRenderers) << "Renderer index too high";
// Flush the audio
audio_renderer_[index]->DiscardAllPackets(CompletionCallback());
ExpectCallback();
payload_buffer_[index].Unmap();
}
// SetUpCapturer
//
// For loopback tests, setup an audio_capturer interface
void AudioLoopbackTest::SetUpCapturer(int16_t data) {
int16_t* buffer;
zx::vmo capture_vmo;
audio_sync_->CreateAudioCapturer(audio_capturer_.NewRequest(), true);
ASSERT_TRUE(audio_capturer_.is_bound());
audio_capturer_.set_error_handler(ErrorHandler());
fuchsia::media::AudioStreamType format;
format.sample_format = fuchsia::media::AudioSampleFormat::SIGNED_16;
format.channels = kChannelCount;
format.frames_per_second = kSampleRate;
capture_sample_size_ = sizeof(int16_t);
capture_frames_ = format.frames_per_second * kSampleSeconds;
capture_size_ = capture_frames_ * format.channels * capture_sample_size_;
// ZX_VM_PERM_WRITE taken here as we pre-fill the buffer to catch cases where we get back a packet
// without anything having been done with it.
zx_status_t status = capture_buffer_.CreateAndMap(
capture_size_, ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, nullptr, &capture_vmo,
ZX_RIGHT_READ | ZX_RIGHT_WRITE | ZX_RIGHT_MAP | ZX_RIGHT_TRANSFER);
ASSERT_EQ(status, ZX_OK) << "Capturer VmoMapper:::CreateAndMap failed - " << status;
buffer = reinterpret_cast<int16_t*>(capture_buffer_.start());
for (int32_t i = 0; i < kSampleRate * kSampleSeconds; i++)
buffer[i] = data;
audio_capturer_->SetPcmStreamType(format);
audio_capturer_->AddPayloadBuffer(0, std::move(capture_vmo));
// All audio capturers, by default, are set to 0 dB unity gain (passthru).
}
void AudioLoopbackTest::TestLoopback(unsigned int num_renderers) {
FXL_CHECK(num_renderers <= kMaxNumRenderers);
fuchsia::media::StreamPacket packet[kMaxNumRenderers];
// SetUp loopback capture
SetUpCapturer(kInitialCaptureData);
zx_duration_t sleep_duration = 0;
int16_t expected_val = 0;
// SetUp playback streams
for (auto renderer_num = 0u; renderer_num < num_renderers; ++renderer_num) {
SetUpRenderer(renderer_num, kPlaybackData[renderer_num]);
expected_val += kPlaybackData[renderer_num];
// Get the minimum duration after submitting a packet to when we can start capturing what we
// sent on the loopback interface. This assumes that the latency will be the same for both
// playback streams. This happens to be true for this test as we create the renderers with the
// same parameters, but is not a safe assumption for the general users of this API to make.
audio_renderer_[renderer_num]->GetMinLeadTime(CompletionCallback(
[&sleep_duration](zx_duration_t t) { sleep_duration = std::max(sleep_duration, t); }));
ExpectCallback();
packet[renderer_num].payload_offset = 0;
packet[renderer_num].payload_size = playback_size_[renderer_num];
audio_renderer_[renderer_num]->SendPacketNoReply(packet[renderer_num]);
}
sleep_duration += ZX_MSEC(5); // Give a little wiggle room.
int64_t ref_time_received = -1;
int64_t media_time_received = -1;
// Start playing right now, so that after we've delayed at least 1 leadtime, we should have mixed
// audio available for capture. Our playback is sized to be much much larger than our capture to
// prevent test flakes.
auto play_at = zx::clock::get_monotonic().get();
// Only get the callback for one renderer -- arbitrarily, renderer 0.
audio_renderer_[0]->Play(play_at, 0,
CompletionCallback([&ref_time_received, &media_time_received](
int64_t ref_time, int64_t media_time) {
ref_time_received = ref_time;
media_time_received = media_time;
}));
ExpectCallback();
// We expect that media_time 0 played back at some point after the 'zero' time on the system.
EXPECT_EQ(media_time_received, 0);
EXPECT_GT(ref_time_received, 0);
// Start the other renderers at exactly the same [ref_time, media_time] correspondence.
for (auto renderer_num = 1u; renderer_num < num_renderers; ++renderer_num) {
audio_renderer_[renderer_num]->PlayNoReply(ref_time_received, media_time_received);
}
// Add a callback for when we get our captured packet.
fuchsia::media::StreamPacket captured;
audio_capturer_.events().OnPacketProduced =
CompletionCallback([this, &captured](fuchsia::media::StreamPacket packet) {
// We only care about the first set of captured samples
if (captured.payload_size == 0) {
captured = packet;
audio_capturer_->StopAsyncCaptureNoReply();
}
});
// Give the playback some time to get mixed.
zx_nanosleep(zx_deadline_after(sleep_duration));
// Capture 10 samples of audio.
audio_capturer_->StartAsyncCapture(10);
ExpectCallback();
// Check that we got 10 samples as we expected.
EXPECT_EQ(captured.payload_size / capture_sample_size_, 10U);
// Check that all of the samples contain the expected data.
auto* capture = reinterpret_cast<int16_t*>(capture_buffer_.start());
for (size_t i = 0; i < (captured.payload_size / capture_sample_size_); i++) {
size_t index = (captured.payload_offset + i) % capture_frames_;
EXPECT_EQ(capture[index], expected_val);
}
for (auto renderer_num = 0u; renderer_num < num_renderers; ++renderer_num) {
CleanUpRenderer(renderer_num);
}
}
// Test Cases
//
// SingleStream
//
// Creates a single output stream and a loopback capture and verifies it gets back what it puts in.
TEST_F(AudioLoopbackTest, SingleStream) { TestLoopback(1); }
// ManyStreams
//
// Verifies loopback capture of 16 output streams.
TEST_F(AudioLoopbackTest, ManyStreams) { TestLoopback(16); }
} // namespace media::audio::test