1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 22:27:35 +00:00

LibVideo: Pass the current sample to demuxers to lazily seek better

In cases where the PlaybackManager's earliest buffered or displayed
sample is closer to the seek target than the demuxer's chosen keyframe,
we don't want to seek at all. To enable this, demuxers now receive an
optional parameter with the earliest timestamp that the caller can
still access.

The demuxer in turn returns an optional to indicate when a seek was not
needed, which allows PlaybackManager to avoid clearing its queue and
re-decoding frames.
This commit is contained in:
Zaggy1024 2023-02-06 01:25:02 -06:00 committed by Andreas Kling
parent 275d57eb6f
commit 3bfef8bfe0
7 changed files with 61 additions and 42 deletions

View file

@ -29,7 +29,9 @@ public:
}
// Returns the timestamp of the keyframe that was seeked to.
virtual DecoderErrorOr<Time> seek_to_most_recent_keyframe(Track track, Time timestamp) = 0;
// The value is `Optional` to allow the demuxer to decide not to seek so that it can keep its position
// in the case that the timestamp is closer to the current time than the nearest keyframe.
virtual DecoderErrorOr<Optional<Time>> seek_to_most_recent_keyframe(Track track, Time timestamp, Optional<Time> earliest_available_sample = OptionalNone()) = 0;
virtual DecoderErrorOr<Time> duration() = 0;

View file

