[FFmpeg-devel,v4,2/2] lavf/hls: add option to defer parsing of variants

Submitted by Rainer Hochecker on Dec. 3, 2017, 2:54 p.m.

Details

Message ID 20171203145436.42095-3-fernetmenta@online.de
State New
Headers show

Commit Message

Rainer Hochecker Dec. 3, 2017, 2:54 p.m.
---
 doc/demuxers.texi |   6 ++++
 libavformat/hls.c | 106 ++++++++++++++++++++++++++++++++++++++++++++++--------
 2 files changed, 98 insertions(+), 14 deletions(-)

Comments

Anssi Hannula Dec. 9, 2017, 8:40 p.m.
Rainer Hochecker kirjoitti 2017-12-03 16:54:
> ---
>  doc/demuxers.texi |   6 ++++
>  libavformat/hls.c | 106 
> ++++++++++++++++++++++++++++++++++++++++++++++--------
>  2 files changed, 98 insertions(+), 14 deletions(-)
> 
> diff --git a/doc/demuxers.texi b/doc/demuxers.texi
> index 73dc0feec1..33643f966a 100644
> --- a/doc/demuxers.texi
> +++ b/doc/demuxers.texi
> @@ -316,6 +316,12 @@ 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 3c2c720abe..500e3c15de 100644
> --- a/libavformat/hls.c
> +++ b/libavformat/hls.c
> @@ -112,6 +112,7 @@ struct playlist {
>      int n_segments;
>      struct segment **segments;
>      int 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 playlist_needed(struct playlist *pls)
> +static int playlist_needed(AVFormatContext *s, struct playlist *pls,
> int check_parsed)
>  {
> -    AVFormatContext *s = pls->parent;
> +    HLSContext *c = s->priv_data;
>      int i, j;
>      int stream_needed = 0;
>      int first_st;
> 
>      /* If there is no context or streams yet, the playlist is needed 
> */
> -    if (!pls->ctx || !pls->n_main_streams)
> +    if (check_parsed && (!pls->ctx || !pls->n_main_streams))
>          return 1;
> 
> +    /* If the playlist belongs to a non discarded variant and is not 
> parsed,
> +     * we need to parse and activate it later */
> +    for (i = 0; i < s->nb_programs; i++) {
> +        AVProgram *program = s->programs[i];
> +        struct variant *var = c->variants[i];
> +        if (program->discard < AVDISCARD_ALL) {
> +            for (j = 0; j < var->n_playlists; j++) {
> +                if (var->playlists[j] == pls && 
> !var->playlists[j]->parsed)
> +                return  1;

I think this is mostly the same check that is in the same function a 
couple of
lines down, except with the added extra is_parsed check and that your 
version
works even without streams.

So maybe we could avoid duplication by making the flow in the function 
like this:
1. If any streams in the playlist are needed => stream_needed = 1
2. If streams_needed == 0 and playlist has streams => return 0
3. If playlist in undiscarded program (your version) => return 1
4. All program were discarded => return 0

So we avoid having to check for ->parsed and separate "check_parsed" 
handling, I think.
Unparsed discarded playlists would return 0 in step 4 and unparsed 
non-discarded playlists
would return 1 in step 3.

> +            }
> +        }
> +    }
> +
>      /* check if any of the streams in the playlist are needed */
>      for (i = 0; i < pls->n_main_streams; i++) {
>          if (pls->main_streams[i]->discard < AVDISCARD_ALL) {
> @@ -1324,7 +1344,7 @@ restart:
> 
>          /* Check that the playlist is still needed before opening a 
> new
>           * segment. */
> -        v->needed = playlist_needed(v);
> +        v->needed = playlist_needed(v->parent, v, 1);
> 
>          if (!v->needed) {
>              av_log(v->parent, AV_LOG_INFO, "No longer receiving 
> playlist %d\n",
> @@ -1418,23 +1438,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);
> +            }
>          }
>      }
[...]
> +static void activate_playlist(AVFormatContext *s, struct playlist 
> *pls) {
> +
> +	HLSContext *c = s->priv_data;
> +
> +	if (pls->index < c->n_variants) {
> +
> +		struct variant *var = c->variants[pls->index];
> +
> +		if (parse_playlist(c, pls->url, pls, NULL) < 0)
> +			return;
> +		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);

Hmm, didn't notice this before, but I don't think these 
add_renditions_to_variant() calls
use anything from the parse_playlist(media_pls) call so there should be 
no need to re-call
them as the calls in hls_read_header() should have already worked 
perfectly.
The video_group, audio_group, subtitles_group strings come from the 
master playlist which is
always parsed in hls_read_header().

Then we could also drop all the changes you made in 
add_renditions_to_variant()
as well as it would not be called more than once per variant.

Or maybe I am missing something?

Trying to keep variants, renditions, playlists, streams and programs 
straight in
one's head does tend to cause headaches, after all...


> +		init_playlist(c, pls);
> +	}
[...]

Patch hide | download patch | download mbox

diff --git a/doc/demuxers.texi b/doc/demuxers.texi
index 73dc0feec1..33643f966a 100644
--- a/doc/demuxers.texi
+++ b/doc/demuxers.texi
@@ -316,6 +316,12 @@  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 3c2c720abe..500e3c15de 100644
--- a/libavformat/hls.c
+++ b/libavformat/hls.c
@@ -112,6 +112,7 @@  struct playlist {
     int n_segments;
     struct segment **segments;
     int 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)
@@ -315,6 +317,7 @@  static struct playlist *new_playlist(HLSContext *c, const char *url,
     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;
@@ -867,6 +870,10 @@  fail:
     av_free(new_url);
     if (close_in)
         ff_format_io_close(c->ctx, &in);
+
+    if (pls)
+        pls->parsed = 1;
+
     return ret;
 }
 
@@ -1260,17 +1267,30 @@  static int64_t default_reload_interval(struct playlist *pls)
                           pls->target_duration;
 }
 
-static int playlist_needed(struct playlist *pls)
+static int playlist_needed(AVFormatContext *s, struct playlist *pls, int check_parsed)
 {
-    AVFormatContext *s = pls->parent;
+    HLSContext *c = s->priv_data;
     int i, j;
     int stream_needed = 0;
     int first_st;
 
     /* If there is no context or streams yet, the playlist is needed */
-    if (!pls->ctx || !pls->n_main_streams)
+    if (check_parsed && (!pls->ctx || !pls->n_main_streams))
         return 1;
 
+    /* If the playlist belongs to a non discarded variant and is not parsed,
+     * we need to parse and activate it later */
+    for (i = 0; i < s->nb_programs; i++) {
+        AVProgram *program = s->programs[i];
+        struct variant *var = c->variants[i];
+        if (program->discard < AVDISCARD_ALL) {
+            for (j = 0; j < var->n_playlists; j++) {
+                if (var->playlists[j] == pls && !var->playlists[j]->parsed)
+                return  1;
+            }
+        }
+    }
+
     /* check if any of the streams in the playlist are needed */
     for (i = 0; i < pls->n_main_streams; i++) {
         if (pls->main_streams[i]->discard < AVDISCARD_ALL) {
@@ -1324,7 +1344,7 @@  restart:
 
         /* Check that the playlist is still needed before opening a new
          * segment. */
-        v->needed = playlist_needed(v);
+        v->needed = playlist_needed(v->parent, v, 1);
 
         if (!v->needed) {
             av_log(v->parent, AV_LOG_INFO, "No longer receiving playlist %d\n",
@@ -1418,23 +1438,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);
+            }
         }
     }
 }
@@ -1831,8 +1869,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)
@@ -1876,6 +1921,10 @@  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 */
@@ -1893,6 +1942,9 @@  static int hls_read_header(AVFormatContext *s)
     for (i = 0; i < c->n_playlists; i++) {
         struct playlist *pls = c->playlists[i];
 
+        if (!pls->parsed)
+            continue;
+
         if ((ret = init_playlist(c, pls)) < 0)
 		    goto fail;
 
@@ -1906,6 +1958,26 @@  fail:
     return ret;
 }
 
+static void activate_playlist(AVFormatContext *s, struct playlist *pls) {
+
+	HLSContext *c = s->priv_data;
+
+	if (pls->index < c->n_variants) {
+
+		struct variant *var = c->variants[pls->index];
+
+		if (parse_playlist(c, pls->url, pls, NULL) < 0)
+			return;
+		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 int recheck_discard_flags(AVFormatContext *s, int first)
 {
     HLSContext *c = s->priv_data;
@@ -1916,9 +1988,11 @@  static int recheck_discard_flags(AVFormatContext *s, int first)
     for (i = 0; i < c->n_playlists; i++) {
         struct playlist *pls = c->playlists[i];
 
-        cur_needed = playlist_needed(c->playlists[i]);
+        cur_needed = playlist_needed(s, c->playlists[i], 0);
 
         if (cur_needed && !pls->needed) {
+            if (!pls->parsed)
+                activate_playlist(s, pls);
             pls->needed = 1;
             changed = 1;
             pls->cur_seq_no = select_cur_seq_no(c, pls);
@@ -2165,6 +2239,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);
@@ -2221,6 +2297,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", "parse all playlists of all variants at startup",
+		OFFSET(load_all_variants), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, FLAGS},
     {NULL}
 };