[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