Message ID | 20170411041208.12551-1-timothy.ty.lee@gmail.com |
---|---|
State | New |
Headers | show |
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,
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
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;