blob: 1336460bc5574c8e05438d248b67c45b65b2bbdd [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/gtest/real_loop_fixture.h>
#include "lib/component/cpp/environment_services_helper.h"
#include "src/lib/fxl/logging.h"
#include "src/media/audio/audio_core/test/audio_tests_shared.h"
namespace media::audio::test {
// The AudioLoopbackEnvironment class allows us to make configuration changes
// before any test case begins, and after all test cases complete.
class AudioLoopbackEnvironment : public ::testing::Environment {
public:
// Before any test cases in this program, synchronously connect to the service
// to ensure that the audio and audio_core components are present and loaded,
// and that at least one (virtual) audio output device is present.
//
// On assert-false during this SetUp method, no test cases run, and they may
// display as passed. However, the overall binary returns non-zero (fail).
void SetUp() override {
// This is an unchanging input for the entire component; get it once here.
auto environment_services = component::GetEnvironmentServices();
// We need at least one active audio output, for loopback capture to work.
// So use this Control to enable virtualaudio and add a virtual audio output
// that will exist for the entirety of this binary's test cases. We will
// remove it and disable virtual_audio immediately afterward.
environment_services->ConnectToService(
virtual_audio_control_sync_.NewRequest());
zx_status_t status = virtual_audio_control_sync_->Enable();
ASSERT_EQ(status, ZX_OK) << "Failed to enable virtualaudio";
// Create an output device using default settings, save it while tests run.
environment_services->ConnectToService(
virtual_audio_output_sync_.NewRequest());
status = virtual_audio_output_sync_->Add();
ASSERT_EQ(status, ZX_OK) << "Failed to add virtual audio output";
// Ensure that the output is active before we proceed to running tests.
uint32_t num_inputs, num_outputs = 0, num_tries = 0;
do {
status =
virtual_audio_control_sync_->GetNumDevices(&num_inputs, &num_outputs);
ASSERT_EQ(status, ZX_OK) << "GetNumDevices failed";
++num_tries;
} while (num_outputs == 0 && num_tries < 100);
ASSERT_GT(num_outputs, 0u)
<< "Timed out trying to add virtual audio output";
// Synchronously calling a FIDL method with a callback guarantees that the
// service is loaded and running before the sync method itself returns.
//
// This is not the case for sync calls _without_ callback, nor async calls,
// because of pipelining inherent in FIDL's design.
environment_services->ConnectToService(audio_dev_enum_sync_.NewRequest());
// Ensure that the audio_core binary is resident.
uint64_t default_output;
bool connected_to_svc = (audio_dev_enum_sync_->GetDefaultOutputDevice(
&default_output) == ZX_OK);
ASSERT_TRUE(connected_to_svc) << "Failed in GetDefaultOutputDevice";
}
// Ensure that our virtual device is gone when our test bin exits.
void TearDown() override {
// Remove our virtual audio output device
zx_status_t status = virtual_audio_output_sync_->Remove();
ASSERT_EQ(status, ZX_OK) << "Failed to add virtual audio output";
// And ensure that virtualaudio is disabled, by the time we leave.
status = virtual_audio_control_sync_->Disable();
ASSERT_EQ(status, ZX_OK) << "Failed to disable virtualaudio";
// Wait for GetNumDevices(output) to equal zero before proceeding.
uint32_t num_inputs = 1, num_outputs = 1, num_tries = 0;
do {
status =
virtual_audio_control_sync_->GetNumDevices(&num_inputs, &num_outputs);
ASSERT_EQ(status, ZX_OK) << "GetNumDevices failed";
++num_tries;
} while ((num_outputs != 0 || num_inputs != 0) && num_tries < 100);
ASSERT_EQ(num_outputs, 0u) << "Timed out while disabling virtualaudio";
ASSERT_EQ(num_inputs, 0u) << "Timed out while disabling virtualaudio";
virtual_audio_output_sync_.Unbind();
virtual_audio_control_sync_.Unbind();
}
// If needed, this (overriding) function would also need to be public.
// ~AudioLoopbackEnvironment() override {}
fuchsia::virtualaudio::ControlSyncPtr virtual_audio_control_sync_;
fuchsia::virtualaudio::OutputSyncPtr virtual_audio_output_sync_;
fuchsia::media::AudioDeviceEnumeratorSyncPtr audio_dev_enum_sync_;
};
//
// AudioLoopbackTest
//
// Base Class for testing simple playback and capture with loopback.
class AudioLoopbackTest : public gtest::RealLoopFixture {
public:
static constexpr int32_t kSampleRate = 8000;
static constexpr int kChannelCount = 1;
static constexpr int kSampleSeconds = 1;
static constexpr int16_t kPlaybackData1 = 0x1000;
static constexpr int16_t kPlaybackData2 = 0xfff;
static constexpr int16_t kCaptureData1 = 0x7fff;
protected:
void SetUp() override;
void TearDown() override;
std::shared_ptr<component::Services> environment_services_;
fuchsia::media::AudioPtr audio_;
void SetUpRenderer(unsigned int index, int16_t data);
void CleanUpRenderer(unsigned int index);
fuchsia::media::AudioRendererPtr audio_renderer_[2];
fzl::VmoMapper payload_buffer_[2];
size_t playback_size_[2];
size_t playback_sample_size_[2];
void SetUpCapturer(unsigned int index, int16_t data);
void CleanUpCapturer(unsigned int index);
fuchsia::media::AudioCapturerPtr audio_capturer_[1];
fzl::VmoMapper capture_buffer_[1];
size_t capture_size_[1];
size_t capture_sample_size_[1];
bool error_occurred_ = false;
void ErrorHandler(zx_status_t error) {
error_occurred_ = true;
FXL_LOG(ERROR) << "Unexpected error: " << error;
}
};
// AudioLoopbackTest implementation
//
// SetUpRenderer
//
// For loopback tests, setup the first audio_renderer interface.
void AudioLoopbackTest::SetUpRenderer(unsigned int index, int16_t data) {
ASSERT_LT(index, countof(audio_renderer_));
int16_t* buffer;
zx::vmo payload_vmo;
audio_->CreateAudioRenderer(audio_renderer_[index].NewRequest());
ASSERT_TRUE(audio_renderer_[index].is_bound());
audio_renderer_[index].set_error_handler(
[this](zx_status_t error) { ErrorHandler(error); });
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));
}
// CleanUpRenderer
//
// Flush the output and free the vmo that was used by Renderer1.
void AudioLoopbackTest::CleanUpRenderer(unsigned int index) {
ASSERT_LT(index, countof(audio_renderer_));
bool flushed = false;
// Flush the audio
audio_renderer_[index]->DiscardAllPackets(
[&flushed]() { flushed = true; });
EXPECT_TRUE(RunLoopWithTimeoutOrUntil(
[this, &flushed]() { return error_occurred_ || flushed; },
kDurationResponseExpected, kDurationGranularity))
<< kTimeoutErr;
EXPECT_TRUE(flushed);
payload_buffer_[index].Unmap();
}
// SetUpCapturer
//
// For loopback tests, setup an audio_capturer interface
void AudioLoopbackTest::SetUpCapturer(unsigned int index, int16_t data) {
ASSERT_LT(index, countof(audio_capturer_));
int16_t* buffer;
zx::vmo capture_vmo;
audio_->CreateAudioCapturer(audio_capturer_[index].NewRequest(), true);
ASSERT_TRUE(audio_capturer_[index].is_bound());
audio_capturer_[index].set_error_handler(
[this](zx_status_t error) { ErrorHandler(error); });
fuchsia::media::AudioStreamType format;
format.sample_format = fuchsia::media::AudioSampleFormat::SIGNED_16;
format.channels = kChannelCount;
format.frames_per_second = kSampleRate;
capture_sample_size_[index] = sizeof(int16_t);
capture_size_[index] = format.frames_per_second * format.channels *
capture_sample_size_[index] * kSampleSeconds;
// ZX_VM_PERM_WRITE taken here as we pre-fill the buffer to catch any
// cases where we get back a packet without anything having been done
// with it.
zx_status_t status = capture_buffer_[index].CreateAndMap(
capture_size_[index], 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_[index].start());
for (int32_t i = 0; i < kSampleRate * kSampleSeconds; i++)
buffer[i] = data;
audio_capturer_[index]->SetPcmStreamType(format);
audio_capturer_[index]->AddPayloadBuffer(0, std::move(capture_vmo));
}
// CleanUpCapturer
//
void AudioLoopbackTest::CleanUpCapturer(unsigned int index) {
ASSERT_LT(index, countof(audio_capturer_));
}
//
// AudioLoopbackTest implementation
//
void AudioLoopbackTest::SetUp() {
::gtest::RealLoopFixture::SetUp();
environment_services_ = component::GetEnvironmentServices();
environment_services_->ConnectToService(audio_.NewRequest());
ASSERT_TRUE(audio_.is_bound());
audio_.set_error_handler([this](zx_status_t error) { ErrorHandler(error); });
audio_->SetSystemGain(0.0f);
audio_->SetSystemMute(false);
}
void AudioLoopbackTest::TearDown() {
EXPECT_FALSE(error_occurred_);
EXPECT_TRUE(audio_.is_bound());
::gtest::RealLoopFixture::TearDown();
}
// SingleStream
//
// Creates a single output stream and a loopback capture and verifies it gets
// back what it puts in.
TEST_F(AudioLoopbackTest, SingleStream) {
fuchsia::media::StreamPacket packet, captured;
// SetUp playback stream
SetUpRenderer(0, kPlaybackData1);
SetUpCapturer(0, kCaptureData1);
auto* capture = reinterpret_cast<int16_t*>(capture_buffer_[0].start());
// Add a callback for when we get our captured packet.
bool produced_packet = false;
audio_capturer_[0].events().OnPacketProduced =
[this, &captured, &produced_packet](fuchsia::media::StreamPacket packet) {
// We only care about the first set of captured samples
if (captured.payload_size == 0) {
captured = packet;
audio_capturer_[0]->StopAsyncCaptureNoReply();
produced_packet = true;
}
};
// Get the minimum duration after submitting a packet to when we can start
// capturing what we sent on the loopback interface
zx_duration_t sleep_duration = 0;
audio_renderer_[0]->GetMinLeadTime([&sleep_duration](zx_duration_t t) {
// Give a little wiggle room.
sleep_duration = t + ZX_MSEC(5);
});
ASSERT_TRUE(RunLoopWithTimeoutOrUntil(
[this, &sleep_duration]() {
return error_occurred_ || (sleep_duration > 0);
},
kDurationResponseExpected, kDurationGranularity))
<< kTimeoutErr;
ASSERT_FALSE(error_occurred_);
packet.payload_offset = 0;
packet.payload_size = playback_size_[0];
audio_renderer_[0]->SendPacketNoReply(packet);
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.
audio_renderer_[0]->Play(zx_clock_get_monotonic(), 0,
[&ref_time_received, &media_time_received](
int64_t ref_time, int64_t media_time) {
ref_time_received = ref_time;
media_time_received = media_time;
});
ASSERT_TRUE(RunLoopWithTimeoutOrUntil(
[this, &ref_time_received]() {
return error_occurred_ || (ref_time_received > -1);
},
kDurationResponseExpected, kDurationGranularity))
<< kTimeoutErr;
ASSERT_FALSE(error_occurred_);
// 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_GE(ref_time_received, 0);
// Give the playback some time to get mixed.
zx_nanosleep(zx_clock_get_monotonic() + sleep_duration);
// Capture 10 samples of audio.
audio_capturer_[0]->StartAsyncCapture(10);
EXPECT_TRUE(RunLoopWithTimeoutOrUntil(
[this, &produced_packet]() { return error_occurred_ || produced_packet; },
kDurationResponseExpected, kDurationGranularity))
<< kTimeoutErr;
// Check that we got 10 samples as we expected.
EXPECT_EQ(captured.payload_size / capture_sample_size_[0], 10U);
// Check that all of the samples contain the expected data.
for (size_t i = 0; i < (captured.payload_size / capture_sample_size_[0]);
i++) {
size_t index = (captured.payload_offset + i) % 8000;
EXPECT_EQ(capture[index], kPlaybackData1);
}
CleanUpRenderer(0);
CleanUpCapturer(0);
}
// DualStream
//
// Creates a pair of output streams and a loopback capture and verifies it gets
// back what it puts in.
TEST_F(AudioLoopbackTest, DualStream) {
fuchsia::media::StreamPacket packet[2], captured;
// SetUp playback streams
SetUpRenderer(0, kPlaybackData1);
SetUpRenderer(1, kPlaybackData2);
// SetUp loopback capture
SetUpCapturer(0, kCaptureData1);
auto* capture = reinterpret_cast<int16_t*>(capture_buffer_[0].start());
// Add a callback for when we get our captured packet.
bool produced_packet = false;
audio_capturer_[0].events().OnPacketProduced =
[this, &captured, &produced_packet](fuchsia::media::StreamPacket packet) {
// We only care about the first set of captured samples
if (captured.payload_size == 0) {
captured = packet;
audio_capturer_[0]->StopAsyncCaptureNoReply();
produced_packet = true;
}
};
// 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.
zx_duration_t sleep_duration = 0;
audio_renderer_[0]->GetMinLeadTime([&sleep_duration](zx_duration_t t) {
// Give a little wiggle room.
sleep_duration = t + ZX_MSEC(5);
});
ASSERT_TRUE(RunLoopWithTimeoutOrUntil(
[this, &sleep_duration]() {
return error_occurred_ || (sleep_duration > 0);
},
kDurationResponseExpected, kDurationGranularity))
<< kTimeoutErr;
ASSERT_FALSE(error_occurred_);
for (int i = 0; i < 2; i++) {
packet[i].payload_offset = 0;
packet[i].payload_size = playback_size_[i];
audio_renderer_[i]->SendPacketNoReply(packet[i]);
}
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 playat = zx_clock_get_monotonic();
audio_renderer_[0]->PlayNoReply(playat, 0);
// Only get the callback for the second renderer.
audio_renderer_[1]->Play(playat, 0,
[&ref_time_received, &media_time_received](
int64_t ref_time, int64_t media_time) {
ref_time_received = ref_time;
media_time_received = media_time;
});
ASSERT_TRUE(RunLoopWithTimeoutOrUntil(
[this, &ref_time_received]() {
return error_occurred_ || (ref_time_received > -1);
},
kDurationResponseExpected, kDurationGranularity))
<< kTimeoutErr;
ASSERT_FALSE(error_occurred_);
// 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);
// Give the playback some time to get mixed.
zx_nanosleep(zx_deadline_after(sleep_duration));
// Capture 10 samples of audio.
audio_capturer_[0]->StartAsyncCapture(10);
EXPECT_TRUE(RunLoopWithTimeoutOrUntil(
[this, &produced_packet]() { return error_occurred_ || produced_packet; },
kDurationResponseExpected, kDurationGranularity))
<< kTimeoutErr;
// Check that we got 10 samples as we expected.
EXPECT_EQ(captured.payload_size / capture_sample_size_[0], 10U);
// Check that all of the samples contain the expected data.
for (size_t i = 0; i < (captured.payload_size / capture_sample_size_[0]);
i++) {
size_t index = (captured.payload_offset + i) % 8000;
EXPECT_EQ(capture[index], kPlaybackData1 + kPlaybackData2);
}
CleanUpRenderer(1);
CleanUpRenderer(0);
CleanUpCapturer(0);
}
} // namespace media::audio::test
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
// gtest takes ownership of registered environments: *do not delete them*
::testing::AddGlobalTestEnvironment(
new ::media::audio::test::AudioLoopbackEnvironment);
int result = RUN_ALL_TESTS();
return result;
}