PR #25279 opened by Forgejo_Fairy URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25279 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25279.patch
With libcurl-backed HLS segments, automatic `http_multiple` detection reads the writable integer `http_version` request preference. Its serialized value never matches the response-version strings expected by HLS, so automatic prefetching remains disabled even for HTTP/1.1 and HTTP/2. Export a separate read-only `http_response_version`, captured with CURLINFO_HTTP_VERSION from the initial successful response after redirects. Publish the string after the opening probe completes and keep it immutable during subsequent requests. HLS reads this option first, retaining the existing native HTTP fallback, the HTTP/1.0 safeguard, and explicit user overrides. Recognize HTTP/1.1, HTTP/2, and HTTP/3 response values and document the new export. Validation on ARM64 with libcurl 8.5.0 covered native and libcurl HTTP/1.0/1.1, segment redirects, explicit `http_multiple` overrides, and libcurl HTTP/2 over TLS. Automatic libcurl prefetching changes from 0 to 1 for HTTP/1.1 and HTTP/2; HTTP/1.0 remains disabled. All HLS cases produced identical packet counts and packet-payload SHA-256 before and after the fix. Seven enabled relevant FATE tests passed: mkdir, rename, http-status-line, noproxy, url, seek_utils, and hls-self-variant-demux. HTTP/3 was not tested because the installed libcurl lacks support. Reproduction scripts, build instructions, and recorded results are preserved separately on [fairy/issue24811-tooling](https://code.ffmpeg.org/Forgejo_Fairy/FFmpeg/src/branch/fairy/issue24811-tooling/tools/issue24811/README.md). Fixes #24811. >From 126e5913698b955bc344a6092cad9d531063f975 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Wed, 21 Oct 2026 01:07:29 +0000 Subject: [PATCH] avformat/hls: use the negotiated HTTP version with libcurl Export the initial successful libcurl response version separately from the writable http_version request preference. Capture CURLINFO_HTTP_VERSION in the loop thread and publish an immutable string after the open probe completes. Use this response version for automatic HLS segment prefetching, retaining the native HTTP fallback and the HTTP/1.0 safeguard. Recognize HTTP/2 and HTTP/3 response versions as well. Fixes: #24811 Assisted-by: Fairy --- doc/demuxers.texi | 2 +- doc/protocols.texi | 6 ++++++ libavformat/hls.c | 8 ++++++-- libavformat/libcurl.c | 21 +++++++++++++++++++++ 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/doc/demuxers.texi b/doc/demuxers.texi index 76100551931..f076e4fa053 100644 --- a/doc/demuxers.texi +++ b/doc/demuxers.texi @@ -607,7 +607,7 @@ Enabled by default. @item http_multiple Use multiple HTTP connections for downloading HTTP segments. -Enabled by default for HTTP/1.1 servers. +Enabled by default for HTTP/1.1 and later servers. @item http_seekable Use HTTP partial requests for downloading HTTP segments. diff --git a/doc/protocols.texi b/doc/protocols.texi index 55e22b1bbe5..f45352772d1 100644 --- a/doc/protocols.texi +++ b/doc/protocols.texi @@ -1194,6 +1194,12 @@ setting it to @code{0} uses a fresh connection per transfer. HTTP version to use: @code{auto} (default), @code{1.0}, @code{1.1}, @code{2}, @code{2tls}, @code{2-prior-knowledge}, @code{3} or @code{3only}. +@item http_response_version +Export the HTTP version of the initial successful response, after redirects. +This read-only option is @code{1.0}, @code{1.1}, @code{2.0} or @code{3.0}, +or empty if the version is unknown. It does not change on subsequent requests +made for seeking or retrying. + @item buffer_size Size in bytes of the receive buffer. Default is 4 MiB. diff --git a/libavformat/hls.c b/libavformat/hls.c index 519d3d852fd..26b4f56aa50 100644 --- a/libavformat/hls.c +++ b/libavformat/hls.c @@ -1940,9 +1940,13 @@ restart: if (c->http_multiple == -1) { uint8_t *http_version_opt = NULL; - int r = av_opt_get(v->input, "http_version", AV_OPT_SEARCH_CHILDREN, &http_version_opt); + int r = av_opt_get(v->input, "http_response_version", AV_OPT_SEARCH_CHILDREN, &http_version_opt); + if (r == AVERROR_OPTION_NOT_FOUND) + r = av_opt_get(v->input, "http_version", AV_OPT_SEARCH_CHILDREN, &http_version_opt); if (r >= 0) { - c->http_multiple = (!strncmp((const char *)http_version_opt, "1.1", 3) || !strncmp((const char *)http_version_opt, "2.0", 3)); + c->http_multiple = (!strncmp((const char *)http_version_opt, "1.1", 3) || + !strncmp((const char *)http_version_opt, "2.0", 3) || + !strncmp((const char *)http_version_opt, "3.0", 3)); av_freep(&http_version_opt); } } diff --git a/libavformat/libcurl.c b/libavformat/libcurl.c index fc487602db9..d7a81ea7ca4 100644 --- a/libavformat/libcurl.c +++ b/libavformat/libcurl.c @@ -134,6 +134,7 @@ struct CurlContext { int max_redirects; int multiple_requests; int http_version; + char *http_response_version; /* initial response version (output) */ int64_t buffer_size; int64_t request_size; int64_t initial_request_size; @@ -175,6 +176,7 @@ struct CurlContext { /* Probe result. Set by the loop thread, read by url_open() once probed. */ int probed; int probe_ok; /* the first reply can be read */ + long probe_http_version; int stream_ok; int seekable; int64_t content_size; @@ -588,6 +590,7 @@ static size_t header_callback(char *ptr, size_t size, size_t nitems, void *userd * for compatibility with http.c. */ if (!c->probed) { const char *eff = NULL; + curl_easy_getinfo(c->easy, CURLINFO_HTTP_VERSION, &c->probe_http_version); if (curl_easy_getinfo(c->easy, CURLINFO_EFFECTIVE_URL, &eff) == CURLE_OK && eff) { char *dup = av_strdup(eff); @@ -1509,6 +1512,23 @@ static int libcurl_open(URLContext *h, const char *url, int flags, if (ret < 0) goto fail; + /* Publish the first response's version only after the probe completes. + * Keep it immutable while the loop handles subsequent requests. */ + const char *version = NULL; + switch (c->probe_http_version) { + case CURL_HTTP_VERSION_1_0: version = "1.0"; break; + case CURL_HTTP_VERSION_1_1: version = "1.1"; break; + case CURL_HTTP_VERSION_2_0: version = "2.0"; break; + case CURL_HTTP_VERSION_3: version = "3.0"; break; + } + if (version) { + c->http_response_version = av_strdup(version); + if (!c->http_response_version) { + ret = AVERROR(ENOMEM); + goto fail; + } + } + pthread_mutex_lock(&c->mutex); h->is_streamed = !c->seekable; pthread_mutex_unlock(&c->mutex); @@ -1744,6 +1764,7 @@ static const AVOption options[] = { { "buffer_size", "receive buffer size in bytes", OFFSET(buffer_size), AV_OPT_TYPE_INT64, { .i64 = CURL_DEFAULT_BUFFER_SIZE }, CURL_MAX_WRITE_SIZE, INT_MAX, D }, { "request_size", "split a transfer into ranged requests of at most this many bytes (0 = unlimited)", OFFSET(request_size), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, INT64_MAX, D }, { "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 }, + { "http_response_version", "export the initial HTTP response version", OFFSET(http_response_version), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, AV_OPT_FLAG_EXPORT | AV_OPT_FLAG_READONLY }, { "http_version", "HTTP version to use", OFFSET(http_version), AV_OPT_TYPE_INT, { .i64 = CURL_HTTP_VERSION_NONE }, 0, INT_MAX, D, .unit = "http_version" }, { "auto", "negotiate the best supported version", 0, AV_OPT_TYPE_CONST, { .i64 = CURL_HTTP_VERSION_NONE }, 0, 0, D, .unit = "http_version" }, { "1.0", "HTTP/1.0", 0, AV_OPT_TYPE_CONST, { .i64 = CURL_HTTP_VERSION_1_0 }, 0, 0, D, .unit = "http_version" }, -- 2.54.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
