]> git.sesse.net Git - ffmpeg/blobdiff - libavformat/hls.c
avformat/hls: enable http_multiple only for http/1.1 servers
[ffmpeg] / libavformat / hls.c
index 786934af032f3410aa382a5d023f91fa43caf61b..dccc7c7dd2c3fb57f5ee45d33758ddeb9eb7b2c5 100644 (file)
@@ -26,6 +26,7 @@
  * 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"
@@ -94,6 +95,9 @@ struct playlist {
     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;
@@ -111,7 +115,7 @@ struct playlist {
     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;
@@ -206,6 +210,9 @@ typedef struct HLSContext {
     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)
@@ -256,6 +263,10 @@ static void free_playlist_list(HLSContext *c)
         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);
@@ -597,13 +608,32 @@ static void update_options(char **dest, const char *name, void *src)
         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);
@@ -629,7 +659,7 @@ static int open_url(AVFormatContext *s, AVIOContext **pb, const char *url,
             return AVERROR_INVALIDDATA;
         }
     } else if (av_strstart(proto_name, "http", NULL)) {
-        ;
+        is_http = 1;
     } else
         return AVERROR_INVALIDDATA;
 
@@ -640,7 +670,20 @@ static int open_url(AVFormatContext *s, AVIOContext **pb, const char *url,
     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;
@@ -658,8 +701,8 @@ static int open_url(AVFormatContext *s, AVIOContext **pb, const char *url,
 
     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;
 }
@@ -682,11 +725,25 @@ static int parse_playlist(HLSContext *c, const char *url,
     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);
 
@@ -696,10 +753,18 @@ static int parse_playlist(HLSContext *c, const char *url,
         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)
@@ -873,6 +938,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,
@@ -1098,7 +1171,7 @@ static void intercept_id3(struct playlist *pls, uint8_t *buf,
         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;
@@ -1111,6 +1184,9 @@ static int open_input(HLSContext *c, struct playlist *pls, struct segment *seg)
     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) */
@@ -1122,12 +1198,12 @@ static int open_input(HLSContext *c, struct playlist *pls, struct segment *seg)
            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)) {
@@ -1153,7 +1229,7 @@ static int open_input(HLSContext *c, struct playlist *pls, struct segment *seg)
         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);
 
@@ -1180,11 +1256,11 @@ static int open_input(HLSContext *c, struct playlist *pls, struct segment *seg)
      * 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);
         }
     }
 
@@ -1210,7 +1286,7 @@ static int update_init_section(struct playlist *pls, struct segment *seg)
     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",
@@ -1258,33 +1334,72 @@ static int64_t default_reload_interval(struct playlist *pls)
                           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);
@@ -1329,6 +1444,7 @@ reload:
             goto reload;
         }
 
+        v->input_read_done = 0;
         seg = current_segment(v);
 
         /* load/update Media Initialization Section, if any */
@@ -1336,11 +1452,18 @@ reload:
         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;
@@ -1348,6 +1471,27 @@ 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);
@@ -1356,7 +1500,8 @@ reload:
         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
@@ -1366,7 +1511,11 @@ reload:
 
         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;
@@ -1627,6 +1776,7 @@ static int hls_close(AVFormatContext *s)
     free_rendition_list(c);
 
     av_dict_free(&c->avio_opts);
+    ff_format_io_close(c->ctx, &c->playlist_pb);
 
     return 0;
 }
@@ -1841,20 +1991,15 @@ static int recheck_discard_flags(AVFormatContext *s, int first)
 {
     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);
@@ -1866,9 +2011,13 @@ static int recheck_discard_flags(AVFormatContext *s, int first)
                 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);
@@ -2103,6 +2252,10 @@ static int hls_read_seek(AVFormatContext *s, int stream_index,
         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;
@@ -2157,6 +2310,10 @@ static const AVOption hls_options[] = {
         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}
 };