mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 04:58:13 +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:
parent
f4c476b26f
commit
393cfdd5c5
11 changed files with 576 additions and 310 deletions
|
@ -9,8 +9,12 @@
|
||||||
|
|
||||||
extern "C" int LLVMFuzzerTestOneInput(u8 const* data, size_t size)
|
extern "C" int LLVMFuzzerTestOneInput(u8 const* data, size_t size)
|
||||||
{
|
{
|
||||||
auto matroska_document = Video::Matroska::Reader::parse_matroska_from_data(data, size);
|
auto matroska_reader_result = Video::Matroska::Reader::from_data({ data, size });
|
||||||
if (!matroska_document)
|
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 -1;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,29 +11,33 @@
|
||||||
|
|
||||||
static void decode_video(StringView path, size_t expected_frame_count)
|
static void decode_video(StringView path, size_t expected_frame_count)
|
||||||
{
|
{
|
||||||
auto matroska_document = MUST(Video::Matroska::Reader::parse_matroska_from_file(path));
|
auto matroska_reader = MUST(Video::Matroska::Reader::from_file(path));
|
||||||
auto video_track_optional = matroska_document->track_for_track_type(Video::Matroska::TrackEntry::TrackType::Video);
|
u64 video_track = 0;
|
||||||
VERIFY(video_track_optional.has_value());
|
MUST(matroska_reader.for_each_track_of_type(Video::Matroska::TrackEntry::TrackType::Video, [&](Video::Matroska::TrackEntry const& track_entry) -> Video::DecoderErrorOr<IterationDecision> {
|
||||||
auto video_track_entry = video_track_optional.value();
|
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 frame_count = 0;
|
||||||
size_t cluster_index, block_index, frame_index;
|
|
||||||
Video::VP9::Decoder vp9_decoder;
|
Video::VP9::Decoder vp9_decoder;
|
||||||
|
|
||||||
for (cluster_index = 0; cluster_index < matroska_document->clusters().size(); cluster_index++) {
|
while (frame_count <= expected_frame_count) {
|
||||||
auto const& cluster = matroska_document->clusters()[cluster_index];
|
auto block_result = iterator.next_block();
|
||||||
for (block_index = 0; block_index < cluster.blocks().size(); block_index++) {
|
if (block_result.is_error() && block_result.error().category() == Video::DecoderErrorCategory::EndOfStream) {
|
||||||
auto const& block = cluster.blocks()[block_index];
|
VERIFY(frame_count == expected_frame_count);
|
||||||
if (block.track_number() != video_track_entry.track_number())
|
return;
|
||||||
continue;
|
}
|
||||||
|
|
||||||
for (frame_index = 0; frame_index < block.frames().size(); frame_index++) {
|
auto block = block_result.release_value();
|
||||||
MUST(vp9_decoder.receive_sample(block.frames()[frame_index]));
|
for (auto const& frame : block.frames()) {
|
||||||
frame_count++;
|
MUST(vp9_decoder.receive_sample(frame));
|
||||||
}
|
frame_count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
VERIFY(frame_count == expected_frame_count);
|
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE(webm_in_vp9)
|
TEST_CASE(webm_in_vp9)
|
||||||
|
|
|
@ -18,7 +18,7 @@ class Demuxer {
|
||||||
public:
|
public:
|
||||||
virtual ~Demuxer() = default;
|
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)
|
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 DecoderErrorOr<void> seek_to_most_recent_keyframe(Track track, size_t timestamp) = 0;
|
||||||
|
|
||||||
virtual Time duration() = 0;
|
virtual DecoderErrorOr<Time> duration() = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual DecoderErrorOr<NonnullOwnPtr<Sample>> get_next_sample_for_track(Track track) = 0;
|
virtual DecoderErrorOr<NonnullOwnPtr<Sample>> get_next_sample_for_track(Track track) = 0;
|
||||||
|
|
|
@ -150,8 +150,6 @@ public:
|
||||||
EBML = 0b11,
|
EBML = 0b11,
|
||||||
};
|
};
|
||||||
|
|
||||||
Block() = default;
|
|
||||||
|
|
||||||
u64 track_number() const { return m_track_number; }
|
u64 track_number() const { return m_track_number; }
|
||||||
void set_track_number(u64 track_number) { m_track_number = track_number; }
|
void set_track_number(u64 track_number) { m_track_number = track_number; }
|
||||||
i16 timestamp() const { return m_timestamp; }
|
i16 timestamp() const { return m_timestamp; }
|
||||||
|
@ -164,10 +162,11 @@ public:
|
||||||
void set_lacing(Lacing lacing) { m_lacing = lacing; }
|
void set_lacing(Lacing lacing) { m_lacing = lacing; }
|
||||||
bool discardable() const { return m_discardable; }
|
bool discardable() const { return m_discardable; }
|
||||||
void set_discardable(bool discardable) { m_discardable = 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(); }
|
u64 frame_count() const { return m_frames.size(); }
|
||||||
Vector<ByteBuffer> const& frames() const { return m_frames; }
|
Vector<ReadonlyBytes> 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)); }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
u64 m_track_number { 0 };
|
u64 m_track_number { 0 };
|
||||||
|
@ -176,64 +175,16 @@ private:
|
||||||
bool m_invisible { false };
|
bool m_invisible { false };
|
||||||
Lacing m_lacing { None };
|
Lacing m_lacing { None };
|
||||||
bool m_discardable { true };
|
bool m_discardable { true };
|
||||||
Vector<ByteBuffer> m_frames;
|
Vector<ReadonlyBytes> m_frames;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Cluster {
|
class Cluster {
|
||||||
public:
|
public:
|
||||||
u64 timestamp() const { return m_timestamp; }
|
u64 timestamp() const { return m_timestamp; }
|
||||||
void set_timestamp(u64 timestamp) { m_timestamp = timestamp; }
|
void set_timestamp(u64 timestamp) { m_timestamp = timestamp; }
|
||||||
NonnullOwnPtrVector<Block>& blocks() { return m_blocks; }
|
|
||||||
NonnullOwnPtrVector<Block> const& blocks() const { return m_blocks; }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
u64 m_timestamp { 0 };
|
u64 m_timestamp;
|
||||||
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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,15 +10,15 @@ namespace Video::Matroska {
|
||||||
|
|
||||||
DecoderErrorOr<NonnullOwnPtr<MatroskaDemuxer>> MatroskaDemuxer::from_file(StringView filename)
|
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)
|
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;
|
TrackEntry::TrackType matroska_track_type;
|
||||||
|
|
||||||
|
@ -35,21 +35,20 @@ Vector<Track> MatroskaDemuxer::get_tracks_for_type(TrackType type)
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector<Track> tracks;
|
Vector<Track> tracks;
|
||||||
|
TRY(m_reader.for_each_track_of_type(matroska_track_type, [&](TrackEntry const& track_entry) -> DecoderErrorOr<IterationDecision> {
|
||||||
for (auto const& track_table_entry : m_document->tracks()) {
|
VERIFY(track_entry.track_type() == matroska_track_type);
|
||||||
auto const& track_entry = track_table_entry.value;
|
DECODER_TRY_ALLOC(tracks.try_append(Track(type, track_entry.track_number())));
|
||||||
if (matroska_track_type == track_entry->track_type())
|
return IterationDecision::Continue;
|
||||||
tracks.append(Track(type, track_entry->track_number()));
|
}));
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: Sort the vector, presumably the hashtable will not have a consistent order.
|
|
||||||
return tracks;
|
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))
|
if (!m_track_statuses.contains(track)) {
|
||||||
TRY(m_track_statuses.try_set(track, TrackStatus()));
|
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();
|
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)
|
DecoderErrorOr<void> MatroskaDemuxer::seek_to_most_recent_keyframe(Track track, size_t timestamp)
|
||||||
{
|
{
|
||||||
if (timestamp == 0) {
|
if (timestamp == 0) {
|
||||||
auto track_status = DECODER_TRY_ALLOC(get_track_status(track));
|
// Removing the track status will cause us to start from the beginning.
|
||||||
track_status->m_cluster_index = 0;
|
m_track_statuses.remove(track);
|
||||||
track_status->m_block_index = 0;
|
|
||||||
track_status->m_frame_index = 0;
|
|
||||||
return {};
|
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)
|
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++) {
|
if (!status.block.has_value() || status.frame_index >= status.block->frame_count()) {
|
||||||
auto const& cluster = m_document->clusters()[track_status->m_cluster_index];
|
status.block = TRY(status.iterator.next_block());
|
||||||
for (; track_status->m_block_index < cluster.blocks().size(); track_status->m_block_index++) {
|
status.frame_index = 0;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
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();
|
return Time::zero();
|
||||||
if (!m_document->segment_information()->duration().has_value())
|
return Time::from_nanoseconds(static_cast<i64>(segment_information.duration().value() * segment_information.timestamp_scale()));
|
||||||
return Time::zero();
|
|
||||||
return Time::from_nanoseconds(m_document->segment_information()->duration().value() * m_document->segment_information()->timestamp_scale());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,30 +20,30 @@ public:
|
||||||
static DecoderErrorOr<NonnullOwnPtr<MatroskaDemuxer>> from_file(StringView filename);
|
static DecoderErrorOr<NonnullOwnPtr<MatroskaDemuxer>> from_file(StringView filename);
|
||||||
static DecoderErrorOr<NonnullOwnPtr<MatroskaDemuxer>> from_data(ReadonlyBytes data);
|
static DecoderErrorOr<NonnullOwnPtr<MatroskaDemuxer>> from_data(ReadonlyBytes data);
|
||||||
|
|
||||||
MatroskaDemuxer(NonnullOwnPtr<MatroskaDocument>&& document)
|
MatroskaDemuxer(Reader&& reader)
|
||||||
: m_document(move(document))
|
: 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;
|
DecoderErrorOr<void> seek_to_most_recent_keyframe(Track track, size_t timestamp) override;
|
||||||
|
|
||||||
Time duration() override;
|
DecoderErrorOr<Time> duration() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
DecoderErrorOr<NonnullOwnPtr<Sample>> get_next_sample_for_track(Track track) override;
|
DecoderErrorOr<NonnullOwnPtr<Sample>> get_next_sample_for_track(Track track) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct TrackStatus {
|
struct TrackStatus {
|
||||||
size_t m_cluster_index { 0 };
|
SampleIterator iterator;
|
||||||
size_t m_block_index { 0 };
|
Optional<Block> block {};
|
||||||
size_t m_frame_index { 0 };
|
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;
|
HashMap<Track, TrackStatus> m_track_statuses;
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include <AK/Function.h>
|
#include <AK/Function.h>
|
||||||
#include <AK/Optional.h>
|
#include <AK/Optional.h>
|
||||||
|
#include <AK/Time.h>
|
||||||
#include <AK/Utf8View.h>
|
#include <AK/Utf8View.h>
|
||||||
#include <LibCore/MappedFile.h>
|
#include <LibCore/MappedFile.h>
|
||||||
|
|
||||||
|
@ -16,10 +17,18 @@ namespace Video::Matroska {
|
||||||
|
|
||||||
#define TRY_READ(expression) DECODER_TRY(DecoderErrorCategory::Corrupted, expression)
|
#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 EBML_MASTER_ELEMENT_ID = 0x1A45DFA3;
|
||||||
constexpr u32 SEGMENT_ELEMENT_ID = 0x18538067;
|
constexpr u32 SEGMENT_ELEMENT_ID = 0x18538067;
|
||||||
constexpr u32 DOCTYPE_ELEMENT_ID = 0x4282;
|
constexpr u32 DOCTYPE_ELEMENT_ID = 0x4282;
|
||||||
constexpr u32 DOCTYPE_VERSION_ELEMENT_ID = 0x4287;
|
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 SEGMENT_INFORMATION_ELEMENT_ID = 0x1549A966;
|
||||||
constexpr u32 TRACK_ELEMENT_ID = 0x1654AE6B;
|
constexpr u32 TRACK_ELEMENT_ID = 0x1654AE6B;
|
||||||
constexpr u32 CLUSTER_ELEMENT_ID = 0x1F43B675;
|
constexpr u32 CLUSTER_ELEMENT_ID = 0x1F43B675;
|
||||||
|
@ -55,19 +64,22 @@ constexpr u32 BIT_DEPTH_ID = 0x6264;
|
||||||
constexpr u32 SIMPLE_BLOCK_ID = 0xA3;
|
constexpr u32 SIMPLE_BLOCK_ID = 0xA3;
|
||||||
constexpr u32 TIMESTAMP_ID = 0xE7;
|
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));
|
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);
|
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());
|
auto element_data_size = TRY_READ(streamer.read_variable_size_integer());
|
||||||
dbgln_if(MATROSKA_DEBUG, "{} has {} octets of data.", element_name, element_data_size);
|
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) {
|
while (streamer.octets_read() < element_data_size) {
|
||||||
dbgln_if(MATROSKA_TRACE_DEBUG, "====== Reading element ======");
|
dbgln_if(MATROSKA_TRACE_DEBUG, "====== Reading element ======");
|
||||||
auto element_id = TRY_READ(streamer.read_variable_size_integer(false));
|
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);
|
dbgln_if(MATROSKA_TRACE_DEBUG, "Read {} octets of the {} so far.", streamer.octets_read(), element_name);
|
||||||
}
|
}
|
||||||
streamer.pop_octets_read();
|
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)
|
static DecoderErrorOr<EBMLHeader> parse_ebml_header(Streamer& streamer)
|
||||||
{
|
{
|
||||||
EBMLHeader header;
|
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) {
|
switch (element_id) {
|
||||||
case DOCTYPE_ELEMENT_ID:
|
case DOCTYPE_ELEMENT_ID:
|
||||||
header.doc_type = TRY_READ(streamer.read_string());
|
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());
|
TRY_READ(streamer.read_unknown_element());
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return IterationDecision::Continue;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return header;
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
static DecoderErrorOr<NonnullOwnPtr<SegmentInformation>> parse_information(Streamer& streamer);
|
DecoderErrorOr<void> Reader::parse_initial_data()
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
dbgln_if(MATROSKA_DEBUG, "Parsing segment elements");
|
Streamer streamer { m_data };
|
||||||
return parse_master_element(streamer, "Segment"sv, [&](u64 element_id) -> DecoderErrorOr<void> {
|
auto first_element_id = TRY_READ(streamer.read_variable_size_integer(false));
|
||||||
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));
|
|
||||||
dbgln_if(MATROSKA_TRACE_DEBUG, "First element ID is {:#010x}\n", first_element_id);
|
dbgln_if(MATROSKA_TRACE_DEBUG, "First element ID is {:#010x}\n", first_element_id);
|
||||||
if (first_element_id != EBML_MASTER_ELEMENT_ID)
|
if (first_element_id != EBML_MASTER_ELEMENT_ID)
|
||||||
return DecoderError::corrupted("First element was not an EBML header"sv);
|
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");
|
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)
|
if (root_element_id != SEGMENT_ELEMENT_ID)
|
||||||
return DecoderError::corrupted("Second element was not a segment element"sv);
|
return DecoderError::corrupted("Second element was not a segment element"sv);
|
||||||
|
|
||||||
auto matroska_document = make<MatroskaDocument>(header);
|
m_segment_contents_size = TRY_READ(streamer.read_variable_size_integer());
|
||||||
TRY(parse_segment_elements(m_streamer, *matroska_document));
|
m_segment_contents_position = streamer.position();
|
||||||
return matroska_document;
|
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>();
|
return parse_master_element(streamer, "SeekHead"sv, [&](u64 seek_head_child_id, size_t) -> DecoderErrorOr<IterationDecision> {
|
||||||
TRY(parse_master_element(streamer, "Segment Information"sv, [&](u64 element_id) -> DecoderErrorOr<void> {
|
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) {
|
switch (element_id) {
|
||||||
case TIMESTAMP_SCALE_ID:
|
case TIMESTAMP_SCALE_ID:
|
||||||
segment_information->set_timestamp_scale(TRY_READ(streamer.read_u64()));
|
segment_information.set_timestamp_scale(TRY_READ(streamer.read_u64()));
|
||||||
dbgln_if(MATROSKA_DEBUG, "Read TimestampScale attribute: {}", segment_information->timestamp_scale());
|
dbgln_if(MATROSKA_DEBUG, "Read TimestampScale attribute: {}", segment_information.timestamp_scale());
|
||||||
break;
|
break;
|
||||||
case MUXING_APP_ID:
|
case MUXING_APP_ID:
|
||||||
segment_information->set_muxing_app(TRY_READ(streamer.read_string()));
|
segment_information.set_muxing_app(TRY_READ(streamer.read_string()));
|
||||||
dbgln_if(MATROSKA_DEBUG, "Read MuxingApp attribute: {}", segment_information->muxing_app().as_string());
|
dbgln_if(MATROSKA_DEBUG, "Read MuxingApp attribute: {}", segment_information.muxing_app().as_string());
|
||||||
break;
|
break;
|
||||||
case WRITING_APP_ID:
|
case WRITING_APP_ID:
|
||||||
segment_information->set_writing_app(TRY_READ(streamer.read_string()));
|
segment_information.set_writing_app(TRY_READ(streamer.read_string()));
|
||||||
dbgln_if(MATROSKA_DEBUG, "Read WritingApp attribute: {}", segment_information->writing_app().as_string());
|
dbgln_if(MATROSKA_DEBUG, "Read WritingApp attribute: {}", segment_information.writing_app().as_string());
|
||||||
break;
|
break;
|
||||||
case DURATION_ID:
|
case DURATION_ID:
|
||||||
segment_information->set_duration(TRY_READ(streamer.read_float()));
|
segment_information.set_duration(TRY_READ(streamer.read_float()));
|
||||||
dbgln_if(MATROSKA_DEBUG, "Read Duration attribute: {}", segment_information->duration().value());
|
dbgln_if(MATROSKA_DEBUG, "Read Duration attribute: {}", segment_information.duration().value());
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
TRY_READ(streamer.read_unknown_element());
|
TRY_READ(streamer.read_unknown_element());
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return IterationDecision::Continue;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return segment_information;
|
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)
|
static DecoderErrorOr<TrackEntry::ColorFormat> parse_video_color_information(Streamer& streamer)
|
||||||
{
|
{
|
||||||
TrackEntry::ColorFormat color_format {};
|
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) {
|
switch (element_id) {
|
||||||
case PRIMARIES_ID:
|
case PRIMARIES_ID:
|
||||||
color_format.color_primaries = static_cast<ColorPrimaries>(TRY_READ(streamer.read_u64()));
|
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());
|
TRY_READ(streamer.read_unknown_element());
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return IterationDecision::Continue;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return color_format;
|
return color_format;
|
||||||
|
@ -221,7 +344,7 @@ static DecoderErrorOr<TrackEntry::VideoTrack> parse_video_track_information(Stre
|
||||||
{
|
{
|
||||||
TrackEntry::VideoTrack video_track {};
|
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) {
|
switch (element_id) {
|
||||||
case PIXEL_WIDTH_ID:
|
case PIXEL_WIDTH_ID:
|
||||||
video_track.pixel_width = TRY_READ(streamer.read_u64());
|
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());
|
TRY_READ(streamer.read_unknown_element());
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return IterationDecision::Continue;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return video_track;
|
return video_track;
|
||||||
|
@ -248,7 +371,7 @@ static DecoderErrorOr<TrackEntry::AudioTrack> parse_audio_track_information(Stre
|
||||||
{
|
{
|
||||||
TrackEntry::AudioTrack audio_track {};
|
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) {
|
switch (element_id) {
|
||||||
case CHANNELS_ID:
|
case CHANNELS_ID:
|
||||||
audio_track.channels = TRY_READ(streamer.read_u64());
|
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());
|
TRY_READ(streamer.read_unknown_element());
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return IterationDecision::Continue;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return audio_track;
|
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>();
|
TrackEntry track_entry;
|
||||||
TRY(parse_master_element(streamer, "Track"sv, [&](u64 element_id) -> DecoderErrorOr<void> {
|
TRY(parse_master_element(streamer, "Track"sv, [&](u64 element_id, size_t) -> DecoderErrorOr<IterationDecision> {
|
||||||
switch (element_id) {
|
switch (element_id) {
|
||||||
case TRACK_NUMBER_ID:
|
case TRACK_NUMBER_ID:
|
||||||
track_entry->set_track_number(TRY_READ(streamer.read_u64()));
|
track_entry.set_track_number(TRY_READ(streamer.read_u64()));
|
||||||
dbgln_if(MATROSKA_TRACE_DEBUG, "Read TrackNumber attribute: {}", track_entry->track_number());
|
dbgln_if(MATROSKA_TRACE_DEBUG, "Read TrackNumber attribute: {}", track_entry.track_number());
|
||||||
break;
|
break;
|
||||||
case TRACK_UID_ID:
|
case TRACK_UID_ID:
|
||||||
track_entry->set_track_uid(TRY_READ(streamer.read_u64()));
|
track_entry.set_track_uid(TRY_READ(streamer.read_u64()));
|
||||||
dbgln_if(MATROSKA_TRACE_DEBUG, "Read TrackUID attribute: {}", track_entry->track_uid());
|
dbgln_if(MATROSKA_TRACE_DEBUG, "Read TrackUID attribute: {}", track_entry.track_uid());
|
||||||
break;
|
break;
|
||||||
case TRACK_TYPE_ID:
|
case TRACK_TYPE_ID:
|
||||||
track_entry->set_track_type(static_cast<TrackEntry::TrackType>(TRY_READ(streamer.read_u64())));
|
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());
|
dbgln_if(MATROSKA_TRACE_DEBUG, "Read TrackType attribute: {}", to_underlying(track_entry.track_type()));
|
||||||
break;
|
break;
|
||||||
case TRACK_LANGUAGE_ID:
|
case TRACK_LANGUAGE_ID:
|
||||||
track_entry->set_language(TRY_READ(streamer.read_string()));
|
track_entry.set_language(TRY_READ(streamer.read_string()));
|
||||||
dbgln_if(MATROSKA_TRACE_DEBUG, "Read Track's Language attribute: {}", track_entry->language());
|
dbgln_if(MATROSKA_TRACE_DEBUG, "Read Track's Language attribute: {}", track_entry.language());
|
||||||
break;
|
break;
|
||||||
case TRACK_CODEC_ID:
|
case TRACK_CODEC_ID:
|
||||||
track_entry->set_codec_id(TRY_READ(streamer.read_string()));
|
track_entry.set_codec_id(TRY_READ(streamer.read_string()));
|
||||||
dbgln_if(MATROSKA_TRACE_DEBUG, "Read Track's CodecID attribute: {}", track_entry->codec_id());
|
dbgln_if(MATROSKA_TRACE_DEBUG, "Read Track's CodecID attribute: {}", track_entry.codec_id());
|
||||||
break;
|
break;
|
||||||
case TRACK_VIDEO_ID:
|
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;
|
break;
|
||||||
case TRACK_AUDIO_ID:
|
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;
|
break;
|
||||||
default:
|
default:
|
||||||
TRY_READ(streamer.read_unknown_element());
|
TRY_READ(streamer.read_unknown_element());
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return IterationDecision::Continue;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return track_entry;
|
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) {
|
if (element_id == TRACK_ENTRY_ID) {
|
||||||
dbgln_if(MATROSKA_DEBUG, "Parsing track");
|
|
||||||
auto track_entry = TRY(parse_track_entry(streamer));
|
auto track_entry = TRY(parse_track_entry(streamer));
|
||||||
auto track_number = track_entry->track_number();
|
dbgln_if(MATROSKA_DEBUG, "Parsed track {}", track_entry.track_number());
|
||||||
dbgln_if(MATROSKA_DEBUG, "Adding track {} to document", track_number);
|
DECODER_TRY_ALLOC(m_tracks.try_set(track_entry.track_number(), track_entry));
|
||||||
matroska_document.add_track(track_number, track_entry.release_nonnull<TrackEntry>());
|
|
||||||
} else {
|
} else {
|
||||||
TRY_READ(streamer.read_unknown_element());
|
TRY_READ(streamer.read_unknown_element());
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return IterationDecision::Continue;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static DecoderErrorOr<NonnullOwnPtr<Block>> parse_simple_block(Streamer& streamer);
|
DecoderErrorOr<void> Reader::for_each_track(TrackEntryCallback callback)
|
||||||
|
|
||||||
static DecoderErrorOr<NonnullOwnPtr<Cluster>> parse_cluster(Streamer& streamer)
|
|
||||||
{
|
{
|
||||||
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) {
|
switch (element_id) {
|
||||||
case SIMPLE_BLOCK_ID:
|
|
||||||
cluster->blocks().append(TRY(parse_simple_block(streamer)));
|
|
||||||
break;
|
|
||||||
case TIMESTAMP_ID:
|
case TIMESTAMP_ID:
|
||||||
cluster->set_timestamp(TRY_READ(streamer.read_u64()));
|
timestamp = TRY_READ(streamer.read_u64());
|
||||||
break;
|
return IterationDecision::Break;
|
||||||
default:
|
default:
|
||||||
TRY_READ(streamer.read_unknown_element());
|
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;
|
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 content_size = TRY_READ(streamer.read_variable_size_integer());
|
||||||
|
|
||||||
auto octets_read_before_track_number = streamer.octets_read();
|
auto position_before_track_number = streamer.position();
|
||||||
auto track_number = TRY_READ(streamer.read_variable_size_integer());
|
block.set_track_number(TRY_READ(streamer.read_variable_size_integer()));
|
||||||
block->set_track_number(track_number);
|
|
||||||
|
|
||||||
block->set_timestamp(TRY_READ(streamer.read_i16()));
|
block.set_timestamp(TRY_READ(streamer.read_i16()));
|
||||||
|
|
||||||
auto flags = TRY_READ(streamer.read_octet());
|
auto flags = TRY_READ(streamer.read_octet());
|
||||||
block->set_only_keyframes((flags & (1u << 7u)) != 0);
|
block.set_only_keyframes((flags & (1u << 7u)) != 0);
|
||||||
block->set_invisible((flags & (1u << 3u)) != 0);
|
block.set_invisible((flags & (1u << 3u)) != 0);
|
||||||
block->set_lacing(static_cast<Block::Lacing>((flags & 0b110u) >> 1u));
|
block.set_lacing(static_cast<Block::Lacing>((flags & 0b110u) >> 1u));
|
||||||
block->set_discardable((flags & 1u) != 0);
|
block.set_discardable((flags & 1u) != 0);
|
||||||
|
|
||||||
auto total_frame_content_size = content_size - (streamer.octets_read() - octets_read_before_track_number);
|
auto total_frame_content_size = content_size - (streamer.position() - position_before_track_number);
|
||||||
if (block->lacing() == Block::Lacing::EBML) {
|
|
||||||
|
Vector<ReadonlyBytes> frames;
|
||||||
|
|
||||||
|
if (block.lacing() == Block::Lacing::EBML) {
|
||||||
auto octets_read_before_frame_sizes = streamer.octets_read();
|
auto octets_read_before_frame_sizes = streamer.octets_read();
|
||||||
auto frame_count = TRY_READ(streamer.read_octet()) + 1;
|
auto frame_count = TRY_READ(streamer.read_octet()) + 1;
|
||||||
Vector<u64> frame_sizes;
|
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++) {
|
for (int i = 0; i < frame_count - 2; i++) {
|
||||||
auto frame_size_difference = TRY_READ(streamer.read_variable_size_signed_integer());
|
auto frame_size_difference = TRY_READ(streamer.read_variable_size_signed_integer());
|
||||||
u64 frame_size;
|
u64 frame_size;
|
||||||
// FIXME: x - (-y) == x + y??
|
// FIXME: x - (-y) == x + y?
|
||||||
if (frame_size_difference < 0)
|
if (frame_size_difference < 0)
|
||||||
frame_size = previous_frame_size - (-frame_size_difference);
|
frame_size = previous_frame_size - (-frame_size_difference);
|
||||||
else
|
else
|
||||||
|
@ -399,30 +571,89 @@ static DecoderErrorOr<NonnullOwnPtr<Block>> parse_simple_block(Streamer& streame
|
||||||
for (int i = 0; i < frame_count; i++) {
|
for (int i = 0; i < frame_count; i++) {
|
||||||
// FIXME: ReadonlyBytes instead of copying the frame data?
|
// FIXME: ReadonlyBytes instead of copying the frame data?
|
||||||
auto current_frame_size = frame_sizes.at(i);
|
auto current_frame_size = frame_sizes.at(i);
|
||||||
block->add_frame(DECODER_TRY_ALLOC(ByteBuffer::copy(streamer.data(), current_frame_size)));
|
frames.append(TRY_READ(streamer.read_raw_octets(current_frame_size)));
|
||||||
TRY_READ(streamer.drop_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 frame_count = TRY_READ(streamer.read_octet()) + 1;
|
||||||
auto individual_frame_size = total_frame_content_size / frame_count;
|
auto individual_frame_size = total_frame_content_size / frame_count;
|
||||||
for (int i = 0; i < frame_count; i++) {
|
for (int i = 0; i < frame_count; i++)
|
||||||
block->add_frame(DECODER_TRY_ALLOC(ByteBuffer::copy(streamer.data(), individual_frame_size)));
|
frames.append(TRY_READ(streamer.read_raw_octets(individual_frame_size)));
|
||||||
TRY_READ(streamer.drop_octets(individual_frame_size));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
block->add_frame(DECODER_TRY_ALLOC(ByteBuffer::copy(streamer.data(), total_frame_content_size)));
|
frames.append(TRY_READ(streamer.read_raw_octets(total_frame_content_size)));
|
||||||
TRY_READ(streamer.drop_octets(total_frame_content_size));
|
|
||||||
}
|
}
|
||||||
|
block.set_frames(move(frames));
|
||||||
return block;
|
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()
|
ErrorOr<String> Streamer::read_string()
|
||||||
{
|
{
|
||||||
auto string_length = TRY(read_variable_size_integer());
|
auto string_length = TRY(read_variable_size_integer());
|
||||||
if (remaining() < string_length)
|
if (remaining() < string_length)
|
||||||
return Error::from_string_literal("String length extends past the end of the stream");
|
return Error::from_string_literal("String length extends past the end of the stream");
|
||||||
auto string_value = String(data_as_chars(), string_length);
|
auto string_value = String(data_as_chars(), string_length);
|
||||||
TRY(drop_octets(string_length));
|
TRY(read_raw_octets(string_length));
|
||||||
return string_value;
|
return string_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -498,13 +729,14 @@ ErrorOr<i64> Streamer::read_variable_size_signed_integer()
|
||||||
return result;
|
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)
|
if (remaining() < num_octets)
|
||||||
return Error::from_string_literal("Tried to drop octets past the end of the stream");
|
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_position += num_octets;
|
||||||
m_octets_read.last() += num_octets;
|
m_octets_read.last() += num_octets;
|
||||||
return {};
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorOr<u64> Streamer::read_u64()
|
ErrorOr<u64> Streamer::read_u64()
|
||||||
|
@ -540,7 +772,17 @@ ErrorOr<double> Streamer::read_float()
|
||||||
ErrorOr<void> Streamer::read_unknown_element()
|
ErrorOr<void> Streamer::read_unknown_element()
|
||||||
{
|
{
|
||||||
auto element_length = TRY(read_variable_size_integer());
|
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 {};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,17 +7,101 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/Concepts.h>
|
||||||
#include <AK/Debug.h>
|
#include <AK/Debug.h>
|
||||||
#include <AK/IntegralMath.h>
|
#include <AK/IntegralMath.h>
|
||||||
#include <AK/NonnullOwnPtrVector.h>
|
#include <AK/NonnullOwnPtrVector.h>
|
||||||
#include <AK/Optional.h>
|
#include <AK/Optional.h>
|
||||||
#include <AK/OwnPtr.h>
|
#include <AK/OwnPtr.h>
|
||||||
|
#include <LibCore/MappedFile.h>
|
||||||
#include <LibVideo/DecoderError.h>
|
#include <LibVideo/DecoderError.h>
|
||||||
|
|
||||||
#include "Document.h"
|
#include "Document.h"
|
||||||
|
|
||||||
namespace Video::Matroska {
|
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 {
|
class Streamer {
|
||||||
public:
|
public:
|
||||||
Streamer(ReadonlyBytes data)
|
Streamer(ReadonlyBytes data)
|
||||||
|
@ -54,12 +138,15 @@ public:
|
||||||
|
|
||||||
ErrorOr<void> read_unknown_element();
|
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 at_end() const { return remaining() == 0; }
|
||||||
bool has_octet() const { return remaining() >= 1; }
|
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:
|
private:
|
||||||
ReadonlyBytes m_data;
|
ReadonlyBytes m_data;
|
||||||
|
@ -67,20 +154,4 @@ private:
|
||||||
Vector<size_t> m_octets_read { 0 };
|
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ namespace Video {
|
||||||
DecoderErrorOr<NonnullOwnPtr<PlaybackManager>> PlaybackManager::from_file(Core::Object& event_handler, StringView filename)
|
DecoderErrorOr<NonnullOwnPtr<PlaybackManager>> PlaybackManager::from_file(Core::Object& event_handler, StringView filename)
|
||||||
{
|
{
|
||||||
NonnullOwnPtr<Demuxer> demuxer = TRY(Matroska::MatroskaDemuxer::from_file(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())
|
if (video_tracks.is_empty())
|
||||||
return DecoderError::with_description(DecoderErrorCategory::Invalid, "No video track is present"sv);
|
return DecoderError::with_description(DecoderErrorCategory::Invalid, "No video track is present"sv);
|
||||||
auto track = video_tracks[0];
|
auto track = video_tracks[0];
|
||||||
|
@ -101,7 +101,10 @@ Time PlaybackManager::current_playback_time()
|
||||||
|
|
||||||
Time PlaybackManager::duration()
|
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)
|
void PlaybackManager::on_decoder_error(DecoderError error)
|
||||||
|
|
|
@ -21,7 +21,7 @@ public:
|
||||||
|
|
||||||
class VideoSample : public Sample {
|
class VideoSample : public Sample {
|
||||||
public:
|
public:
|
||||||
VideoSample(ByteBuffer const& data, CodingIndependentCodePoints container_cicp, Time timestamp)
|
VideoSample(ReadonlyBytes data, CodingIndependentCodePoints container_cicp, Time timestamp)
|
||||||
: m_data(data)
|
: m_data(data)
|
||||||
, m_container_cicp(container_cicp)
|
, m_container_cicp(container_cicp)
|
||||||
, m_timestamp(timestamp)
|
, m_timestamp(timestamp)
|
||||||
|
@ -29,12 +29,12 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_video_sample() const override { return true; }
|
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; }
|
CodingIndependentCodePoints container_cicp() const { return m_container_cicp; }
|
||||||
Time timestamp() const { return m_timestamp; }
|
Time timestamp() const { return m_timestamp; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ByteBuffer m_data;
|
ReadonlyBytes m_data;
|
||||||
CodingIndependentCodePoints m_container_cicp;
|
CodingIndependentCodePoints m_container_cicp;
|
||||||
Time m_timestamp;
|
Time m_timestamp;
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,55 +5,64 @@
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <AK/Function.h>
|
||||||
#include <LibMain/Main.h>
|
#include <LibMain/Main.h>
|
||||||
#include <LibVideo/Containers/Matroska/Reader.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)
|
ErrorOr<int> serenity_main(Main::Arguments)
|
||||||
{
|
{
|
||||||
auto document_result = Video::Matroska::Reader::parse_matroska_from_file("/home/anon/Videos/test-webm.webm"sv);
|
auto reader = TRY_PARSE(Video::Matroska::Reader::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();
|
|
||||||
|
|
||||||
outln("DocType is {}", document->header().doc_type.characters());
|
outln("DocType is {}", reader.header().doc_type.characters());
|
||||||
outln("DocTypeVersion is {}", document->header().doc_type_version);
|
outln("DocTypeVersion is {}", reader.header().doc_type_version);
|
||||||
auto segment_information = document->segment_information();
|
auto segment_information = TRY_PARSE(reader.segment_information());
|
||||||
if (segment_information.has_value()) {
|
outln("Timestamp scale is {}", segment_information.timestamp_scale());
|
||||||
outln("Timestamp scale is {}", segment_information.value().timestamp_scale());
|
outln("Muxing app is \"{}\"", segment_information.muxing_app().as_string().to_string().characters());
|
||||||
outln("Muxing app is \"{}\"", segment_information.value().muxing_app().as_string().to_string().characters());
|
outln("Writing app is \"{}\"", segment_information.writing_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());
|
|
||||||
|
|
||||||
if (track.track_type() == Video::Matroska::TrackEntry::TrackType::Video) {
|
outln("Document has {} tracks", TRY_PARSE(reader.track_count()));
|
||||||
auto const video_track = track.video_track().value();
|
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);
|
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) {
|
} else if (track_entry.track_type() == Video::Matroska::TrackEntry::TrackType::Audio) {
|
||||||
auto const audio_track = track.audio_track().value();
|
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("\t\tAudio has {} channels with a bit depth of {}", audio_track.channels, audio_track.bit_depth);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
outln("Document has {} clusters", document->clusters().size());
|
outln("\tBlocks:");
|
||||||
for (auto const& cluster : document->clusters()) {
|
auto iterator = TRY(reader.create_sample_iterator(track_entry.track_number()));
|
||||||
outln("\tCluster timestamp is {}", cluster.timestamp());
|
|
||||||
|
|
||||||
outln("\tCluster has {} blocks", cluster.blocks().size());
|
while (true) {
|
||||||
for (auto const& block : cluster.blocks()) {
|
auto block_result = iterator.next_block();
|
||||||
(void)block;
|
if (block_result.is_error()) {
|
||||||
outln("\t\tBlock for track #{} has {} frames", block.track_number(), block.frame_count());
|
if (block_result.error().category() == Video::DecoderErrorCategory::EndOfStream)
|
||||||
outln("\t\tBlock's timestamp is {}", block.timestamp());
|
break;
|
||||||
outln("\t\tBlock has lacing {}", static_cast<u8>(block.lacing()));
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue