diff mbox

[FFmpeg-devel,v2,3/5] avformat/hls: add http_persistent option

Message ID 20171213003511.25342-4-ffmpeg@tmm1.net
State New
Headers show

Commit Message

Aman Karmani Dec. 13, 2017, 12:35 a.m. UTC
From: Aman Gupta <aman@tmm1.net>

This teaches the HLS demuxer to use the HTTP protocols
multiple_requests=1 option, to take advantage of "Connection:
Keep-Alive" when downloading playlists and segments from the HLS server.

With the new option, you can avoid TCP connection and TLS negotiation
overhead, which is particularly beneficial when streaming via a
high-latency internet connection.

Similar to the http_persistent option recently implemented in hlsenc.c
---
 doc/demuxers.texi |  3 +++
 libavformat/hls.c | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++----
 2 files changed, 67 insertions(+), 4 deletions(-)

Comments

Anssi Hannula Dec. 17, 2017, 8:33 p.m. UTC | #1
Hi!

Aman Gupta kirjoitti 2017-12-13 02:35:
> From: Aman Gupta <aman@tmm1.net>
> 
> This teaches the HLS demuxer to use the HTTP protocols
> multiple_requests=1 option, to take advantage of "Connection:
> Keep-Alive" when downloading playlists and segments from the HLS 
> server.
> 
> With the new option, you can avoid TCP connection and TLS negotiation
> overhead, which is particularly beneficial when streaming via a
> high-latency internet connection.
> 
> Similar to the http_persistent option recently implemented in hlsenc.c

Why is this implemented as an option instead of simply being always used 
by the demuxer?

