* http://tools.ietf.org/html/draft-pantos-http-live-streaming
*/
+#include "libavformat/http.h"
#include "libavutil/avstring.h"
#include "libavutil/avassert.h"
#include "libavutil/intreadwrite.h"
AVIOContext pb;
uint8_t* read_buffer;
AVIOContext *input;
+ int input_read_done;
+ AVIOContext *input_next;
+ int input_next_requested;
AVFormatContext *parent;
int index;
AVFormatContext *ctx;
int start_seq_no;
int n_segments;
struct segment **segments;
- int needed, cur_needed;
+ int needed;
int cur_seq_no;
int64_t cur_seg_offset;
int64_t last_load_time;
int strict_std_compliance;
char *allowed_extensions;
int max_reload;
+ int http_persistent;
+ int http_multiple;
+ AVIOContext *playlist_pb;
} HLSContext;
static int read_chomp_line(AVIOContext *s, char *buf, int maxlen)
av_freep(&pls->pb.buffer);
if (pls->input)
ff_format_io_close(c->ctx, &pls->input);
+ pls->input_read_done = 0;
+ if (pls->input_next)
+ ff_format_io_close(c->ctx, &pls->input_next);
+ pls->input_next_requested = 0;
if (pls->ctx) {
pls->ctx->pb = NULL;
avformat_close_input(&pls->ctx);
av_freep(dest);
}
+static int open_url_keepalive(AVFormatContext *s, AVIOContext **pb,
+ const char *url)
+{
+#if !CONFIG_HTTP_PROTOCOL
+ return AVERROR_PROTOCOL_NOT_FOUND;
+#else
+ int ret;
+ URLContext *uc = ffio_geturlcontext(*pb);
+ av_assert0(uc);
+ (*pb)->eof_reached = 0;
+ ret = ff_http_do_new_request(uc, url);
+ if (ret < 0) {
+ ff_format_io_close(s, pb);
+ }
+ return ret;
+#endif
+}
+
static int open_url(AVFormatContext *s, AVIOContext **pb, const char *url,
- AVDictionary *opts, AVDictionary *opts2, int *is_http)
+ AVDictionary *opts, AVDictionary *opts2, int *is_http_out)
{
HLSContext *c = s->priv_data;
AVDictionary *tmp = NULL;
const char *proto_name = NULL;
int ret;
+ int is_http = 0;
av_dict_copy(&tmp, opts, 0);
av_dict_copy(&tmp, opts2, 0);
return AVERROR_INVALIDDATA;
}
} else if (av_strstart(proto_name, "http", NULL)) {
- ;
+ is_http = 1;
} else
return AVERROR_INVALIDDATA;
else if (strcmp(proto_name, "file") || !strncmp(url, "file,", 5))
return AVERROR_INVALIDDATA;
- ret = s->io_open(s, pb, url, AVIO_FLAG_READ, &tmp);
+ if (is_http && c->http_persistent && *pb) {
+ ret = open_url_keepalive(c->ctx, pb, url);
+ if (ret == AVERROR_EXIT) {
+ return ret;
+ } else if (ret < 0) {
+ if (ret != AVERROR_EOF)
+ av_log(s, AV_LOG_WARNING,
+ "keepalive request failed for '%s', retrying with new connection: %s\n",
+ url, av_err2str(ret));
+ ret = s->io_open(s, pb, url, AVIO_FLAG_READ, &tmp);
+ }
+ } else {
+ ret = s->io_open(s, pb, url, AVIO_FLAG_READ, &tmp);
+ }
if (ret >= 0) {
// update cookies on http response with setcookies.
char *new_cookies = NULL;
av_dict_free(&tmp);
- if (is_http)
- *is_http = av_strstart(proto_name, "http", NULL);
+ if (is_http_out)
+ *is_http_out = is_http;
return ret;
}
struct variant_info variant_info;
char tmp_str[MAX_URL_SIZE];
struct segment *cur_init_section = NULL;
+ int is_http = av_strstart(url, "http", NULL);
+
+ if (is_http && !in && c->http_persistent && c->playlist_pb) {
+ in = c->playlist_pb;
+ ret = open_url_keepalive(c->ctx, &c->playlist_pb, url);
+ if (ret == AVERROR_EXIT) {
+ return ret;
+ } else if (ret < 0) {
+ if (ret != AVERROR_EOF)
+ av_log(c->ctx, AV_LOG_WARNING,
+ "keepalive request failed for '%s', retrying with new connection: %s\n",
+ url, av_err2str(ret));
+ in = NULL;
+ }
+ }
if (!in) {
#if 1
AVDictionary *opts = NULL;
- close_in = 1;
/* Some HLS servers don't like being sent the range header */
av_dict_set(&opts, "seekable", "0", 0);
av_dict_set(&opts, "headers", c->headers, 0);
av_dict_set(&opts, "http_proxy", c->http_proxy, 0);
+ if (c->http_persistent)
+ av_dict_set(&opts, "multiple_requests", "1", 0);
+
ret = c->ctx->io_open(c->ctx, &in, url, AVIO_FLAG_READ, &opts);
av_dict_free(&opts);
if (ret < 0)
return ret;
+
+ if (is_http && c->http_persistent)
+ c->playlist_pb = in;
+ else
+ close_in = 1;
#else
ret = open_in(c, &in, url);
if (ret < 0)
return pls->segments[pls->cur_seq_no - pls->start_seq_no];
}
+static struct segment *next_segment(struct playlist *pls)
+{
+ int n = pls->cur_seq_no - pls->start_seq_no + 1;
+ if (n >= pls->n_segments)
+ return NULL;
+ return pls->segments[n];
+}
+
enum ReadFromURLMode {
READ_NORMAL,
READ_COMPLETE,
pls->is_id3_timestamped = (pls->id3_mpegts_timestamp != AV_NOPTS_VALUE);
}
-static int open_input(HLSContext *c, struct playlist *pls, struct segment *seg)
+static int open_input(HLSContext *c, struct playlist *pls, struct segment *seg, AVIOContext **in)
{
AVDictionary *opts = NULL;
int ret;
av_dict_set(&opts, "http_proxy", c->http_proxy, 0);
av_dict_set(&opts, "seekable", "0", 0);
+ if (c->http_persistent)
+ av_dict_set(&opts, "multiple_requests", "1", 0);
+
if (seg->size >= 0) {
/* try to restrict the HTTP request to the part we want
* (if this is in fact a HTTP request) */
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, &is_http);
+ ret = open_url(pls->parent, in, 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;
+ AVIOContext *pb = NULL;
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_dict_set(&opts2, "key", key, 0);
av_dict_set(&opts2, "iv", iv, 0);
- ret = open_url(pls->parent, &pls->input, url, opts2, opts, &is_http);
+ ret = open_url(pls->parent, in, url, opts2, opts, &is_http);
av_dict_free(&opts2);
* 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);
+ int64_t seekret = avio_seek(*in, 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);
ret = seekret;
- ff_format_io_close(pls->parent, &pls->input);
+ ff_format_io_close(pls->parent, in);
}
}
if (!seg->init_section)
return 0;
- ret = open_input(c, pls, seg->init_section);
+ ret = open_input(c, pls, seg->init_section, &pls->input);
if (ret < 0) {
av_log(pls->parent, AV_LOG_WARNING,
"Failed to open an initialization section in playlist %d\n",
pls->target_duration;
}
+static int playlist_needed(struct playlist *pls)
+{
+ AVFormatContext *s = pls->parent;
+ int i, j;
+ int stream_needed = 0;
+ int first_st;
+
+ /* If there is no context or streams yet, the playlist is needed */
+ if (!pls->ctx || !pls->n_main_streams)
+ return 1;
+
+ /* check if any of the streams in the playlist are needed */
+ for (i = 0; i < pls->n_main_streams; i++) {
+ if (pls->main_streams[i]->discard < AVDISCARD_ALL) {
+ stream_needed = 1;
+ break;
+ }
+ }
+
+ /* If all streams in the playlist were discarded, the playlist is not
+ * needed (regardless of whether whole programs are discarded or not). */
+ if (!stream_needed)
+ return 0;
+
+ /* Otherwise, check if all the programs (variants) this playlist is in are
+ * discarded. Since all streams in the playlist are part of the same programs
+ * we can just check the programs of the first stream. */
+
+ first_st = pls->main_streams[0]->index;
+
+ for (i = 0; i < s->nb_programs; i++) {
+ AVProgram *program = s->programs[i];
+ if (program->discard < AVDISCARD_ALL) {
+ for (j = 0; j < program->nb_stream_indexes; j++) {
+ if (program->stream_index[j] == first_st) {
+ /* playlist is in an undiscarded program */
+ return 1;
+ }
+ }
+ }
+ }
+
+ /* some streams were not discarded but all the programs were */
+ return 0;
+}
+
static int read_data(void *opaque, uint8_t *buf, int buf_size)
{
struct playlist *v = opaque;
HLSContext *c = v->parent->priv_data;
- int ret, i;
+ int ret;
int just_opened = 0;
int reload_count = 0;
+ struct segment *seg;
restart:
if (!v->needed)
return AVERROR_EOF;
- if (!v->input) {
+ if (!v->input || (c->http_persistent && v->input_read_done)) {
int64_t reload_interval;
- struct segment *seg;
/* Check that the playlist is still needed before opening a new
* segment. */
- if (v->ctx && v->ctx->nb_streams) {
- v->needed = 0;
- for (i = 0; i < v->n_main_streams; i++) {
- if (v->main_streams[i]->discard < AVDISCARD_ALL) {
- v->needed = 1;
- break;
- }
- }
- }
+ v->needed = playlist_needed(v);
+
if (!v->needed) {
av_log(v->parent, AV_LOG_INFO, "No longer receiving playlist %d\n",
v->index);
goto reload;
}
+ v->input_read_done = 0;
seg = current_segment(v);
/* load/update Media Initialization Section, if any */
if (ret)
return ret;
- ret = open_input(c, v, seg);
+ if (c->http_multiple == 1 && v->input_next_requested) {
+ FFSWAP(AVIOContext *, v->input, v->input_next);
+ v->input_next_requested = 0;
+ ret = 0;
+ } else {
+ ret = open_input(c, v, seg, &v->input);
+ }
if (ret < 0) {
if (ff_check_interrupt(c->interrupt_callback))
return AVERROR_EXIT;
- av_log(v->parent, AV_LOG_WARNING, "Failed to open segment of playlist %d\n",
+ av_log(v->parent, AV_LOG_WARNING, "Failed to open segment %d of playlist %d\n",
+ v->cur_seq_no,
v->index);
v->cur_seq_no += 1;
goto reload;
just_opened = 1;
}
+ if (c->http_multiple == -1) {
+ uint8_t *http_version_opt = NULL;
+ av_opt_get(v->input, "http_version", AV_OPT_SEARCH_CHILDREN, &http_version_opt);
+ c->http_multiple = http_version_opt && strncmp((const char *)http_version_opt, "1.1", 3) == 0;
+ }
+
+ seg = next_segment(v);
+ if (c->http_multiple == 1 && !v->input_next_requested &&
+ seg && av_strstart(seg->url, "http", NULL)) {
+ ret = open_input(c, v, seg, &v->input_next);
+ if (ret < 0) {
+ if (ff_check_interrupt(c->interrupt_callback))
+ return AVERROR_EXIT;
+ av_log(v->parent, AV_LOG_WARNING, "Failed to open segment %d of playlist %d\n",
+ v->cur_seq_no + 1,
+ v->index);
+ } else {
+ v->input_next_requested = 1;
+ }
+ }
+
if (v->init_sec_buf_read_offset < v->init_sec_data_len) {
/* Push init section out first before first actual segment */
int copy_size = FFMIN(v->init_sec_data_len - v->init_sec_buf_read_offset, buf_size);
return copy_size;
}
- ret = read_from_url(v, current_segment(v), buf, buf_size, READ_NORMAL);
+ seg = current_segment(v);
+ ret = read_from_url(v, seg, buf, buf_size, READ_NORMAL);
if (ret > 0) {
if (just_opened && v->is_id3_timestamped != 0) {
/* Intercept ID3 tags here, elementary audio streams are required
return ret;
}
- ff_format_io_close(v->parent, &v->input);
+ if (c->http_persistent && av_strstart(seg->url, "http", NULL)) {
+ v->input_read_done = 1;
+ } else {
+ ff_format_io_close(v->parent, &v->input);
+ }
v->cur_seq_no++;
c->cur_seq_no = v->cur_seq_no;
free_rendition_list(c);
av_dict_free(&c->avio_opts);
+ ff_format_io_close(c->ctx, &c->playlist_pb);
return 0;
}
{
HLSContext *c = s->priv_data;
int i, changed = 0;
+ int cur_needed;
/* Check if any new streams are needed */
- 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 playlist *pls = c->playlists[s->streams[i]->id];
- if (st->discard < AVDISCARD_ALL)
- pls->cur_needed = 1;
- }
for (i = 0; i < c->n_playlists; i++) {
struct playlist *pls = c->playlists[i];
- if (pls->cur_needed && !pls->needed) {
+
+ cur_needed = playlist_needed(c->playlists[i]);
+
+ if (cur_needed && !pls->needed) {
pls->needed = 1;
changed = 1;
pls->cur_seq_no = select_cur_seq_no(c, pls);
pls->seek_stream_index = -1;
}
av_log(s, AV_LOG_INFO, "Now receiving playlist %d, segment %d\n", i, pls->cur_seq_no);
- } else if (first && !pls->cur_needed && pls->needed) {
+ } else if (first && !cur_needed && pls->needed) {
if (pls->input)
ff_format_io_close(pls->parent, &pls->input);
+ pls->input_read_done = 0;
+ if (pls->input_next)
+ ff_format_io_close(pls->parent, &pls->input_next);
+ pls->input_next_requested = 0;
pls->needed = 0;
changed = 1;
av_log(s, AV_LOG_INFO, "No longer receiving playlist %d\n", i);
struct playlist *pls = c->playlists[i];
if (pls->input)
ff_format_io_close(pls->parent, &pls->input);
+ pls->input_read_done = 0;
+ if (pls->input_next)
+ ff_format_io_close(pls->parent, &pls->input_next);
+ pls->input_next_requested = 0;
av_packet_unref(&pls->pkt);
reset_packet(&pls->pkt);
pls->pb.eof_reached = 0;
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},
+ {"http_persistent", "Use persistent HTTP connections",
+ OFFSET(http_persistent), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, FLAGS },
+ {"http_multiple", "Use multiple HTTP connections for fetching segments",
+ OFFSET(http_multiple), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, FLAGS},
{NULL}
};