[FFmpeg-devel] [PATCH v2] avformat/hlsenc: add hls_min_time to guarantee minimal segment duration

Ingo Oppermann ingo at datarhei.com
Thu Jun 5 10:23:48 EEST 2025


The current implementation of the hls_time might produce segments
with shorter duration in some cases which is not as expected. With the
hls_min_time option the minimal segment duration will be guaranteed by
splitting by the next keyframe after the desired duration of the
segment has been reached.

Instead of changing the current behaviour of hls_time which might
break existing workflows, the new option hls_min_time will override
hls_time and guarantee the minimal segment duration.

hls_time is supposed to define the minimal length of a segment,
however this is not respected in all cases when a stream has variable
GOP sizes.

Imagine a stream starts with a key frame every 10 seconds for
e.g. 30 seconds. After that, key frames will come every second. This
will result in segments that are first 10 seconds (as expected), then
1 second for some time (not as expected) and later 2 seconds (as
expected).

How to reproduce:
ffmpeg -t 30 -f lavfi -i testsrc2 -codec:v libx264 -g 250 part1.mp4
ffmpeg -t 30 -f lavfi -i testsrc2 -codec:v libx264 -g 25 part2.mp4
echo "file part1.mp4\nfile part2.mp4" > list.txt
ffmpeg -f concat -i list.txt -codec copy \
    -f hls -hls_time 2 -hls_list_size 0 parts.m3u8
cat parts.m3u8

v2 changes:
Adjusting wording in commit message as suggested by Steven Liu.

Signed-off-by: Ingo Oppermann <ingo at datarhei.com>
---
 doc/muxers.texi      |  7 +++++++
 libavformat/hlsenc.c | 27 ++++++++++++++++++++-------
 2 files changed, 27 insertions(+), 7 deletions(-)

diff --git a/doc/muxers.texi b/doc/muxers.texi
index 30c95c3d34..082647bd9d 100644
--- a/doc/muxers.texi
+++ b/doc/muxers.texi
@@ -1929,6 +1929,13 @@ Set the target segment length. Default value is 2.
 see @ref{time duration syntax,,the Time duration section in the ffmpeg-utils(1) manual,ffmpeg-utils}.
 Segment will be cut on the next key frame after this time has passed.
 
+ at item hls_min_time @var{duration}
+Set the minimum target segment length. Default value is 0.
+
+ at var{duration} must be a time duration specification,
+see @ref{time duration syntax,,the Time duration section in the ffmpeg-utils(1) manual,ffmpeg-utils}.
+Segment will be at least this long and will be cut at the following key frame. If set, it will override @option{hls_time}.
+
 @item hls_list_size @var{size}
 Set the maximum number of playlist entries. If set to 0 the list file
 will contain all the segments. Default value is 5.
diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c
index a93d35ab75..082229467f 100644
--- a/libavformat/hlsenc.c
+++ b/libavformat/hlsenc.c
@@ -204,6 +204,7 @@ typedef struct HLSContext {
     uint32_t start_sequence_source_type;  // enum StartSequenceSourceType
 
     int64_t time;          // Set by a private option.
+    int64_t min_time;      // Set by a private option.
     int64_t init_time;     // Set by a private option.
     int max_nb_segments;   // Set by a private option.
     int hls_delete_threshold; // Set by a private option.
@@ -2506,7 +2507,7 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
     HLSContext *hls = s->priv_data;
     AVFormatContext *oc = NULL;
     AVStream *st = s->streams[pkt->stream_index];
-    int64_t end_pts = 0;
+    int64_t end_pts = 0, current_pts;
     int is_ref_pkt = 1;
     int ret = 0, can_split = 1, i, j;
     int stream_index = 0;
@@ -2547,11 +2548,16 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
     end_pts = hls->recording_time * vs->number;
 
     if (vs->sequence - vs->nb_entries > hls->start_sequence && hls->init_time > 0) {
-        /* reset end_pts, hls->recording_time at end of the init hls list */
-        int64_t init_list_dur = hls->init_time * vs->nb_entries;
-        int64_t after_init_list_dur = (vs->sequence - hls->start_sequence - vs->nb_entries) * hls->time;
-        hls->recording_time = hls->time;
-        end_pts = init_list_dur + after_init_list_dur ;
+        if (hls->min_time > 0) {
+            hls->recording_time = hls->min_time;
+            hls->init_time = 0;
+        } else {
+            /* reset end_pts, hls->recording_time at end of the init hls list */
+            int64_t init_list_dur = hls->init_time * vs->nb_entries;
+            int64_t after_init_list_dur = (vs->sequence - hls->start_sequence - vs->nb_entries) * hls->time;
+            hls->recording_time = hls->time;
+            end_pts = init_list_dur + after_init_list_dur;
+        }
     }
 
     if (vs->start_pts == AV_NOPTS_VALUE) {
@@ -2591,8 +2597,14 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
         }
     }
 
+    current_pts = pkt->pts - vs->start_pts;
+    if (hls->min_time > 0 && hls->init_time == 0) {
+        current_pts = pkt->pts - vs->end_pts;
+        end_pts = hls->min_time;
+    }
+
     can_split = can_split && (pkt->pts - vs->end_pts > 0);
-    if (vs->packets_written && can_split && av_compare_ts(pkt->pts - vs->start_pts, st->time_base,
+    if (vs->packets_written && can_split && av_compare_ts(current_pts, st->time_base,
                                                           end_pts, AV_TIME_BASE_Q) >= 0) {
         int64_t new_start_pos;
         int byterange_mode = (hls->flags & HLS_SINGLE_FILE) || (hls->max_seg_size > 0);
@@ -3214,6 +3226,7 @@ static int hls_init(AVFormatContext *s)
 static const AVOption options[] = {
     {"start_number",  "set first number in the sequence",        OFFSET(start_sequence),AV_OPT_TYPE_INT64,  {.i64 = 0},     0, INT64_MAX, E},
     {"hls_time",      "set segment length",                      OFFSET(time),          AV_OPT_TYPE_DURATION, {.i64 = 2000000}, 0, INT64_MAX, E},
+    {"hls_min_time",  "set minimum segment length",              OFFSET(min_time),      AV_OPT_TYPE_DURATION, {.i64 = 0},       0, INT64_MAX, E},
     {"hls_init_time", "set segment length at init list",         OFFSET(init_time),     AV_OPT_TYPE_DURATION, {.i64 = 0},       0, INT64_MAX, E},
     {"hls_list_size", "set maximum number of playlist entries",  OFFSET(max_nb_segments),    AV_OPT_TYPE_INT,    {.i64 = 5},     0, INT_MAX, E},
     {"hls_delete_threshold", "set number of unreferenced segments to keep before deleting",  OFFSET(hls_delete_threshold),    AV_OPT_TYPE_INT,    {.i64 = 1},     1, INT_MAX, E},

base-commit: a4c1a5b08409cdfeb8e7f92c7fd443c8ff42e00d
-- 
2.39.5 (Apple Git-154)



More information about the ffmpeg-devel mailing list