aboutsummaryrefslogtreecommitdiffstats
path: root/libavformat
diff options
context:
space:
mode:
authorAman Gupta <aman@tmm1.net>2017-12-12 15:25:46 -0800
committerAman Gupta <aman@tmm1.net>2017-12-22 14:42:50 -0800
commit1f0eaa02aa7141f371e095540ac285698a0bdf46 (patch)
tree7f42dc170ae1aeb630085326aa00dc038b486ab0 /libavformat
parent03765aa6fa9c519571586db87d4dade1102c4c86 (diff)
downloadffmpeg-1f0eaa02aa7141f371e095540ac285698a0bdf46.tar.gz
avformat/hls: add http_multiple option
This improves network throughput of the hls demuxer by avoiding the latency introduced by downloading segments one at a time. The problem is particularly noticable over high-latency network connections: for instance, if RTT is 250ms, there will a 250ms idle period between when one segment response is read and the next one starts. The obvious solution to this is to use HTTP pipelining, where a second request can be sent (on the persistent http/1.1 connection) before the first response is fully read. Unfortunately the way the http protocol is implemented in avformat makes implementing pipleining very complex. Instead, this commit simulates pipelining using two separate persistent http connections. This has the advantage of working independently of the http_persistent option, and can be used with http/1.0 servers as well. The pair of connections is swapped every time a new segment starts downloading, and a request for the next segment is sent on the secondary connection right away. This means the second response will be ready and waiting by the time the current response is fully read. Signed-off-by: Aman Gupta <aman@tmm1.net> Signed-off-by: Anssi Hannula <anssi.hannula@iki.fi>
Diffstat (limited to 'libavformat')
-rw-r--r--libavformat/hls.c49
1 files changed, 46 insertions, 3 deletions
diff --git a/libavformat/hls.c b/libavformat/hls.c
index e1846ad773..26bf61c805 100644
--- a/libavformat/hls.c
+++ b/libavformat/hls.c
@@ -96,6 +96,8 @@ struct playlist {
uint8_t* read_buffer;
AVIOContext *input;
int input_read_done;
+ AVIOContext *input_next;
+ int input_next_requested;
AVFormatContext *parent;
int index;
AVFormatContext *ctx;
@@ -209,6 +211,7 @@ typedef struct HLSContext {
char *allowed_extensions;
int max_reload;
int http_persistent;
+ int http_multiple;
AVIOContext *playlist_pb;
} HLSContext;
@@ -261,6 +264,9 @@ static void free_playlist_list(HLSContext *c)
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);
@@ -924,6 +930,14 @@ static struct segment *current_segment(struct playlist *pls)
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,
@@ -1365,6 +1379,7 @@ static int read_data(void *opaque, uint8_t *buf, int buf_size)
int ret;
int just_opened = 0;
int reload_count = 0;
+ struct segment *seg;
restart:
if (!v->needed)
@@ -1372,7 +1387,6 @@ restart:
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. */
@@ -1430,11 +1444,18 @@ reload:
if (ret)
return ret;
- ret = open_input(c, v, seg, &v->input);
+ if (c->http_multiple && av_strstart(seg->url, "http", NULL) && 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;
@@ -1442,6 +1463,20 @@ reload:
just_opened = 1;
}
+ seg = next_segment(v);
+ if (c->http_multiple && !v->input_next_requested && seg) {
+ 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);
@@ -1964,6 +1999,9 @@ static int recheck_discard_flags(AVFormatContext *s, int first)
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);
@@ -2199,6 +2237,9 @@ static int hls_read_seek(AVFormatContext *s, int stream_index,
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;
@@ -2255,6 +2296,8 @@ static const AVOption hls_options[] = {
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}, 0, 1, FLAGS},
{NULL}
};