blob: f8ba11ad34088164c8597193242ca000eac2c6db [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 <lib/syslog/cpp/macros.h>
#include <algorithm>
#include <gtest/gtest.h>
#include "../chunk_input_stream.h"
#include "test_codec_packets.h"
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.ptr(0);
// Initialize buffer with bytes counting from 0 to (>=99).
const uint8_t* end = buffer->base() + buffer->size();
for (uint8_t* pos = buffer->base(); pos < end; ++pos) {
*pos = static_cast<uint8_t>(pos - buffer->base());
}
// Assign packets random lengths until the buffer is accounted for.
std::vector<std::pair<uint32_t, uint32_t>> packet_lengths_and_offsets;
uint8_t* pos = buffer->base();
while (pos < end) {
uint32_t packet_length = std::min((rand() % 10) + 1, static_cast<int>(end - pos));
packet_lengths_and_offsets.push_back({packet_length, pos - 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.ptr(i)->SetValidLengthBytes(packet_length);
packets.ptr(i)->SetBuffer(buffer);
packets.ptr(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 (size_t i = 0; i < packets.packets.size(); ++i) {
EXPECT_EQ(under_test.ProcessInputPacket(packets.ptr(i)), 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.ptr(0);
auto buffer = buffers.ptr(0);
constexpr size_t kExpectedByte = 44;
packet->SetValidLengthBytes(kPacketLen);
packet->SetBuffer(buffer);
packet->SetStartOffset(0);
*(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.ptr(0);
auto buffer = buffers.ptr(0);
const uint8_t kExpectedBytes[kPacketLen] = {3, 4, 5, 88, 92, 101, 77};
packet->SetValidLengthBytes(kPacketLen);
packet->SetBuffer(buffer);
packet->SetStartOffset(0);
memcpy(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.ptr(0);
auto buffer = buffers.ptr(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 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.ptr(0)->SetValidLengthBytes(kPacketLen);
packets.ptr(0)->SetStartOffset(0);
packets.ptr(0)->SetBuffer(buffers.ptr(0));
const uint64_t kInputTimestamp = 30;
our_extrapolator.Inform(4, kInputTimestamp);
const uint64_t kExpectedTimestamp = *our_extrapolator.Extrapolate(5);
packets.ptr(1)->SetValidLengthBytes(kPacketLen);
packets.ptr(1)->SetBuffer(buffers.ptr(1));
packets.ptr(1)->SetStartOffset(0);
packets.ptr(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, TimestampExtrapolator(ZX_SEC(1), ZX_SEC(1)),
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.ptr(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.ptr(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.ptr(0)->SetValidLengthBytes(kPacketLen);
packets.ptr(0)->SetStartOffset(0);
packets.ptr(0)->SetBuffer(buffers.ptr(0));
packets.ptr(0)->SetTimstampIsh(kExpectedTimestamp);
packets.ptr(1)->SetValidLengthBytes(kPacketLen);
packets.ptr(1)->SetBuffer(buffers.ptr(1));
packets.ptr(1)->SetStartOffset(0);
packets.ptr(1)->SetTimstampIsh(4096);
packets.ptr(2)->SetValidLengthBytes(kPacketLen);
packets.ptr(2)->SetBuffer(buffers.ptr(2));
packets.ptr(2)->SetStartOffset(0);
packets.ptr(2)->SetTimstampIsh(2048);
packets.ptr(3)->SetValidLengthBytes(kChunkSize);
packets.ptr(3)->SetBuffer(buffers.ptr(3));
packets.ptr(3)->SetStartOffset(0);
packets.ptr(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.ptr(0)), ChunkInputStream::kOk);
EXPECT_FALSE(was_called_for_packet_0);
packet_index += 1;
EXPECT_EQ(under_test.ProcessInputPacket(packets.ptr(1)), ChunkInputStream::kOk);
EXPECT_FALSE(was_called_for_packet_1);
packet_index += 1;
EXPECT_EQ(under_test.ProcessInputPacket(packets.ptr(2)), ChunkInputStream::kOk);
EXPECT_FALSE(was_called_for_packet_2);
packet_index += 1;
EXPECT_EQ(under_test.ProcessInputPacket(packets.ptr(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.ptr(0)->SetValidLengthBytes(static_cast<uint32_t>(buffers.ptr(0)->size()));
packets.ptr(0)->SetStartOffset(0);
packets.ptr(0)->SetBuffer(buffers.ptr(0));
const uint64_t kInputTimestamp = 30;
packets.ptr(1)->SetValidLengthBytes(static_cast<uint32_t>(buffers.ptr(1)->size()));
packets.ptr(1)->SetBuffer(buffers.ptr(1));
packets.ptr(1)->SetStartOffset(0);
packets.ptr(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.ptr(0)), ChunkInputStream::kOk);
EXPECT_FALSE(was_called_for_packet_0);
packet_index += 1;
EXPECT_EQ(under_test.ProcessInputPacket(packets.ptr(1)),
ChunkInputStream::kExtrapolationFailedWithoutTimebase);
// Should have been called once for finishing the first input packet, without
// timestamp.
EXPECT_EQ(calls_for_packet_2, 1u);
}
TEST(ChunkInputStream, ProvidesFlushTimestamp) {
constexpr size_t kChunkSize = 4;
constexpr size_t kPacketLen = 2;
auto packets = Packets(2);
auto buffers = Buffers({kPacketLen});
auto buffer = buffers.ptr(0);
packets.ptr(0)->SetValidLengthBytes(kPacketLen);
packets.ptr(0)->SetBuffer(buffer);
packets.ptr(0)->SetStartOffset(0);
packets.ptr(0)->SetTimstampIsh(10);
packets.ptr(1)->SetValidLengthBytes(kPacketLen);
packets.ptr(1)->SetBuffer(buffer);
packets.ptr(1)->SetStartOffset(0);
packets.ptr(1)->SetTimstampIsh(20);
bool flush_called = false;
auto input_block_processor = [&flush_called](ChunkInputStream::InputBlock input_block) {
if (input_block.is_end_of_stream) {
flush_called = true;
EXPECT_TRUE(input_block.flush_timestamp_ish.has_value());
return ChunkInputStream::kContinue;
}
return ChunkInputStream::kContinue;
};
auto under_test = ChunkInputStream(kChunkSize, TimestampExtrapolator(ZX_SEC(1), ZX_SEC(1)),
input_block_processor);
EXPECT_EQ(under_test.ProcessInputPacket(packets.ptr(0)), ChunkInputStream::kOk);
EXPECT_EQ(under_test.ProcessInputPacket(packets.ptr(1)), ChunkInputStream::kOk);
EXPECT_EQ(under_test.Flush(), ChunkInputStream::kOk);
EXPECT_TRUE(flush_called);
}