Commit 9371d70b authored by Anssi Hannula's avatar Anssi Hannula

avformat/hls: decouple playlists from variants

Not all "sub-playlists" are variant playlists (containing the same
content with a different bitrate, etc) in the current version of the HLS
specification. They can now also be alternative renditions, containing
e.g. alternative audio tracks etc.

Decouple playlists from variants to prepare for handling the new
features.
Signed-off-by: 's avatarAnssi Hannula <anssi.hannula@iki.fi>
parent 857841c1
...@@ -64,12 +64,11 @@ struct segment { ...@@ -64,12 +64,11 @@ struct segment {
}; };
/* /*
* Each variant has its own demuxer. If it currently is active, * Each playlist has its own demuxer. If it currently is active,
* it has an open AVIOContext too, and potentially an AVPacket * it has an open AVIOContext too, and potentially an AVPacket
* containing the next packet from this stream. * containing the next packet from this stream.
*/ */
struct variant { struct playlist {
int bandwidth;
char url[MAX_URL_SIZE]; char url[MAX_URL_SIZE];
AVIOContext pb; AVIOContext pb;
uint8_t* read_buffer; uint8_t* read_buffer;
...@@ -93,9 +92,18 @@ struct variant { ...@@ -93,9 +92,18 @@ struct variant {
uint8_t key[16]; uint8_t key[16];
}; };
struct variant {
int bandwidth;
int n_playlists;
struct playlist **playlists;
};
typedef struct HLSContext { typedef struct HLSContext {
int n_variants; int n_variants;
struct variant **variants; struct variant **variants;
int n_playlists;
struct playlist **playlists;
int cur_seq_no; int cur_seq_no;
int end_of_segment; int end_of_segment;
int first_packet; int first_packet;
...@@ -116,13 +124,35 @@ static int read_chomp_line(AVIOContext *s, char *buf, int maxlen) ...@@ -116,13 +124,35 @@ static int read_chomp_line(AVIOContext *s, char *buf, int maxlen)
return len; return len;
} }
static void free_segment_list(struct variant *var) static void free_segment_list(struct playlist *pls)
{
int i;
for (i = 0; i < pls->n_segments; i++)
av_free(pls->segments[i]);
av_freep(&pls->segments);
pls->n_segments = 0;
}
static void free_playlist_list(HLSContext *c)
{ {
int i; int i;
for (i = 0; i < var->n_segments; i++) for (i = 0; i < c->n_playlists; i++) {
av_free(var->segments[i]); struct playlist *pls = c->playlists[i];
av_freep(&var->segments); free_segment_list(pls);
var->n_segments = 0; av_free_packet(&pls->pkt);
av_free(pls->pb.buffer);
if (pls->input)
ffurl_close(pls->input);
if (pls->ctx) {
pls->ctx->pb = NULL;
avformat_close_input(&pls->ctx);
}
av_free(pls);
}
av_freep(&c->playlists);
av_freep(&c->cookies);
av_freep(&c->user_agent);
c->n_playlists = 0;
} }
static void free_variant_list(HLSContext *c) static void free_variant_list(HLSContext *c)
...@@ -130,20 +160,10 @@ static void free_variant_list(HLSContext *c) ...@@ -130,20 +160,10 @@ static void free_variant_list(HLSContext *c)
int i; int i;
for (i = 0; i < c->n_variants; i++) { for (i = 0; i < c->n_variants; i++) {
struct variant *var = c->variants[i]; struct variant *var = c->variants[i];
free_segment_list(var); av_freep(&var->playlists);
av_free_packet(&var->pkt);
av_free(var->pb.buffer);
if (var->input)
ffurl_close(var->input);
if (var->ctx) {
var->ctx->pb = NULL;
avformat_close_input(&var->ctx);
}
av_free(var); av_free(var);
} }
av_freep(&c->variants); av_freep(&c->variants);
av_freep(&c->cookies);
av_freep(&c->user_agent);
c->n_variants = 0; c->n_variants = 0;
} }
...@@ -157,16 +177,35 @@ static void reset_packet(AVPacket *pkt) ...@@ -157,16 +177,35 @@ static void reset_packet(AVPacket *pkt)
pkt->data = NULL; pkt->data = NULL;
} }
static struct playlist *new_playlist(HLSContext *c, const char *url,
const char *base)
{
struct playlist *pls = av_mallocz(sizeof(struct playlist));
if (!pls)
return NULL;
reset_packet(&pls->pkt);
ff_make_absolute_url(pls->url, sizeof(pls->url), base, url);
dynarray_add(&c->playlists, &c->n_playlists, pls);
return pls;
}
static struct variant *new_variant(HLSContext *c, int bandwidth, static struct variant *new_variant(HLSContext *c, int bandwidth,
const char *url, const char *base) const char *url, const char *base)
{ {
struct variant *var = av_mallocz(sizeof(struct variant)); struct variant *var;
struct playlist *pls;
pls = new_playlist(c, url, base);
if (!pls)
return NULL;
var = av_mallocz(sizeof(struct variant));
if (!var) if (!var)
return NULL; return NULL;
reset_packet(&var->pkt);
var->bandwidth = bandwidth; var->bandwidth = bandwidth;
ff_make_absolute_url(var->url, sizeof(var->url), base, url);
dynarray_add(&c->variants, &c->n_variants, var); dynarray_add(&c->variants, &c->n_variants, var);
dynarray_add(&var->playlists, &var->n_playlists, pls);
return var; return var;
} }
...@@ -205,7 +244,7 @@ static void handle_key_args(struct key_info *info, const char *key, ...@@ -205,7 +244,7 @@ static void handle_key_args(struct key_info *info, const char *key,
} }
static int parse_playlist(HLSContext *c, const char *url, static int parse_playlist(HLSContext *c, const char *url,
struct variant *var, AVIOContext *in) struct playlist *pls, AVIOContext *in)
{ {
int ret = 0, is_segment = 0, is_variant = 0, bandwidth = 0; int ret = 0, is_segment = 0, is_variant = 0, bandwidth = 0;
int64_t duration = 0; int64_t duration = 0;
...@@ -245,9 +284,9 @@ static int parse_playlist(HLSContext *c, const char *url, ...@@ -245,9 +284,9 @@ static int parse_playlist(HLSContext *c, const char *url,
goto fail; goto fail;
} }
if (var) { if (pls) {
free_segment_list(var); free_segment_list(pls);
var->finished = 0; pls->finished = 0;
} }
while (!url_feof(in)) { while (!url_feof(in)) {
read_chomp_line(in, line, sizeof(line)); read_chomp_line(in, line, sizeof(line));
...@@ -271,26 +310,26 @@ static int parse_playlist(HLSContext *c, const char *url, ...@@ -271,26 +310,26 @@ static int parse_playlist(HLSContext *c, const char *url,
} }
av_strlcpy(key, info.uri, sizeof(key)); av_strlcpy(key, info.uri, sizeof(key));
} else if (av_strstart(line, "#EXT-X-TARGETDURATION:", &ptr)) { } else if (av_strstart(line, "#EXT-X-TARGETDURATION:", &ptr)) {
if (!var) { if (!pls) {
var = new_variant(c, 0, url, NULL); if (!new_variant(c, 0, url, NULL)) {
if (!var) {
ret = AVERROR(ENOMEM); ret = AVERROR(ENOMEM);
goto fail; goto fail;
} }
pls = c->playlists[c->n_playlists - 1];
} }
var->target_duration = atoi(ptr) * AV_TIME_BASE; pls->target_duration = atoi(ptr) * AV_TIME_BASE;
} else if (av_strstart(line, "#EXT-X-MEDIA-SEQUENCE:", &ptr)) { } else if (av_strstart(line, "#EXT-X-MEDIA-SEQUENCE:", &ptr)) {
if (!var) { if (!pls) {
var = new_variant(c, 0, url, NULL); if (!new_variant(c, 0, url, NULL)) {
if (!var) {
ret = AVERROR(ENOMEM); ret = AVERROR(ENOMEM);
goto fail; goto fail;
} }
pls = c->playlists[c->n_playlists - 1];
} }
var->start_seq_no = atoi(ptr); pls->start_seq_no = atoi(ptr);
} else if (av_strstart(line, "#EXT-X-ENDLIST", &ptr)) { } else if (av_strstart(line, "#EXT-X-ENDLIST", &ptr)) {
if (var) if (pls)
var->finished = 1; pls->finished = 1;
} else if (av_strstart(line, "#EXTINF:", &ptr)) { } else if (av_strstart(line, "#EXTINF:", &ptr)) {
is_segment = 1; is_segment = 1;
duration = atof(ptr) * AV_TIME_BASE; duration = atof(ptr) * AV_TIME_BASE;
...@@ -307,12 +346,12 @@ static int parse_playlist(HLSContext *c, const char *url, ...@@ -307,12 +346,12 @@ static int parse_playlist(HLSContext *c, const char *url,
} }
if (is_segment) { if (is_segment) {
struct segment *seg; struct segment *seg;
if (!var) { if (!pls) {
var = new_variant(c, 0, url, NULL); if (!new_variant(c, 0, url, NULL)) {
if (!var) {
ret = AVERROR(ENOMEM); ret = AVERROR(ENOMEM);
goto fail; goto fail;
} }
pls = c->playlists[c->n_playlists - 1];
} }
seg = av_malloc(sizeof(struct segment)); seg = av_malloc(sizeof(struct segment));
if (!seg) { if (!seg) {
...@@ -324,19 +363,19 @@ static int parse_playlist(HLSContext *c, const char *url, ...@@ -324,19 +363,19 @@ static int parse_playlist(HLSContext *c, const char *url,
if (has_iv) { if (has_iv) {
memcpy(seg->iv, iv, sizeof(iv)); memcpy(seg->iv, iv, sizeof(iv));
} else { } else {
int seq = var->start_seq_no + var->n_segments; int seq = pls->start_seq_no + pls->n_segments;
memset(seg->iv, 0, sizeof(seg->iv)); memset(seg->iv, 0, sizeof(seg->iv));
AV_WB32(seg->iv + 12, seq); AV_WB32(seg->iv + 12, seq);
} }
ff_make_absolute_url(seg->key, sizeof(seg->key), url, key); ff_make_absolute_url(seg->key, sizeof(seg->key), url, key);
ff_make_absolute_url(seg->url, sizeof(seg->url), url, line); ff_make_absolute_url(seg->url, sizeof(seg->url), url, line);
dynarray_add(&var->segments, &var->n_segments, seg); dynarray_add(&pls->segments, &pls->n_segments, seg);
is_segment = 0; is_segment = 0;
} }
} }
} }
if (var) if (pls)
var->last_load_time = av_gettime(); pls->last_load_time = av_gettime();
fail: fail:
av_free(new_url); av_free(new_url);
...@@ -345,11 +384,11 @@ fail: ...@@ -345,11 +384,11 @@ fail:
return ret; return ret;
} }
static int open_input(HLSContext *c, struct variant *var) static int open_input(HLSContext *c, struct playlist *pls)
{ {
AVDictionary *opts = NULL; AVDictionary *opts = NULL;
int ret; int ret;
struct segment *seg = var->segments[var->cur_seq_no - var->start_seq_no]; struct segment *seg = pls->segments[pls->cur_seq_no - pls->start_seq_no];
// broker prior HTTP options that should be consistent across requests // broker prior HTTP options that should be consistent across requests
av_dict_set(&opts, "user-agent", c->user_agent, 0); av_dict_set(&opts, "user-agent", c->user_agent, 0);
...@@ -358,17 +397,17 @@ static int open_input(HLSContext *c, struct variant *var) ...@@ -358,17 +397,17 @@ static int open_input(HLSContext *c, struct variant *var)
av_dict_set(&opts, "seekable", "0", 0); av_dict_set(&opts, "seekable", "0", 0);
if (seg->key_type == KEY_NONE) { if (seg->key_type == KEY_NONE) {
ret = ffurl_open(&var->input, seg->url, AVIO_FLAG_READ, ret = ffurl_open(&pls->input, seg->url, AVIO_FLAG_READ,
&var->parent->interrupt_callback, &opts); &pls->parent->interrupt_callback, &opts);
goto cleanup; goto cleanup;
} else if (seg->key_type == KEY_AES_128) { } else if (seg->key_type == KEY_AES_128) {
char iv[33], key[33], url[MAX_URL_SIZE]; char iv[33], key[33], url[MAX_URL_SIZE];
if (strcmp(seg->key, var->key_url)) { if (strcmp(seg->key, pls->key_url)) {
URLContext *uc; URLContext *uc;
if (ffurl_open(&uc, seg->key, AVIO_FLAG_READ, if (ffurl_open(&uc, seg->key, AVIO_FLAG_READ,
&var->parent->interrupt_callback, &opts) == 0) { &pls->parent->interrupt_callback, &opts) == 0) {
if (ffurl_read_complete(uc, var->key, sizeof(var->key)) if (ffurl_read_complete(uc, pls->key, sizeof(pls->key))
!= sizeof(var->key)) { != sizeof(pls->key)) {
av_log(NULL, AV_LOG_ERROR, "Unable to read key file %s\n", av_log(NULL, AV_LOG_ERROR, "Unable to read key file %s\n",
seg->key); seg->key);
} }
...@@ -377,26 +416,26 @@ static int open_input(HLSContext *c, struct variant *var) ...@@ -377,26 +416,26 @@ static int open_input(HLSContext *c, struct variant *var)
av_log(NULL, AV_LOG_ERROR, "Unable to open key file %s\n", av_log(NULL, AV_LOG_ERROR, "Unable to open key file %s\n",
seg->key); seg->key);
} }
av_strlcpy(var->key_url, seg->key, sizeof(var->key_url)); av_strlcpy(pls->key_url, seg->key, sizeof(pls->key_url));
} }
ff_data_to_hex(iv, seg->iv, sizeof(seg->iv), 0); ff_data_to_hex(iv, seg->iv, sizeof(seg->iv), 0);
ff_data_to_hex(key, var->key, sizeof(var->key), 0); ff_data_to_hex(key, pls->key, sizeof(pls->key), 0);
iv[32] = key[32] = '\0'; iv[32] = key[32] = '\0';
if (strstr(seg->url, "://")) if (strstr(seg->url, "://"))
snprintf(url, sizeof(url), "crypto+%s", seg->url); snprintf(url, sizeof(url), "crypto+%s", seg->url);
else else
snprintf(url, sizeof(url), "crypto:%s", seg->url); snprintf(url, sizeof(url), "crypto:%s", seg->url);
if ((ret = ffurl_alloc(&var->input, url, AVIO_FLAG_READ, if ((ret = ffurl_alloc(&pls->input, url, AVIO_FLAG_READ,
&var->parent->interrupt_callback)) < 0) &pls->parent->interrupt_callback)) < 0)
goto cleanup; goto cleanup;
av_opt_set(var->input->priv_data, "key", key, 0); av_opt_set(pls->input->priv_data, "key", key, 0);
av_opt_set(var->input->priv_data, "iv", iv, 0); av_opt_set(pls->input->priv_data, "iv", iv, 0);
/* Need to repopulate options */ /* Need to repopulate options */
av_dict_free(&opts); av_dict_free(&opts);
av_dict_set(&opts, "seekable", "0", 0); av_dict_set(&opts, "seekable", "0", 0);
if ((ret = ffurl_connect(var->input, &opts)) < 0) { if ((ret = ffurl_connect(pls->input, &opts)) < 0) {
ffurl_close(var->input); ffurl_close(pls->input);
var->input = NULL; pls->input = NULL;
goto cleanup; goto cleanup;
} }
ret = 0; ret = 0;
...@@ -411,14 +450,14 @@ cleanup: ...@@ -411,14 +450,14 @@ cleanup:
static int read_data(void *opaque, uint8_t *buf, int buf_size) static int read_data(void *opaque, uint8_t *buf, int buf_size)
{ {
struct variant *v = opaque; struct playlist *v = opaque;
HLSContext *c = v->parent->priv_data; HLSContext *c = v->parent->priv_data;
int ret, i; int ret, i;
restart: restart:
if (!v->input) { if (!v->input) {
/* If this is a live stream and the reload interval has elapsed since /* If this is a live stream and the reload interval has elapsed since
* the last playlist reload, reload the variant playlists now. */ * the last playlist reload, reload the playlists now. */
int64_t reload_interval = v->n_segments > 0 ? int64_t reload_interval = v->n_segments > 0 ?
v->segments[v->n_segments - 1]->duration : v->segments[v->n_segments - 1]->duration :
v->target_duration; v->target_duration;
...@@ -475,13 +514,32 @@ reload: ...@@ -475,13 +514,32 @@ reload:
} }
} }
if (!v->needed) { if (!v->needed) {
av_log(v->parent, AV_LOG_INFO, "No longer receiving variant %d\n", av_log(v->parent, AV_LOG_INFO, "No longer receiving playlist %d\n",
v->index); v->index);
return AVERROR_EOF; return AVERROR_EOF;
} }
goto restart; 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 int hls_read_header(AVFormatContext *s) static int hls_read_header(AVFormatContext *s)
{ {
URLContext *u = (s->flags & AVFMT_FLAG_CUSTOM_IO) ? NULL : s->pb->opaque; URLContext *u = (s->flags & AVFMT_FLAG_CUSTOM_IO) ? NULL : s->pb->opaque;
...@@ -519,17 +577,17 @@ static int hls_read_header(AVFormatContext *s) ...@@ -519,17 +577,17 @@ static int hls_read_header(AVFormatContext *s)
ret = AVERROR_EOF; ret = AVERROR_EOF;
goto fail; goto fail;
} }
/* If the playlist only contained variants, parse each individual /* If the playlist only contained playlists (Master Playlist),
* variant playlist. */ * parse each individual playlist. */
if (c->n_variants > 1 || c->variants[0]->n_segments == 0) { if (c->n_playlists > 1 || c->playlists[0]->n_segments == 0) {
for (i = 0; i < c->n_variants; i++) { for (i = 0; i < c->n_playlists; i++) {
struct variant *v = c->variants[i]; struct playlist *pls = c->playlists[i];
if ((ret = parse_playlist(c, v->url, v, NULL)) < 0) if ((ret = parse_playlist(c, pls->url, pls, NULL)) < 0)
goto fail; goto fail;
} }
} }
if (c->variants[0]->n_segments == 0) { if (c->variants[0]->playlists[0]->n_segments == 0) {
av_log(NULL, AV_LOG_WARNING, "Empty playlist\n"); av_log(NULL, AV_LOG_WARNING, "Empty playlist\n");
ret = AVERROR_EOF; ret = AVERROR_EOF;
goto fail; goto fail;
...@@ -537,88 +595,107 @@ static int hls_read_header(AVFormatContext *s) ...@@ -537,88 +595,107 @@ static int hls_read_header(AVFormatContext *s)
/* If this isn't a live stream, calculate the total duration of the /* If this isn't a live stream, calculate the total duration of the
* stream. */ * stream. */
if (c->variants[0]->finished) { if (c->variants[0]->playlists[0]->finished) {
int64_t duration = 0; int64_t duration = 0;
for (i = 0; i < c->variants[0]->n_segments; i++) for (i = 0; i < c->variants[0]->playlists[0]->n_segments; i++)
duration += c->variants[0]->segments[i]->duration; duration += c->variants[0]->playlists[0]->segments[i]->duration;
s->duration = duration; s->duration = duration;
} }
/* Open the demuxer for each variant */ /* Open the demuxer for each playlist */
for (i = 0; i < c->n_variants; i++) { for (i = 0; i < c->n_playlists; i++) {
struct variant *v = c->variants[i]; struct playlist *pls = c->playlists[i];
AVInputFormat *in_fmt = NULL; AVInputFormat *in_fmt = NULL;
char bitrate_str[20];
AVProgram *program;
if (v->n_segments == 0) if (pls->n_segments == 0)
continue; continue;
if (!(v->ctx = avformat_alloc_context())) { if (!(pls->ctx = avformat_alloc_context())) {
ret = AVERROR(ENOMEM); ret = AVERROR(ENOMEM);
goto fail; goto fail;
} }
v->index = i; pls->index = i;
v->needed = 1; pls->needed = 1;
v->parent = s; pls->parent = s;
/* If this is a live stream with more than 3 segments, start at the /* If this is a live stream with more than 3 segments, start at the
* third last segment. */ * third last segment. */
v->cur_seq_no = v->start_seq_no; pls->cur_seq_no = pls->start_seq_no;
if (!v->finished && v->n_segments > 3) if (!pls->finished && pls->n_segments > 3)
v->cur_seq_no = v->start_seq_no + v->n_segments - 3; pls->cur_seq_no = pls->start_seq_no + pls->n_segments - 3;
v->read_buffer = av_malloc(INITIAL_BUFFER_SIZE); pls->read_buffer = av_malloc(INITIAL_BUFFER_SIZE);
ffio_init_context(&v->pb, v->read_buffer, INITIAL_BUFFER_SIZE, 0, v, ffio_init_context(&pls->pb, pls->read_buffer, INITIAL_BUFFER_SIZE, 0, pls,
read_data, NULL, NULL); read_data, NULL, NULL);
v->pb.seekable = 0; pls->pb.seekable = 0;
ret = av_probe_input_buffer(&v->pb, &in_fmt, v->segments[0]->url, ret = av_probe_input_buffer(&pls->pb, &in_fmt, pls->segments[0]->url,
NULL, 0, 0); NULL, 0, 0);
if (ret < 0) { if (ret < 0) {
/* Free the ctx - it isn't initialized properly at this point, /* Free the ctx - it isn't initialized properly at this point,
* so avformat_close_input shouldn't be called. If * so avformat_close_input shouldn't be called. If
* avformat_open_input fails below, it frees and zeros the * avformat_open_input fails below, it frees and zeros the
* context, so it doesn't need any special treatment like this. */ * context, so it doesn't need any special treatment like this. */
av_log(s, AV_LOG_ERROR, "Error when loading first segment '%s'\n", v->segments[0]->url); av_log(s, AV_LOG_ERROR, "Error when loading first segment '%s'\n", pls->segments[0]->url);
avformat_free_context(v->ctx); avformat_free_context(pls->ctx);
v->ctx = NULL; pls->ctx = NULL;
goto fail; goto fail;
} }
v->ctx->pb = &v->pb; pls->ctx->pb = &pls->pb;
v->stream_offset = stream_offset; pls->stream_offset = stream_offset;
ret = avformat_open_input(&v->ctx, v->segments[0]->url, in_fmt, NULL); ret = avformat_open_input(&pls->ctx, pls->segments[0]->url, in_fmt, NULL);
if (ret < 0) if (ret < 0)
goto fail; goto fail;
v->ctx->ctx_flags &= ~AVFMTCTX_NOHEADER; pls->ctx->ctx_flags &= ~AVFMTCTX_NOHEADER;
ret = avformat_find_stream_info(v->ctx, NULL); ret = avformat_find_stream_info(pls->ctx, NULL);
if (ret < 0) if (ret < 0)
goto fail; goto fail;
snprintf(bitrate_str, sizeof(bitrate_str), "%d", v->bandwidth);
program = av_new_program(s, i);
if (!program)
goto fail;
av_dict_set(&program->metadata, "variant_bitrate", bitrate_str, 0);
/* Create new AVStreams for each stream in this variant */ /* Create new AVStreams for each stream in this playlist */
for (j = 0; j < v->ctx->nb_streams; j++) { for (j = 0; j < pls->ctx->nb_streams; j++) {
AVStream *st = avformat_new_stream(s, NULL); AVStream *st = avformat_new_stream(s, NULL);
AVStream *ist = v->ctx->streams[j]; AVStream *ist = pls->ctx->streams[j];
if (!st) { if (!st) {
ret = AVERROR(ENOMEM); ret = AVERROR(ENOMEM);
goto fail; goto fail;
} }
ff_program_add_stream_index(s, i, stream_offset + j);
st->id = i; st->id = i;
avpriv_set_pts_info(st, ist->pts_wrap_bits, ist->time_base.num, ist->time_base.den); avpriv_set_pts_info(st, ist->pts_wrap_bits, ist->time_base.num, ist->time_base.den);
avcodec_copy_context(st->codec, v->ctx->streams[j]->codec); avcodec_copy_context(st->codec, pls->ctx->streams[j]->codec);
if (v->bandwidth) }
av_dict_set(&st->metadata, "variant_bitrate", bitrate_str,
0); 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];
char bitrate_str[20];
AVProgram *program;
snprintf(bitrate_str, sizeof(bitrate_str), "%d", v->bandwidth);
program = av_new_program(s, i);
if (!program)
goto fail;
av_dict_set(&program->metadata, "variant_bitrate", bitrate_str, 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];
ff_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(&st->metadata, "variant_bitrate", bitrate_str, 0);
}
} }
stream_offset += v->ctx->nb_streams;
} }
c->first_packet = 1; c->first_packet = 1;
...@@ -627,6 +704,7 @@ static int hls_read_header(AVFormatContext *s) ...@@ -627,6 +704,7 @@ static int hls_read_header(AVFormatContext *s)
return 0; return 0;
fail: fail:
free_playlist_list(c);
free_variant_list(c); free_variant_list(c);
return ret; return ret;
} }
...@@ -637,30 +715,30 @@ static int recheck_discard_flags(AVFormatContext *s, int first) ...@@ -637,30 +715,30 @@ static int recheck_discard_flags(AVFormatContext *s, int first)
int i, changed = 0; int i, changed = 0;
/* Check if any new streams are needed */ /* Check if any new streams are needed */
for (i = 0; i < c->n_variants; i++) for (i = 0; i < c->n_playlists; i++)
c->variants[i]->cur_needed = 0; c->playlists[i]->cur_needed = 0;
for (i = 0; i < s->nb_streams; i++) { for (i = 0; i < s->nb_streams; i++) {
AVStream *st = s->streams[i]; AVStream *st = s->streams[i];
struct variant *var = c->variants[s->streams[i]->id]; struct playlist *pls = c->playlists[s->streams[i]->id];
if (st->discard < AVDISCARD_ALL) if (st->discard < AVDISCARD_ALL)
var->cur_needed = 1; pls->cur_needed = 1;
} }
for (i = 0; i < c->n_variants; i++) { for (i = 0; i < c->n_playlists; i++) {
struct variant *v = c->variants[i]; struct playlist *pls = c->playlists[i];
if (v->cur_needed && !v->needed) { if (pls->cur_needed && !pls->needed) {
v->needed = 1; pls->needed = 1;
changed = 1; changed = 1;
v->cur_seq_no = c->cur_seq_no; pls->cur_seq_no = c->cur_seq_no;
v->pb.eof_reached = 0; pls->pb.eof_reached = 0;
av_log(s, AV_LOG_INFO, "Now receiving variant %d\n", i); av_log(s, AV_LOG_INFO, "Now receiving playlist %d\n", i);
} else if (first && !v->cur_needed && v->needed) { } else if (first && !pls->cur_needed && pls->needed) {
if (v->input) if (pls->input)
ffurl_close(v->input); ffurl_close(pls->input);
v->input = NULL; pls->input = NULL;
v->needed = 0; pls->needed = 0;
changed = 1; changed = 1;
av_log(s, AV_LOG_INFO, "No longer receiving variant %d\n", i); av_log(s, AV_LOG_INFO, "No longer receiving playlist %d\n", i);
} }
} }
return changed; return changed;
...@@ -669,7 +747,7 @@ static int recheck_discard_flags(AVFormatContext *s, int first) ...@@ -669,7 +747,7 @@ static int recheck_discard_flags(AVFormatContext *s, int first)
static int hls_read_packet(AVFormatContext *s, AVPacket *pkt) static int hls_read_packet(AVFormatContext *s, AVPacket *pkt)
{ {
HLSContext *c = s->priv_data; HLSContext *c = s->priv_data;
int ret, i, minvariant = -1; int ret, i, minplaylist = -1;
if (c->first_packet) { if (c->first_packet) {
recheck_discard_flags(s, 1); recheck_discard_flags(s, 1);
...@@ -678,64 +756,64 @@ static int hls_read_packet(AVFormatContext *s, AVPacket *pkt) ...@@ -678,64 +756,64 @@ static int hls_read_packet(AVFormatContext *s, AVPacket *pkt)
start: start:
c->end_of_segment = 0; c->end_of_segment = 0;
for (i = 0; i < c->n_variants; i++) { for (i = 0; i < c->n_playlists; i++) {
struct variant *var = c->variants[i]; struct playlist *pls = c->playlists[i];
/* Make sure we've got one buffered packet from each open variant /* Make sure we've got one buffered packet from each open playlist
* stream */ * stream */
if (var->needed && !var->pkt.data) { if (pls->needed && !pls->pkt.data) {
while (1) { while (1) {
int64_t ts_diff; int64_t ts_diff;
AVStream *st; AVStream *st;
ret = av_read_frame(var->ctx, &var->pkt); ret = av_read_frame(pls->ctx, &pls->pkt);
if (ret < 0) { if (ret < 0) {
if (!url_feof(&var->pb) && ret != AVERROR_EOF) if (!url_feof(&pls->pb) && ret != AVERROR_EOF)
return ret; return ret;
reset_packet(&var->pkt); reset_packet(&pls->pkt);
break; break;
} else { } else {
if (c->first_timestamp == AV_NOPTS_VALUE && if (c->first_timestamp == AV_NOPTS_VALUE &&
var->pkt.dts != AV_NOPTS_VALUE) pls->pkt.dts != AV_NOPTS_VALUE)
c->first_timestamp = av_rescale_q(var->pkt.dts, c->first_timestamp = av_rescale_q(pls->pkt.dts,
var->ctx->streams[var->pkt.stream_index]->time_base, pls->ctx->streams[pls->pkt.stream_index]->time_base,
AV_TIME_BASE_Q); AV_TIME_BASE_Q);
} }
if (c->seek_timestamp == AV_NOPTS_VALUE) if (c->seek_timestamp == AV_NOPTS_VALUE)
break; break;
if (var->pkt.dts == AV_NOPTS_VALUE) { if (pls->pkt.dts == AV_NOPTS_VALUE) {
c->seek_timestamp = AV_NOPTS_VALUE; c->seek_timestamp = AV_NOPTS_VALUE;
break; break;
} }
st = var->ctx->streams[var->pkt.stream_index]; st = pls->ctx->streams[pls->pkt.stream_index];
ts_diff = av_rescale_rnd(var->pkt.dts, AV_TIME_BASE, ts_diff = av_rescale_rnd(pls->pkt.dts, AV_TIME_BASE,
st->time_base.den, AV_ROUND_DOWN) - st->time_base.den, AV_ROUND_DOWN) -
c->seek_timestamp; c->seek_timestamp;
if (ts_diff >= 0 && (c->seek_flags & AVSEEK_FLAG_ANY || if (ts_diff >= 0 && (c->seek_flags & AVSEEK_FLAG_ANY ||
var->pkt.flags & AV_PKT_FLAG_KEY)) { pls->pkt.flags & AV_PKT_FLAG_KEY)) {
c->seek_timestamp = AV_NOPTS_VALUE; c->seek_timestamp = AV_NOPTS_VALUE;
break; break;
} }
av_free_packet(&var->pkt); av_free_packet(&pls->pkt);
reset_packet(&var->pkt); reset_packet(&pls->pkt);
} }
} }
/* Check if this stream still is on an earlier segment number, or /* Check if this stream still is on an earlier segment number, or
* has the packet with the lowest dts */ * has the packet with the lowest dts */
if (var->pkt.data) { if (pls->pkt.data) {
struct variant *minvar = minvariant < 0 ? struct playlist *minpls = minplaylist < 0 ?
NULL : c->variants[minvariant]; NULL : c->playlists[minplaylist];
if (minvariant < 0 || var->cur_seq_no < minvar->cur_seq_no) { if (minplaylist < 0 || pls->cur_seq_no < minpls->cur_seq_no) {
minvariant = i; minplaylist = i;
} else if (var->cur_seq_no == minvar->cur_seq_no) { } else if (pls->cur_seq_no == minpls->cur_seq_no) {
int64_t dts = var->pkt.dts; int64_t dts = pls->pkt.dts;
int64_t mindts = minvar->pkt.dts; int64_t mindts = minpls->pkt.dts;
AVStream *st = var->ctx->streams[var->pkt.stream_index]; AVStream *st = pls->ctx->streams[pls->pkt.stream_index];
AVStream *minst = minvar->ctx->streams[minvar->pkt.stream_index]; AVStream *minst = minpls->ctx->streams[minpls->pkt.stream_index];
if (dts == AV_NOPTS_VALUE) { if (dts == AV_NOPTS_VALUE) {
minvariant = i; minplaylist = i;
} else if (mindts != AV_NOPTS_VALUE) { } else if (mindts != AV_NOPTS_VALUE) {
if (st->start_time != AV_NOPTS_VALUE) if (st->start_time != AV_NOPTS_VALUE)
dts -= st->start_time; dts -= st->start_time;
...@@ -744,7 +822,7 @@ start: ...@@ -744,7 +822,7 @@ start:
if (av_compare_ts(dts, st->time_base, if (av_compare_ts(dts, st->time_base,
mindts, minst->time_base) < 0) mindts, minst->time_base) < 0)
minvariant = i; minplaylist = i;
} }
} }
} }
...@@ -754,10 +832,10 @@ start: ...@@ -754,10 +832,10 @@ start:
goto start; goto start;
} }
/* If we got a packet, return it */ /* If we got a packet, return it */
if (minvariant >= 0) { if (minplaylist >= 0) {
*pkt = c->variants[minvariant]->pkt; *pkt = c->playlists[minplaylist]->pkt;
pkt->stream_index += c->variants[minvariant]->stream_offset; pkt->stream_index += c->playlists[minplaylist]->stream_offset;
reset_packet(&c->variants[minvariant]->pkt); reset_packet(&c->playlists[minplaylist]->pkt);
return 0; return 0;
} }
return AVERROR_EOF; return AVERROR_EOF;
...@@ -767,6 +845,7 @@ static int hls_close(AVFormatContext *s) ...@@ -767,6 +845,7 @@ static int hls_close(AVFormatContext *s)
{ {
HLSContext *c = s->priv_data; HLSContext *c = s->priv_data;
free_playlist_list(c);
free_variant_list(c); free_variant_list(c);
return 0; return 0;
} }
...@@ -777,7 +856,7 @@ static int hls_read_seek(AVFormatContext *s, int stream_index, ...@@ -777,7 +856,7 @@ static int hls_read_seek(AVFormatContext *s, int stream_index,
HLSContext *c = s->priv_data; HLSContext *c = s->priv_data;
int i, j, ret; int i, j, ret;
if ((flags & AVSEEK_FLAG_BYTE) || !c->variants[0]->finished) if ((flags & AVSEEK_FLAG_BYTE) || !c->variants[0]->playlists[0]->finished)
return AVERROR(ENOSYS); return AVERROR(ENOSYS);
c->seek_flags = flags; c->seek_flags = flags;
...@@ -796,32 +875,32 @@ static int hls_read_seek(AVFormatContext *s, int stream_index, ...@@ -796,32 +875,32 @@ static int hls_read_seek(AVFormatContext *s, int stream_index,
} }
ret = AVERROR(EIO); ret = AVERROR(EIO);
for (i = 0; i < c->n_variants; i++) { for (i = 0; i < c->n_playlists; i++) {
/* Reset reading */ /* Reset reading */
struct variant *var = c->variants[i]; struct playlist *pls = c->playlists[i];
int64_t pos = c->first_timestamp == AV_NOPTS_VALUE ? int64_t pos = c->first_timestamp == AV_NOPTS_VALUE ?
0 : c->first_timestamp; 0 : c->first_timestamp;
if (var->input) { if (pls->input) {
ffurl_close(var->input); ffurl_close(pls->input);
var->input = NULL; pls->input = NULL;
} }
av_free_packet(&var->pkt); av_free_packet(&pls->pkt);
reset_packet(&var->pkt); reset_packet(&pls->pkt);
var->pb.eof_reached = 0; pls->pb.eof_reached = 0;
/* Clear any buffered data */ /* Clear any buffered data */
var->pb.buf_end = var->pb.buf_ptr = var->pb.buffer; pls->pb.buf_end = pls->pb.buf_ptr = pls->pb.buffer;
/* Reset the pos, to let the mpegts demuxer know we've seeked. */ /* Reset the pos, to let the mpegts demuxer know we've seeked. */
var->pb.pos = 0; pls->pb.pos = 0;
/* Locate the segment that contains the target timestamp */ /* Locate the segment that contains the target timestamp */
for (j = 0; j < var->n_segments; j++) { for (j = 0; j < pls->n_segments; j++) {
if (timestamp >= pos && if (timestamp >= pos &&
timestamp < pos + var->segments[j]->duration) { timestamp < pos + pls->segments[j]->duration) {
var->cur_seq_no = var->start_seq_no + j; pls->cur_seq_no = pls->start_seq_no + j;
ret = 0; ret = 0;
break; break;
} }
pos += var->segments[j]->duration; pos += pls->segments[j]->duration;
} }
if (ret) if (ret)
c->seek_timestamp = AV_NOPTS_VALUE; c->seek_timestamp = AV_NOPTS_VALUE;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment