[FFmpeg-devel] [PATCH] hls demuxer: add option to defer parsing of variants
Steven Liu
lingjiujianke at gmail.com
Sun Nov 26 01:18:24 EET 2017
2017-11-25 17:31 GMT+08:00 Rainer Hochecker <fernetmenta at online.de>:
> fate runs now without error, sorry for that
>
> ---
> doc/demuxers.texi | 5 +
> libavformat/hls.c | 302 ++++++++++++++++++++++++++++++++++++------------------
> 2 files changed, 207 insertions(+), 100 deletions(-)
>
> 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.
> +
> + 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
>
> @section image2
> diff --git a/libavformat/hls.c b/libavformat/hls.c
> index 786934af03..c1c93f8067 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;
Is that pls->read_buffer will memleak?
> + }
> + 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;
> @@ -1663,6 +1803,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;
> +
> if ((ret = save_avio_options(s)) < 0)
> goto fail;
>
> @@ -1675,8 +1818,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 +1870,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 +1890,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 +1943,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 +2020,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 +2199,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 +2257,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}
> };
>
> --
> 2.14.1
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel at ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
More information about the ffmpeg-devel
mailing list