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]

Reply via email to