[FFmpeg-devel] [PATCH] avformat/http: parse Content-Range responses with unknown document size

Aman Karmani ffmpeg at tmm1.net
Wed Oct 6 02:32:44 EEST 2021


From: Aman Karmani <aman at tmm1.net>

The HTTP spec allows '*' for document size in Content-Range response headers,
to specify that the file size is unknown:

    Content-Range: <unit> <range-start>-<range-end>/<size>
    Content-Range: <unit> <range-start>-<range-end>/*
    Content-Range: <unit> */<size>

In the case where the filesize is unknown, this patch keeps track of a new fileend variable
which is the approximate size based on the range and data returned by the server.

Practically speaking, this patch can be used effectively "tail" a stream that is
still being appended to. For example, a PVR system may record an mpegts stream to disk,
and serve up the same file to players while the stream is actively being recorded.

By adding a `Content-Range: bytes X-Y/*` header to the response, the server can
inform the ffmpeg-based player that the stream is "active" and that the file size
will continue to grow. For every incoming request, the server would:

- open file handle to recording in progress
- seek file handle to offset requested by the incoming `Range` header
- query the current file size of the file
- return a `Content-Range` header with the format `bytes <start offset>-<current file size>/*`
- copy bytes from the file to the http socket
- on eof, wait and try reading from the file again to "tail" it as long as the recording is "active"

On the ffmpeg size, we simply use MAX(s->off, s->fileend) to decide how large the file is
whenever requested via AVSEEK_SIZE. This effectively allows ffmpeg based players to
watch and seek around a growing mpegts stream even while it is being appended to.

Signed-off-by: Aman Karmani <aman at tmm1.net>
---
 libavformat/http.c | 27 ++++++++++++++++++++-------
 1 file changed, 20 insertions(+), 7 deletions(-)

diff --git a/libavformat/http.c b/libavformat/http.c
index 476b9a8456..9dc0ff935d 100644
--- a/libavformat/http.c
+++ b/libavformat/http.c
@@ -68,7 +68,7 @@ typedef struct HTTPContext {
     /* Used if "Transfer-Encoding: chunked" otherwise -1. */
     uint64_t chunksize;
     int chunkend;
-    uint64_t off, end_off, filesize;
+    uint64_t off, end_off, filesize, fileend;
     char *location;
     HTTPAuthState auth_state;
     HTTPAuthState proxy_auth_state;
@@ -621,6 +621,7 @@ static int http_open(URLContext *h, const char *uri, int flags,
         h->is_streamed = 1;
 
     s->filesize = UINT64_MAX;
+    s->fileend = UINT64_MAX;
     s->location = av_strdup(uri);
     if (!s->location)
         return AVERROR(ENOMEM);
@@ -750,13 +751,22 @@ static int parse_location(HTTPContext *s, const char *p)
 static void parse_content_range(URLContext *h, const char *p)
 {
     HTTPContext *s = h->priv_data;
-    const char *slash;
+    const char *slash, *dash;
+    uint64_t range_end = UINT64_MAX;
 
     if (!strncmp(p, "bytes ", 6)) {
         p     += 6;
         s->off = strtoull(p, NULL, 10);
-        if ((slash = strchr(p, '/')) && strlen(slash) > 0)
-            s->filesize = strtoull(slash + 1, NULL, 10);
+        if ((dash = strchr(p, '-')))
+            range_end = strtoll(dash + 1, NULL, 10);
+        if ((slash = strchr(p, '/')) && strlen(slash) > 0) {
+            if (slash[0] == '*') {
+                s->fileend = range_end;
+            } else {
+                s->filesize = strtoull(slash + 1, NULL, 10);
+                s->fileend = UINT64_MAX;
+            }
+        }
     }
     if (s->seekable == -1 && (!s->is_akamai || s->filesize != 2147483647))
         h->is_streamed = 0; /* we _can_ in fact seek */
@@ -1404,6 +1414,7 @@ static int http_connect(URLContext *h, const char *path, const char *local_path,
     s->off              = 0;
     s->icy_data_read    = 0;
     s->filesize         = UINT64_MAX;
+    s->fileend          = UINT64_MAX;
     s->willclose        = 0;
     s->end_chunked_post = 0;
     s->end_header       = 0;
@@ -1779,19 +1790,21 @@ static int64_t http_seek_internal(URLContext *h, int64_t off, int whence, int fo
     int old_buf_size, ret;
     AVDictionary *options = NULL;
 
-    if (whence == AVSEEK_SIZE)
+    if (whence == AVSEEK_SIZE && s->fileend != UINT64_MAX)
+        return s->off > s->fileend ? s->off : s->fileend;
+    else if (whence == AVSEEK_SIZE)
         return s->filesize;
     else if (!force_reconnect &&
              ((whence == SEEK_CUR && off == 0) ||
               (whence == SEEK_SET && off == s->off)))
         return s->off;
-    else if ((s->filesize == UINT64_MAX && whence == SEEK_END))
+    else if ((s->filesize == UINT64_MAX && s->fileend == UINT64_MAX && whence == SEEK_END))
         return AVERROR(ENOSYS);
 
     if (whence == SEEK_CUR)
         off += s->off;
     else if (whence == SEEK_END)
-        off += s->filesize;
+        off += s->fileend > 0 ? s->fileend : s->filesize;
     else if (whence != SEEK_SET)
         return AVERROR(EINVAL);
     if (off < 0)
-- 
2.33.0



More information about the ffmpeg-devel mailing list