From patchwork Wed Jan 11 23:01:08 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Joel Cunningham X-Patchwork-Id: 2185 Delivered-To: ffmpegpatchwork@gmail.com Received: by 10.103.89.21 with SMTP id n21csp1011725vsb; Wed, 11 Jan 2017 15:01:22 -0800 (PST) X-Received: by 10.223.167.130 with SMTP id j2mr6041257wrc.154.1484175682246; Wed, 11 Jan 2017 15:01:22 -0800 (PST) Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id rm10si5715787wjb.144.2017.01.11.15.01.21; Wed, 11 Jan 2017 15:01:22 -0800 (PST) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@me.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=me.com Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 0EB9F68A1B4; Thu, 12 Jan 2017 01:01:12 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from pv38p43im-ztdg05071201.me.com (pv38p43im-ztdg05071201.me.com [17.133.183.8]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 98DCE68A18C for ; Thu, 12 Jan 2017 01:01:05 +0200 (EET) Received: from process-dkim-sign-daemon.pv38p43im-ztdg05071201.me.com by pv38p43im-ztdg05071201.me.com (Oracle Communications Messaging Server 7.0.5.38.0 64bit (built Feb 26 2016)) id <0OJN005001713O00@pv38p43im-ztdg05071201.me.com> for ffmpeg-devel@ffmpeg.org; Wed, 11 Jan 2017 23:01:11 +0000 (GMT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=me.com; s=4d515a; t=1484175671; bh=b572Mzup/FI7TUTCRRD6GLHHNYRAfH/cawy93eAn/kY=; h=From:Content-type:MIME-version:Subject:Message-id:Date:To; b=CUNe+jqOp0IsWGC5+428xef5FrOk1nGr1k2sHt8pgOO33uYol8aEl0o07hAU/wPh4 9pCDG7Vo4vMXv/nzYltcZLDANyai2QPpVSsrAHCBVBFMiODLRkmmdAppFxusVwdaie LYObjLNMkrK/Vc5ggc6sd9detc9TT1Sa+kcoIVBfuPqit9ZgnODn3UwMPO96dH52Fq rcyLz6+3HEKNa7BUKEFOTbpHEQA+cI3/CU91llLXuQkq5Xg8uOhlc18XMIuRkcNpo9 TIEWOTPITTBq3ze52tGRsReYQGXHMauUGo6TUci0YPxW4YxOXSZYD+lyTedLgTitNR 92Lpm5b6JgSBQ== Received: from [192.168.1.9] (75-130-6-224.dhcp.ftwo.tx.charter.com [75.130.6.224]) by pv38p43im-ztdg05071201.me.com (Oracle Communications Messaging Server 7.0.5.38.0 64bit (built Feb 26 2016)) with ESMTPSA id <0OJN00E3619X6420@pv38p43im-ztdg05071201.me.com> for ffmpeg-devel@ffmpeg.org; Wed, 11 Jan 2017 23:01:10 +0000 (GMT) X-Proofpoint-Virus-Version: vendor=fsecure engine=2.50.10432:,, definitions=2017-01-11_18:,, signatures=0 X-Proofpoint-Spam-Details: rule=notspam policy=default score=0 spamscore=0 clxscore=1034 suspectscore=0 malwarescore=0 phishscore=0 adultscore=0 bulkscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.0.1-1603290000 definitions=main-1701110302 From: Joel Cunningham MIME-version: 1.0 (Mac OS X Mail 10.2 \(3259\)) Message-id: <5C6D12FC-B117-4EF8-AA7C-320426C12D5D@me.com> Date: Wed, 11 Jan 2017 17:01:08 -0600 To: FFmpeg development discussions and patches X-Mailer: Apple Mail (2.3259) Subject: [FFmpeg-devel] [PATCH] HTTP: optimize forward seek performance X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Hi, I’ve been working on optimizing HTTP forward seek performance for ffmpeg and would like to contribute this patch into mainline ffmpeg. Please see the below patch for an explanation of the issue and proposed fix. I have provided evidence of the current performance issue and my sample MP4 so others can reproduce and observe the behavior. Files are located in Dropbox here: https://www.dropbox.com/sh/4q4ru8isdv22joj/AABU3XyXmgLMiEFqucf1LdZ3a?dl=0 GRMT0003.MP4 - test video file mac-ffplay-baseline.pcapng - wireshark capture of ffplay (49abd) playing the above test file on MacOS 10.12.2 from a remote NGINX server mac-ffplay-optimize-patch.pcapng - same ffplay setup but with patch applied ffplay_output.log - console output of ffplay with patch (loglevel debug) I’m happy to discuss this issue further if the below description doesn’t fully communicate the issue. Thanks, Joel From 89a3ed8aab9168313b4f7e83c00857f9b715ba4e Mon Sep 17 00:00:00 2001 From: Joel Cunningham Date: Wed, 11 Jan 2017 13:55:02 -0600 Subject: [PATCH] HTTP: optimize forward seek performance This commit optimizes HTTP forward seeks by advancing the stream on the current connection when the seek amount is within the current TCP window rather than closing the connection and opening a new one. This improves performance because with TCP flow control, a window's worth of data is always either in the local socket buffer already or in-flight from the sender. 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 inital 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: 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 for a total of ~20MB of TCP data communication. To support this feature, a new URL function has been added to get the stream buffer size from the TCP protocol. The stream advancement logic has been implemented in the HTTP layer since this the layer in charge of the seek and creating/destroying the TCP connections. 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 --- libavformat/avio.c | 7 ++++++ libavformat/http.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ libavformat/tcp.c | 21 +++++++++++++++++ libavformat/url.h | 8 +++++++ 4 files changed, 105 insertions(+) diff --git a/libavformat/avio.c b/libavformat/avio.c index 3606eb0..34dcf09 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_stream_size(URLContext *h) +{ + if (!h->prot->url_get_stream_size) + return -1; + return h->prot->url_get_stream_size(h); +} + int ffurl_shutdown(URLContext *h, int flags) { if (!h->prot->url_shutdown) diff --git a/libavformat/http.c b/libavformat/http.c index 944a6cf..0026168 100644 --- a/libavformat/http.c +++ b/libavformat/http.c @@ -1462,6 +1462,61 @@ static int http_close(URLContext *h) return ret; } +#define DISCARD_BUFFER_SIZE (256 * 1024) +static int http_stream_forward(HTTPContext *s, int amount) +{ + int len; + int ret; + int discard = amount; + int discard_buf_size; + uint8_t * discard_buf; + + /* advance local buffer first */ + len = FFMIN(discard, s->buf_end - s->buf_ptr); + if (len > 0) { + av_log(s, AV_LOG_DEBUG, "advancing input buffer by %d bytes\n", len); + s->buf_ptr += len; + discard -= len; + if (discard == 0) { + return amount; + } + } + + /* if underlying protocol is a stream with flow control and we are seeking + within the stream buffer size, it performs better to advance through the stream + rather than abort the connection and open a new one */ + len = ffurl_get_stream_size(s->hd); + av_log(s, AV_LOG_DEBUG, "stream buffer size: %d\n", len); + if ((len <= 0) || (discard > len)) { + return AVERROR(EINVAL); + } + + /* forward stream by discarding data till new position */ + discard_buf_size = FFMIN(discard, DISCARD_BUFFER_SIZE); + discard_buf = av_malloc(discard_buf_size); + if (!discard_buf) { + return AVERROR(ENOMEM); + } + + av_log(s, AV_LOG_DEBUG, "advancing stream by discarding %d bytes\n", discard); + while (discard > 0) { + ret = ffurl_read(s->hd, discard_buf, FFMIN(discard, discard_buf_size)); + if (ret > 0) { + discard -= ret; + } else { + ret = (!ret ? AVERROR_EOF : ff_neterrno()); + av_log(s, AV_LOG_DEBUG, "read error: %d\n", ret); + break; + } + } + av_freep(&discard_buf); + + if (discard == 0) { + return amount; + } + return ret; +} + static int64_t http_seek_internal(URLContext *h, int64_t off, int whence, int force_reconnect) { HTTPContext *s = h->priv_data; @@ -1493,6 +1548,20 @@ static int64_t http_seek_internal(URLContext *h, int64_t off, int whence, int fo if (s->off && h->is_streamed) return AVERROR(ENOSYS); + /* if seeking forward, see if we can satisfy the seek with our current connection */ + if ((s->off > old_off) && + (s->off - old_off <= INT32_MAX)) { + int amount = (int)(s->off - old_off); + int res; + + av_log(s, AV_LOG_DEBUG, "attempting stream forwarding, old: %lld, new: %lld bytes: %d\n", + old_off, s->off, amount); + res = http_stream_forward(s, amount); + if (res == amount) { + return off; + } + } + /* we save the old context in case the seek fails */ old_buf_size = s->buf_end - s->buf_ptr; memcpy(old_buf, s->buf_ptr, old_buf_size); diff --git a/libavformat/tcp.c b/libavformat/tcp.c index 25abafc..b73e500 100644 --- a/libavformat/tcp.c +++ b/libavformat/tcp.c @@ -265,6 +265,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(EINVAL); + } + #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, @@ -273,6 +293,7 @@ const URLProtocol ff_tcp_protocol = { .url_write = tcp_write, .url_close = tcp_close, .url_get_file_handle = tcp_get_file_handle, + .url_get_stream_size = 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..5e607c9 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_stream_size)(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 sizes (in bytes) of stream based URL buffer. + * + * @return size on success or <0 on error. + */ +int ffurl_get_stream_size(URLContext *h); + +/** * Signal the URLContext that we are done reading or writing the stream. * * @param h pointer to the resource