blob: 53b2bb05e03c5e44855738c9992006b37e7f73be [file] [log] [blame]
// Copyright 2022 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/silence_padding_stream.h"
#include <deque>
#include <ffl/string.h>
#include <gtest/gtest.h>
#include "src/media/audio/audio_core/mixer/intersect.h"
#include "src/media/audio/audio_core/testing/packet_factory.h"
#include "src/media/audio/lib/clock/clone_mono.h"
using ASF = fuchsia::media::AudioSampleFormat;
namespace media::audio {
namespace {
// Used when the ReadLockContext is unused by the test.
static media::audio::ReadableStream::ReadLockContext rlctx;
class FakeStream : public ReadableStream {
public:
struct QueuedBuffer {
Fixed start;
Fixed end;
void* payload;
StreamUsageMask usage;
float gain;
};
explicit FakeStream(std::vector<QueuedBuffer>&& buffers)
: ReadableStream("FakeStream", Format::Create<ASF::SIGNED_16>(1, 48000).take_value()),
buffers_(std::move(buffers)),
audio_clock_(AudioClock::ClientFixed(audio::clock::CloneOfMonotonic())) {}
TimelineFunctionSnapshot ref_time_to_frac_presentation_frame() const override { return {}; }
AudioClock& reference_clock() override { return audio_clock_; }
std::optional<Buffer> ReadLockImpl(ReadLockContext& ctx, Fixed dest_frame,
int64_t frame_count) override {
if (buffers_.empty()) {
return std::nullopt;
}
auto& next_buffer = buffers_.front();
Fixed length = next_buffer.end - next_buffer.start;
FX_CHECK(length.Fraction() == Fixed(0));
auto packet = mixer::Packet{
.start = next_buffer.start,
.length = length.Floor(),
.payload = next_buffer.payload,
};
auto isect = IntersectPacket(format(), packet, dest_frame, frame_count);
if (!isect) {
return std::nullopt;
}
// When caching, the start dest_frame must intersect the request, but we can cache
// an arbitrary number of frames in the future. See comments for MakeCachedBuffer.
Fixed packet_end = packet.start + Fixed(packet.length);
isect->length = Fixed(packet_end - isect->start).Floor();
return MakeCachedBuffer(isect->start, isect->length, isect->payload, next_buffer.usage,
next_buffer.gain);
}
void TrimImpl(Fixed dest_frame) override {
// Free old buffers.
while (!buffers_.empty() && buffers_[0].end <= dest_frame) {
buffers_.erase(buffers_.begin());
}
}
private:
std::vector<QueuedBuffer> buffers_;
AudioClock audio_clock_;
};
void ExpectNullBuffer(const std::optional<ReadableStream::Buffer>& buffer) {
EXPECT_FALSE(buffer) << ffl::String::DecRational << "start=" << buffer->start()
<< " end=" << buffer->end();
}
void ExpectBuffer(const std::optional<ReadableStream::Buffer>& buffer, Fixed want_start,
Fixed want_end, int16_t want_sample, StreamUsageMask want_usage,
float want_gain) {
ASSERT_TRUE(buffer);
EXPECT_EQ(want_sample, reinterpret_cast<int16_t*>(buffer->payload())[0]);
EXPECT_EQ(want_start, buffer->start());
EXPECT_EQ(want_end, buffer->end());
EXPECT_EQ(want_usage, buffer->usage_mask());
EXPECT_EQ(want_gain, buffer->total_applied_gain_db());
}
} // namespace
TEST(SilencePaddingStreamTest, EmptySource) {
auto source = std::make_shared<FakeStream>(std::vector<FakeStream::QueuedBuffer>());
auto stream =
SilencePaddingStream::Create(source, Fixed(10), /*fractional_gaps_round_down=*/true);
// Since the source is empty, the stream should be empty.
auto buffer = stream->ReadLock(rlctx, Fixed(0), 20);
ExpectNullBuffer(buffer);
}
TEST(SilencePaddingStreamTest, AfterOneBuffer) {
const int16_t expected_sample = 1;
const StreamUsageMask expected_usage({StreamUsage::WithRenderUsage(RenderUsage::MEDIA)});
const float expected_gain = -20;
std::vector<int16_t> source_payload(20, expected_sample);
auto source = std::make_shared<FakeStream>(std::vector<FakeStream::QueuedBuffer>{
{
.start = Fixed(0),
.end = Fixed(20),
.payload = &source_payload[0],
.usage = expected_usage,
.gain = expected_gain,
},
});
auto stream = SilencePaddingStream::Create(source, Fixed(5), /*fractional_gaps_round_down=*/true);
// Source buffer.
{
SCOPED_TRACE("ReadLock(0, 20)");
auto buffer = stream->ReadLock(rlctx, Fixed(0), 20);
ExpectBuffer(buffer, Fixed(0), Fixed(20), expected_sample, expected_usage, expected_gain);
}
// Silence.
{
SCOPED_TRACE("ReadLock(20, 10)");
auto buffer = stream->ReadLock(rlctx, Fixed(20), 10);
ExpectBuffer(buffer, Fixed(20), Fixed(25), 0, expected_usage, expected_gain);
}
// No further data.
{
SCOPED_TRACE("ReadLock(25, 10)");
auto buffer = stream->ReadLock(rlctx, Fixed(25), 10);
ExpectNullBuffer(buffer);
}
}
TEST(SilencePaddingStreamTest, AfterTwoBuffers) {
const int16_t expected_sample_1 = 1;
const StreamUsageMask expected_usage_1({StreamUsage::WithRenderUsage(RenderUsage::MEDIA)});
const float expected_gain_1 = -10;
std::vector<int16_t> source_payload_1(20, expected_sample_1);
const int16_t expected_sample_2 = 2;
const StreamUsageMask expected_usage_2({StreamUsage::WithRenderUsage(RenderUsage::BACKGROUND)});
const float expected_gain_2 = -20;
std::vector<int16_t> source_payload_2(20, expected_sample_2);
auto source = std::make_shared<FakeStream>(std::vector<FakeStream::QueuedBuffer>{
{
.start = Fixed(00),
.end = Fixed(20),
.payload = &source_payload_1[0],
.usage = expected_usage_1,
.gain = expected_gain_1,
},
{
.start = Fixed(20),
.end = Fixed(40),
.payload = &source_payload_2[0],
.usage = expected_usage_2,
.gain = expected_gain_2,
},
});
auto stream = SilencePaddingStream::Create(source, Fixed(5), /*fractional_gaps_round_down=*/true);
// First source buffer.
{
SCOPED_TRACE("ReadLock(0, 20)");
auto buffer = stream->ReadLock(rlctx, Fixed(0), 20);
ExpectBuffer(buffer, Fixed(0), Fixed(20), expected_sample_1, expected_usage_1, expected_gain_1);
}
// Second source buffer.
{
SCOPED_TRACE("ReadLock(20, 20)");
auto buffer = stream->ReadLock(rlctx, Fixed(20), 20);
ExpectBuffer(buffer, Fixed(20), Fixed(40), expected_sample_2, expected_usage_2,
expected_gain_2);
}
// Silence.
{
SCOPED_TRACE("ReadLock(40, 10)");
auto buffer = stream->ReadLock(rlctx, Fixed(40), 10);
ExpectBuffer(buffer, Fixed(40), Fixed(45), 0, expected_usage_2, expected_gain_2);
}
// No further data.
{
SCOPED_TRACE("ReadLock(45, 10)");
auto buffer = stream->ReadLock(rlctx, Fixed(45), 10);
ExpectNullBuffer(buffer);
}
}
TEST(SilencePaddingStreamTest, SkipBuffer) {
const int16_t expected_sample = 1;
const StreamUsageMask expected_usage({StreamUsage::WithRenderUsage(RenderUsage::MEDIA)});
const float expected_gain = -20;
std::vector<int16_t> source_payload(20, expected_sample);
auto source = std::make_shared<FakeStream>(std::vector<FakeStream::QueuedBuffer>{
{
.start = Fixed(0),
.end = Fixed(20),
.payload = &source_payload[0],
.usage = expected_usage,
.gain = expected_gain,
},
});
auto stream = SilencePaddingStream::Create(source, Fixed(5), /*fractional_gaps_round_down=*/true);
// If we completely skip over the source buffer, this is a discontinuity.
// There's no need to emit silence because there was no prior audio to "ring out".
{
SCOPED_TRACE("ReadLock(20, 10)");
auto buffer = stream->ReadLock(rlctx, Fixed(20), 10);
ExpectNullBuffer(buffer);
}
}
TEST(SilencePaddingStreamTest, GapBetweenBuffersLongerThanSilence) {
const int16_t expected_sample = 1;
const StreamUsageMask expected_usage({StreamUsage::WithRenderUsage(RenderUsage::MEDIA)});
const float expected_gain = -20;
std::vector<int16_t> source_payload(20, expected_sample);
auto source = std::make_shared<FakeStream>(std::vector<FakeStream::QueuedBuffer>{
{
.start = Fixed(10),
.end = Fixed(20),
.payload = &source_payload[0],
.usage = expected_usage,
.gain = expected_gain,
},
{
.start = Fixed(45),
.end = Fixed(55),
.payload = &source_payload[0],
.usage = expected_usage,
.gain = expected_gain,
},
});
auto stream = SilencePaddingStream::Create(source, Fixed(5), /*fractional_gaps_round_down=*/true);
// First source buffer.
{
SCOPED_TRACE("ReadLock(0, 20)");
auto buffer = stream->ReadLock(rlctx, Fixed(0), 20);
ExpectBuffer(buffer, Fixed(10), Fixed(20), expected_sample, expected_usage, expected_gain);
}
// First silence.
{
SCOPED_TRACE("ReadLock(20, 10)");
auto buffer = stream->ReadLock(rlctx, Fixed(20), 10);
ExpectBuffer(buffer, Fixed(20), Fixed(25), 0, expected_usage, expected_gain);
}
// Empty gap.
{
SCOPED_TRACE("ReadLock(25, 20)");
auto buffer = stream->ReadLock(rlctx, Fixed(25), 20);
ExpectNullBuffer(buffer);
}
// Second source buffer.
{
SCOPED_TRACE("ReadLock(45, 10)");
auto buffer = stream->ReadLock(rlctx, Fixed(45), 10);
ExpectBuffer(buffer, Fixed(45), Fixed(55), expected_sample, expected_usage, expected_gain);
}
// Second silence.
{
SCOPED_TRACE("ReadLock(55, 10)");
auto buffer = stream->ReadLock(rlctx, Fixed(55), 10);
ExpectBuffer(buffer, Fixed(55), Fixed(60), 0, expected_usage, expected_gain);
}
}
TEST(SilencePaddingStreamTest, GapBetweenBuffersShorterThanSilence) {
const int16_t expected_sample = 1;
const StreamUsageMask expected_usage({StreamUsage::WithRenderUsage(RenderUsage::MEDIA)});
const float expected_gain = -20;
std::vector<int16_t> source_payload(20, expected_sample);
auto source = std::make_shared<FakeStream>(std::vector<FakeStream::QueuedBuffer>{
{
.start = Fixed(10),
.end = Fixed(20),
.payload = &source_payload[0],
.usage = expected_usage,
.gain = expected_gain,
},
{
.start = Fixed(21),
.end = Fixed(31),
.payload = &source_payload[0],
.usage = expected_usage,
.gain = expected_gain,
},
});
auto stream = SilencePaddingStream::Create(source, Fixed(5), /*fractional_gaps_round_down=*/true);
// First source buffer.
{
SCOPED_TRACE("ReadLock(0, 20)");
auto buffer = stream->ReadLock(rlctx, Fixed(0), 20);
ExpectBuffer(buffer, Fixed(10), Fixed(20), expected_sample, expected_usage, expected_gain);
}
// First silence.
// Although we've configured 5 frames of silence, the second buffer starts
// after just one frame of silence, so we emit just one frame of silence.
{
SCOPED_TRACE("ReadLock(20, 20)");
auto buffer = stream->ReadLock(rlctx, Fixed(20), 20);
ExpectBuffer(buffer, Fixed(20), Fixed(21), 0, expected_usage, expected_gain);
}
// Second source buffer.
{
SCOPED_TRACE("ReadLock(21, 20)");
auto buffer = stream->ReadLock(rlctx, Fixed(21), 20);
ExpectBuffer(buffer, Fixed(21), Fixed(31), expected_sample, expected_usage, expected_gain);
}
}
TEST(SilencePaddingStreamTest, GapBetweenBuffersShorterThanSilenceAndFractionalRoundDown) {
const int16_t expected_sample = 1;
const StreamUsageMask expected_usage({StreamUsage::WithRenderUsage(RenderUsage::MEDIA)});
const float expected_gain = -20;
std::vector<int16_t> source_payload(20, expected_sample);
auto source = std::make_shared<FakeStream>(std::vector<FakeStream::QueuedBuffer>{
{
.start = Fixed(10),
.end = Fixed(20),
.payload = &source_payload[0],
.usage = expected_usage,
.gain = expected_gain,
},
{
.start = Fixed(21) + ffl::FromRatio(1, 2),
.end = Fixed(31) + ffl::FromRatio(1, 2),
.payload = &source_payload[0],
.usage = expected_usage,
.gain = expected_gain,
},
});
auto stream = SilencePaddingStream::Create(source, Fixed(5), /*fractional_gaps_round_down=*/true);
// First source buffer.
{
SCOPED_TRACE("ReadLock(0, 20)");
auto buffer = stream->ReadLock(rlctx, Fixed(0), 20);
ExpectBuffer(buffer, Fixed(10), Fixed(20), expected_sample, expected_usage, expected_gain);
}
// First silence.
// Although we've configured 5 frames of silence, the second buffer starts
// after just 1.5 frames of silence, so we round down to 1.0 frames of silence.
{
SCOPED_TRACE("ReadLock(20, 20)");
auto buffer = stream->ReadLock(rlctx, Fixed(20), 20);
ExpectBuffer(buffer, Fixed(20), Fixed(21), 0, expected_usage, expected_gain);
}
// Second source buffer.
{
SCOPED_TRACE("ReadLock(21, 20)");
auto buffer = stream->ReadLock(rlctx, Fixed(21), 20);
ExpectBuffer(buffer, Fixed(21) + ffl::FromRatio(1, 2), Fixed(31) + ffl::FromRatio(1, 2),
expected_sample, expected_usage, expected_gain);
}
}
TEST(SilencePaddingStreamTest, GapBetweenBuffersShorterThanSilenceAndFractionalRoundUp) {
const int16_t expected_sample = 1;
const StreamUsageMask expected_usage({StreamUsage::WithRenderUsage(RenderUsage::MEDIA)});
const float expected_gain = -20;
std::vector<int16_t> source_payload(20, expected_sample);
auto source = std::make_shared<FakeStream>(std::vector<FakeStream::QueuedBuffer>{
{
.start = Fixed(10),
.end = Fixed(20),
.payload = &source_payload[0],
.usage = expected_usage,
.gain = expected_gain,
},
{
.start = Fixed(21) + ffl::FromRatio(1, 2),
.end = Fixed(31) + ffl::FromRatio(1, 2),
.payload = &source_payload[0],
.usage = expected_usage,
.gain = expected_gain,
},
});
auto stream =
SilencePaddingStream::Create(source, Fixed(5), /*fractional_gaps_round_down=*/false);
// First source buffer.
{
SCOPED_TRACE("ReadLock(0, 20)");
auto buffer = stream->ReadLock(rlctx, Fixed(0), 20);
ExpectBuffer(buffer, Fixed(10), Fixed(20), expected_sample, expected_usage, expected_gain);
}
// First silence.
// Although we've configured 5 frames of silence, the second buffer starts
// after just 1.5 frames of silence, so we round up to 2.0 frames of silence.
{
SCOPED_TRACE("ReadLock(20, 20)");
auto buffer = stream->ReadLock(rlctx, Fixed(20), 20);
ExpectBuffer(buffer, Fixed(20), Fixed(22), 0, expected_usage, expected_gain);
}
// Second source buffer.
{
SCOPED_TRACE("ReadLock(22, 20)");
auto buffer = stream->ReadLock(rlctx, Fixed(22), 20);
ExpectBuffer(buffer, Fixed(21) + ffl::FromRatio(1, 2), Fixed(31) + ffl::FromRatio(1, 2),
expected_sample, expected_usage, expected_gain);
}
}
TEST(SilencePaddingStreamTest, GapBetweenBuffersLessThanOneFrameRoundDown) {
const int16_t expected_sample = 1;
const StreamUsageMask expected_usage({StreamUsage::WithRenderUsage(RenderUsage::MEDIA)});
const float expected_gain = -20;
std::vector<int16_t> source_payload(20, expected_sample);
auto source = std::make_shared<FakeStream>(std::vector<FakeStream::QueuedBuffer>{
{
.start = Fixed(10),
.end = Fixed(20),
.payload = &source_payload[0],
.usage = expected_usage,
.gain = expected_gain,
},
{
.start = Fixed(20) + ffl::FromRatio(1, 2),
.end = Fixed(30) + ffl::FromRatio(1, 2),
.payload = &source_payload[0],
.usage = expected_usage,
.gain = expected_gain,
},
});
auto stream = SilencePaddingStream::Create(source, Fixed(5), /*fractional_gaps_round_down=*/true);
// First source buffer.
{
SCOPED_TRACE("ReadLock(0, 20)");
auto buffer = stream->ReadLock(rlctx, Fixed(0), 20);
ExpectBuffer(buffer, Fixed(10), Fixed(20), expected_sample, expected_usage, expected_gain);
}
// Second source buffer.
// The gap between packets is smaller than one frame and we're rounding down,
// so don't emit silence.
{
SCOPED_TRACE("ReadLock(20, 20)");
auto buffer = stream->ReadLock(rlctx, Fixed(20), 20);
ExpectBuffer(buffer, Fixed(20) + ffl::FromRatio(5, 10), Fixed(30) + ffl::FromRatio(5, 10),
expected_sample, expected_usage, expected_gain);
}
}
TEST(SilencePaddingStreamTest, GapBetweenBuffersLessThanOneFrameRoundUp) {
const int16_t expected_sample = 1;
const StreamUsageMask expected_usage({StreamUsage::WithRenderUsage(RenderUsage::MEDIA)});
const float expected_gain = -20;
std::vector<int16_t> source_payload(20, expected_sample);
auto source = std::make_shared<FakeStream>(std::vector<FakeStream::QueuedBuffer>{
{
.start = Fixed(10),
.end = Fixed(20),
.payload = &source_payload[0],
.usage = expected_usage,
.gain = expected_gain,
},
{
.start = Fixed(20) + ffl::FromRatio(1, 2),
.end = Fixed(30) + ffl::FromRatio(1, 2),
.payload = &source_payload[0],
.usage = expected_usage,
.gain = expected_gain,
},
});
auto stream =
SilencePaddingStream::Create(source, Fixed(5), /*fractional_gaps_round_down=*/false);
// First source buffer.
{
SCOPED_TRACE("ReadLock(0, 20)");
auto buffer = stream->ReadLock(rlctx, Fixed(0), 20);
ExpectBuffer(buffer, Fixed(10), Fixed(20), expected_sample, expected_usage, expected_gain);
}
// Second source buffer.
// The gap between packets is smaller than one frame, but we're rounding up,
// so emit one frame of silence.
{
SCOPED_TRACE("ReadLock(20, 20)");
auto buffer = stream->ReadLock(rlctx, Fixed(20), 20);
ExpectBuffer(buffer, Fixed(20), Fixed(21), 0, expected_usage, expected_gain);
}
// Now read the second source buffer.
{
SCOPED_TRACE("ReadLock(21, 20)");
auto buffer = stream->ReadLock(rlctx, Fixed(21), 20);
ExpectBuffer(buffer, Fixed(20) + ffl::FromRatio(5, 10), Fixed(30) + ffl::FromRatio(5, 10),
expected_sample, expected_usage, expected_gain);
}
}
TEST(SilencePaddingStreamTest, CreateRoundsUpNumberOfFrames) {
const int16_t expected_sample = 1;
const StreamUsageMask expected_usage({StreamUsage::WithRenderUsage(RenderUsage::MEDIA)});
const float expected_gain = -20;
std::vector<int16_t> source_payload(20, expected_sample);
auto source = std::make_shared<FakeStream>(std::vector<FakeStream::QueuedBuffer>{
{
.start = Fixed(10),
.end = Fixed(20),
.payload = &source_payload[0],
.usage = expected_usage,
.gain = expected_gain,
},
});
auto stream = SilencePaddingStream::Create(source, ffl::FromRatio(1, 2),
/*fractional_gaps_round_down=*/true);
// Source buffer.
{
SCOPED_TRACE("ReadLock(0, 20)");
auto buffer = stream->ReadLock(rlctx, Fixed(0), 20);
ExpectBuffer(buffer, Fixed(10), Fixed(20), expected_sample, expected_usage, expected_gain);
}
// We asked for 0.5 frames of silence, but should get 1.0 frames.
{
SCOPED_TRACE("ReadLock(20, 10)");
auto buffer = stream->ReadLock(rlctx, Fixed(20), 10);
ExpectBuffer(buffer, Fixed(20), Fixed(21), 0, expected_usage, expected_gain);
}
// No more data.
{
SCOPED_TRACE("ReadLock(21, 10)");
auto buffer = stream->ReadLock(rlctx, Fixed(21), 10);
ExpectNullBuffer(buffer);
}
}
} // namespace media::audio