> ---
>  doc/demuxers.texi |  3 +++
>  libavformat/hls.c | 68 
> +++++++++++++++++++++++++++++++++++++++++++++++++++----
>  2 files changed, 67 insertions(+), 4 deletions(-)
> 
> diff --git a/doc/demuxers.texi b/doc/demuxers.texi
> index 73dc0feec1..18ff7101ac 100644
> --- a/doc/demuxers.texi
> +++ b/doc/demuxers.texi
> @@ -316,6 +316,9 @@ segment index to start live streams at (negative
> values are from the end).
>  @item max_reload
>  Maximum number of times a insufficient list is attempted to be 
> reloaded.
>  Default value is 1000.
> +
> +@item http_persistent
> +Use persistent HTTP connections. Applicable only for HTTP streams.
>  @end table
> 
>  @section image2
> diff --git a/libavformat/hls.c b/libavformat/hls.c
> index ab6ff187a6..5c1895c180 100644
> --- a/libavformat/hls.c
> +++ b/libavformat/hls.c
> @@ -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,7 @@ struct playlist {
>      AVIOContext pb;
>      uint8_t* read_buffer;
>      AVIOContext *input;
> +    int input_read_done;
>      AVFormatContext *parent;
>      int index;
>      AVFormatContext *ctx;
> @@ -206,6 +208,8 @@ typedef struct HLSContext {
>      int strict_std_compliance;
>      char *allowed_extensions;
>      int max_reload;
> +    int http_persistent;
> +    AVIOContext *playlist_pb;
>  } HLSContext;
> 
>  static int read_chomp_line(AVIOContext *s, char *buf, int maxlen)
> @@ -256,6 +260,7 @@ 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->ctx) {
>              pls->ctx->pb = NULL;
>              avformat_close_input(&pls->ctx);
> @@ -640,7 +645,24 @@ 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 (c->http_persistent && *pb && av_strstart(proto_name, "http", 
> NULL)) {
> +        URLContext *uc = ffio_geturlcontext(*pb);
> +        av_assert0(uc);
> +        (*pb)->eof_reached = 0;
> +        ret = ff_http_do_new_request(uc, url);
> +        if (ret == AVERROR_EXIT) {
> +            ff_format_io_close(c->ctx, &c->playlist_pb);
> +            return ret;
> +        } else if (ret < 0) {
> +            av_log(s, AV_LOG_WARNING,
> +                "keepalive request failed for '%s', retrying with new
> connection: %s\n",
> +                url, av_err2str(ret));
> +            ff_format_io_close(c->ctx, pb);
> +            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;
> @@ -683,10 +705,27 @@ static int parse_playlist(HLSContext *c, const 
> char *url,
>      char tmp_str[MAX_URL_SIZE];
>      struct segment *cur_init_section = NULL;
> 
> +    if (!in && c->http_persistent && c->playlist_pb) {
> +        in = c->playlist_pb;
> +        URLContext *uc = ffio_geturlcontext(in);
> +        av_assert0(uc);
> +        in->eof_reached = 0;
> +        ret = ff_http_do_new_request(uc, url);
> +        if (ret == AVERROR_EXIT) {
> +            ff_format_io_close(c->ctx, &c->playlist_pb);
> +            return ret;
> +        } else if (ret < 0) {
> +            av_log(c->ctx, AV_LOG_WARNING,
> +                "keepalive request failed for '%s', retrying with new
> connection: %s\n",
> +                url, av_err2str(ret));
> +            ff_format_io_close(c->ctx, &c->playlist_pb);
> +            in = NULL;
> +        }
> +    }
> +

The above code seems duplicated, please put it in a common function.


[..]
Aman Gupta Dec. 17, 2017, 8:41 p.m. UTC | #2
On Sun, Dec 17, 2017 at 12:34 PM Anssi Hannula <anssi.hannula@iki.fi> wrote:

> Hi!
>
> Aman Gupta kirjoitti 2017-12-13 02:35:
> > From: Aman Gupta <aman@tmm1.net>
> >
> > This teaches the HLS demuxer to use the HTTP protocols
> > multiple_requests=1 option, to take advantage of "Connection:
> > Keep-Alive" when downloading playlists and segments from the HLS
> > server.
> >
> > With the new option, you can avoid TCP connection and TLS negotiation
> > overhead, which is particularly beneficial when streaming via a
> > high-latency internet connection.
> >
> > Similar to the http_persistent option recently implemented in hlsenc.c
>
> Why is this implemented as an option instead of simply being always used
> by the demuxer?


I have no strong feeling on this either way. I've tested the new option
against a few different HLS servers and would be comfortable enabling it by
default. I do think it's worth keeping as an option in case someone wants
to turn it off for whatever reason.


>
> > ---
> >  doc/demuxers.texi |  3 +++
> >  libavformat/hls.c | 68
> > +++++++++++++++++++++++++++++++++++++++++++++++++++----
> >  2 files changed, 67 insertions(+), 4 deletions(-)
> >
> > diff --git a/doc/demuxers.texi b/doc/demuxers.texi
> > index 73dc0feec1..18ff7101ac 100644
> > --- a/doc/demuxers.texi
> > +++ b/doc/demuxers.texi
> > @@ -316,6 +316,9 @@ segment index to start live streams at (negative
> > values are from the end).
> >  @item max_reload
> >  Maximum number of times a insufficient list is attempted to be
> > reloaded.
> >  Default value is 1000.
> > +
> > +@item http_persistent
> > +Use persistent HTTP connections. Applicable only for HTTP streams.
> >  @end table
> >
> >  @section image2
> > diff --git a/libavformat/hls.c b/libavformat/hls.c
> > index ab6ff187a6..5c1895c180 100644
> > --- a/libavformat/hls.c
> > +++ b/libavformat/hls.c
> > @@ -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,7 @@ struct playlist {
> >      AVIOContext pb;
> >      uint8_t* read_buffer;
> >      AVIOContext *input;
> > +    int input_read_done;
> >      AVFormatContext *parent;
> >      int index;
> >      AVFormatContext *ctx;
> > @@ -206,6 +208,8 @@ typedef struct HLSContext {
> >      int strict_std_compliance;
> >      char *allowed_extensions;
> >      int max_reload;
> > +    int http_persistent;
> > +    AVIOContext *playlist_pb;
> >  } HLSContext;
> >
> >  static int read_chomp_line(AVIOContext *s, char *buf, int maxlen)
> > @@ -256,6 +260,7 @@ 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->ctx) {
> >              pls->ctx->pb = NULL;
> >              avformat_close_input(&pls->ctx);
> > @@ -640,7 +645,24 @@ 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 (c->http_persistent && *pb && av_strstart(proto_name, "http",
> > NULL)) {
> > +        URLContext *uc = ffio_geturlcontext(*pb);
> > +        av_assert0(uc);
> > +        (*pb)->eof_reached = 0;
> > +        ret = ff_http_do_new_request(uc, url);
> > +        if (ret == AVERROR_EXIT) {
> > +            ff_format_io_close(c->ctx, &c->playlist_pb);
> > +            return ret;
> > +        } else if (ret < 0) {
> > +            av_log(s, AV_LOG_WARNING,
> > +                "keepalive request failed for '%s', retrying with new
> > connection: %s\n",
> > +                url, av_err2str(ret));
> > +            ff_format_io_close(c->ctx, pb);
> > +            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;
> > @@ -683,10 +705,27 @@ static int parse_playlist(HLSContext *c, const
> > char *url,
> >      char tmp_str[MAX_URL_SIZE];
> >      struct segment *cur_init_section = NULL;
> >
> > +    if (!in && c->http_persistent && c->playlist_pb) {
> > +        in = c->playlist_pb;
> > +        URLContext *uc = ffio_geturlcontext(in);
> > +        av_assert0(uc);
> > +        in->eof_reached = 0;
> > +        ret = ff_http_do_new_request(uc, url);
> > +        if (ret == AVERROR_EXIT) {
> > +            ff_format_io_close(c->ctx, &c->playlist_pb);
> > +            return ret;
> > +        } else if (ret < 0) {
> > +            av_log(c->ctx, AV_LOG_WARNING,
> > +                "keepalive request failed for '%s', retrying with new
> > connection: %s\n",
> > +                url, av_err2str(ret));
> > +            ff_format_io_close(c->ctx, &c->playlist_pb);
> > +            in = NULL;
> > +        }
> > +    }
> > +
>
> The above code seems duplicated, please put it in a common function.


Sure, will do.

Thanks for reviewing!


>
>
> [..]
> --
> Anssi Hannula
>
Anssi Hannula Dec. 17, 2017, 9:13 p.m. UTC | #3
Aman Gupta kirjoitti 2017-12-17 22:41:
> On Sun, Dec 17, 2017 at 12:34 PM Anssi Hannula <anssi.hannula@iki.fi> 
> wrote:
> 
>> Hi!
>> 
>> Aman Gupta kirjoitti 2017-12-13 02:35:
>> > From: Aman Gupta <aman@tmm1.net>
>> >
>> > This teaches the HLS demuxer to use the HTTP protocols
>> > multiple_requests=1 option, to take advantage of "Connection:
>> > Keep-Alive" when downloading playlists and segments from the HLS
>> > server.
>> >
>> > With the new option, you can avoid TCP connection and TLS negotiation
>> > overhead, which is particularly beneficial when streaming via a
>> > high-latency internet connection.
>> >
>> > Similar to the http_persistent option recently implemented in hlsenc.c
>> 
>> Why is this implemented as an option instead of simply being always 
>> used
>> by the demuxer?
> 
> 
> I have no strong feeling on this either way. I've tested the new option
> against a few different HLS servers and would be comfortable enabling 
> it by
> default. I do think it's worth keeping as an option in case someone 
> wants
> to turn it off for whatever reason.

OK. The only other demuxer that reuses HTTP connections seems to be 
rtmphttp, and it does not have an option.
I think I'd prefer no option here as well unless a use case is known, 
but I'm not too strongly against it so I guess it could stay (but 
default to true).

Does anyone else have any thoughts?


Also, what happens if the hostname in the URI varies, does it properly 
open a new connection then?

> 
>> 
>> > ---
>> >  doc/demuxers.texi |  3 +++
>> >  libavformat/hls.c | 68
>> > +++++++++++++++++++++++++++++++++++++++++++++++++++----
>> >  2 files changed, 67 insertions(+), 4 deletions(-)
>> >
>> > diff --git a/doc/demuxers.texi b/doc/demuxers.texi
>> > index 73dc0feec1..18ff7101ac 100644
>> > --- a/doc/demuxers.texi
>> > +++ b/doc/demuxers.texi
>> > @@ -316,6 +316,9 @@ segment index to start live streams at (negative
>> > values are from the end).
>> >  @item max_reload
>> >  Maximum number of times a insufficient list is attempted to be
>> > reloaded.
>> >  Default value is 1000.
>> > +
>> > +@item http_persistent
>> > +Use persistent HTTP connections. Applicable only for HTTP streams.
>> >  @end table
>> >
>> >  @section image2
>> > diff --git a/libavformat/hls.c b/libavformat/hls.c
>> > index ab6ff187a6..5c1895c180 100644
>> > --- a/libavformat/hls.c
>> > +++ b/libavformat/hls.c
>> > @@ -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,7 @@ struct playlist {
>> >      AVIOContext pb;
>> >      uint8_t* read_buffer;
>> >      AVIOContext *input;
>> > +    int input_read_done;
>> >      AVFormatContext *parent;
>> >      int index;
>> >      AVFormatContext *ctx;
>> > @@ -206,6 +208,8 @@ typedef struct HLSContext {
>> >      int strict_std_compliance;
>> >      char *allowed_extensions;
>> >      int max_reload;
>> > +    int http_persistent;
>> > +    AVIOContext *playlist_pb;
>> >  } HLSContext;
>> >
>> >  static int read_chomp_line(AVIOContext *s, char *buf, int maxlen)
>> > @@ -256,6 +260,7 @@ 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->ctx) {
>> >              pls->ctx->pb = NULL;
>> >              avformat_close_input(&pls->ctx);
>> > @@ -640,7 +645,24 @@ 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 (c->http_persistent && *pb && av_strstart(proto_name, "http",
>> > NULL)) {
>> > +        URLContext *uc = ffio_geturlcontext(*pb);
>> > +        av_assert0(uc);
>> > +        (*pb)->eof_reached = 0;
>> > +        ret = ff_http_do_new_request(uc, url);
>> > +        if (ret == AVERROR_EXIT) {
>> > +            ff_format_io_close(c->ctx, &c->playlist_pb);
>> > +            return ret;
>> > +        } else if (ret < 0) {
>> > +            av_log(s, AV_LOG_WARNING,
>> > +                "keepalive request failed for '%s', retrying with new
>> > connection: %s\n",
>> > +                url, av_err2str(ret));
>> > +            ff_format_io_close(c->ctx, pb);
>> > +            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;
>> > @@ -683,10 +705,27 @@ static int parse_playlist(HLSContext *c, const
>> > char *url,
>> >      char tmp_str[MAX_URL_SIZE];
>> >      struct segment *cur_init_section = NULL;
>> >
>> > +    if (!in && c->http_persistent && c->playlist_pb) {
>> > +        in = c->playlist_pb;
>> > +        URLContext *uc = ffio_geturlcontext(in);
>> > +        av_assert0(uc);
>> > +        in->eof_reached = 0;
>> > +        ret = ff_http_do_new_request(uc, url);
>> > +        if (ret == AVERROR_EXIT) {
>> > +            ff_format_io_close(c->ctx, &c->playlist_pb);
>> > +            return ret;
>> > +        } else if (ret < 0) {
>> > +            av_log(c->ctx, AV_LOG_WARNING,
>> > +                "keepalive request failed for '%s', retrying with new
>> > connection: %s\n",
>> > +                url, av_err2str(ret));
>> > +            ff_format_io_close(c->ctx, &c->playlist_pb);
>> > +            in = NULL;
>> > +        }
>> > +    }
>> > +
>> 
>> The above code seems duplicated, please put it in a common function.
> 
> 
> Sure, will do.
> 
> Thanks for reviewing!
> 
> 
>> 
>> 
>> [..]
>> --
>> Anssi Hannula
>> 
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
Aman Karmani Dec. 17, 2017, 10:41 p.m. UTC | #4
On Sun, Dec 17, 2017 at 1:21 PM Anssi Hannula <anssi.hannula@iki.fi> wrote:

> Aman Gupta kirjoitti 2017-12-17 22:41:
> > On Sun, Dec 17, 2017 at 12:34 PM Anssi Hannula <anssi.hannula@iki.fi>
> > wrote:
> >
> >> Hi!
> >>
> >> Aman Gupta kirjoitti 2017-12-13 02:35:
> >> > From: Aman Gupta <aman@tmm1.net>
> >> >
> >> > This teaches the HLS demuxer to use the HTTP protocols
> >> > multiple_requests=1 option, to take advantage of "Connection:
> >> > Keep-Alive" when downloading playlists and segments from the HLS
> >> > server.
> >> >
> >> > With the new option, you can avoid TCP connection and TLS negotiation
> >> > overhead, which is particularly beneficial when streaming via a
> >> > high-latency internet connection.
> >> >
> >> > Similar to the http_persistent option recently implemented in hlsenc.c
> >>
> >> Why is this implemented as an option instead of simply being always
> >> used
> >> by the demuxer?
> >
> >
> > I have no strong feeling on this either way. I've tested the new option
> > against a few different HLS servers and would be comfortable enabling
> > it by
> > default. I do think it's worth keeping as an option in case someone
> > wants
> > to turn it off for whatever reason.
>
> OK. The only other demuxer that reuses HTTP connections seems to be
> rtmphttp, and it does not have an option.
> I think I'd prefer no option here as well unless a use case is known,
> but I'm not too strongly against it so I guess it could stay (but
> default to true).


>
> Does anyone else have any thoughts?


>
>
> Also, what happens if the hostname in the URI varies, does it properly
> open a new connection then?


Yes, ff_http_do_new_request will return EINVAL because of an earlier patch
in the series, and so the demuxer will print "keepalive request failed" and
fallback to a new connection.

I also tested with an http server configured to allow only N keepalive
requests on a socket. An ECONNRESET is similarly caught and printed, and
then a new connection is made.

There might be other cases or specific webserver/proxy behaviors not
correctly accounted for, which I why I think the option should be left in
place for now in case anyone need to force http/1.0 compatible behavior.

Aman


>
> >
> >>
> >> > ---
> >> >  doc/demuxers.texi |  3 +++
> >> >  libavformat/hls.c | 68
> >> > +++++++++++++++++++++++++++++++++++++++++++++++++++----
> >> >  2 files changed, 67 insertions(+), 4 deletions(-)
> >> >
> >> > diff --git a/doc/demuxers.texi b/doc/demuxers.texi
> >> > index 73dc0feec1..18ff7101ac 100644
> >> > --- a/doc/demuxers.texi
> >> > +++ b/doc/demuxers.texi
> >> > @@ -316,6 +316,9 @@ segment index to start live streams at (negative
> >> > values are from the end).
> >> >  @item max_reload
> >> >  Maximum number of times a insufficient list is attempted to be
> >> > reloaded.
> >> >  Default value is 1000.
> >> > +
> >> > +@item http_persistent
> >> > +Use persistent HTTP connections. Applicable only for HTTP streams.
> >> >  @end table
> >> >
> >> >  @section image2
> >> > diff --git a/libavformat/hls.c b/libavformat/hls.c
> >> > index ab6ff187a6..5c1895c180 100644
> >> > --- a/libavformat/hls.c
> >> > +++ b/libavformat/hls.c
> >> > @@ -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,7 @@ struct playlist {
> >> >      AVIOContext pb;
> >> >      uint8_t* read_buffer;
> >> >      AVIOContext *input;
> >> > +    int input_read_done;
> >> >      AVFormatContext *parent;
> >> >      int index;
> >> >      AVFormatContext *ctx;
> >> > @@ -206,6 +208,8 @@ typedef struct HLSContext {
> >> >      int strict_std_compliance;
> >> >      char *allowed_extensions;
> >> >      int max_reload;
> >> > +    int http_persistent;
> >> > +    AVIOContext *playlist_pb;
> >> >  } HLSContext;
> >> >
> >> >  static int read_chomp_line(AVIOContext *s, char *buf, int maxlen)
> >> > @@ -256,6 +260,7 @@ 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->ctx) {
> >> >              pls->ctx->pb = NULL;
> >> >              avformat_close_input(&pls->ctx);
> >> > @@ -640,7 +645,24 @@ 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 (c->http_persistent && *pb && av_strstart(proto_name, "http",
> >> > NULL)) {
> >> > +        URLContext *uc = ffio_geturlcontext(*pb);
> >> > +        av_assert0(uc);
> >> > +        (*pb)->eof_reached = 0;
> >> > +        ret = ff_http_do_new_request(uc, url);
> >> > +        if (ret == AVERROR_EXIT) {
> >> > +            ff_format_io_close(c->ctx, &c->playlist_pb);
> >> > +            return ret;
> >> > +        } else if (ret < 0) {
> >> > +            av_log(s, AV_LOG_WARNING,
> >> > +                "keepalive request failed for '%s', retrying with new
> >> > connection: %s\n",
> >> > +                url, av_err2str(ret));
> >> > +            ff_format_io_close(c->ctx, pb);
> >> > +            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;
> >> > @@ -683,10 +705,27 @@ static int parse_playlist(HLSContext *c, const
> >> > char *url,
> >> >      char tmp_str[MAX_URL_SIZE];
> >> >      struct segment *cur_init_section = NULL;
> >> >
> >> > +    if (!in && c->http_persistent && c->playlist_pb) {
> >> > +        in = c->playlist_pb;
> >> > +        URLContext *uc = ffio_geturlcontext(in);
> >> > +        av_assert0(uc);
> >> > +        in->eof_reached = 0;
> >> > +        ret = ff_http_do_new_request(uc, url);
> >> > +        if (ret == AVERROR_EXIT) {
> >> > +            ff_format_io_close(c->ctx, &c->playlist_pb);
> >> > +            return ret;
> >> > +        } else if (ret < 0) {
> >> > +            av_log(c->ctx, AV_LOG_WARNING,
> >> > +                "keepalive request failed for '%s', retrying with new
> >> > connection: %s\n",
> >> > +                url, av_err2str(ret));
> >> > +            ff_format_io_close(c->ctx, &c->playlist_pb);
> >> > +            in = NULL;
> >> > +        }
> >> > +    }
> >> > +
> >>
> >> The above code seems duplicated, please put it in a common function.
> >
> >
> > Sure, will do.
> >
> > Thanks for reviewing!
> >
> >
> >>
> >>
> >> [..]
> >> --
> >> Anssi Hannula
> >>
> > _______________________________________________
> > ffmpeg-devel mailing list
> > ffmpeg-devel@ffmpeg.org
> > http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> --
> Anssi Hannula
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
Aman Karmani Dec. 20, 2017, 7:36 p.m. UTC | #5
On Sun, Dec 17, 2017 at 1:13 PM, Anssi Hannula <anssi.hannula@iki.fi> wrote:

> Aman Gupta kirjoitti 2017-12-17 22:41:
>
>> On Sun, Dec 17, 2017 at 12:34 PM Anssi Hannula <anssi.hannula@iki.fi>
>> wrote:
>>
>> Hi!
>>>
>>> Aman Gupta kirjoitti 2017-12-13 02:35:
>>> > From: Aman Gupta <aman@tmm1.net>
>>> >
>>> > This teaches the HLS demuxer to use the HTTP protocols
>>> > multiple_requests=1 option, to take advantage of "Connection:
>>> > Keep-Alive" when downloading playlists and segments from the HLS
>>> > server.
>>> >
>>> > With the new option, you can avoid TCP connection and TLS negotiation
>>> > overhead, which is particularly beneficial when streaming via a
>>> > high-latency internet connection.
>>> >
>>> > Similar to the http_persistent option recently implemented in hlsenc.c
>>>
>>> Why is this implemented as an option instead of simply being always used
>>> by the demuxer?
>>>
>>
>>
>> I have no strong feeling on this either way. I've tested the new option
>> against a few different HLS servers and would be comfortable enabling it
>> by
>> default. I do think it's worth keeping as an option in case someone wants
>> to turn it off for whatever reason.
>>
>
> OK. The only other demuxer that reuses HTTP connections seems to be
> rtmphttp, and it does not have an option.
> I think I'd prefer no option here as well unless a use case is known, but
> I'm not too strongly against it so I guess it could stay (but default to
> true).
>
> Does anyone else have any thoughts?
>

If no one objects, I will push this patchset with the requested changed in
a few days.

Aman


>
>
> Also, what happens if the hostname in the URI varies, does it properly
> open a new connection then?
>
>
>>
>>> > ---
>>> >  doc/demuxers.texi |  3 +++
>>> >  libavformat/hls.c | 68
>>> > +++++++++++++++++++++++++++++++++++++++++++++++++++----
>>> >  2 files changed, 67 insertions(+), 4 deletions(-)
>>> >
>>> > diff --git a/doc/demuxers.texi b/doc/demuxers.texi
>>> > index 73dc0feec1..18ff7101ac 100644
>>> > --- a/doc/demuxers.texi
>>> > +++ b/doc/demuxers.texi
>>> > @@ -316,6 +316,9 @@ segment index to start live streams at (negative
>>> > values are from the end).
>>> >  @item max_reload
>>> >  Maximum number of times a insufficient list is attempted to be
>>> > reloaded.
>>> >  Default value is 1000.
>>> > +
>>> > +@item http_persistent
>>> > +Use persistent HTTP connections. Applicable only for HTTP streams.
>>> >  @end table
>>> >
>>> >  @section image2
>>> > diff --git a/libavformat/hls.c b/libavformat/hls.c
>>> > index ab6ff187a6..5c1895c180 100644
>>> > --- a/libavformat/hls.c
>>> > +++ b/libavformat/hls.c
>>> > @@ -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,7 @@ struct playlist {
>>> >      AVIOContext pb;
>>> >      uint8_t* read_buffer;
>>> >      AVIOContext *input;
>>> > +    int input_read_done;
>>> >      AVFormatContext *parent;
>>> >      int index;
>>> >      AVFormatContext *ctx;
>>> > @@ -206,6 +208,8 @@ typedef struct HLSContext {
>>> >      int strict_std_compliance;
>>> >      char *allowed_extensions;
>>> >      int max_reload;
>>> > +    int http_persistent;
>>> > +    AVIOContext *playlist_pb;
>>> >  } HLSContext;
>>> >
>>> >  static int read_chomp_line(AVIOContext *s, char *buf, int maxlen)
>>> > @@ -256,6 +260,7 @@ 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->ctx) {
>>> >              pls->ctx->pb = NULL;
>>> >              avformat_close_input(&pls->ctx);
>>> > @@ -640,7 +645,24 @@ 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 (c->http_persistent && *pb && av_strstart(proto_name, "http",
>>> > NULL)) {
>>> > +        URLContext *uc = ffio_geturlcontext(*pb);
>>> > +        av_assert0(uc);
>>> > +        (*pb)->eof_reached = 0;
>>> > +        ret = ff_http_do_new_request(uc, url);
>>> > +        if (ret == AVERROR_EXIT) {
>>> > +            ff_format_io_close(c->ctx, &c->playlist_pb);
>>> > +            return ret;
>>> > +        } else if (ret < 0) {
>>> > +            av_log(s, AV_LOG_WARNING,
>>> > +                "keepalive request failed for '%s', retrying with new
>>> > connection: %s\n",
>>> > +                url, av_err2str(ret));
>>> > +            ff_format_io_close(c->ctx, pb);
>>> > +            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;
>>> > @@ -683,10 +705,27 @@ static int parse_playlist(HLSContext *c, const
>>> > char *url,
>>> >      char tmp_str[MAX_URL_SIZE];
>>> >      struct segment *cur_init_section = NULL;
>>> >
>>> > +    if (!in && c->http_persistent && c->playlist_pb) {
>>> > +        in = c->playlist_pb;
>>> > +        URLContext *uc = ffio_geturlcontext(in);
>>> > +        av_assert0(uc);
>>> > +        in->eof_reached = 0;
>>> > +        ret = ff_http_do_new_request(uc, url);
>>> > +        if (ret == AVERROR_EXIT) {
>>> > +            ff_format_io_close(c->ctx, &c->playlist_pb);
>>> > +            return ret;
>>> > +        } else if (ret < 0) {
>>> > +            av_log(c->ctx, AV_LOG_WARNING,
>>> > +                "keepalive request failed for '%s', retrying with new
>>> > connection: %s\n",
>>> > +                url, av_err2str(ret));
>>> > +            ff_format_io_close(c->ctx, &c->playlist_pb);
>>> > +            in = NULL;
>>> > +        }
>>> > +    }
>>> > +
>>>
>>> The above code seems duplicated, please put it in a common function.
>>>
>>
>>
>> Sure, will do.
>>
>> Thanks for reviewing!
>>
>>
>>
>>>
>>> [..]
>>> --
>>> Anssi Hannula
>>>
>>> _______________________________________________
>> ffmpeg-devel mailing list
>> ffmpeg-devel@ffmpeg.org
>> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>>
>
> --
> Anssi Hannula
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
Aman Karmani Dec. 22, 2017, 11:02 p.m. UTC | #6
On Wed, Dec 20, 2017 at 11:36 AM, Aman Gupta <ffmpeg@tmm1.net> wrote:

>
>
> On Sun, Dec 17, 2017 at 1:13 PM, Anssi Hannula <anssi.hannula@iki.fi>
> wrote:
>
>> Aman Gupta kirjoitti 2017-12-17 22:41:
>>
>>> On Sun, Dec 17, 2017 at 12:34 PM Anssi Hannula <anssi.hannula@iki.fi>
>>> wrote:
>>>
>>> Hi!
>>>>
>>>> Aman Gupta kirjoitti 2017-12-13 02:35:
>>>> > From: Aman Gupta <aman@tmm1.net>
>>>> >
>>>> > This teaches the HLS demuxer to use the HTTP protocols
>>>> > multiple_requests=1 option, to take advantage of "Connection:
>>>> > Keep-Alive" when downloading playlists and segments from the HLS
>>>> > server.
>>>> >
>>>> > With the new option, you can avoid TCP connection and TLS negotiation
>>>> > overhead, which is particularly beneficial when streaming via a
>>>> > high-latency internet connection.
>>>> >
>>>> > Similar to the http_persistent option recently implemented in hlsenc.c
>>>>
>>>> Why is this implemented as an option instead of simply being always used
>>>> by the demuxer?
>>>>
>>>
>>>
>>> I have no strong feeling on this either way. I've tested the new option
>>> against a few different HLS servers and would be comfortable enabling it
>>> by
>>> default. I do think it's worth keeping as an option in case someone wants
>>> to turn it off for whatever reason.
>>>
>>
>> OK. The only other demuxer that reuses HTTP connections seems to be
>> rtmphttp, and it does not have an option.
>> I think I'd prefer no option here as well unless a use case is known, but
>> I'm not too strongly against it so I guess it could stay (but default to
>> true).
>>
>> Does anyone else have any thoughts?
>>
>
> If no one objects, I will push this patchset with the requested changed in
> a few days.
>

Patchset applied.

I ran some more tests before pushing to master, with:
`-i https://bitdash-a.akamaihd.net/content/sintel/hls/video/4000kbit.m3u8
-t 30 -f null /dev/null`

-http_persistent 0 -http_multiple 0
    frame=  722 fps=296 q=-1.0 Lsize=N/A time=00:00:29.95 bitrate=N/A
speed=12.3x
    frame=  722 fps=324 q=-1.0 Lsize=N/A time=00:00:29.95 bitrate=N/A
speed=13.4x

-http_persistent 0 -http_multiple 1
    frame=  722 fps=304 q=-1.0 Lsize=N/A time=00:00:29.95 bitrate=N/A
speed=12.6x
    frame=  722 fps=325 q=-1.0 Lsize=N/A time=00:00:29.95 bitrate=N/A
speed=13.5x

-http_persistent 1 -http_multiple 0
    frame=  722 fps=682 q=-1.0 Lsize=N/A time=00:00:29.95 bitrate=N/A
speed=28.3x
    frame=  722 fps=713 q=-1.0 Lsize=N/A time=00:00:29.95 bitrate=N/A
speed=29.6x

-http_persistent 1 -http_multiple 1
    frame=  722 fps=0.0 q=-1.0 Lsize=N/A time=00:00:29.95 bitrate=N/A
speed=39.4x
    frame=  722 fps=0.0 q=-1.0 Lsize=N/A time=00:00:29.95 bitrate=N/A
speed=40.6x

In this case, the new default settings achieve a 3x throughput increase as
compared to the previous defaults.

Aman


>
> Aman
>
>
>>
>>
>> Also, what happens if the hostname in the URI varies, does it properly
>> open a new connection then?
>>
>>
>>>
>>>> > ---
>>>> >  doc/demuxers.texi |  3 +++
>>>> >  libavformat/hls.c | 68
>>>> > +++++++++++++++++++++++++++++++++++++++++++++++++++----
>>>> >  2 files changed, 67 insertions(+), 4 deletions(-)
>>>> >
>>>> > diff --git a/doc/demuxers.texi b/doc/demuxers.texi
>>>> > index 73dc0feec1..18ff7101ac 100644
>>>> > --- a/doc/demuxers.texi
>>>> > +++ b/doc/demuxers.texi
>>>> > @@ -316,6 +316,9 @@ segment index to start live streams at (negative
>>>> > values are from the end).
>>>> >  @item max_reload
>>>> >  Maximum number of times a insufficient list is attempted to be
>>>> > reloaded.
>>>> >  Default value is 1000.
>>>> > +
>>>> > +@item http_persistent
>>>> > +Use persistent HTTP connections. Applicable only for HTTP streams.
>>>> >  @end table
>>>> >
>>>> >  @section image2
>>>> > diff --git a/libavformat/hls.c b/libavformat/hls.c
>>>> > index ab6ff187a6..5c1895c180 100644
>>>> > --- a/libavformat/hls.c
>>>> > +++ b/libavformat/hls.c
>>>> > @@ -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,7 @@ struct playlist {
>>>> >      AVIOContext pb;
>>>> >      uint8_t* read_buffer;
>>>> >      AVIOContext *input;
>>>> > +    int input_read_done;
>>>> >      AVFormatContext *parent;
>>>> >      int index;
>>>> >      AVFormatContext *ctx;
>>>> > @@ -206,6 +208,8 @@ typedef struct HLSContext {
>>>> >      int strict_std_compliance;
>>>> >      char *allowed_extensions;
>>>> >      int max_reload;
>>>> > +    int http_persistent;
>>>> > +    AVIOContext *playlist_pb;
>>>> >  } HLSContext;
>>>> >
>>>> >  static int read_chomp_line(AVIOContext *s, char *buf, int maxlen)
>>>> > @@ -256,6 +260,7 @@ 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->ctx) {
>>>> >              pls->ctx->pb = NULL;
>>>> >              avformat_close_input(&pls->ctx);
>>>> > @@ -640,7 +645,24 @@ 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 (c->http_persistent && *pb && av_strstart(proto_name, "http",
>>>> > NULL)) {
>>>> > +        URLContext *uc = ffio_geturlcontext(*pb);
>>>> > +        av_assert0(uc);
>>>> > +        (*pb)->eof_reached = 0;
>>>> > +        ret = ff_http_do_new_request(uc, url);
>>>> > +        if (ret == AVERROR_EXIT) {
>>>> > +            ff_format_io_close(c->ctx, &c->playlist_pb);
>>>> > +            return ret;
>>>> > +        } else if (ret < 0) {
>>>> > +            av_log(s, AV_LOG_WARNING,
>>>> > +                "keepalive request failed for '%s', retrying with new
>>>> > connection: %s\n",
>>>> > +                url, av_err2str(ret));
>>>> > +            ff_format_io_close(c->ctx, pb);
>>>> > +            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;
>>>> > @@ -683,10 +705,27 @@ static int parse_playlist(HLSContext *c, const
>>>> > char *url,
>>>> >      char tmp_str[MAX_URL_SIZE];
>>>> >      struct segment *cur_init_section = NULL;
>>>> >
>>>> > +    if (!in && c->http_persistent && c->playlist_pb) {
>>>> > +        in = c->playlist_pb;
>>>> > +        URLContext *uc = ffio_geturlcontext(in);
>>>> > +        av_assert0(uc);
>>>> > +        in->eof_reached = 0;
>>>> > +        ret = ff_http_do_new_request(uc, url);
>>>> > +        if (ret == AVERROR_EXIT) {
>>>> > +            ff_format_io_close(c->ctx, &c->playlist_pb);
>>>> > +            return ret;
>>>> > +        } else if (ret < 0) {
>>>> > +            av_log(c->ctx, AV_LOG_WARNING,
>>>> > +                "keepalive request failed for '%s', retrying with new
>>>> > connection: %s\n",
>>>> > +                url, av_err2str(ret));
>>>> > +            ff_format_io_close(c->ctx, &c->playlist_pb);
>>>> > +            in = NULL;
>>>> > +        }
>>>> > +    }
>>>> > +
>>>>
>>>> The above code seems duplicated, please put it in a common function.
>>>>
>>>
>>>
>>> Sure, will do.
>>>
>>> Thanks for reviewing!
>>>
>>>
>>>
>>>>
>>>> [..]
>>>> --
>>>> Anssi Hannula
>>>>
>>>> _______________________________________________
>>> ffmpeg-devel mailing list
>>> ffmpeg-devel@ffmpeg.org
>>> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>>>
>>
>> --
>> Anssi Hannula
>> _______________________________________________
>> ffmpeg-devel mailing list
>> ffmpeg-devel@ffmpeg.org
>> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>>
>
>
diff mbox

Patch

diff --git a/doc/demuxers.texi b/doc/demuxers.texi
index 73dc0feec1..18ff7101ac 100644
--- a/doc/demuxers.texi
+++ b/doc/demuxers.texi
@@ -316,6 +316,9 @@  segment index to start live streams at (negative values are from the end).
 @item max_reload
 Maximum number of times a insufficient list is attempted to be reloaded.
 Default value is 1000.
+
+@item http_persistent
+Use persistent HTTP connections. Applicable only for HTTP streams.
 @end table
 
 @section image2
diff --git a/libavformat/hls.c b/libavformat/hls.c
index ab6ff187a6..5c1895c180 100644
--- a/libavformat/hls.c
+++ b/libavformat/hls.c
@@ -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,7 @@  struct playlist {
     AVIOContext pb;
     uint8_t* read_buffer;
     AVIOContext *input;
+    int input_read_done;
     AVFormatContext *parent;
     int index;
     AVFormatContext *ctx;
@@ -206,6 +208,8 @@  typedef struct HLSContext {
     int strict_std_compliance;
     char *allowed_extensions;
     int max_reload;
+    int http_persistent;
+    AVIOContext *playlist_pb;
 } HLSContext;
 
 static int read_chomp_line(AVIOContext *s, char *buf, int maxlen)
@@ -256,6 +260,7 @@  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->ctx) {
             pls->ctx->pb = NULL;
             avformat_close_input(&pls->ctx);
@@ -640,7 +645,24 @@  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 (c->http_persistent && *pb && av_strstart(proto_name, "http", NULL)) {
+        URLContext *uc = ffio_geturlcontext(*pb);
+        av_assert0(uc);
+        (*pb)->eof_reached = 0;
+        ret = ff_http_do_new_request(uc, url);
+        if (ret == AVERROR_EXIT) {
+            ff_format_io_close(c->ctx, &c->playlist_pb);
+            return ret;
+        } else if (ret < 0) {
+            av_log(s, AV_LOG_WARNING,
+                "keepalive request failed for '%s', retrying with new connection: %s\n",
+                url, av_err2str(ret));
+            ff_format_io_close(c->ctx, pb);
+            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;
@@ -683,10 +705,27 @@  static int parse_playlist(HLSContext *c, const char *url,
     char tmp_str[MAX_URL_SIZE];
     struct segment *cur_init_section = NULL;
 
+    if (!in && c->http_persistent && c->playlist_pb) {
+        in = c->playlist_pb;
+        URLContext *uc = ffio_geturlcontext(in);
+        av_assert0(uc);
+        in->eof_reached = 0;
+        ret = ff_http_do_new_request(uc, url);
+        if (ret == AVERROR_EXIT) {
+            ff_format_io_close(c->ctx, &c->playlist_pb);
+            return ret;
+        } else if (ret < 0) {
+            av_log(c->ctx, AV_LOG_WARNING,
+                "keepalive request failed for '%s', retrying with new connection: %s\n",
+                url, av_err2str(ret));
+            ff_format_io_close(c->ctx, &c->playlist_pb);
+            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 +735,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 (c->http_persistent)
+            c->playlist_pb = in;
+        else
+            close_in = 1;
 #else
         ret = open_in(c, &in, url);
         if (ret < 0)
@@ -1111,6 +1158,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) */
@@ -1316,7 +1366,7 @@  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;
 
@@ -1368,6 +1418,7 @@  reload:
             goto reload;
         }
 
+        v->input_read_done = 0;
         seg = current_segment(v);
 
         /* load/update Media Initialization Section, if any */
@@ -1405,7 +1456,11 @@  reload:
 
         return ret;
     }
-    ff_format_io_close(v->parent, &v->input);
+    if (c->http_persistent) {
+        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;
@@ -1666,6 +1721,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;
 }
@@ -1903,6 +1959,7 @@  static int recheck_discard_flags(AVFormatContext *s, int first)
         } else if (first && !cur_needed && pls->needed) {
             if (pls->input)
                 ff_format_io_close(pls->parent, &pls->input);
+            pls->input_read_done = 0;
             pls->needed = 0;
             changed = 1;
             av_log(s, AV_LOG_INFO, "No longer receiving playlist %d\n", i);
@@ -2137,6 +2194,7 @@  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;
         av_packet_unref(&pls->pkt);
         reset_packet(&pls->pkt);
         pls->pb.eof_reached = 0;
@@ -2191,6 +2249,8 @@  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 = 0}, 0, 1, FLAGS },
     {NULL}
 };