diff mbox

[FFmpeg-devel,02/15] avformat/dashenc: allow setting fragment durations

Message ID 20191221180252.6091-2-jamrial@gmail.com
State New
Headers show

Commit Message

James Almer Dec. 21, 2019, 6:02 p.m. UTC
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 <jamrial@gmail.com>
---
 libavformat/dashenc.c | 84 ++++++++++++++++++++++++++++++++++---------
 1 file changed, 68 insertions(+), 16 deletions(-)
diff mbox

Patch

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 },