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

Submitted by Timothy Lee on April 2, 2017, 11:09 p.m.

Details

Message ID 20170402230932.10906-1-timothy.ty.lee@gmail.com
State New
Headers show

Commit Message

Timothy Lee April 2, 2017, 11:09 p.m.
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

Comments

Nicolas George April 3, 2017, 8:35 a.m.
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,
Timothy Lee April 3, 2017, 9:10 a.m.
On 04/03/2017 06:35 PM, Nicolas George wrote:
> 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.

Hi Nicolas,

Thanks for your quick reply.  Regarding the almost direct copy of code 
from cache.c, I previously submitted a patch on 31 March that adds a 
"cache_file" option to the cache protocol.  It was intended to allow a 
specifically named cache file to serve as a dump of the input stream.

Michael Niedermayer explained that his intention was to maintain a 
caching system that was more consistent with how a browser's cache 
works, and my changes to the cache protocol was not appropriate.

Hence my attempt to duplicate the code from cache.c and use it for the 
capture protocol.

>> ---
>>   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.
OK.
>> + * Copyright (c) 2017 Timothy Lee
> If the file "borrows heavily", then copyright is owed.
Understood.  If I end up using cache.c, then I'll amend the copyright to 
reflect that.
>> +#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?
> These are only preliminary comments. Pending explanations on the
> relation with cache:, I have not yet looked at the global logic.
> Regards,
Thank you for your suggestion.  I will look into the possibility of 
using AVIO URL to implement the capture protocol instead.

Timothy Lee
Michael Niedermayer April 3, 2017, 9:25 a.m.
On Mon, Apr 03, 2017 at 07:10:42PM +1000, Timothy Lee wrote:
> On 04/03/2017 06:35 PM, Nicolas George wrote:
> >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.
> 
> Hi Nicolas,
> 
> Thanks for your quick reply.  Regarding the almost direct copy of
> code from cache.c, I previously submitted a patch on 31 March that
> adds a "cache_file" option to the cache protocol.  It was intended
> to allow a specifically named cache file to serve as a dump of the
> input stream.
> 
> Michael Niedermayer explained that his intention was to maintain a
> caching system that was more consistent with how a browser's cache
> works, and my changes to the cache protocol was not appropriate.

both can be done in cache.
the primary way of caching in cache.c should be automatic though
and not require the user to manually set it up per url

[...]
Nicolas George April 3, 2017, 9:43 a.m.
Le quartidi 14 germinal, an CCXXV, Timothy Lee a écrit :
> Thanks for your quick reply.  Regarding the almost direct copy of code from
> cache.c, I previously submitted a patch on 31 March that adds a "cache_file"
> option to the cache protocol.  It was intended to allow a specifically named
> cache file to serve as a dump of the input stream.
> 
> Michael Niedermayer explained that his intention was to maintain a caching
> system that was more consistent with how a browser's cache works, and my
> changes to the cache protocol was not appropriate.
> 
> Hence my attempt to duplicate the code from cache.c and use it for the
> capture protocol.

I see. IMHO, there are two options here: either implement the feature
you want within cache.c while respecting Michael's intents or implement
a protocol that does only capture, no caching at all.

If you go the first way, then you need to discuss the solution with
Michael (but on the mailing-list) to find a way that suits you both. It
is probably the best option.

If you go the second way, then I think you must remove from your patch
all that is related to caching, i.e. re-reading from the capture file.
The capture file becomes just a copy of the octets read from the
underlying protocol. In particular, if you do not need seeking, I
strongly suggest to remove it; otherwise, you would need an auxiliary
map file to report valid ranges.

Regards,
wm4 April 3, 2017, 10:12 a.m.
On Mon, 3 Apr 2017 11:25:39 +0200
Michael Niedermayer <michael@niedermayer.cc> wrote:

> On Mon, Apr 03, 2017 at 07:10:42PM +1000, Timothy Lee wrote:
> > On 04/03/2017 06:35 PM, Nicolas George wrote:  
> > >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.  
> > 
> > Hi Nicolas,
> > 
> > Thanks for your quick reply.  Regarding the almost direct copy of
> > code from cache.c, I previously submitted a patch on 31 March that
> > adds a "cache_file" option to the cache protocol.  It was intended
> > to allow a specifically named cache file to serve as a dump of the
> > input stream.
> > 
> > Michael Niedermayer explained that his intention was to maintain a
> > caching system that was more consistent with how a browser's cache
> > works, and my changes to the cache protocol was not appropriate.  
> 
> both can be done in cache.
> the primary way of caching in cache.c should be automatic though
> and not require the user to manually set it up per url

I don't think disk cache management should be in the scope of FFmpeg.
Steven Liu April 3, 2017, 10:20 a.m.
2017-04-03 18:12 GMT+08:00 wm4 <nfxjfg@googlemail.com>:

> On Mon, 3 Apr 2017 11:25:39 +0200
> Michael Niedermayer <michael@niedermayer.cc> wrote:
>
> > On Mon, Apr 03, 2017 at 07:10:42PM +1000, Timothy Lee wrote:
> > > On 04/03/2017 06:35 PM, Nicolas George wrote:
> > > >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.
> > >
> > > Hi Nicolas,
> > >
> > > Thanks for your quick reply.  Regarding the almost direct copy of
> > > code from cache.c, I previously submitted a patch on 31 March that
> > > adds a "cache_file" option to the cache protocol.  It was intended
> > > to allow a specifically named cache file to serve as a dump of the
> > > input stream.
> > >
> > > Michael Niedermayer explained that his intention was to maintain a
> > > caching system that was more consistent with how a browser's cache
> > > works, and my changes to the cache protocol was not appropriate.
> >
> > both can be done in cache.
> > the primary way of caching in cache.c should be automatic though
> > and not require the user to manually set it up per url
>
> I don't think disk cache management should be in the scope of FFmpeg.
>
Perhaps i misunderstand something, dose he want add CDN(Content Delivery
Network) Cache function into FFmpeg?

> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
wm4 April 3, 2017, 10:42 a.m.
On Mon, 3 Apr 2017 18:20:35 +0800
Steven Liu <lingjiujianke@gmail.com> wrote:

> 2017-04-03 18:12 GMT+08:00 wm4 <nfxjfg@googlemail.com>:
> 
> > On Mon, 3 Apr 2017 11:25:39 +0200
> > Michael Niedermayer <michael@niedermayer.cc> wrote:
> >  
> > > On Mon, Apr 03, 2017 at 07:10:42PM +1000, Timothy Lee wrote:  
> > > > On 04/03/2017 06:35 PM, Nicolas George wrote:  
> > > > >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.  
> > > >
> > > > Hi Nicolas,
> > > >
> > > > Thanks for your quick reply.  Regarding the almost direct copy of
> > > > code from cache.c, I previously submitted a patch on 31 March that
> > > > adds a "cache_file" option to the cache protocol.  It was intended
> > > > to allow a specifically named cache file to serve as a dump of the
> > > > input stream.
> > > >
> > > > Michael Niedermayer explained that his intention was to maintain a
> > > > caching system that was more consistent with how a browser's cache
> > > > works, and my changes to the cache protocol was not appropriate.  
> > >
> > > both can be done in cache.
> > > the primary way of caching in cache.c should be automatic though
> > > and not require the user to manually set it up per url  
> >
> > I don't think disk cache management should be in the scope of FFmpeg.
> >  
> Perhaps i misunderstand something, dose he want add CDN(Content Delivery
> Network) Cache function into FFmpeg?

No, this is about caching on the client side. Timothy Lee wants a
simple option that would write the cache contents to disk, while
Michael Niedermayer wants it to work like in a browser, with automatic
deletion of old files etc. (if I understood him right). I think that's
a bit too much for libavformat.

Patch hide | download patch | download mbox

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;