@ -5,6 +5,7 @@
*/
#include "MatroskaDemuxer.h"
#include "AK/Debug.h"
namespace Video::Matroska {
@ -53,7 +54,7 @@ DecoderErrorOr<MatroskaDemuxer::TrackStatus*> MatroskaDemuxer::get_track_status(
return &m_track_statuses.get(track).release_value();
}
DecoderErrorOr<Time> MatroskaDemuxer::seek_to_most_recent_keyframe(Track track, Time timestamp)
DecoderErrorOr<Optional<Time>> MatroskaDemuxer::seek_to_most_recent_keyframe(Track track, Time timestamp, Optional<Time> earliest_available_sample)
{
// Removing the track status will cause us to start from the beginning.
if (timestamp.is_zero()) {
@ -62,7 +63,22 @@ DecoderErrorOr<Time> MatroskaDemuxer::seek_to_most_recent_keyframe(Track track,
}
auto& track_status = *TRY(get_track_status(track));
TRY(m_reader.seek_to_random_access_point(track_status.iterator, timestamp));
auto seeked_iterator = TRY(m_reader.seek_to_random_access_point(track_status.iterator, timestamp));
VERIFY(seeked_iterator.last_timestamp().has_value());
auto last_sample = earliest_available_sample;
if (!last_sample.has_value()) {
last_sample = track_status.iterator.last_timestamp();
}
if (last_sample.has_value()) {
bool skip_seek = seeked_iterator.last_timestamp().value() <= last_sample.value() && last_sample.value() <= timestamp;
dbgln_if(MATROSKA_DEBUG, "The last available sample at {}ms is {}closer to target timestamp {}ms than the keyframe at {}ms, {}", last_sample->to_milliseconds(), skip_seek ? ""sv : "not "sv, timestamp.to_milliseconds(), seeked_iterator.last_timestamp()->to_milliseconds(), skip_seek ? "skipping seek"sv : "seeking"sv);
if (skip_seek) {
return OptionalNone();
}
}
track_status.iterator = move(seeked_iterator);
return track_status.iterator.last_timestamp();
}

View file

@ -27,7 +27,7 @@ public:
DecoderErrorOr<Vector<Track>> get_tracks_for_type(TrackType type) override;
DecoderErrorOr<Time> seek_to_most_recent_keyframe(Track track, Time timestamp) override;
DecoderErrorOr<Optional<Time>> seek_to_most_recent_keyframe(Track track, Time timestamp, Optional<Time> earliest_available_sample = OptionalNone()) override;
DecoderErrorOr<Time> duration() override;

View file

@ -806,7 +806,7 @@ DecoderErrorOr<void> Reader::seek_to_cue_for_timestamp(SampleIterator& iterator,
return {};
}
static DecoderErrorOr<bool> find_keyframe_before_timestamp(SampleIterator& iterator, Time const& timestamp)
static DecoderErrorOr<void> search_clusters_for_keyframe_before_timestamp(SampleIterator& iterator, Time const& timestamp)
{
#if MATROSKA_DEBUG
size_t inter_frames_count;
@ -837,10 +837,9 @@ static DecoderErrorOr<bool> find_keyframe_before_timestamp(SampleIterator& itera
dbgln("Seeked to a keyframe with {} inter frames to skip", inter_frames_count);
#endif
iterator = last_keyframe.release_value();
return true;
}
return false;
return {};
}
DecoderErrorOr<bool> Reader::has_cues_for_track(u64 track_number)
@ -849,37 +848,23 @@ DecoderErrorOr<bool> Reader::has_cues_for_track(u64 track_number)
return m_cues.contains(track_number);
}
DecoderErrorOr<void> Reader::seek_to_random_access_point(SampleIterator& iterator, Time timestamp)
DecoderErrorOr<SampleIterator> Reader::seek_to_random_access_point(SampleIterator iterator, Time timestamp)
{
if (iterator.m_last_timestamp == timestamp)
return {};
if (TRY(has_cues_for_track(iterator.m_track.track_number()))) {
auto seeked_iterator = iterator;
TRY(seek_to_cue_for_timestamp(seeked_iterator, timestamp));
VERIFY(seeked_iterator.m_last_timestamp <= timestamp);
// We only need to seek to a keyframe if it's not faster to continue from the current position.
if (timestamp < iterator.m_last_timestamp || seeked_iterator.m_last_timestamp > iterator.m_last_timestamp)
iterator = seeked_iterator;
return {};
TRY(seek_to_cue_for_timestamp(iterator, timestamp));
VERIFY(iterator.last_timestamp().has_value() && iterator.last_timestamp().value() <= timestamp);
return iterator;
}
// FIXME: This could cache the keyframes it finds. Is it worth doing? Probably not, most files will have Cues :^)
if (timestamp < iterator.last_timestamp() || iterator.last_timestamp().is_negative()) {
if (!iterator.last_timestamp().has_value() || timestamp < iterator.last_timestamp().value()) {
// If the timestamp is before the iterator's current position, then we need to start from the beginning of the Segment.
iterator = TRY(create_sample_iterator(iterator.m_track.track_number()));
if (!TRY(find_keyframe_before_timestamp(iterator, timestamp)))
return DecoderError::corrupted("No random access points found"sv);
return {};
TRY(search_clusters_for_keyframe_before_timestamp(iterator, timestamp));
return iterator;
}
auto seeked_iterator = iterator;
if (TRY(find_keyframe_before_timestamp(seeked_iterator, timestamp)))
iterator = seeked_iterator;
VERIFY(iterator.last_timestamp() <= timestamp);
return {};
TRY(search_clusters_for_keyframe_before_timestamp(iterator, timestamp));
return iterator;
}
DecoderErrorOr<Optional<Vector<CuePoint> const&>> Reader::cue_points_for_track(u64 track_number)

View file

@ -38,7 +38,7 @@ public:
DecoderErrorOr<size_t> track_count();
DecoderErrorOr<SampleIterator> create_sample_iterator(u64 track_number);
DecoderErrorOr<void> seek_to_random_access_point(SampleIterator&, Time);
DecoderErrorOr<SampleIterator> seek_to_random_access_point(SampleIterator, Time);
DecoderErrorOr<Optional<Vector<CuePoint> const&>> cue_points_for_track(u64 track_number);
DecoderErrorOr<bool> has_cues_for_track(u64 track_number);
@ -83,7 +83,7 @@ class SampleIterator {
public:
DecoderErrorOr<Block> next_block();
Cluster const& current_cluster() { return *m_current_cluster; }
Time const& last_timestamp() { return m_last_timestamp; }
Optional<Time> const& last_timestamp() { return m_last_timestamp; }
private:
friend class Reader;
@ -107,7 +107,7 @@ private:
// Must always point to an element ID or the end of the stream.
size_t m_position { 0 };
Time m_last_timestamp { Time::min() };
Optional<Time> m_last_timestamp;
Optional<Cluster> m_current_cluster;
};