#include "libavutil/mathematics.h"
#include "libavutil/opt.h"
#include "libavutil/dict.h"
+#include "libavutil/time.h"
#include "avformat.h"
#include "internal.h"
-#include <unistd.h>
#include "avio_internal.h"
#include "url.h"
* An apple http stream consists of a playlist with media segment files,
* played sequentially. There may be several playlists with the same
* video content, in different bandwidth variants, that are played in
- * parallel (preferrably only one bandwidth variant at a time). In this case,
+ * parallel (preferably only one bandwidth variant at a time). In this case,
* the user supplied the url to a main playlist that only lists the variant
* playlists.
*
};
struct segment {
- int duration;
+ int64_t duration;
char url[MAX_URL_SIZE];
char key[MAX_URL_SIZE];
enum KeyType key_type;
int stream_offset;
int finished;
- int target_duration;
+ int64_t target_duration;
int start_seq_no;
int n_segments;
struct segment **segments;
int end_of_segment;
int first_packet;
int64_t first_timestamp;
+ int64_t seek_timestamp;
+ int seek_flags;
AVIOInterruptCB *interrupt_callback;
+ AVDictionary *avio_opts;
} HLSContext;
static int read_chomp_line(AVIOContext *s, char *buf, int maxlen)
{
int len = ff_get_line(s, buf, maxlen);
- while (len > 0 && isspace(buf[len - 1]))
+ while (len > 0 && av_isspace(buf[len - 1]))
buf[--len] = '\0';
return len;
}
}
}
+static int open_in(HLSContext *c, AVIOContext **in, const char *url)
+{
+ AVDictionary *tmp = NULL;
+ int ret;
+
+ av_dict_copy(&tmp, c->avio_opts, 0);
+
+ ret = avio_open2(in, url, AVIO_FLAG_READ, c->interrupt_callback, &tmp);
+
+ av_dict_free(&tmp);
+ return ret;
+}
+
+static int url_connect(struct variant *var, AVDictionary *opts)
+{
+ AVDictionary *tmp = NULL;
+ int ret;
+
+ av_dict_copy(&tmp, opts, 0);
+
+ av_opt_set_dict(var->input, &tmp);
+
+ if ((ret = ffurl_connect(var->input, NULL)) < 0) {
+ ffurl_close(var->input);
+ var->input = NULL;
+ }
+
+ av_dict_free(&tmp);
+ return ret;
+}
+
+static int open_url(HLSContext *c, URLContext **uc, const char *url)
+{
+ AVDictionary *tmp = NULL;
+ int ret;
+
+ av_dict_copy(&tmp, c->avio_opts, 0);
+
+ ret = ffurl_open(uc, url, AVIO_FLAG_READ, c->interrupt_callback, &tmp);
+
+ av_dict_free(&tmp);
+
+ return ret;
+}
+
static int parse_playlist(HLSContext *c, const char *url,
struct variant *var, AVIOContext *in)
{
- int ret = 0, duration = 0, is_segment = 0, is_variant = 0, bandwidth = 0;
+ int ret = 0, is_segment = 0, is_variant = 0, bandwidth = 0;
+ int64_t duration = 0;
enum KeyType key_type = KEY_NONE;
uint8_t iv[16] = "";
int has_iv = 0;
char line[1024];
const char *ptr;
int close_in = 0;
+ uint8_t *new_url = NULL;
if (!in) {
- close_in = 1;
- if ((ret = avio_open2(&in, url, AVIO_FLAG_READ,
- c->interrupt_callback, NULL)) < 0)
+ ret = open_in(c, &in, url);
+ if (ret < 0)
return ret;
+ close_in = 1;
}
+ if (av_opt_get(in, "location", AV_OPT_SEARCH_CHILDREN, &new_url) >= 0)
+ url = new_url;
+
read_chomp_line(in, line, sizeof(line));
if (strcmp(line, "#EXTM3U")) {
ret = AVERROR_INVALIDDATA;
goto fail;
}
}
- var->target_duration = atoi(ptr);
+ var->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);
var->finished = 1;
} else if (av_strstart(line, "#EXTINF:", &ptr)) {
is_segment = 1;
- duration = atoi(ptr);
+ duration = atof(ptr) * AV_TIME_BASE;
} else if (av_strstart(line, "#", NULL)) {
continue;
} else if (line[0]) {
}
}
if (var)
- var->last_load_time = av_gettime();
+ var->last_load_time = av_gettime_relative();
fail:
+ av_free(new_url);
if (close_in)
avio_close(in);
return ret;
{
struct segment *seg = var->segments[var->cur_seq_no - var->start_seq_no];
if (seg->key_type == KEY_NONE) {
- return ffurl_open(&var->input, seg->url, AVIO_FLAG_READ,
- &var->parent->interrupt_callback, NULL);
+ return open_url(var->parent->priv_data, &var->input, seg->url);
} else if (seg->key_type == KEY_AES_128) {
+ HLSContext *c = var->parent->priv_data;
char iv[33], key[33], url[MAX_URL_SIZE];
int ret;
if (strcmp(seg->key, var->key_url)) {
URLContext *uc;
- if (ffurl_open(&uc, seg->key, AVIO_FLAG_READ,
- &var->parent->interrupt_callback, NULL) == 0) {
+ if (open_url(var->parent->priv_data, &uc, seg->key) == 0) {
if (ffurl_read_complete(uc, var->key, sizeof(var->key))
!= sizeof(var->key)) {
av_log(NULL, AV_LOG_ERROR, "Unable to read key file %s\n",
return ret;
av_opt_set(var->input->priv_data, "key", key, 0);
av_opt_set(var->input->priv_data, "iv", iv, 0);
- if ((ret = ffurl_connect(var->input, NULL)) < 0) {
- ffurl_close(var->input);
- var->input = NULL;
- return ret;
- }
- return 0;
+
+ return url_connect(var, c->avio_opts);
}
return AVERROR(ENOSYS);
}
int64_t reload_interval = v->n_segments > 0 ?
v->segments[v->n_segments - 1]->duration :
v->target_duration;
- reload_interval *= 1000000;
reload:
if (!v->finished &&
- av_gettime() - v->last_load_time >= reload_interval) {
+ av_gettime_relative() - v->last_load_time >= reload_interval) {
if ((ret = parse_playlist(c, v->url, v, NULL)) < 0)
return ret;
/* If we need to reload the playlist again below (if
* there's still no more segments), switch to a reload
* interval of half the target duration. */
- reload_interval = v->target_duration * 500000;
+ reload_interval = v->target_duration / 2;
}
if (v->cur_seq_no < v->start_seq_no) {
av_log(NULL, AV_LOG_WARNING,
if (v->cur_seq_no >= v->start_seq_no + v->n_segments) {
if (v->finished)
return AVERROR_EOF;
- while (av_gettime() - v->last_load_time < reload_interval) {
+ while (av_gettime_relative() - v->last_load_time < reload_interval) {
if (ff_check_interrupt(c->interrupt_callback))
return AVERROR_EXIT;
- usleep(100*1000);
+ av_usleep(100*1000);
}
/* Enough time has elapsed since the last reload */
goto reload;
ret = ffurl_read(v->input, buf, buf_size);
if (ret > 0)
return ret;
- if (ret < 0 && ret != AVERROR_EOF)
- return ret;
ffurl_close(v->input);
v->input = NULL;
v->cur_seq_no++;
c->end_of_segment = 1;
c->cur_seq_no = v->cur_seq_no;
- if (v->ctx && v->ctx->nb_streams) {
+ if (v->ctx && v->ctx->nb_streams &&
+ v->parent->nb_streams >= v->stream_offset + v->ctx->nb_streams) {
v->needed = 0;
for (i = v->stream_offset; i < v->stream_offset + v->ctx->nb_streams;
i++) {
goto restart;
}
+static int save_avio_options(AVFormatContext *s)
+{
+ HLSContext *c = s->priv_data;
+ const char *opts[] = { "headers", "user_agent", NULL }, **opt = opts;
+ uint8_t *buf;
+ int ret = 0;
+
+ while (*opt) {
+ if (av_opt_get(s->pb, *opt, AV_OPT_SEARCH_CHILDREN, &buf) >= 0) {
+ ret = av_dict_set(&c->avio_opts, *opt, buf,
+ AV_DICT_DONT_STRDUP_VAL);
+ if (ret < 0)
+ return ret;
+ }
+ opt++;
+ }
+
+ return ret;
+}
+
static int hls_read_header(AVFormatContext *s)
{
HLSContext *c = s->priv_data;
if ((ret = parse_playlist(c, s->filename, NULL, s->pb)) < 0)
goto fail;
+ if ((ret = save_avio_options(s)) < 0)
+ goto fail;
+
if (c->n_variants == 0) {
av_log(NULL, AV_LOG_WARNING, "Empty playlist\n");
ret = AVERROR_EOF;
int64_t duration = 0;
for (i = 0; i < c->variants[0]->n_segments; i++)
duration += c->variants[0]->segments[i]->duration;
- s->duration = duration * AV_TIME_BASE;
+ s->duration = duration;
}
/* Open the demuxer for each variant */
struct variant *v = c->variants[i];
AVInputFormat *in_fmt = NULL;
char bitrate_str[20];
+ AVProgram *program;
+
if (v->n_segments == 0)
continue;
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);
if (ret < 0)
goto fail;
- v->stream_offset = stream_offset;
+
+ v->ctx->ctx_flags &= ~AVFMTCTX_NOHEADER;
+ ret = avformat_find_stream_info(v->ctx, NULL);
+ if (ret < 0)
+ 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 */
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;
}
+ 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,
c->first_packet = 1;
c->first_timestamp = AV_NOPTS_VALUE;
+ c->seek_timestamp = AV_NOPTS_VALUE;
return 0;
fail:
/* Make sure we've got one buffered packet from each open variant
* stream */
if (var->needed && !var->pkt.data) {
- ret = av_read_frame(var->ctx, &var->pkt);
- if (ret < 0) {
- if (!var->pb.eof_reached)
- return ret;
+ while (1) {
+ int64_t ts_diff;
+ AVStream *st;
+ ret = av_read_frame(var->ctx, &var->pkt);
+ if (ret < 0) {
+ if (!var->pb.eof_reached)
+ return ret;
+ reset_packet(&var->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,
+ AV_TIME_BASE_Q);
+ }
+
+ if (c->seek_timestamp == AV_NOPTS_VALUE)
+ break;
+
+ if (var->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->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)) {
+ c->seek_timestamp = AV_NOPTS_VALUE;
+ break;
+ }
+ av_free_packet(&var->pkt);
reset_packet(&var->pkt);
- } else {
- if (c->first_timestamp == AV_NOPTS_VALUE)
- c->first_timestamp = var->pkt.dts;
}
}
- /* Check if this stream has the packet with the lowest dts */
+ /* Check if this stream still is on an earlier segment number, or
+ * has the packet with the lowest dts */
if (var->pkt.data) {
- if (minvariant < 0 ||
- var->pkt.dts < c->variants[minvariant]->pkt.dts)
+ 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 (dts == AV_NOPTS_VALUE) {
+ minvariant = i;
+ } else if (mindts != AV_NOPTS_VALUE) {
+ if (st->start_time != AV_NOPTS_VALUE)
+ dts -= st->start_time;
+ if (minst->start_time != AV_NOPTS_VALUE)
+ mindts -= minst->start_time;
+
+ if (av_compare_ts(dts, st->time_base,
+ mindts, minst->time_base) < 0)
+ minvariant = i;
+ }
+ }
}
}
if (c->end_of_segment) {
HLSContext *c = s->priv_data;
free_variant_list(c);
+
+ av_dict_free(&c->avio_opts);
+
return 0;
}
if ((flags & AVSEEK_FLAG_BYTE) || !c->variants[0]->finished)
return AVERROR(ENOSYS);
- timestamp = av_rescale_rnd(timestamp, 1, stream_index >= 0 ?
+ c->seek_flags = flags;
+ c->seek_timestamp = stream_index < 0 ? timestamp :
+ av_rescale_rnd(timestamp, AV_TIME_BASE,
+ s->streams[stream_index]->time_base.den,
+ flags & AVSEEK_FLAG_BACKWARD ?
+ AV_ROUND_DOWN : AV_ROUND_UP);
+ timestamp = av_rescale_rnd(timestamp, AV_TIME_BASE, stream_index >= 0 ?
s->streams[stream_index]->time_base.den :
AV_TIME_BASE, flags & AVSEEK_FLAG_BACKWARD ?
AV_ROUND_DOWN : AV_ROUND_UP);
+ if (s->duration < c->seek_timestamp) {
+ c->seek_timestamp = AV_NOPTS_VALUE;
+ return AVERROR(EIO);
+ }
+
ret = AVERROR(EIO);
for (i = 0; i < c->n_variants; i++) {
/* Reset reading */
struct variant *var = c->variants[i];
- int64_t pos = c->first_timestamp == AV_NOPTS_VALUE ? 0 :
- av_rescale_rnd(c->first_timestamp, 1,
- stream_index >= 0 ? s->streams[stream_index]->time_base.den : AV_TIME_BASE,
- flags & AVSEEK_FLAG_BACKWARD ? AV_ROUND_DOWN : AV_ROUND_UP);
+ int64_t pos = c->first_timestamp == AV_NOPTS_VALUE ?
+ 0 : c->first_timestamp;
if (var->input) {
ffurl_close(var->input);
var->input = NULL;
av_free_packet(&var->pkt);
reset_packet(&var->pkt);
var->pb.eof_reached = 0;
+ /* Clear any buffered data */
+ var->pb.buf_end = var->pb.buf_ptr = var->pb.buffer;
+ /* Reset the pos, to let the mpegts demuxer know we've seeked. */
+ var->pb.pos = 0;
/* Locate the segment that contains the target timestamp */
for (j = 0; j < var->n_segments; j++) {
}
pos += var->segments[j]->duration;
}
+ if (ret)
+ c->seek_timestamp = AV_NOPTS_VALUE;
}
return ret;
}
AVInputFormat ff_hls_demuxer = {
.name = "hls,applehttp",
- .long_name = NULL_IF_CONFIG_SMALL("Apple HTTP Live Streaming format"),
+ .long_name = NULL_IF_CONFIG_SMALL("Apple HTTP Live Streaming"),
.priv_data_size = sizeof(HLSContext),
.read_probe = hls_probe,
.read_header = hls_read_header,