The demuxer implements the RFC 8216 section 6.3.3 hold-back rule through
live_start_index (default -3: start three segments from the end of a
live playlist). When a live playlist initially contains fewer segments
than that, the start position silently clamps to the newest segment and
playback begins at the live edge with no hold-back margin. Every frame
then arrives at approximately its presentation deadline, so realtime
players continuously drop late frames while audio plays on: video
appears frozen or stuttering and does not recover. Servers commonly
publish a live playlist as soon as its first segment completes
(MediaMTX, GStreamer hlssink3, ffmpeg's own hls muxer), so this is easy
to hit by opening a stream shortly after it starts.

Add a live_start_wait option: when enabled and a live playlist has
fewer segments than a negative live_start_index needs, reload it until
enough segments exist. The wait is interruptible, bounded by roughly
the number of missing segments times the target duration, abandoned
early when the server's sliding window turns out to be smaller than the
requested position (further waiting cannot help), and skipped when
EXT-X-START is present and preferred. No media is skipped compared to
the previous behaviour: with too few segments the old code started at
the oldest listed segment anyway; waiting starts at that same segment
and only delays avformat_open_input() by the time the server needs to
produce the missing segments.

The option is disabled by default to keep existing open() latency
behaviour; realtime players are encouraged to enable it.

Measured with ffplay against a MediaMTX mpegts HLS live stream (4 s
GOP), attaching at the moment the playlist listed one segment, 65 s
observation: default/off: 135 late video frames dropped, sustained;
live_start_wait=1: 0.
---
 doc/demuxers.texi     |  9 +++++++
 libavformat/hls.c     | 56 +++++++++++++++++++++++++++++++++++++++++++
 libavformat/version.h |  2 +-
 3 files changed, 66 insertions(+), 1 deletion(-)

diff --git a/doc/demuxers.texi b/doc/demuxers.texi
index a1dd879..5a1141a 100644
--- a/doc/demuxers.texi
+++ b/doc/demuxers.texi
@@ -562,6 +562,15 @@ It accepts the following options:
 @item live_start_index
 segment index to start live streams at (negative values are from the end).
 
+@item live_start_wait
+If enabled, opening a live stream whose playlist does not yet contain enough
+segments for a negative @option{live_start_index} position waits (bounded by
+roughly the missing number of target durations) for the playlist to grow,
+instead of starting at the newest segment with no hold-back margin. Starting a
+live stream closer than three target durations to the end of the playlist
+violates the HLS specification's hold-back rule and typically degrades
+realtime playback into sustained late-frame drops. Disabled by default.
+
 @item prefer_x_start
 prefer to use #EXT-X-START if it's in playlist instead of live_start_index.
 
diff --git a/libavformat/hls.c b/libavformat/hls.c
index e76e68e..71a7443 100644
--- a/libavformat/hls.c
+++ b/libavformat/hls.c
@@ -228,6 +228,7 @@ typedef struct HLSContext {
     int64_t cur_seq_no;
     int m3u8_hold_counters;
     int live_start_index;
+    int live_start_wait;
     int prefer_x_start;
     int first_packet;
     int64_t first_timestamp;
@@ -2360,6 +2361,59 @@ static int hls_read_header(AVFormatContext *s)
         av_dict_set_int(&program->metadata, "variant_bitrate", v->bandwidth, 
0);
     }
 
+    /* A live playlist that is too short offers no start position satisfying
+     * the hold-back rule (RFC 8216 6.3.3: do not choose a segment closer than
+     * three target durations to the end). live_start_index then clamps to the
+     * newest segment and playback starts with zero scheduling margin, which
+     * degrades realtime playback into sustained late-frame drops. Servers
+     * commonly publish a live playlist as soon as its first segment exists,
+     * so optionally reload until enough segments accumulate (bounded,
+     * interruptible, and abandoned early when the server's sliding window is
+     * too small to ever satisfy the request). Only meaningful for negative
+     * live_start_index (positions relative to the live edge). */
+    for (i = 0; c->live_start_wait && c->live_start_index < 0 && i < 
c->n_playlists; i++) {
+        struct playlist *pls = c->playlists[i];
+        int64_t needed  = -(int64_t)c->live_start_index;
+        int64_t first_seq, per_seg, max_wait, waited = 0;
+
+        if (pls->finished || pls->is_subtitle || !pls->n_segments ||
+            pls->n_segments >= needed ||
+            (c->prefer_x_start && pls->time_offset_flag))
+            continue;
+
+        per_seg  = pls->target_duration > 0 &&
+                   pls->target_duration < 60 * AV_TIME_BASE ?
+                   pls->target_duration : 5 * AV_TIME_BASE;
+        max_wait = FFMIN(needed - pls->n_segments + 1, 16) * per_seg;
+        first_seq = pls->start_seq_no + pls->n_segments;
+        av_log(s, AV_LOG_INFO,
+               "Live playlist has %d segment(s), fewer than %"PRId64" needed 
for a "
+               "safe live start position; waiting up to %.1fs for more\n",
+               pls->n_segments, needed, max_wait / (double)AV_TIME_BASE);
+        while (pls->n_segments < needed && waited < max_wait) {
+            if (ff_check_interrupt(c->interrupt_callback))
+                return AVERROR_EXIT;
+            av_usleep(200 * 1000);
+            waited += 200 * 1000;
+            if (av_gettime_relative() - pls->last_load_time >=
+                default_reload_interval(pls)) {
+                int err = parse_playlist(c, pls->url, pls, NULL);
+                if (err == AVERROR_EXIT)
+                    return err;
+                if (err < 0)
+                    break;
+                if (pls->finished)
+                    break;
+                /* The server has produced enough new segments, yet the window
+                 * still cannot hold them: it is smaller than needed, and
+                 * waiting further cannot help. */
+                if (pls->start_seq_no + pls->n_segments - first_seq >= needed 
&&
+                    pls->n_segments < needed)
+                    break;
+            }
+        }
+    }
+
     /* Select the starting segments */
     for (i = 0; i < c->n_playlists; i++) {
         struct playlist *pls = c->playlists[i];
@@ -3030,6 +3084,8 @@ static int hls_probe(const AVProbeData *p)
 static const AVOption hls_options[] = {
     {"live_start_index", "segment index to start live streams at (negative 
values are from the end)",
         OFFSET(live_start_index), AV_OPT_TYPE_INT, {.i64 = -3}, INT_MIN, 
INT_MAX, FLAGS},
+    {"live_start_wait", "wait until a live playlist has enough segments for 
the live_start_index position",
+        OFFSET(live_start_wait), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS},
     {"prefer_x_start", "prefer to use #EXT-X-START if it's in playlist instead 
of live_start_index",
         OFFSET(prefer_x_start), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS},
     {"allowed_extensions", "List of file extensions that hls is allowed to 
access",
diff --git a/libavformat/version.h b/libavformat/version.h
index 7ff1483..ee91990 100644
--- a/libavformat/version.h
+++ b/libavformat/version.h
@@ -32,7 +32,7 @@
 #include "version_major.h"
 
 #define LIBAVFORMAT_VERSION_MINOR   3
-#define LIBAVFORMAT_VERSION_MICRO 100
+#define LIBAVFORMAT_VERSION_MICRO 101
 
 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
                                                LIBAVFORMAT_VERSION_MINOR, \
-- 
2.39.5 (Apple Git-154)

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to