blob: fe6ed277e8cd33ca944d62f91c42a0fbc64ad7a4 [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 <lib/gtest/real_loop_fixture.h>
#include "lib/component/cpp/environment_services_helper.h"
#include "src/media/audio/audio_core/test/audio_tests_shared.h"
namespace media::audio::test {
//
// AudioRendererTest
//
// This set of tests verifies asynchronous usage of AudioRenderer.
class AudioRendererTest : public gtest::RealLoopFixture {
protected:
void SetUp() override;
void TearDown() override;
void SetNegativeExpectations();
void ExpectDisconnect();
std::shared_ptr<component::Services> environment_services_;
fuchsia::media::AudioPtr audio_;
fuchsia::media::AudioRendererPtr audio_renderer_;
fuchsia::media::audio::GainControlPtr gain_control_;
bool error_occurred_ = false;
bool expect_error_ = false;
bool expect_renderer_ = true;
};
//
// AudioRendererTest implementation
//
void AudioRendererTest::SetUp() {
::gtest::RealLoopFixture::SetUp();
auto err_handler = [this](zx_status_t error) { error_occurred_ = true; };
environment_services_ = component::GetEnvironmentServices();
environment_services_->ConnectToService(audio_.NewRequest());
audio_.set_error_handler(err_handler);
audio_->CreateAudioRenderer(audio_renderer_.NewRequest());
audio_renderer_.set_error_handler(err_handler);
}
void AudioRendererTest::SetNegativeExpectations() {
expect_error_ = true;
expect_renderer_ = false;
}
void AudioRendererTest::TearDown() {
ASSERT_TRUE(audio_.is_bound());
EXPECT_EQ(expect_error_, error_occurred_);
EXPECT_EQ(expect_renderer_, audio_renderer_.is_bound());
::gtest::RealLoopFixture::TearDown();
}
void AudioRendererTest::ExpectDisconnect() {
EXPECT_TRUE(RunLoopWithTimeoutOrUntil([this]() { return error_occurred_; },
kDurationResponseExpected,
kDurationGranularity));
}
//
// AudioRenderer implements the base classes StreamBufferSet and StreamSink.
//
// StreamBufferSet validation
//
// TODO(mpuryear): test AddPayloadBuffer(uint32 id, handle<vmo> payload_buffer);
// Also negative testing: bad id, null or bad handle
// TODO(mpuryear): test RemovePayloadBuffer(uint32 id);
// Also negative testing: unknown or already-removed id
//
// StreamSink validation
//
// TODO(mpuryear): test SendPacket(StreamPacket packet) -> ();
// Also negative testing: malformed packet
// TODO(mpuryear): test SendPacketNoReply(StreamPacket packet);
// Also negative testing: malformed packet
// TODO(mpuryear): test EndOfStream();
// Also proper sequence of callbacks/completions
// TODO(mpuryear): test DiscardAllPackets() -> ();
// Also when no packets, when started
// TODO(mpuryear): test DiscardAllPacketsNoReply();
// Also when no packets, when started
//
// AudioRenderer validation
//
// AudioRenderer contains an internal state machine. To enter the "configured"
// state, it must receive and successfully execute both SetPcmStreamType and
// SetPayloadBuffer calls. From a Configured state only, it then transitions to
// "operational" mode when any packets are enqueued (received and not yet played
// and/or released).
// **** Before we enter Configured mode:
// SendPacket before SetPcmStreamType must fail.
// SendPacket before SetPayloadBuffer must fail.
// **** While in Configured mode:
// Before SendPacket, all valid SetPayloadBuffer should succeed.
// **** While in Operational mode:
// After SetPcmStreamType+SetPayloadBuffer, valid SendPacket should succeed.
// While renderer Operational, SetPcmStreamType must fail.
// While renderer Operational, SetPayloadBuffer must fail.
// Calling Flush must cancel+return all enqueued (sent) packets.
// **** Once back in Configured (non-Operational) mode
// Flush OR "enqueued packets drain" take renderer out of Operational.
// Once no packets are queued, all valid SetPcmStreamType should succeed.
// Once no packets are queued, all valid SetPayloadBuffer should succeed.
//
// Setting PCM format within known-supportable range of values should succeed.
// Before renderers are operational, multiple SetPcmStreamTypes should succeed.
// We test twice because of previous bug, where the first succeeded but any
// subsequent call (before Play) would cause a FIDL channel disconnect.
//
// TODO(mpuryear): Refactor tests to eliminate "wait for nothing bad to happen".
TEST_F(AudioRendererTest, SetPcmStreamType) {
fuchsia::media::AudioStreamType format;
format.sample_format = fuchsia::media::AudioSampleFormat::FLOAT;
format.channels = 2;
format.frames_per_second = 48000;
audio_renderer_->SetPcmStreamType(format);
fuchsia::media::AudioStreamType format2;
format2.sample_format = fuchsia::media::AudioSampleFormat::UNSIGNED_8;
format2.channels = 1;
format2.frames_per_second = 44100;
audio_renderer_->SetPcmStreamType(format2);
// Allow an error Disconnect callback, but we expect a timeout instead.
EXPECT_FALSE(RunLoopWithTimeoutOrUntil([this]() { return error_occurred_; },
kDurationTimeoutExpected))
<< kConnectionErr;
}
// TODO(mpuryear): test SetPtsUnits(uint32 tick_per_sec_num,uint32 denom);
// Also negative testing: zero values, nullptrs, huge num/small denom
// TODO(mpuryear): test SetPtsContinuityThreshold(float32 threshold_sec);
// Also negative testing: NaN, negative, very large, infinity
// TODO(mpuryear): test SetReferenceClock(handle reference_clock);
// Also negative testing: null handle, bad handle, handle to something else
// TODO(mpuryear): test Play(int64 ref_time, int64 med)->(int64 ref, int64 med);
// Verify success after setting format and submitting buffers.
// Also: when already in Play, very positive vals, very negative vals
// TODO(mpuryear): test PlayNoReply(int64 reference_time, int64 media_time);
// Verify success after setting format and submitting buffers.
// Also: when already in Play, very positive vals, very negative vals
// TODO(mpuryear): test Pause()->(int64 reference_time, int64 media_time);
// Verify success after setting format and submitting buffers.
// Also: when already in Pause
// TODO(mpuryear): test PauseNoReply();
// Verify success after setting format and submitting buffers.
// Also: when already in Pause
// Validate MinLeadTime events, when enabled.
TEST_F(AudioRendererTest, EnableMinLeadTimeEvents) {
int64_t min_lead_time = -1;
audio_renderer_.events().OnMinLeadTimeChanged =
[&min_lead_time](int64_t min_lead_time_nsec) {
min_lead_time = min_lead_time_nsec;
};
audio_renderer_->EnableMinLeadTimeEvents(true);
// After enabling MinLeadTime events, we expect an initial notification.
// Because we have not yet set the format, we expect MinLeadTime to be 0.
EXPECT_TRUE(RunLoopWithTimeoutOrUntil(
[this, &min_lead_time]() {
return error_occurred_ || (min_lead_time >= 0);
},
kDurationResponseExpected, kDurationGranularity))
<< kTimeoutErr;
EXPECT_EQ(min_lead_time, 0);
// FYI: after setting format, MinLeadTime should change to be greater than 0
// IF the target has AudioOutput devices, or remain 0 (no callback) if it has
// none. Both are valid possibilities, so we don't test that aspect here.
}
// Validate MinLeadTime events, when disabled.
//
// TODO(mpuryear): Refactor tests to eliminate "wait for nothing bad to happen".
TEST_F(AudioRendererTest, DisableMinLeadTimeEvents) {
int64_t min_lead_time = -1;
audio_renderer_.events().OnMinLeadTimeChanged =
[&min_lead_time](int64_t min_lead_time_nsec) {
min_lead_time = min_lead_time_nsec;
};
audio_renderer_->EnableMinLeadTimeEvents(false);
// Callback should not be received (expect loop to timeout? TRUE).
// If we did, either way it is an error: MinLeadTime event or disconnect.
EXPECT_FALSE(RunLoopWithTimeoutOrUntil(
[this, &min_lead_time]() {
return error_occurred_ || (min_lead_time >= 0);
},
kDurationTimeoutExpected));
EXPECT_EQ(min_lead_time, -1) << "Received unexpected MinLeadTime update";
}
//
// Basic validation of GetMinLeadTime() for the asynchronous AudioRenderer.
// Before SetPcmStreamType is called, MinLeadTime should equal zero.
TEST_F(AudioRendererTest, GetMinLeadTime) {
int64_t min_lead_time = -1;
audio_renderer_->GetMinLeadTime([&min_lead_time](int64_t min_lead_time_nsec) {
min_lead_time = min_lead_time_nsec;
});
// Wait to receive Lead time callback (will loop timeout? EXPECT_FALSE)
EXPECT_TRUE(RunLoopWithTimeoutOrUntil(
[this, &min_lead_time]() {
return error_occurred_ || (min_lead_time >= 0);
},
kDurationResponseExpected, kDurationGranularity))
<< kTimeoutErr;
EXPECT_EQ(min_lead_time, 0);
}
// Test creation and interface independence of GainControl.
// In a number of tests below, we run the message loop to give the AudioRenderer
// or GainControl binding a chance to disconnect, if an error occurred.
//
// TODO(mpuryear): Refactor tests to eliminate "wait for nothing bad to happen".
TEST_F(AudioRendererTest, BindGainControl) {
// Validate AudioRenderers can create GainControl interfaces.
audio_renderer_->BindGainControl(gain_control_.NewRequest());
bool gc_error_occurred = false;
auto gc_err_handler = [&gc_error_occurred](zx_status_t error) {
gc_error_occurred = true;
};
gain_control_.set_error_handler(gc_err_handler);
fuchsia::media::AudioRendererPtr audio_renderer_2;
audio_->CreateAudioRenderer(audio_renderer_2.NewRequest());
bool ar2_error_occurred = false;
auto ar2_err_handler = [&ar2_error_occurred](zx_status_t error) {
ar2_error_occurred = true;
};
audio_renderer_2.set_error_handler(ar2_err_handler);
fuchsia::media::audio::GainControlPtr gain_control_2;
audio_renderer_2->BindGainControl(gain_control_2.NewRequest());
bool gc2_error_occurred = false;
auto gc2_err_handler = [&gc2_error_occurred](zx_status_t error) {
gc2_error_occurred = true;
};
gain_control_2.set_error_handler(gc2_err_handler);
// Validate GainControl does NOT persist after AudioRenderer is unbound.
expect_renderer_ = false;
audio_renderer_.Unbind();
// Validate that AudioRenderer2 persists without GainControl2.
gain_control_2.Unbind();
// ...give the two interfaces a chance to completely unbind...
EXPECT_FALSE(RunLoopWithTimeoutOrUntil(
[this, &ar2_error_occurred, &gc2_error_occurred]() {
return (error_occurred_ || ar2_error_occurred || gc2_error_occurred);
},
kDurationTimeoutExpected * 2));
// Explicitly unbinding audio_renderer_ should not trigger its disconnect
// (error_occurred_), but should trigger gain_control_'s disconnect.
EXPECT_TRUE(gc_error_occurred);
EXPECT_FALSE(gain_control_.is_bound());
// Explicitly unbinding gain_control_2 should not trigger its disconnect, nor
// its parent audio_renderer_2's.
EXPECT_FALSE(ar2_error_occurred);
EXPECT_FALSE(gc2_error_occurred);
EXPECT_TRUE(audio_renderer_2.is_bound());
}
//
// SetStreamType is not yet implemented. We expect the AudioRenderer binding to
// disconnect, and our AudioRenderer interface ptr to be reset.
TEST_F(AudioRendererTest, SetStreamType) {
SetNegativeExpectations();
fuchsia::media::AudioStreamType stream_format;
stream_format.sample_format = fuchsia::media::AudioSampleFormat::SIGNED_16;
stream_format.channels = 1;
stream_format.frames_per_second = 8000;
fuchsia::media::StreamType stream_type;
stream_type.encoding = fuchsia::media::AUDIO_ENCODING_LPCM;
stream_type.medium_specific.set_audio(stream_format);
audio_renderer_->SetStreamType(std::move(stream_type));
// Binding should Disconnect (EXPECT loop to NOT timeout)
ExpectDisconnect();
}
// Before setting format, Play should not succeed.
TEST_F(AudioRendererTest, PlayWithoutFormat) {
SetNegativeExpectations();
int64_t ref_time_received = -1;
int64_t media_time_received = -1;
audio_renderer_->Play(fuchsia::media::NO_TIMESTAMP,
fuchsia::media::NO_TIMESTAMP,
[&ref_time_received, &media_time_received](
int64_t ref_time, int64_t media_time) {
ref_time_received = ref_time;
media_time_received = media_time;
});
// Disconnect callback should be received
ExpectDisconnect();
EXPECT_EQ(ref_time_received, -1);
EXPECT_EQ(media_time_received, -1);
}
// After setting format but before submitting buffers, Play should not succeed.
TEST_F(AudioRendererTest, PlayWithoutBuffers) {
SetNegativeExpectations();
fuchsia::media::AudioStreamType format;
format.sample_format = fuchsia::media::AudioSampleFormat::FLOAT;
format.channels = 1;
format.frames_per_second = 32000;
audio_renderer_->SetPcmStreamType(format);
int64_t ref_time_received = -1;
int64_t media_time_received = -1;
audio_renderer_->Play(fuchsia::media::NO_TIMESTAMP,
fuchsia::media::NO_TIMESTAMP,
[&ref_time_received, &media_time_received](
int64_t ref_time, int64_t media_time) {
ref_time_received = ref_time;
media_time_received = media_time;
});
// Disconnect callback should be received
ExpectDisconnect();
EXPECT_EQ(ref_time_received, -1);
EXPECT_EQ(media_time_received, -1);
}
// Before setting format, PlayNoReply should cause a Disconnect.
TEST_F(AudioRendererTest, PlayNoReplyWithoutFormat) {
SetNegativeExpectations();
audio_renderer_->PlayNoReply(fuchsia::media::NO_TIMESTAMP,
fuchsia::media::NO_TIMESTAMP);
// Disconnect callback should be received.
ExpectDisconnect();
}
// Before setting format, Pause should not succeed.
TEST_F(AudioRendererTest, PauseWithoutFormat) {
SetNegativeExpectations();
int64_t ref_time_received = -1;
int64_t media_time_received = -1;
audio_renderer_->Pause([&ref_time_received, &media_time_received](
int64_t ref_time, int64_t media_time) {
ref_time_received = ref_time;
media_time_received = media_time;
});
// Disconnect callback should be received
ExpectDisconnect();
EXPECT_EQ(ref_time_received, -1);
EXPECT_EQ(media_time_received, -1);
}
// After setting format but before submitting buffers, Pause should not succeed.
TEST_F(AudioRendererTest, PauseWithoutBuffers) {
SetNegativeExpectations();
fuchsia::media::AudioStreamType format;
format.sample_format = fuchsia::media::AudioSampleFormat::FLOAT;
format.channels = 1;
format.frames_per_second = 32000;
audio_renderer_->SetPcmStreamType(format);
int64_t ref_time_received = -1;
int64_t media_time_received = -1;
audio_renderer_->Pause([&ref_time_received, &media_time_received](
int64_t ref_time, int64_t media_time) {
ref_time_received = ref_time;
media_time_received = media_time;
});
// Disconnect callback should be received
ExpectDisconnect();
EXPECT_EQ(ref_time_received, -1);
EXPECT_EQ(media_time_received, -1);
}
// Before setting format, PauseNoReply should cause a Disconnect.
TEST_F(AudioRendererTest, PauseNoReplyWithoutFormat) {
SetNegativeExpectations();
audio_renderer_->PauseNoReply();
// Disconnect callback should be received.
ExpectDisconnect();
}
} // namespace media::audio::test