diff --git a/doc/protocols.texi b/doc/protocols.texi index 350c52a465..9c068075d5 100644 --- a/doc/protocols.texi +++ b/doc/protocols.texi @@ -473,6 +473,19 @@ Set the Referer header. Include 'Referer: URL' header in HTTP request. @item multiple_requests Use persistent connections if set to 1, default is 0. +@item initial_request_size +Limit the size of initial requests. This is useful when dealing with formats +that require frequent seeks during initial parsing. This lasts until the +demuxer makes a read request larger than this size (without a seek in between), +after which the implementation will continue using unbounded requests as usual. +Disabled (set to 0) by default. + +Note that if enabling this option, it's strongly recommended to also enable +the @option{multiple_requests} option, as well as setting +@option{short_seek_size} to the same value or higher. Doing so allows FFmpeg +to reuse a single HTTP connection wherever possible, even for formats like +MXF or MOV that require frequent small seeks during initial parsing. + @item post_data Set custom HTTP post data. diff --git a/libavformat/http.c b/libavformat/http.c index 8c98ea0a8f..b0803f26aa 100644 --- a/libavformat/http.c +++ b/libavformat/http.c @@ -146,6 +146,8 @@ typedef struct HTTPContext { unsigned int retry_after; int reconnect_max_retries; int reconnect_delay_total_max; + uint64_t initial_request_size; + int partial_requests; /* whether or not to limit requests to initial_request_size */ } HTTPContext; #define OFFSET(x) offsetof(HTTPContext, x) @@ -162,6 +164,7 @@ static const AVOption options[] = { { "user_agent", "override User-Agent header", OFFSET(user_agent), AV_OPT_TYPE_STRING, { .str = DEFAULT_USER_AGENT }, 0, 0, D }, { "referer", "override referer header", OFFSET(referer), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D }, { "multiple_requests", "use persistent connections", OFFSET(multiple_requests), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D | E }, + { "initial_request_size", "size (in bytes) of initial requests made during probing / header parsing", OFFSET(initial_request_size), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, INT64_MAX, D }, { "post_data", "set custom HTTP post data", OFFSET(post_data), AV_OPT_TYPE_BINARY, .flags = D | E }, { "mime_type", "export the MIME type", OFFSET(mime_type), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, AV_OPT_FLAG_EXPORT | AV_OPT_FLAG_READONLY }, { "http_version", "export the http response version", OFFSET(http_version), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, AV_OPT_FLAG_EXPORT | AV_OPT_FLAG_READONLY }, @@ -754,6 +757,7 @@ static int http_open(URLContext *h, const char *uri, int flags, else h->is_streamed = 1; + s->partial_requests = s->seekable != 0 && s->initial_request_size > 0; s->filesize = UINT64_MAX; s->location = av_strdup(uri); @@ -1455,6 +1459,9 @@ static int http_read_header(URLContext *h) if (s->seekable == -1 && s->is_mediagateway && s->filesize == 2000000000) h->is_streamed = 1; /* we can in fact _not_ seek */ + if (h->is_streamed) + s->partial_requests = 0; /* unable to use partial requests */ + // add any new cookies into the existing cookie string cookie_string(s->cookie_dict, &s->cookies); av_dict_free(&s->cookie_dict); @@ -1564,7 +1571,15 @@ static int http_connect(URLContext *h, const char *path, const char *local_path, // server supports seeking by analysing the reply headers. if (!has_header(s->headers, "\r\nRange: ") && !post && (s->off > 0 || s->end_off || s->seekable != 0)) { av_bprintf(&request, "Range: bytes=%"PRIu64"-", s->off); - if (s->end_off) + if (s->partial_requests && s->seekable != 0) { + uint64_t target_off = s->off + s->initial_request_size; + if (target_off < s->off) /* overflow */ + target_off = UINT64_MAX; + if (s->end_off) + target_off = FFMIN(target_off, s->end_off); + if (target_off != UINT64_MAX) + av_bprintf(&request, "%"PRId64, target_off - 1); + } else if (s->end_off) av_bprintf(&request, "%"PRId64, s->end_off - 1); av_bprintf(&request, "\r\n"); } @@ -1802,6 +1817,7 @@ static int http_read_stream(URLContext *h, uint8_t *buf, int size) AVDictionary *options = NULL; if (s->willclose) ffurl_closep(&s->hd); + s->partial_requests = 0; /* continue streaming uninterrupted from now on */ read_ret = http_open_cnx(h, &options); av_dict_free(&options); if (read_ret == 0)