From patchwork Sat Dec 21 18:02:39 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: James Almer X-Patchwork-Id: 16905 Return-Path: X-Original-To: patchwork@ffaux-bg.ffmpeg.org Delivered-To: patchwork@ffaux-bg.ffmpeg.org Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org [79.124.17.100]) by ffaux.localdomain (Postfix) with ESMTP id 8CF5944958D for ; Sat, 21 Dec 2019 20:03:39 +0200 (EET) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 79CA468A938; Sat, 21 Dec 2019 20:03:39 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-pj1-f65.google.com (mail-pj1-f65.google.com [209.85.216.65]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id F176A68A6C7 for ; Sat, 21 Dec 2019 20:03:31 +0200 (EET) Received: by mail-pj1-f65.google.com with SMTP id kx11so1465282pjb.4 for ; Sat, 21 Dec 2019 10:03:31 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:subject:date:message-id:in-reply-to:references:mime-version :content-transfer-encoding; bh=tl0lbliMGkbpV6NRi/7qBczx09pQ2Lfg0UzjupChXQc=; b=E2IZ5YG+zIR3SH/Gi4y3WMG4wcvrnQGmgWXOtWqin+C8GpU9ZeZvYvoiFLmU1poFgH xPVj7yJM2HNBPdOSJtziYmM9vDdQMkY5YrXN6HpzJex6gkPrnEXfvvwYjBiEFkAFR170 N7kIOFvqF0k9rpVwfLbrHANAP9nVrY72VIcIkBw7Pn3itB9lftdhfkO2gvYMMTT4QhoW yhRnCBBCAc/kdBmKsqi49o0m9L0S0H+Ksu/Ph7opEcPdGYaFdK2dGpX0Dm4SP80ztxFx IThHEVE2hd7iGT5M2P1uVwCo6m9nNwGtOmBQZMnCc2Ajn551jTHXJl2tppY/Ok4FE8MV M0wA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=tl0lbliMGkbpV6NRi/7qBczx09pQ2Lfg0UzjupChXQc=; b=pFqvQg++S7zPQOzwwsxchaeJFhPYRDdG2BXhiELvvefUkiBXY614LVC6+O6diSqlXH pjMCJg2bDa3Ge4jbqsS9sClKAo3KOBYg8FpbD/vVXx7CFhygtgmXHPajd6b45WNaNO4M WW/k+ZkfRiDewBbjkpWyjGSws3MAJ45rozdDTWbQgCQnnOivF+omH0U9OiwzFWeWej6Y zRPDF4Y7AD2r5FwLb2PAkVdAkCYgaIYLZlSfit9jTz9etLLpGqQY6YeGDGgVth4Z6fuf iZrEsGMGmoke8aUjArqNWIng/pMnkOYpMoGaj9j1fa/OB7lel2rdpHuIE9+hn/TvOI9X RE6g== X-Gm-Message-State: APjAAAUXWs2rD4oIRWS/QG8Q81CfeErCEo5czL6NKKpwrJC/gYZ15XOR Ne23USA8/xOdhujrFVG8X6UGzNgt X-Google-Smtp-Source: APXvYqwL5vK92HLuBWzQxplbB3xajbx17eRAy9fB0bV3WHD33CNeuo1YFdzM44Krxw+m3FhsQqzl0w== X-Received: by 2002:a17:90a:21d1:: with SMTP id q75mr23222158pjc.0.1576951409543; Sat, 21 Dec 2019 10:03:29 -0800 (PST) Received: from localhost.localdomain ([191.83.216.116]) by smtp.gmail.com with ESMTPSA id s196sm18223564pfs.136.2019.12.21.10.03.28 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 21 Dec 2019 10:03:29 -0800 (PST) From: James Almer To: ffmpeg-devel@ffmpeg.org Date: Sat, 21 Dec 2019 15:02:39 -0300 Message-Id: <20191221180252.6091-2-jamrial@gmail.com> X-Mailer: git-send-email 2.24.1 In-Reply-To: <20191221180252.6091-1-jamrial@gmail.com> References: <20191221180252.6091-1-jamrial@gmail.com> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH 02/15] avformat/dashenc: allow setting fragment durations X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 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 Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Implemented as as a frag_duration muxer option and key=value entry in the adaptation_sets muxer option. It has the same syntax as the seg_duration option. A new frag_type option is also introduced to select the kind of fragmentation. Signed-off-by: James Almer --- libavformat/dashenc.c | 84 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 68 insertions(+), 16 deletions(-) diff --git a/libavformat/dashenc.c b/libavformat/dashenc.c index 419043ee0b..05f2700d16 100644 --- a/libavformat/dashenc.c +++ b/libavformat/dashenc.c @@ -58,6 +58,13 @@ typedef enum { SEGMENT_TYPE_NB } SegmentType; +enum { + FRAG_TYPE_NONE = 0, + FRAG_TYPE_EVERY_FRAME, + FRAG_TYPE_DURATION, + FRAG_TYPE_NB +}; + typedef struct Segment { char file[1024]; int64_t start_pos; @@ -72,6 +79,7 @@ typedef struct AdaptationSet { char id[10]; char *descriptor; int64_t seg_duration; + int64_t frag_duration; enum AVMediaType media_type; AVDictionary *metadata; AVRational min_frame_rate, max_frame_rate; @@ -88,6 +96,7 @@ typedef struct OutputStream { int init_range_length; int nb_segments, segments_size, segment_index; int64_t seg_duration; + int64_t frag_duration; Segment **segments; int64_t first_pts, start_pts, max_pts; int64_t last_dts, last_pts; @@ -107,6 +116,7 @@ typedef struct OutputStream { double availability_time_offset; int total_pkt_size; int muxer_overhead; + int frag_type; } OutputStream; typedef struct DASHContext { @@ -120,6 +130,7 @@ typedef struct DASHContext { int min_seg_duration; #endif int64_t seg_duration; + int64_t frag_duration; int remove_at_exit; int use_template; int use_timeline; @@ -153,6 +164,7 @@ typedef struct DASHContext { int master_publish_rate; int nr_of_streams_to_flush; int nr_of_streams_flushed; + int frag_type; } DASHContext; static struct codec_string { @@ -842,7 +854,7 @@ static int parse_adaptation_sets(AVFormatContext *s) { DASHContext *c = s->priv_data; const char *p = c->adaptation_sets; - enum { new_set, parse_id, parsing_streams, parse_descriptor, parse_seg_duration } state; + enum { new_set, parse_default, parsing_streams, parse_seg_duration, parse_frag_duration } state; AdaptationSet *as; int i, n, ret; @@ -860,11 +872,11 @@ static int parse_adaptation_sets(AVFormatContext *s) // syntax id=0,streams=0,1,2 id=1,streams=3,4 and so on // option id=0,descriptor=descriptor_str,streams=0,1,2 and so on - // option id=0,seg_duration=2.5,streams=0,1,2 and so on + // option id=0,seg_duration=2.5,frag_duration=0.5,streams=0,1,2 and so on // descriptor is useful to the scheme defined by ISO/IEC 23009-1:2014/Amd.2:2015 // descriptor_str should be a self-closing xml tag. - // seg_duration has the same syntax as the global seg_duration option, and has - // precedence over it if set. + // seg_duration and frag_duration have the same syntax as the global options of + // the same name, and the former have precedence over them if set. state = new_set; while (*p) { if (*p == ' ') { @@ -881,8 +893,12 @@ static int parse_adaptation_sets(AVFormatContext *s) p += n; if (*p) p++; - state = parse_id; + state = parse_default; } else if (state != new_set && av_strstart(p, "seg_duration=", &p)) { + state = parse_seg_duration; + } else if (state != new_set && av_strstart(p, "frag_duration=", &p)) { + state = parse_frag_duration; + } else if (state == parse_seg_duration || state == parse_frag_duration) { char str[32]; int64_t usecs = 0; @@ -898,8 +914,11 @@ static int parse_adaptation_sets(AVFormatContext *s) return ret; } - as->seg_duration = usecs; - state = parse_seg_duration; + if (state == parse_seg_duration) + as->seg_duration = usecs; + else + as->frag_duration = usecs; + state = parse_default; } else if (state != new_set && av_strstart(p, "descriptor=", &p)) { n = strcspn(p, ">") + 1; //followed by one comma, so plus 1 if (n < strlen(p)) { @@ -911,8 +930,8 @@ static int parse_adaptation_sets(AVFormatContext *s) p += n; if (*p) p++; - state = parse_descriptor; - } else if ((state != new_set) && av_strstart(p, "streams=", &p)) { //descriptor and duration are optional + state = parse_default; + } else if ((state != new_set) && av_strstart(p, "streams=", &p)) { //descriptor and durations are optional state = parsing_streams; } else if (state == parsing_streams) { AdaptationSet *as = &c->as[c->nb_as - 1]; @@ -1203,6 +1222,10 @@ static int dash_init(AVFormatContext *s) av_log(s, AV_LOG_WARNING, "Global SIDX option will be ignored as streaming is enabled\n"); c->global_sidx = 0; } + if (c->frag_type == FRAG_TYPE_NONE && c->streaming) { + av_log(s, AV_LOG_VERBOSE, "Changing frag_type from none to every_frame as streaming is enabled\n"); + c->frag_type = FRAG_TYPE_EVERY_FRAME; + } av_strlcpy(c->dirname, s->url, sizeof(c->dirname)); ptr = strrchr(c->dirname, '/'); @@ -1332,19 +1355,31 @@ static int dash_init(AVFormatContext *s) if (ret < 0) return ret; } + os->frag_duration = as->frag_duration ? as->frag_duration : c->frag_duration; + os->frag_type = c->frag_type; + + if (os->frag_type == FRAG_TYPE_DURATION && !os->frag_duration) { + av_log(s, AV_LOG_WARNING, "frag_type set to duration for stream %d but no frag_duration set\n", i); + os->frag_type = c->streaming ? FRAG_TYPE_EVERY_FRAME : FRAG_TYPE_NONE; + } if (os->segment_type == SEGMENT_TYPE_MP4) { if (c->streaming) - // frag_every_frame : Allows lower latency streaming // skip_sidx : Reduce bitrate overhead // skip_trailer : Avoids growing memory usage with time - av_dict_set(&opts, "movflags", "frag_every_frame+dash+delay_moov+skip_sidx+skip_trailer", 0); + av_dict_set(&opts, "movflags", "dash+delay_moov+skip_sidx+skip_trailer", 0); else { if (c->global_sidx) - av_dict_set(&opts, "movflags", "frag_custom+dash+delay_moov+global_sidx+skip_trailer", 0); + av_dict_set(&opts, "movflags", "dash+delay_moov+global_sidx+skip_trailer", 0); else - av_dict_set(&opts, "movflags", "frag_custom+dash+delay_moov+skip_trailer", 0); + av_dict_set(&opts, "movflags", "dash+delay_moov+skip_trailer", 0); } + if (os->frag_type == FRAG_TYPE_EVERY_FRAME) + av_dict_set(&opts, "movflags", "+frag_every_frame", AV_DICT_APPEND); + else + av_dict_set(&opts, "movflags", "+frag_custom", AV_DICT_APPEND); + if (os->frag_type == FRAG_TYPE_DURATION) + av_dict_set_int(&opts, "frag_duration", os->frag_duration, 0); } else { av_dict_set_int(&opts, "cluster_time_limit", c->seg_duration / 1000, 0); av_dict_set_int(&opts, "cluster_size_limit", 5 * 1024 * 1024, 0); // set a large cluster size limit @@ -1764,9 +1799,21 @@ static int dash_write_packet(AVFormatContext *s, AVPacket *pkt) sizeof(c->availability_start_time)); } - if (!os->availability_time_offset && pkt->duration) { - int64_t frame_duration = av_rescale_q(pkt->duration, st->time_base, - AV_TIME_BASE_Q); + if (!os->availability_time_offset && + (os->frag_type == FRAG_TYPE_NONE || + os->frag_type == FRAG_TYPE_DURATION || + (os->frag_type == FRAG_TYPE_EVERY_FRAME && pkt->duration))) { + int64_t frame_duration = 0; + + switch (os->frag_type) { + case FRAG_TYPE_DURATION: + frame_duration = os->frag_duration; + break; + case FRAG_TYPE_EVERY_FRAME: + frame_duration = av_rescale_q(pkt->duration, st->time_base, AV_TIME_BASE_Q); + break; + } + os->availability_time_offset = ((double) os->seg_duration - frame_duration) / AV_TIME_BASE; } @@ -1945,6 +1992,11 @@ static const AVOption options[] = { { "min_seg_duration", "minimum segment duration (in microseconds) (will be deprecated)", OFFSET(min_seg_duration), AV_OPT_TYPE_INT, { .i64 = 5000000 }, 0, INT_MAX, E }, #endif { "seg_duration", "segment duration (in seconds, fractional value can be set)", OFFSET(seg_duration), AV_OPT_TYPE_DURATION, { .i64 = 5000000 }, 0, INT_MAX, E }, + { "frag_duration", "fragment duration (in seconds, fractional value can be set)", OFFSET(frag_duration), AV_OPT_TYPE_DURATION, { .i64 = 0 }, 0, INT_MAX, E }, + { "frag_type", "set type of interval for fragments", OFFSET(frag_type), AV_OPT_TYPE_INT, {.i64 = FRAG_TYPE_NONE }, 0, FRAG_TYPE_NB - 1, E, "frag_type"}, + { "none", "one fragment per segment", 0, AV_OPT_TYPE_CONST, {.i64 = FRAG_TYPE_NONE }, 0, UINT_MAX, E, "frag_type"}, + { "every_frame", "fragment at every frame", 0, AV_OPT_TYPE_CONST, {.i64 = FRAG_TYPE_EVERY_FRAME }, 0, UINT_MAX, E, "frag_type"}, + { "duration", "fragment at specific time intervals", 0, AV_OPT_TYPE_CONST, {.i64 = FRAG_TYPE_DURATION }, 0, UINT_MAX, E, "frag_type"}, { "remove_at_exit", "remove all segments when finished", OFFSET(remove_at_exit), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E }, { "use_template", "Use SegmentTemplate instead of SegmentList", OFFSET(use_template), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, E }, { "use_timeline", "Use SegmentTimeline in SegmentTemplate", OFFSET(use_timeline), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, E },