blob: a86bab1eff0d9bf891cf5a6e4ca144b9907bc374 [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 <fuchsia/mediacodec/cpp/fidl.h>
#include <src/lib/fxl/logging.h>
#include <algorithm>
#include "../chunk_input_stream.h"
#include "gtest/gtest.h"
constexpr uint32_t kBufferLifetimeOrdinal = 1;
class CodecPacketForTest : public CodecPacket {
public:
CodecPacketForTest(uint32_t index)
: CodecPacket(kBufferLifetimeOrdinal, index) {}
};
fuchsia::media::StreamBuffer StreamBufferOfSize(size_t size, uint32_t index) {
zx::vmo vmo_handle;
zx_status_t err = zx::vmo::create(size, 0u, &vmo_handle);
FXL_CHECK(err == ZX_OK) << "Failed to create vmo: " << err;
fuchsia::media::StreamBufferDataVmo vmo;
vmo.set_vmo_handle(std::move(vmo_handle));
vmo.set_vmo_usable_start(0);
vmo.set_vmo_usable_size(size);
fuchsia::media::StreamBufferData data;
data.set_vmo(std::move(vmo));
fuchsia::media::StreamBuffer buffer;
buffer.set_data(std::move(data));
buffer.set_buffer_index(index);
buffer.set_buffer_lifetime_ordinal(kBufferLifetimeOrdinal);
return buffer;
}
class CodecBufferForTest : public CodecBuffer {
public:
CodecBufferForTest(size_t size, uint32_t index)
: CodecBuffer(/*parent=*/nullptr, kOutputPort,
StreamBufferOfSize(size, index)) {
Init();
}
};
struct TestPackets {
std::vector<std::unique_ptr<CodecPacketForTest>> packets;
std::vector<CodecPacket*> ptrs;
};
TestPackets Packets(size_t count) {
TestPackets packets;
for (size_t i = 0; i < count; ++i) {
packets.packets.push_back(std::make_unique<CodecPacketForTest>(i));
packets.ptrs.push_back(packets.packets[i].get());
}
return packets;
}
struct TestBuffers {
std::vector<std::unique_ptr<CodecBufferForTest>> buffers;
std::vector<const CodecBuffer*> ptrs;
};
TestBuffers Buffers(std::vector<size_t> sizes) {
TestBuffers buffers;
for (size_t i = 0; i < sizes.size(); ++i) {
buffers.buffers.push_back(
std::make_unique<CodecBufferForTest>(sizes[i], i));
buffers.ptrs.push_back(buffers.buffers[i].get());
}
return buffers;
}
size_t AlignUp(size_t v, size_t alignment) {
ZX_ASSERT(alignment);
return (v + alignment - 1) / alignment * alignment;
}
TEST(ChunkInputStream, ChunkBoundaries) {
srand(100);
// Each test run creates a buffer that counts from 0 to (>=99), and packets
// that point to contiguous regions in that buffer of random lengths. They
// are fed to the chunk input stream and we expect to find the same sequence
// of 0 to (>=99).
auto test_chunk_size = [](size_t chunk_size) {
// Ensures we send enough packets to get 100 bytes out. We may add more
// bytes to complete a chunk and force the output.
const size_t buffer_size = AlignUp(100, chunk_size);
auto buffers = Buffers({buffer_size});
auto buffer = buffers.ptrs[0];
// Initialize buffer with bytes counting from 0 to (>=99).
const uint8_t* end = buffer->buffer_base() + buffer->buffer_size();
for (uint8_t* pos = buffer->buffer_base(); pos < end; ++pos) {
*pos = pos - buffer->buffer_base();
}
// Assign packets random lengths until the buffer is accounted for.
std::vector<std::pair<size_t, size_t>> packet_lengths_and_offsets;
uint8_t* pos = buffer->buffer_base();
while (pos < end) {
size_t packet_length =
std::min((rand() % 10) + 1, static_cast<int>(end - pos));
packet_lengths_and_offsets.push_back(
{packet_length, pos - buffer->buffer_base()});
pos += packet_length;
}
auto packets = Packets(packet_lengths_and_offsets.size());
size_t i = 0;
for (auto [packet_length, packet_offset] : packet_lengths_and_offsets) {
packets.ptrs[i]->SetValidLengthBytes(packet_length);
packets.ptrs[i]->SetBuffer(buffer);
packets.ptrs[i]->SetStartOffset(packet_offset);
++i;
}
size_t seen = 0;
auto input_block_processor =
[&seen, chunk_size](ChunkInputStream::InputBlock input_block) {
EXPECT_EQ(input_block.len, chunk_size);
EXPECT_EQ(input_block.non_padding_len, input_block.len);
EXPECT_FALSE(input_block.is_end_of_stream);
for (size_t i = 0; i < input_block.len; ++i) {
EXPECT_EQ(*(input_block.data + i), static_cast<uint8_t>(seen));
++seen;
}
return ChunkInputStream::kContinue;
};
auto under_test = ChunkInputStream(chunk_size, TimestampExtrapolator(),
std::move(input_block_processor));
for (auto packet : packets.ptrs) {
EXPECT_EQ(under_test.ProcessInputPacket(packet), ChunkInputStream::kOk);
}
EXPECT_EQ(seen, buffer_size) << "Failure on chunk size " << chunk_size;
};
for (size_t i = 0; i < 30u; ++i) {
test_chunk_size((rand() % 50) + 1);
}
}
TEST(ChunkInputStream, FlushIncomplete) {
constexpr size_t kChunkSize = 5;
constexpr size_t kPacketLen = 1;
auto packets = Packets(1);
auto buffers = Buffers({kPacketLen});
auto packet = packets.ptrs[0];
auto buffer = buffers.ptrs[0];
constexpr size_t kExpectedByte = 44;
packet->SetValidLengthBytes(kPacketLen);
packet->SetBuffer(buffer);
packet->SetStartOffset(0);
*(buffer->buffer_base()) = kExpectedByte;
bool was_called_for_input_block = false;
bool flush_called = false;
auto input_block_processor =
[&was_called_for_input_block, &flush_called,
kChunkSize](ChunkInputStream::InputBlock input_block) {
if (input_block.is_end_of_stream) {
flush_called = true;
const size_t expected[kChunkSize] = {kExpectedByte};
EXPECT_EQ(input_block.len, kChunkSize);
EXPECT_EQ(input_block.non_padding_len, kPacketLen % kChunkSize);
EXPECT_TRUE(input_block.is_end_of_stream);
EXPECT_EQ(memcmp(input_block.data, expected, input_block.len), 0);
return ChunkInputStream::kContinue;
}
was_called_for_input_block = true;
return ChunkInputStream::kContinue;
};
auto under_test = ChunkInputStream(kChunkSize, TimestampExtrapolator(),
input_block_processor);
// We load the stream with one packet that is too short to complete a block,
// and expect no input blocks to come from it.
EXPECT_EQ(under_test.ProcessInputPacket(packet), ChunkInputStream::kOk);
EXPECT_FALSE(was_called_for_input_block);
// Now we flush and expect to get our data at the start of a buffer, with 0s
// padded to complete a block.
EXPECT_EQ(ChunkInputStream::kOk, under_test.Flush());
EXPECT_TRUE(flush_called);
}
TEST(ChunkInputStream, FlushLeftover) {
constexpr size_t kChunkSize = 5;
constexpr size_t kPacketLen = 7;
auto packets = Packets(1);
auto buffers = Buffers({kPacketLen});
auto packet = packets.ptrs[0];
auto buffer = buffers.ptrs[0];
const uint8_t kExpectedBytes[kPacketLen] = {3, 4, 5, 88, 92, 101, 77};
packet->SetValidLengthBytes(kPacketLen);
packet->SetBuffer(buffer);
packet->SetStartOffset(0);
memcpy(buffer->buffer_base(), kExpectedBytes, kPacketLen);
size_t input_block_call_count = 0;
bool flush_called = false;
auto input_block_processor =
[&input_block_call_count, &flush_called, kChunkSize,
kExpectedBytes](ChunkInputStream::InputBlock input_block) {
if (input_block.is_end_of_stream) {
flush_called = true;
const uint8_t expected[kChunkSize] = {kExpectedBytes[kPacketLen - 2],
kExpectedBytes[kPacketLen - 1]};
EXPECT_EQ(input_block.len, kChunkSize);
EXPECT_EQ(input_block.non_padding_len, kPacketLen % kChunkSize);
EXPECT_TRUE(input_block.is_end_of_stream);
EXPECT_EQ(memcmp(input_block.data, expected, input_block.len), 0);
return ChunkInputStream::kContinue;
}
input_block_call_count += 1;
EXPECT_NE(input_block.data, nullptr);
EXPECT_EQ(input_block.len, kChunkSize);
EXPECT_EQ(input_block.non_padding_len, input_block.len);
EXPECT_FALSE(input_block.is_end_of_stream);
EXPECT_EQ(memcmp(input_block.data, kExpectedBytes, kChunkSize), 0);
return ChunkInputStream::kContinue;
};
auto under_test = ChunkInputStream(kChunkSize, TimestampExtrapolator(),
input_block_processor);
// We send a packet that is long enough for an input block and a little of the
// next input block. We expect only one complete input block.
EXPECT_EQ(under_test.ProcessInputPacket(packet), ChunkInputStream::kOk);
EXPECT_EQ(input_block_call_count, 1u);
// Now we flush and expect the leftover data in a buffer with padded 0s to
// complete the input block.
EXPECT_EQ(under_test.Flush(), ChunkInputStream::kOk);
EXPECT_TRUE(flush_called);
}
TEST(ChunkInputStream, TimestampsCarry) {
constexpr size_t kChunkSize = 5;
constexpr size_t kPacketLen = 7;
auto packets = Packets(1);
auto buffers = Buffers({kPacketLen});
auto packet = packets.ptrs[0];
auto buffer = buffers.ptrs[0];
const uint64_t kExpectedTimestamp = 30;
packet->SetValidLengthBytes(kPacketLen);
packet->SetBuffer(buffer);
packet->SetStartOffset(0);
packet->SetTimstampIsh(kExpectedTimestamp);
bool was_called_for_input_block = false;
bool flush_called = false;
auto input_block_processor =
[&was_called_for_input_block, &flush_called,
kExpectedTimestamp](ChunkInputStream::InputBlock input_block) {
if (input_block.is_end_of_stream) {
flush_called = true;
EXPECT_EQ(input_block.timestamp_ish.has_value(), false);
return ChunkInputStream::kContinue;
}
was_called_for_input_block = true;
EXPECT_EQ(input_block.timestamp_ish.has_value(), true);
EXPECT_EQ(input_block.timestamp_ish.value_or(0), kExpectedTimestamp);
return ChunkInputStream::kContinue;
};
auto under_test = ChunkInputStream(kChunkSize, TimestampExtrapolator(),
input_block_processor);
// We expect our single timestamp to come in the first input block.
EXPECT_EQ(under_test.ProcessInputPacket(packet), ChunkInputStream::kOk);
EXPECT_TRUE(was_called_for_input_block);
// We expect that the timestamp was consumed.
EXPECT_EQ(under_test.Flush(), ChunkInputStream::kOk);
EXPECT_TRUE(flush_called);
}
TEST(ChunkInputStream, TimestampsExtrapolate) {
constexpr size_t kChunkSize = 5;
constexpr size_t kPacketLen = 4;
auto our_extrapolator = TimestampExtrapolator(ZX_SEC(1), ZX_SEC(1));
auto stream_extrapolator = our_extrapolator;
auto packets = Packets(2);
auto buffers = Buffers({kPacketLen, kPacketLen});
// Configure two packets, the first length 4. The second will contain a
// timestamp. Since the chunk size is 5, the second packet will need its
// timestamp extrapolated 1 byte.
packets.ptrs[0]->SetValidLengthBytes(kPacketLen);
packets.ptrs[0]->SetStartOffset(0);
packets.ptrs[0]->SetBuffer(buffers.ptrs[0]);
const uint64_t kInputTimestamp = 30;
our_extrapolator.Inform(4, kInputTimestamp);
const uint64_t kExpectedTimestamp = *our_extrapolator.Extrapolate(5);
packets.ptrs[1]->SetValidLengthBytes(kPacketLen);
packets.ptrs[1]->SetBuffer(buffers.ptrs[1]);
packets.ptrs[1]->SetStartOffset(0);
packets.ptrs[1]->SetTimstampIsh(kInputTimestamp);
size_t packet_index = 0; // We use this to run different code when processing
// each packet.
bool was_called_for_packet_0 = false;
bool was_called_for_packet_1 = false;
bool flush_called = false;
auto input_block_processor =
[&packet_index, &was_called_for_packet_0, &was_called_for_packet_1,
&flush_called,
kExpectedTimestamp](ChunkInputStream::InputBlock input_block) {
if (input_block.is_end_of_stream) {
flush_called = true;
EXPECT_EQ(input_block.timestamp_ish.has_value(), true);
EXPECT_EQ(input_block.timestamp_ish.value_or(0), kExpectedTimestamp);
return ChunkInputStream::kContinue;
}
switch (packet_index) {
case 0:
was_called_for_packet_0 = true;
return ChunkInputStream::kContinue;
case 1:
was_called_for_packet_1 = true;
EXPECT_EQ(input_block.timestamp_ish.has_value(), false);
return ChunkInputStream::kContinue;
default:
EXPECT_FALSE(true) << "This should not happen.";
return ChunkInputStream::kTerminate;
}
};
auto under_test = ChunkInputStream(kChunkSize, std::move(stream_extrapolator),
input_block_processor);
// We send a short packet in that isn't a full input block to bring our
// stream out of alignment. This one doesn't have a timestamp.
EXPECT_EQ(under_test.ProcessInputPacket(packets.ptrs[0]),
ChunkInputStream::kOk);
EXPECT_FALSE(was_called_for_packet_0);
// We send in a packet to complete the first block. It should not have a
// timestamp even though the new packet has one, because we only extrapolate
// forward.
packet_index += 1;
EXPECT_EQ(under_test.ProcessInputPacket(packets.ptrs[1]),
ChunkInputStream::kOk);
EXPECT_TRUE(was_called_for_packet_1);
// We expect the flush to contain a timestamp extrapolated from the second
// packet's timestamp.
EXPECT_EQ(under_test.Flush(), ChunkInputStream::kOk);
EXPECT_TRUE(flush_called);
}
TEST(ChunkInputStream, TimestampsDropWhenInsideBlock) {
constexpr size_t kChunkSize = 5;
constexpr size_t kPacketLen = 1;
auto packets = Packets(4);
auto buffers = Buffers({kPacketLen, kPacketLen, kPacketLen, kChunkSize});
// Configure 4 packets, each with a timestamp, all starting in the same
// input block because they are small. In the output we should see the
// timestamp for the first packet, and a timestamp extrapolated from the 4th
// packet, where the middle 2 timestamps do not influence the output.
const uint64_t kExpectedTimestamp = 5;
packets.ptrs[0]->SetValidLengthBytes(kPacketLen);
packets.ptrs[0]->SetStartOffset(0);
packets.ptrs[0]->SetBuffer(buffers.ptrs[0]);
packets.ptrs[0]->SetTimstampIsh(kExpectedTimestamp);
packets.ptrs[1]->SetValidLengthBytes(kPacketLen);
packets.ptrs[1]->SetBuffer(buffers.ptrs[1]);
packets.ptrs[1]->SetStartOffset(0);
packets.ptrs[1]->SetTimstampIsh(4096);
packets.ptrs[2]->SetValidLengthBytes(kPacketLen);
packets.ptrs[2]->SetBuffer(buffers.ptrs[2]);
packets.ptrs[2]->SetStartOffset(0);
packets.ptrs[2]->SetTimstampIsh(2048);
packets.ptrs[3]->SetValidLengthBytes(kChunkSize);
packets.ptrs[3]->SetBuffer(buffers.ptrs[3]);
packets.ptrs[3]->SetStartOffset(0);
packets.ptrs[3]->SetTimstampIsh(10);
const uint64_t kExpectedExtrapolatedTimestamp = 12;
size_t packet_index = 0; // We use this to run different code when processing
// each packet.
bool was_called_for_packet_0 = false;
bool was_called_for_packet_1 = false;
bool was_called_for_packet_2 = false;
bool was_called_for_packet_3 = false;
bool flush_called = false;
auto input_block_processor = [&packet_index, &was_called_for_packet_0,
&was_called_for_packet_1,
&was_called_for_packet_2,
&was_called_for_packet_3, kExpectedTimestamp,
&flush_called, kExpectedExtrapolatedTimestamp](
ChunkInputStream::InputBlock input_block) {
if (input_block.is_end_of_stream) {
flush_called = true;
EXPECT_EQ(input_block.timestamp_ish.has_value(), true);
EXPECT_EQ(input_block.timestamp_ish.value_or(0),
kExpectedExtrapolatedTimestamp);
return ChunkInputStream::kContinue;
}
switch (packet_index) {
case 0:
was_called_for_packet_0 = true;
return ChunkInputStream::kContinue;
case 1:
was_called_for_packet_1 = true;
return ChunkInputStream::kContinue;
case 2:
was_called_for_packet_2 = true;
return ChunkInputStream::kContinue;
case 3:
was_called_for_packet_3 = true;
EXPECT_EQ(input_block.timestamp_ish.has_value(), true);
EXPECT_EQ(input_block.timestamp_ish.value_or(0), kExpectedTimestamp);
return ChunkInputStream::kContinue;
default:
EXPECT_FALSE(true) << "This should not happen.";
return ChunkInputStream::kTerminate;
}
};
auto under_test =
ChunkInputStream(kChunkSize, TimestampExtrapolator(ZX_SEC(1), ZX_SEC(1)),
input_block_processor);
EXPECT_EQ(under_test.ProcessInputPacket(packets.ptrs[0]),
ChunkInputStream::kOk);
EXPECT_FALSE(was_called_for_packet_0);
packet_index += 1;
EXPECT_EQ(under_test.ProcessInputPacket(packets.ptrs[1]),
ChunkInputStream::kOk);
EXPECT_FALSE(was_called_for_packet_1);
packet_index += 1;
EXPECT_EQ(under_test.ProcessInputPacket(packets.ptrs[2]),
ChunkInputStream::kOk);
EXPECT_FALSE(was_called_for_packet_2);
packet_index += 1;
EXPECT_EQ(under_test.ProcessInputPacket(packets.ptrs[3]),
ChunkInputStream::kOk);
EXPECT_TRUE(was_called_for_packet_3);
EXPECT_EQ(ChunkInputStream::kOk, under_test.Flush());
EXPECT_TRUE(flush_called);
}
TEST(ChunkInputStream, ReportsErrorWhenMissingTimebase) {
constexpr size_t kChunkSize = 5;
auto packets = Packets(2);
auto buffers = Buffers({4, 20});
// Configure two packets, the first length 4. The second will contain a
// timestamp. Since the chunk size is 5, the second packet will need its
// timestamp extrapolated 1 byte.
packets.ptrs[0]->SetValidLengthBytes(buffers.ptrs[0]->buffer_size());
packets.ptrs[0]->SetStartOffset(0);
packets.ptrs[0]->SetBuffer(buffers.ptrs[0]);
const uint64_t kInputTimestamp = 30;
packets.ptrs[1]->SetValidLengthBytes(buffers.ptrs[1]->buffer_size());
packets.ptrs[1]->SetBuffer(buffers.ptrs[1]);
packets.ptrs[1]->SetStartOffset(0);
packets.ptrs[1]->SetTimstampIsh(kInputTimestamp);
size_t packet_index = 0; // We use this to run different code when processing
// each packet.
bool was_called_for_packet_0 = false;
size_t calls_for_packet_2 = 0;
auto input_block_processor =
[&packet_index, &was_called_for_packet_0,
&calls_for_packet_2](ChunkInputStream::InputBlock input_block) {
switch (packet_index) {
case 0:
was_called_for_packet_0 = true;
return ChunkInputStream::kContinue;
case 1:
calls_for_packet_2 += 1;
return ChunkInputStream::kContinue;
default:
EXPECT_TRUE(false) << "This should not happen.";
return ChunkInputStream::kTerminate;
}
};
auto under_test = ChunkInputStream(kChunkSize, TimestampExtrapolator(),
input_block_processor);
EXPECT_EQ(under_test.ProcessInputPacket(packets.ptrs[0]),
ChunkInputStream::kOk);
EXPECT_FALSE(was_called_for_packet_0);
packet_index += 1;
EXPECT_EQ(under_test.ProcessInputPacket(packets.ptrs[1]),
ChunkInputStream::kExtrapolationFailedWithoutTimebase);
// Should have been called once for finishing the first input packet, without
// timestamp.
EXPECT_EQ(calls_for_packet_2, 1u);
}