]> 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 35f81e83c13ff0a0190e32aac3b4aef27b7671e0..02b02bbf54d6bcd16afec76d1b6d07206175fef6 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Input cache protocol.
- * Copyright (c) 2011 Michael Niedermayer
+ * Copyright (c) 2011,2014 Michael Niedermayer
  *
  * This file is part of FFmpeg.
  *
@@ -23,7 +23,6 @@
 
 /**
  * @TODO
- *      support non continuous caching
  *      support keeping files
  *      support filling with a background thread
  */
@@ -31,6 +30,8 @@
 #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>
 #if HAVE_IO_H
 #include "os_support.h"
 #include "url.h"
 
+typedef struct CacheEntry {
+    int64_t logical_pos;
+    int64_t physical_pos;
+    int size;
+} CacheEntry;
+
 typedef struct Context {
+    AVClass *class;
     int fd;
+    struct AVTreeNode *root;
+    int64_t logical_pos;
+    int64_t cache_pos;
+    int64_t inner_pos;
     int64_t end;
-    int64_t pos;
+    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)
+{
+    return (*(int64_t *) key) - ((const CacheEntry *) node)->logical_pos;
+}
+
 static int cache_open(URLContext *h, const char *arg, int flags)
 {
     char *buffername;
@@ -70,66 +89,228 @@ static int cache_open(URLContext *h, const char *arg, int flags)
     return ffurl_open(&c->inner, arg, flags, &h->interrupt_callback, NULL);
 }
 
+static int add_entry(URLContext *h, const unsigned char *buf, int size)
+{
+    Context *c= h->priv_data;
+    int64_t pos = -1;
+    int ret;
+    CacheEntry *entry = NULL, *next[2] = {NULL, NULL};
+    CacheEntry *entry_ret;
+    struct AVTreeNode *node = NULL;
+
+    //FIXME avoid lseek
+    pos = lseek(c->fd, 0, SEEK_END);
+    if (pos < 0) {
+        ret = AVERROR(errno);
+        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) {
+        ret = AVERROR(errno);
+        av_log(h, AV_LOG_ERROR, "write in cache failed\n");
+        goto fail;
+    }
+    c->cache_pos += ret;
+
+    entry = av_tree_find(c->root, &c->logical_pos, cmp, (void**)next);
+
+    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;
+}
+
 static int cache_read(URLContext *h, unsigned char *buf, int size)
 {
     Context *c= h->priv_data;
+    CacheEntry *entry, *next[2] = {NULL, NULL};
     int r;
 
-    if(c->pos<c->end){
-        r = read(c->fd, buf, FFMIN(size, c->end - c->pos));
-        if(r>0)
-            c->pos += r;
-        return (-1 == r)?AVERROR(errno):r;
-    }else{
-        r = ffurl_read(c->inner, buf, size);
-        if(r > 0){
-            int r2= write(c->fd, buf, r);
-            av_assert0(r2==r); // FIXME handle cache failure
-            c->pos += r;
-            c->end += r;
+    entry = av_tree_find(c->root, &c->logical_pos, cmp, (void**)next);
+
+    if (!entry)
+        entry = next[0];
+
+    if (entry) {
+        int64_t in_block_pos = c->logical_pos - entry->logical_pos;
+        av_assert0(entry->logical_pos <= c->logical_pos);
+        if (in_block_pos < entry->size) {
+            int64_t physical_target = entry->physical_pos + in_block_pos;
+
+            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;
+            }
         }
-        return r;
     }
+
+    // Cache miss or some kind of fault with the cache
+
+    if (c->logical_pos != c->inner_pos) {
+        r = ffurl_seek(c->inner, c->logical_pos, SEEK_SET);
+        if (r<0) {
+            av_log(h, AV_LOG_ERROR, "Failed to perform internal seek\n");
+            return r;
+        }
+        c->inner_pos = r;
+    }
+
+    r = ffurl_read(c->inner, buf, size);
+    if (r == 0 && size>0) {
+        c->is_true_eof = 1;
+        av_assert0(c->end >= c->logical_pos);
+    }
+    if (r<=0)
+        return r;
+    c->inner_pos += r;
+
+    c->cache_miss ++;
+
+    add_entry(h, buf, r);
+    c->logical_pos += r;
+    c->end = FFMAX(c->end, c->logical_pos);
+
+    return r;
 }
 
 static int64_t cache_seek(URLContext *h, int64_t pos, int whence)
 {
     Context *c= h->priv_data;
+    int64_t ret;
 
     if (whence == AVSEEK_SIZE) {
         pos= ffurl_seek(c->inner, pos, whence);
         if(pos <= 0){
             pos= ffurl_seek(c->inner, -1, SEEK_END);
-            ffurl_seek(c->inner, c->end, SEEK_SET);
-            if(pos <= 0)
-                return c->end;
+            if (ffurl_seek(c->inner, c->inner_pos, SEEK_SET) < 0)
+                av_log(h, AV_LOG_ERROR, "Inner protocol failed to seekback end : %"PRId64"\n", pos);
         }
+        if (pos > 0)
+            c->is_true_eof = 1;
+        c->end = FFMAX(c->end, pos);
         return pos;
     }
 
-    pos= lseek(c->fd, pos, whence);
-    if(pos<0){
-        return pos;
-    }else if(pos <= c->end){
-        c->pos= pos;
+    if (whence == SEEK_CUR) {
+        whence = SEEK_SET;
+        pos += c->logical_pos;
+    } else if (whence == SEEK_END && c->is_true_eof) {
+resolve_eof:
+        whence = SEEK_SET;
+        pos += c->end;
+    }
+
+    if (whence == SEEK_SET && pos >= 0 && pos < c->end) {
+        //Seems within filesize, assume it will not fail.
+        c->logical_pos = pos;
         return pos;
-    }else{
-        if(lseek(c->fd, c->pos, SEEK_SET) < 0) {
-            av_log(h, AV_LOG_ERROR, "Failure to seek in cache\n");
+    }
+
+    //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;
         }
-        return AVERROR(EPIPE);
     }
+
+    if (ret >= 0) {
+        c->logical_pos = ret;
+        c->end = FFMAX(c->end, ret);
+    }
+
+    return ret;
 }
 
 static int cache_close(URLContext *h)
 {
     Context *c= h->priv_data;
+
+    av_log(h, AV_LOG_INFO, "Statistics, cache hits:%"PRId64" cache misses:%"PRId64"\n",
+           c->cache_hit, c->cache_miss);
+
     close(c->fd);
     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,
@@ -137,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,
 };