LibMedia: Allow demuxers to specify a preferred track

...and return all tracks of a matching type from FFmpegDemuxer, rather
than only the single best one.
This commit is contained in:
Zaggy1024 2025-09-11 18:25:05 -05:00 committed by Jelle Raaijmakers
parent 8d64e72655
commit 6653b747ff
Notes: github-actions[bot] 2025-09-12 09:25:12 +00:00
8 changed files with 138 additions and 58 deletions

View file

@ -25,47 +25,83 @@ DecoderErrorOr<NonnullOwnPtr<MatroskaDemuxer>> MatroskaDemuxer::from_data(Readon
return make<MatroskaDemuxer>(TRY(Reader::from_data(data)));
}
DecoderErrorOr<Vector<Track>> MatroskaDemuxer::get_tracks_for_type(TrackType type)
static TrackEntry::TrackType matroska_track_type_from_track_type(TrackType type)
{
TrackEntry::TrackType matroska_track_type;
switch (type) {
case TrackType::Video:
matroska_track_type = TrackEntry::TrackType::Video;
break;
return TrackEntry::TrackType::Video;
case TrackType::Audio:
matroska_track_type = TrackEntry::TrackType::Audio;
break;
return TrackEntry::TrackType::Audio;
case TrackType::Subtitles:
matroska_track_type = TrackEntry::TrackType::Subtitle;
return TrackEntry::TrackType::Subtitle;
case TrackType::Unknown:
return TrackEntry::TrackType::Invalid;
}
VERIFY_NOT_REACHED();
}
static TrackType track_type_from_matroska_track_type(TrackEntry::TrackType type)
{
switch (type) {
case TrackEntry::TrackType::Video:
return TrackType::Video;
case TrackEntry::TrackType::Audio:
return TrackType::Audio;
case TrackEntry::TrackType::Subtitle:
return TrackType::Subtitles;
case TrackEntry::TrackType::Invalid:
return TrackType::Unknown;
case TrackEntry::TrackType::Complex:
case TrackEntry::TrackType::Logo:
case TrackEntry::TrackType::Buttons:
case TrackEntry::TrackType::Control:
case TrackEntry::TrackType::Metadata:
break;
}
VERIFY_NOT_REACHED();
}
static Track track_from_track_entry(TrackEntry const& track_entry)
{
Track track(track_type_from_matroska_track_type(track_entry.track_type()), track_entry.track_number());
if (track.type() == TrackType::Video) {
auto video_track = track_entry.video_track();
if (video_track.has_value()) {
track.set_video_data({
.pixel_width = video_track->pixel_width,
.pixel_height = video_track->pixel_height,
});
}
}
return track;
}
DecoderErrorOr<Vector<Track>> MatroskaDemuxer::get_tracks_for_type(TrackType type)
{
auto matroska_track_type = matroska_track_type_from_track_type(type);
Vector<Track> tracks;
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);
Track track(type, track_entry.track_number());
switch (type) {
case TrackType::Video:
if (auto video_track = track_entry.video_track(); video_track.has_value()) {
track.set_video_data({
.pixel_width = video_track->pixel_width,
.pixel_height = video_track->pixel_height
});
}
break;
default:
break;
}
DECODER_TRY_ALLOC(tracks.try_append(track));
DECODER_TRY_ALLOC(tracks.try_append(track_from_track_entry(track_entry)));
return IterationDecision::Continue;
}));
return tracks;
}
DecoderErrorOr<Optional<Track>> MatroskaDemuxer::get_preferred_track_for_type(TrackType type)
{
auto matroska_track_type = matroska_track_type_from_track_type(type);
Optional<Track> result;
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);
result = track_from_track_entry(track_entry);
return IterationDecision::Break;
}));
return result;
}
DecoderErrorOr<MatroskaDemuxer::TrackStatus*> MatroskaDemuxer::get_track_status(Track track)
{
if (!m_track_statuses.contains(track)) {

View file

@ -28,6 +28,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 track, AK::Duration timestamp, Optional<AK::Duration> earliest_available_sample = OptionalNone()) override;

View file

@ -21,6 +21,9 @@ public:
virtual ~Demuxer() = default;
virtual DecoderErrorOr<Vector<Track>> get_tracks_for_type(TrackType type) = 0;
// Returns the container's preferred track for a given track type. This must return a value if any track of the
// given type is present.
virtual DecoderErrorOr<Optional<Track>> get_preferred_track_for_type(TrackType type) = 0;
virtual DecoderErrorOr<Sample> get_next_sample_for_track(Track track) = 0;

View file

@ -85,47 +85,48 @@ DecoderErrorOr<AK::Duration> FFmpegDemuxer::duration_of_track(Track const& track
return time_units_to_duration(m_format_context->duration, 1, AV_TIME_BASE);
}
DecoderErrorOr<Vector<Track>> FFmpegDemuxer::get_tracks_for_type(TrackType type)
DecoderErrorOr<Track> FFmpegDemuxer::get_track_for_stream_index(u32 stream_index)
{
AVMediaType media_type;
VERIFY(stream_index < m_format_context->nb_streams);
switch (type) {
case TrackType::Video:
media_type = AVMediaType::AVMEDIA_TYPE_VIDEO;
break;
case TrackType::Audio:
media_type = AVMediaType::AVMEDIA_TYPE_AUDIO;
break;
case TrackType::Subtitles:
media_type = AVMediaType::AVMEDIA_TYPE_SUBTITLE;
break;
}
// Find the best stream to play within the container
int best_stream_index = av_find_best_stream(m_format_context, media_type, -1, -1, nullptr, 0);
if (best_stream_index == AVERROR_STREAM_NOT_FOUND)
return DecoderError::format(DecoderErrorCategory::Unknown, "No stream for given type found in container");
if (best_stream_index == AVERROR_DECODER_NOT_FOUND)
return DecoderError::format(DecoderErrorCategory::Unknown, "No suitable decoder found for stream");
if (best_stream_index < 0)
return DecoderError::format(DecoderErrorCategory::Unknown, "Failed to find a stream for the given type");
auto* stream = m_format_context->streams[best_stream_index];
Track track(type, best_stream_index);
auto& stream = *m_format_context->streams[stream_index];
auto type = track_type_from_ffmpeg_media_type(stream.codecpar->codec_type);
Track track(type, stream_index);
if (type == TrackType::Video) {
track.set_video_data({
.pixel_width = static_cast<u64>(stream->codecpar->width),
.pixel_height = static_cast<u64>(stream->codecpar->height),
.pixel_width = static_cast<u64>(stream.codecpar->width),
.pixel_height = static_cast<u64>(stream.codecpar->height),
});
}
Vector<Track> tracks;
tracks.append(move(track));
return track;
}
DecoderErrorOr<Vector<Track>> FFmpegDemuxer::get_tracks_for_type(TrackType type)
{
auto media_type = ffmpeg_media_type_from_track_type(type);
Vector<Track> tracks = {};
for (u32 i = 0; i < m_format_context->nb_streams; i++) {
auto& stream = *m_format_context->streams[i];
if (stream.codecpar->codec_type != media_type)
continue;
tracks.append(TRY(get_track_for_stream_index(i)));
}
return tracks;
}
DecoderErrorOr<Optional<Track>> FFmpegDemuxer::get_preferred_track_for_type(TrackType type)
{
auto media_type = ffmpeg_media_type_from_track_type(type);
auto best_stream_index = av_find_best_stream(m_format_context, media_type, -1, -1, nullptr, 0);
if (best_stream_index < 0)
return OptionalNone();
return get_track_for_stream_index(best_stream_index);
}
DecoderErrorOr<Optional<AK::Duration>> FFmpegDemuxer::seek_to_most_recent_keyframe(Track track, AK::Duration timestamp, Optional<AK::Duration> earliest_available_sample)
{
// FIXME: What do we do with this here?

View file

@ -26,6 +26,7 @@ public:
virtual ~FFmpegDemuxer() override;
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 track, AK::Duration timestamp, Optional<AK::Duration> earliest_available_sample = OptionalNone()) override;
@ -39,6 +40,7 @@ public:
private:
DecoderErrorOr<AK::Duration> duration_of_track(Track const& track);
DecoderErrorOr<Track> get_track_for_stream_index(u32 stream_index);
NonnullOwnPtr<SeekableStream> m_stream;
AVCodecContext* m_codec_context { nullptr };

View file

@ -7,6 +7,7 @@
#pragma once
#include <LibMedia/CodecID.h>
#include <LibMedia/Track.h>
extern "C" {
#include <libavcodec/avcodec.h>
@ -76,4 +77,38 @@ static inline CodecID media_codec_id_from_ffmpeg_codec_id(AVCodecID codec)
}
}
static inline AVMediaType ffmpeg_media_type_from_track_type(TrackType track_type)
{
switch (track_type) {
case TrackType::Video:
return AVMediaType::AVMEDIA_TYPE_VIDEO;
case TrackType::Audio:
return AVMediaType::AVMEDIA_TYPE_AUDIO;
case TrackType::Subtitles:
return AVMediaType::AVMEDIA_TYPE_SUBTITLE;
case TrackType::Unknown:
return AVMediaType::AVMEDIA_TYPE_UNKNOWN;
}
VERIFY_NOT_REACHED();
}
static inline TrackType track_type_from_ffmpeg_media_type(AVMediaType media_type)
{
switch (media_type) {
case AVMediaType::AVMEDIA_TYPE_VIDEO:
return TrackType::Video;
case AVMediaType::AVMEDIA_TYPE_AUDIO:
return TrackType::Audio;
case AVMediaType::AVMEDIA_TYPE_SUBTITLE:
return TrackType::Subtitles;
case AVMediaType::AVMEDIA_TYPE_DATA:
case AVMediaType::AVMEDIA_TYPE_ATTACHMENT:
case AVMediaType::AVMEDIA_TYPE_UNKNOWN:
return TrackType::Unknown;
case AVMediaType::AVMEDIA_TYPE_NB:
VERIFY_NOT_REACHED();
}
VERIFY_NOT_REACHED();
}
}

View file

@ -698,11 +698,12 @@ private:
DecoderErrorOr<NonnullOwnPtr<PlaybackManager>> PlaybackManager::create(NonnullOwnPtr<Demuxer> demuxer)
{
auto video_tracks = TRY(demuxer->get_tracks_for_type(TrackType::Video));
if (video_tracks.is_empty())
auto optional_track = TRY(demuxer->get_preferred_track_for_type(TrackType::Video));
if (!optional_track.has_value()) {
return DecoderError::with_description(DecoderErrorCategory::Invalid, "No video track is present"sv);
auto track = video_tracks[0];
}
auto track = optional_track.release_value();
dbgln_if(PLAYBACK_MANAGER_DEBUG, "Selecting video track number {}", track.identifier());
auto codec_id = TRY(demuxer->get_codec_id_for_track(track));

View file

@ -18,6 +18,7 @@ enum class TrackType : u32 {
Video,
Audio,
Subtitles,
Unknown,
};
class Track {