PR #24211 opened by kostazol URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24211 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24211.patch
# avformat/hlsenc: provide next packet timing before fMP4 fragment flush This pull request addresses [FFmpeg issue #24207](https://code.ffmpeg.org/FFmpeg/FFmpeg/issues/24207). ## What the patch changes The HLS muxer currently flushes the child fragmented-MOV muxer before the boundary packet is forwarded to it. When the preceding Matroska packet has a two-frame duration, MOV's accumulated `track_duration` can extend past the DTS of the next HLS boundary packet. The subsequent `check_pkt()` path then rewrites that packet's DTS and clears its PTS. This patch: * adds the internal helper `ff_mov_set_fragment_end_hint()`; * passes the next reference packet's DTS/PTS to the child MOV muxer immediately before the fMP4 fragment flush; * converts the hint into the child MOV time base with the muxer offset and `dts_shift` applied; * shrinks the accumulated fragment duration only when the next DTS is monotonic and overlaps the current duration; * leaves the boundary packet itself unchanged; * does not modify Matroska demuxing or generic `check_pkt()` behavior. The change is narrowly scoped and is a no-op when the accumulated fragment duration does not overlap the next packet DTS. No regressions were observed in the completed controls and tests. ## Validation All measurements below use the clean upstream master at `564f92cce23ae95399476617b8a1dc357f002a47` and the three-file patch in this branch. ### Minimal MKV reproducer The unmodified build reports `1` `Packet duration` warning and `1` `pts has no value` warning. The patched build reports `0/0` and keeps two HLS media segments with the same `7.175000` second playlist duration. At the first affected boundary, the unmodified output changes the incoming packet from PTS/DTS `112112/109440` to `110097/110097`. With the patch, the previous sample's effective duration is `672` ticks and the boundary packet remains PTS/DTS `112112/109440`. ### Full MKV The unmodified build reports `336/336` warnings. The patched build reports `0/0`, keeps `518` media segments, and keeps the `3108.396000` second playlist duration. All former affected boundaries are covered by the same shrink-only accounting. ### Controls and payloads The minimal MP4 no-overlap control reports `0/0` before and after the patch; its playlist, video packet table, audio packet table, and both media segments are byte-identical. MPEG-TS and static MP4 controls are byte-identical in the tested minimal and full cases. The full MP4 artifact is not a no-overlap control in this test set: clean master reports `81/81` warnings, so its patched fMP4 output is expected to change. The independently extracted Annex-B HEVC payload of the minimal MKV and MP4 controls matches: `d4d92728d6455781e213252330c93c2cfa27e539a89ea90186b4fccc5027f502` The baseline and patched HLS video payloads also match, and the tested audio packet tables are identical. ### Tests Targeted HLS fMP4, MOV fragment-flush, VFR/B-frame duration, and hybrid fragmented-MOV FATE tests passed on both clean and patched builds. The full `make fate` run reached the same pre-existing failure, `mov-reenc-delete-format-metadata`, on both builds; no additional failure was observed. Two optional full-input `hls_time=10` matrix runs were not completed because the WSL input path returned I/O errors after many segments. The complete validation report and reproducible artifacts are available in the accompanying research package. >From 51ccfb7f0ee1f9321ebd0eaa0d3af08be7e3f147 Mon Sep 17 00:00:00 2001 From: Kostazol <[email protected]> Date: Wed, 19 Aug 2026 23:00:57 +0700 Subject: [PATCH] avformat/hlsenc: provide next packet timing before fMP4 fragment flush Pass the next HLS boundary packet timing to the child MOV muxer before flushing an fMP4 fragment. The MOV helper only shrinks an overlapping accumulated fragment duration; normal boundaries remain unchanged and the boundary packet itself is not modified. --- libavformat/hlsenc.c | 8 ++++++++ libavformat/movenc.c | 42 ++++++++++++++++++++++++++++++++++++++++++ libavformat/movenc.h | 3 +++ 3 files changed, 53 insertions(+) diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c index 50f19f9694..4726a4a708 100644 --- a/libavformat/hlsenc.c +++ b/libavformat/hlsenc.c @@ -53,6 +53,9 @@ #include "hlsplaylist.h" #include "internal.h" #include "mux.h" +#if CONFIG_MP4_MUXER +#include "movenc.h" +#endif #include "os_support.h" #include "url.h" @@ -2520,6 +2523,11 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt) int byterange_mode = (hls->flags & HLS_SINGLE_FILE) || (hls->max_seg_size > 0); double cur_duration; +#if CONFIG_MP4_MUXER + if (hls->segment_type == SEGMENT_TYPE_FMP4 && is_ref_pkt && + pkt->dts != AV_NOPTS_VALUE) + ff_mov_set_fragment_end_hint(oc, stream_index, pkt, st->time_base); +#endif av_write_frame(oc, NULL); /* Flush any buffered data */ new_start_pos = avio_tell(oc->pb); vs->size = new_start_pos - vs->start_pos; diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 2a3226c70f..367caecee9 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -6948,6 +6948,48 @@ static int check_pkt(AVFormatContext *s, MOVTrack *trk, AVPacket *pkt) return 0; } +int ff_mov_set_fragment_end_hint(AVFormatContext *s, int stream_index, + const AVPacket *pkt, AVRational src_time_base) +{ + MOVMuxContext *mov = s->priv_data; + AVStream *st; + MOVTrack *track; + int64_t offset, dts, pts, candidate_duration; + + if (!(mov->flags & FF_MOV_FLAG_FRAGMENT) || + stream_index < 0 || stream_index >= s->nb_streams || + pkt->dts == AV_NOPTS_VALUE) + return 0; + + st = s->streams[stream_index]; + track = st->priv_data; + if (!track->entry || track->start_dts == AV_NOPTS_VALUE) + return 0; + + if (ff_get_muxer_ts_offset(s, stream_index, &offset) < 0) + return 0; + + dts = av_rescale_q(pkt->dts, src_time_base, st->time_base) + offset; + pts = pkt->pts == AV_NOPTS_VALUE + ? AV_NOPTS_VALUE + : av_rescale_q(pkt->pts, src_time_base, st->time_base) + offset; + if (track->dts_shift != AV_NOPTS_VALUE) + dts += track->dts_shift; + + candidate_duration = dts - track->start_dts; + if (dts <= track->cluster[track->entry - 1].dts || + candidate_duration < 0 || candidate_duration >= track->track_duration) + return 0; + + track->track_duration = candidate_duration; + track->end_pts = pts != AV_NOPTS_VALUE ? pts : dts; + if (!(pkt->flags & AV_PKT_FLAG_DISCARD)) + track->elst_end_pts = track->end_pts; + track->end_reliable = 1; + + return 1; +} + int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) { MOVMuxContext *mov = s->priv_data; diff --git a/libavformat/movenc.h b/libavformat/movenc.h index 5d1e7099b4..c375659c93 100644 --- a/libavformat/movenc.h +++ b/libavformat/movenc.h @@ -303,6 +303,9 @@ typedef struct MOVMuxContext { int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt); +int ff_mov_set_fragment_end_hint(AVFormatContext *s, int stream_index, + const AVPacket *pkt, AVRational src_time_base); + int ff_mov_init_hinting(AVFormatContext *s, int index, int src_index); int ff_mov_add_hinted_packet(AVFormatContext *s, AVPacket *pkt, int track_index, int sample, -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
