ladybird/Tests/LibMedia/TestParseMatroska.cpp

687 lines
30 KiB
C++
Raw Permalink Normal View History

/*
* Copyright (c) 2023-2026, Gregory Bertilson <gregory@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibCore/File.h>
#include <LibMedia/Containers/Matroska/MatroskaDemuxer.h>
#include <LibMedia/ReadonlyBytesCursor.h>
#include <LibTest/TestCase.h>
#include <LibMedia/Containers/Matroska/Reader.h>
static Media::Matroska::Streamer streamer_from_bytes(ReadonlyBytes bytes)
{
return Media::Matroska::Streamer(make_ref_counted<Media::ReadonlyBytesCursor>(bytes));
}
TEST_CASE(streamer_read_element_id)
{
// 1-byte ID: 0xEC (Void)
auto streamer = streamer_from_bytes("\xEC"sv.bytes());
EXPECT_EQ(MUST(streamer.read_element_id()), 0xECu);
// 2-byte ID: 0x42 0x86 (EBMLVersion)
streamer = streamer_from_bytes("\x42\x86"sv.bytes());
EXPECT_EQ(MUST(streamer.read_element_id()), 0x4286u);
// 4-byte ID: 0x1A 0x45 0xDF 0xA3 (EBML)
streamer = streamer_from_bytes("\x1A\x45\xDF\xA3"sv.bytes());
EXPECT_EQ(MUST(streamer.read_element_id()), 0x1A45DFA3u);
// Invalid: leading byte 0x00 (no width bit set)
streamer = streamer_from_bytes("\x00"sv.bytes());
EXPECT(streamer.read_element_id().is_error());
}
TEST_CASE(streamer_read_element_size)
{
// 1-byte size: 0x85 = size 5
auto streamer = streamer_from_bytes("\x85"sv.bytes());
auto size = MUST(streamer.read_element_size());
EXPECT(size.has_value());
EXPECT_EQ(size.value(), 5u);
// 1-byte size: 0x80 = size 0
streamer = streamer_from_bytes("\x80"sv.bytes());
size = MUST(streamer.read_element_size());
EXPECT(size.has_value());
EXPECT_EQ(size.value(), 0u);
// 2-byte size: 0x40 0x02 = size 2
streamer = streamer_from_bytes("\x40\x02"sv.bytes());
size = MUST(streamer.read_element_size());
EXPECT(size.has_value());
EXPECT_EQ(size.value(), 2u);
// 1-byte unknown size: 0xFF
streamer = streamer_from_bytes("\xFF"sv.bytes());
size = MUST(streamer.read_element_size());
EXPECT(!size.has_value());
// 2-byte unknown size: 0x7F 0xFF
streamer = streamer_from_bytes("\x7F\xFF"sv.bytes());
size = MUST(streamer.read_element_size());
EXPECT(!size.has_value());
// 4-byte unknown size: 0x1F 0xFF 0xFF 0xFF
streamer = streamer_from_bytes("\x1F\xFF\xFF\xFF"sv.bytes());
size = MUST(streamer.read_element_size());
EXPECT(!size.has_value());
// 8-byte unknown size: 0x01 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF
streamer = streamer_from_bytes("\x01\xFF\xFF\xFF\xFF\xFF\xFF\xFF"sv.bytes());
size = MUST(streamer.read_element_size());
EXPECT(!size.has_value());
}
TEST_CASE(streamer_read_variable_size_integer)
{
// 1-byte VINT: 7 data bits
// 0x80 → data=0
auto streamer = streamer_from_bytes("\x80"sv.bytes());
EXPECT_EQ(MUST(streamer.read_variable_size_integer()), 0u);
// 0x81 → data=1
streamer = streamer_from_bytes("\x81"sv.bytes());
EXPECT_EQ(MUST(streamer.read_variable_size_integer()), 1u);
// 0xFE → data=126
streamer = streamer_from_bytes("\xFE"sv.bytes());
EXPECT_EQ(MUST(streamer.read_variable_size_integer()), 126u);
// 0xFF → data=127 (max for 1-byte)
streamer = streamer_from_bytes("\xFF"sv.bytes());
EXPECT_EQ(MUST(streamer.read_variable_size_integer()), 127u);
// 2-byte VINT: 14 data bits
// 0x40 0x00 → data=0
streamer = streamer_from_bytes("\x40\x00"sv.bytes());
EXPECT_EQ(MUST(streamer.read_variable_size_integer()), 0u);
// 0x40 0x80 → data=128
streamer = streamer_from_bytes("\x40\x80"sv.bytes());
EXPECT_EQ(MUST(streamer.read_variable_size_integer()), 128u);
// 0x7F 0xFF → data=16383 (max for 2-byte)
streamer = streamer_from_bytes("\x7F\xFF"sv.bytes());
EXPECT_EQ(MUST(streamer.read_variable_size_integer()), 16383u);
// 4-byte VINT: 28 data bits
// 0x10 0x00 0x01 0x00 → data=256
streamer = streamer_from_bytes("\x10\x00\x01\x00"sv.bytes());
EXPECT_EQ(MUST(streamer.read_variable_size_integer()), 256u);
// Invalid: leading byte 0x00
streamer = streamer_from_bytes("\x00"sv.bytes());
EXPECT(streamer.read_variable_size_integer().is_error());
}
TEST_CASE(streamer_read_variable_size_signed_integer)
{
// 1-byte signed VINT: 7 data bits, bias = 2^6 - 1 = 63
// 0xBF → data=63 → 63-63 = 0
auto streamer = streamer_from_bytes("\xBF"sv.bytes());
EXPECT_EQ(MUST(streamer.read_variable_size_signed_integer()), 0);
// 0x80 → data=0 → 0-63 = -63
streamer = streamer_from_bytes("\x80"sv.bytes());
EXPECT_EQ(MUST(streamer.read_variable_size_signed_integer()), -63);
// 0xFF → data=127 → 127-63 = 64
streamer = streamer_from_bytes("\xFF"sv.bytes());
EXPECT_EQ(MUST(streamer.read_variable_size_signed_integer()), 64);
// 2-byte signed VINT: 14 data bits, bias = 2^13 - 1 = 8191
// 0x60 0x00 → data=8191 → 8191-8191 = 0
streamer = streamer_from_bytes("\x5F\xFF"sv.bytes());
EXPECT_EQ(MUST(streamer.read_variable_size_signed_integer()), 0);
// 0x40 0x00 → data=0 → 0-8191 = -8191
streamer = streamer_from_bytes("\x40\x00"sv.bytes());
EXPECT_EQ(MUST(streamer.read_variable_size_signed_integer()), -8191);
// 0x7F 0xFE → data=16382 → 16382-8191 = 8191
streamer = streamer_from_bytes("\x7F\xFE"sv.bytes());
EXPECT_EQ(MUST(streamer.read_variable_size_signed_integer()), 8191);
}
TEST_CASE(streamer_read_u64)
{
// Zero-length integer = 0
auto streamer = streamer_from_bytes("\x80"sv.bytes());
EXPECT_EQ(MUST(streamer.read_u64()), 0u);
// 1-byte integer: size=1, value=42
streamer = streamer_from_bytes("\x81\x2A"sv.bytes());
EXPECT_EQ(MUST(streamer.read_u64()), 42u);
// 2-byte integer: size=2, value=0x0100 = 256
streamer = streamer_from_bytes("\x82\x01\x00"sv.bytes());
EXPECT_EQ(MUST(streamer.read_u64()), 256u);
// 1-byte integer: size=1, value=0xFF = 255
streamer = streamer_from_bytes("\x81\xFF"sv.bytes());
EXPECT_EQ(MUST(streamer.read_u64()), 255u);
}
TEST_CASE(streamer_read_i64)
{
// Zero-length signed integer = 0
auto streamer = streamer_from_bytes("\x80"sv.bytes());
EXPECT_EQ(MUST(streamer.read_i64()), 0);
// 1-byte signed: size=1, value=0x01 = 1
streamer = streamer_from_bytes("\x81\x01"sv.bytes());
EXPECT_EQ(MUST(streamer.read_i64()), 1);
// 1-byte signed: size=1, value=0xFF = -1 (sign extended)
streamer = streamer_from_bytes("\x81\xFF"sv.bytes());
EXPECT_EQ(MUST(streamer.read_i64()), -1);
// 1-byte signed: size=1, value=0x80 = -128
streamer = streamer_from_bytes("\x81\x80"sv.bytes());
EXPECT_EQ(MUST(streamer.read_i64()), -128);
// 1-byte signed: size=1, value=0x7F = 127
streamer = streamer_from_bytes("\x81\x7F"sv.bytes());
EXPECT_EQ(MUST(streamer.read_i64()), 127);
// 2-byte signed: size=2, value=0xFF80 = -128
streamer = streamer_from_bytes("\x82\xFF\x80"sv.bytes());
EXPECT_EQ(MUST(streamer.read_i64()), -128);
// 2-byte signed: size=2, value=0x0080 = 128
streamer = streamer_from_bytes("\x82\x00\x80"sv.bytes());
EXPECT_EQ(MUST(streamer.read_i64()), 128);
}
TEST_CASE(streamer_read_float)
{
// Zero-length float = 0.0
auto streamer = streamer_from_bytes("\x80"sv.bytes());
EXPECT_EQ(MUST(streamer.read_float()), 0.0);
// 4-byte float: size=4, IEEE 754 1.0f = 0x3F800000
streamer = streamer_from_bytes("\x84\x3F\x80\x00\x00"sv.bytes());
EXPECT_EQ(MUST(streamer.read_float()), 1.0);
// 8-byte double: size=8, IEEE 754 1.0 = 0x3FF0000000000000
streamer = streamer_from_bytes("\x88\x3F\xF0\x00\x00\x00\x00\x00\x00"sv.bytes());
EXPECT_EQ(MUST(streamer.read_float()), 1.0);
// 4-byte float: size=4, IEEE 754 -1.0f = 0xBF800000
streamer = streamer_from_bytes("\x84\xBF\x80\x00\x00"sv.bytes());
EXPECT_EQ(MUST(streamer.read_float()), -1.0);
// Invalid float size (3 bytes)
streamer = streamer_from_bytes("\x83\x00\x00\x00"sv.bytes());
EXPECT(streamer.read_float().is_error());
}
TEST_CASE(master_elements_containing_crc32)
{
auto file = MUST(Core::File::open("./master_elements_containing_crc32.mkv"sv, Core::File::OpenMode::Read));
auto stream = Media::IncrementallyPopulatedStream::create_from_buffer(MUST(file->read_until_eof()));
auto matroska_reader = MUST(Media::Matroska::Reader::from_stream(stream->create_cursor()));
u64 video_track = 0;
MUST(matroska_reader.for_each_track_of_type(Media::Matroska::TrackEntry::TrackType::Video, [&](Media::Matroska::TrackEntry const& track_entry) -> Media::DecoderErrorOr<IterationDecision> {
video_track = track_entry.track_number();
return IterationDecision::Break;
}));
EXPECT_EQ(video_track, 1u);
auto iterator = MUST(matroska_reader.create_sample_iterator(stream->create_cursor(), video_track));
MUST(iterator.next_block());
MUST(matroska_reader.seek_to_random_access_point(iterator, AK::Duration::from_seconds(7)));
MUST(iterator.next_block());
}
TEST_CASE(seek_in_multi_frame_blocks)
{
auto file = MUST(Core::File::open("./test-webm-xiph-lacing.mka"sv, Core::File::OpenMode::Read));
auto stream = Media::IncrementallyPopulatedStream::create_from_buffer(MUST(file->read_until_eof()));
auto demuxer = MUST(Media::Matroska::MatroskaDemuxer::from_stream(stream));
auto optional_track = MUST(demuxer->get_preferred_track_for_type(Media::TrackType::Audio));
EXPECT(optional_track.has_value());
auto track = optional_track.release_value();
MUST(demuxer->create_context_for_track(track));
auto initial_coded_frame = MUST(demuxer->get_next_sample_for_track(track));
EXPECT(initial_coded_frame.timestamp() <= AK::Duration::zero());
auto forward_seek_time = AK::Duration::from_seconds(5);
MUST(demuxer->seek_to_most_recent_keyframe(track, forward_seek_time, Media::DemuxerSeekOptions::None));
auto coded_frame_after_forward_seek = MUST(demuxer->get_next_sample_for_track(track));
EXPECT(coded_frame_after_forward_seek.timestamp() > AK::Duration::zero());
EXPECT(coded_frame_after_forward_seek.timestamp() <= forward_seek_time);
auto backward_seek_time = AK::Duration::from_seconds(2);
MUST(demuxer->seek_to_most_recent_keyframe(track, backward_seek_time, Media::DemuxerSeekOptions::None));
auto coded_frame_after_backward_seek = MUST(demuxer->get_next_sample_for_track(track));
EXPECT(coded_frame_after_backward_seek.timestamp() > AK::Duration::zero());
EXPECT(coded_frame_after_backward_seek.timestamp() <= backward_seek_time);
}
TEST_CASE(block_group)
{
auto file = MUST(Core::File::open("./test-matroska-block-group.mkv"sv, Core::File::OpenMode::Read));
auto stream = Media::IncrementallyPopulatedStream::create_from_buffer(MUST(file->read_until_eof()));
auto matroska_reader = MUST(Media::Matroska::Reader::from_stream(stream->create_cursor()));
u64 video_track = 0;
MUST(matroska_reader.for_each_track_of_type(Media::Matroska::TrackEntry::TrackType::Video, [&](Media::Matroska::TrackEntry const& track_entry) -> Media::DecoderErrorOr<IterationDecision> {
video_track = track_entry.track_number();
return IterationDecision::Break;
}));
EXPECT_EQ(video_track, 1u);
auto iterator = MUST(matroska_reader.create_sample_iterator(stream->create_cursor(), video_track));
auto first_block = MUST(iterator.next_block());
EXPECT(first_block.duration().has_value());
EXPECT_EQ(first_block.duration()->to_milliseconds(), 33);
auto second_block = MUST(iterator.next_block());
EXPECT_EQ(second_block.timestamp().value().to_milliseconds(), 33);
EXPECT(second_block.only_keyframes());
}
TEST_CASE(fixed_size_lacing)
{
auto file = MUST(Core::File::open("./test-matroska-fixed-size-lacing.mkv"sv, Core::File::OpenMode::Read));
auto stream = Media::IncrementallyPopulatedStream::create_from_buffer(MUST(file->read_until_eof()));
auto matroska_reader = MUST(Media::Matroska::Reader::from_stream(stream->create_cursor()));
u64 video_track = 0;
MUST(matroska_reader.for_each_track_of_type(Media::Matroska::TrackEntry::TrackType::Video, [&](Media::Matroska::TrackEntry const& track_entry) -> Media::DecoderErrorOr<IterationDecision> {
video_track = track_entry.track_number();
return IterationDecision::Break;
}));
EXPECT_EQ(video_track, 1u);
auto iterator = MUST(matroska_reader.create_sample_iterator(stream->create_cursor(), video_track));
// Block 1: 4 frames × 4 bytes
auto block1 = MUST(iterator.next_block());
EXPECT_EQ(block1.timestamp().value().to_milliseconds(), 0);
EXPECT(block1.only_keyframes());
EXPECT_EQ(block1.lacing(), Media::Matroska::Block::Lacing::FixedSize);
auto frames1 = MUST(iterator.get_frames(block1));
EXPECT_EQ(frames1.size(), 4u);
for (auto const& frame : frames1)
EXPECT_EQ(frame.size(), 4u);
// Block 2: 2 frames × 8 bytes
auto block2 = MUST(iterator.next_block());
EXPECT_EQ(block2.timestamp().value().to_milliseconds(), 33);
EXPECT_EQ(block2.lacing(), Media::Matroska::Block::Lacing::FixedSize);
auto frames2 = MUST(iterator.get_frames(block2));
EXPECT_EQ(frames2.size(), 2u);
for (auto const& frame : frames2)
EXPECT_EQ(frame.size(), 8u);
// Block 3: 3 frames × 1 byte
auto block3 = MUST(iterator.next_block());
EXPECT_EQ(block3.timestamp().value().to_milliseconds(), 66);
EXPECT_EQ(block3.lacing(), Media::Matroska::Block::Lacing::FixedSize);
auto frames3 = MUST(iterator.get_frames(block3));
EXPECT_EQ(frames3.size(), 3u);
for (auto const& frame : frames3)
EXPECT_EQ(frame.size(), 1u);
}
TEST_CASE(fixed_size_lacing_invalid)
{
auto file = MUST(Core::File::open("./test-matroska-fixed-size-lacing-invalid.mkv"sv, Core::File::OpenMode::Read));
auto stream = Media::IncrementallyPopulatedStream::create_from_buffer(MUST(file->read_until_eof()));
auto matroska_reader = MUST(Media::Matroska::Reader::from_stream(stream->create_cursor()));
u64 video_track = 0;
MUST(matroska_reader.for_each_track_of_type(Media::Matroska::TrackEntry::TrackType::Video, [&](Media::Matroska::TrackEntry const& track_entry) -> Media::DecoderErrorOr<IterationDecision> {
video_track = track_entry.track_number();
return IterationDecision::Break;
}));
EXPECT_EQ(video_track, 1u);
auto iterator = MUST(matroska_reader.create_sample_iterator(stream->create_cursor(), video_track));
auto block = MUST(iterator.next_block());
EXPECT_EQ(block.lacing(), Media::Matroska::Block::Lacing::FixedSize);
auto frames_or_error = iterator.get_frames(block);
EXPECT(frames_or_error.is_error());
}
TEST_CASE(ebml_lacing)
{
auto file = MUST(Core::File::open("./test-matroska-ebml-lacing.mkv"sv, Core::File::OpenMode::Read));
auto stream = Media::IncrementallyPopulatedStream::create_from_buffer(MUST(file->read_until_eof()));
auto matroska_reader = MUST(Media::Matroska::Reader::from_stream(stream->create_cursor()));
u64 video_track = 0;
MUST(matroska_reader.for_each_track_of_type(Media::Matroska::TrackEntry::TrackType::Video, [&](Media::Matroska::TrackEntry const& track_entry) -> Media::DecoderErrorOr<IterationDecision> {
video_track = track_entry.track_number();
return IterationDecision::Break;
}));
EXPECT_EQ(video_track, 1u);
auto iterator = MUST(matroska_reader.create_sample_iterator(stream->create_cursor(), video_track));
auto block1 = MUST(iterator.next_block());
EXPECT_EQ(block1.lacing(), Media::Matroska::Block::Lacing::EBML);
auto frames1 = MUST(iterator.get_frames(block1));
EXPECT_EQ(frames1.size(), 2u);
EXPECT_EQ(frames1[0].size(), 4u);
EXPECT_EQ(frames1[1].size(), 4u);
auto block2 = MUST(iterator.next_block());
auto frames2 = MUST(iterator.get_frames(block2));
EXPECT_EQ(frames2.size(), 3u);
EXPECT_EQ(frames2[0].size(), 2u);
EXPECT_EQ(frames2[1].size(), 4u);
EXPECT_EQ(frames2[2].size(), 6u);
auto block3 = MUST(iterator.next_block());
auto frames3 = MUST(iterator.get_frames(block3));
EXPECT_EQ(frames3.size(), 3u);
EXPECT_EQ(frames3[0].size(), 6u);
EXPECT_EQ(frames3[1].size(), 4u);
EXPECT_EQ(frames3[2].size(), 2u);
auto block4 = MUST(iterator.next_block());
auto frames4 = MUST(iterator.get_frames(block4));
EXPECT_EQ(frames4.size(), 4u);
EXPECT_EQ(frames4[0].size(), 4u);
EXPECT_EQ(frames4[1].size(), 6u);
EXPECT_EQ(frames4[2].size(), 3u);
EXPECT_EQ(frames4[3].size(), 5u);
auto block5 = MUST(iterator.next_block());
auto frames5 = MUST(iterator.get_frames(block5));
EXPECT_EQ(frames5.size(), 5u);
for (auto const& frame : frames5)
EXPECT_EQ(frame.size(), 3u);
auto block6 = MUST(iterator.next_block());
auto frames6 = MUST(iterator.get_frames(block6));
EXPECT_EQ(frames6.size(), 2u);
EXPECT_EQ(frames6[0].size(), 1u);
EXPECT_EQ(frames6[1].size(), 10u);
auto block7 = MUST(iterator.next_block());
auto frames7 = MUST(iterator.get_frames(block7));
EXPECT_EQ(frames7.size(), 3u);
EXPECT_EQ(frames7[0].size(), 10u);
EXPECT_EQ(frames7[1].size(), 1u);
EXPECT_EQ(frames7[2].size(), 8u);
}
TEST_CASE(seeking)
{
auto test_files = {
"./test-matroska-seeking.mkv"sv,
"./test-matroska-seeking-without-cues.mkv"sv,
};
for (auto test_file : test_files) {
auto file = MUST(Core::File::open(test_file, Core::File::OpenMode::Read));
auto stream = Media::IncrementallyPopulatedStream::create_from_buffer(MUST(file->read_until_eof()));
auto matroska_reader = MUST(Media::Matroska::Reader::from_stream(stream->create_cursor()));
u64 video_track = 0;
MUST(matroska_reader.for_each_track_of_type(Media::Matroska::TrackEntry::TrackType::Video, [&](Media::Matroska::TrackEntry const& track_entry) -> Media::DecoderErrorOr<IterationDecision> {
video_track = track_entry.track_number();
return IterationDecision::Break;
}));
EXPECT_EQ(video_track, 1u);
auto iterator = MUST(matroska_reader.create_sample_iterator(stream->create_cursor(), video_track));
auto first_block = MUST(iterator.next_block());
EXPECT_EQ(first_block.timestamp().value().to_milliseconds(), 0);
EXPECT(first_block.only_keyframes());
iterator = MUST(matroska_reader.seek_to_random_access_point(iterator, AK::Duration::from_milliseconds(150)));
auto block_after_forward_seek = MUST(iterator.next_block());
EXPECT_EQ(block_after_forward_seek.timestamp().value().to_milliseconds(), 100);
EXPECT(block_after_forward_seek.only_keyframes());
iterator = MUST(matroska_reader.seek_to_random_access_point(iterator, AK::Duration::from_milliseconds(220)));
auto block_at_200 = MUST(iterator.next_block());
EXPECT_EQ(block_at_200.timestamp().value().to_milliseconds(), 200);
EXPECT(block_at_200.only_keyframes());
iterator = MUST(matroska_reader.seek_to_random_access_point(iterator, AK::Duration::from_milliseconds(50)));
auto block_at_0 = MUST(iterator.next_block());
EXPECT_EQ(block_at_0.timestamp().value().to_milliseconds(), 0);
EXPECT(block_at_0.only_keyframes());
iterator = MUST(matroska_reader.seek_to_random_access_point(iterator, AK::Duration::from_milliseconds(100)));
auto block_exact_100 = MUST(iterator.next_block());
EXPECT_EQ(block_exact_100.timestamp().value().to_milliseconds(), 100);
EXPECT(block_exact_100.only_keyframes());
}
}
TEST_CASE(opus_frame_duration)
{
auto file = MUST(Core::File::open("./vp9_in_webm.webm"sv, Core::File::OpenMode::Read));
auto stream = Media::IncrementallyPopulatedStream::create_from_buffer(MUST(file->read_until_eof()));
auto matroska_reader = MUST(Media::Matroska::Reader::from_stream(stream->create_cursor()));
u64 audio_track = 0;
MUST(matroska_reader.for_each_track_of_type(Media::Matroska::TrackEntry::TrackType::Audio, [&](Media::Matroska::TrackEntry const& track_entry) -> Media::DecoderErrorOr<IterationDecision> {
audio_track = track_entry.track_number();
return IterationDecision::Break;
}));
EXPECT_NE(audio_track, 0u);
auto iterator = MUST(matroska_reader.create_sample_iterator(stream->create_cursor(), audio_track));
// WebM files typically don't specify a default frame duration. However, we parse the Opus frame header
// to get the duration, so the reader should return durations of 20ms.
auto expected_duration = AK::Duration::from_milliseconds(20);
for (int i = 0; i < 5; i++) {
auto block = MUST(iterator.next_block());
VERIFY(block.duration().has_value());
EXPECT_EQ(block.duration().value(), expected_duration);
}
}
static ByteBuffer load_test_file_data(StringView path)
{
auto file = MUST(Core::File::open(path, Core::File::OpenMode::Read));
return MUST(file->read_until_eof());
}
static constexpr size_t CUES_START = 298382;
static auto create_incremental_demuxer(ByteBuffer const& file_data, NonnullRefPtr<Media::IncrementallyPopulatedStream>& stream, size_t initial_end)
{
stream = Media::IncrementallyPopulatedStream::create_empty();
stream->add_chunk_at(0, file_data.bytes().slice(0, initial_end));
stream->add_chunk_at(CUES_START, file_data.bytes().slice(CUES_START));
return MUST(Media::Matroska::MatroskaDemuxer::from_stream(stream));
}
TEST_CASE(buffered_time_ranges_full_file)
{
auto file_data = load_test_file_data("./vp9_in_webm.webm"sv);
auto stream = Media::IncrementallyPopulatedStream::create_from_buffer(file_data);
auto demuxer = MUST(Media::Matroska::MatroskaDemuxer::from_stream(stream));
auto ranges = demuxer->buffered_time_ranges();
EXPECT_EQ(ranges.size(), 1u);
EXPECT_EQ(ranges[0].start, AK::Duration::from_microseconds(500));
EXPECT_EQ(ranges[0].end, AK::Duration::from_microseconds(1021500));
}
TEST_CASE(buffered_time_ranges_incremental_thirds)
{
auto file_data = load_test_file_data("./vp9_in_webm.webm"sv);
auto start = AK::Duration::from_microseconds(500);
size_t one_third = file_data.size() / 3;
size_t two_thirds = one_third * 2;
NonnullRefPtr<Media::IncrementallyPopulatedStream> stream = Media::IncrementallyPopulatedStream::create_empty();
auto demuxer = create_incremental_demuxer(file_data, stream, one_third);
// Stage 1: first third.
auto ranges_1 = demuxer->buffered_time_ranges();
EXPECT_EQ(ranges_1.size(), 1u);
EXPECT_EQ(ranges_1[0].start, start);
EXPECT_EQ(ranges_1[0].end, AK::Duration::from_microseconds(91000));
// Stage 2: extend to two thirds.
stream->add_chunk_at(one_third, file_data.bytes().slice(one_third, two_thirds - one_third));
auto ranges_2 = demuxer->buffered_time_ranges();
EXPECT_EQ(ranges_2.size(), 1u);
EXPECT_EQ(ranges_2[0].start, start);
EXPECT(ranges_2[0].end > ranges_1[0].end);
EXPECT_EQ(ranges_2[0].end, AK::Duration::from_microseconds(571000));
// Stage 3: complete the file.
stream->add_chunk_at(two_thirds, file_data.bytes().slice(two_thirds, CUES_START - two_thirds));
stream->close();
auto ranges_3 = demuxer->buffered_time_ranges();
EXPECT_EQ(ranges_3.size(), 1u);
EXPECT_EQ(ranges_3[0].start, start);
EXPECT(ranges_3[0].end > ranges_2[0].end);
EXPECT_EQ(ranges_3[0].end, AK::Duration::from_microseconds(1021500));
}
// big_buck_bunny_5s.webm cluster layout:
// Cluster 0 (0ms): byte 482
// Cluster 1 (500ms): byte 6128
// Cluster 2 (1000ms): byte 13177
// Cluster 3 (1500ms): byte 21628
// Cluster 4 (2000ms): byte 31913
// Cluster 5 (2500ms): byte 49246
// Cluster 6 (3000ms): byte 59860
// Cluster 7 (3500ms): byte 71977
// Cluster 8 (4000ms): byte 91687
// Cluster 9 (4500ms): byte 102297
// Cues: byte 113303
// File size: 113491
static constexpr size_t BBB_CUES_START = 113303;
TEST_CASE(buffered_time_ranges_gap_then_fill)
{
auto file_data = load_test_file_data("./big_buck_bunny_5s.webm"sv);
// Buffer clusters 0-3 (0-2s) and clusters 7-9 (3.5-5s), leaving a gap at clusters 4-6 (2-3.5s).
constexpr size_t first_chunk_end = 31913;
constexpr size_t second_chunk_start = 71977;
auto stream = Media::IncrementallyPopulatedStream::create_empty();
stream->add_chunk_at(0, file_data.bytes().slice(0, first_chunk_end));
stream->add_chunk_at(BBB_CUES_START, file_data.bytes().slice(BBB_CUES_START));
stream->close();
stream->add_chunk_at(second_chunk_start, file_data.bytes().slice(second_chunk_start, BBB_CUES_START - second_chunk_start));
auto demuxer = MUST(Media::Matroska::MatroskaDemuxer::from_stream(stream));
auto ranges_gap = demuxer->buffered_time_ranges();
EXPECT_EQ(ranges_gap.size(), 2u);
EXPECT_EQ(ranges_gap[0].start, AK::Duration::zero());
EXPECT_EQ(ranges_gap[1].start, AK::Duration::from_milliseconds(3500));
// Fill the gap with clusters 4-6.
stream->add_chunk_at(first_chunk_end, file_data.bytes().slice(first_chunk_end, second_chunk_start - first_chunk_end));
auto ranges_filled = demuxer->buffered_time_ranges();
EXPECT_EQ(ranges_filled.size(), 1u);
EXPECT_EQ(ranges_filled[0].start, AK::Duration::zero());
EXPECT_EQ(ranges_filled[0].end, AK::Duration::from_nanoseconds(4999666666));
}
TEST_CASE(buffered_time_ranges_reverse_order_chunks)
{
auto file_data = load_test_file_data("./big_buck_bunny_5s.webm"sv);
// Buffer init + clusters 0-2 (0-1.5s) and clusters 7-9 (3.5-5s), then fill the middle.
constexpr size_t first_chunk_end = 21628;
constexpr size_t second_chunk_start = 71977;
NonnullRefPtr<Media::IncrementallyPopulatedStream> stream = Media::IncrementallyPopulatedStream::create_empty();
stream = Media::IncrementallyPopulatedStream::create_empty();
stream->add_chunk_at(0, file_data.bytes().slice(0, first_chunk_end));
stream->add_chunk_at(second_chunk_start, file_data.bytes().slice(second_chunk_start));
stream->close();
auto demuxer = MUST(Media::Matroska::MatroskaDemuxer::from_stream(stream));
auto ranges_1 = demuxer->buffered_time_ranges();
EXPECT_EQ(ranges_1.size(), 2u);
EXPECT_EQ(ranges_1[0].start, AK::Duration::zero());
EXPECT_EQ(ranges_1[1].start, AK::Duration::from_milliseconds(3500));
// Fill the gap with clusters 3-6.
stream->add_chunk_at(first_chunk_end, file_data.bytes().slice(first_chunk_end, second_chunk_start - first_chunk_end));
auto ranges_2 = demuxer->buffered_time_ranges();
EXPECT_EQ(ranges_2.size(), 1u);
EXPECT_EQ(ranges_2[0].start, AK::Duration::zero());
EXPECT(ranges_2[0].end > AK::Duration::from_milliseconds(4900));
}
TEST_CASE(buffered_time_ranges_repeated_query)
{
auto file_data = load_test_file_data("./vp9_in_webm.webm"sv);
auto stream = Media::IncrementallyPopulatedStream::create_from_buffer(file_data);
auto demuxer = MUST(Media::Matroska::MatroskaDemuxer::from_stream(stream));
// Query multiple times — results should be identical and stable.
auto ranges_1 = demuxer->buffered_time_ranges();
auto ranges_2 = demuxer->buffered_time_ranges();
auto ranges_3 = demuxer->buffered_time_ranges();
EXPECT_EQ(ranges_1, ranges_2);
EXPECT_EQ(ranges_2, ranges_3);
}
TEST_CASE(buffered_time_ranges_evicted_start)
{
// Simulate data eviction by calling buffered_time_ranges with the full file first,
// then with a byte range whose start is later (as if the beginning was evicted).
auto file_data = load_test_file_data("./big_buck_bunny_5s.webm"sv);
auto stream = Media::IncrementallyPopulatedStream::create_from_buffer(file_data);
auto reader = MUST(Media::Matroska::Reader::from_stream(stream->create_cursor()));
auto cursor = stream->create_cursor();
// Get buffered ranges for the full file.
Vector<Media::MediaStream::ByteRange> byte_ranges;
byte_ranges.append({ 0, file_data.size() });
auto time_ranges = reader.buffered_time_ranges(cursor, byte_ranges);
EXPECT_EQ(time_ranges.size(), 1u);
EXPECT_EQ(time_ranges[0].start, AK::Duration::zero());
EXPECT_EQ(time_ranges[0].end, AK::Duration::from_nanoseconds(4999666666));
// Simulate eviction of the first four clusters.
byte_ranges[0] = { 31913, file_data.size() };
time_ranges = reader.buffered_time_ranges(cursor, byte_ranges);
EXPECT_EQ(time_ranges.size(), 1u);
EXPECT_EQ(time_ranges[0].start, AK::Duration::from_milliseconds(2000));
EXPECT_EQ(time_ranges[0].end, AK::Duration::from_nanoseconds(4999666666));
}
TEST_CASE(buffered_time_ranges_evicted_start_appended_end)
{
// Simulate data eviction by calling buffered_time_ranges with an early portion of the file,
// then again with a new range that does not overlap.
auto file_data = load_test_file_data("./big_buck_bunny_5s.webm"sv);
auto stream = Media::IncrementallyPopulatedStream::create_from_buffer(file_data);
auto reader = MUST(Media::Matroska::Reader::from_stream(stream->create_cursor()));
auto cursor = stream->create_cursor();
// Get buffered ranges with only the first two clusters available.
Vector<Media::MediaStream::ByteRange> byte_ranges;
byte_ranges.append({ 0, 21628 });
auto time_ranges = reader.buffered_time_ranges(cursor, byte_ranges);
EXPECT_EQ(time_ranges.size(), 1u);
EXPECT_EQ(time_ranges[0].start, AK::Duration::zero());
EXPECT_EQ(time_ranges[0].end, AK::Duration::from_nanoseconds(1499666666));
// Get buffered ranges with only clusters 8 and 9 available.
byte_ranges[0] = { 91687, 113303 };
time_ranges = reader.buffered_time_ranges(cursor, byte_ranges);
EXPECT_EQ(time_ranges.size(), 1u);
EXPECT_EQ(time_ranges[0].start, AK::Duration::from_milliseconds(4000));
EXPECT_EQ(time_ranges[0].end, AK::Duration::from_nanoseconds(4999666666));
// Get buffered ranges with a byte range containing no clusters.
byte_ranges[0] = { 113303, file_data.size() };
time_ranges = reader.buffered_time_ranges(cursor, byte_ranges);
EXPECT_EQ(time_ranges.size(), 0u);
}