1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-14 09:24:57 +00:00

LibVideo: Read Matroska lazily so that large files can start quickly

The Demuxer class was changed to return errors for more functions so
that all of the underlying reading can be done lazily. Other than that,
the demuxer interface is unchanged, and only the underlying reader was
modified.

The MatroskaDocument class is no more, and MatroskaReader's getter
functions replace it. Every MatroskaReader getter beyond the Segment
element's position is parsed lazily from the file as needed. This means
that all getter functions can return DecoderErrors which must be
handled by callers.
This commit is contained in:
Zaggy1024 2022-11-11 17:14:27 -06:00 committed by Andreas Kling
parent f4c476b26f
commit 393cfdd5c5
11 changed files with 576 additions and 310 deletions

View file

@ -5,55 +5,64 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Function.h>
#include <LibMain/Main.h>
#include <LibVideo/Containers/Matroska/Reader.h>
#define TRY_PARSE(expression) \
({ \
auto _temporary_result = ((expression)); \
if (_temporary_result.is_error()) [[unlikely]] { \
outln("Encountered a parsing error: {}", _temporary_result.error().string_literal()); \
return Error::from_string_literal("Failed to parse :("); \
} \
_temporary_result.release_value(); \
})
ErrorOr<int> serenity_main(Main::Arguments)
{
auto document_result = Video::Matroska::Reader::parse_matroska_from_file("/home/anon/Videos/test-webm.webm"sv);
if (document_result.is_error()) {
outln("Encountered an error during parsing: {}", document_result.release_error().string_literal());
return Error::from_string_literal("Failed to parse :(");
}
auto document = document_result.release_value();
auto reader = TRY_PARSE(Video::Matroska::Reader::from_file("/home/anon/Videos/test-webm.webm"sv));
outln("DocType is {}", document->header().doc_type.characters());
outln("DocTypeVersion is {}", document->header().doc_type_version);
auto segment_information = document->segment_information();
if (segment_information.has_value()) {
outln("Timestamp scale is {}", segment_information.value().timestamp_scale());
outln("Muxing app is \"{}\"", segment_information.value().muxing_app().as_string().to_string().characters());
outln("Writing app is \"{}\"", segment_information.value().writing_app().as_string().to_string().characters());
}
outln("Document has {} tracks", document->tracks().size());
for (auto const& track_entry : document->tracks()) {
auto const& track = *track_entry.value;
outln("\tTrack #{} with TrackID {}", track.track_number(), track.track_uid());
outln("\tTrack has TrackType {}", static_cast<u8>(track.track_type()));
outln("\tTrack has Language \"{}\"", track.language().characters());
outln("\tTrack has CodecID \"{}\"", track.codec_id().characters());
outln("DocType is {}", reader.header().doc_type.characters());
outln("DocTypeVersion is {}", reader.header().doc_type_version);
auto segment_information = TRY_PARSE(reader.segment_information());
outln("Timestamp scale is {}", segment_information.timestamp_scale());
outln("Muxing app is \"{}\"", segment_information.muxing_app().as_string().to_string().characters());
outln("Writing app is \"{}\"", segment_information.writing_app().as_string().to_string().characters());
if (track.track_type() == Video::Matroska::TrackEntry::TrackType::Video) {
auto const video_track = track.video_track().value();
outln("Document has {} tracks", TRY_PARSE(reader.track_count()));
TRY_PARSE(reader.for_each_track([&](Video::Matroska::TrackEntry const& track_entry) -> Video::DecoderErrorOr<IterationDecision> {
outln("\tTrack #{} with TrackID {}", track_entry.track_number(), track_entry.track_uid());
outln("\tTrack has TrackType {}", static_cast<u8>(track_entry.track_type()));
outln("\tTrack has Language \"{}\"", track_entry.language().characters());
outln("\tTrack has CodecID \"{}\"", track_entry.codec_id().characters());
if (track_entry.track_type() == Video::Matroska::TrackEntry::TrackType::Video) {
auto const video_track = track_entry.video_track().value();
outln("\t\tVideo is {} pixels wide by {} pixels tall", video_track.pixel_width, video_track.pixel_height);
} else if (track.track_type() == Video::Matroska::TrackEntry::TrackType::Audio) {
auto const audio_track = track.audio_track().value();
} else if (track_entry.track_type() == Video::Matroska::TrackEntry::TrackType::Audio) {
auto const audio_track = track_entry.audio_track().value();
outln("\t\tAudio has {} channels with a bit depth of {}", audio_track.channels, audio_track.bit_depth);
}
}
outln("Document has {} clusters", document->clusters().size());
for (auto const& cluster : document->clusters()) {
outln("\tCluster timestamp is {}", cluster.timestamp());
outln("\tBlocks:");
auto iterator = TRY(reader.create_sample_iterator(track_entry.track_number()));
outln("\tCluster has {} blocks", cluster.blocks().size());
for (auto const& block : cluster.blocks()) {
(void)block;
outln("\t\tBlock for track #{} has {} frames", block.track_number(), block.frame_count());
outln("\t\tBlock's timestamp is {}", block.timestamp());
outln("\t\tBlock has lacing {}", static_cast<u8>(block.lacing()));
while (true) {
auto block_result = iterator.next_block();
if (block_result.is_error()) {
if (block_result.error().category() == Video::DecoderErrorCategory::EndOfStream)
break;
return block_result.release_error();
}
auto block = block_result.release_value();
outln("\t\tBlock at timestamp {}:", iterator.current_cluster().timestamp() + block.timestamp());
outln("\t\t\tContains {} frames", block.frame_count());
outln("\t\t\tLacing is {}", static_cast<u8>(block.lacing()));
}
}
return IterationDecision::Continue;
}));
return 0;
}