blob: 555a44a98bbfc8f6ba9bc26e60677457cc806ab1 [file] [log] [blame]
// Copyright 2023 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 <zircon/types.h>
#include <cmath>
#include <cstdint>
#include "src/media/audio/audio_core/test/api/audio_renderer_test_shared.h"
#include "src/media/audio/lib/clock/clone_mono.h"
namespace media::audio::test {
using AudioRenderUsage = fuchsia::media::AudioRenderUsage;
class AudioRendererBufferErrorTest : public AudioRendererBufferTest {};
// It is invalid to add a payload buffer with a duplicate id.
TEST_F(AudioRendererBufferErrorTest, AddPayloadBufferDuplicateId) {
CreateAndAddPayloadBuffer(0);
CreateAndAddPayloadBuffer(0);
ExpectDisconnect(audio_renderer());
}
// It is invalid to add a payload buffer while there are queued packets.
// Attempt to add new payload buffer while the packet is in flight. This should fail.
TEST_F(AudioRendererBufferErrorTest, AddPayloadBufferWhileOperatingShouldDisconnect) {
CreateAndAddPayloadBuffer(0);
audio_renderer()->SetPcmStreamType(kTestStreamType);
audio_renderer()->SendPacketNoReply(kTestPacket);
CreateAndAddPayloadBuffer(1);
ExpectDisconnect(audio_renderer());
}
// It is invalid to remove a payload buffer while there are queued packets.
TEST_F(AudioRendererBufferErrorTest, RemovePayloadBufferWhileOperatingShouldDisconnect) {
CreateAndAddPayloadBuffer(0);
audio_renderer()->SetPcmStreamType(kTestStreamType);
ExpectConnected(); // ensure that if/when we disconnect, it is not because of the above
audio_renderer()->SendPacketNoReply(kTestPacket);
audio_renderer()->RemovePayloadBuffer(0);
ExpectDisconnect(audio_renderer());
}
// Test RemovePayloadBuffer with an invalid ID (no corresponding AddPayloadBuffer).
TEST_F(AudioRendererBufferErrorTest, RemovePayloadBufferInvalidBufferIdShouldDisconnect) {
audio_renderer()->RemovePayloadBuffer(0);
ExpectDisconnect(audio_renderer());
}
class AudioRendererPacketErrorTest : public AudioRendererPacketTest {};
TEST_F(AudioRendererPacketErrorTest, SendPacketTooManyShouldDisconnect) {
audio_renderer()->SetPcmStreamType(kTestStreamType);
CreateAndAddPayloadBuffer(0);
// The exact limit is a function of the size of some internal data structures. We verify this
// limit is somewhere between 500 and 600 packets.
for (int i = 0; i < 500; ++i) {
audio_renderer()->SendPacket(kTestPacket, []() {});
}
ExpectConnectedAndDiscardAllPackets();
for (int i = 0; i < 600; ++i) {
audio_renderer()->SendPacket(kTestPacket, []() {});
}
ExpectDisconnect(audio_renderer());
}
// SendPacket cannot be called before the stream type has been configured (SetPcmStreamType).
TEST_F(AudioRendererPacketErrorTest, SendPacketWithoutFormatShouldDisconnect) {
// Add a payload buffer but no stream type.
CreateAndAddPayloadBuffer(0);
// SendPacket should trigger a disconnect due to a lack of a configured stream type.
audio_renderer()->SendPacket(kTestPacket, AddCallback("SendPacket"));
ExpectDisconnect(audio_renderer());
}
// SendPacket cannot be called before the payload buffer has been added.
TEST_F(AudioRendererPacketErrorTest, SendPacketWithoutBufferShouldDisconnect) {
// Add a stream type but no payload buffer.
audio_renderer()->SetPcmStreamType(kTestStreamType);
// SendPacket should trigger a disconnect due to a lack of a configured stream type.
audio_renderer()->SendPacket(kTestPacket, AddCallback("SendPacket"));
ExpectDisconnect(audio_renderer());
}
// SendPacket with an unknown |payload_buffer_id|
TEST_F(AudioRendererPacketErrorTest, SendPacketInvalidPayloadBufferIdShouldDisconnect) {
CreateAndAddPayloadBuffer(0);
audio_renderer()->SetPcmStreamType(kTestStreamType);
// We never added a payload buffer with this ID, so this should cause a disconnect
auto packet = kTestPacket;
packet.payload_buffer_id = 1234;
audio_renderer()->SendPacket(std::move(packet), []() {});
ExpectDisconnect(audio_renderer());
}
// SendPacket with a |payload_size| that is invalid
TEST_F(AudioRendererPacketErrorTest, SendPacketInvalidPayloadBufferSizeShouldDisconnect) {
// kTestStreamType frames are 8 bytes (float32 x Stereo).
// As an invalid packet size, we specify a value (9) that is NOT a perfect multiple of 8.
constexpr uint64_t kInvalidPayloadSize = sizeof(float) * kTestStreamType.channels + 1;
audio_renderer()->SetPcmStreamType(kTestStreamType);
CreateAndAddPayloadBuffer(0);
auto packet = kTestPacket;
packet.payload_size = kInvalidPayloadSize;
audio_renderer()->SendPacket(std::move(packet), []() {});
ExpectDisconnect(audio_renderer());
}
// |payload_offset| starts beyond the end of the payload buffer.
TEST_F(AudioRendererPacketErrorTest, SendPacketBufferOutOfBoundsShouldDisconnect) {
CreateAndAddPayloadBuffer(0);
audio_renderer()->SetPcmStreamType(kTestStreamType);
auto packet = kTestPacket;
packet.payload_offset = DefaultPayloadBufferSize();
audio_renderer()->SendPacket(std::move(packet), []() {});
ExpectDisconnect(audio_renderer());
}
// |payload_offset| + |payload_size| extends beyond the end of the payload buffer.
TEST_F(AudioRendererPacketErrorTest, SendPacketBufferOverrunShouldDisconnect) {
audio_renderer()->SetPcmStreamType(kTestStreamType);
CreateAndAddPayloadBuffer(0);
auto packet = kTestPacket;
packet.payload_size = kDefaultPacketSize * 2;
packet.payload_offset = DefaultPayloadBufferSize() - kDefaultPacketSize;
audio_renderer()->SendPacket(std::move(packet), []() {});
ExpectDisconnect(audio_renderer());
}
class AudioRendererPtsLeadTimeErrorTest : public AudioRendererPtsLeadTimeTest {};
// SetPtsUnits accepts uint numerator and denominator that must be within certain range
//
// Numerator cannot be zero
TEST_F(AudioRendererPtsLeadTimeErrorTest, SetPtsUnitsZeroNumeratorShouldDisconnect) {
audio_renderer()->SetPtsUnits(0, 1);
ExpectDisconnect(audio_renderer());
}
// There cannot be more than one PTS tick per nanosecond. We use ratio 1e9/1 + epsilon to test this
// limit. The smallest such epsilon we can encode in uint32 / uint32 is (4e9+1)/4, where epsilon =
// 1/4. The next smallest (5e9+1)/5 cannot be encoded because 5e9+1 exceeds MAX_UINT32.
TEST_F(AudioRendererPtsLeadTimeErrorTest, SetPtsUnitsTooHighShouldDisconnect) {
// This value equates to 0.99999999975 nanoseconds.
audio_renderer()->SetPtsUnits(4'000'000'001, 4);
ExpectDisconnect(audio_renderer());
}
// Denominator cannot be zero
TEST_F(AudioRendererPtsLeadTimeErrorTest, SetPtsUnitsZeroDenominatorShouldDisconnect) {
audio_renderer()->SetPtsUnits(1000, 0);
ExpectDisconnect(audio_renderer());
}
// There must be at least one PTS tick per minute. We test this limit with ratio 1/60 - epsilon.
// To compute the smallest epsilon that can be encoded in uint32 / uint32, we find the largest X
// and Y such that X/Y = 1/60, then use a ratio of X/(Y+1).
// floor(2^32/60) = 71582788, so we use the ratio 71582788 / (4294967280+1).
TEST_F(AudioRendererPtsLeadTimeErrorTest, SetPtsUnitsTooLowShouldDisconnect) {
// This value equates to 60.000000013969839 seconds.
audio_renderer()->SetPtsUnits(71582788, 4294967281);
ExpectDisconnect(audio_renderer());
}
TEST_F(AudioRendererPtsLeadTimeErrorTest, SetPtsUnitsWhileOperatingShouldDisconnect) {
CreateAndAddPayloadBuffer(0);
audio_renderer()->SetPcmStreamType(kTestStreamType);
audio_renderer()->SendPacketNoReply(kTestPacket);
audio_renderer()->SetPtsUnits(kTestStreamType.frames_per_second, 1);
ExpectDisconnect(audio_renderer());
}
// If active packets are outstanding, calling SetPtsContinuityThreshold will cause a disconnect
TEST_F(AudioRendererPtsLeadTimeErrorTest, SetPtsContThresholdWhileOperatingCausesDisconnect) {
CreateAndAddPayloadBuffer(0);
audio_renderer()->SetPcmStreamType(kTestStreamType);
audio_renderer()->SendPacketNoReply(kTestPacket);
audio_renderer()->SetPtsContinuityThreshold(0.01f);
ExpectDisconnect(audio_renderer());
}
// SetPtsContinuityThreshold parameter must be non-negative
TEST_F(AudioRendererPtsLeadTimeErrorTest, SetPtsContThresholdNegativeValueCausesDisconnect) {
audio_renderer()->SetPtsContinuityThreshold(-0.01f);
ExpectDisconnect(audio_renderer());
}
// SetPtsContinuityThreshold parameter must be a normal number
TEST_F(AudioRendererPtsLeadTimeErrorTest, SetPtsContThresholdNanCausesDisconnect) {
audio_renderer()->SetPtsContinuityThreshold(NAN);
ExpectDisconnect(audio_renderer());
}
// SetPtsContinuityThreshold parameter must be a finite number
TEST_F(AudioRendererPtsLeadTimeErrorTest, SetPtsContThresholdInfinityCausesDisconnect) {
audio_renderer()->SetPtsContinuityThreshold(INFINITY);
ExpectDisconnect(audio_renderer());
}
// SetPtsContinuityThreshold parameter must be a number within the finite range
TEST_F(AudioRendererPtsLeadTimeErrorTest, SetPtsContThresholdHugeValCausesDisconnect) {
audio_renderer()->SetPtsContinuityThreshold(HUGE_VALF);
ExpectDisconnect(audio_renderer());
}
// SetPtsContinuityThreshold parameter must be a normal (not sub-normal) number
TEST_F(AudioRendererPtsLeadTimeErrorTest, SetPtsContThresholdSubNormalValCausesDisconnect) {
audio_renderer()->SetPtsContinuityThreshold(FLT_MIN / 2);
ExpectDisconnect(audio_renderer());
}
class AudioRendererClockErrorTest : public AudioRendererClockTest {};
// inadequate ZX_RIGHTS (no DUPLICATE) should cause GetReferenceClock to fail.
TEST_F(AudioRendererClockErrorTest, SetRefClockWithoutDuplicateShouldDisconnect) {
zx::clock dupe_clock, orig_clock = clock::CloneOfMonotonic();
ASSERT_EQ(orig_clock.duplicate(kClockRights & ~ZX_RIGHT_DUPLICATE, &dupe_clock), ZX_OK);
audio_renderer()->SetReferenceClock(std::move(dupe_clock));
ExpectDisconnect(audio_renderer());
}
// inadequate ZX_RIGHTS (no READ) should cause GetReferenceClock to fail.
TEST_F(AudioRendererClockErrorTest, SetRefClockWithoutReadShouldDisconnect) {
zx::clock dupe_clock, orig_clock = clock::CloneOfMonotonic();
ASSERT_EQ(orig_clock.duplicate(kClockRights & ~ZX_RIGHT_READ, &dupe_clock), ZX_OK);
audio_renderer()->SetReferenceClock(std::move(dupe_clock));
ExpectDisconnect(audio_renderer());
}
// Regardless of the type of clock, calling SetReferenceClock a second time should fail.
// Set a custom clock, then try to select the audio_core supplied 'flexible' clock.
TEST_F(AudioRendererClockErrorTest, SetRefClockCustomThenFlexibleShouldDisconnect) {
audio_renderer()->SetReferenceClock(clock::AdjustableCloneOfMonotonic());
audio_renderer()->SetReferenceClock(zx::clock(ZX_HANDLE_INVALID));
ExpectDisconnect(audio_renderer());
}
// Regardless of the type of clock, calling SetReferenceClock a second time should fail.
// Select the audio_core supplied 'flexible' clock, then try to set a custom clock.
TEST_F(AudioRendererClockErrorTest, SetRefClockFlexibleThenCustomShouldDisconnect) {
audio_renderer()->SetReferenceClock(zx::clock(ZX_HANDLE_INVALID));
audio_renderer()->SetReferenceClock(clock::AdjustableCloneOfMonotonic());
ExpectDisconnect(audio_renderer());
}
// Regardless of the type of clock, calling SetReferenceClock a second time should fail.
// Set a custom clock, then try to set a different custom clock.
TEST_F(AudioRendererClockErrorTest, SetRefClockSecondCustomShouldDisconnect) {
audio_renderer()->SetReferenceClock(clock::AdjustableCloneOfMonotonic());
audio_renderer()->SetReferenceClock(clock::AdjustableCloneOfMonotonic());
ExpectDisconnect(audio_renderer());
}
// Regardless of the type of clock, calling SetReferenceClock a second time should fail.
// Select the audio_core supplied 'flexible' clock, then make the same call a second time.
TEST_F(AudioRendererClockErrorTest, SetRefClockSecondFlexibleShouldDisconnect) {
audio_renderer()->SetReferenceClock(zx::clock(ZX_HANDLE_INVALID));
audio_renderer()->SetReferenceClock(zx::clock(ZX_HANDLE_INVALID));
ExpectDisconnect(audio_renderer());
}
// Setting the reference clock at any time afterSetPcmStreamType should fail
TEST_F(AudioRendererClockErrorTest, SetRefClockAfterSetFormatShouldDisconnect) {
audio_renderer()->SetPcmStreamType(kTestStreamType);
audio_renderer()->SetReferenceClock(clock::CloneOfMonotonic());
ExpectDisconnect(audio_renderer());
}
// Once the format is set, setting a ref clock should fail even if post-Pause with no packets.
TEST_F(AudioRendererClockErrorTest, SetRefClockAfterPacketShouldDisconnect) {
CreateAndAddPayloadBuffer(0);
audio_renderer()->SetPcmStreamType(kTestStreamType);
audio_renderer()->SendPacketNoReply(kTestPacket);
audio_renderer()->PlayNoReply(fuchsia::media::NO_TIMESTAMP, fuchsia::media::NO_TIMESTAMP);
audio_renderer()->Pause(AddCallback("Pause"));
ExpectCallbacks();
audio_renderer()->DiscardAllPackets(AddCallback("DiscardAllPackets"));
ExpectCallbacks();
audio_renderer()->SetReferenceClock(clock::AdjustableCloneOfMonotonic());
ExpectDisconnect(audio_renderer());
}
class AudioRendererFormatUsageErrorTest : public AudioRendererFormatUsageTest {};
// Once the format has been set, SetUsage may no longer be called any time thereafter.
TEST_F(AudioRendererFormatUsageErrorTest, SetUsageAfterFormatShouldDisconnect) {
audio_renderer()->SetPcmStreamType(kTestStreamType);
audio_renderer()->SetUsage(AudioRenderUsage::COMMUNICATION);
ExpectDisconnect(audio_renderer());
}
// ... this restriction is not lifted even after all packets have been returned.
TEST_F(AudioRendererFormatUsageErrorTest, SetUsageAfterOperatingShouldDisconnect) {
audio_renderer()->SetPcmStreamType(kTestStreamType);
CreateAndAddPayloadBuffer(0);
audio_renderer()->PlayNoReply(fuchsia::media::NO_TIMESTAMP, 0);
audio_renderer()->SendPacket(kTestPacket, AddCallback("SendPacket"));
ExpectCallbacks(); // Send a packet and allow it to drain out.
audio_renderer()->Pause(AddCallback("Pause"));
ExpectCallbacks();
audio_renderer()->SetUsage(AudioRenderUsage::BACKGROUND);
ExpectDisconnect(audio_renderer());
}
TEST_F(AudioRendererFormatUsageErrorTest, SetPcmStreamTypeWhileOperatingShouldDisconnect) {
audio_renderer()->SetPcmStreamType(kTestStreamType);
CreateAndAddPayloadBuffer(0);
audio_renderer()->SendPacketNoReply(kTestPacket);
audio_renderer()->SetPcmStreamType({
.sample_format = fuchsia::media::AudioSampleFormat::UNSIGNED_8,
.channels = 1,
.frames_per_second = 44100,
});
ExpectDisconnect(audio_renderer());
}
class AudioRendererTransportErrorTest : public AudioRendererTransportTest {};
// Without a format, Play should not succeed.
TEST_F(AudioRendererTransportErrorTest, PlayWithoutFormatShouldDisconnect) {
CreateAndAddPayloadBuffer(0);
audio_renderer()->Play(fuchsia::media::NO_TIMESTAMP, fuchsia::media::NO_TIMESTAMP,
AddUnexpectedCallback("Play"));
zx_nanosleep(zx_deadline_after(ZX_MSEC(100)));
ExpectDisconnect(audio_renderer());
}
// Without a payload buffer, Play should not succeed.
TEST_F(AudioRendererTransportErrorTest, PlayWithoutBufferShouldDisconnect) {
audio_renderer()->SetPcmStreamType({
.sample_format = fuchsia::media::AudioSampleFormat::FLOAT,
.channels = 1,
.frames_per_second = 32000,
});
audio_renderer()->Play(fuchsia::media::NO_TIMESTAMP, fuchsia::media::NO_TIMESTAMP,
AddUnexpectedCallback("Play"));
zx_nanosleep(zx_deadline_after(ZX_MSEC(100)));
ExpectDisconnect(audio_renderer());
}
TEST_F(AudioRendererTransportErrorTest, PlayWithLargeReferenceTimeShouldDisconnect) {
CreateAndAddPayloadBuffer(0);
audio_renderer()->SetPcmStreamType(kTestStreamType);
audio_renderer()->SendPacket(kTestPacket, AddCallback("SendPacket"));
constexpr int64_t kLargeTimestamp = std::numeric_limits<int64_t>::max() - 1;
audio_renderer()->Play(kLargeTimestamp, fuchsia::media::NO_TIMESTAMP,
AddUnexpectedCallback("Play"));
ExpectDisconnect(audio_renderer());
}
TEST_F(AudioRendererTransportErrorTest, PlayWithLargeMediaTimeShouldDisconnect) {
audio_renderer()->SetPcmStreamType(kTestStreamType);
CreateAndAddPayloadBuffer(0);
// Use 1 tick per 2 frames to overflow the translation from PTS -> frames.
audio_renderer()->SetPtsUnits(kTestStreamType.frames_per_second / 2, 1);
audio_renderer()->SendPacket(kTestPacket, AddCallback("SendPacket"));
constexpr int64_t kLargeTimestamp = std::numeric_limits<int64_t>::max() / 2 + 1;
audio_renderer()->Play(fuchsia::media::NO_TIMESTAMP, kLargeTimestamp,
AddUnexpectedCallback("Play"));
ExpectDisconnect(audio_renderer());
}
TEST_F(AudioRendererTransportErrorTest, PlayWithLargeNegativeMediaTimeShouldDisconnect) {
CreateAndAddPayloadBuffer(0);
audio_renderer()->SetPcmStreamType(kTestStreamType);
// Use 1 tick per 2 frames to overflow the translation from PTS -> frames.
audio_renderer()->SetPtsUnits(kTestStreamType.frames_per_second / 2, 1);
audio_renderer()->SendPacket(kTestPacket, AddCallback("SendPacket"));
constexpr int64_t kLargeTimestamp = std::numeric_limits<int64_t>::min() + 1;
audio_renderer()->Play(fuchsia::media::NO_TIMESTAMP, kLargeTimestamp,
AddUnexpectedCallback("Play"));
ExpectDisconnect(audio_renderer());
}
// Without a format, Pause should not succeed.
TEST_F(AudioRendererTransportErrorTest, PauseWithoutFormatShouldDisconnect) {
CreateAndAddPayloadBuffer(0);
audio_renderer()->Pause(AddUnexpectedCallback("Pause"));
zx_nanosleep(zx_deadline_after(ZX_MSEC(100)));
ExpectDisconnect(audio_renderer());
}
// Without a payload buffer, Pause should not succeed.
TEST_F(AudioRendererTransportErrorTest, PauseWithoutBufferShouldDisconnect) {
audio_renderer()->SetPcmStreamType(kTestStreamType);
audio_renderer()->Pause(AddUnexpectedCallback("Pause"));
zx_nanosleep(zx_deadline_after(ZX_MSEC(100)));
ExpectDisconnect(audio_renderer());
}
} // namespace media::audio::test