[FFmpeg-devel] [PATCH 1/3] libavformat: add "capture:" protocol

Timothy Lee timothy.ty.lee at gmail.com
Mon Apr 3 02:09:30 EEST 2017


Capture is an input stream capture protocol that dumps the input stream to a
file.  The default name of the output file is "capture.dat", but it can be
changed using the "capture_file" option.

capture.c borrows heavily from cache.c.
---
 libavformat/Makefile    |   1 +
 libavformat/capture.c   | 321 ++++++++++++++++++++++++++++++++++++++++++++++++
 libavformat/protocols.c |   1 +
 3 files changed, 323 insertions(+)
 create mode 100644 libavformat/capture.c

diff --git a/libavformat/Makefile b/libavformat/Makefile
index f56ef16532..10b07e1774 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -548,6 +548,7 @@ OBJS-$(CONFIG_ASYNC_PROTOCOL)            += async.o
 OBJS-$(CONFIG_APPLEHTTP_PROTOCOL)        += hlsproto.o
 OBJS-$(CONFIG_BLURAY_PROTOCOL)           += bluray.o
 OBJS-$(CONFIG_CACHE_PROTOCOL)            += cache.o
+OBJS-$(CONFIG_CAPTURE_PROTOCOL)          += capture.o
 OBJS-$(CONFIG_CONCAT_PROTOCOL)           += concat.o
 OBJS-$(CONFIG_CRYPTO_PROTOCOL)           += crypto.o
 OBJS-$(CONFIG_DATA_PROTOCOL)             += data_uri.o
diff --git a/libavformat/capture.c b/libavformat/capture.c
new file mode 100644
index 0000000000..6802fc4c28
--- /dev/null
+++ b/libavformat/capture.c
@@ -0,0 +1,321 @@
+/*
+ * Input capture protocol.
+ * Copyright (c) 2017 Timothy Lee
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Based on libavformat/cache.c by Michael Niedermayer
+ */
+
+#include "libavutil/avassert.h"
+#include "libavutil/avstring.h"
+#include "libavutil/internal.h"
+#include "libavutil/opt.h"
+#include "libavutil/tree.h"
+#include "avformat.h"
+#include <fcntl.h>
+#if HAVE_IO_H
+#include <io.h>
+#endif
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <sys/stat.h>
+#include <stdlib.h>
+#include "os_support.h"
+#include "url.h"
+
+#ifndef O_BINARY
+#   define O_BINARY 0
+#endif
+
+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 capture_pos;
+    int64_t inner_pos;
+    int64_t end;
+    int is_true_eof;
+    URLContext *inner;
+    int read_ahead_limit;
+    const char *capture_file;
+} Context;
+
+static int cmp(const void *key, const void *node)
+{
+    return FFDIFFSIGN(*(const int64_t *)key, ((const CacheEntry *) node)->logical_pos);
+}
+
+static int capture_open(URLContext *h, const char *arg, int flags, AVDictionary **options)
+{
+    Context *c= h->priv_data;
+
+    av_strstart(arg, "capture:", &arg);
+
+    c->fd = avpriv_open(c->capture_file, O_RDWR | O_BINARY | O_CREAT, 0666);
+    if (c->fd < 0){
+        av_log(h, AV_LOG_ERROR, "Failed to create capture file\n");
+        return c->fd;
+    }
+
+    return ffurl_open_whitelist(&c->inner, arg, flags, &h->interrupt_callback,
+                                options, h->protocol_whitelist, h->protocol_blacklist, h);
+}
+
+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 capture file failed\n");
+        goto fail;
+    }
+    c->capture_pos = pos;
+
+    ret = write(c->fd, buf, size);
+    if (ret < 0) {
+        ret = AVERROR(errno);
+        av_log(h, AV_LOG_ERROR, "write to capture file failed\n");
+        goto fail;
+    }
+    c->capture_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 isn't available in VS so
+    //for simplicty we just leave the file a bit larger
+    av_free(entry);
+    av_free(node);
+    return ret;
+}
+
+static int capture_read(URLContext *h, unsigned char *buf, int size)
+{
+    Context *c= h->priv_data;
+    CacheEntry *entry, *next[2] = {NULL, NULL};
+    int64_t 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->capture_pos != physical_target) {
+                r = lseek(c->fd, physical_target, SEEK_SET);
+            } else
+                r = c->capture_pos;
+
+            if (r >= 0) {
+                c->capture_pos = r;
+                r = read(c->fd, buf, FFMIN(size, entry->size - in_block_pos));
+            }
+
+            if (r > 0) {
+                c->capture_pos += r;
+                c->logical_pos += r;
+                return r;
+            }
+        }
+    }
+
+    //cache miss or some kind of fault with the capture file
+
+    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;
+
+    add_entry(h, buf, r);
+    c->logical_pos += r;
+    c->end = FFMAX(c->end, c->logical_pos);
+
+    return r;
+}
+
+static int64_t capture_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);
+            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;
+    }
+
+    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;
+    }
+
+    //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 = capture_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;
+        c->end = FFMAX(c->end, ret);
+    }
+
+    return ret;
+}
+
+static int enu_free(void *opaque, void *elem)
+{
+    av_free(elem);
+    return 0;
+}
+
+static int capture_close(URLContext *h)
+{
+    Context *c= h->priv_data;
+
+    av_log(h, AV_LOG_INFO, "Captured %"PRId64" bytes\n", c->end);
+
+    close(c->fd);
+    ffurl_close(c->inner);
+    av_tree_enumerate(c->root, NULL, NULL, enu_free);
+    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 isn't supported, -1 for unlimited", OFFSET(read_ahead_limit), AV_OPT_TYPE_INT, { .i64 = 65536 }, -1, INT_MAX, D },
+    { "capture_file", "Name of capture file", OFFSET(capture_file), AV_OPT_TYPE_STRING, { .str = "capture.dat" },  CHAR_MIN, CHAR_MAX, D },
+    {NULL},
+};
+
+static const AVClass capture_context_class = {
+    .class_name = "Capture",
+    .item_name  = av_default_item_name,
+    .option     = options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+const URLProtocol ff_capture_protocol = {
+    .name                = "capture",
+    .url_open2           = capture_open,
+    .url_read            = capture_read,
+    .url_seek            = capture_seek,
+    .url_close           = capture_close,
+    .priv_data_size      = sizeof(Context),
+    .priv_data_class     = &capture_context_class,
+};
diff --git a/libavformat/protocols.c b/libavformat/protocols.c
index 8d3555ed52..0855588740 100644
--- a/libavformat/protocols.c
+++ b/libavformat/protocols.c
@@ -26,6 +26,7 @@
 extern const URLProtocol ff_async_protocol;
 extern const URLProtocol ff_bluray_protocol;
 extern const URLProtocol ff_cache_protocol;
+extern const URLProtocol ff_capture_protocol;
 extern const URLProtocol ff_concat_protocol;
 extern const URLProtocol ff_crypto_protocol;
 extern const URLProtocol ff_data_protocol;
-- 
2.12.1



More information about the ffmpeg-devel mailing list