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

Submitted by Timothy Lee on April 11, 2017, 4:12 a.m.

Details

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

Commit Message

Timothy Lee April 11, 2017, 4:12 a.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.
---
 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

Comments

Nicolas George April 11, 2017, 9:19 a.m.
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
>  
> +@section capture
> +
> +Input stream capturing protocol.
> +
> +A capture URL has the form:
> +@example
> +capture:@var{URL}
> +@end example
> +
> +This protocol accepts the following option:
> +@table @option
> +
> +@item capture_file
> +Name of the capture file.
> +If not specified, the input stream will be written into @file{capture.dat}.
> +
> +@end table
> +
> +For example, to capture the input stream as @file{stream.sav} during playback:
> +@example
> +ffplay -capture_file stream.sav capture:@var{URL}
> +@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,
Moritz Barsnick April 11, 2017, 10:43 p.m.
On Tue, Apr 11, 2017 at 14:12:08 +1000, Timothy Lee wrote:
> 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.

Nice!

> +Input stream capturing protocol.

Probably worth pointing out more exactly what it does. Even I can only
guess: Dumps the raw payload, in *addition* to demuxing the input as
normal.

> +A capture URL has the form:
> +@example
> +capture:@var{URL}

That makes it an additional protocol prefix, right? So you can end up
having two (or more?). "capture:file:/path/bla"
(Confusing to me as a user at first sight, but ingenious if it works.
Yes, I did test it.)

> +For example, to capture the input stream as @file{stream.sav} during playback:
> +@example
> +ffplay -capture_file stream.sav capture:@var{URL}

So, is this the perfect replacement for "mplayer -dumpstream [-dumpfile
capture.dat]"? :-) Probably also worth noting somewhere.

If you go with Nicolas's suggestions, this comment is obsolete, but:

> +    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");

If you're going to print a message, please also evaluate errno, for the
sake of the user.

Moritz

Patch hide | download patch | download mbox

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
 
+@section capture
+
+Input stream capturing protocol.
+
+A capture URL has the form:
+@example
+capture:@var{URL}
+@end example
+
+This protocol accepts the following option:
+@table @option
+
+@item capture_file
+Name of the capture file.
+If not specified, the input stream will be written into @file{capture.dat}.
+
+@end table
+
+For example, to capture the input stream as @file{stream.sav} during playback:
+@example
+ffplay -capture_file stream.sav capture:@var{URL}
+@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
+
+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");
+    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))
+    {
+        if (write(c->fd, buf, r) == r)
+            c->count += r;
+        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);
+}
+
+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;