PR #24359 opened by Niklas Haas (haasn)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24359
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24359.patch

Since libcurl.c doesn't have to care about the exact number of bytes read,
we just perform the seek but defer the actual `start_request()` call until
the next on_done() callback, while simply discarding all data read in the
meantime.

Sponsored-by: nxtedition AB
Signed-off-by: Niklas Haas <[email protected]>


>From c386d347d1038ad675de669a7a3c4637c37d9605 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Wed, 24 Jun 2026 19:21:41 +0200
Subject: [PATCH] avformat/libcurl: implement -short_seek_size

Since libcurl.c doesn't have to care about the exact number of bytes read,
we just perform the seek but defer the actual `start_request()` call until
the next on_done() callback, while simply discarding all data read in the
meantime.

Sponsored-by: nxtedition AB
Signed-off-by: Niklas Haas <[email protected]>
---
 doc/protocols.texi    |  8 +++++++
 libavformat/libcurl.c | 54 +++++++++++++++++++++++++++++++++++++++----
 2 files changed, 58 insertions(+), 4 deletions(-)

diff --git a/doc/protocols.texi b/doc/protocols.texi
index 9790317d28..af0d006762 100644
--- a/doc/protocols.texi
+++ b/doc/protocols.texi
@@ -1166,9 +1166,17 @@ read request larger than this size (without a seek in 
between), after which
 the implementation will continue using requests as usual. Disabled (set to 0)
 by default.
 
+Note that if enabling this option, it's strongly recommended to also set
+@option{short_seek_size} to the same value or higher.
+
 @item max_retries
 Maximum number of retries after a recoverable error on a seekable transfer.
 Default is @code{5}.
+
+@item short_seek_size
+Set the threshold, in bytes, for when a readahead should be preferred over a 
seek and
+new HTTP request. This is useful, for example, to make sure the same connection
+is used for reading large video packets with small audio packets in between.
 @end table
 
 For more information see: @url{https://curl.se/libcurl/}.
diff --git a/libavformat/libcurl.c b/libavformat/libcurl.c
index a2f7e6201e..b44c4516ea 100644
--- a/libavformat/libcurl.c
+++ b/libavformat/libcurl.c
@@ -120,6 +120,7 @@ struct CurlContext {
     int64_t         buffer_size;
     int64_t         request_size;
     int64_t         initial_request_size;
+    int64_t         short_seek_size;
     int             max_retries;
 
     int64_t         logical_pos; /* next byte url_read() will return, caller 
side */
@@ -131,6 +132,7 @@ struct CurlContext {
     int64_t         request_end;     /* expected end of request, or -1 if 
unknown */
     int             retry_count;     /* consecutive recoverable failures */
     int             is_initial;      /* using reduced request size */
+    int             seek_queued;     /* soft seeking; drain remaining bytes 
until done */
 
     /* Per-response-block header scratch, loop thread only. */
     int             hdr_accept_ranges;
@@ -211,6 +213,11 @@ static size_t write_callback(char *ptr, size_t size, 
size_t nmemb, void *userdat
         return CURL_WRITEFUNC_ERROR;
     }
 
+    if (c->seek_queued) {
+        pthread_mutex_unlock(&c->mutex);
+        return bytes; /* discard */
+    }
+
     space = av_fifo_can_write(c->fifo);
     if (space < bytes) {
         /* pause the transfer and wait for the consumer to drain. */
@@ -487,6 +494,13 @@ static void on_done(CurlContext *c, CURLcode code)
     if (aborted)
         return;
 
+    if (c->seek_queued && !aborted) {
+        /* previous soft seek drain finished; can start new request now */
+        c->seek_queued = 0;
+        start_request(c);
+        return;
+    }
+
     if (code == CURLE_OK && c->stream_ok) {
         c->retry_count = 0;
         int64_t file_end = c->content_size > 0 ? c->content_size - 1 : -1;
@@ -531,6 +545,20 @@ static void on_done(CurlContext *c, CURLcode code)
 /* event loop thread + command queue                                         */
 /* ------------------------------------------------------------------------- */
 
+static int test_short_seek(CurlContext *c)
+{
+    if (c->seek_queued)
+        return 1; /* short seek already queued */
+
+    if (c->short_seek_size <= 0 || /* short seek disabled */
+        c->request_end < 0)        /* content size not known */
+        return 0;
+
+    const int64_t total = c->request_end - c->request_start + 1;
+    const int64_t remaining = total - c->request_received;
+    return remaining <= c->short_seek_size;
+}
+
 static void execute_command(CurlLoop *loop, CurlCmd *cmd)
 {
     CurlContext *c = cmd->ctx;
@@ -553,7 +581,13 @@ static void execute_command(CurlLoop *loop, CurlCmd *cmd)
         curl_easy_pause(c->easy, CURLPAUSE_CONT);
         break;
     case CMD_SEEK:
-        if (c->active) {
+        if (c->active && test_short_seek(c)) {
+            c->seek_queued = 1;
+            if (c->paused) {
+                curl_easy_pause(c->easy, CURLPAUSE_CONT);
+                c->paused = 0;
+            }
+        } else if (c->active) {
             curl_multi_remove_handle(loop->multi, c->easy);
             c->active = 0;
         }
@@ -562,9 +596,11 @@ static void execute_command(CurlLoop *loop, CurlCmd *cmd)
         c->paused = 0;
         c->status = 0;
         pthread_mutex_unlock(&c->mutex);
-        c->request_start = cmd->pos;
-        c->retry_count   = 0;
-        start_request(c);
+        c->request_start    = cmd->pos;
+        c->request_received = 0;
+        c->retry_count      = 0;
+        if (!c->seek_queued)
+            start_request(c);
         break;
     }
 }
@@ -1174,6 +1210,14 @@ static int libcurl_close(URLContext *h)
     return 0;
 }
 
+static int libcurl_get_short_seek(URLContext *h)
+{
+    CurlContext *c = h->priv_data;
+    if (c->short_seek_size >= 1)
+        return FFMIN(c->short_seek_size, INT_MAX);
+    return AVERROR(ENOSYS);
+}
+
 #define OFFSET(x) offsetof(CurlContext, x)
 #define D AV_OPT_FLAG_DECODING_PARAM
 #define E AV_OPT_FLAG_ENCODING_PARAM
@@ -1207,6 +1251,7 @@ static const AVOption options[] = {
         { "2-prior-knowledge", "HTTP/2 without an upgrade handshake",   0, 
AV_OPT_TYPE_CONST, { .i64 = CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE },   0, 0, D, 
.unit = "http_version" },
         { "3",                 "HTTP/3, fall back to earlier versions", 0, 
AV_OPT_TYPE_CONST, { .i64 = CURL_HTTP_VERSION_3 },                   0, 0, D, 
.unit = "http_version" },
         { "3only",             "HTTP/3 only",                           0, 
AV_OPT_TYPE_CONST, { .i64 = CURL_HTTP_VERSION_3ONLY },               0, 0, D, 
.unit = "http_version" },
+    { "short_seek_size", "threshold to favor readahead over seek", 
OFFSET(short_seek_size), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, INT64_MAX, D },
     { NULL }
 };
 
@@ -1223,6 +1268,7 @@ const URLProtocol ff_libcurl_protocol = {
     .url_read        = libcurl_read,
     .url_seek        = libcurl_seek,
     .url_close       = libcurl_close,
+    .url_get_short_seek = libcurl_get_short_seek,
     .priv_data_size  = sizeof(CurlContext),
     .priv_data_class = &libcurl_context_class,
     .flags           = URL_PROTOCOL_FLAG_NETWORK,
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to