From patchwork Sun Apr 2 23:09:30 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Timothy Lee X-Patchwork-Id: 3249 Delivered-To: ffmpegpatchwork@gmail.com Received: by 10.103.44.195 with SMTP id s186csp2951259vss; Sun, 2 Apr 2017 16:10:27 -0700 (PDT) X-Received: by 10.28.29.138 with SMTP id d132mr6714696wmd.40.1491174627711; Sun, 02 Apr 2017 16:10:27 -0700 (PDT) Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id n129si4347867wma.100.2017.04.02.16.10.04; Sun, 02 Apr 2017 16:10:27 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=gmail.com Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 82209688286; Mon, 3 Apr 2017 02:09:59 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-pg0-f51.google.com (mail-pg0-f51.google.com [74.125.83.51]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id A9E5E680B28 for ; Mon, 3 Apr 2017 02:09:52 +0300 (EEST) Received: by mail-pg0-f51.google.com with SMTP id g2so101550777pge.3 for ; Sun, 02 Apr 2017 16:09:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:subject:date:message-id; bh=baTfulSqFQ2B2KPP295OxxfDHiVi2QwhyEEahMEQ5WA=; b=kR19zfqNZdkSx9bjLoxUaB393AYORhNS8MYzXkrHGy0yLqzTdW3qZKfFr9IOBgcMIW ueMJ9KKPAcCHK2Tu+PEKgZVn+glnnokDflc6fRQDzQW51Nbxv7pB1eaGN/X7ASL8kCUE w/Wxiyg/zGWgQCrkCgOzDwho08rIozads/J38zgEvpejg7PLfvIlPssjjfUtmbC6Mrj1 7aQgyL/FIcL/KkG+P/PqggTwv5xWicvHJqBRNKcwx0f8XjwbUuoZwmbVN3UkkDt3hhnS eusZJUco2E0DQGjYbHW3L6LMMbdrW5Hu7y2roly5RQ5gWELTxALGydbgKSKYrovMGdAd 7HJQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:subject:date:message-id; bh=baTfulSqFQ2B2KPP295OxxfDHiVi2QwhyEEahMEQ5WA=; b=P4PMY1oWZQJzHcaTQLKr6XkjqbDbJHQIdVCFYEiFy0eF2mzJCkLiHPZsZrdtPKyrYY 3mwRwbfAlakRjurkFlWmmG4qGnck5UwhzNXhySgcD9CuHbOjrkuybKIOLOOPVUkDZjSh t3FABmJzzz6XZxw6V0w9pu58ob3SGOVxtH4CME894F7n3t45/FoGNjdlQkUNO+k96EbC KjIjfjFsJipLr8VFR64zjGj5bZZiJ77ZIp4X3eZsO41I35fMr/+a1sdx4C42OFVEjWXg u38pd2ZXZUbLphyeLpOjfIIFJFWTPQPKnujOULEEUlQp7m3mHZKobxqQe/5KHgbCX4oB xtYg== X-Gm-Message-State: AFeK/H2QAxd2J1SyG9vL3f+Ed+dExx+XUby7TpNqwNJCi7bm7ikYoMwKFzcDFO/W7kcOUQ== X-Received: by 10.99.167.1 with SMTP id d1mr15011240pgf.129.1491174591982; Sun, 02 Apr 2017 16:09:51 -0700 (PDT) Received: from localhost.localdomain (59-100-13-146.mel.static-ipl.aapt.com.au. [59.100.13.146]) by smtp.gmail.com with ESMTPSA id p6sm2213393pgn.9.2017.04.02.16.09.50 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Sun, 02 Apr 2017 16:09:51 -0700 (PDT) From: Timothy Lee To: ffmpeg-devel@ffmpeg.org Date: Mon, 3 Apr 2017 09:09:30 +1000 Message-Id: <20170402230932.10906-1-timothy.ty.lee@gmail.com> X-Mailer: git-send-email 2.12.1 Subject: [FFmpeg-devel] [PATCH 1/3] libavformat: add "capture:" protocol X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches MIME-Version: 1.0 Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" 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 +#if HAVE_IO_H +#include +#endif +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#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;