[FFmpeg-devel] [PATCH] HTTP: optimize forward seek performance

Joel Cunningham joel.cunningham at me.com
Thu Jan 12 17:25:47 EET 2017


> On Jan 12, 2017, at 6:47 AM, Ronald S. Bultje <rsbultje at gmail.com> wrote:
> 
> Hi Joel,
> 
> On Wed, Jan 11, 2017 at 6:01 PM, Joel Cunningham <joel.cunningham at me.com>
> wrote:
> 
>> 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 <joel.cunningham at me.com>
>> 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(+)
> 
> 
> Very interesting. There's some minor stylistic nits (double brackets where
> there shouldn't be, I didn't check super-closely for that), but overall
> this is a pretty thoughtful patch in what it's trying to do.

My apologies for any stylistic issues, this is my first ffmpeg change and I’m still getting familiar with the style

> 
> As for Windows, I'm surprised that there wouldn't be any API to get the
> real current window size. I mean, I understand that they would decrease
> window size on-demand, but I would expect them to do that w/o throwing out
> already-downloaded data - so essentially I expect delayed auto-tuning. In
> that case, getting the current window size should be trivial and valid
> until the next read. Very strange. I suppose no workarounds (perhaps with
> windows-specific API) exist? Also, what is the practical implication? Does
> it work as before? Or better but not optimal? Or worse?

For Windows, when TCP receive window auto-tuning is enabled, SO_RCVBUF returns 8192 regardless of what the window value is.  According TCP RFCs, a receiver is not allowed to decrease the window, so the stack will only grow the window.  Typically in Windows it starts out at 17KB (on a WiFi link) and then grows upwards of 256KB depending on bandwidth-product delay.  If you manually set SO_RCVBUF via setsockopt, the TCP window is set to the specified value, auto-tuning is disabled and you can retrieve the correct value at anytime.  I didn’t find any windows specific API that worked when auto-tuning is enabled.  This issue isn’t as bad on Windows platforms because the window starts out small and grows very conservatively, thus less data is discarded and re-sent during each seek.

In terms of practical implications, Windows platforms will behave the same as before this patch.  The call to ffurl_get_stream_size() returns an error and then http_seek_internal proceeds with closing the connection and opening a new one at the new offset.  I did add support for checking if s->recv_buffer_size had been set in the TCP layer, which would mean the window had been manually set and retrieving the buffer size should be correct.  Unfortunately there is not currently a way to set recv_buffer_size for an HTTP protocol via options, so that would need an additional patch, but I tested with hardcoding s->recv_buffer_size to test the feature on Windows.

> 
> I'm wondering if there's some way to make the interaction between tcp and
> http less awkward. Similar problems exist between rtp/udp, where we want
> deeper protocol integration and the generic API basically inhibits us.
> Probably out of scope for this patch and not a terribly big deal because
> the URL API is currently not exposed, but we eventually want to expose this
> API (IMO) so at some point this will need some more thoughts.
> 

I agree there is some clunkiness in the abstraction since TCP specific things have to be generalized into a concept that could fit into other protocols, thus I came up with “stream size” nomenclature for the function.

> If nobody else has comments, I'm inclined to accept this patch (I'll fix
> the cosmetics myself).
> 

Joel


More information about the ffmpeg-devel mailing list