A new MP3Navigator class is added, which determines timestamps for byte positions by resyncing to a frame and then interpolating between known points on either side. The known points start out as the first frame's position in the file at timestamp 0, and EOF at the timestamp for FFmpeg's file duration estimate. New buffered ranges are interpolated between those two points, but also between the end of a prior range and the start of the next. Since MP3 can have variable bitrate without declaring it in the file header, we have to allow buffered ranges to shift forward as new data arrives to make room for underestimated durations. This is done for all ranges following the first that has been appended to, keeping the start of the current range consistent, so that subsequent seeks within that range remain consistent. Seeking is also implemented within the navigator to ensure that the byte<->timestamp mapping is consistent and the buffered ranges begin exactly where the seek landed.
101 lines
3.7 KiB
C++
101 lines
3.7 KiB
C++
/*
|
|
* Copyright (c) 2025, Luke Wilde <luke@ladybird.org>
|
|
* Copyright (c) 2025, Gregory Bertilson <gregory@ladybird.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <AK/ByteBuffer.h>
|
|
#include <AK/Forward.h>
|
|
#include <AK/HashMap.h>
|
|
#include <AK/NonnullOwnPtr.h>
|
|
#include <AK/OwnPtr.h>
|
|
#include <LibMedia/CodecID.h>
|
|
#include <LibMedia/Demuxer.h>
|
|
#include <LibMedia/Export.h>
|
|
#include <LibMedia/FFmpeg/FFmpegForward.h>
|
|
#include <LibMedia/FFmpeg/FFmpegIOContext.h>
|
|
#include <LibMedia/Forward.h>
|
|
|
|
namespace Media::FFmpeg {
|
|
|
|
class MEDIA_API FFmpegDemuxer : public Demuxer {
|
|
public:
|
|
static DecoderErrorOr<NonnullRefPtr<FFmpegDemuxer>> from_stream(NonnullRefPtr<MediaStream> const&);
|
|
|
|
virtual ~FFmpegDemuxer() override;
|
|
|
|
virtual DecoderErrorOr<void> create_context_for_track(Track const&) override;
|
|
|
|
virtual DecoderErrorOr<Vector<Track>> get_tracks_for_type(TrackType) override;
|
|
virtual DecoderErrorOr<Optional<Track>> get_preferred_track_for_type(TrackType) override;
|
|
|
|
virtual AK::Duration select_fast_seek_target_for_track(Track const&, AK::Duration target, SeekMode) override;
|
|
virtual DecoderErrorOr<DemuxerSeekResult> seek_to_most_recent_keyframe(Track const&, AK::Duration timestamp, DemuxerSeekOptions) override;
|
|
|
|
virtual DecoderErrorOr<AK::Duration> duration_of_track(Track const&) override;
|
|
virtual DecoderErrorOr<AK::Duration> total_duration() override;
|
|
virtual Optional<AK::UnixDateTime> start_time_realtime() const override;
|
|
|
|
virtual TimeRanges buffered_time_ranges() const override;
|
|
|
|
virtual DecoderErrorOr<CodecID> get_codec_id_for_track(Track const&) override;
|
|
|
|
virtual DecoderErrorOr<ReadonlyBytes> get_codec_initialization_data_for_track(Track const&) override;
|
|
|
|
virtual DecoderErrorOr<CodedFrame> get_next_sample_for_track(Track const&) override;
|
|
|
|
virtual void set_blocking_reads_aborted_for_track(Track const&) override;
|
|
virtual void reset_blocking_reads_aborted_for_track(Track const&) override;
|
|
virtual bool is_read_blocked_for_track(Track const&) override;
|
|
|
|
private:
|
|
struct StreamInfo {
|
|
Track track;
|
|
CodecID codec_id;
|
|
ByteBuffer codec_initialization_data;
|
|
AK::Duration duration;
|
|
i32 time_base_numerator;
|
|
i32 time_base_denominator;
|
|
};
|
|
|
|
struct TrackContext {
|
|
TrackContext(NonnullRefPtr<MediaStreamCursor>&& cursor, NonnullOwnPtr<FFmpegIOContext>&& io_context)
|
|
: cursor(move(cursor))
|
|
, io_context(move(io_context))
|
|
{
|
|
}
|
|
~TrackContext();
|
|
TrackContext(TrackContext&&) = default;
|
|
|
|
NonnullRefPtr<MediaStreamCursor> cursor;
|
|
NonnullOwnPtr<FFmpegIOContext> io_context;
|
|
AVFormatContext* format_context { nullptr };
|
|
AVPacket* packet { nullptr };
|
|
bool is_seekable { true };
|
|
bool peeked_packet_already { false };
|
|
Optional<AK::Duration> pending_timestamp_offset;
|
|
AK::Duration timestamp_offset;
|
|
};
|
|
|
|
FFmpegDemuxer(NonnullRefPtr<MediaStream> const&);
|
|
|
|
static OwnPtr<ContainerNavigator> create_container_navigator(AVFormatContext&, AK::Duration, NonnullRefPtr<MediaStream> const&);
|
|
static OwnPtr<ContainerNavigator> create_container_navigator_from_index(AVFormatContext&);
|
|
|
|
StreamInfo const& get_track_info(Track const&) const;
|
|
TrackContext& get_track_context(Track const&);
|
|
|
|
NonnullRefPtr<MediaStream> m_stream;
|
|
AK::Duration m_total_duration;
|
|
Optional<AK::UnixDateTime> m_start_time_realtime;
|
|
Vector<StreamInfo> m_stream_info;
|
|
OwnPtr<ContainerNavigator> m_container_navigator;
|
|
Array<int, to_underlying(TrackType::Unknown)> m_preferred_track_for_type;
|
|
|
|
HashMap<Track, NonnullOwnPtr<TrackContext>> m_track_contexts;
|
|
};
|
|
|
|
}
|