From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from ffbox0-bg.ffmpeg.org (ffbox0-bg.ffmpeg.org [79.124.17.100]) by master.gitmailbox.com (Postfix) with ESMTPS id 988294DE40 for ; Thu, 5 Jun 2025 07:24:31 +0000 (UTC) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.ffmpeg.org (Postfix) with ESMTP id 872D168C980; Thu, 5 Jun 2025 10:24:27 +0300 (EEST) Received: from mail-lf1-f43.google.com (mail-lf1-f43.google.com [209.85.167.43]) by ffbox0-bg.ffmpeg.org (Postfix) with ESMTPS id BBBD168C50D for ; Thu, 5 Jun 2025 10:24:20 +0300 (EEST) Received: by mail-lf1-f43.google.com with SMTP id 2adb3069b0e04-551fe46934eso784264e87.1 for ; Thu, 05 Jun 2025 00:24:20 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=datarhei.com; s=google; t=1749108259; x=1749713059; darn=ffmpeg.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=1R9Usy3rRdqZTo9AMS9MwqyBJQxFJrPUBd03jEKNQow=; b=L3BfBJnfnBwVJOdaAs4CXUgQnP/DFACH4bdRA5/qQRgAOuSFOmBDgevNhJ0KoR0une PrC7g2l0W7siipifsuyVQnBrmwqTWM7NAZ9t8kLmOpy347jSPQBLX5mh6o66otqaprnl oUvPeyExBxYJ87s4yT0Q1pgI8xGjfBOYZxwfF4gaw1aMiMcXf0Cjy+MjSPKV0YdKOdIG kW+E5zVcLXm7/opbOz/rKyBTkhnpq0aA/s94l1fUNVptEes4P/5+tF0nmgXl2NlcKrv1 Vi6EskN6N+Bzo2uT2PWDj96rEYvqe0F93wIUepBZQZfqymHK4AoRINpr+kTPKxso/0id maBw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1749108259; x=1749713059; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=1R9Usy3rRdqZTo9AMS9MwqyBJQxFJrPUBd03jEKNQow=; b=AX2HUN+b7ih3UzNzwnpjLi+hwQSCL92oz5DrjE9ytwXi8r1zT2oMGafmbQRwf+giBT ndtX9FXG7svNPIVHj7yJqipshL0UQ3JGyOur6U4C0/ZjkspcrsULiijMK6Of7LV6GyK+ yll/dxtDKxcG5PMrgyUrvcmSW8uK3a5fyARoYOksRzDX+FgxG+7Uqe/tC/oCTtObIbN8 0lq8U8U0Fb8JNFPWt9fNII7qjd+GGojQRGwx01lSl/Nt2I5jvmQzZZV0JZEUQYP+W+pe DrzhFdmwoXUJ8TEbzLB4LbO9XCa0B6djAppE/jnuqMbHGydS95kh111xfsI9+cLTfMGj v+sA== X-Gm-Message-State: AOJu0Yyp8Dpuf7ayrobvGT5KPzdqvfoNSGK1CO34DxxVm8xrPsb8ruCo SYu2pbcTMu8mX5X38KzaK0oVSZ9ENSgLGDnbm9gbbhXdkAtrFRjyWxj3vyZ42ICcdfjUROuoIm9 xX80= X-Gm-Gg: ASbGncvLQlG2QdevWWhKKiCvaCJXlg6qSb8+f/ME1aVYd1zC0bhCyQnXJKY2FnvlwHR WZ/76rxQp2TMkQjJRp7H0SS6RRQlWWtfI7cWBBn3G2Yr1RgRjnFmHxGwpmDHa50ae7SHiPKRt/C p84sP/nL9T/4qhNLhv35iYxxaqBVI6v3t7XK6J0dBxNJU4D2ZJSO7ozuNa1ySgE74xTbsd/wat6 QMJibPH+m7bmQE+Jtg9RAWlTTdvc851BJ9BQXS6NuL0+N3AKoqLs/2irkuSOPBUi5nILoQkJWy/ wj9gUhyLKtk6xvuwP0bsfHWnFqGYz2yZ9QG+cMb8sEwpV95R9COFVdaYM747dE9VYLj6rJzZ4Yf 9ROE= X-Google-Smtp-Source: AGHT+IGl8CY4N3Ai1AcMB7ygXMadLZpkfsFK4K/xi1Xw5Lv4OdI2tT9I5BbiumXoamTdjWlhdBlMYg== X-Received: by 2002:a05:6512:3f08:b0:549:8ff1:e0f3 with SMTP id 2adb3069b0e04-5535d8f5142mr629725e87.5.1749108259115; Thu, 05 Jun 2025 00:24:19 -0700 (PDT) Received: from localhost.localdomain ([46.55.200.6]) by smtp.gmail.com with ESMTPSA id 2adb3069b0e04-553378a1398sm2539226e87.98.2025.06.05.00.24.18 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Thu, 05 Jun 2025 00:24:18 -0700 (PDT) From: Ingo Oppermann To: ffmpeg-devel@ffmpeg.org Date: Thu, 5 Jun 2025 09:23:48 +0200 Message-Id: <20250605072348.56374-1-ingo@datarhei.com> X-Mailer: git-send-email 2.39.5 (Apple Git-154) MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH v2] avformat/hlsenc: add hls_min_time to guarantee minimal segment duration X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: Ingo Oppermann Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Archived-At: List-Archive: List-Post: 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 --- 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. +@item hls_min_time @var{duration} +Set the minimum target segment length. Default value is 0. + +@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) _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".