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
Notes: sideshowbarker 2024-07-17 08:59:18 +09:00
11 changed files with 576 additions and 310 deletions

View File

@ -9,8 +9,12 @@
extern "C" int LLVMFuzzerTestOneInput(u8 const* data, size_t size)
{
auto matroska_document = Video::Matroska::Reader::parse_matroska_from_data(data, size);
if (!matroska_document)
auto matroska_reader_result = Video::Matroska::Reader::from_data({ data, size });
if (matroska_reader_result.is_error())
return -1;
if (auto result = matroska_reader_result.value().segment_information(); result.is_error())
return -1;
if (auto result = matroska_reader_result.value().track_count(); result.is_error())
return -1;
return 0;
}

View File

@ -11,29 +11,33 @@
static void decode_video(StringView path, size_t expected_frame_count)
{
auto matroska_document = MUST(Video::Matroska::Reader::parse_matroska_from_file(path));
auto video_track_optional = matroska_document->track_for_track_type(Video::Matroska::TrackEntry::TrackType::Video);
VERIFY(video_track_optional.has_value());
auto video_track_entry = video_track_optional.value();
auto matroska_reader = MUST(Video::Matroska::Reader::from_file(path));
u64 video_track = 0;
MUST(matroska_reader.for_each_track_of_type(Video::Matroska::TrackEntry::TrackType::Video, [&](Video::Matroska::TrackEntry const& track_entry) -> Video::DecoderErrorOr<IterationDecision> {
video_track = track_entry.track_number();
return IterationDecision::Break;
}));
VERIFY(video_track != 0);
auto iterator = MUST(matroska_reader.create_sample_iterator(video_track));
size_t frame_count = 0;
size_t cluster_index, block_index, frame_index;
Video::VP9::Decoder vp9_decoder;
for (cluster_index = 0; cluster_index < matroska_document->clusters().size(); cluster_index++) {
auto const& cluster = matroska_document->clusters()[cluster_index];
for (block_index = 0; block_index < cluster.blocks().size(); block_index++) {
auto const& block = cluster.blocks()[block_index];
if (block.track_number() != video_track_entry.track_number())
continue;
while (frame_count <= expected_frame_count) {
auto block_result = iterator.next_block();
if (block_result.is_error() && block_result.error().category() == Video::DecoderErrorCategory::EndOfStream) {
VERIFY(frame_count == expected_frame_count);
return;
}
for (frame_index = 0; frame_index < block.frames().size(); frame_index++) {
MUST(vp9_decoder.receive_sample(block.frames()[frame_index]));
frame_count++;
}
auto block = block_result.release_value();
for (auto const& frame : block.frames()) {
MUST(vp9_decoder.receive_sample(frame));
frame_count++;
}
}
VERIFY(frame_count == expected_frame_count);
VERIFY_NOT_REACHED();
}
TEST_CASE(webm_in_vp9)

View File

@ -18,7 +18,7 @@ class Demuxer {
public:
virtual ~Demuxer() = default;
virtual Vector<Track> get_tracks_for_type(TrackType type) = 0;
virtual DecoderErrorOr<Vector<Track>> get_tracks_for_type(TrackType type) = 0;
DecoderErrorOr<NonnullOwnPtr<VideoSample>> get_next_video_sample_for_track(Track track)
{
@ -30,7 +30,7 @@ public:
virtual DecoderErrorOr<void> seek_to_most_recent_keyframe(Track track, size_t timestamp) = 0;
virtual Time duration() = 0;
virtual DecoderErrorOr<Time> duration() = 0;
protected:
virtual DecoderErrorOr<NonnullOwnPtr<Sample>> get_next_sample_for_track(Track track) = 0;

View File

@ -150,8 +150,6 @@ public:
EBML = 0b11,
};
Block() = default;
u64 track_number() const { return m_track_number; }
void set_track_number(u64 track_number) { m_track_number = track_number; }
i16 timestamp() const { return m_timestamp; }
@ -164,10 +162,11 @@ public:
void set_lacing(Lacing lacing) { m_lacing = lacing; }
bool discardable() const { return m_discardable; }
void set_discardable(bool discardable) { m_discardable = discardable; }
void set_frames(Vector<ReadonlyBytes>&& frames) { m_frames = move(frames); }
ReadonlyBytes const& frame(size_t index) const { return frames()[index]; }
u64 frame_count() const { return m_frames.size(); }
Vector<ByteBuffer> const& frames() const { return m_frames; }
ByteBuffer const& frame(size_t index) const { return frames()[index]; }
void add_frame(ByteBuffer frame) { m_frames.append(move(frame)); }
Vector<ReadonlyBytes> const& frames() const { return m_frames; }
private:
u64 m_track_number { 0 };
@ -176,64 +175,16 @@ private:
bool m_invisible { false };
Lacing m_lacing { None };
bool m_discardable { true };
Vector<ByteBuffer> m_frames;
Vector<ReadonlyBytes> m_frames;
};
class Cluster {
public:
u64 timestamp() const { return m_timestamp; }
void set_timestamp(u64 timestamp) { m_timestamp = timestamp; }
NonnullOwnPtrVector<Block>& blocks() { return m_blocks; }
NonnullOwnPtrVector<Block> const& blocks() const { return m_blocks; }
private:
u64 m_timestamp { 0 };
NonnullOwnPtrVector<Block> m_blocks;
};
class MatroskaDocument {
public:
explicit MatroskaDocument(EBMLHeader m_header)
: m_header(move(m_header))
{
}
EBMLHeader const& header() const { return m_header; }
Optional<SegmentInformation> segment_information() const
{
if (!m_segment_information)
return {};
return *m_segment_information;
}
void set_segment_information(OwnPtr<SegmentInformation> segment_information) { m_segment_information = move(segment_information); }
HashMap<u64, NonnullOwnPtr<TrackEntry>> const& tracks() const { return m_tracks; }
Optional<TrackEntry> track_for_track_number(u64 track_number) const
{
auto track = m_tracks.get(track_number);
if (!track.has_value())
return {};
return *track.value();
}
Optional<TrackEntry> track_for_track_type(TrackEntry::TrackType type) const
{
for (auto& track_entry : m_tracks) {
if (track_entry.value->track_type() == type)
return *track_entry.value;
}
return {};
}
void add_track(u64 track_number, NonnullOwnPtr<TrackEntry> track)
{
m_tracks.set(track_number, move(track));
}
NonnullOwnPtrVector<Cluster>& clusters() { return m_clusters; }
private:
EBMLHeader m_header;
OwnPtr<SegmentInformation> m_segment_information;
HashMap<u64, NonnullOwnPtr<TrackEntry>> m_tracks;
NonnullOwnPtrVector<Cluster> m_clusters;
u64 m_timestamp;
};
}

View File

@ -10,15 +10,15 @@ namespace Video::Matroska {
DecoderErrorOr<NonnullOwnPtr<MatroskaDemuxer>> MatroskaDemuxer::from_file(StringView filename)
{
return make<MatroskaDemuxer>(TRY(Reader::parse_matroska_from_file(filename)));
return make<MatroskaDemuxer>(TRY(Reader::from_file(filename)));
}
DecoderErrorOr<NonnullOwnPtr<MatroskaDemuxer>> MatroskaDemuxer::from_data(ReadonlyBytes data)
{
return make<MatroskaDemuxer>(TRY(Reader::parse_matroska_from_data(data)));
return make<MatroskaDemuxer>(TRY(Reader::from_data(data)));
}
Vector<Track> MatroskaDemuxer::get_tracks_for_type(TrackType type)
DecoderErrorOr<Vector<Track>> MatroskaDemuxer::get_tracks_for_type(TrackType type)
{
TrackEntry::TrackType matroska_track_type;
@ -35,21 +35,20 @@ Vector<Track> MatroskaDemuxer::get_tracks_for_type(TrackType type)
}
Vector<Track> tracks;
for (auto const& track_table_entry : m_document->tracks()) {
auto const& track_entry = track_table_entry.value;
if (matroska_track_type == track_entry->track_type())
tracks.append(Track(type, track_entry->track_number()));
}
// FIXME: Sort the vector, presumably the hashtable will not have a consistent order.
TRY(m_reader.for_each_track_of_type(matroska_track_type, [&](TrackEntry const& track_entry) -> DecoderErrorOr<IterationDecision> {
VERIFY(track_entry.track_type() == matroska_track_type);
DECODER_TRY_ALLOC(tracks.try_append(Track(type, track_entry.track_number())));
return IterationDecision::Continue;
}));
return tracks;
}
ErrorOr<MatroskaDemuxer::TrackStatus*> MatroskaDemuxer::get_track_status(Track track)
DecoderErrorOr<MatroskaDemuxer::TrackStatus*> MatroskaDemuxer::get_track_status(Track track)
{
if (!m_track_statuses.contains(track))
TRY(m_track_statuses.try_set(track, TrackStatus()));
if (!m_track_statuses.contains(track)) {
auto iterator = TRY(m_reader.create_sample_iterator(track.identifier()));
DECODER_TRY_ALLOC(m_track_statuses.try_set(track, { iterator }));
}
return &m_track_statuses.get(track).release_value();
}
@ -57,10 +56,8 @@ ErrorOr<MatroskaDemuxer::TrackStatus*> MatroskaDemuxer::get_track_status(Track t
DecoderErrorOr<void> MatroskaDemuxer::seek_to_most_recent_keyframe(Track track, size_t timestamp)
{
if (timestamp == 0) {
auto track_status = DECODER_TRY_ALLOC(get_track_status(track));
track_status->m_cluster_index = 0;
track_status->m_block_index = 0;
track_status->m_frame_index = 0;
// Removing the track status will cause us to start from the beginning.
m_track_statuses.remove(track);
return {};
}
@ -69,41 +66,26 @@ DecoderErrorOr<void> MatroskaDemuxer::seek_to_most_recent_keyframe(Track track,
DecoderErrorOr<NonnullOwnPtr<Sample>> MatroskaDemuxer::get_next_sample_for_track(Track track)
{
auto track_status = DECODER_TRY_ALLOC(get_track_status(track));
// FIXME: This makes a copy of the sample, which shouldn't be necessary.
// Matroska should make a RefPtr<ByteBuffer>, probably.
auto& status = *TRY(get_track_status(track));
for (; track_status->m_cluster_index < m_document->clusters().size(); track_status->m_cluster_index++) {
auto const& cluster = m_document->clusters()[track_status->m_cluster_index];
for (; track_status->m_block_index < cluster.blocks().size(); track_status->m_block_index++) {
auto const& block = cluster.blocks()[track_status->m_block_index];
if (block.track_number() != track.identifier())
continue;
if (track_status->m_frame_index < block.frame_count()) {
switch (track.type()) {
case TrackType::Video: {
// FIXME: This makes a copy of the sample, which shouldn't be necessary.
// Matroska should make a RefPtr<ByteBuffer>, probably.
auto cicp = m_document->track_for_track_number(track.identifier())->video_track()->color_format.to_cicp();
Time timestamp = Time::from_nanoseconds((cluster.timestamp() + block.timestamp()) * m_document->segment_information()->timestamp_scale());
return make<VideoSample>(block.frame(track_status->m_frame_index++), cicp, timestamp);
}
default:
return DecoderError::not_implemented();
}
}
track_status->m_frame_index = 0;
}
track_status->m_block_index = 0;
if (!status.block.has_value() || status.frame_index >= status.block->frame_count()) {
status.block = TRY(status.iterator.next_block());
status.frame_index = 0;
}
return DecoderError::with_description(DecoderErrorCategory::EndOfStream, "End of stream reached."sv);
auto const& cluster = status.iterator.current_cluster();
Time timestamp = Time::from_nanoseconds((cluster.timestamp() + status.block->timestamp()) * TRY(m_reader.segment_information()).timestamp_scale());
auto cicp = TRY(m_reader.track_for_track_number(track.identifier())).video_track()->color_format.to_cicp();
return make<VideoSample>(status.block->frame(status.frame_index++), cicp, timestamp);
}
Time MatroskaDemuxer::duration()
DecoderErrorOr<Time> MatroskaDemuxer::duration()
{
if (!m_document->segment_information().has_value())
auto segment_information = TRY(m_reader.segment_information());
if (!segment_information.duration().has_value())
return Time::zero();
if (!m_document->segment_information()->duration().has_value())
return Time::zero();
return Time::from_nanoseconds(m_document->segment_information()->duration().value() * m_document->segment_information()->timestamp_scale());
return Time::from_nanoseconds(static_cast<i64>(segment_information.duration().value() * segment_information.timestamp_scale()));
}
}

View File

@ -20,30 +20,30 @@ public:
static DecoderErrorOr<NonnullOwnPtr<MatroskaDemuxer>> from_file(StringView filename);
static DecoderErrorOr<NonnullOwnPtr<MatroskaDemuxer>> from_data(ReadonlyBytes data);
MatroskaDemuxer(NonnullOwnPtr<MatroskaDocument>&& document)
: m_document(move(document))
MatroskaDemuxer(Reader&& reader)
: m_reader(move(reader))
{
}
Vector<Track> get_tracks_for_type(TrackType type) override;
DecoderErrorOr<Vector<Track>> get_tracks_for_type(TrackType type) override;
DecoderErrorOr<void> seek_to_most_recent_keyframe(Track track, size_t timestamp) override;
Time duration() override;
DecoderErrorOr<Time> duration() override;
protected:
DecoderErrorOr<NonnullOwnPtr<Sample>> get_next_sample_for_track(Track track) override;
private:
struct TrackStatus {
size_t m_cluster_index { 0 };
size_t m_block_index { 0 };
size_t m_frame_index { 0 };
SampleIterator iterator;
Optional<Block> block {};
size_t frame_index { 0 };
};
ErrorOr<TrackStatus*> get_track_status(Track track);
DecoderErrorOr<TrackStatus*> get_track_status(Track track);
NonnullOwnPtr<MatroskaDocument> m_document;
Reader m_reader;
HashMap<Track, TrackStatus> m_track_statuses;
};

View File

@ -7,6 +7,7 @@
#include <AK/Function.h>
#include <AK/Optional.h>
#include <AK/Time.h>
#include <AK/Utf8View.h>
#include <LibCore/MappedFile.h>
@ -16,10 +17,18 @@ namespace Video::Matroska {
#define TRY_READ(expression) DECODER_TRY(DecoderErrorCategory::Corrupted, expression)
// Elements IDs and types are listed at this URL:
// https://www.matroska.org/technical/elements.html
constexpr u32 EBML_MASTER_ELEMENT_ID = 0x1A45DFA3;
constexpr u32 SEGMENT_ELEMENT_ID = 0x18538067;
constexpr u32 DOCTYPE_ELEMENT_ID = 0x4282;
constexpr u32 DOCTYPE_VERSION_ELEMENT_ID = 0x4287;
constexpr u32 SEEK_HEAD_ELEMENT_ID = 0x114D9B74;
constexpr u32 SEEK_ELEMENT_ID = 0x4DBB;
constexpr u32 SEEK_ID_ELEMENT_ID = 0x53AB;
constexpr u32 SEEK_POSITION_ELEMENT_ID = 0x53AC;
constexpr u32 SEGMENT_INFORMATION_ELEMENT_ID = 0x1549A966;
constexpr u32 TRACK_ELEMENT_ID = 0x1654AE6B;
constexpr u32 CLUSTER_ELEMENT_ID = 0x1F43B675;
@ -55,19 +64,22 @@ constexpr u32 BIT_DEPTH_ID = 0x6264;
constexpr u32 SIMPLE_BLOCK_ID = 0xA3;
constexpr u32 TIMESTAMP_ID = 0xE7;
DecoderErrorOr<NonnullOwnPtr<MatroskaDocument>> Reader::parse_matroska_from_file(StringView path)
DecoderErrorOr<Reader> Reader::from_file(StringView path)
{
auto mapped_file = DECODER_TRY(DecoderErrorCategory::IO, Core::MappedFile::map(path));
return parse_matroska_from_data(mapped_file->bytes());
auto reader = TRY(from_data(mapped_file->bytes()));
reader.m_mapped_file = mapped_file;
return reader;
}
DecoderErrorOr<NonnullOwnPtr<MatroskaDocument>> Reader::parse_matroska_from_data(ReadonlyBytes data)
DecoderErrorOr<Reader> Reader::from_data(ReadonlyBytes data)
{
Reader reader(data);
return reader.parse();
TRY(reader.parse_initial_data());
return reader;
}
static DecoderErrorOr<void> parse_master_element(Streamer& streamer, [[maybe_unused]] StringView element_name, Function<DecoderErrorOr<void>(u64)> element_consumer)
static DecoderErrorOr<void> parse_master_element(Streamer& streamer, [[maybe_unused]] StringView element_name, Function<DecoderErrorOr<IterationDecision>(u64, size_t)> element_consumer)
{
auto element_data_size = TRY_READ(streamer.read_variable_size_integer());
dbgln_if(MATROSKA_DEBUG, "{} has {} octets of data.", element_name, element_data_size);
@ -76,9 +88,15 @@ static DecoderErrorOr<void> parse_master_element(Streamer& streamer, [[maybe_unu
while (streamer.octets_read() < element_data_size) {
dbgln_if(MATROSKA_TRACE_DEBUG, "====== Reading element ======");
auto element_id = TRY_READ(streamer.read_variable_size_integer(false));
dbgln_if(MATROSKA_TRACE_DEBUG, "{:s} element ID is {:#010x}\n", element_name, element_id);
auto element_position = streamer.position();
dbgln_if(MATROSKA_TRACE_DEBUG, "{:s} element ID is {:#010x}", element_name, element_id);
auto result = element_consumer(element_id, element_position);
if (result.is_error())
return DecoderError::format(result.error().category(), "{} -> {}", element_name, result.error().description());
if (result.release_value() == IterationDecision::Break)
break;
TRY(element_consumer(element_id));
dbgln_if(MATROSKA_TRACE_DEBUG, "Read {} octets of the {} so far.", streamer.octets_read(), element_name);
}
streamer.pop_octets_read();
@ -89,7 +107,7 @@ static DecoderErrorOr<void> parse_master_element(Streamer& streamer, [[maybe_unu
static DecoderErrorOr<EBMLHeader> parse_ebml_header(Streamer& streamer)
{
EBMLHeader header;
TRY(parse_master_element(streamer, "Header"sv, [&](u64 element_id) -> DecoderErrorOr<void> {
TRY(parse_master_element(streamer, "Header"sv, [&](u64 element_id, size_t) -> DecoderErrorOr<IterationDecision> {
switch (element_id) {
case DOCTYPE_ELEMENT_ID:
header.doc_type = TRY_READ(streamer.read_string());
@ -103,93 +121,198 @@ static DecoderErrorOr<EBMLHeader> parse_ebml_header(Streamer& streamer)
TRY_READ(streamer.read_unknown_element());
}
return {};
return IterationDecision::Continue;
}));
return header;
}
static DecoderErrorOr<NonnullOwnPtr<SegmentInformation>> parse_information(Streamer& streamer);
static DecoderErrorOr<void> parse_tracks(Streamer& streamer, MatroskaDocument& matroska_document);
static DecoderErrorOr<NonnullOwnPtr<Cluster>> parse_cluster(Streamer& streamer);
static DecoderErrorOr<void> parse_segment_elements(Streamer& streamer, MatroskaDocument& matroska_document)
DecoderErrorOr<void> Reader::parse_initial_data()
{
dbgln_if(MATROSKA_DEBUG, "Parsing segment elements");
return parse_master_element(streamer, "Segment"sv, [&](u64 element_id) -> DecoderErrorOr<void> {
switch (element_id) {
case SEGMENT_INFORMATION_ELEMENT_ID:
matroska_document.set_segment_information(TRY(parse_information(streamer)));
break;
case TRACK_ELEMENT_ID:
TRY(parse_tracks(streamer, matroska_document));
break;
case CLUSTER_ELEMENT_ID:
matroska_document.clusters().append(TRY(parse_cluster(streamer)));
break;
default:
TRY_READ(streamer.read_unknown_element());
}
return {};
});
}
DecoderErrorOr<NonnullOwnPtr<MatroskaDocument>> Reader::parse()
{
auto first_element_id = TRY_READ(m_streamer.read_variable_size_integer(false));
Streamer streamer { m_data };
auto first_element_id = TRY_READ(streamer.read_variable_size_integer(false));
dbgln_if(MATROSKA_TRACE_DEBUG, "First element ID is {:#010x}\n", first_element_id);
if (first_element_id != EBML_MASTER_ELEMENT_ID)
return DecoderError::corrupted("First element was not an EBML header"sv);
auto header = TRY(parse_ebml_header(m_streamer));
m_header = TRY(parse_ebml_header(streamer));
dbgln_if(MATROSKA_DEBUG, "Parsed EBML header");
auto root_element_id = TRY_READ(m_streamer.read_variable_size_integer(false));
auto root_element_id = TRY_READ(streamer.read_variable_size_integer(false));
if (root_element_id != SEGMENT_ELEMENT_ID)
return DecoderError::corrupted("Second element was not a segment element"sv);
auto matroska_document = make<MatroskaDocument>(header);
TRY(parse_segment_elements(m_streamer, *matroska_document));
return matroska_document;
m_segment_contents_size = TRY_READ(streamer.read_variable_size_integer());
m_segment_contents_position = streamer.position();
dbgln_if(true, "Segment is at {} with size {}, available size is {}", m_segment_contents_position, m_segment_contents_size, m_data.size() - m_segment_contents_position);
m_segment_contents_size = min(m_segment_contents_size, m_data.size() - m_segment_contents_position);
return {};
}
static DecoderErrorOr<NonnullOwnPtr<SegmentInformation>> parse_information(Streamer& streamer)
static DecoderErrorOr<void> parse_seek_head(Streamer& streamer, size_t base_position, HashMap<u32, size_t>& table)
{
auto segment_information = make<SegmentInformation>();
TRY(parse_master_element(streamer, "Segment Information"sv, [&](u64 element_id) -> DecoderErrorOr<void> {
return parse_master_element(streamer, "SeekHead"sv, [&](u64 seek_head_child_id, size_t) -> DecoderErrorOr<IterationDecision> {
if (seek_head_child_id == SEEK_ELEMENT_ID) {
Optional<u64> seek_id;
Optional<u64> seek_position;
TRY(parse_master_element(streamer, "Seek"sv, [&](u64 seek_entry_child_id, size_t) -> DecoderErrorOr<IterationDecision> {
switch (seek_entry_child_id) {
case SEEK_ID_ELEMENT_ID:
seek_id = TRY_READ(streamer.read_u64());
dbgln_if(MATROSKA_TRACE_DEBUG, "Read Seek Element ID value {:#010x}", seek_id.value());
break;
case SEEK_POSITION_ELEMENT_ID:
seek_position = TRY_READ(streamer.read_u64());
dbgln_if(MATROSKA_TRACE_DEBUG, "Read Seek Position value {}", seek_position.value());
break;
default:
TRY_READ(streamer.read_unknown_element());
}
return IterationDecision::Continue;
}));
if (!seek_id.has_value())
return DecoderError::corrupted("Seek entry is missing the element ID"sv);
if (!seek_position.has_value())
return DecoderError::corrupted("Seek entry is missing the seeking position"sv);
if (seek_id.value() > NumericLimits<u32>::max())
return DecoderError::corrupted("Seek entry's element ID is too large"sv);
dbgln_if(MATROSKA_TRACE_DEBUG, "Seek entry found with ID {:#010x} and position {} offset from SeekHead at {}", seek_id.value(), seek_position.value(), base_position);
// FIXME: SeekHead can reference another SeekHead, we should recursively parse all SeekHeads.
if (table.contains(seek_id.value())) {
dbgln_if(MATROSKA_DEBUG, "Warning: Duplicate seek entry with ID {:#010x} at position {}", seek_id.value(), seek_position.value());
return IterationDecision::Continue;
}
DECODER_TRY_ALLOC(table.try_set(seek_id.release_value(), base_position + seek_position.release_value()));
} else {
dbgln_if(MATROSKA_TRACE_DEBUG, "Unknown SeekHead child element ID {:#010x}", seek_head_child_id);
}
return IterationDecision::Continue;
});
}
DecoderErrorOr<Optional<size_t>> Reader::find_first_top_level_element_with_id([[maybe_unused]] StringView element_name, u32 element_id)
{
dbgln_if(MATROSKA_DEBUG, "====== Finding element {} with ID {:#010x} ======", element_name, element_id);
if (m_seek_entries.contains(element_id)) {
dbgln_if(MATROSKA_TRACE_DEBUG, "Cache hit!");
return m_seek_entries.get(element_id).release_value();
}
Streamer streamer { m_data };
if (m_last_top_level_element_position != 0)
TRY_READ(streamer.seek_to_position(m_last_top_level_element_position));
else
TRY_READ(streamer.seek_to_position(m_segment_contents_position));
Optional<size_t> position;
while (streamer.position() < m_segment_contents_position + m_segment_contents_size) {
auto found_element_id = TRY_READ(streamer.read_variable_size_integer(false));
auto found_element_position = streamer.position();
dbgln_if(MATROSKA_TRACE_DEBUG, "Found element ID {:#010x} with position {}.", found_element_id, found_element_position);
if (found_element_id == SEEK_HEAD_ELEMENT_ID) {
dbgln_if(MATROSKA_TRACE_DEBUG, "Found SeekHead, parsing it into the lookup table.");
m_seek_entries.clear();
TRY(parse_seek_head(streamer, found_element_position, m_seek_entries));
m_last_top_level_element_position = 0;
if (m_seek_entries.contains(element_id)) {
dbgln_if(MATROSKA_TRACE_DEBUG, "SeekHead hit!");
position = m_seek_entries.get(element_id).release_value();
break;
}
continue;
}
auto result = streamer.read_unknown_element();
if (result.is_error())
return DecoderError::format(DecoderErrorCategory::Corrupted, "While seeking to {}: {}", element_name, result.release_error().string_literal());
m_last_top_level_element_position = streamer.position();
DECODER_TRY_ALLOC(m_seek_entries.try_set(found_element_id, found_element_position));
if (found_element_id == element_id) {
position = found_element_position;
break;
}
dbgln_if(MATROSKA_TRACE_DEBUG, "Skipped to position {}.", m_last_top_level_element_position);
}
return position;
}
static DecoderErrorOr<SegmentInformation> parse_information(Streamer& streamer)
{
SegmentInformation segment_information;
TRY(parse_master_element(streamer, "Segment Information"sv, [&](u64 element_id, size_t) -> DecoderErrorOr<IterationDecision> {
switch (element_id) {
case TIMESTAMP_SCALE_ID:
segment_information->set_timestamp_scale(TRY_READ(streamer.read_u64()));
dbgln_if(MATROSKA_DEBUG, "Read TimestampScale attribute: {}", segment_information->timestamp_scale());
segment_information.set_timestamp_scale(TRY_READ(streamer.read_u64()));
dbgln_if(MATROSKA_DEBUG, "Read TimestampScale attribute: {}", segment_information.timestamp_scale());
break;
case MUXING_APP_ID:
segment_information->set_muxing_app(TRY_READ(streamer.read_string()));
dbgln_if(MATROSKA_DEBUG, "Read MuxingApp attribute: {}", segment_information->muxing_app().as_string());
segment_information.set_muxing_app(TRY_READ(streamer.read_string()));
dbgln_if(MATROSKA_DEBUG, "Read MuxingApp attribute: {}", segment_information.muxing_app().as_string());
break;
case WRITING_APP_ID:
segment_information->set_writing_app(TRY_READ(streamer.read_string()));
dbgln_if(MATROSKA_DEBUG, "Read WritingApp attribute: {}", segment_information->writing_app().as_string());
segment_information.set_writing_app(TRY_READ(streamer.read_string()));
dbgln_if(MATROSKA_DEBUG, "Read WritingApp attribute: {}", segment_information.writing_app().as_string());
break;
case DURATION_ID:
segment_information->set_duration(TRY_READ(streamer.read_float()));
dbgln_if(MATROSKA_DEBUG, "Read Duration attribute: {}", segment_information->duration().value());
segment_information.set_duration(TRY_READ(streamer.read_float()));
dbgln_if(MATROSKA_DEBUG, "Read Duration attribute: {}", segment_information.duration().value());
break;
default:
TRY_READ(streamer.read_unknown_element());
}
return {};
return IterationDecision::Continue;
}));
return segment_information;
}
DecoderErrorOr<SegmentInformation> Reader::segment_information()
{
if (m_segment_information.has_value())
return m_segment_information.value();
auto position = TRY(find_first_top_level_element_with_id("Segment Information"sv, SEGMENT_INFORMATION_ELEMENT_ID));
if (!position.has_value())
return DecoderError::corrupted("No Segment Information element found"sv);
Streamer streamer { m_data };
TRY_READ(streamer.seek_to_position(position.release_value()));
m_segment_information = TRY(parse_information(streamer));
return m_segment_information.value();
}
DecoderErrorOr<void> Reader::ensure_tracks_are_parsed()
{
if (!m_tracks.is_empty())
return {};
auto position = TRY(find_first_top_level_element_with_id("Tracks"sv, TRACK_ELEMENT_ID));
if (!position.has_value())
return DecoderError::corrupted("No Tracks element found"sv);
Streamer streamer { m_data };
TRY_READ(streamer.seek_to_position(position.release_value()));
TRY(parse_tracks(streamer));
return {};
}
static DecoderErrorOr<TrackEntry::ColorFormat> parse_video_color_information(Streamer& streamer)
{
TrackEntry::ColorFormat color_format {};
TRY(parse_master_element(streamer, "Colour"sv, [&](u64 element_id) -> DecoderErrorOr<void> {
TRY(parse_master_element(streamer, "Colour"sv, [&](u64 element_id, size_t) -> DecoderErrorOr<IterationDecision> {
switch (element_id) {
case PRIMARIES_ID:
color_format.color_primaries = static_cast<ColorPrimaries>(TRY_READ(streamer.read_u64()));
@ -211,7 +334,7 @@ static DecoderErrorOr<TrackEntry::ColorFormat> parse_video_color_information(Str
TRY_READ(streamer.read_unknown_element());
}
return {};
return IterationDecision::Continue;
}));
return color_format;
@ -221,7 +344,7 @@ static DecoderErrorOr<TrackEntry::VideoTrack> parse_video_track_information(Stre
{
TrackEntry::VideoTrack video_track {};
TRY(parse_master_element(streamer, "VideoTrack"sv, [&](u64 element_id) -> DecoderErrorOr<void> {
TRY(parse_master_element(streamer, "VideoTrack"sv, [&](u64 element_id, size_t) -> DecoderErrorOr<IterationDecision> {
switch (element_id) {
case PIXEL_WIDTH_ID:
video_track.pixel_width = TRY_READ(streamer.read_u64());
@ -238,7 +361,7 @@ static DecoderErrorOr<TrackEntry::VideoTrack> parse_video_track_information(Stre
TRY_READ(streamer.read_unknown_element());
}
return {};
return IterationDecision::Continue;
}));
return video_track;
@ -248,7 +371,7 @@ static DecoderErrorOr<TrackEntry::AudioTrack> parse_audio_track_information(Stre
{
TrackEntry::AudioTrack audio_track {};
TRY(parse_master_element(streamer, "AudioTrack"sv, [&](u64 element_id) -> DecoderErrorOr<void> {
TRY(parse_master_element(streamer, "AudioTrack"sv, [&](u64 element_id, size_t) -> DecoderErrorOr<IterationDecision> {
switch (element_id) {
case CHANNELS_ID:
audio_track.channels = TRY_READ(streamer.read_u64());
@ -262,114 +385,163 @@ static DecoderErrorOr<TrackEntry::AudioTrack> parse_audio_track_information(Stre
TRY_READ(streamer.read_unknown_element());
}
return {};
return IterationDecision::Continue;
}));
return audio_track;
}
static DecoderErrorOr<NonnullOwnPtr<TrackEntry>> parse_track_entry(Streamer& streamer)
static DecoderErrorOr<TrackEntry> parse_track_entry(Streamer& streamer)
{
auto track_entry = make<TrackEntry>();
TRY(parse_master_element(streamer, "Track"sv, [&](u64 element_id) -> DecoderErrorOr<void> {
TrackEntry track_entry;
TRY(parse_master_element(streamer, "Track"sv, [&](u64 element_id, size_t) -> DecoderErrorOr<IterationDecision> {
switch (element_id) {
case TRACK_NUMBER_ID:
track_entry->set_track_number(TRY_READ(streamer.read_u64()));
dbgln_if(MATROSKA_TRACE_DEBUG, "Read TrackNumber attribute: {}", track_entry->track_number());
track_entry.set_track_number(TRY_READ(streamer.read_u64()));
dbgln_if(MATROSKA_TRACE_DEBUG, "Read TrackNumber attribute: {}", track_entry.track_number());
break;
case TRACK_UID_ID:
track_entry->set_track_uid(TRY_READ(streamer.read_u64()));
dbgln_if(MATROSKA_TRACE_DEBUG, "Read TrackUID attribute: {}", track_entry->track_uid());
track_entry.set_track_uid(TRY_READ(streamer.read_u64()));
dbgln_if(MATROSKA_TRACE_DEBUG, "Read TrackUID attribute: {}", track_entry.track_uid());
break;
case TRACK_TYPE_ID:
track_entry->set_track_type(static_cast<TrackEntry::TrackType>(TRY_READ(streamer.read_u64())));
dbgln_if(MATROSKA_TRACE_DEBUG, "Read TrackType attribute: {}", track_entry->track_type());
track_entry.set_track_type(static_cast<TrackEntry::TrackType>(TRY_READ(streamer.read_u64())));
dbgln_if(MATROSKA_TRACE_DEBUG, "Read TrackType attribute: {}", to_underlying(track_entry.track_type()));
break;
case TRACK_LANGUAGE_ID:
track_entry->set_language(TRY_READ(streamer.read_string()));
dbgln_if(MATROSKA_TRACE_DEBUG, "Read Track's Language attribute: {}", track_entry->language());
track_entry.set_language(TRY_READ(streamer.read_string()));
dbgln_if(MATROSKA_TRACE_DEBUG, "Read Track's Language attribute: {}", track_entry.language());
break;
case TRACK_CODEC_ID:
track_entry->set_codec_id(TRY_READ(streamer.read_string()));
dbgln_if(MATROSKA_TRACE_DEBUG, "Read Track's CodecID attribute: {}", track_entry->codec_id());
track_entry.set_codec_id(TRY_READ(streamer.read_string()));
dbgln_if(MATROSKA_TRACE_DEBUG, "Read Track's CodecID attribute: {}", track_entry.codec_id());
break;
case TRACK_VIDEO_ID:
track_entry->set_video_track(TRY(parse_video_track_information(streamer)));
track_entry.set_video_track(TRY(parse_video_track_information(streamer)));
break;
case TRACK_AUDIO_ID:
track_entry->set_audio_track(TRY(parse_audio_track_information(streamer)));
track_entry.set_audio_track(TRY(parse_audio_track_information(streamer)));
break;
default:
TRY_READ(streamer.read_unknown_element());
}
return {};
return IterationDecision::Continue;
}));
return track_entry;
}
static DecoderErrorOr<void> parse_tracks(Streamer& streamer, MatroskaDocument& matroska_document)
DecoderErrorOr<void> Reader::parse_tracks(Streamer& streamer)
{
return parse_master_element(streamer, "Tracks"sv, [&](u64 element_id) -> DecoderErrorOr<void> {
return parse_master_element(streamer, "Tracks"sv, [&](u64 element_id, size_t) -> DecoderErrorOr<IterationDecision> {
if (element_id == TRACK_ENTRY_ID) {
dbgln_if(MATROSKA_DEBUG, "Parsing track");
auto track_entry = TRY(parse_track_entry(streamer));
auto track_number = track_entry->track_number();
dbgln_if(MATROSKA_DEBUG, "Adding track {} to document", track_number);
matroska_document.add_track(track_number, track_entry.release_nonnull<TrackEntry>());
dbgln_if(MATROSKA_DEBUG, "Parsed track {}", track_entry.track_number());
DECODER_TRY_ALLOC(m_tracks.try_set(track_entry.track_number(), track_entry));
} else {
TRY_READ(streamer.read_unknown_element());
}
return {};
return IterationDecision::Continue;
});
}
static DecoderErrorOr<NonnullOwnPtr<Block>> parse_simple_block(Streamer& streamer);
static DecoderErrorOr<NonnullOwnPtr<Cluster>> parse_cluster(Streamer& streamer)
DecoderErrorOr<void> Reader::for_each_track(TrackEntryCallback callback)
{
auto cluster = make<Cluster>();
TRY(ensure_tracks_are_parsed());
for (auto const& track_entry : m_tracks) {
auto decision = TRY(callback(track_entry.value));
if (decision == IterationDecision::Break)
break;
}
return {};
}
DecoderErrorOr<void> Reader::for_each_track_of_type(TrackEntry::TrackType type, TrackEntryCallback callback)
{
return for_each_track([&](TrackEntry const& track_entry) -> DecoderErrorOr<IterationDecision> {
if (track_entry.track_type() != type)
return IterationDecision::Continue;
return callback(track_entry);
});
}
DecoderErrorOr<TrackEntry> Reader::track_for_track_number(u64 track_number)
{
TRY(ensure_tracks_are_parsed());
auto optional_track_entry = m_tracks.get(track_number);
if (!optional_track_entry.has_value())
return DecoderError::format(DecoderErrorCategory::Invalid, "No track found with number {}", track_number);
return optional_track_entry.release_value();
}
DecoderErrorOr<size_t> Reader::track_count()
{
TRY(ensure_tracks_are_parsed());
return m_tracks.size();
}
constexpr size_t get_element_id_size(u32 element_id)
{
return sizeof(element_id) - (count_leading_zeroes(element_id) / 8);
}
static DecoderErrorOr<Cluster> parse_cluster(Streamer& streamer)
{
Optional<u64> timestamp;
size_t first_element_position = 0;
TRY(parse_master_element(streamer, "Cluster"sv, [&](u64 element_id, size_t position) -> DecoderErrorOr<IterationDecision> {
if (first_element_position == 0)
first_element_position = position - get_element_id_size(element_id);
TRY(parse_master_element(streamer, "Cluster"sv, [&](u64 element_id) -> DecoderErrorOr<void> {
switch (element_id) {
case SIMPLE_BLOCK_ID:
cluster->blocks().append(TRY(parse_simple_block(streamer)));
break;
case TIMESTAMP_ID:
cluster->set_timestamp(TRY_READ(streamer.read_u64()));
break;
timestamp = TRY_READ(streamer.read_u64());
return IterationDecision::Break;
default:
TRY_READ(streamer.read_unknown_element());
}
return {};
return IterationDecision::Continue;
}));
if (!timestamp.has_value())
return DecoderError::corrupted("Cluster was missing a timestamp"sv);
if (first_element_position == 0)
return DecoderError::corrupted("Cluster had no children"sv);
dbgln_if(MATROSKA_TRACE_DEBUG, "Seeking back to position {}", first_element_position);
TRY_READ(streamer.seek_to_position(first_element_position));
Cluster cluster;
cluster.set_timestamp(timestamp.release_value());
return cluster;
}
static DecoderErrorOr<NonnullOwnPtr<Block>> parse_simple_block(Streamer& streamer)
static DecoderErrorOr<Block> parse_simple_block(Streamer& streamer)
{
auto block = make<Block>();
Block block;
auto content_size = TRY_READ(streamer.read_variable_size_integer());
auto octets_read_before_track_number = streamer.octets_read();
auto track_number = TRY_READ(streamer.read_variable_size_integer());
block->set_track_number(track_number);
auto position_before_track_number = streamer.position();
block.set_track_number(TRY_READ(streamer.read_variable_size_integer()));
block->set_timestamp(TRY_READ(streamer.read_i16()));
block.set_timestamp(TRY_READ(streamer.read_i16()));
auto flags = TRY_READ(streamer.read_octet());
block->set_only_keyframes((flags & (1u << 7u)) != 0);
block->set_invisible((flags & (1u << 3u)) != 0);
block->set_lacing(static_cast<Block::Lacing>((flags & 0b110u) >> 1u));
block->set_discardable((flags & 1u) != 0);
block.set_only_keyframes((flags & (1u << 7u)) != 0);
block.set_invisible((flags & (1u << 3u)) != 0);
block.set_lacing(static_cast<Block::Lacing>((flags & 0b110u) >> 1u));
block.set_discardable((flags & 1u) != 0);
auto total_frame_content_size = content_size - (streamer.octets_read() - octets_read_before_track_number);
if (block->lacing() == Block::Lacing::EBML) {
auto total_frame_content_size = content_size - (streamer.position() - position_before_track_number);
Vector<ReadonlyBytes> frames;
if (block.lacing() == Block::Lacing::EBML) {
auto octets_read_before_frame_sizes = streamer.octets_read();
auto frame_count = TRY_READ(streamer.read_octet()) + 1;
Vector<u64> frame_sizes;
@ -385,7 +557,7 @@ static DecoderErrorOr<NonnullOwnPtr<Block>> parse_simple_block(Streamer& streame
for (int i = 0; i < frame_count - 2; i++) {
auto frame_size_difference = TRY_READ(streamer.read_variable_size_signed_integer());
u64 frame_size;
// FIXME: x - (-y) == x + y??
// FIXME: x - (-y) == x + y?
if (frame_size_difference < 0)
frame_size = previous_frame_size - (-frame_size_difference);
else
@ -399,30 +571,89 @@ static DecoderErrorOr<NonnullOwnPtr<Block>> parse_simple_block(Streamer& streame
for (int i = 0; i < frame_count; i++) {
// FIXME: ReadonlyBytes instead of copying the frame data?
auto current_frame_size = frame_sizes.at(i);
block->add_frame(DECODER_TRY_ALLOC(ByteBuffer::copy(streamer.data(), current_frame_size)));
TRY_READ(streamer.drop_octets(current_frame_size));
frames.append(TRY_READ(streamer.read_raw_octets(current_frame_size)));
}
} else if (block->lacing() == Block::Lacing::FixedSize) {
} else if (block.lacing() == Block::Lacing::FixedSize) {
auto frame_count = TRY_READ(streamer.read_octet()) + 1;
auto individual_frame_size = total_frame_content_size / frame_count;
for (int i = 0; i < frame_count; i++) {
block->add_frame(DECODER_TRY_ALLOC(ByteBuffer::copy(streamer.data(), individual_frame_size)));
TRY_READ(streamer.drop_octets(individual_frame_size));
}
for (int i = 0; i < frame_count; i++)
frames.append(TRY_READ(streamer.read_raw_octets(individual_frame_size)));
} else {
block->add_frame(DECODER_TRY_ALLOC(ByteBuffer::copy(streamer.data(), total_frame_content_size)));
TRY_READ(streamer.drop_octets(total_frame_content_size));
frames.append(TRY_READ(streamer.read_raw_octets(total_frame_content_size)));
}
block.set_frames(move(frames));
return block;
}
DecoderErrorOr<SampleIterator> Reader::create_sample_iterator(u64 track_number)
{
auto optional_position = TRY(find_first_top_level_element_with_id("Cluster"sv, CLUSTER_ELEMENT_ID));
if (!optional_position.has_value())
return DecoderError::corrupted("No clusters are present in the segment"sv);
ReadonlyBytes segment_view = m_data.slice(m_segment_contents_position, m_segment_contents_size);
// We need to have the element ID included so that the iterator knows where it is.
auto position = optional_position.value() - get_element_id_size(CLUSTER_ELEMENT_ID) - m_segment_contents_position;
dbgln_if(MATROSKA_DEBUG, "Creating sample iterator starting at {} relative to segment at {}", position, m_segment_contents_position);
return SampleIterator(this->m_mapped_file, segment_view, track_number, position, TRY(segment_information()).timestamp_scale());
}
DecoderErrorOr<void> Reader::seek_to_random_access_point(SampleIterator& iterator, Time timestamp)
{
(void)iterator;
(void)timestamp;
return DecoderError::not_implemented();
}
DecoderErrorOr<Block> SampleIterator::next_block()
{
if (m_position >= m_data.size())
return DecoderError::with_description(DecoderErrorCategory::EndOfStream, "Still at end of stream :^)"sv);
Streamer streamer { m_data };
TRY_READ(streamer.seek_to_position(m_position));
Optional<Block> block;
while (streamer.has_octet()) {
#if MATROSKA_TRACE_DEBUG
auto element_position = streamer.position();
#endif
auto element_id = TRY_READ(streamer.read_variable_size_integer(false));
#if MATROSKA_TRACE_DEBUG
dbgln("Iterator found element with ID {:#010x} at offset {} within the segment.", element_id, element_position);
#endif
if (element_id == CLUSTER_ELEMENT_ID) {
dbgln_if(MATROSKA_DEBUG, " Iterator is parsing new cluster.");
m_current_cluster = TRY(parse_cluster(streamer));
} else if (element_id == SIMPLE_BLOCK_ID) {
dbgln_if(MATROSKA_TRACE_DEBUG, " Iterator is parsing new block.");
auto candidate_block = TRY(parse_simple_block(streamer));
if (candidate_block.track_number() == m_track_id)
block = move(candidate_block);
} else {
dbgln_if(MATROSKA_TRACE_DEBUG, " Iterator is skipping unknown element with ID {:#010x}.", element_id);
TRY_READ(streamer.read_unknown_element());
}
m_position = streamer.position();
if (block.has_value())
return block.release_value();
}
m_current_cluster.clear();
return DecoderError::with_description(DecoderErrorCategory::EndOfStream, "End of stream"sv);
}
ErrorOr<String> Streamer::read_string()
{
auto string_length = TRY(read_variable_size_integer());
if (remaining() < string_length)
return Error::from_string_literal("String length extends past the end of the stream");
auto string_value = String(data_as_chars(), string_length);
TRY(drop_octets(string_length));
TRY(read_raw_octets(string_length));
return string_value;
}
@ -498,13 +729,14 @@ ErrorOr<i64> Streamer::read_variable_size_signed_integer()
return result;
}
ErrorOr<void> Streamer::drop_octets(size_t num_octets)
ErrorOr<ReadonlyBytes> Streamer::read_raw_octets(size_t num_octets)
{
if (remaining() < num_octets)
return Error::from_string_literal("Tried to drop octets past the end of the stream");
ReadonlyBytes result = { data(), num_octets };
m_position += num_octets;
m_octets_read.last() += num_octets;
return {};
return result;
}
ErrorOr<u64> Streamer::read_u64()
@ -540,7 +772,17 @@ ErrorOr<double> Streamer::read_float()
ErrorOr<void> Streamer::read_unknown_element()
{
auto element_length = TRY(read_variable_size_integer());
return drop_octets(element_length);
dbgln_if(MATROSKA_TRACE_DEBUG, "Skipping unknown element of size {}.", element_length);
TRY(read_raw_octets(element_length));
return {};
}
ErrorOr<void> Streamer::seek_to_position(size_t position)
{
if (position >= m_data.size())
return Error::from_string_literal("Attempted to seek past the end of the stream");
m_position = position;
return {};
}
}

View File

@ -7,17 +7,101 @@
#pragma once
#include <AK/Concepts.h>
#include <AK/Debug.h>
#include <AK/IntegralMath.h>
#include <AK/NonnullOwnPtrVector.h>
#include <AK/Optional.h>
#include <AK/OwnPtr.h>
#include <LibCore/MappedFile.h>
#include <LibVideo/DecoderError.h>
#include "Document.h"
namespace Video::Matroska {
class SampleIterator;
class Streamer;
class Reader {
public:
typedef Function<DecoderErrorOr<IterationDecision>(TrackEntry const&)> TrackEntryCallback;
static DecoderErrorOr<Reader> from_file(StringView path);
static DecoderErrorOr<Reader> from_data(ReadonlyBytes data);
EBMLHeader const& header() const { return m_header.value(); }
DecoderErrorOr<SegmentInformation> segment_information();
DecoderErrorOr<void> for_each_track(TrackEntryCallback);
DecoderErrorOr<void> for_each_track_of_type(TrackEntry::TrackType, TrackEntryCallback);
DecoderErrorOr<TrackEntry> track_for_track_number(u64);
DecoderErrorOr<size_t> track_count();
DecoderErrorOr<SampleIterator> create_sample_iterator(u64 track_number);
DecoderErrorOr<void> seek_to_random_access_point(SampleIterator&, Time);
private:
Reader(ReadonlyBytes data)
: m_data(data)
{
}
DecoderErrorOr<void> parse_initial_data();
DecoderErrorOr<Optional<size_t>> find_first_top_level_element_with_id([[maybe_unused]] StringView element_name, u32 element_id);
DecoderErrorOr<void> ensure_tracks_are_parsed();
DecoderErrorOr<void> parse_tracks(Streamer&);
RefPtr<Core::MappedFile> m_mapped_file;
ReadonlyBytes m_data;
Optional<EBMLHeader> m_header;
size_t m_segment_contents_position { 0 };
size_t m_segment_contents_size { 0 };
HashMap<u32, size_t> m_seek_entries;
size_t m_last_top_level_element_position { 0 };
Optional<SegmentInformation> m_segment_information;
OrderedHashMap<u64, TrackEntry> m_tracks;
};
class SampleIterator {
public:
DecoderErrorOr<Block> next_block();
Cluster const& current_cluster() { return *m_current_cluster; }
private:
friend class Reader;
SampleIterator(RefPtr<Core::MappedFile> file, ReadonlyBytes data, u64 track_id, size_t position, u64 timestamp_scale)
: m_file(move(file))
, m_data(data)
, m_track_id(track_id)
, m_position(position)
, m_timestamp_scale(timestamp_scale)
{
}
DecoderErrorOr<void> set_position(size_t position);
RefPtr<Core::MappedFile> m_file;
ReadonlyBytes m_data;
u64 m_track_id;
// Must always point to an element ID or the end of the stream.
size_t m_position { 0 };
u64 m_timestamp_scale { 0 };
Optional<Cluster> m_current_cluster;
};
class Streamer {
public:
Streamer(ReadonlyBytes data)
@ -54,12 +138,15 @@ public:
ErrorOr<void> read_unknown_element();
ErrorOr<void> drop_octets(size_t num_octets);
ErrorOr<ReadonlyBytes> read_raw_octets(size_t num_octets);
size_t position() const { return m_position; }
size_t remaining() const { return m_data.size() - position(); }
bool at_end() const { return remaining() == 0; }
bool has_octet() const { return remaining() >= 1; }
size_t remaining() const { return m_data.size() - m_position; }
ErrorOr<void> seek_to_position(size_t position);
private:
ReadonlyBytes m_data;
@ -67,20 +154,4 @@ private:
Vector<size_t> m_octets_read { 0 };
};
class Reader {
public:
Reader(ReadonlyBytes data)
: m_streamer(data)
{
}
static DecoderErrorOr<NonnullOwnPtr<MatroskaDocument>> parse_matroska_from_file(StringView path);
static DecoderErrorOr<NonnullOwnPtr<MatroskaDocument>> parse_matroska_from_data(ReadonlyBytes data);
DecoderErrorOr<NonnullOwnPtr<MatroskaDocument>> parse();
private:
Streamer m_streamer;
};
}

View File

@ -16,7 +16,7 @@ namespace Video {
DecoderErrorOr<NonnullOwnPtr<PlaybackManager>> PlaybackManager::from_file(Core::Object& event_handler, StringView filename)
{
NonnullOwnPtr<Demuxer> demuxer = TRY(Matroska::MatroskaDemuxer::from_file(filename));
auto video_tracks = demuxer->get_tracks_for_type(TrackType::Video);
auto video_tracks = TRY(demuxer->get_tracks_for_type(TrackType::Video));
if (video_tracks.is_empty())
return DecoderError::with_description(DecoderErrorCategory::Invalid, "No video track is present"sv);
auto track = video_tracks[0];
@ -101,7 +101,10 @@ Time PlaybackManager::current_playback_time()
Time PlaybackManager::duration()
{
return m_demuxer->duration();
auto duration_result = m_demuxer->duration();
if (duration_result.is_error())
on_decoder_error(duration_result.release_error());
return duration_result.release_value();
}
void PlaybackManager::on_decoder_error(DecoderError error)

View File

@ -21,7 +21,7 @@ public:
class VideoSample : public Sample {
public:
VideoSample(ByteBuffer const& data, CodingIndependentCodePoints container_cicp, Time timestamp)
VideoSample(ReadonlyBytes data, CodingIndependentCodePoints container_cicp, Time timestamp)
: m_data(data)
, m_container_cicp(container_cicp)
, m_timestamp(timestamp)
@ -29,12 +29,12 @@ public:
}
bool is_video_sample() const override { return true; }
ByteBuffer const& data() const { return m_data; }
ReadonlyBytes const& data() const { return m_data; }
CodingIndependentCodePoints container_cicp() const { return m_container_cicp; }
Time timestamp() const { return m_timestamp; }
private:
ByteBuffer m_data;
ReadonlyBytes m_data;
CodingIndependentCodePoints m_container_cicp;
Time m_timestamp;
};

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;
}