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

Anssi Hannula anssi.hannula at iki.fi
Sat Dec 2 17:09:39 EET 2017


Hi,

Sorry about the delay.

Rainer Hochecker kirjoitti 2017-11-28 00:23:
> 2017-11-27 22:53 GMT+01:00 Anssi Hannula <anssi.hannula at iki.fi>:
>> Hi,
>> 
>> Rainer Hochecker kirjoitti 2017-11-26 12:46:
>>> 
>>> fixed mem leak poined out by Steven
>>> 
>>> ---
>>>  doc/demuxers.texi |   5 +
>>>  libavformat/hls.c | 304
>>> ++++++++++++++++++++++++++++++++++++------------------
>>>  2 files changed, 209 insertions(+), 100 deletions(-)
>>> 
>> [...]
>>> 
>>> +
>>> + at 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
>> 
>> 
>> If playlist download+parsing is indeed taking long enough time that 
>> this is
>> warranted, I wonder if we should maybe just never read any variant 
>> playlists
>> in read_header(), and instead set AVFMTCTX_NOHEADER.
>> Then the user may discard AVPrograms right after open, before unneeded
>> playlists are loaded+parsed.
>> 
>> This would avoid having a separate option/mode for this.
>> 
>> Any thoughts?
> 
> I actually tried it like this but somwhow did not like it because this
> is different
> to all other demuxers/formats. In general you open an input, call
> find_stream_info,
> and select a program. I did not like selecting the program between open 
> and
> find_stream_info.

OK I guess. Though arguably hls is already quite different from mpegts 
which is the only other demuxer that creates multiple AVPrograms.

In the long run, I think I'd prefer the HLS demuxer to have a mode where 
only a single program/variant is given to the user at a time, with the 
demuxer handling the switching between the variants. But that would be a 
lot more work and I'm not even sure it would be feasible. So I guess 
your solution is OK, at least for now.


Is there a reason the first variant/playlist is still parsed, i.e. why 
not simply not parse any media playlists (i.e. only master pls) when the 
new mode is selected?
If the player is selecting the variant/program based on bitrate (like 
Kodi does), then the first playlist would likely have been 
downloaded+parsed unnecessarily.


Also, even with your current patch you will need to set 
AVFMTCTX_NOHEADER as you defer avformat_new_stream() calls to 
read_packet(). I think you can update update_noheader_flag() to set 
flag_needed=1 if any pls is !pls->is_parsed.

>> 
>> 
>>>  @section image2
>>> diff --git a/libavformat/hls.c b/libavformat/hls.c
>>> index 786934af03..c42e0b0f95 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;
>> 
>> [...]
>>> 
>>> @@ -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;
>>>      }
>> 
>> 
>> That "pls->parsed = 1" is in the "reinit old playlist for re-parse" 
>> block,
>> is that intentional?
>> 
>> I'd think it should rather be at the end of the function alongside 
>> setting
>> pls->last_load_time, so it is set to 1 whenever parsing has been done.
>> 
> 
> I put it at the beginning because I wanted to avoid that it gets tried 
> again
> on error.

OK. But now it is not set for master playlists, so maybe move 
pls->parsed = 1 below the "fail" label then?

>>>      while (!avio_feof(in)) {
>>>          read_chomp_line(in, line, sizeof(line));
>> 
>> [...]
>>> 
>>> @@ -1631,6 +1655,124 @@ static int hls_close(AVFormatContext *s)
>>>      return 0;
>>>  }
>>> 
>>> +static int init_playlist(HLSContext *c, struct playlist *pls)
>>> +{
>> 
>> 
>> This init_playlist() seems to be mostly code moved from below, could 
>> you
>> split the patch so that the first patch moves the code around but does 
>> not
>> change behavior, and the second patch makes the actual changes (i.e. 
>> adds
>> the option)?
>> 
>> That would ease e.g. future bisecting.
> 
> Sure. At least I can try.
> 
>> 
>> [...]
>>> 
>>> +    return 0;
>>> +}
>>> +
>>>  static int hls_read_header(AVFormatContext *s)
>>>  {
>>>      void *u = (s->flags & AVFMT_FLAG_CUSTOM_IO) ? NULL : s->pb;
>>> @@ -1663,6 +1805,9 @@ static int hls_read_header(AVFormatContext *s)
>>>      if ((ret = parse_playlist(c, s->filename, NULL, s->pb)) < 0)
>>>          goto fail;
>>> 
>>> +    /* first playlist was created, set it to parsed */
>>> +    c->variants[0]->playlists[0]->parsed = 1;
>>> +
>> 
>> 
>> I think parse_playlist() should set this when it parses something.
> 
> That was pitfall for my v1. The first playlist gets parsed with no
> playlist given as argument.
> fate failed because non-master playlists were not set to parsed.

I don't think I follow. If parse_playlist() would set the playlist as 
parsed on every call (whether failed or not), then all playlists would 
be set to parsed and this line would be unnecessary.

>> 
>>>      if ((ret = save_avio_options(s)) < 0)
>>>          goto fail;
>>> 
>>> @@ -1675,8 +1820,15 @@ static int hls_read_header(AVFormatContext *s)
>>>          goto fail;
>> 
>> [...]
>>> 
>>> 
>>>      /* 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;
>> 
>> 
>> Why?
> 
> playlists not parsed are invalid, right? maybe n_segments is 0 for
> not parsed playlists but this was no obvious to me.

Yes, but after your change any parsed zero-segment playlists will get 
select_cur_seq_no() called while they didn't before.

[...]
>     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);
> +            }
> +        }
> +    }
> +}
> +

I applied a fix few days ago for hls that refactored the discard flag 
handling.

Instead of adding this new recheck_discard_programs(), could you instead 
update the playlist_needed() logic to return 0 on appropriate situations 
in the new mode, and then have recheck_discard_flags() call a function 
to do the needed initialization/parsing in the "if (cur_needed && 
!pls->needed)" case?

Sorry about the timing.

[...]
> +    {"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},

The help text is a bit confusing for the user as it is a boolean. Maybe 
just:
"parse all playlists of all variants at startup"


-- 
Anssi Hannula



More information about the ffmpeg-devel mailing list