ladybird/Tests/LibMedia/TestParseMatroska.cpp

250 lines
12 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 <LibTest/TestCase.h>
#include <LibMedia/Containers/Matroska/Reader.h>
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->create_cursor()));
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, stream->create_cursor()));
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().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().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().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().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().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().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().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().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().to_milliseconds(), 100);
EXPECT(block_exact_100.only_keyframes());
}
}