LibMedia: Give demuxer seeks an option to always seek to a keyframe

By default, MatroskaDemuxer chooses not to seek if the current frame
is closer to the seek target than the keyframe that precedes the seek
target. However, it can be desirable to seek to a keyframe anyway, so
let's allow that.
This commit is contained in:
Zaggy1024 2025-10-02 18:03:36 -05:00 committed by Jelle Raaijmakers
parent e94ab24e66
commit 86e236519d
6 changed files with 18 additions and 13 deletions

View File

@ -169,7 +169,7 @@ DecoderErrorOr<ReadonlyBytes> MatroskaDemuxer::get_codec_initialization_data_for
return TRY(m_reader.track_for_track_number(track.identifier()))->codec_private_data();
}
DecoderErrorOr<Optional<AK::Duration>> MatroskaDemuxer::seek_to_most_recent_keyframe(Track const& track, AK::Duration timestamp, Optional<AK::Duration> earliest_available_sample)
DecoderErrorOr<Optional<AK::Duration>> MatroskaDemuxer::seek_to_most_recent_keyframe(Track const& track, AK::Duration timestamp, DemuxerSeekOptions options)
{
// Removing the track status will cause us to start from the beginning.
if (timestamp.is_zero()) {
@ -180,9 +180,9 @@ DecoderErrorOr<Optional<AK::Duration>> MatroskaDemuxer::seek_to_most_recent_keyf
auto& track_status = *TRY(get_track_status(track));
auto seeked_iterator = TRY(m_reader.seek_to_random_access_point(track_status.iterator, timestamp));
auto last_sample = earliest_available_sample;
if (!last_sample.has_value())
last_sample = track_status.iterator.last_timestamp();
auto last_sample = track_status.iterator.last_timestamp();
if (has_flag(options, DemuxerSeekOptions::Force))
last_sample = {};
if (last_sample.has_value() && seeked_iterator.last_timestamp().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);

View File

@ -30,7 +30,7 @@ public:
DecoderErrorOr<Vector<Track>> get_tracks_for_type(TrackType type) override;
DecoderErrorOr<Optional<Track>> get_preferred_track_for_type(TrackType type) override;
DecoderErrorOr<Optional<AK::Duration>> seek_to_most_recent_keyframe(Track const& track, AK::Duration timestamp, Optional<AK::Duration> earliest_available_sample = OptionalNone()) override;
DecoderErrorOr<Optional<AK::Duration>> seek_to_most_recent_keyframe(Track const& track, AK::Duration timestamp, DemuxerSeekOptions) override;
DecoderErrorOr<AK::Duration> duration_of_track(Track const& track) override;
DecoderErrorOr<AK::Duration> total_duration() override;

View File

@ -7,6 +7,7 @@
#pragma once
#include <AK/AtomicRefCounted.h>
#include <AK/EnumBits.h>
#include <AK/NonnullOwnPtr.h>
#include <LibCore/EventReceiver.h>
@ -17,6 +18,13 @@
namespace Media {
enum class DemuxerSeekOptions : u8 {
None = 0,
Force = 1 << 0,
};
AK_ENUM_BITWISE_OPERATORS(DemuxerSeekOptions);
class Demuxer : public AtomicRefCounted<Demuxer> {
public:
virtual ~Demuxer() = default;
@ -35,7 +43,7 @@ public:
// Returns the timestamp of the keyframe that was seeked to.
// 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<AK::Duration>> seek_to_most_recent_keyframe(Track const& track, AK::Duration timestamp, Optional<AK::Duration> earliest_available_sample = OptionalNone()) = 0;
virtual DecoderErrorOr<Optional<AK::Duration>> seek_to_most_recent_keyframe(Track const& track, AK::Duration timestamp, DemuxerSeekOptions = DemuxerSeekOptions::None) = 0;
virtual DecoderErrorOr<AK::Duration> duration_of_track(Track const&) = 0;
virtual DecoderErrorOr<AK::Duration> total_duration() = 0;

View File

@ -141,11 +141,8 @@ DecoderErrorOr<Optional<Track>> FFmpegDemuxer::get_preferred_track_for_type(Trac
return get_track_for_stream_index(best_stream_index);
}
DecoderErrorOr<Optional<AK::Duration>> FFmpegDemuxer::seek_to_most_recent_keyframe(Track const& track, AK::Duration timestamp, Optional<AK::Duration> earliest_available_sample)
DecoderErrorOr<Optional<AK::Duration>> FFmpegDemuxer::seek_to_most_recent_keyframe(Track const& track, AK::Duration timestamp, DemuxerSeekOptions)
{
// FIXME: What do we do with this here?
(void)earliest_available_sample;
VERIFY(track.identifier() < m_format_context->nb_streams);
auto* stream = m_format_context->streams[track.identifier()];
auto time_base = av_q2d(stream->time_base);

View File

@ -29,7 +29,7 @@ public:
virtual DecoderErrorOr<Vector<Track>> get_tracks_for_type(TrackType type) override;
virtual DecoderErrorOr<Optional<Track>> get_preferred_track_for_type(TrackType type) override;
virtual DecoderErrorOr<Optional<AK::Duration>> seek_to_most_recent_keyframe(Track const& track, AK::Duration timestamp, Optional<AK::Duration> earliest_available_sample = OptionalNone()) override;
virtual DecoderErrorOr<Optional<AK::Duration>> seek_to_most_recent_keyframe(Track const& track, AK::Duration timestamp, DemuxerSeekOptions) override;
virtual DecoderErrorOr<AK::Duration> duration_of_track(Track const&) override;
virtual DecoderErrorOr<AK::Duration> total_duration() override;

View File

@ -62,10 +62,10 @@ public:
});
}
virtual DecoderErrorOr<Optional<AK::Duration>> seek_to_most_recent_keyframe(Track const& track, AK::Duration timestamp, Optional<AK::Duration> earliest_available_sample = {}) override
virtual DecoderErrorOr<Optional<AK::Duration>> seek_to_most_recent_keyframe(Track const& track, AK::Duration timestamp, DemuxerSeekOptions seek_options = DemuxerSeekOptions::None) override
{
return m_demuxer.with_locked([&](auto& demuxer) {
return demuxer->seek_to_most_recent_keyframe(track, timestamp, earliest_available_sample);
return demuxer->seek_to_most_recent_keyframe(track, timestamp, seek_options);
});
}