X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libavformat%2Fhls.c;h=35ece98bb8386f89348f1157fe2d17f36a85a8ce;hb=83db3c84fa72e8d4d864ed796438ebfb2ee4bcc3;hp=b962d67abce588594770b9a196524af658976a77;hpb=543142990b6f7b8757753c13ea6dbc56275c5c7e;p=ffmpeg diff --git a/libavformat/hls.c b/libavformat/hls.c index b962d67abce..35ece98bb83 100644 --- a/libavformat/hls.c +++ b/libavformat/hls.c @@ -98,7 +98,11 @@ struct playlist { int index; AVFormatContext *ctx; AVPacket pkt; - int stream_offset; + + /* main demuxer streams associated with this playlist + * indexed by the subdemuxer stream indexes */ + AVStream **main_streams; + int n_main_streams; int finished; enum PlaylistType type; @@ -239,6 +243,7 @@ static void free_playlist_list(HLSContext *c) struct playlist *pls = c->playlists[i]; free_segment_list(pls); free_init_section_list(pls); + av_freep(&pls->main_streams); av_freep(&pls->renditions); av_freep(&pls->id3_buf); av_dict_free(&pls->id3_initial); @@ -590,7 +595,7 @@ static void update_options(char **dest, const char *name, void *src) } static int open_url(AVFormatContext *s, AVIOContext **pb, const char *url, - AVDictionary *opts, AVDictionary *opts2) + AVDictionary *opts, AVDictionary *opts2, int *is_http) { HLSContext *c = s->priv_data; AVDictionary *tmp = NULL; @@ -631,6 +636,9 @@ static int open_url(AVFormatContext *s, AVIOContext **pb, const char *url, av_dict_free(&tmp); + if (is_http) + *is_http = av_strstart(proto_name, "http", NULL); + return ret; } @@ -1072,6 +1080,7 @@ static int open_input(HLSContext *c, struct playlist *pls, struct segment *seg) { AVDictionary *opts = NULL; int ret; + int is_http = 0; // broker prior HTTP options that should be consistent across requests av_dict_set(&opts, "user-agent", c->user_agent, 0); @@ -1091,13 +1100,13 @@ static int open_input(HLSContext *c, struct playlist *pls, struct segment *seg) seg->url, seg->url_offset, pls->index); if (seg->key_type == KEY_NONE) { - ret = open_url(pls->parent, &pls->input, seg->url, c->avio_opts, opts); + ret = open_url(pls->parent, &pls->input, seg->url, c->avio_opts, opts, &is_http); } else if (seg->key_type == KEY_AES_128) { AVDictionary *opts2 = NULL; char iv[33], key[33], url[MAX_URL_SIZE]; if (strcmp(seg->key, pls->key_url)) { AVIOContext *pb; - if (open_url(pls->parent, &pb, seg->key, c->avio_opts, opts) == 0) { + if (open_url(pls->parent, &pb, seg->key, c->avio_opts, opts, NULL) == 0) { ret = avio_read(pb, pls->key, sizeof(pls->key)); if (ret != sizeof(pls->key)) { av_log(NULL, AV_LOG_ERROR, "Unable to read key file %s\n", @@ -1122,7 +1131,7 @@ static int open_input(HLSContext *c, struct playlist *pls, struct segment *seg) av_dict_set(&opts2, "key", key, 0); av_dict_set(&opts2, "iv", iv, 0); - ret = open_url(pls->parent, &pls->input, url, opts2, opts); + ret = open_url(pls->parent, &pls->input, url, opts2, opts, &is_http); av_dict_free(&opts2); @@ -1140,8 +1149,15 @@ static int open_input(HLSContext *c, struct playlist *pls, struct segment *seg) /* Seek to the requested position. If this was a HTTP request, the offset * should already be where want it to, but this allows e.g. local testing - * without a HTTP server. */ - if (ret == 0 && seg->key_type == KEY_NONE && seg->url_offset) { + * without a HTTP server. + * + * This is not done for HTTP at all as avio_seek() does internal bookkeeping + * of file offset which is out-of-sync with the actual offset when "offset" + * AVOption is used with http protocol, causing the seek to not be a no-op + * as would be expected. Wrong offset received from the server will not be + * noticed without the call, though. + */ + if (ret == 0 && !is_http && seg->key_type == KEY_NONE && seg->url_offset) { int64_t seekret = avio_seek(pls->input, seg->url_offset, SEEK_SET); if (seekret < 0) { av_log(pls->parent, AV_LOG_ERROR, "Unable to seek to offset %"PRId64" of HLS segment '%s'\n", seg->url_offset, seg->url); @@ -1237,13 +1253,13 @@ restart: /* Check that the playlist is still needed before opening a new * segment. */ - if (v->ctx && v->ctx->nb_streams && - v->parent->nb_streams >= v->stream_offset + v->ctx->nb_streams) { + if (v->ctx && v->ctx->nb_streams) { v->needed = 0; - for (i = v->stream_offset; i < v->stream_offset + v->ctx->nb_streams; - i++) { - if (v->parent->streams[i]->discard < AVDISCARD_ALL) + for (i = 0; i < v->n_main_streams; i++) { + if (v->main_streams[i]->discard < AVDISCARD_ALL) { v->needed = 1; + break; + } } } if (!v->needed) { @@ -1332,25 +1348,6 @@ reload: goto restart; } -static int playlist_in_multiple_variants(HLSContext *c, struct playlist *pls) -{ - int variant_count = 0; - int i, j; - - for (i = 0; i < c->n_variants && variant_count < 2; i++) { - struct variant *v = c->variants[i]; - - for (j = 0; j < v->n_playlists; j++) { - if (v->playlists[j] == pls) { - variant_count++; - break; - } - } - } - - return variant_count >= 2; -} - static void add_renditions_to_variant(HLSContext *c, struct variant *var, enum AVMediaType type, const char *group_id) { @@ -1381,8 +1378,8 @@ static void add_metadata_from_renditions(AVFormatContext *s, struct playlist *pl int rend_idx = 0; int i; - for (i = 0; i < pls->ctx->nb_streams; i++) { - AVStream *st = s->streams[pls->stream_offset + i]; + for (i = 0; i < pls->n_main_streams; i++) { + AVStream *st = pls->main_streams[i]; if (st->codecpar->codec_type != type) continue; @@ -1504,11 +1501,66 @@ static int nested_io_open(AVFormatContext *s, AVIOContext **pb, const char *url, return AVERROR(EPERM); } +static void add_stream_to_programs(AVFormatContext *s, struct playlist *pls, AVStream *stream) +{ + HLSContext *c = s->priv_data; + int i, j; + int bandwidth = -1; + + for (i = 0; i < c->n_variants; i++) { + struct variant *v = c->variants[i]; + + for (j = 0; j < v->n_playlists; j++) { + if (v->playlists[j] != pls) + continue; + + av_program_add_stream_index(s, i, stream->index); + + if (bandwidth < 0) + bandwidth = v->bandwidth; + else if (bandwidth != v->bandwidth) + bandwidth = -1; /* stream in multiple variants with different bandwidths */ + } + } + + if (bandwidth >= 0) + av_dict_set_int(&stream->metadata, "variant_bitrate", bandwidth, 0); +} + +/* add new subdemuxer streams to our context, if any */ +static int update_streams_from_subdemuxer(AVFormatContext *s, struct playlist *pls) +{ + while (pls->n_main_streams < pls->ctx->nb_streams) { + int ist_idx = pls->n_main_streams; + AVStream *st = avformat_new_stream(s, NULL); + AVStream *ist = pls->ctx->streams[ist_idx]; + + if (!st) + return AVERROR(ENOMEM); + + st->id = pls->index; + + avcodec_parameters_copy(st->codecpar, ist->codecpar); + + if (pls->is_id3_timestamped) /* custom timestamps via id3 */ + avpriv_set_pts_info(st, 33, 1, MPEG_TIME_BASE); + else + avpriv_set_pts_info(st, ist->pts_wrap_bits, ist->time_base.num, ist->time_base.den); + + dynarray_add(&pls->main_streams, &pls->n_main_streams, st); + + add_stream_to_programs(s, pls, st); + } + + return 0; +} + static int hls_read_header(AVFormatContext *s) { void *u = (s->flags & AVFMT_FLAG_CUSTOM_IO) ? NULL : s->pb; HLSContext *c = s->priv_data; - int ret = 0, i, j, stream_offset = 0; + int ret = 0, i; + int highest_cur_seq_no = 0; c->ctx = s; c->interrupt_callback = &s->interrupt_callback; @@ -1583,6 +1635,28 @@ static int hls_read_header(AVFormatContext *s) add_renditions_to_variant(c, var, AVMEDIA_TYPE_SUBTITLE, var->subtitles_group); } + /* Create a program for each variant */ + for (i = 0; i < c->n_variants; i++) { + struct variant *v = c->variants[i]; + AVProgram *program; + + program = av_new_program(s, i); + if (!program) + goto fail; + av_dict_set_int(&program->metadata, "variant_bitrate", v->bandwidth, 0); + } + + /* Select the starting segments */ + for (i = 0; i < c->n_playlists; i++) { + struct playlist *pls = c->playlists[i]; + + if (pls->n_segments == 0) + continue; + + pls->cur_seq_no = select_cur_seq_no(c, pls); + highest_cur_seq_no = FFMAX(highest_cur_seq_no, pls->cur_seq_no); + } + /* Open the demuxer for each playlist */ for (i = 0; i < c->n_playlists; i++) { struct playlist *pls = c->playlists[i]; @@ -1599,7 +1673,18 @@ static int hls_read_header(AVFormatContext *s) pls->index = i; pls->needed = 1; pls->parent = s; - pls->cur_seq_no = select_cur_seq_no(c, pls); + + /* + * 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){ @@ -1625,7 +1710,6 @@ static int hls_read_header(AVFormatContext *s) } pls->ctx->pb = &pls->pb; pls->ctx->io_open = nested_io_open; - pls->stream_offset = stream_offset; if ((ret = ff_copy_whiteblacklists(pls->ctx, s)) < 0) goto fail; @@ -1650,55 +1734,13 @@ static int hls_read_header(AVFormatContext *s) av_log(s, AV_LOG_WARNING, "No expected HTTP requests have been made\n"); /* Create new AVStreams for each stream in this playlist */ - for (j = 0; j < pls->ctx->nb_streams; j++) { - AVStream *st = avformat_new_stream(s, NULL); - AVStream *ist = pls->ctx->streams[j]; - if (!st) { - ret = AVERROR(ENOMEM); - goto fail; - } - st->id = i; - - avcodec_parameters_copy(st->codecpar, pls->ctx->streams[j]->codecpar); - - if (pls->is_id3_timestamped) /* custom timestamps via id3 */ - avpriv_set_pts_info(st, 33, 1, MPEG_TIME_BASE); - else - avpriv_set_pts_info(st, ist->pts_wrap_bits, ist->time_base.num, ist->time_base.den); - } + 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); - - stream_offset += pls->ctx->nb_streams; - } - - /* Create a program for each variant */ - for (i = 0; i < c->n_variants; i++) { - struct variant *v = c->variants[i]; - AVProgram *program; - - program = av_new_program(s, i); - if (!program) - goto fail; - av_dict_set_int(&program->metadata, "variant_bitrate", v->bandwidth, 0); - - for (j = 0; j < v->n_playlists; j++) { - struct playlist *pls = v->playlists[j]; - int is_shared = playlist_in_multiple_variants(c, pls); - int k; - - for (k = 0; k < pls->ctx->nb_streams; k++) { - struct AVStream *st = s->streams[pls->stream_offset + k]; - - av_program_add_stream_index(s, i, pls->stream_offset + k); - - /* Set variant_bitrate for streams unique to this variant */ - if (!is_shared && v->bandwidth) - av_dict_set_int(&st->metadata, "variant_bitrate", v->bandwidth, 0); - } - } } return 0; @@ -1871,8 +1913,17 @@ static int hls_read_packet(AVFormatContext *s, AVPacket *pkt) /* If we got a packet, return it */ if (minplaylist >= 0) { struct playlist *pls = c->playlists[minplaylist]; + + if (pls->pkt.stream_index >= pls->n_main_streams) { + av_log(s, AV_LOG_ERROR, "stream index inconsistency: index %d, %d main streams, %d subdemuxer streams\n", + pls->pkt.stream_index, pls->n_main_streams, pls->ctx->nb_streams); + av_packet_unref(&pls->pkt); + reset_packet(&pls->pkt); + return AVERROR_BUG; + } + *pkt = pls->pkt; - pkt->stream_index += pls->stream_offset; + pkt->stream_index = pls->main_streams[pls->pkt.stream_index]->index; reset_packet(&c->playlists[minplaylist]->pkt); if (pkt->dts != AV_NOPTS_VALUE) @@ -1904,6 +1955,8 @@ static int hls_read_seek(AVFormatContext *s, int stream_index, HLSContext *c = s->priv_data; struct playlist *seek_pls = NULL; int i, seq_no; + int j; + int stream_subdemuxer_index; int64_t first_timestamp, seek_timestamp, duration; if ((flags & AVSEEK_FLAG_BYTE) || @@ -1927,10 +1980,12 @@ static int hls_read_seek(AVFormatContext *s, int stream_index, /* find the playlist with the specified stream */ for (i = 0; i < c->n_playlists; i++) { struct playlist *pls = c->playlists[i]; - if (stream_index >= pls->stream_offset && - stream_index - pls->stream_offset < pls->ctx->nb_streams) { - seek_pls = pls; - break; + for (j = 0; j < pls->n_main_streams; j++) { + if (pls->main_streams[j] == s->streams[stream_index]) { + seek_pls = pls; + stream_subdemuxer_index = j; + break; + } } } /* check if the timestamp is valid for the playlist with the @@ -1940,7 +1995,7 @@ static int hls_read_seek(AVFormatContext *s, int stream_index, /* set segment now so we do not need to search again below */ seek_pls->cur_seq_no = seq_no; - seek_pls->seek_stream_index = stream_index - seek_pls->stream_offset; + seek_pls->seek_stream_index = stream_subdemuxer_index; for (i = 0; i < c->n_playlists; i++) { /* Reset reading */