blob: 5bff4e77d2794293e35e79b939256281f970e9ae [file] [log] [blame]
// Copyright 2020 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/capture_packet_queue.h"
#include <lib/syslog/cpp/macros.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
using ASF = fuchsia::media::AudioSampleFormat;
using StreamPacket = fuchsia::media::StreamPacket;
namespace media::audio {
namespace {
static constexpr auto kFrameRate = 48000;
static const auto kFormat = Format::Create<ASF::SIGNED_16>(1, kFrameRate).value();
static const auto kBytesPerFrame = kFormat.bytes_per_frame();
} // namespace
class CapturePacketQueueTest : public ::testing::Test {
public:
void CreateMapper(size_t frames) {
auto status =
payload_buffer_.CreateAndMap(frames * kBytesPerFrame, /*flags=*/0, nullptr, &payload_vmo_);
FX_CHECK(status == ZX_OK) << status;
payload_start_ = static_cast<char*>(payload_buffer_.start());
}
using Packet = CapturePacketQueue::Packet;
void ExpectPacket(fbl::RefPtr<Packet> got, StreamPacket want) {
EXPECT_EQ(got->stream_packet().payload_buffer_id, want.payload_buffer_id);
EXPECT_EQ(got->stream_packet().payload_offset, want.payload_offset);
EXPECT_EQ(got->stream_packet().payload_size, want.payload_size);
}
void PopAndExpectPacketAtOffset(CapturePacketQueue* pq, size_t want_offset_bytes,
size_t want_size_bytes) {
auto mix_state = pq->NextMixerJob();
ASSERT_TRUE(mix_state.has_value());
EXPECT_EQ(mix_state->target, payload_start_ + want_offset_bytes);
EXPECT_EQ(mix_state->frames, want_size_bytes / kBytesPerFrame);
ASSERT_EQ(CapturePacketQueue::PacketMixStatus::Done, pq->FinishMixerJob(*mix_state));
ASSERT_EQ(pq->ReadySize(), 1u);
auto p = pq->PopReady();
ExpectPacket(p, {
.payload_buffer_id = 0,
.payload_offset = want_offset_bytes,
.payload_size = want_size_bytes,
});
if (p->callback()) {
p->callback()(p->stream_packet());
}
}
zx::vmo payload_vmo_;
fzl::VmoMapper payload_buffer_;
char* payload_start_ = nullptr;
};
TEST_F(CapturePacketQueueTest, Preallocated_FramesFitPerfectly) {
CreateMapper(40);
auto result = CapturePacketQueue::CreatePreallocated(payload_buffer_, kFormat, 10);
ASSERT_TRUE(result.is_ok()) << result.error();
const auto kBytesPerPacket = 10 * kBytesPerFrame;
auto pq = result.take_value();
{
SCOPED_TRACE("pop #1");
ASSERT_EQ(pq->PendingSize(), 4u);
PopAndExpectPacketAtOffset(pq.get(), 0 * kBytesPerPacket, kBytesPerPacket);
}
{
SCOPED_TRACE("pop #2");
ASSERT_EQ(pq->PendingSize(), 3u);
PopAndExpectPacketAtOffset(pq.get(), 1 * kBytesPerPacket, kBytesPerPacket);
}
{
SCOPED_TRACE("pop #3");
ASSERT_EQ(pq->PendingSize(), 2u);
PopAndExpectPacketAtOffset(pq.get(), 2 * kBytesPerPacket, kBytesPerPacket);
}
{
SCOPED_TRACE("pop #4");
ASSERT_EQ(pq->PendingSize(), 1u);
PopAndExpectPacketAtOffset(pq.get(), 3 * kBytesPerPacket, kBytesPerPacket);
}
ASSERT_TRUE(pq->empty());
ASSERT_EQ(pq->PendingSize(), 0u);
ASSERT_EQ(pq->ReadySize(), 0u);
}
TEST_F(CapturePacketQueueTest, Preallocated_FramesLeftover) {
CreateMapper(40);
auto result = CapturePacketQueue::CreatePreallocated(payload_buffer_, kFormat, 15);
ASSERT_TRUE(result.is_ok()) << result.error();
const auto kBytesPerPacket = 15 * kBytesPerFrame;
auto pq = result.take_value();
// 40 frames in the payload, 15 frames per packet, so the packets have
// frames [0,14] and [15,29].
{
SCOPED_TRACE("pop #1");
ASSERT_EQ(pq->PendingSize(), 2u);
PopAndExpectPacketAtOffset(pq.get(), 0 * kBytesPerPacket, kBytesPerPacket);
}
{
SCOPED_TRACE("pop #2");
ASSERT_EQ(pq->PendingSize(), 1u);
PopAndExpectPacketAtOffset(pq.get(), 1 * kBytesPerPacket, kBytesPerPacket);
}
ASSERT_TRUE(pq->empty());
ASSERT_EQ(pq->PendingSize(), 0u);
ASSERT_EQ(pq->ReadySize(), 0u);
}
TEST_F(CapturePacketQueueTest, Preallocated_MixStatePreserved) {
CreateMapper(20);
auto result = CapturePacketQueue::CreatePreallocated(payload_buffer_, kFormat, 10);
ASSERT_TRUE(result.is_ok()) << result.error();
const auto kBytesPerPacket = 10 * kBytesPerFrame;
auto pq = result.take_value();
ASSERT_EQ(pq->PendingSize(), 2u);
auto mix_state = pq->NextMixerJob().value();
mix_state.capture_timestamp = 99;
mix_state.flags = 1;
ASSERT_EQ(CapturePacketQueue::PacketMixStatus::Done, pq->FinishMixerJob(mix_state));
ASSERT_EQ(pq->ReadySize(), 1u);
ExpectPacket(pq->PopReady(), {
.pts = 99,
.payload_buffer_id = 0,
.payload_offset = 0,
.payload_size = kBytesPerPacket,
.flags = 1,
});
}
TEST_F(CapturePacketQueueTest, Preallocated_PartialMix) {
CreateMapper(20);
auto result = CapturePacketQueue::CreatePreallocated(payload_buffer_, kFormat, 10);
ASSERT_TRUE(result.is_ok()) << result.error();
auto pq = result.take_value();
{
SCOPED_TRACE("partial mix");
ASSERT_EQ(pq->PendingSize(), 2u);
auto mix_state = pq->NextMixerJob().value();
EXPECT_EQ(mix_state.target, payload_start_ + 0);
EXPECT_EQ(mix_state.frames, 10u);
mix_state.capture_timestamp = 99;
mix_state.flags = 1;
mix_state.frames = 6;
ASSERT_EQ(CapturePacketQueue::PacketMixStatus::Partial, pq->FinishMixerJob(mix_state));
ASSERT_EQ(pq->ReadySize(), 0u);
}
{
SCOPED_TRACE("finish mix");
ASSERT_EQ(pq->PendingSize(), 2u);
auto mix_state = pq->NextMixerJob().value();
EXPECT_EQ(mix_state.capture_timestamp, 99);
EXPECT_EQ(mix_state.flags, 1u);
EXPECT_EQ(mix_state.target, payload_start_ + 6 * kBytesPerFrame);
EXPECT_EQ(mix_state.frames, 4u);
ASSERT_EQ(CapturePacketQueue::PacketMixStatus::Done, pq->FinishMixerJob(mix_state));
ASSERT_EQ(pq->ReadySize(), 1u);
}
}
TEST_F(CapturePacketQueueTest, Preallocated_DiscardedMix) {
CreateMapper(20);
auto result = CapturePacketQueue::CreatePreallocated(payload_buffer_, kFormat, 10);
ASSERT_TRUE(result.is_ok()) << result.error();
const auto kBytesPerPacket = 10 * kBytesPerFrame;
auto pq = result.take_value();
ASSERT_EQ(pq->PendingSize(), 2u);
auto mix_state = pq->NextMixerJob().value();
EXPECT_EQ(mix_state.target, payload_start_ + 0);
EXPECT_EQ(mix_state.frames, 10u);
// Before completing this mix, discard all pending packets.
pq->DiscardPendingPackets();
ASSERT_EQ(pq->ReadySize(), 2u);
ExpectPacket(pq->PopReady(), {
.payload_buffer_id = 0,
.payload_offset = 0 * kBytesPerPacket,
.payload_size = 0,
});
ExpectPacket(pq->PopReady(), {
.payload_buffer_id = 0,
.payload_offset = 1 * kBytesPerPacket,
.payload_size = 0,
});
EXPECT_EQ(pq->ReadySize(), 0u);
EXPECT_EQ(CapturePacketQueue::PacketMixStatus::Discarded, pq->FinishMixerJob(mix_state));
}
TEST_F(CapturePacketQueueTest, Preallocated_DiscardedAfterPartialMix) {
CreateMapper(20);
auto result = CapturePacketQueue::CreatePreallocated(payload_buffer_, kFormat, 10);
ASSERT_TRUE(result.is_ok()) << result.error();
const auto kBytesPerPacket = 10 * kBytesPerFrame;
auto pq = result.take_value();
// Partial mix.
ASSERT_EQ(pq->PendingSize(), 2u);
auto mix_state = pq->NextMixerJob().value();
EXPECT_EQ(mix_state.target, payload_start_ + 0);
EXPECT_EQ(mix_state.frames, 10u);
mix_state.frames = 6;
EXPECT_EQ(CapturePacketQueue::PacketMixStatus::Partial, pq->FinishMixerJob(mix_state));
// Second mix.
mix_state = pq->NextMixerJob().value();
EXPECT_EQ(mix_state.target, payload_start_ + 6 * kBytesPerFrame);
EXPECT_EQ(mix_state.frames, 4u);
// Before completing this mix, discard all pending packets.
EXPECT_EQ(pq->PendingSize(), 2u);
ASSERT_EQ(pq->ReadySize(), 0u);
pq->DiscardPendingPackets();
EXPECT_EQ(pq->PendingSize(), 0u);
ASSERT_EQ(pq->ReadySize(), 2u);
ExpectPacket(pq->PopReady(), {
.payload_buffer_id = 0,
.payload_offset = 0 * kBytesPerPacket,
.payload_size = 6 * kBytesPerFrame, // this was partially mixed
});
ExpectPacket(pq->PopReady(), {
.payload_buffer_id = 0,
.payload_offset = 1 * kBytesPerPacket,
.payload_size = 0,
});
EXPECT_EQ(pq->ReadySize(), 0u);
EXPECT_EQ(CapturePacketQueue::PacketMixStatus::Discarded, pq->FinishMixerJob(mix_state));
}
TEST_F(CapturePacketQueueTest, Preallocated_Recycle) {
CreateMapper(20);
auto result = CapturePacketQueue::CreatePreallocated(payload_buffer_, kFormat, 10);
ASSERT_TRUE(result.is_ok()) << result.error();
const auto kBytesPerPacket = 10 * kBytesPerFrame;
auto pq = result.take_value();
{
SCOPED_TRACE("pop and recycle #1");
ASSERT_EQ(pq->PendingSize(), 2u);
auto mix_state = pq->NextMixerJob().value();
ASSERT_EQ(CapturePacketQueue::PacketMixStatus::Done, pq->FinishMixerJob(mix_state));
ASSERT_EQ(pq->ReadySize(), 1u);
auto p = pq->PopReady();
ExpectPacket(p, {
.payload_buffer_id = 0,
.payload_offset = 0,
.payload_size = kBytesPerPacket,
});
ASSERT_EQ(pq->PendingSize(), 1u);
auto result = pq->Recycle(p->stream_packet());
ASSERT_TRUE(result.is_ok()) << result.error();
}
{
SCOPED_TRACE("pop #2");
ASSERT_EQ(pq->PendingSize(), 2u);
PopAndExpectPacketAtOffset(pq.get(), 1 * kBytesPerPacket, kBytesPerPacket);
}
{
SCOPED_TRACE("pop #1 again");
ASSERT_EQ(pq->PendingSize(), 1u);
PopAndExpectPacketAtOffset(pq.get(), 0 * kBytesPerPacket, kBytesPerPacket);
}
}
TEST_F(CapturePacketQueueTest, Preallocated_RecycleErrors) {
CreateMapper(20);
auto result = CapturePacketQueue::CreatePreallocated(payload_buffer_, kFormat, 10);
ASSERT_TRUE(result.is_ok()) << result.error();
const auto kBytesPerPacket = 10 * kBytesPerFrame;
auto pq = result.take_value();
// Pop the first packet.
ASSERT_EQ(CapturePacketQueue::PacketMixStatus::Done,
pq->FinishMixerJob(pq->NextMixerJob().value()));
ASSERT_EQ(pq->ReadySize(), 1u);
auto p1 = pq->PopReady();
// Offset not found.
auto recycle_result = pq->Recycle({
.payload_buffer_id = 0,
.payload_offset = 100,
.payload_size = kBytesPerPacket,
});
ASSERT_TRUE(recycle_result.is_error());
// Wrong buffer ID.
recycle_result = pq->Recycle({
.payload_buffer_id = 1,
.payload_offset = 0,
.payload_size = kBytesPerPacket,
});
ASSERT_TRUE(recycle_result.is_error());
// Wrong size.
recycle_result = pq->Recycle({
.payload_buffer_id = 0,
.payload_offset = 0,
.payload_size = kBytesPerPacket - 1,
});
ASSERT_TRUE(recycle_result.is_error());
// Double recycle fails.
StreamPacket sp = fidl::Clone(p1->stream_packet());
recycle_result = pq->Recycle(sp);
ASSERT_TRUE(recycle_result.is_ok()) << recycle_result.error();
recycle_result = pq->Recycle(sp);
ASSERT_TRUE(recycle_result.is_error());
}
TEST_F(CapturePacketQueueTest, DynamicallyAllocated) {
CreateMapper(50);
auto pq = CapturePacketQueue::CreateDynamicallyAllocated(payload_buffer_, kFormat);
ASSERT_TRUE(pq->empty());
ASSERT_EQ(pq->PendingSize(), 0u);
bool got_p1_callback = false;
auto push_result =
pq->PushPending(0, 10, [&got_p1_callback](StreamPacket p) { got_p1_callback = true; });
ASSERT_TRUE(push_result.is_ok()) << push_result.error();
ASSERT_EQ(pq->PendingSize(), 1u);
bool got_p2_callback = false;
push_result =
pq->PushPending(15, 20, [&got_p2_callback](StreamPacket p) { got_p2_callback = true; });
ASSERT_TRUE(push_result.is_ok()) << push_result.error();
{
SCOPED_TRACE("pop #1");
ASSERT_EQ(pq->PendingSize(), 2u);
PopAndExpectPacketAtOffset(pq.get(), 0 * kBytesPerFrame, 10 * kBytesPerFrame);
EXPECT_TRUE(got_p1_callback);
EXPECT_FALSE(got_p2_callback);
}
{
SCOPED_TRACE("pop #2");
ASSERT_EQ(pq->PendingSize(), 1u);
PopAndExpectPacketAtOffset(pq.get(), 15 * kBytesPerFrame, 20 * kBytesPerFrame);
EXPECT_TRUE(got_p2_callback);
}
ASSERT_TRUE(pq->empty());
ASSERT_EQ(pq->PendingSize(), 0u);
}
TEST_F(CapturePacketQueueTest, DynamicallyAllocated_PushErrors) {
CreateMapper(50);
auto pq = CapturePacketQueue::CreateDynamicallyAllocated(payload_buffer_, kFormat);
// num_frames == 0
auto push_result = pq->PushPending(0, 0, nullptr);
ASSERT_TRUE(push_result.is_error());
// Payload goes past end of buffer.
push_result = pq->PushPending(40, 11, nullptr);
ASSERT_TRUE(push_result.is_error());
}
} // namespace media::audio