mirror of
https://github.com/FFmpeg/FFmpeg.git
synced 2024-12-23 12:43:46 +02:00
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: Anssi Hannula <anssi.hannula@iki.fi>
This commit is contained in:
parent
857841c1b6
commit
9371d70bad
@ -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
|
||||
* containing the next packet from this stream.
|
||||
*/
|
||||
struct variant {
|
||||
int bandwidth;
|
||||
struct playlist {
|
||||
char url[MAX_URL_SIZE];
|
||||
AVIOContext pb;
|
||||
uint8_t* read_buffer;
|
||||
@ -93,9 +92,18 @@ struct variant {
|
||||
uint8_t key[16];
|
||||
};
|
||||
|
||||
struct variant {
|
||||
int bandwidth;
|
||||
int n_playlists;
|
||||
struct playlist **playlists;
|
||||
};
|
||||
|
||||
typedef struct HLSContext {
|
||||
int n_variants;
|
||||
struct variant **variants;
|
||||
int n_playlists;
|
||||
struct playlist **playlists;
|
||||
|
||||
int cur_seq_no;
|
||||
int end_of_segment;
|
||||
int first_packet;
|
||||
@ -116,13 +124,35 @@ static int read_chomp_line(AVIOContext *s, char *buf, int maxlen)
|
||||
return len;
|
||||
}
|
||||
|
||||
static void free_segment_list(struct variant *var)
|
||||
static void free_segment_list(struct playlist *pls)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < var->n_segments; i++)
|
||||
av_free(var->segments[i]);
|
||||
av_freep(&var->segments);
|
||||
var->n_segments = 0;
|
||||
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;
|
||||
for (i = 0; i < c->n_playlists; i++) {
|
||||
struct playlist *pls = c->playlists[i];
|
||||
free_segment_list(pls);
|
||||
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)
|
||||
@ -130,20 +160,10 @@ static void free_variant_list(HLSContext *c)
|
||||
int i;
|
||||
for (i = 0; i < c->n_variants; i++) {
|
||||
struct variant *var = c->variants[i];
|
||||
free_segment_list(var);
|
||||
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_freep(&var->playlists);
|
||||
av_free(var);
|
||||
}
|
||||
av_freep(&c->variants);
|
||||
av_freep(&c->cookies);
|
||||
av_freep(&c->user_agent);
|
||||
c->n_variants = 0;
|
||||
}
|
||||
|
||||
@ -157,16 +177,35 @@ static void reset_packet(AVPacket *pkt)
|
||||
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,
|
||||
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)
|
||||
return NULL;
|
||||
reset_packet(&var->pkt);
|
||||
|
||||
var->bandwidth = bandwidth;
|
||||
ff_make_absolute_url(var->url, sizeof(var->url), base, url);
|
||||
dynarray_add(&c->variants, &c->n_variants, var);
|
||||
dynarray_add(&var->playlists, &var->n_playlists, pls);
|
||||
return var;
|
||||
}
|
||||
|
||||
@ -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,
|
||||
struct variant *var, AVIOContext *in)
|
||||
struct playlist *pls, AVIOContext *in)
|
||||
{
|
||||
int ret = 0, is_segment = 0, is_variant = 0, bandwidth = 0;
|
||||
int64_t duration = 0;
|
||||
@ -245,9 +284,9 @@ static int parse_playlist(HLSContext *c, const char *url,
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (var) {
|
||||
free_segment_list(var);
|
||||
var->finished = 0;
|
||||
if (pls) {
|
||||
free_segment_list(pls);
|
||||
pls->finished = 0;
|
||||
}
|
||||
while (!url_feof(in)) {
|
||||
read_chomp_line(in, line, sizeof(line));
|
||||
@ -271,26 +310,26 @@ static int parse_playlist(HLSContext *c, const char *url,
|
||||
}
|
||||
av_strlcpy(key, info.uri, sizeof(key));
|
||||
} else if (av_strstart(line, "#EXT-X-TARGETDURATION:", &ptr)) {
|
||||
if (!var) {
|
||||
var = new_variant(c, 0, url, NULL);
|
||||
if (!var) {
|
||||
if (!pls) {
|
||||
if (!new_variant(c, 0, url, NULL)) {
|
||||
ret = AVERROR(ENOMEM);
|
||||
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)) {
|
||||
if (!var) {
|
||||
var = new_variant(c, 0, url, NULL);
|
||||
if (!var) {
|
||||
if (!pls) {
|
||||
if (!new_variant(c, 0, url, NULL)) {
|
||||
ret = AVERROR(ENOMEM);
|
||||
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)) {
|
||||
if (var)
|
||||
var->finished = 1;
|
||||
if (pls)
|
||||
pls->finished = 1;
|
||||
} else if (av_strstart(line, "#EXTINF:", &ptr)) {
|
||||
is_segment = 1;
|
||||
duration = atof(ptr) * AV_TIME_BASE;
|
||||
@ -307,12 +346,12 @@ static int parse_playlist(HLSContext *c, const char *url,
|
||||
}
|
||||
if (is_segment) {
|
||||
struct segment *seg;
|
||||
if (!var) {
|
||||
var = new_variant(c, 0, url, NULL);
|
||||
if (!var) {
|
||||
if (!pls) {
|
||||
if (!new_variant(c, 0, url, NULL)) {
|
||||
ret = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
pls = c->playlists[c->n_playlists - 1];
|
||||
}
|
||||
seg = av_malloc(sizeof(struct segment));
|
||||
if (!seg) {
|
||||
@ -324,19 +363,19 @@ static int parse_playlist(HLSContext *c, const char *url,
|
||||
if (has_iv) {
|
||||
memcpy(seg->iv, iv, sizeof(iv));
|
||||
} 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));
|
||||
AV_WB32(seg->iv + 12, seq);
|
||||
}
|
||||
ff_make_absolute_url(seg->key, sizeof(seg->key), url, key);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (var)
|
||||
var->last_load_time = av_gettime();
|
||||
if (pls)
|
||||
pls->last_load_time = av_gettime();
|
||||
|
||||
fail:
|
||||
av_free(new_url);
|
||||
@ -345,11 +384,11 @@ fail:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int open_input(HLSContext *c, struct variant *var)
|
||||
static int open_input(HLSContext *c, struct playlist *pls)
|
||||
{
|
||||
AVDictionary *opts = NULL;
|
||||
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
|
||||
av_dict_set(&opts, "user-agent", c->user_agent, 0);
|
||||
@ -358,17 +397,17 @@ static int open_input(HLSContext *c, struct variant *var)
|
||||
av_dict_set(&opts, "seekable", "0", 0);
|
||||
|
||||
if (seg->key_type == KEY_NONE) {
|
||||
ret = ffurl_open(&var->input, seg->url, AVIO_FLAG_READ,
|
||||
&var->parent->interrupt_callback, &opts);
|
||||
ret = ffurl_open(&pls->input, seg->url, AVIO_FLAG_READ,
|
||||
&pls->parent->interrupt_callback, &opts);
|
||||
goto cleanup;
|
||||
} else if (seg->key_type == KEY_AES_128) {
|
||||
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;
|
||||
if (ffurl_open(&uc, seg->key, AVIO_FLAG_READ,
|
||||
&var->parent->interrupt_callback, &opts) == 0) {
|
||||
if (ffurl_read_complete(uc, var->key, sizeof(var->key))
|
||||
!= sizeof(var->key)) {
|
||||
&pls->parent->interrupt_callback, &opts) == 0) {
|
||||
if (ffurl_read_complete(uc, pls->key, sizeof(pls->key))
|
||||
!= sizeof(pls->key)) {
|
||||
av_log(NULL, AV_LOG_ERROR, "Unable to read key file %s\n",
|
||||
seg->key);
|
||||
}
|
||||
@ -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",
|
||||
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(key, var->key, sizeof(var->key), 0);
|
||||
ff_data_to_hex(key, pls->key, sizeof(pls->key), 0);
|
||||
iv[32] = key[32] = '\0';
|
||||
if (strstr(seg->url, "://"))
|
||||
snprintf(url, sizeof(url), "crypto+%s", seg->url);
|
||||
else
|
||||
snprintf(url, sizeof(url), "crypto:%s", seg->url);
|
||||
if ((ret = ffurl_alloc(&var->input, url, AVIO_FLAG_READ,
|
||||
&var->parent->interrupt_callback)) < 0)
|
||||
if ((ret = ffurl_alloc(&pls->input, url, AVIO_FLAG_READ,
|
||||
&pls->parent->interrupt_callback)) < 0)
|
||||
goto cleanup;
|
||||
av_opt_set(var->input->priv_data, "key", key, 0);
|
||||
av_opt_set(var->input->priv_data, "iv", iv, 0);
|
||||
av_opt_set(pls->input->priv_data, "key", key, 0);
|
||||
av_opt_set(pls->input->priv_data, "iv", iv, 0);
|
||||
/* Need to repopulate options */
|
||||
av_dict_free(&opts);
|
||||
av_dict_set(&opts, "seekable", "0", 0);
|
||||
if ((ret = ffurl_connect(var->input, &opts)) < 0) {
|
||||
ffurl_close(var->input);
|
||||
var->input = NULL;
|
||||
if ((ret = ffurl_connect(pls->input, &opts)) < 0) {
|
||||
ffurl_close(pls->input);
|
||||
pls->input = NULL;
|
||||
goto cleanup;
|
||||
}
|
||||
ret = 0;
|
||||
@ -411,14 +450,14 @@ cleanup:
|
||||
|
||||
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;
|
||||
int ret, i;
|
||||
|
||||
restart:
|
||||
if (!v->input) {
|
||||
/* 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 ?
|
||||
v->segments[v->n_segments - 1]->duration :
|
||||
v->target_duration;
|
||||
@ -475,13 +514,32 @@ reload:
|
||||
}
|
||||
}
|
||||
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);
|
||||
return AVERROR_EOF;
|
||||
}
|
||||
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)
|
||||
{
|
||||
URLContext *u = (s->flags & AVFMT_FLAG_CUSTOM_IO) ? NULL : s->pb->opaque;
|
||||
@ -519,17 +577,17 @@ static int hls_read_header(AVFormatContext *s)
|
||||
ret = AVERROR_EOF;
|
||||
goto fail;
|
||||
}
|
||||
/* If the playlist only contained variants, parse each individual
|
||||
* variant playlist. */
|
||||
if (c->n_variants > 1 || c->variants[0]->n_segments == 0) {
|
||||
for (i = 0; i < c->n_variants; i++) {
|
||||
struct variant *v = c->variants[i];
|
||||
if ((ret = parse_playlist(c, v->url, v, NULL)) < 0)
|
||||
/* If the playlist only contained playlists (Master Playlist),
|
||||
* parse each individual playlist. */
|
||||
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)
|
||||
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");
|
||||
ret = AVERROR_EOF;
|
||||
goto fail;
|
||||
@ -537,64 +595,85 @@ static int hls_read_header(AVFormatContext *s)
|
||||
|
||||
/* If this isn't a live stream, calculate the total duration of the
|
||||
* stream. */
|
||||
if (c->variants[0]->finished) {
|
||||
if (c->variants[0]->playlists[0]->finished) {
|
||||
int64_t duration = 0;
|
||||
for (i = 0; i < c->variants[0]->n_segments; i++)
|
||||
duration += c->variants[0]->segments[i]->duration;
|
||||
for (i = 0; i < c->variants[0]->playlists[0]->n_segments; i++)
|
||||
duration += c->variants[0]->playlists[0]->segments[i]->duration;
|
||||
s->duration = duration;
|
||||
}
|
||||
|
||||
/* Open the demuxer for each variant */
|
||||
for (i = 0; i < c->n_variants; i++) {
|
||||
struct variant *v = c->variants[i];
|
||||
/* Open the demuxer for each playlist */
|
||||
for (i = 0; i < c->n_playlists; i++) {
|
||||
struct playlist *pls = c->playlists[i];
|
||||
AVInputFormat *in_fmt = NULL;
|
||||
char bitrate_str[20];
|
||||
AVProgram *program;
|
||||
|
||||
if (v->n_segments == 0)
|
||||
if (pls->n_segments == 0)
|
||||
continue;
|
||||
|
||||
if (!(v->ctx = avformat_alloc_context())) {
|
||||
if (!(pls->ctx = avformat_alloc_context())) {
|
||||
ret = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
v->index = i;
|
||||
v->needed = 1;
|
||||
v->parent = s;
|
||||
pls->index = i;
|
||||
pls->needed = 1;
|
||||
pls->parent = s;
|
||||
|
||||
/* If this is a live stream with more than 3 segments, start at the
|
||||
* third last segment. */
|
||||
v->cur_seq_no = v->start_seq_no;
|
||||
if (!v->finished && v->n_segments > 3)
|
||||
v->cur_seq_no = v->start_seq_no + v->n_segments - 3;
|
||||
pls->cur_seq_no = pls->start_seq_no;
|
||||
if (!pls->finished && pls->n_segments > 3)
|
||||
pls->cur_seq_no = pls->start_seq_no + pls->n_segments - 3;
|
||||
|
||||
v->read_buffer = av_malloc(INITIAL_BUFFER_SIZE);
|
||||
ffio_init_context(&v->pb, v->read_buffer, INITIAL_BUFFER_SIZE, 0, v,
|
||||
pls->read_buffer = av_malloc(INITIAL_BUFFER_SIZE);
|
||||
ffio_init_context(&pls->pb, pls->read_buffer, INITIAL_BUFFER_SIZE, 0, pls,
|
||||
read_data, NULL, NULL);
|
||||
v->pb.seekable = 0;
|
||||
ret = av_probe_input_buffer(&v->pb, &in_fmt, v->segments[0]->url,
|
||||
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", v->segments[0]->url);
|
||||
avformat_free_context(v->ctx);
|
||||
v->ctx = NULL;
|
||||
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;
|
||||
}
|
||||
v->ctx->pb = &v->pb;
|
||||
v->stream_offset = stream_offset;
|
||||
ret = avformat_open_input(&v->ctx, v->segments[0]->url, in_fmt, NULL);
|
||||
pls->ctx->pb = &pls->pb;
|
||||
pls->stream_offset = stream_offset;
|
||||
ret = avformat_open_input(&pls->ctx, pls->segments[0]->url, in_fmt, NULL);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
v->ctx->ctx_flags &= ~AVFMTCTX_NOHEADER;
|
||||
ret = avformat_find_stream_info(v->ctx, NULL);
|
||||
pls->ctx->ctx_flags &= ~AVFMTCTX_NOHEADER;
|
||||
ret = avformat_find_stream_info(pls->ctx, NULL);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
/* 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;
|
||||
avpriv_set_pts_info(st, ist->pts_wrap_bits, ist->time_base.num, ist->time_base.den);
|
||||
avcodec_copy_context(st->codec, pls->ctx->streams[j]->codec);
|
||||
}
|
||||
|
||||
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);
|
||||
@ -602,23 +681,21 @@ static int hls_read_header(AVFormatContext *s)
|
||||
goto fail;
|
||||
av_dict_set(&program->metadata, "variant_bitrate", bitrate_str, 0);
|
||||
|
||||
/* Create new AVStreams for each stream in this variant */
|
||||
for (j = 0; j < v->ctx->nb_streams; j++) {
|
||||
AVStream *st = avformat_new_stream(s, NULL);
|
||||
AVStream *ist = v->ctx->streams[j];
|
||||
if (!st) {
|
||||
ret = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
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);
|
||||
}
|
||||
ff_program_add_stream_index(s, i, stream_offset + j);
|
||||
st->id = i;
|
||||
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);
|
||||
if (v->bandwidth)
|
||||
av_dict_set(&st->metadata, "variant_bitrate", bitrate_str,
|
||||
0);
|
||||
}
|
||||
stream_offset += v->ctx->nb_streams;
|
||||
}
|
||||
|
||||
c->first_packet = 1;
|
||||
@ -627,6 +704,7 @@ static int hls_read_header(AVFormatContext *s)
|
||||
|
||||
return 0;
|
||||
fail:
|
||||
free_playlist_list(c);
|
||||
free_variant_list(c);
|
||||
return ret;
|
||||
}
|
||||
@ -637,30 +715,30 @@ static int recheck_discard_flags(AVFormatContext *s, int first)
|
||||
int i, changed = 0;
|
||||
|
||||
/* Check if any new streams are needed */
|
||||
for (i = 0; i < c->n_variants; i++)
|
||||
c->variants[i]->cur_needed = 0;
|
||||
for (i = 0; i < c->n_playlists; i++)
|
||||
c->playlists[i]->cur_needed = 0;
|
||||
|
||||
for (i = 0; i < s->nb_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)
|
||||
var->cur_needed = 1;
|
||||
pls->cur_needed = 1;
|
||||
}
|
||||
for (i = 0; i < c->n_variants; i++) {
|
||||
struct variant *v = c->variants[i];
|
||||
if (v->cur_needed && !v->needed) {
|
||||
v->needed = 1;
|
||||
for (i = 0; i < c->n_playlists; i++) {
|
||||
struct playlist *pls = c->playlists[i];
|
||||
if (pls->cur_needed && !pls->needed) {
|
||||
pls->needed = 1;
|
||||
changed = 1;
|
||||
v->cur_seq_no = c->cur_seq_no;
|
||||
v->pb.eof_reached = 0;
|
||||
av_log(s, AV_LOG_INFO, "Now receiving variant %d\n", i);
|
||||
} else if (first && !v->cur_needed && v->needed) {
|
||||
if (v->input)
|
||||
ffurl_close(v->input);
|
||||
v->input = NULL;
|
||||
v->needed = 0;
|
||||
pls->cur_seq_no = c->cur_seq_no;
|
||||
pls->pb.eof_reached = 0;
|
||||
av_log(s, AV_LOG_INFO, "Now receiving playlist %d\n", i);
|
||||
} else if (first && !pls->cur_needed && pls->needed) {
|
||||
if (pls->input)
|
||||
ffurl_close(pls->input);
|
||||
pls->input = NULL;
|
||||
pls->needed = 0;
|
||||
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;
|
||||
@ -669,7 +747,7 @@ static int recheck_discard_flags(AVFormatContext *s, int first)
|
||||
static int hls_read_packet(AVFormatContext *s, AVPacket *pkt)
|
||||
{
|
||||
HLSContext *c = s->priv_data;
|
||||
int ret, i, minvariant = -1;
|
||||
int ret, i, minplaylist = -1;
|
||||
|
||||
if (c->first_packet) {
|
||||
recheck_discard_flags(s, 1);
|
||||
@ -678,64 +756,64 @@ static int hls_read_packet(AVFormatContext *s, AVPacket *pkt)
|
||||
|
||||
start:
|
||||
c->end_of_segment = 0;
|
||||
for (i = 0; i < c->n_variants; i++) {
|
||||
struct variant *var = c->variants[i];
|
||||
/* Make sure we've got one buffered packet from each open variant
|
||||
for (i = 0; i < c->n_playlists; i++) {
|
||||
struct playlist *pls = c->playlists[i];
|
||||
/* Make sure we've got one buffered packet from each open playlist
|
||||
* stream */
|
||||
if (var->needed && !var->pkt.data) {
|
||||
if (pls->needed && !pls->pkt.data) {
|
||||
while (1) {
|
||||
int64_t ts_diff;
|
||||
AVStream *st;
|
||||
ret = av_read_frame(var->ctx, &var->pkt);
|
||||
ret = av_read_frame(pls->ctx, &pls->pkt);
|
||||
if (ret < 0) {
|
||||
if (!url_feof(&var->pb) && ret != AVERROR_EOF)
|
||||
if (!url_feof(&pls->pb) && ret != AVERROR_EOF)
|
||||
return ret;
|
||||
reset_packet(&var->pkt);
|
||||
reset_packet(&pls->pkt);
|
||||
break;
|
||||
} else {
|
||||
if (c->first_timestamp == AV_NOPTS_VALUE &&
|
||||
var->pkt.dts != AV_NOPTS_VALUE)
|
||||
c->first_timestamp = av_rescale_q(var->pkt.dts,
|
||||
var->ctx->streams[var->pkt.stream_index]->time_base,
|
||||
pls->pkt.dts != AV_NOPTS_VALUE)
|
||||
c->first_timestamp = av_rescale_q(pls->pkt.dts,
|
||||
pls->ctx->streams[pls->pkt.stream_index]->time_base,
|
||||
AV_TIME_BASE_Q);
|
||||
}
|
||||
|
||||
if (c->seek_timestamp == AV_NOPTS_VALUE)
|
||||
break;
|
||||
|
||||
if (var->pkt.dts == AV_NOPTS_VALUE) {
|
||||
if (pls->pkt.dts == AV_NOPTS_VALUE) {
|
||||
c->seek_timestamp = AV_NOPTS_VALUE;
|
||||
break;
|
||||
}
|
||||
|
||||
st = var->ctx->streams[var->pkt.stream_index];
|
||||
ts_diff = av_rescale_rnd(var->pkt.dts, AV_TIME_BASE,
|
||||
st = pls->ctx->streams[pls->pkt.stream_index];
|
||||
ts_diff = av_rescale_rnd(pls->pkt.dts, AV_TIME_BASE,
|
||||
st->time_base.den, AV_ROUND_DOWN) -
|
||||
c->seek_timestamp;
|
||||
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;
|
||||
break;
|
||||
}
|
||||
av_free_packet(&var->pkt);
|
||||
reset_packet(&var->pkt);
|
||||
av_free_packet(&pls->pkt);
|
||||
reset_packet(&pls->pkt);
|
||||
}
|
||||
}
|
||||
/* Check if this stream still is on an earlier segment number, or
|
||||
* has the packet with the lowest dts */
|
||||
if (var->pkt.data) {
|
||||
struct variant *minvar = minvariant < 0 ?
|
||||
NULL : c->variants[minvariant];
|
||||
if (minvariant < 0 || var->cur_seq_no < minvar->cur_seq_no) {
|
||||
minvariant = i;
|
||||
} else if (var->cur_seq_no == minvar->cur_seq_no) {
|
||||
int64_t dts = var->pkt.dts;
|
||||
int64_t mindts = minvar->pkt.dts;
|
||||
AVStream *st = var->ctx->streams[var->pkt.stream_index];
|
||||
AVStream *minst = minvar->ctx->streams[minvar->pkt.stream_index];
|
||||
if (pls->pkt.data) {
|
||||
struct playlist *minpls = minplaylist < 0 ?
|
||||
NULL : c->playlists[minplaylist];
|
||||
if (minplaylist < 0 || pls->cur_seq_no < minpls->cur_seq_no) {
|
||||
minplaylist = i;
|
||||
} else if (pls->cur_seq_no == minpls->cur_seq_no) {
|
||||
int64_t dts = pls->pkt.dts;
|
||||
int64_t mindts = minpls->pkt.dts;
|
||||
AVStream *st = pls->ctx->streams[pls->pkt.stream_index];
|
||||
AVStream *minst = minpls->ctx->streams[minpls->pkt.stream_index];
|
||||
|
||||
if (dts == AV_NOPTS_VALUE) {
|
||||
minvariant = i;
|
||||
minplaylist = i;
|
||||
} else if (mindts != AV_NOPTS_VALUE) {
|
||||
if (st->start_time != AV_NOPTS_VALUE)
|
||||
dts -= st->start_time;
|
||||
@ -744,7 +822,7 @@ start:
|
||||
|
||||
if (av_compare_ts(dts, st->time_base,
|
||||
mindts, minst->time_base) < 0)
|
||||
minvariant = i;
|
||||
minplaylist = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -754,10 +832,10 @@ start:
|
||||
goto start;
|
||||
}
|
||||
/* If we got a packet, return it */
|
||||
if (minvariant >= 0) {
|
||||
*pkt = c->variants[minvariant]->pkt;
|
||||
pkt->stream_index += c->variants[minvariant]->stream_offset;
|
||||
reset_packet(&c->variants[minvariant]->pkt);
|
||||
if (minplaylist >= 0) {
|
||||
*pkt = c->playlists[minplaylist]->pkt;
|
||||
pkt->stream_index += c->playlists[minplaylist]->stream_offset;
|
||||
reset_packet(&c->playlists[minplaylist]->pkt);
|
||||
return 0;
|
||||
}
|
||||
return AVERROR_EOF;
|
||||
@ -767,6 +845,7 @@ static int hls_close(AVFormatContext *s)
|
||||
{
|
||||
HLSContext *c = s->priv_data;
|
||||
|
||||
free_playlist_list(c);
|
||||
free_variant_list(c);
|
||||
return 0;
|
||||
}
|
||||
@ -777,7 +856,7 @@ static int hls_read_seek(AVFormatContext *s, int stream_index,
|
||||
HLSContext *c = s->priv_data;
|
||||
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);
|
||||
|
||||
c->seek_flags = flags;
|
||||
@ -796,32 +875,32 @@ static int hls_read_seek(AVFormatContext *s, int stream_index,
|
||||
}
|
||||
|
||||
ret = AVERROR(EIO);
|
||||
for (i = 0; i < c->n_variants; i++) {
|
||||
for (i = 0; i < c->n_playlists; i++) {
|
||||
/* Reset reading */
|
||||
struct variant *var = c->variants[i];
|
||||
struct playlist *pls = c->playlists[i];
|
||||
int64_t pos = c->first_timestamp == AV_NOPTS_VALUE ?
|
||||
0 : c->first_timestamp;
|
||||
if (var->input) {
|
||||
ffurl_close(var->input);
|
||||
var->input = NULL;
|
||||
if (pls->input) {
|
||||
ffurl_close(pls->input);
|
||||
pls->input = NULL;
|
||||
}
|
||||
av_free_packet(&var->pkt);
|
||||
reset_packet(&var->pkt);
|
||||
var->pb.eof_reached = 0;
|
||||
av_free_packet(&pls->pkt);
|
||||
reset_packet(&pls->pkt);
|
||||
pls->pb.eof_reached = 0;
|
||||
/* 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. */
|
||||
var->pb.pos = 0;
|
||||
pls->pb.pos = 0;
|
||||
|
||||
/* 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 &&
|
||||
timestamp < pos + var->segments[j]->duration) {
|
||||
var->cur_seq_no = var->start_seq_no + j;
|
||||
timestamp < pos + pls->segments[j]->duration) {
|
||||
pls->cur_seq_no = pls->start_seq_no + j;
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
pos += var->segments[j]->duration;
|
||||
pos += pls->segments[j]->duration;
|
||||
}
|
||||
if (ret)
|
||||
c->seek_timestamp = AV_NOPTS_VALUE;
|
||||
|
Loading…
Reference in New Issue
Block a user