[FFmpeg-devel] [PATCH] hls demuxer: add option to defer parsing of variants
Rainer Hochecker
fernetmenta at online.de
Fri Nov 24 00:20:10 EET 2017
---
doc/demuxers.texi | 5 +
libavformat/hls.c | 299 ++++++++++++++++++++++++++++++++++++------------------
2 files changed, 204 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..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}
};
--
2.14.1
More information about the ffmpeg-devel
mailing list