diff mbox

[FFmpeg-devel] hls demuxer: add option to defer parsing of variants

Message ID 20171123222010.43313-2-fernetmenta@online.de
State Superseded
Headers show

Commit Message

Rainer Hochecker Nov. 23, 2017, 10:20 p.m. UTC
---
 doc/demuxers.texi |   5 +
 libavformat/hls.c | 299 ++++++++++++++++++++++++++++++++++++------------------
 2 files changed, 204 insertions(+), 100 deletions(-)

Comments

Carl Eugen Hoyos Nov. 23, 2017, 11:41 p.m. UTC | #1
2017-11-23 23:20 GMT+01:00 Rainer Hochecker <fernetmenta@online.de>:

> +@item load_all_variants
> +If 0, only the first variant/playlist is loaded on open. All other variants
> +get disabled and can be enabled by setting discard option in program.
> +Default value is 1.

Shouldn't this be:
0 (or -1) for all streams (default), n>0 (or > -1) for the nth stream.
I guess it is at least possible that the user knows in advance which
streams the source will offer.
No?

Carl Eugen
Rainer Hochecker Nov. 24, 2017, 6:52 a.m. UTC | #2
> Shouldn't this be:
> 0 (or -1) for all streams (default), n>0 (or > -1) for the nth stream.
> I guess it is at least possible that the user knows in advance which
> streams the source will offer.
> No?
>
> Carl Eugen

It is very unlikely that this is known in advance. Also there is no reason
why you want to load more than a single variant at a time.
Note this are variants, not streams. Different variants bundle the same
streams with different resolutions.

Rainer
Michael Niedermayer Nov. 24, 2017, 10:02 p.m. UTC | #3
On Thu, Nov 23, 2017 at 11:20:10PM +0100, Rainer Hochecker wrote:
> ---
>  doc/demuxers.texi |   5 +
>  libavformat/hls.c | 299 ++++++++++++++++++++++++++++++++++++------------------
>  2 files changed, 204 insertions(+), 100 deletions(-)

breaks fate-segment-mp4-to-ts
...
Output #0, framecrc, to 'pipe:':
Output file #0 does not contain any stream


[...]
Carl Eugen Hoyos Nov. 25, 2017, 11:57 p.m. UTC | #4
2017-11-24 7:52 GMT+01:00 Rainer Hochecker <rainer.hochecker@onlinehome.de>:
>> Shouldn't this be:
>> 0 (or -1) for all streams (default), n>0 (or > -1) for the nth stream.
>> I guess it is at least possible that the user knows in advance which
>> streams the source will offer.
>> No?

> It is very unlikely that this is known in advance.

> Also there is no reason why you want to load
> more than a single variant at a time.

It is generally impossible to load the second (third)
variant?

Carl Eugen
Rainer Hochecker Nov. 26, 2017, 9:37 a.m. UTC | #5
Variants are presented as programs and can be loaded later by
setting discard flags on the program. Currently Kodi chooses the
program that best matches the desired bit rate.

Rainer
diff mbox

Patch

diff --git a/doc/demuxers.texi b/doc/demuxers.texi
index 73dc0feec1..634b122e10 100644
--- a/doc/demuxers.texi
+++ b/doc/demuxers.texi
@@ -316,6 +316,11 @@  segment index to start live streams at (negative values are from the end).
 @item max_reload
 Maximum number of times a insufficient list is attempted to be reloaded.
 Default value is 1000.
+
+@item load_all_variants
+If 0, only the first variant/playlist is loaded on open. All other variants
+get disabled and can be enabled by setting discard option in program.
+Default value is 1.
 @end table
 
 @section image2
