Message ID | 20171125093128.23220-1-fernetmenta@online.de |
---|---|
State | Superseded |
Headers | show |
2017-11-25 17:31 GMT+08:00 Rainer Hochecker <fernetmenta@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. > + > +@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@ffmpeg.org > http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>> + /* >> + * 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? > > yes, looks like this. this is already an issue in current code. nevertheless, I will fix it here.
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..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; + } + 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} };