Commit 1f0eaa02 authored by Aman Gupta's avatar Aman Gupta

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: 's avatarAman Gupta <aman@tmm1.net>
Signed-off-by: 's avatarAnssi Hannula <anssi.hannula@iki.fi>
parent 03765aa6
...@@ -320,6 +320,10 @@ Default value is 1000. ...@@ -320,6 +320,10 @@ Default value is 1000.
@item http_persistent @item http_persistent
Use persistent HTTP connections. Applicable only for HTTP streams. Use persistent HTTP connections. Applicable only for HTTP streams.
Enabled by default. Enabled by default.
@item http_multiple
Use multiple HTTP connections for downloading HTTP segments.
Enabled by default.
@end table @end table
@section image2 @section image2
......
...@@ -96,6 +96,8 @@ struct playlist { ...@@ -96,6 +96,8 @@ struct playlist {
uint8_t* read_buffer; uint8_t* read_buffer;
AVIOContext *input; AVIOContext *input;
int input_read_done; int input_read_done;
AVIOContext *input_next;
int input_next_requested;
AVFormatContext *parent; AVFormatContext *parent;
int index; int index;
AVFormatContext *ctx; AVFormatContext *ctx;
...@@ -209,6 +211,7 @@ typedef struct HLSContext { ...@@ -209,6 +211,7 @@ typedef struct HLSContext {
char *allowed_extensions; char *allowed_extensions;
int max_reload; int max_reload;
int http_persistent; int http_persistent;
int http_multiple;
AVIOContext *playlist_pb; AVIOContext *playlist_pb;
} HLSContext; } HLSContext;
...@@ -261,6 +264,9 @@ static void free_playlist_list(HLSContext *c) ...@@ -261,6 +264,9 @@ static void free_playlist_list(HLSContext *c)
if (pls->input) if (pls->input)
ff_format_io_close(c->ctx, &pls->input); ff_format_io_close(c->ctx, &pls->input);
pls->input_read_done = 0; 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) { if (pls->ctx) {
pls->ctx->pb = NULL; pls->ctx->pb = NULL;
avformat_close_input(&pls->ctx); avformat_close_input(&pls->ctx);
...@@ -924,6 +930,14 @@ static struct segment *current_segment(struct playlist *pls) ...@@ -924,6 +930,14 @@ static struct segment *current_segment(struct playlist *pls)
return pls->segments[pls->cur_seq_no - pls->start_seq_no]; 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 { enum ReadFromURLMode {
READ_NORMAL, READ_NORMAL,
READ_COMPLETE, READ_COMPLETE,
...@@ -1365,6 +1379,7 @@ static int read_data(void *opaque, uint8_t *buf, int buf_size) ...@@ -1365,6 +1379,7 @@ static int read_data(void *opaque, uint8_t *buf, int buf_size)
int ret; int ret;
int just_opened = 0; int just_opened = 0;
int reload_count = 0; int reload_count = 0;
struct segment *seg;
restart: restart:
if (!v->needed) if (!v->needed)
...@@ -1372,7 +1387,6 @@ restart: ...@@ -1372,7 +1387,6 @@ restart:
if (!v->input || (c->http_persistent && v->input_read_done)) { if (!v->input || (c->http_persistent && v->input_read_done)) {
int64_t reload_interval; int64_t reload_interval;
struct segment *seg;
/* Check that the playlist is still needed before opening a new /* Check that the playlist is still needed before opening a new
* segment. */ * segment. */
...@@ -1430,11 +1444,18 @@ reload: ...@@ -1430,11 +1444,18 @@ reload:
if (ret) if (ret)
return 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 (ret < 0) {
if (ff_check_interrupt(c->interrupt_callback)) if (ff_check_interrupt(c->interrupt_callback))
return AVERROR_EXIT; 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->index);
v->cur_seq_no += 1; v->cur_seq_no += 1;
goto reload; goto reload;
...@@ -1442,6 +1463,20 @@ reload: ...@@ -1442,6 +1463,20 @@ reload:
just_opened = 1; 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) { if (v->init_sec_buf_read_offset < v->init_sec_data_len) {
/* Push init section out first before first actual segment */ /* 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); 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) ...@@ -1964,6 +1999,9 @@ static int recheck_discard_flags(AVFormatContext *s, int first)
if (pls->input) if (pls->input)
ff_format_io_close(pls->parent, &pls->input); ff_format_io_close(pls->parent, &pls->input);
pls->input_read_done = 0; 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; pls->needed = 0;
changed = 1; changed = 1;
av_log(s, AV_LOG_INFO, "No longer receiving playlist %d\n", i); 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, ...@@ -2199,6 +2237,9 @@ static int hls_read_seek(AVFormatContext *s, int stream_index,
if (pls->input) if (pls->input)
ff_format_io_close(pls->parent, &pls->input); ff_format_io_close(pls->parent, &pls->input);
pls->input_read_done = 0; 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); av_packet_unref(&pls->pkt);
reset_packet(&pls->pkt); reset_packet(&pls->pkt);
pls->pb.eof_reached = 0; pls->pb.eof_reached = 0;
...@@ -2255,6 +2296,8 @@ static const AVOption hls_options[] = { ...@@ -2255,6 +2296,8 @@ static const AVOption hls_options[] = {
OFFSET(max_reload), AV_OPT_TYPE_INT, {.i64 = 1000}, 0, INT_MAX, FLAGS}, OFFSET(max_reload), AV_OPT_TYPE_INT, {.i64 = 1000}, 0, INT_MAX, FLAGS},
{"http_persistent", "Use persistent HTTP connections", {"http_persistent", "Use persistent HTTP connections",
OFFSET(http_persistent), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, FLAGS }, 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} {NULL}
}; };
......
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