diff --git a/libavformat/hls.c b/libavformat/hls.c
index 786934af03..c55f5ea439 100644
--- a/libavformat/hls.c
+++ b/libavformat/hls.c
@@ -112,6 +112,7 @@  struct playlist {
     int n_segments;
     struct segment **segments;
     int needed, cur_needed;
+    int parsed;
     int cur_seq_no;
     int64_t cur_seg_offset;
     int64_t last_load_time;
@@ -206,6 +207,7 @@  typedef struct HLSContext {
     int strict_std_compliance;
     char *allowed_extensions;
     int max_reload;
+    int load_all_variants;
 } HLSContext;
 
 static int read_chomp_line(AVIOContext *s, char *buf, int maxlen)
@@ -314,6 +316,9 @@  static struct playlist *new_playlist(HLSContext *c, const char *url,
     pls->is_id3_timestamped = -1;
     pls->id3_mpegts_timestamp = AV_NOPTS_VALUE;
 
+    pls->index = c->n_playlists;
+    pls->parsed = 0;
+    pls->needed = 0;
     dynarray_add(&c->playlists, &c->n_playlists, pls);
     return pls;
 }
@@ -721,6 +726,7 @@  static int parse_playlist(HLSContext *c, const char *url,
         free_segment_list(pls);
         pls->finished = 0;
         pls->type = PLS_TYPE_UNSPECIFIED;
+        pls->parsed = 1;
     }
     while (!avio_feof(in)) {
         read_chomp_line(in, line, sizeof(line));
@@ -1377,23 +1383,41 @@  reload:
 static void add_renditions_to_variant(HLSContext *c, struct variant *var,
                                       enum AVMediaType type, const char *group_id)
 {
-    int i;
+    int i, j;
+    int found;
 
     for (i = 0; i < c->n_renditions; i++) {
         struct rendition *rend = c->renditions[i];
 
         if (rend->type == type && !strcmp(rend->group_id, group_id)) {
 
-            if (rend->playlist)
+            if (rend->playlist) {
                 /* rendition is an external playlist
                  * => add the playlist to the variant */
-                dynarray_add(&var->playlists, &var->n_playlists, rend->playlist);
-            else
+                found = 0;
+                for (j = 0; j < var->n_playlists; j++) {
+                    if (var->playlists[j] == rend->playlist) {
+                        found = 1;
+                        break;
+                    }
+                }
+                if (!found)
+                    dynarray_add(&var->playlists, &var->n_playlists, rend->playlist);
+            } else {
                 /* rendition is part of the variant main Media Playlist
                  * => add the rendition to the main Media Playlist */
-                dynarray_add(&var->playlists[0]->renditions,
-                             &var->playlists[0]->n_renditions,
-                             rend);
+                found = 0;
+                for (j = 0; j < var->playlists[0]->n_renditions; j++) {
+                    if (var->playlists[0]->renditions[j] == rend) {
+                        found = 1;
+                        break;
+                    }
+                }
+                if (!found)
+                    dynarray_add(&var->playlists[0]->renditions,
+                                 &var->playlists[0]->n_renditions,
+                                 rend);
+            }
         }
     }
 }
@@ -1631,6 +1655,122 @@  static int hls_close(AVFormatContext *s)
     return 0;
 }
 
+static int init_playlist(HLSContext *c, struct playlist *pls)
+{
+    AVInputFormat *in_fmt = NULL;
+    int highest_cur_seq_no = 0;
+    int ret;
+    int i;
+
+    if (!(pls->ctx = avformat_alloc_context())) {
+        return AVERROR(ENOMEM);
+    }
+
+    if (pls->n_segments == 0)
+        return 0;
+
+    pls->needed = 1;
+    pls->parent = c->ctx;
+
+    /*
+     * If this is a live stream and this playlist looks like it is one segment
+     * behind, try to sync it up so that every substream starts at the same
+     * time position (so e.g. avformat_find_stream_info() will see packets from
+     * all active streams within the first few seconds). This is not very generic,
+     * though, as the sequence numbers are technically independent.
+     */
+    highest_cur_seq_no = 0;
+    for (i = 0; i < c->n_playlists; i++) {
+        struct playlist *pls = c->playlists[i];
+        if (!pls->parsed)
+            continue;
+        if (pls->cur_seq_no > highest_cur_seq_no)
+            highest_cur_seq_no = pls->cur_seq_no;
+    }
+    if (!pls->finished && pls->cur_seq_no == highest_cur_seq_no - 1 &&
+        highest_cur_seq_no < pls->start_seq_no + pls->n_segments) {
+        pls->cur_seq_no = highest_cur_seq_no;
+    }
+
+    pls->read_buffer = av_malloc(INITIAL_BUFFER_SIZE);
+    if (!pls->read_buffer){
+        ret = AVERROR(ENOMEM);
+        avformat_free_context(pls->ctx);
+        pls->ctx = NULL;
+        return ret;
+    }
+    ffio_init_context(&pls->pb, pls->read_buffer, INITIAL_BUFFER_SIZE, 0, pls,
+                      read_data, NULL, NULL);
+    pls->pb.seekable = 0;
+    ret = av_probe_input_buffer(&pls->pb, &in_fmt, pls->segments[0]->url,
+                                NULL, 0, 0);
+    if (ret < 0) {
+        /* Free the ctx - it isn't initialized properly at this point,
+         * so avformat_close_input shouldn't be called. If
+         * avformat_open_input fails below, it frees and zeros the
+         * context, so it doesn't need any special treatment like this. */
+        av_log(c->ctx, AV_LOG_ERROR, "Error when loading first segment '%s'\n", pls->segments[0]->url);
+        avformat_free_context(pls->ctx);
+        pls->ctx = NULL;
+        return ret;
+    }
+    pls->ctx->pb       = &pls->pb;
+    pls->ctx->io_open  = nested_io_open;
+    pls->ctx->flags   |= c->ctx->flags & ~AVFMT_FLAG_CUSTOM_IO;
+
+    if ((ret = ff_copy_whiteblacklists(pls->ctx, c->ctx)) < 0)
+        return ret;
+
+    ret = avformat_open_input(&pls->ctx, pls->segments[0]->url, in_fmt, NULL);
+    if (ret < 0) {
+        av_log(c->ctx, AV_LOG_ERROR, "Error opening playlist %s", pls->segments[0]->url);
+        avformat_free_context(pls->ctx);
+        pls->ctx = NULL;
+        return ret;
+    }
+
+    if (pls->id3_deferred_extra && pls->ctx->nb_streams == 1) {
+        ff_id3v2_parse_apic(pls->ctx, &pls->id3_deferred_extra);
+        avformat_queue_attached_pictures(pls->ctx);
+        ff_id3v2_free_extra_meta(&pls->id3_deferred_extra);
+        pls->id3_deferred_extra = NULL;
+    }
+
+    if (pls->is_id3_timestamped == -1)
+        av_log(c->ctx, AV_LOG_WARNING, "No expected HTTP requests have been made\n");
+
+    /*
+     * For ID3 timestamped raw audio streams we need to detect the packet
+     * durations to calculate timestamps in fill_timing_for_id3_timestamped_stream(),
+     * but for other streams we can rely on our user calling avformat_find_stream_info()
+     * on us if they want to.
+     */
+    if (pls->is_id3_timestamped) {
+        ret = avformat_find_stream_info(pls->ctx, NULL);
+        if (ret < 0) {
+            avformat_free_context(pls->ctx);
+            pls->ctx = NULL;
+            return ret;
+        }
+    }
+
+    pls->has_noheader_flag = !!(pls->ctx->ctx_flags & AVFMTCTX_NOHEADER);
+
+    /* Create new AVStreams for each stream in this playlist */
+    ret = update_streams_from_subdemuxer(c->ctx, pls);
+    if (ret < 0) {
+        avformat_free_context(pls->ctx);
+        pls->ctx = NULL;
+        return ret;
+    }
+
+    add_metadata_from_renditions(c->ctx, pls, AVMEDIA_TYPE_AUDIO);
+    add_metadata_from_renditions(c->ctx, pls, AVMEDIA_TYPE_VIDEO);
+    add_metadata_from_renditions(c->ctx, pls, AVMEDIA_TYPE_SUBTITLE);
+
+    return 0;
+}
+
 static int hls_read_header(AVFormatContext *s)
 {
     void *u = (s->flags & AVFMT_FLAG_CUSTOM_IO) ? NULL : s->pb;
@@ -1675,8 +1815,15 @@  static int hls_read_header(AVFormatContext *s)
         goto fail;
     }
     /* If the playlist only contained playlists (Master Playlist),
-     * parse each individual playlist. */
-    if (c->n_playlists > 1 || c->playlists[0]->n_segments == 0) {
+     * parse all individual playlists.
+     * If option load_all_variants is false, load only first variant */
+    if (!c->load_all_variants && c->n_variants > 1) {
+        for (i = 0; i < c->variants[0]->n_playlists; i++) {
+            struct playlist *pls = c->variants[0]->playlists[i];
+            if ((ret = parse_playlist(c, pls->url, pls, NULL)) < 0)
+                goto fail;
+        }
+    } else if (c->n_playlists > 1 || c->playlists[0]->n_segments == 0) {
         for (i = 0; i < c->n_playlists; i++) {
             struct playlist *pls = c->playlists[i];
             if ((ret = parse_playlist(c, pls->url, pls, NULL)) < 0)
@@ -1720,13 +1867,17 @@  static int hls_read_header(AVFormatContext *s)
         if (!program)
             goto fail;
         av_dict_set_int(&program->metadata, "variant_bitrate", v->bandwidth, 0);
+
+        /* start with the first variant and disable all others */
+        if (i > 0 && !c->load_all_variants)
+            program->discard = AVDISCARD_ALL;
     }
 
     /* Select the starting segments */
     for (i = 0; i < c->n_playlists; i++) {
         struct playlist *pls = c->playlists[i];
 
-        if (pls->n_segments == 0)
+        if (pls->n_segments == 0 && !pls->parsed)
             continue;
 
         pls->cur_seq_no = select_cur_seq_no(c, pls);
@@ -1736,97 +1887,9 @@  static int hls_read_header(AVFormatContext *s)
     /* Open the demuxer for each playlist */
     for (i = 0; i < c->n_playlists; i++) {
         struct playlist *pls = c->playlists[i];
-        AVInputFormat *in_fmt = NULL;
-
-        if (!(pls->ctx = avformat_alloc_context())) {
-            ret = AVERROR(ENOMEM);
-            goto fail;
-        }
-
-        if (pls->n_segments == 0)
-            continue;
-
-        pls->index  = i;
-        pls->needed = 1;
-        pls->parent = s;
-
-        /*
-         * If this is a live stream and this playlist looks like it is one segment
-         * behind, try to sync it up so that every substream starts at the same
-         * time position (so e.g. avformat_find_stream_info() will see packets from
-         * all active streams within the first few seconds). This is not very generic,
-         * though, as the sequence numbers are technically independent.
-         */
-        if (!pls->finished && pls->cur_seq_no == highest_cur_seq_no - 1 &&
-            highest_cur_seq_no < pls->start_seq_no + pls->n_segments) {
-            pls->cur_seq_no = highest_cur_seq_no;
-        }
-
-        pls->read_buffer = av_malloc(INITIAL_BUFFER_SIZE);
-        if (!pls->read_buffer){
-            ret = AVERROR(ENOMEM);
-            avformat_free_context(pls->ctx);
-            pls->ctx = NULL;
-            goto fail;
-        }
-        ffio_init_context(&pls->pb, pls->read_buffer, INITIAL_BUFFER_SIZE, 0, pls,
-                          read_data, NULL, NULL);
-        pls->pb.seekable = 0;
-        ret = av_probe_input_buffer(&pls->pb, &in_fmt, pls->segments[0]->url,
-                                    NULL, 0, 0);
-        if (ret < 0) {
-            /* Free the ctx - it isn't initialized properly at this point,
-             * so avformat_close_input shouldn't be called. If
-             * avformat_open_input fails below, it frees and zeros the
-             * context, so it doesn't need any special treatment like this. */
-            av_log(s, AV_LOG_ERROR, "Error when loading first segment '%s'\n", pls->segments[0]->url);
-            avformat_free_context(pls->ctx);
-            pls->ctx = NULL;
-            goto fail;
-        }
-        pls->ctx->pb       = &pls->pb;
-        pls->ctx->io_open  = nested_io_open;
-        pls->ctx->flags   |= s->flags & ~AVFMT_FLAG_CUSTOM_IO;
-
-        if ((ret = ff_copy_whiteblacklists(pls->ctx, s)) < 0)
-            goto fail;
-
-        ret = avformat_open_input(&pls->ctx, pls->segments[0]->url, in_fmt, NULL);
-        if (ret < 0)
-            goto fail;
-
-        if (pls->id3_deferred_extra && pls->ctx->nb_streams == 1) {
-            ff_id3v2_parse_apic(pls->ctx, &pls->id3_deferred_extra);
-            avformat_queue_attached_pictures(pls->ctx);
-            ff_id3v2_free_extra_meta(&pls->id3_deferred_extra);
-            pls->id3_deferred_extra = NULL;
-        }
-
-        if (pls->is_id3_timestamped == -1)
-            av_log(s, AV_LOG_WARNING, "No expected HTTP requests have been made\n");
-
-        /*
-         * For ID3 timestamped raw audio streams we need to detect the packet
-         * durations to calculate timestamps in fill_timing_for_id3_timestamped_stream(),
-         * but for other streams we can rely on our user calling avformat_find_stream_info()
-         * on us if they want to.
-         */
-        if (pls->is_id3_timestamped) {
-            ret = avformat_find_stream_info(pls->ctx, NULL);
-            if (ret < 0)
-                goto fail;
-        }
 
-        pls->has_noheader_flag = !!(pls->ctx->ctx_flags & AVFMTCTX_NOHEADER);
-
-        /* Create new AVStreams for each stream in this playlist */
-        ret = update_streams_from_subdemuxer(s, pls);
-        if (ret < 0)
-            goto fail;
-
-        add_metadata_from_renditions(s, pls, AVMEDIA_TYPE_AUDIO);
-        add_metadata_from_renditions(s, pls, AVMEDIA_TYPE_VIDEO);
-        add_metadata_from_renditions(s, pls, AVMEDIA_TYPE_SUBTITLE);
+        if (pls->parsed)
+            init_playlist(c, pls);
     }
 
     update_noheader_flag(s);
@@ -1877,6 +1940,36 @@  static int recheck_discard_flags(AVFormatContext *s, int first)
     return changed;
 }
 
+static void recheck_discard_programs(AVFormatContext *s)
+{
+    HLSContext *c = s->priv_data;
+    int i, j;
+
+    for (i = 0; i < c->n_variants; i++) {
+        struct variant *var = c->variants[i];
+        AVProgram *program = s->programs[i];
+
+        if (program->discard >= AVDISCARD_ALL)
+            continue;
+
+        for (j = 0; j < c->variants[i]->n_playlists; j++) {
+            struct playlist *pls = c->variants[i]->playlists[j];
+
+            if (!pls->parsed) {
+                if (parse_playlist(c, pls->url, pls, NULL) < 0)
+                    continue;
+                if (var->audio_group[0])
+                    add_renditions_to_variant(c, var, AVMEDIA_TYPE_AUDIO, var->audio_group);
+                if (var->video_group[0])
+                    add_renditions_to_variant(c, var, AVMEDIA_TYPE_VIDEO, var->video_group);
+                if (var->subtitles_group[0])
+                    add_renditions_to_variant(c, var, AVMEDIA_TYPE_SUBTITLE, var->subtitles_group);
+                init_playlist(c, pls);
+            }
+        }
+    }
+}
+
 static void fill_timing_for_id3_timestamped_stream(struct playlist *pls)
 {
     if (pls->id3_offset >= 0) {
@@ -1924,6 +2017,8 @@  static int hls_read_packet(AVFormatContext *s, AVPacket *pkt)
     HLSContext *c = s->priv_data;
     int ret, i, minplaylist = -1;
 
+    recheck_discard_programs(s);
+
     recheck_discard_flags(s, c->first_packet);
     c->first_packet = 0;
 
@@ -2101,6 +2196,8 @@  static int hls_read_seek(AVFormatContext *s, int stream_index,
     for (i = 0; i < c->n_playlists; i++) {
         /* Reset reading */
         struct playlist *pls = c->playlists[i];
+        if (!pls->parsed)
+            continue;
         if (pls->input)
             ff_format_io_close(pls->parent, &pls->input);
         av_packet_unref(&pls->pkt);
@@ -2157,6 +2254,8 @@  static const AVOption hls_options[] = {
         INT_MIN, INT_MAX, FLAGS},
     {"max_reload", "Maximum number of times a insufficient list is attempted to be reloaded",
         OFFSET(max_reload), AV_OPT_TYPE_INT, {.i64 = 1000}, 0, INT_MAX, FLAGS},
+    {"load_all_variants", "if > 0 all playlists of all variants are opened at startup",
+        OFFSET(load_all_variants), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, FLAGS},
     {NULL}
 };