ladybird/Tests/LibMedia/TestParseMatroska.cpp

686 lines
30 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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);
}