From cae14c763dec70bdbee679c1a58d195e72463930 Mon Sep 17 00:00:00 2001 From: Zaggy1024 Date: Fri, 5 Sep 2025 17:38:27 -0500 Subject: [PATCH] LibMedia: Convert FFmpeg time units to AK::Duration with integer math The existing conversion was rounding to the nearest millisecond, which is much less precision than most videos will want. Instead, use only integer math to directly convert the presentation time to seconds and nanoseconds for our AK::Duration to represent accurately. --- Libraries/LibMedia/FFmpeg/FFmpegDemuxer.cpp | 35 ++++++++++++++------- Libraries/LibMedia/FFmpeg/FFmpegDemuxer.h | 2 +- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/Libraries/LibMedia/FFmpeg/FFmpegDemuxer.cpp b/Libraries/LibMedia/FFmpeg/FFmpegDemuxer.cpp index 34d0327b2bd..6a7ceb012d9 100644 --- a/Libraries/LibMedia/FFmpeg/FFmpegDemuxer.cpp +++ b/Libraries/LibMedia/FFmpeg/FFmpegDemuxer.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -51,15 +52,29 @@ ErrorOr> FFmpegDemuxer::create(NonnullOwnPtr FFmpegDemuxer::duration_of_track_in_milliseconds(Track const& track) +static inline AK::Duration time_units_to_duration(i64 time_units, int numerator, int denominator) +{ + VERIFY(numerator != 0); + VERIFY(denominator != 0); + auto seconds = time_units * numerator / denominator; + auto seconds_in_time_units = seconds * denominator / numerator; + auto remainder_in_time_units = time_units - seconds_in_time_units; + auto nanoseconds = ((remainder_in_time_units * 1'000'000'000 * numerator) + (denominator / 2)) / denominator; + return AK::Duration::from_seconds(seconds) + AK::Duration::from_nanoseconds(nanoseconds); +} + +static inline AK::Duration time_units_to_duration(i64 time_units, AVRational const& time_base) +{ + return time_units_to_duration(time_units, time_base.num, time_base.den); +} + +DecoderErrorOr FFmpegDemuxer::duration_of_track(Track const& track) { VERIFY(track.identifier() < m_format_context->nb_streams); auto* stream = m_format_context->streams[track.identifier()]; if (stream->duration >= 0) { - auto time_base = av_q2d(stream->time_base); - double duration_in_milliseconds = static_cast(stream->duration) * time_base * 1000.0; - return AK::Duration::from_milliseconds(AK::round_to(duration_in_milliseconds)); + return time_units_to_duration(stream->duration, stream->time_base); } // If the stream doesn't specify the duration, fallback to what the container says the duration is. @@ -67,8 +82,7 @@ DecoderErrorOr FFmpegDemuxer::duration_of_track_in_milliseconds(Tr if (m_format_context->duration < 0) return DecoderError::format(DecoderErrorCategory::Unknown, "Negative stream duration"); - double duration_in_milliseconds = (static_cast(m_format_context->duration) / AV_TIME_BASE) * 1000.0; - return AK::Duration::from_milliseconds(AK::round_to(duration_in_milliseconds)); + return time_units_to_duration(m_format_context->duration, 1, AV_TIME_BASE); } DecoderErrorOr> FFmpegDemuxer::get_tracks_for_type(TrackType type) @@ -102,7 +116,7 @@ DecoderErrorOr> FFmpegDemuxer::get_tracks_for_type(TrackType type) if (type == TrackType::Video) { track.set_video_data({ - .duration = TRY(duration_of_track_in_milliseconds(track)), + .duration = TRY(duration_of_track(track)), .pixel_width = static_cast(stream->codecpar->width), .pixel_height = static_cast(stream->codecpar->height), }); @@ -132,7 +146,7 @@ DecoderErrorOr> FFmpegDemuxer::seek_to_most_recent_keyfra DecoderErrorOr FFmpegDemuxer::duration(Track track) { - return duration_of_track_in_milliseconds(track); + return duration_of_track(track); } DecoderErrorOr FFmpegDemuxer::get_codec_id_for_track(Track track) @@ -181,15 +195,12 @@ DecoderErrorOr FFmpegDemuxer::get_next_sample_for_track(Track track) } }(); - auto time_base = av_q2d(stream->time_base); - double timestamp_in_milliseconds = static_cast(m_packet->pts) * time_base * 1000.0; - // Copy the packet data so that we have a permanent reference to it whilst the Sample is alive, which allows us // to wipe the packet afterwards. auto packet_data = DECODER_TRY_ALLOC(ByteBuffer::copy(m_packet->data, m_packet->size)); auto sample = Sample( - AK::Duration::from_milliseconds(AK::round_to(timestamp_in_milliseconds)), + time_units_to_duration(m_packet->pts, stream->time_base), move(packet_data), VideoSampleData(CodingIndependentCodePoints(color_primaries, transfer_characteristics, matrix_coefficients, color_range))); diff --git a/Libraries/LibMedia/FFmpeg/FFmpegDemuxer.h b/Libraries/LibMedia/FFmpeg/FFmpegDemuxer.h index 97a91e25de5..7d011486a69 100644 --- a/Libraries/LibMedia/FFmpeg/FFmpegDemuxer.h +++ b/Libraries/LibMedia/FFmpeg/FFmpegDemuxer.h @@ -38,7 +38,7 @@ public: virtual DecoderErrorOr get_next_sample_for_track(Track track) override; private: - DecoderErrorOr duration_of_track_in_milliseconds(Track const& track); + DecoderErrorOr duration_of_track(Track const& track); NonnullOwnPtr m_stream; AVCodecContext* m_codec_context { nullptr };