avformat/http: allow limiting initial request size

Sometimes, HTTP sources require a lot of seeking during probing / header
parsing (especially for formats like MXF). Currently, we need to completely
tear down and re-establish the connection most times this happens, which puts
a lot of stress on the network stack and also results in transmission of
possibly many unnecessary bytes.

This patch adds an option to allow FFmpeg to request partial ranges during
the initialization stage. This is done until the initial request size is fully
read, after which we fall back to the normal behavior (i.e. infinite streaming
via an unbounded request).

The usefulness of this is limited without also specifying -multiple_requests 1,
since otherwise there is little point to requesting partial ranges to begin
with. (However, it is semantically independent, so we keep it that way.)
This commit is contained in:
Niklas Haas
2026-01-23 11:19:17 +01:00
committed by Niklas Haas
parent e03b034e45
commit 4f5d91e43f
2 changed files with 30 additions and 1 deletions

View File

@@ -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.

View File

@@ -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)