blob: 8b9373abb92672b13b32280aaf3f1c2602470f06 [file] [log] [blame]
// Copyright 2019 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/packet_queue.h"
#include <lib/gtest/test_loop_fixture.h>
#include <unordered_map>
#include <fbl/ref_ptr.h>
#include "src/lib/syslog/cpp/logger.h"
namespace media::audio {
namespace {
class PacketQueueTest : public gtest::TestLoopFixture {
protected:
std::unique_ptr<PacketQueue> CreatePacketQueue() {
// Use a simple transform of one frame per millisecond to make validations simple in the test
// (ex: frame 1 will be consumed after 1ms).
auto one_frame_per_ms = fbl::MakeRefCounted<VersionedTimelineFunction>(
TimelineFunction(TimelineRate(FractionalFrames<uint32_t>(1).raw_value(), 1'000'000)));
return std::make_unique<PacketQueue>(
Format::Create({
.sample_format = fuchsia::media::AudioSampleFormat::FLOAT,
.channels = 2,
.frames_per_second = 48000,
})
.take_value(),
std::move(one_frame_per_ms));
}
fbl::RefPtr<Packet> CreatePacket(
uint32_t payload_buffer_id, FractionalFrames<int64_t> start = FractionalFrames<int64_t>(0),
FractionalFrames<uint32_t> length = FractionalFrames<uint32_t>(0),
bool* release_flag = nullptr) {
if (release_flag) {
*release_flag = false;
}
auto it = payload_buffers_.find(payload_buffer_id);
if (it == payload_buffers_.end()) {
auto vmo_mapper = fbl::MakeRefCounted<RefCountedVmoMapper>();
zx_status_t res = vmo_mapper->CreateAndMap(PAGE_SIZE, ZX_VM_PERM_READ | ZX_VM_PERM_WRITE);
if (res != ZX_OK) {
FX_PLOGS(ERROR, res) << "Failed to map payload buffer";
return nullptr;
}
auto result = payload_buffers_.emplace(payload_buffer_id, std::move(vmo_mapper));
FX_CHECK(result.second);
it = result.first;
}
auto callback = [this, release_flag] {
++released_packet_count_;
if (release_flag) {
*release_flag = true;
}
};
return allocator_.New(it->second, 0, length, start, dispatcher(), callback);
}
size_t released_packet_count() const { return released_packet_count_; }
zx::time time_after(zx::duration duration) { return zx::time(duration.to_nsecs()); }
private:
Packet::Allocator allocator_{1, true};
size_t released_packet_count_ = 0;
std::unordered_map<uint32_t, fbl::RefPtr<RefCountedVmoMapper>> payload_buffers_;
};
TEST_F(PacketQueueTest, PushPacket) {
auto packet_queue = CreatePacketQueue();
// Enqueue a packet.
ASSERT_TRUE(packet_queue->empty());
packet_queue->PushPacket(CreatePacket(0));
ASSERT_FALSE(packet_queue->empty());
ASSERT_EQ(0u, released_packet_count());
}
TEST_F(PacketQueueTest, Flush) {
auto packet_queue = CreatePacketQueue();
// Enqueue a packet.
ASSERT_TRUE(packet_queue->empty());
packet_queue->PushPacket(CreatePacket(0));
ASSERT_FALSE(packet_queue->empty());
ASSERT_EQ(0u, released_packet_count());
// Flush queue (discard all packets). Expect to see one packet released back to us.
packet_queue->Flush(nullptr);
RunLoopUntilIdle();
ASSERT_TRUE(packet_queue->empty());
ASSERT_EQ(1u, released_packet_count());
}
// Simulate the packet sink popping packets off the queue.
TEST_F(PacketQueueTest, LockUnlockBuffer) {
auto packet_queue = CreatePacketQueue();
auto packet_frame = FractionalFrames<int64_t>(0);
auto packet_length = FractionalFrames<uint32_t>(20);
// Enqueue some packets.
ASSERT_TRUE(packet_queue->empty());
auto packet0 = CreatePacket(0, packet_frame, packet_length);
packet_frame += packet_length;
auto packet1 = CreatePacket(1, packet_frame, packet_length);
packet_frame += packet_length;
auto packet2 = CreatePacket(2, packet_frame, packet_length);
packet_queue->PushPacket(packet0);
packet_queue->PushPacket(packet1);
packet_queue->PushPacket(packet2);
ASSERT_FALSE(packet_queue->empty());
ASSERT_EQ(0u, released_packet_count());
// Now pop off the packets in FIFO order.
//
// Packet #0:
auto buffer = packet_queue->LockBuffer(Now(), 0, 0);
ASSERT_TRUE(buffer);
ASSERT_FALSE(buffer->is_continuous());
ASSERT_EQ(0, buffer->start());
ASSERT_EQ(20u, buffer->length());
ASSERT_EQ(20, buffer->end());
ASSERT_EQ(packet0->payload(), buffer->payload());
ASSERT_FALSE(packet_queue->empty());
ASSERT_EQ(0u, released_packet_count());
packet0 = nullptr;
packet_queue->UnlockBuffer(true);
RunLoopUntilIdle();
ASSERT_FALSE(packet_queue->empty());
ASSERT_EQ(1u, released_packet_count());
// Packet #1
buffer = packet_queue->LockBuffer(Now(), 0, 0);
ASSERT_TRUE(buffer);
ASSERT_TRUE(buffer->is_continuous());
ASSERT_EQ(20, buffer->start());
ASSERT_EQ(20u, buffer->length());
ASSERT_EQ(40, buffer->end());
ASSERT_EQ(packet1->payload(), buffer->payload());
packet1 = nullptr;
packet_queue->UnlockBuffer(true);
RunLoopUntilIdle();
ASSERT_FALSE(packet_queue->empty());
ASSERT_EQ(2u, released_packet_count());
// ...and #2
buffer = packet_queue->LockBuffer(Now(), 0, 0);
ASSERT_TRUE(buffer);
ASSERT_TRUE(buffer->is_continuous());
ASSERT_EQ(40, buffer->start());
ASSERT_EQ(20u, buffer->length());
ASSERT_EQ(60, buffer->end());
ASSERT_EQ(packet2->payload(), buffer->payload());
packet2 = nullptr;
packet_queue->UnlockBuffer(true);
RunLoopUntilIdle();
ASSERT_TRUE(packet_queue->empty());
ASSERT_EQ(3u, released_packet_count());
}
TEST_F(PacketQueueTest, Trim) {
auto packet_queue = CreatePacketQueue();
auto packet_frame = FractionalFrames<int64_t>(0);
auto packet_length = FractionalFrames<uint32_t>(20);
// Enqueue some packets.
bool packet0_released, packet1_released, packet2_released, packet3_released;
{
ASSERT_TRUE(packet_queue->empty());
auto packet0 = CreatePacket(0, packet_frame, packet_length, &packet0_released);
packet_frame += packet_length;
auto packet1 = CreatePacket(0, packet_frame, packet_length, &packet1_released);
packet_frame += packet_length;
auto packet2 = CreatePacket(0, packet_frame, packet_length, &packet2_released);
packet_frame += packet_length;
auto packet3 = CreatePacket(0, packet_frame, packet_length, &packet3_released);
packet_queue->PushPacket(packet0);
packet_queue->PushPacket(packet1);
packet_queue->PushPacket(packet2);
packet_queue->PushPacket(packet3);
}
ASSERT_FALSE(packet_queue->empty());
ASSERT_EQ(0u, released_packet_count());
ASSERT_FALSE(packet0_released);
ASSERT_FALSE(packet1_released);
ASSERT_FALSE(packet2_released);
ASSERT_FALSE(packet3_released);
// The first packet should be trimmed after 20ms. Verify we haven't released at the instant
// before.
packet_queue->Trim(time_after(zx::msec(20) - zx::nsec(1)));
RunLoopUntilIdle();
ASSERT_FALSE(packet_queue->empty());
ASSERT_EQ(0u, released_packet_count());
// Trim again with the same deadline just to verify Trim is idempotent.
packet_queue->Trim(time_after(zx::msec(20) - zx::nsec(1)));
RunLoopUntilIdle();
ASSERT_FALSE(packet_queue->empty());
ASSERT_EQ(0u, released_packet_count());
// Now trim |packet0|
packet_queue->Trim(time_after(zx::msec(20)));
RunLoopUntilIdle();
ASSERT_FALSE(packet_queue->empty());
ASSERT_EQ(1u, released_packet_count());
ASSERT_TRUE(packet0_released);
// Now trim |packet1| and |packet2| in one go (run until just before |packet3| should be released.
packet_queue->Trim(time_after(zx::msec(80) - zx::nsec(1)));
RunLoopUntilIdle();
ASSERT_FALSE(packet_queue->empty());
ASSERT_EQ(3u, released_packet_count());
ASSERT_TRUE(packet1_released);
ASSERT_TRUE(packet2_released);
// Now trim past the end of all packets
packet_queue->Trim(time_after(zx::sec(1)));
RunLoopUntilIdle();
ASSERT_TRUE(packet_queue->empty());
ASSERT_EQ(4u, released_packet_count());
ASSERT_TRUE(packet3_released);
}
} // namespace
} // namespace media::audio