]> git.sesse.net Git - ffmpeg/blobdiff - libavformat/cache.c
avformat/rtsp: Use avio_closep() to avoid leaving stale pointers in memory
[ffmpeg] / libavformat / cache.c
index 48a342f0ec5bd3c6931159a6739842cab67ceaf8..02b02bbf54d6bcd16afec76d1b6d07206175fef6 100644 (file)
@@ -30,6 +30,7 @@
 #include "libavutil/avassert.h"
 #include "libavutil/avstring.h"
 #include "libavutil/file.h"
+#include "libavutil/opt.h"
 #include "libavutil/tree.h"
 #include "avformat.h"
 #include <fcntl.h>
@@ -51,6 +52,7 @@ typedef struct CacheEntry {
 } CacheEntry;
 
 typedef struct Context {
+    AVClass *class;
     int fd;
     struct AVTreeNode *root;
     int64_t logical_pos;
@@ -60,6 +62,7 @@ typedef struct Context {
     int is_true_eof;
     URLContext *inner;
     int64_t cache_hit, cache_miss;
+    int read_ahead_limit;
 } Context;
 
 static int cmp(void *key, const void *node)
@@ -89,16 +92,11 @@ static int cache_open(URLContext *h, const char *arg, int flags)
 static int add_entry(URLContext *h, const unsigned char *buf, int size)
 {
     Context *c= h->priv_data;
-    int64_t pos;
+    int64_t pos = -1;
     int ret;
-    CacheEntry *entry = av_malloc(sizeof(*entry));
+    CacheEntry *entry = NULL, *next[2] = {NULL, NULL};
     CacheEntry *entry_ret;
-    struct AVTreeNode *node = av_tree_node_alloc();
-
-    if (!entry || !node) {
-        ret = AVERROR(ENOMEM);
-        goto fail;
-    }
+    struct AVTreeNode *node = NULL;
 
     //FIXME avoid lseek
     pos = lseek(c->fd, 0, SEEK_END);
@@ -107,6 +105,7 @@ static int add_entry(URLContext *h, const unsigned char *buf, int size)
         av_log(h, AV_LOG_ERROR, "seek in cache failed\n");
         goto fail;
     }
+    c->cache_pos = pos;
 
     ret = write(c->fd, buf, size);
     if (ret < 0) {
@@ -114,21 +113,40 @@ static int add_entry(URLContext *h, const unsigned char *buf, int size)
         av_log(h, AV_LOG_ERROR, "write in cache failed\n");
         goto fail;
     }
+    c->cache_pos += ret;
 
-    entry->logical_pos = c->logical_pos;
-    entry->physical_pos = pos;
-    entry->size = ret;
+    entry = av_tree_find(c->root, &c->logical_pos, cmp, (void**)next);
 
-    entry_ret = av_tree_insert(&c->root, entry, cmp, &node);
-    if (entry_ret && entry_ret != entry) {
-        ret = -1;
-        av_log(h, AV_LOG_ERROR, "av_tree_insert failed\n");
-        goto fail;
-    }
-    c->cache_pos = entry->physical_pos + entry->size;
+    if (!entry)
+        entry = next[0];
+
+    if (!entry ||
+        entry->logical_pos  + entry->size != c->logical_pos ||
+        entry->physical_pos + entry->size != pos
+    ) {
+        entry = av_malloc(sizeof(*entry));
+        node = av_tree_node_alloc();
+        if (!entry || !node) {
+            ret = AVERROR(ENOMEM);
+            goto fail;
+        }
+        entry->logical_pos = c->logical_pos;
+        entry->physical_pos = pos;
+        entry->size = ret;
+
+        entry_ret = av_tree_insert(&c->root, entry, cmp, &node);
+        if (entry_ret && entry_ret != entry) {
+            ret = -1;
+            av_log(h, AV_LOG_ERROR, "av_tree_insert failed\n");
+            goto fail;
+        }
+    } else
+        entry->size += ret;
 
     return 0;
 fail:
+    //we could truncate the file to pos here if pos >=0 but ftruncate isnt available in VS so
+    //for simplicty we just leave the file a bit larger
     av_free(entry);
     av_free(node);
     return ret;
@@ -150,12 +168,19 @@ static int cache_read(URLContext *h, unsigned char *buf, int size)
         av_assert0(entry->logical_pos <= c->logical_pos);
         if (in_block_pos < entry->size) {
             int64_t physical_target = entry->physical_pos + in_block_pos;
-            //FIXME avoid seek if unneeded
-            r = lseek(c->fd, physical_target, SEEK_SET);
-            if (r >= 0)
+
+            if (c->cache_pos != physical_target) {
+                r = lseek(c->fd, physical_target, SEEK_SET);
+            } else
+                r = c->cache_pos;
+
+            if (r >= 0) {
+                c->cache_pos = r;
                 r = read(c->fd, buf, FFMIN(size, entry->size - in_block_pos));
+            }
 
             if (r > 0) {
+                c->cache_pos += r;
                 c->logical_pos += r;
                 c->cache_hit ++;
                 return r;
@@ -214,6 +239,7 @@ static int64_t cache_seek(URLContext *h, int64_t pos, int whence)
         whence = SEEK_SET;
         pos += c->logical_pos;
     } else if (whence == SEEK_END && c->is_true_eof) {
+resolve_eof:
         whence = SEEK_SET;
         pos += c->end;
     }
@@ -226,6 +252,27 @@ static int64_t cache_seek(URLContext *h, int64_t pos, int whence)
 
     //cache miss
     ret= ffurl_seek(c->inner, pos, whence);
+    if ((whence == SEEK_SET && pos >= c->logical_pos ||
+         whence == SEEK_END && pos <= 0) && ret < 0) {
+        if (   (whence == SEEK_SET && c->read_ahead_limit >= pos - c->logical_pos)
+            || c->read_ahead_limit < 0) {
+            uint8_t tmp[32768];
+            while (c->logical_pos < pos || whence == SEEK_END) {
+                int size = sizeof(tmp);
+                if (whence == SEEK_SET)
+                    size = FFMIN(sizeof(tmp), pos - c->logical_pos);
+                ret = cache_read(h, tmp, size);
+                if (ret == 0 && whence == SEEK_END) {
+                    av_assert0(c->is_true_eof);
+                    goto resolve_eof;
+                }
+                if (ret < 0) {
+                    return ret;
+                }
+            }
+            return c->logical_pos;
+        }
+    }
 
     if (ret >= 0) {
         c->logical_pos = ret;
@@ -246,10 +293,24 @@ static int cache_close(URLContext *h)
     ffurl_close(c->inner);
     av_tree_destroy(c->root);
 
-
     return 0;
 }
 
+#define OFFSET(x) offsetof(Context, x)
+#define D AV_OPT_FLAG_DECODING_PARAM
+
+static const AVOption options[] = {
+    { "read_ahead_limit", "Amount in bytes that may be read ahead when seeking isnt supported, -1 for unlimited", OFFSET(read_ahead_limit), AV_OPT_TYPE_INT, { .i64 = 65536 }, -1, INT_MAX, D },
+    {NULL},
+};
+
+static const AVClass cache_context_class = {
+    .class_name = "Cache",
+    .item_name  = av_default_item_name,
+    .option     = options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
 URLProtocol ff_cache_protocol = {
     .name                = "cache",
     .url_open            = cache_open,
@@ -257,4 +318,5 @@ URLProtocol ff_cache_protocol = {
     .url_seek            = cache_seek,
     .url_close           = cache_close,
     .priv_data_size      = sizeof(Context),
+    .priv_data_class     = &cache_context_class,
 };