[FFmpeg-devel] [PATCH] HTTP: improve performance by reducing forward seeks

Joel Cunningham joel.cunningham at me.com
Mon Feb 13 17:57:13 EET 2017


Friendly ping!  Any issues receiving this updated patch (submitted with git send-email)?  Anyone try it out yet?

Thanks,

Joel


> On Jan 30, 2017, at 10:00 AM, Joel Cunningham <joel.cunningham at me.com> wrote:
> 
> This commit optimizes HTTP performance by reducing forward seeks, instead
> favoring a read-ahead and discard on the current connection (referred to
> as a short seek) for seeks that are within a TCP window's worth of data.
> This improves performance because with TCP flow control, a window's worth
> of data will be in the local socket buffer already or in-flight from the
> sender once congestion control on the sender is fully utilizing the window.
> 
> Note: this approach doesn't attempt to differentiate from a newly opened
> connection which may not be fully utilizing the window due to congestion
> control vs one that is. The receiver can't get at this information, so we
> assume worst case; that full window is in use (we did advertise it after all)
> and that data could be in-flight
> 
> The previous behavior of closing the connection, then opening a new
> with a new HTTP range value results in a massive amounts of discarded
> and re-sent data when large TCP windows are used.  This has been observed
> on MacOS/iOS which starts with an initial window of 256KB and grows up to
> 1MB depending on the bandwidth-product delay.
> 
> When seeking within a window's worth of data and we close the connection,
> then open a new one within the same window's worth of data, we discard
> from the current offset till the end of the window.  Then on the new
> connection the server ends up re-sending the previous data from new
> offset till the end of old window.
> 
> Example (assumes full window utilization):
> 
> TCP window size: 64KB
> Position: 32KB
> Forward seek position: 40KB
> 
>      *                      (Next window)
> 32KB |--------------| 96KB |---------------| 160KB
>        *
>  40KB |---------------| 104KB
> 
> Re-sent amount: 96KB - 40KB = 56KB
> 
> For a real world test example, I have MP4 file of ~25MB, which ffplay
> only reads ~16MB and performs 177 seeks. With current ffmpeg, this results
> in 177 HTTP GETs and ~73MB worth of TCP data communication.  With this
> patch, ffmpeg issues 4 HTTP GETs and 3 seeks for a total of ~22MB of TCP data
> communication.
> 
> To support this feature, the short seek logic in avio_seek() has been
> extended to call a function to get the short seek threshold value.  This
> callback has been plumbed to the URLProtocol structure, which now has
> infrastructure in HTTP and TCP to get the underlying receiver window size
> via SO_RCVBUF.  If the underlying URL and protocol don't support returning
> a short seek threshold, the default s->short_seek_threshold is used
> 
> This feature has been tested on Windows 7 and MacOS/iOS.  Windows support
> is slightly complicated by the fact that when TCP window auto-tuning is
> enabled, SO_RCVBUF doesn't report the real window size, but it does if
> SO_RCVBUF was manually set (disabling auto-tuning). So we can only use
> this optimization on Windows in the later case
> 
> Signed-off-by: Joel Cunningham <joel.cunningham at me.com>
> ---
> libavformat/avio.c    |  7 +++++++
> libavformat/avio.h    |  6 ++++++
> libavformat/aviobuf.c | 19 ++++++++++++++++++-
> libavformat/http.c    |  8 ++++++++
> libavformat/tcp.c     | 21 +++++++++++++++++++++
> libavformat/url.h     |  8 ++++++++
> 6 files changed, 68 insertions(+), 1 deletion(-)
> 
> diff --git a/libavformat/avio.c b/libavformat/avio.c
> index 3606eb0..62233a6 100644
> --- a/libavformat/avio.c
> +++ b/libavformat/avio.c
> @@ -645,6 +645,13 @@ int ffurl_get_multi_file_handle(URLContext *h, int **handles, int *numhandles)
>     return h->prot->url_get_multi_file_handle(h, handles, numhandles);
> }
> 
> +int ffurl_get_short_seek(URLContext *h)
> +{
> +    if (!h->prot->url_get_short_seek)
> +        return AVERROR(ENOSYS);
> +    return h->prot->url_get_short_seek(h);
> +}
> +
> int ffurl_shutdown(URLContext *h, int flags)
> {
>     if (!h->prot->url_shutdown)
> diff --git a/libavformat/avio.h b/libavformat/avio.h
> index e2cb4af..8040094 100644
> --- a/libavformat/avio.h
> +++ b/libavformat/avio.h
> @@ -313,6 +313,12 @@ typedef struct AVIOContext {
>      */
>     enum AVIODataMarkerType current_type;
>     int64_t last_time;
> +
> +    /**
> +     * A callback that is used instead of short_seek_threshold.
> +     * This is current internal only, do not use from outside.
> +     */
> +    int (*short_seek_get)(void *opaque);
> } AVIOContext;
> 
> /**
> diff --git a/libavformat/aviobuf.c b/libavformat/aviobuf.c
> index bf7e5f8..4ade4d0 100644
> --- a/libavformat/aviobuf.c
> +++ b/libavformat/aviobuf.c
> @@ -119,6 +119,7 @@ int ffio_init_context(AVIOContext *s,
>     s->ignore_boundary_point = 0;
>     s->current_type          = AVIO_DATA_MARKER_UNKNOWN;
>     s->last_time             = AV_NOPTS_VALUE;
> +    s->short_seek_get        = NULL;
> 
>     return 0;
> }
> @@ -233,6 +234,7 @@ int64_t avio_seek(AVIOContext *s, int64_t offset, int whence)
>     int64_t pos;
>     int force = whence & AVSEEK_FORCE;
>     int buffer_size;
> +    int short_seek;
>     whence &= ~AVSEEK_FORCE;
> 
>     if(!s)
> @@ -254,13 +256,21 @@ int64_t avio_seek(AVIOContext *s, int64_t offset, int whence)
>     if (offset < 0)
>         return AVERROR(EINVAL);
> 
> +    if (s->short_seek_get) {
> +        short_seek = s->short_seek_get(s->opaque);
> +        /* fallback to default short seek */
> +        if (short_seek <= 0)
> +            short_seek = s->short_seek_threshold;
> +    } else
> +        short_seek = s->short_seek_threshold;
> +
>     offset1 = offset - pos; // "offset1" is the relative offset from the beginning of s->buffer
>     if (!s->must_flush && (!s->direct || !s->seek) &&
>         offset1 >= 0 && offset1 <= buffer_size - s->write_flag) {
>         /* can do the seek inside the buffer */
>         s->buf_ptr = s->buffer + offset1;
>     } else if ((!s->seekable ||
> -               offset1 <= buffer_size + s->short_seek_threshold) &&
> +               offset1 <= buffer_size + short_seek) &&
>                !s->write_flag && offset1 >= 0 &&
>                (!s->direct || !s->seek) &&
>               (whence != SEEK_END || force)) {
> @@ -858,6 +868,12 @@ static int64_t io_seek(void *opaque, int64_t offset, int whence)
>     return ffurl_seek(internal->h, offset, whence);
> }
> 
> +static int io_short_seek(void *opaque)
> +{
> +    AVIOInternal *internal = opaque;
> +    return ffurl_get_short_seek(internal->h);
> +}
> +
> static int io_read_pause(void *opaque, int pause)
> {
>     AVIOInternal *internal = opaque;
> @@ -919,6 +935,7 @@ int ffio_fdopen(AVIOContext **s, URLContext *h)
>         (*s)->read_pause = io_read_pause;
>         (*s)->read_seek  = io_read_seek;
>     }
> +    (*s)->short_seek_get = io_short_seek;
>     (*s)->av_class = &ff_avio_class;
>     return 0;
> fail:
> diff --git a/libavformat/http.c b/libavformat/http.c
> index 944a6cf..b354c87 100644
> --- a/libavformat/http.c
> +++ b/libavformat/http.c
> @@ -1524,6 +1524,12 @@ static int http_get_file_handle(URLContext *h)
>     return ffurl_get_file_handle(s->hd);
> }
> 
> +static int http_get_short_seek(URLContext *h)
> +{
> +    HTTPContext *s = h->priv_data;
> +    return ffurl_get_short_seek(s->hd);
> +}
> +
> #define HTTP_CLASS(flavor)                          \
> static const AVClass flavor ## _context_class = {   \
>     .class_name = # flavor,                         \
> @@ -1545,6 +1551,7 @@ const URLProtocol ff_http_protocol = {
>     .url_seek            = http_seek,
>     .url_close           = http_close,
>     .url_get_file_handle = http_get_file_handle,
> +    .url_get_short_seek  = http_get_short_seek,
>     .url_shutdown        = http_shutdown,
>     .priv_data_size      = sizeof(HTTPContext),
>     .priv_data_class     = &http_context_class,
> @@ -1564,6 +1571,7 @@ const URLProtocol ff_https_protocol = {
>     .url_seek            = http_seek,
>     .url_close           = http_close,
>     .url_get_file_handle = http_get_file_handle,
> +    .url_get_short_seek  = http_get_short_seek,
>     .url_shutdown        = http_shutdown,
>     .priv_data_size      = sizeof(HTTPContext),
>     .priv_data_class     = &https_context_class,
> diff --git a/libavformat/tcp.c b/libavformat/tcp.c
> index 5f00ba7..3055e48 100644
> --- a/libavformat/tcp.c
> +++ b/libavformat/tcp.c
> @@ -266,6 +266,26 @@ static int tcp_get_file_handle(URLContext *h)
>     return s->fd;
> }
> 
> +static int tcp_get_window_size(URLContext *h)
> +{
> +    TCPContext *s = h->priv_data;
> +    int avail;
> +    int avail_len = sizeof(avail);
> +
> +#if HAVE_WINSOCK2_H
> +    /* SO_RCVBUF with winsock only reports the actual TCP window size when
> +    auto-tuning has been disabled via setting SO_RCVBUF */
> +    if (s->recv_buffer_size < 0) {
> +        return AVERROR(ENOSYS);
> +    }
> +#endif
> +
> +    if (getsockopt(s->fd, SOL_SOCKET, SO_RCVBUF, &avail, &avail_len)) {
> +        return ff_neterrno();
> +    }
> +    return avail;
> +}
> +
> const URLProtocol ff_tcp_protocol = {
>     .name                = "tcp",
>     .url_open            = tcp_open,
> @@ -274,6 +294,7 @@ const URLProtocol ff_tcp_protocol = {
>     .url_write           = tcp_write,
>     .url_close           = tcp_close,
>     .url_get_file_handle = tcp_get_file_handle,
> +    .url_get_short_seek  = tcp_get_window_size,
>     .url_shutdown        = tcp_shutdown,
>     .priv_data_size      = sizeof(TCPContext),
>     .flags               = URL_PROTOCOL_FLAG_NETWORK,
> diff --git a/libavformat/url.h b/libavformat/url.h
> index 5c50245..910f1e0 100644
> --- a/libavformat/url.h
> +++ b/libavformat/url.h
> @@ -84,6 +84,7 @@ typedef struct URLProtocol {
>     int (*url_get_file_handle)(URLContext *h);
>     int (*url_get_multi_file_handle)(URLContext *h, int **handles,
>                                      int *numhandles);
> +    int (*url_get_short_seek)(URLContext *h);
>     int (*url_shutdown)(URLContext *h, int flags);
>     int priv_data_size;
>     const AVClass *priv_data_class;
> @@ -249,6 +250,13 @@ int ffurl_get_file_handle(URLContext *h);
> int ffurl_get_multi_file_handle(URLContext *h, int **handles, int *numhandles);
> 
> /**
> + * Return the current short seek threshold value for this URL.
> + *
> + * @return threshold (>0) on success or <=0 on error.
> + */
> +int ffurl_get_short_seek(URLContext *h);
> +
> +/**
>  * Signal the URLContext that we are done reading or writing the stream.
>  *
>  * @param h pointer to the resource
> -- 
> 2.10.0
> 
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel at ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel



More information about the ffmpeg-devel mailing list