Hi. Thanks for the patch. Le quartidi 14 germinal, an CCXXV, Timothy Lee a écrit : > 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. Can you explain more precisely how and why? Borrowing code often means features could be merged or should be more clearly separated, depending on cases. > --- > libavformat/Makefile | 1 + > libavformat/capture.c | 321 > ++++++++++++++++++++++++++++++++++++++++++++++++ > libavformat/protocols.c | 1 + > 3 files changed, 323 insertions(+) > create mode 100644 libavformat/capture.c I think the documentation and ChangeLog patches should be merged with this one. > > 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 If the file "borrows heavily", then copyright is owed. > + * > + * 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 Do you have any particular reason to use a direct file access for the capture file instead of relying on an AVIO URL? > + > +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; c->fd is not a proper return code; other parts of the code that touch avpriv_open() use AVERROR(errno). But using AVIO instead avoids that. > + } > + > + 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) { Theoretically, write() could write less than size but still return success. > + 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 }, This option has no documentation. > + { "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; These are only preliminary comments. Pending explanations on the relation with cache:, I have not yet looked at the global logic. Regards, -- Nicolas George
signature.asc
Description: Digital signature
_______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel