[FFmpeg-devel] [PATCH] libavformat: add "capture:" protocol using AVIO

Nicolas George george at nsup.org
Tue Apr 11 12:19:53 EEST 2017


Le duodi 22 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.
> ---
>  Changelog               |   1 +
>  doc/protocols.texi      |  23 ++++++++++
>  libavformat/Makefile    |   1 +
>  libavformat/capture.c   | 112 ++++++++++++++++++++++++++++++++++++++++++++++++
>  libavformat/protocols.c |   1 +
>  5 files changed, 138 insertions(+)
>  create mode 100644 libavformat/capture.c

Thanks, I like this version much better. See comments below.

> 
> diff --git a/Changelog b/Changelog
> index e76b324f61..3c06e84185 100644
> --- a/Changelog
> +++ b/Changelog
> @@ -36,6 +36,7 @@ version 3.3:
>  - MPEG-7 Video Signature filter
>  - Removed asyncts filter (use af_aresample instead)
>  - Intel QSV-accelerated VP8 video decoding
> +- capture protocol
>  
>  
>  version 3.2:
> diff --git a/doc/protocols.texi b/doc/protocols.texi
> index a7968ff56e..89a1f2afa8 100644
> --- a/doc/protocols.texi
> +++ b/doc/protocols.texi
> @@ -103,6 +103,29 @@ Cache the input stream to temporary file. It brings seeking capability to live s
>  cache:@var{URL}
>  @end example
>  
> + at section capture
> +
> +Input stream capturing protocol.
> +
> +A capture URL has the form:
> + at example
> +capture:@var{URL}
> + at end example
> +
> +This protocol accepts the following option:
> + at table @option
> +
> + at item capture_file
> +Name of the capture file.
> +If not specified, the input stream will be written into @file{capture.dat}.
> +
> + at end table
> +
> +For example, to capture the input stream as @file{stream.sav} during playback:
> + at example
> +ffplay -capture_file stream.sav capture:@var{URL}
> + at end example
> +
>  @section concat
>  
>  Physical concatenation protocol.
> diff --git a/libavformat/Makefile b/libavformat/Makefile
> index a1dae894fe..08d23baf95 100644
> --- a/libavformat/Makefile
> +++ b/libavformat/Makefile
> @@ -549,6 +549,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..90daf40877
> --- /dev/null
> +++ b/libavformat/capture.c
> @@ -0,0 +1,112 @@
> +/*
> + * 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
> + */
> +
> +#include "libavutil/avstring.h"
> +#include "libavutil/opt.h"
> +#include "avformat.h"
> +#include "avio.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

Is there a reason you use direct I/O, and have to suffer all that
boilerplate code? You could achieve the same result with
avio_open2(&c->capture, c->capture_file), with extra features and less
code.

> +
> +typedef struct Context {
> +    AVClass *class;
> +    int fd;
> +    int64_t count;
> +    AVIOContext *io;
> +    const char *capture_file;
> +} Context;
> +
> +static int capture_open(URLContext *h, const char *arg, int flags, AVDictionary **options)
> +{
> +    Context *c= h->priv_data;

> +    c->fd = avpriv_open(c->capture_file, O_WRONLY|O_BINARY|O_TRUNC|O_CREAT, 0666);
> +    if (c->fd < 0)
> +        av_log(h, AV_LOG_ERROR, "Failed to create capture file\n");

I think it would be better to return an error than test fd everywhere.

> +    av_strstart(arg, "capture:", &arg);
> +    return avio_open2(&c->io, arg, flags, &h->interrupt_callback, options);
> +}
> +
> +static int capture_read(URLContext *h, unsigned char *buf, int size)
> +{
> +    Context *c = h->priv_data;
> +    int r = avio_read(c->io, buf, size);

> +    if ((r > 0) && (c->fd >= 0))
> +    {

Nit: braces on the same line.


> +        if (write(c->fd, buf, r) == r)
> +            c->count += r;

Depending on the kind of capture file, you may need to loop over write()
to avoid short writes and EINTR. Using avio takes care of it.

> +        else
> +            av_log(h, AV_LOG_ERROR, "Cannot write to capture file\n");
> +    }
> +    return r;
> +}
> +
> +static int64_t capture_seek(URLContext *h, int64_t pos, int whence)
> +{

> +    Context *c = h->priv_data;
> +    return avio_seek(c->io, pos, whence);

Maybe seek in the capture file too?

> +}
> +
> +static int capture_close(URLContext *h)
> +{
> +    Context *c = h->priv_data;
> +    av_log(h, AV_LOG_INFO, "Captured %"PRId64" bytes\n", c->count);
> +    if (c->fd >= 0)
> +        close(c->fd);
> +    return avio_closep(&c->io);
> +}
> +
> +#define OFFSET(x) offsetof(Context, x)
> +#define D AV_OPT_FLAG_DECODING_PARAM
> +
> +static const AVOption options[] = {
> +    { "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;

Regards,

-- 
  Nicolas George


More information about the ffmpeg-devel mailing list