Message ID | 20180427193723.8361-1-nfxjfg@googlemail.com |
---|---|
State | Superseded |
Headers | show |
On 4/27/2018 4:37 PM, wm4 wrote: > From: wm4 <nfxjfg@googlemail.com> > > This can "demux" .vpy files. > > Some minor code copied from other LGPL parts of FFmpeg. > > Possibly support VS compat pixel formats. > > TODO: > - check whether VS can change format midstream > - test vfr mode, return proper timestamps when using it > - drop "lib" prefix? > --- > configure | 4 + > libavformat/Makefile | 1 + > libavformat/allformats.c | 1 + > libavformat/libvapoursynth.c | 379 +++++++++++++++++++++++++++++++++++++++++++ > 4 files changed, 385 insertions(+) > create mode 100644 libavformat/libvapoursynth.c > > diff --git a/configure b/configure > index 9fa1665496..17e46c5daa 100755 > --- a/configure > +++ b/configure > @@ -265,6 +265,7 @@ External library support: > if openssl or gnutls is not used [no] > --enable-libtwolame enable MP2 encoding via libtwolame [no] > --enable-libv4l2 enable libv4l2/v4l-utils [no] > + --enable-libvapoursynth enable VapourSynth demuxer [no] > --enable-libvidstab enable video stabilization using vid.stab [no] > --enable-libvmaf enable vmaf filter via libvmaf [no] > --enable-libvo-amrwbenc enable AMR-WB encoding via libvo-amrwbenc [no] > @@ -1712,6 +1713,7 @@ EXTERNAL_LIBRARY_LIST=" > libtheora > libtwolame > libv4l2 > + libvapoursynth > libvorbis > libvpx > libwavpack > @@ -3068,6 +3070,7 @@ libspeex_encoder_deps="libspeex" > libspeex_encoder_select="audio_frame_queue" > libtheora_encoder_deps="libtheora" > libtwolame_encoder_deps="libtwolame" > +libvapoursynth_demuxer_deps="libvapoursynth" > libvo_amrwbenc_encoder_deps="libvo_amrwbenc" > libvorbis_decoder_deps="libvorbis" > libvorbis_encoder_deps="libvorbis libvorbisenc" > @@ -6041,6 +6044,7 @@ enabled libtwolame && require libtwolame twolame.h twolame_init -ltwolame > { check_lib libtwolame twolame.h twolame_encode_buffer_float32_interleaved -ltwolame || > die "ERROR: libtwolame must be installed and version must be >= 0.3.10"; } > enabled libv4l2 && require_pkg_config libv4l2 libv4l2 libv4l2.h v4l2_ioctl > +enabled libvapoursynth && require_pkg_config libvapoursynth "vapoursynth >= 42" VapourSynth.h getVapourSynthAPI && > require_pkg_config libvapoursynth "vapoursynth-script >= 42" VSScript.h vsscript_init || die "ERROR: vapoursynth or vsscript not found"; die() is needed only with test_pkg_config and check_pkg_config, not require_pkg_config. And seeing that vapoursynth-script.pc depends on vapoursynth.pc, you can simplify all this by only checking for vapoursynth-script. > enabled libvidstab && require_pkg_config libvidstab "vidstab >= 0.98" vid.stab/libvidstab.h vsMotionDetectInit > enabled libvmaf && require_pkg_config libvmaf "libvmaf >= 0.6.2" libvmaf.h compute_vmaf > enabled libvo_amrwbenc && require libvo_amrwbenc vo-amrwbenc/enc_if.h E_IF_init -lvo-amrwbenc > diff --git a/libavformat/Makefile b/libavformat/Makefile > index 3eeca5091d..731b7ac714 100644 > --- a/libavformat/Makefile > +++ b/libavformat/Makefile > @@ -570,6 +570,7 @@ OBJS-$(CONFIG_LIBRTMPTE_PROTOCOL) += librtmp.o > OBJS-$(CONFIG_LIBSRT_PROTOCOL) += libsrt.o > OBJS-$(CONFIG_LIBSSH_PROTOCOL) += libssh.o > OBJS-$(CONFIG_LIBSMBCLIENT_PROTOCOL) += libsmbclient.o > +OBJS-$(CONFIG_LIBVAPOURSYNTH_DEMUXER) += libvapoursynth.o > > # protocols I/O > OBJS-$(CONFIG_ASYNC_PROTOCOL) += async.o > diff --git a/libavformat/allformats.c b/libavformat/allformats.c > index d582778b3b..67f6c4339c 100644 > --- a/libavformat/allformats.c > +++ b/libavformat/allformats.c > @@ -482,6 +482,7 @@ extern AVOutputFormat ff_chromaprint_muxer; > extern AVInputFormat ff_libgme_demuxer; > extern AVInputFormat ff_libmodplug_demuxer; > extern AVInputFormat ff_libopenmpt_demuxer; > +extern AVInputFormat ff_libvapoursynth_demuxer; > > #include "libavformat/muxer_list.c" > #include "libavformat/demuxer_list.c" > diff --git a/libavformat/libvapoursynth.c b/libavformat/libvapoursynth.c > new file mode 100644 > index 0000000000..95699e81d2 > --- /dev/null > +++ b/libavformat/libvapoursynth.c > @@ -0,0 +1,379 @@ > +/* > + * 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 > + */ > + > +/** > +* @file > +* VapourSynth demuxer > +* > +* Synthesizes vapour (?) > +*/ > + > +#include <VapourSynth.h> > +#include <VSScript.h> > + > +#include "libavutil/avassert.h" > +#include "libavutil/avstring.h" > +#include "libavutil/eval.h" > +#include "libavutil/imgutils.h" > +#include "libavutil/opt.h" > +#include "libavutil/pixdesc.h" > +#include "avformat.h" > +#include "internal.h" > + > +typedef struct VSContext { > + const AVClass *class; > + > + const VSAPI *vsapi; > + VSCore *vscore; > + VSScript *vss; > + > + VSNodeRef *outnode; > + int is_cfr; > + int current_frame; > + > + int c_order[4]; > + > + /* options */ > + int64_t max_size; > +} VSContext; > + > +#define OFFSET(x) offsetof(VSContext, x) > +#define A AV_OPT_FLAG_AUDIO_PARAM > +#define D AV_OPT_FLAG_DECODING_PARAM > +static const AVOption options[] = { > + {"max_size", "set max file size supported (in bytes)", OFFSET(max_size), AV_OPT_TYPE_INT64, {.i64 = 1 * 1024 * 1024}, 0, SIZE_MAX - 1, A|D}, > + {NULL} > +}; > + > +static int read_close_vs(AVFormatContext *s) > +{ > + VSContext *vs = s->priv_data; > + > + if (vs->outnode) > + vs->vsapi->freeNode(vs->outnode); > + > + vsscript_freeScript(vs->vss); > + vs->vss = NULL; > + vs->vsapi = NULL; > + vs->vscore = NULL; > + vs->outnode = NULL; > + > + vsscript_finalize(); > + > + return 0; > +} > + > +static int is_native_endian(enum AVPixelFormat pixfmt) > +{ > + enum AVPixelFormat other = av_pix_fmt_swap_endianness(pixfmt); > + const AVPixFmtDescriptor *pd; > + if (other == AV_PIX_FMT_NONE || other == pixfmt) > + return 1; // not affected by byte order > + pd = av_pix_fmt_desc_get(pixfmt); > + return pd && (!!HAVE_BIGENDIAN == !!(pd->flags & AV_PIX_FMT_FLAG_BE)); > +} > + > +static enum AVPixelFormat match_pixfmt(const VSFormat *vsf, int c_order[4]) > +{ > + static const int yuv_order[4] = {0, 1, 2, 0}; > + static const int rgb_order[4] = {1, 2, 0, 0}; > + const AVPixFmtDescriptor *pd; > + > + for (pd = av_pix_fmt_desc_next(NULL); pd; pd = av_pix_fmt_desc_next(pd)) { > + int is_rgb, is_yuv, i, *order; > + enum AVPixelFormat pixfmt; > + > + pixfmt = av_pix_fmt_desc_get_id(pd); > + > + if (pd->flags & (AV_PIX_FMT_FLAG_BAYER | AV_PIX_FMT_FLAG_ALPHA | > + AV_PIX_FMT_FLAG_HWACCEL | AV_PIX_FMT_FLAG_BITSTREAM)) > + continue; > + > + if (pd->log2_chroma_w != vsf->subSamplingW || > + pd->log2_chroma_h != vsf->subSamplingH) > + continue; > + > + is_rgb = vsf->colorFamily == cmRGB; > + if (is_rgb != !!(pd->flags & AV_PIX_FMT_FLAG_RGB)) > + continue; > + > + is_yuv = vsf->colorFamily == cmYUV || > + vsf->colorFamily == cmYCoCg || > + vsf->colorFamily == cmGray; > + if (!is_rgb && !is_yuv) > + continue; > + > + if (vsf->sampleType != ((pd->flags & AV_PIX_FMT_FLAG_FLOAT) ? stFloat : stInteger)) > + continue; > + > + if (av_pix_fmt_count_planes(pixfmt) != vsf->numPlanes) > + continue; > + > + if (strncmp(pd->name, "xyz", 3) == 0) > + continue; > + > + if (!is_native_endian(pixfmt)) > + continue; > + > + order = is_yuv ? yuv_order : rgb_order; > + > + for (i = 0; i < pd->nb_components; i++) { > + const AVComponentDescriptor *c = &pd->comp[i]; > + if (order[c->plane] != i || > + c->offset != 0 || c->shift != 0 || > + c->step != vsf->bytesPerSample || > + c->depth != vsf->bitsPerSample) > + goto cont; > + } > + > + // Use it. > + memcpy(c_order, order, sizeof(int[4])); > + return pixfmt; > + > + cont: ; > + } > + > + return AV_PIX_FMT_NONE; > +} > + > +static int read_header_vs(AVFormatContext *s) > +{ > + AVStream *st; > + AVIOContext *pb = s->pb; > + VSContext *vs = s->priv_data; > + int64_t sz = avio_size(pb); > + char *buf = NULL; > + char dummy; > + const VSVideoInfo *info; > + int err; > + > + vsscript_init(); > + > + if (sz < 0 || sz > vs->max_size) { > + if (sz < 0) > + av_log(s, AV_LOG_WARNING, "Could not determine file size\n"); > + sz = vs->max_size; > + } > + > + buf = av_malloc(sz + 1); > + if (!buf) { > + err = AVERROR(ENOMEM); > + goto done; > + } > + sz = avio_read(pb, buf, sz); > + > + if (sz < 0) { > + av_log(s, AV_LOG_ERROR, "Could not read script.\n"); > + err = sz; > + goto done; > + } > + > + // Data left means our buffer (the max_size option) is too small > + if (avio_read(pb, &dummy, 1) == 1) { > + av_log(s, AV_LOG_ERROR, "File size is larger than max_size option " > + "value %"PRIi64", consider increasing the max_size option\n", > + vs->max_size); > + err = AVERROR_BUFFER_TOO_SMALL; > + goto done; > + } > + > + if (vsscript_createScript(&vs->vss)) { > + av_log(s, AV_LOG_ERROR, "Failed to create script instance.\n"); > + err = AVERROR_EXTERNAL; > + goto done; > + } > + > + buf[sz] = '\0'; > + if (vsscript_evaluateScript(&vs->vss, buf, s->url, 0)) { > + const char *msg = vsscript_getError(vs->vss); > + av_log(s, AV_LOG_ERROR, "Failed to parse script: %s\n", msg ? msg : "(unknown)"); > + err = AVERROR_EXTERNAL; > + goto done; > + } > + > + vs->vsapi = vsscript_getVSApi(); > + vs->vscore = vsscript_getCore(vs->vss); > + > + vs->outnode = vsscript_getOutput(vs->vss, 0); > + if (!vs->outnode) { > + av_log(s, AV_LOG_ERROR, "Could not get script output node.\n"); > + err = AVERROR_EXTERNAL; > + goto done; > + } > + > + st = avformat_new_stream(s, NULL); > + if (!st) { > + err = AVERROR(ENOMEM); > + goto done; > + } > + > + info = vs->vsapi->getVideoInfo(vs->outnode); > + > + if (info->fpsDen) { > + vs->is_cfr = 1; > + avpriv_set_pts_info(st, 64, info->fpsDen, info->fpsNum); > + st->duration = info->numFrames; > + } else { > + // VFR. Just set "something". > + avpriv_set_pts_info(st, 64, 1, AV_TIME_BASE); > + s->ctx_flags |= AVFMTCTX_UNSEEKABLE; > + } > + > + st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; > + st->codecpar->codec_id = AV_CODEC_ID_WRAPPED_AVFRAME; What exactly is the use case for this? > + st->codecpar->width = info->width; > + st->codecpar->height = info->height; > + st->codecpar->format = match_pixfmt(info->format, vs->c_order); > + > + if (st->codecpar->format == AV_PIX_FMT_NONE) { > + av_log(s, AV_LOG_ERROR, "Unsupported VS pixel format %s\n", info->format->name); > + err = AVERROR_EXTERNAL; > + goto done; > + } > + av_log(s, AV_LOG_VERBOSE, "VS format %s -> pixfmt %s\n", info->format->name, > + av_get_pix_fmt_name(st->codecpar->format)); > + > + if (info->format->colorFamily == cmYCoCg) > + st->codecpar->color_space = AVCOL_SPC_YCGCO; > + > +done: > + av_free(buf); > + if (err < 0) > + read_close_vs(s); > + return err; > +} > + > +static void free_frame(void *opaque, uint8_t *data) > +{ > + AVFrame *frame = (AVFrame *)data; > + > + av_frame_free(&frame); > +} > + > +static int read_packet_vs(AVFormatContext *s, AVPacket *pkt) > +{ > + VSContext *vs = s->priv_data; > + AVStream *st = s->streams[0]; > + AVFrame *frame = NULL; > + char vserr[80]; > + const VSFrameRef *vsframe = NULL; > + const VSVideoInfo *info = vs->vsapi->getVideoInfo(vs->outnode); > + int err = 0; > + const uint8_t *src_data[4]; > + int src_linesizes[4]; > + int i; > + > + if (vs->current_frame >= info->numFrames) > + return AVERROR_EOF; > + > + vsframe = vs->vsapi->getFrame(vs->current_frame, vs->outnode, vserr, sizeof(vserr)); > + if (!vsframe) { > + av_log(s, AV_LOG_ERROR, "Error getting frame: %s\n", vserr); > + err = AVERROR_EXTERNAL; > + goto end; > + } > + > + frame = av_frame_alloc(); > + if (!frame) { > + err = AVERROR(ENOMEM); > + goto end; > + } > + > + frame->format = st->codecpar->format; > + frame->width = st->codecpar->width; > + frame->height = st->codecpar->height; > + frame->colorspace = st->codecpar->color_space; > + > + av_assert0(vs->vsapi->getFrameWidth(vsframe, 0) == frame->width); > + av_assert0(vs->vsapi->getFrameHeight(vsframe, 0) == frame->height); > + > + err = av_frame_get_buffer(frame, 0); > + if (err < 0) > + goto end; > + > + for (i = 0; i < info->format->numPlanes; i++) { > + int p = vs->c_order[i]; > + src_data[i] = vs->vsapi->getReadPtr(vsframe, p); > + src_linesizes[i] = vs->vsapi->getStride(vsframe, p); > + } > + > + av_image_copy(frame->data, frame->linesize, src_data, src_linesizes, > + frame->format, frame->width, frame->height); > + > + pkt->buf = av_buffer_create((uint8_t*)frame, sizeof(*frame), So, the whole wrapped avframe is pretty much fucked up since conception. Did nobody notice that lavc/wrapped_avframe.c is using sizeof(AVFrame)? Much like in here, it's wrong and Bad Things(tm) will happen as soon as we add a new field. > + free_frame, NULL, 0); > + if (!pkt->buf) { > + err = AVERROR(ENOMEM); > + goto end; > + } > + > + frame = NULL; // pkt owns it now > + > + pkt->data = pkt->buf->data; > + pkt->size = pkt->buf->size; > + pkt->flags |= AV_PKT_FLAG_TRUSTED; > + > + if (vs->is_cfr) > + pkt->pts = vs->current_frame; > + > + vs->current_frame++; > + > +end: > + if (err < 0) > + av_packet_unref(pkt); > + av_frame_free(&frame); > + vs->vsapi->freeFrame(vsframe); > + return err; > +} > + > +static int read_seek_vs(AVFormatContext *s, int stream_idx, int64_t ts, int flags) > +{ > + VSContext *vs = s->priv_data; > + > + if (!vs->is_cfr) > + return AVERROR(ENOSYS); > + > + vs->current_frame = FFMIN(FFMAX(0, ts), s->streams[0]->duration); > + return 0; > +} > + > +static int probe_vs(AVProbeData *p) > +{ > + // Explicitly do not support this. VS scripts are written in Python, and > + // can run arbitrary code on the user's system. > + return 0; > +} > + > +static const AVClass class_vs = { > + .class_name = "VapourSynth demuxer", > + .item_name = av_default_item_name, > + .option = options, > + .version = LIBAVUTIL_VERSION_INT, > +}; > + > +AVInputFormat ff_libvapoursynth_demuxer = { > + .name = "libvapoursynth", > + .long_name = NULL_IF_CONFIG_SMALL("VapourSynth demuxer"), > + .priv_data_size = sizeof(VSContext), > + .read_probe = probe_vs, > + .read_header = read_header_vs, > + .read_packet = read_packet_vs, > + .read_close = read_close_vs, > + .read_seek = read_seek_vs, > + .priv_class = &class_vs, > +}; >
> On 28 Apr 2018, at 03:37, wm4 <nfxjfg@googlemail.com> wrote: > > From: wm4 <nfxjfg@googlemail.com> > > This can "demux" .vpy files. > > Some minor code copied from other LGPL parts of FFmpeg. > > Possibly support VS compat pixel formats. > > TODO: > - check whether VS can change format midstream > - test vfr mode, return proper timestamps when using it > - drop "lib" prefix? > --- > configure | 4 + > libavformat/Makefile | 1 + > libavformat/allformats.c | 1 + > libavformat/libvapoursynth.c | 379 +++++++++++++++++++++++++++++++++++++++++++ > 4 files changed, 385 insertions(+) > create mode 100644 libavformat/libvapoursynth.c > > diff --git a/configure b/configure > index 9fa1665496..17e46c5daa 100755 > --- a/configure > +++ b/configure > @@ -265,6 +265,7 @@ External library support: > if openssl or gnutls is not used [no] > --enable-libtwolame enable MP2 encoding via libtwolame [no] > --enable-libv4l2 enable libv4l2/v4l-utils [no] > + --enable-libvapoursynth enable VapourSynth demuxer [no] > --enable-libvidstab enable video stabilization using vid.stab [no] > --enable-libvmaf enable vmaf filter via libvmaf [no] > --enable-libvo-amrwbenc enable AMR-WB encoding via libvo-amrwbenc [no] > @@ -1712,6 +1713,7 @@ EXTERNAL_LIBRARY_LIST=" > libtheora > libtwolame > libv4l2 > + libvapoursynth > libvorbis > libvpx > libwavpack > @@ -3068,6 +3070,7 @@ libspeex_encoder_deps="libspeex" > libspeex_encoder_select="audio_frame_queue" > libtheora_encoder_deps="libtheora" > libtwolame_encoder_deps="libtwolame" > +libvapoursynth_demuxer_deps="libvapoursynth" > libvo_amrwbenc_encoder_deps="libvo_amrwbenc" > libvorbis_decoder_deps="libvorbis" > libvorbis_encoder_deps="libvorbis libvorbisenc" > @@ -6041,6 +6044,7 @@ enabled libtwolame && require libtwolame twolame.h twolame_init -ltwolame > { check_lib libtwolame twolame.h twolame_encode_buffer_float32_interleaved -ltwolame || > die "ERROR: libtwolame must be installed and version must be >= 0.3.10"; } > enabled libv4l2 && require_pkg_config libv4l2 libv4l2 libv4l2.h v4l2_ioctl > +enabled libvapoursynth && require_pkg_config libvapoursynth "vapoursynth >= 42" VapourSynth.h getVapourSynthAPI && require_pkg_config libvapoursynth "vapoursynth-script >= 42" VSScript.h vsscript_init || die "ERROR: vapoursynth or vsscript not found"; > enabled libvidstab && require_pkg_config libvidstab "vidstab >= 0.98" vid.stab/libvidstab.h vsMotionDetectInit > enabled libvmaf && require_pkg_config libvmaf "libvmaf >= 0.6.2" libvmaf.h compute_vmaf > enabled libvo_amrwbenc && require libvo_amrwbenc vo-amrwbenc/enc_if.h E_IF_init -lvo-amrwbenc > diff --git a/libavformat/Makefile b/libavformat/Makefile > index 3eeca5091d..731b7ac714 100644 > --- a/libavformat/Makefile > +++ b/libavformat/Makefile > @@ -570,6 +570,7 @@ OBJS-$(CONFIG_LIBRTMPTE_PROTOCOL) += librtmp.o > OBJS-$(CONFIG_LIBSRT_PROTOCOL) += libsrt.o > OBJS-$(CONFIG_LIBSSH_PROTOCOL) += libssh.o > OBJS-$(CONFIG_LIBSMBCLIENT_PROTOCOL) += libsmbclient.o > +OBJS-$(CONFIG_LIBVAPOURSYNTH_DEMUXER) += libvapoursynth.o > > # protocols I/O > OBJS-$(CONFIG_ASYNC_PROTOCOL) += async.o > diff --git a/libavformat/allformats.c b/libavformat/allformats.c > index d582778b3b..67f6c4339c 100644 > --- a/libavformat/allformats.c > +++ b/libavformat/allformats.c > @@ -482,6 +482,7 @@ extern AVOutputFormat ff_chromaprint_muxer; > extern AVInputFormat ff_libgme_demuxer; > extern AVInputFormat ff_libmodplug_demuxer; > extern AVInputFormat ff_libopenmpt_demuxer; > +extern AVInputFormat ff_libvapoursynth_demuxer; > > #include "libavformat/muxer_list.c" > #include "libavformat/demuxer_list.c" > diff --git a/libavformat/libvapoursynth.c b/libavformat/libvapoursynth.c > new file mode 100644 > index 0000000000..95699e81d2 > --- /dev/null > +++ b/libavformat/libvapoursynth.c > @@ -0,0 +1,379 @@ > +/* > + * 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 > + */ > + > +/** > +* @file > +* VapourSynth demuxer > +* > +* Synthesizes vapour (?) > +*/ > + > +#include <VapourSynth.h> > +#include <VSScript.h> > + > +#include "libavutil/avassert.h" > +#include "libavutil/avstring.h" > +#include "libavutil/eval.h" > +#include "libavutil/imgutils.h" > +#include "libavutil/opt.h" > +#include "libavutil/pixdesc.h" > +#include "avformat.h" > +#include "internal.h" > + > +typedef struct VSContext { > + const AVClass *class; > + > + const VSAPI *vsapi; > + VSCore *vscore; > + VSScript *vss; > + > + VSNodeRef *outnode; > + int is_cfr; > + int current_frame; > + > + int c_order[4]; > + > + /* options */ > + int64_t max_size; > +} VSContext; > + > +#define OFFSET(x) offsetof(VSContext, x) > +#define A AV_OPT_FLAG_AUDIO_PARAM > +#define D AV_OPT_FLAG_DECODING_PARAM > +static const AVOption options[] = { > + {"max_size", "set max file size supported (in bytes)", OFFSET(max_size), AV_OPT_TYPE_INT64, {.i64 = 1 * 1024 * 1024}, 0, SIZE_MAX - 1, A|D}, > + {NULL} > +}; > + > +static int read_close_vs(AVFormatContext *s) > +{ > + VSContext *vs = s->priv_data; > + > + if (vs->outnode) > + vs->vsapi->freeNode(vs->outnode); > + > + vsscript_freeScript(vs->vss); > + vs->vss = NULL; > + vs->vsapi = NULL; > + vs->vscore = NULL; > + vs->outnode = NULL; > + > + vsscript_finalize(); > + > + return 0; > +} > + > +static int is_native_endian(enum AVPixelFormat pixfmt) > +{ > + enum AVPixelFormat other = av_pix_fmt_swap_endianness(pixfmt); > + const AVPixFmtDescriptor *pd; > + if (other == AV_PIX_FMT_NONE || other == pixfmt) > + return 1; // not affected by byte order > + pd = av_pix_fmt_desc_get(pixfmt); > + return pd && (!!HAVE_BIGENDIAN == !!(pd->flags & AV_PIX_FMT_FLAG_BE)); > +} > + > +static enum AVPixelFormat match_pixfmt(const VSFormat *vsf, int c_order[4]) > +{ > + static const int yuv_order[4] = {0, 1, 2, 0}; > + static const int rgb_order[4] = {1, 2, 0, 0}; > + const AVPixFmtDescriptor *pd; > + > + for (pd = av_pix_fmt_desc_next(NULL); pd; pd = av_pix_fmt_desc_next(pd)) { > + int is_rgb, is_yuv, i, *order; > + enum AVPixelFormat pixfmt; > + > + pixfmt = av_pix_fmt_desc_get_id(pd); > + > + if (pd->flags & (AV_PIX_FMT_FLAG_BAYER | AV_PIX_FMT_FLAG_ALPHA | > + AV_PIX_FMT_FLAG_HWACCEL | AV_PIX_FMT_FLAG_BITSTREAM)) > + continue; > + > + if (pd->log2_chroma_w != vsf->subSamplingW || > + pd->log2_chroma_h != vsf->subSamplingH) > + continue; > + > + is_rgb = vsf->colorFamily == cmRGB; > + if (is_rgb != !!(pd->flags & AV_PIX_FMT_FLAG_RGB)) > + continue; > + > + is_yuv = vsf->colorFamily == cmYUV || > + vsf->colorFamily == cmYCoCg || > + vsf->colorFamily == cmGray; > + if (!is_rgb && !is_yuv) > + continue; > + > + if (vsf->sampleType != ((pd->flags & AV_PIX_FMT_FLAG_FLOAT) ? stFloat : stInteger)) > + continue; > + > + if (av_pix_fmt_count_planes(pixfmt) != vsf->numPlanes) > + continue; > + > + if (strncmp(pd->name, "xyz", 3) == 0) > + continue; liuqideMacBook-Pro:xxx liuqi$ ../tools/patcheck ~/Downloads/FFmpeg-devel-avformat-add-vapoursynth-wrapper.patch patCHeck 1e10.0 This tool is intended to help a human check/review patches. It is very far from being free of false positives and negatives, and its output are just hints of what may or may not be bad. When you use it and it misses something or detects something wrong, fix it and send a patch to the ffmpeg-devel mailing list. License: GPL, Author: Michael Niedermayer egrep: empty (sub)expression These functions may need av_cold, please review the whole patch for similar functions needing av_cold /Users/liuqi/Downloads/FFmpeg-devel-avformat-add-vapoursynth-wrapper.patch:147:+static int is_native_endian(enum AVPixelFormat pixfmt) x==0 / x!=0 can be simplified to !x / x /Users/liuqi/Downloads/FFmpeg-devel-avformat-add-vapoursynth-wrapper.patch:193:+ if (strncmp(pd->name, "xyz", 3) == 0) /Users/liuqi/Downloads/FFmpeg-devel-avformat-add-vapoursynth-wrapper.patch:204:+ c->offset != 0 || c->shift != 0 || > + > + if (!is_native_endian(pixfmt)) > + continue; > + > + order = is_yuv ? yuv_order : rgb_order; > + > + for (i = 0; i < pd->nb_components; i++) { > + const AVComponentDescriptor *c = &pd->comp[i]; > + if (order[c->plane] != i || > + c->offset != 0 || c->shift != 0 || > + c->step != vsf->bytesPerSample || > + c->depth != vsf->bitsPerSample) > + goto cont; > + } > + > + // Use it. > + memcpy(c_order, order, sizeof(int[4])); > + return pixfmt; > + > + cont: ; > + } > + > + return AV_PIX_FMT_NONE; > +} > + > +static int read_header_vs(AVFormatContext *s) > +{ > + AVStream *st; > + AVIOContext *pb = s->pb; > + VSContext *vs = s->priv_data; > + int64_t sz = avio_size(pb); > + char *buf = NULL; > + char dummy; > + const VSVideoInfo *info; > + int err; > + > + vsscript_init(); > + > + if (sz < 0 || sz > vs->max_size) { > + if (sz < 0) > + av_log(s, AV_LOG_WARNING, "Could not determine file size\n"); > + sz = vs->max_size; > + } > + > + buf = av_malloc(sz + 1); > + if (!buf) { > + err = AVERROR(ENOMEM); > + goto done; > + } > + sz = avio_read(pb, buf, sz); > + > + if (sz < 0) { > + av_log(s, AV_LOG_ERROR, "Could not read script.\n"); > + err = sz; > + goto done; > + } > + > + // Data left means our buffer (the max_size option) is too small > + if (avio_read(pb, &dummy, 1) == 1) { > + av_log(s, AV_LOG_ERROR, "File size is larger than max_size option " > + "value %"PRIi64", consider increasing the max_size option\n", > + vs->max_size); > + err = AVERROR_BUFFER_TOO_SMALL; > + goto done; > + } > + > + if (vsscript_createScript(&vs->vss)) { > + av_log(s, AV_LOG_ERROR, "Failed to create script instance.\n"); > + err = AVERROR_EXTERNAL; > + goto done; > + } > + > + buf[sz] = '\0'; > + if (vsscript_evaluateScript(&vs->vss, buf, s->url, 0)) { > + const char *msg = vsscript_getError(vs->vss); > + av_log(s, AV_LOG_ERROR, "Failed to parse script: %s\n", msg ? msg : "(unknown)"); > + err = AVERROR_EXTERNAL; > + goto done; > + } > + > + vs->vsapi = vsscript_getVSApi(); > + vs->vscore = vsscript_getCore(vs->vss); > + > + vs->outnode = vsscript_getOutput(vs->vss, 0); > + if (!vs->outnode) { > + av_log(s, AV_LOG_ERROR, "Could not get script output node.\n"); > + err = AVERROR_EXTERNAL; > + goto done; > + } > + > + st = avformat_new_stream(s, NULL); > + if (!st) { > + err = AVERROR(ENOMEM); > + goto done; > + } > + > + info = vs->vsapi->getVideoInfo(vs->outnode); > + > + if (info->fpsDen) { > + vs->is_cfr = 1; > + avpriv_set_pts_info(st, 64, info->fpsDen, info->fpsNum); > + st->duration = info->numFrames; > + } else { > + // VFR. Just set "something". > + avpriv_set_pts_info(st, 64, 1, AV_TIME_BASE); > + s->ctx_flags |= AVFMTCTX_UNSEEKABLE; > + } > + > + st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; > + st->codecpar->codec_id = AV_CODEC_ID_WRAPPED_AVFRAME; > + st->codecpar->width = info->width; > + st->codecpar->height = info->height; > + st->codecpar->format = match_pixfmt(info->format, vs->c_order); > + > + if (st->codecpar->format == AV_PIX_FMT_NONE) { > + av_log(s, AV_LOG_ERROR, "Unsupported VS pixel format %s\n", info->format->name); > + err = AVERROR_EXTERNAL; > + goto done; > + } > + av_log(s, AV_LOG_VERBOSE, "VS format %s -> pixfmt %s\n", info->format->name, > + av_get_pix_fmt_name(st->codecpar->format)); > + > + if (info->format->colorFamily == cmYCoCg) > + st->codecpar->color_space = AVCOL_SPC_YCGCO; > + > +done: > + av_free(buf); > + if (err < 0) > + read_close_vs(s); > + return err; > +} > + > +static void free_frame(void *opaque, uint8_t *data) > +{ > + AVFrame *frame = (AVFrame *)data; > + > + av_frame_free(&frame); > +} > + > +static int read_packet_vs(AVFormatContext *s, AVPacket *pkt) > +{ > + VSContext *vs = s->priv_data; > + AVStream *st = s->streams[0]; > + AVFrame *frame = NULL; > + char vserr[80]; > + const VSFrameRef *vsframe = NULL; > + const VSVideoInfo *info = vs->vsapi->getVideoInfo(vs->outnode); > + int err = 0; > + const uint8_t *src_data[4]; > + int src_linesizes[4]; > + int i; > + > + if (vs->current_frame >= info->numFrames) > + return AVERROR_EOF; > + > + vsframe = vs->vsapi->getFrame(vs->current_frame, vs->outnode, vserr, sizeof(vserr)); > + if (!vsframe) { > + av_log(s, AV_LOG_ERROR, "Error getting frame: %s\n", vserr); > + err = AVERROR_EXTERNAL; > + goto end; > + } > + > + frame = av_frame_alloc(); > + if (!frame) { > + err = AVERROR(ENOMEM); > + goto end; > + } > + > + frame->format = st->codecpar->format; > + frame->width = st->codecpar->width; > + frame->height = st->codecpar->height; > + frame->colorspace = st->codecpar->color_space; > + > + av_assert0(vs->vsapi->getFrameWidth(vsframe, 0) == frame->width); > + av_assert0(vs->vsapi->getFrameHeight(vsframe, 0) == frame->height); > + > + err = av_frame_get_buffer(frame, 0); > + if (err < 0) > + goto end; > + > + for (i = 0; i < info->format->numPlanes; i++) { > + int p = vs->c_order[i]; > + src_data[i] = vs->vsapi->getReadPtr(vsframe, p); > + src_linesizes[i] = vs->vsapi->getStride(vsframe, p); > + } > + > + av_image_copy(frame->data, frame->linesize, src_data, src_linesizes, > + frame->format, frame->width, frame->height); > + > + pkt->buf = av_buffer_create((uint8_t*)frame, sizeof(*frame), > + free_frame, NULL, 0); > + if (!pkt->buf) { > + err = AVERROR(ENOMEM); > + goto end; > + } > + > + frame = NULL; // pkt owns it now > + > + pkt->data = pkt->buf->data; > + pkt->size = pkt->buf->size; > + pkt->flags |= AV_PKT_FLAG_TRUSTED; > + > + if (vs->is_cfr) > + pkt->pts = vs->current_frame; > + > + vs->current_frame++; > + > +end: > + if (err < 0) > + av_packet_unref(pkt); > + av_frame_free(&frame); > + vs->vsapi->freeFrame(vsframe); > + return err; > +} > + > +static int read_seek_vs(AVFormatContext *s, int stream_idx, int64_t ts, int flags) > +{ > + VSContext *vs = s->priv_data; > + > + if (!vs->is_cfr) > + return AVERROR(ENOSYS); > + > + vs->current_frame = FFMIN(FFMAX(0, ts), s->streams[0]->duration); > + return 0; > +} > + > +static int probe_vs(AVProbeData *p) > +{ > + // Explicitly do not support this. VS scripts are written in Python, and > + // can run arbitrary code on the user's system. > + return 0; > +} > + > +static const AVClass class_vs = { > + .class_name = "VapourSynth demuxer", > + .item_name = av_default_item_name, > + .option = options, > + .version = LIBAVUTIL_VERSION_INT, > +}; > + > +AVInputFormat ff_libvapoursynth_demuxer = { > + .name = "libvapoursynth", > + .long_name = NULL_IF_CONFIG_SMALL("VapourSynth demuxer"), > + .priv_data_size = sizeof(VSContext), > + .read_probe = probe_vs, > + .read_header = read_header_vs, > + .read_packet = read_packet_vs, > + .read_close = read_close_vs, > + .read_seek = read_seek_vs, > + .priv_class = &class_vs, > +}; > -- > 2.16.1 > > _______________________________________________ > ffmpeg-devel mailing list > ffmpeg-devel@ffmpeg.org > http://ffmpeg.org/mailman/listinfo/ffmpeg-devel Thanks Steven
On Sat, 28 Apr 2018 11:08:01 +0800 Steven Liu <lq@chinaffmpeg.org> wrote: > > On 28 Apr 2018, at 03:37, wm4 <nfxjfg@googlemail.com> wrote: > > > > + > > + if (strncmp(pd->name, "xyz", 3) == 0) > > + continue; > > liuqideMacBook-Pro:xxx liuqi$ ../tools/patcheck ~/Downloads/FFmpeg-devel-avformat-add-vapoursynth-wrapper.patch > patCHeck 1e10.0 > This tool is intended to help a human check/review patches. It is very far from > being free of false positives and negatives, and its output are just hints of what > may or may not be bad. When you use it and it misses something or detects > something wrong, fix it and send a patch to the ffmpeg-devel mailing list. > License: GPL, Author: Michael Niedermayer > egrep: empty (sub)expression > > These functions may need av_cold, please review the whole patch for similar functions needing av_cold > /Users/liuqi/Downloads/FFmpeg-devel-avformat-add-vapoursynth-wrapper.patch:147:+static int is_native_endian(enum AVPixelFormat pixfmt) av_cold seems dumb but I guess I can cargo-cult that. > x==0 / x!=0 can be simplified to !x / x > /Users/liuqi/Downloads/FFmpeg-devel-avformat-add-vapoursynth-wrapper.patch:193:+ if (strncmp(pd->name, "xyz", 3) == 0) > /Users/liuqi/Downloads/FFmpeg-devel-avformat-add-vapoursynth-wrapper.patch:204:+ c->offset != 0 || c->shift != 0 || I prefer to keep those explicit, especially the strncmp one. But I don't have that strong feelings about it, so if someone minds I can still change it.
On Fri, 27 Apr 2018 22:27:47 -0300 James Almer <jamrial@gmail.com> wrote: > On 4/27/2018 4:37 PM, wm4 wrote: > > From: wm4 <nfxjfg@googlemail.com> > > > > require_pkg_config libvapoursynth "vapoursynth-script >= 42" VSScript.h vsscript_init || die "ERROR: vapoursynth or vsscript not found"; > > die() is needed only with test_pkg_config and check_pkg_config, not > require_pkg_config. OK, will remove. > And seeing that vapoursynth-script.pc depends on vapoursynth.pc, you can > simplify all this by only checking for vapoursynth-script. I'm not sure if I should rely on this. But considering VSScript.h includes VapourSynth.h anyway, and we don't use any VapourSynth functions directly, the risk is low, so I'll remove it. > > + st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; > > + st->codecpar->codec_id = AV_CODEC_ID_WRAPPED_AVFRAME; > > What exactly is the use case for this? You mean using wrapped avframe? It means you don't have to mess with raw encoding/decoding. Unless we have some fancy way to make rawdec.c accept frames packed by av_image_copy_to_buffer() without having to map pixfmts to raw codecs? (rawdec.c is a lot of code, I might have missed a simple way to make it accept that.) > > + pkt->buf = av_buffer_create((uint8_t*)frame, sizeof(*frame), > > So, the whole wrapped avframe is pretty much fucked up since conception. > Did nobody notice that lavc/wrapped_avframe.c is using sizeof(AVFrame)? > > Much like in here, it's wrong and Bad Things(tm) will happen as soon as > we add a new field. Ah, that is true. In theory it's in conflict with out policy that libavutil can append fields to AVFrame, without rebuilding libavcodec. I've copied this AVFrame packing code from kmsgrab.c though (which is also new code, and which I've noticed leaks memory when handling malloc failure). I think it'd be best if we had updated "non-dumb" side data which can manage AVFrames in a correct way. But it'll be a long way until this happens, apparently. If you really want to solve this, we could introduce an av_frame_get_struct_size() call, which would be dumb, but correct. (Or maybe make it avpriv_?)
On 4/28/2018 8:51 AM, wm4 wrote: > On Sat, 28 Apr 2018 11:08:01 +0800 > Steven Liu <lq@chinaffmpeg.org> wrote: > >>> On 28 Apr 2018, at 03:37, wm4 <nfxjfg@googlemail.com> wrote: >>> > >>> + >>> + if (strncmp(pd->name, "xyz", 3) == 0) >>> + continue; >> >> liuqideMacBook-Pro:xxx liuqi$ ../tools/patcheck ~/Downloads/FFmpeg-devel-avformat-add-vapoursynth-wrapper.patch >> patCHeck 1e10.0 >> This tool is intended to help a human check/review patches. It is very far from >> being free of false positives and negatives, and its output are just hints of what >> may or may not be bad. When you use it and it misses something or detects >> something wrong, fix it and send a patch to the ffmpeg-devel mailing list. >> License: GPL, Author: Michael Niedermayer >> egrep: empty (sub)expression >> >> These functions may need av_cold, please review the whole patch for similar functions needing av_cold >> /Users/liuqi/Downloads/FFmpeg-devel-avformat-add-vapoursynth-wrapper.patch:147:+static int is_native_endian(enum AVPixelFormat pixfmt) > > av_cold seems dumb but I guess I can cargo-cult that. Unless av_cold is needed/expected for AVInputFormat->read_header like it's done for AVCodec->init, none of these functions need it at all. Especially since they will be inlined by the compiler anyway. At most, make the small ones like is_native_endian explicitly inline. > >> x==0 / x!=0 can be simplified to !x / x >> /Users/liuqi/Downloads/FFmpeg-devel-avformat-add-vapoursynth-wrapper.patch:193:+ if (strncmp(pd->name, "xyz", 3) == 0) >> /Users/liuqi/Downloads/FFmpeg-devel-avformat-add-vapoursynth-wrapper.patch:204:+ c->offset != 0 || c->shift != 0 || > > I prefer to keep those explicit, especially the strncmp one. But I > don't have that strong feelings about it, so if someone minds I can > still change it. > _______________________________________________ > ffmpeg-devel mailing list > ffmpeg-devel@ffmpeg.org > http://ffmpeg.org/mailman/listinfo/ffmpeg-devel >
On Sat, 28 Apr 2018 11:58:27 -0300 James Almer <jamrial@gmail.com> wrote: > On 4/28/2018 8:51 AM, wm4 wrote: > > On Sat, 28 Apr 2018 11:08:01 +0800 > > Steven Liu <lq@chinaffmpeg.org> wrote: > > > >>> On 28 Apr 2018, at 03:37, wm4 <nfxjfg@googlemail.com> wrote: > >>> > > > >>> + > >>> + if (strncmp(pd->name, "xyz", 3) == 0) > >>> + continue; > >> > >> liuqideMacBook-Pro:xxx liuqi$ ../tools/patcheck ~/Downloads/FFmpeg-devel-avformat-add-vapoursynth-wrapper.patch > >> patCHeck 1e10.0 > >> This tool is intended to help a human check/review patches. It is very far from > >> being free of false positives and negatives, and its output are just hints of what > >> may or may not be bad. When you use it and it misses something or detects > >> something wrong, fix it and send a patch to the ffmpeg-devel mailing list. > >> License: GPL, Author: Michael Niedermayer > >> egrep: empty (sub)expression > >> > >> These functions may need av_cold, please review the whole patch for similar functions needing av_cold > >> /Users/liuqi/Downloads/FFmpeg-devel-avformat-add-vapoursynth-wrapper.patch:147:+static int is_native_endian(enum AVPixelFormat pixfmt) > > > > av_cold seems dumb but I guess I can cargo-cult that. > > Unless av_cold is needed/expected for AVInputFormat->read_header like > it's done for AVCodec->init, none of these functions need it at all. > Especially since they will be inlined by the compiler anyway. Only a small number of functions/formats in libavformat use av_cold. But it doesn't harm either. > At most, make the small ones like is_native_endian explicitly inline. Doesn't seem needed either.
diff --git a/configure b/configure index 9fa1665496..17e46c5daa 100755 --- a/configure +++ b/configure @@ -265,6 +265,7 @@ External library support: if openssl or gnutls is not used [no] --enable-libtwolame enable MP2 encoding via libtwolame [no] --enable-libv4l2 enable libv4l2/v4l-utils [no] + --enable-libvapoursynth enable VapourSynth demuxer [no] --enable-libvidstab enable video stabilization using vid.stab [no] --enable-libvmaf enable vmaf filter via libvmaf [no] --enable-libvo-amrwbenc enable AMR-WB encoding via libvo-amrwbenc [no] @@ -1712,6 +1713,7 @@ EXTERNAL_LIBRARY_LIST=" libtheora libtwolame libv4l2 + libvapoursynth libvorbis libvpx libwavpack @@ -3068,6 +3070,7 @@ libspeex_encoder_deps="libspeex" libspeex_encoder_select="audio_frame_queue" libtheora_encoder_deps="libtheora" libtwolame_encoder_deps="libtwolame" +libvapoursynth_demuxer_deps="libvapoursynth" libvo_amrwbenc_encoder_deps="libvo_amrwbenc" libvorbis_decoder_deps="libvorbis" libvorbis_encoder_deps="libvorbis libvorbisenc" @@ -6041,6 +6044,7 @@ enabled libtwolame && require libtwolame twolame.h twolame_init -ltwolame { check_lib libtwolame twolame.h twolame_encode_buffer_float32_interleaved -ltwolame || die "ERROR: libtwolame must be installed and version must be >= 0.3.10"; } enabled libv4l2 && require_pkg_config libv4l2 libv4l2 libv4l2.h v4l2_ioctl +enabled libvapoursynth && require_pkg_config libvapoursynth "vapoursynth >= 42" VapourSynth.h getVapourSynthAPI && require_pkg_config libvapoursynth "vapoursynth-script >= 42" VSScript.h vsscript_init || die "ERROR: vapoursynth or vsscript not found"; enabled libvidstab && require_pkg_config libvidstab "vidstab >= 0.98" vid.stab/libvidstab.h vsMotionDetectInit enabled libvmaf && require_pkg_config libvmaf "libvmaf >= 0.6.2" libvmaf.h compute_vmaf enabled libvo_amrwbenc && require libvo_amrwbenc vo-amrwbenc/enc_if.h E_IF_init -lvo-amrwbenc diff --git a/libavformat/Makefile b/libavformat/Makefile index 3eeca5091d..731b7ac714 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -570,6 +570,7 @@ OBJS-$(CONFIG_LIBRTMPTE_PROTOCOL) += librtmp.o OBJS-$(CONFIG_LIBSRT_PROTOCOL) += libsrt.o OBJS-$(CONFIG_LIBSSH_PROTOCOL) += libssh.o OBJS-$(CONFIG_LIBSMBCLIENT_PROTOCOL) += libsmbclient.o +OBJS-$(CONFIG_LIBVAPOURSYNTH_DEMUXER) += libvapoursynth.o # protocols I/O OBJS-$(CONFIG_ASYNC_PROTOCOL) += async.o diff --git a/libavformat/allformats.c b/libavformat/allformats.c index d582778b3b..67f6c4339c 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -482,6 +482,7 @@ extern AVOutputFormat ff_chromaprint_muxer; extern AVInputFormat ff_libgme_demuxer; extern AVInputFormat ff_libmodplug_demuxer; extern AVInputFormat ff_libopenmpt_demuxer; +extern AVInputFormat ff_libvapoursynth_demuxer; #include "libavformat/muxer_list.c" #include "libavformat/demuxer_list.c" diff --git a/libavformat/libvapoursynth.c b/libavformat/libvapoursynth.c new file mode 100644 index 0000000000..95699e81d2 --- /dev/null +++ b/libavformat/libvapoursynth.c @@ -0,0 +1,379 @@ +/* + * 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 + */ + +/** +* @file +* VapourSynth demuxer +* +* Synthesizes vapour (?) +*/ + +#include <VapourSynth.h> +#include <VSScript.h> + +#include "libavutil/avassert.h" +#include "libavutil/avstring.h" +#include "libavutil/eval.h" +#include "libavutil/imgutils.h" +#include "libavutil/opt.h" +#include "libavutil/pixdesc.h" +#include "avformat.h" +#include "internal.h" + +typedef struct VSContext { + const AVClass *class; + + const VSAPI *vsapi; + VSCore *vscore; + VSScript *vss; + + VSNodeRef *outnode; + int is_cfr; + int current_frame; + + int c_order[4]; + + /* options */ + int64_t max_size; +} VSContext; + +#define OFFSET(x) offsetof(VSContext, x) +#define A AV_OPT_FLAG_AUDIO_PARAM +#define D AV_OPT_FLAG_DECODING_PARAM +static const AVOption options[] = { + {"max_size", "set max file size supported (in bytes)", OFFSET(max_size), AV_OPT_TYPE_INT64, {.i64 = 1 * 1024 * 1024}, 0, SIZE_MAX - 1, A|D}, + {NULL} +}; + +static int read_close_vs(AVFormatContext *s) +{ + VSContext *vs = s->priv_data; + + if (vs->outnode) + vs->vsapi->freeNode(vs->outnode); + + vsscript_freeScript(vs->vss); + vs->vss = NULL; + vs->vsapi = NULL; + vs->vscore = NULL; + vs->outnode = NULL; + + vsscript_finalize(); + + return 0; +} + +static int is_native_endian(enum AVPixelFormat pixfmt) +{ + enum AVPixelFormat other = av_pix_fmt_swap_endianness(pixfmt); + const AVPixFmtDescriptor *pd; + if (other == AV_PIX_FMT_NONE || other == pixfmt) + return 1; // not affected by byte order + pd = av_pix_fmt_desc_get(pixfmt); + return pd && (!!HAVE_BIGENDIAN == !!(pd->flags & AV_PIX_FMT_FLAG_BE)); +} + +static enum AVPixelFormat match_pixfmt(const VSFormat *vsf, int c_order[4]) +{ + static const int yuv_order[4] = {0, 1, 2, 0}; + static const int rgb_order[4] = {1, 2, 0, 0}; + const AVPixFmtDescriptor *pd; + + for (pd = av_pix_fmt_desc_next(NULL); pd; pd = av_pix_fmt_desc_next(pd)) { + int is_rgb, is_yuv, i, *order; + enum AVPixelFormat pixfmt; + + pixfmt = av_pix_fmt_desc_get_id(pd); + + if (pd->flags & (AV_PIX_FMT_FLAG_BAYER | AV_PIX_FMT_FLAG_ALPHA | + AV_PIX_FMT_FLAG_HWACCEL | AV_PIX_FMT_FLAG_BITSTREAM)) + continue; + + if (pd->log2_chroma_w != vsf->subSamplingW || + pd->log2_chroma_h != vsf->subSamplingH) + continue; + + is_rgb = vsf->colorFamily == cmRGB; + if (is_rgb != !!(pd->flags & AV_PIX_FMT_FLAG_RGB)) + continue; + + is_yuv = vsf->colorFamily == cmYUV || + vsf->colorFamily == cmYCoCg || + vsf->colorFamily == cmGray; + if (!is_rgb && !is_yuv) + continue; + + if (vsf->sampleType != ((pd->flags & AV_PIX_FMT_FLAG_FLOAT) ? stFloat : stInteger)) + continue; + + if (av_pix_fmt_count_planes(pixfmt) != vsf->numPlanes) + continue; + + if (strncmp(pd->name, "xyz", 3) == 0) + continue; + + if (!is_native_endian(pixfmt)) + continue; + + order = is_yuv ? yuv_order : rgb_order; + + for (i = 0; i < pd->nb_components; i++) { + const AVComponentDescriptor *c = &pd->comp[i]; + if (order[c->plane] != i || + c->offset != 0 || c->shift != 0 || + c->step != vsf->bytesPerSample || + c->depth != vsf->bitsPerSample) + goto cont; + } + + // Use it. + memcpy(c_order, order, sizeof(int[4])); + return pixfmt; + + cont: ; + } + + return AV_PIX_FMT_NONE; +} + +static int read_header_vs(AVFormatContext *s) +{ + AVStream *st; + AVIOContext *pb = s->pb; + VSContext *vs = s->priv_data; + int64_t sz = avio_size(pb); + char *buf = NULL; + char dummy; + const VSVideoInfo *info; + int err; + + vsscript_init(); + + if (sz < 0 || sz > vs->max_size) { + if (sz < 0) + av_log(s, AV_LOG_WARNING, "Could not determine file size\n"); + sz = vs->max_size; + } + + buf = av_malloc(sz + 1); + if (!buf) { + err = AVERROR(ENOMEM); + goto done; + } + sz = avio_read(pb, buf, sz); + + if (sz < 0) { + av_log(s, AV_LOG_ERROR, "Could not read script.\n"); + err = sz; + goto done; + } + + // Data left means our buffer (the max_size option) is too small + if (avio_read(pb, &dummy, 1) == 1) { + av_log(s, AV_LOG_ERROR, "File size is larger than max_size option " + "value %"PRIi64", consider increasing the max_size option\n", + vs->max_size); + err = AVERROR_BUFFER_TOO_SMALL; + goto done; + } + + if (vsscript_createScript(&vs->vss)) { + av_log(s, AV_LOG_ERROR, "Failed to create script instance.\n"); + err = AVERROR_EXTERNAL; + goto done; + } + + buf[sz] = '\0'; + if (vsscript_evaluateScript(&vs->vss, buf, s->url, 0)) { + const char *msg = vsscript_getError(vs->vss); + av_log(s, AV_LOG_ERROR, "Failed to parse script: %s\n", msg ? msg : "(unknown)"); + err = AVERROR_EXTERNAL; + goto done; + } + + vs->vsapi = vsscript_getVSApi(); + vs->vscore = vsscript_getCore(vs->vss); + + vs->outnode = vsscript_getOutput(vs->vss, 0); + if (!vs->outnode) { + av_log(s, AV_LOG_ERROR, "Could not get script output node.\n"); + err = AVERROR_EXTERNAL; + goto done; + } + + st = avformat_new_stream(s, NULL); + if (!st) { + err = AVERROR(ENOMEM); + goto done; + } + + info = vs->vsapi->getVideoInfo(vs->outnode); + + if (info->fpsDen) { + vs->is_cfr = 1; + avpriv_set_pts_info(st, 64, info->fpsDen, info->fpsNum); + st->duration = info->numFrames; + } else { + // VFR. Just set "something". + avpriv_set_pts_info(st, 64, 1, AV_TIME_BASE); + s->ctx_flags |= AVFMTCTX_UNSEEKABLE; + } + + st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; + st->codecpar->codec_id = AV_CODEC_ID_WRAPPED_AVFRAME; + st->codecpar->width = info->width; + st->codecpar->height = info->height; + st->codecpar->format = match_pixfmt(info->format, vs->c_order); + + if (st->codecpar->format == AV_PIX_FMT_NONE) { + av_log(s, AV_LOG_ERROR, "Unsupported VS pixel format %s\n", info->format->name); + err = AVERROR_EXTERNAL; + goto done; + } + av_log(s, AV_LOG_VERBOSE, "VS format %s -> pixfmt %s\n", info->format->name, + av_get_pix_fmt_name(st->codecpar->format)); + + if (info->format->colorFamily == cmYCoCg) + st->codecpar->color_space = AVCOL_SPC_YCGCO; + +done: + av_free(buf); + if (err < 0) + read_close_vs(s); + return err; +} + +static void free_frame(void *opaque, uint8_t *data) +{ + AVFrame *frame = (AVFrame *)data; + + av_frame_free(&frame); +} + +static int read_packet_vs(AVFormatContext *s, AVPacket *pkt) +{ + VSContext *vs = s->priv_data; + AVStream *st = s->streams[0]; + AVFrame *frame = NULL; + char vserr[80]; + const VSFrameRef *vsframe = NULL; + const VSVideoInfo *info = vs->vsapi->getVideoInfo(vs->outnode); + int err = 0; + const uint8_t *src_data[4]; + int src_linesizes[4]; + int i; + + if (vs->current_frame >= info->numFrames) + return AVERROR_EOF; + + vsframe = vs->vsapi->getFrame(vs->current_frame, vs->outnode, vserr, sizeof(vserr)); + if (!vsframe) { + av_log(s, AV_LOG_ERROR, "Error getting frame: %s\n", vserr); + err = AVERROR_EXTERNAL; + goto end; + } + + frame = av_frame_alloc(); + if (!frame) { + err = AVERROR(ENOMEM); + goto end; + } + + frame->format = st->codecpar->format; + frame->width = st->codecpar->width; + frame->height = st->codecpar->height; + frame->colorspace = st->codecpar->color_space; + + av_assert0(vs->vsapi->getFrameWidth(vsframe, 0) == frame->width); + av_assert0(vs->vsapi->getFrameHeight(vsframe, 0) == frame->height); + + err = av_frame_get_buffer(frame, 0); + if (err < 0) + goto end; + + for (i = 0; i < info->format->numPlanes; i++) { + int p = vs->c_order[i]; + src_data[i] = vs->vsapi->getReadPtr(vsframe, p); + src_linesizes[i] = vs->vsapi->getStride(vsframe, p); + } + + av_image_copy(frame->data, frame->linesize, src_data, src_linesizes, + frame->format, frame->width, frame->height); + + pkt->buf = av_buffer_create((uint8_t*)frame, sizeof(*frame), + free_frame, NULL, 0); + if (!pkt->buf) { + err = AVERROR(ENOMEM); + goto end; + } + + frame = NULL; // pkt owns it now + + pkt->data = pkt->buf->data; + pkt->size = pkt->buf->size; + pkt->flags |= AV_PKT_FLAG_TRUSTED; + + if (vs->is_cfr) + pkt->pts = vs->current_frame; + + vs->current_frame++; + +end: + if (err < 0) + av_packet_unref(pkt); + av_frame_free(&frame); + vs->vsapi->freeFrame(vsframe); + return err; +} + +static int read_seek_vs(AVFormatContext *s, int stream_idx, int64_t ts, int flags) +{ + VSContext *vs = s->priv_data; + + if (!vs->is_cfr) + return AVERROR(ENOSYS); + + vs->current_frame = FFMIN(FFMAX(0, ts), s->streams[0]->duration); + return 0; +} + +static int probe_vs(AVProbeData *p) +{ + // Explicitly do not support this. VS scripts are written in Python, and + // can run arbitrary code on the user's system. + return 0; +} + +static const AVClass class_vs = { + .class_name = "VapourSynth demuxer", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + +AVInputFormat ff_libvapoursynth_demuxer = { + .name = "libvapoursynth", + .long_name = NULL_IF_CONFIG_SMALL("VapourSynth demuxer"), + .priv_data_size = sizeof(VSContext), + .read_probe = probe_vs, + .read_header = read_header_vs, + .read_packet = read_packet_vs, + .read_close = read_close_vs, + .read_seek = read_seek_vs, + .priv_class = &class_vs, +};
From: wm4 <nfxjfg@googlemail.com> This can "demux" .vpy files. Some minor code copied from other LGPL parts of FFmpeg. Possibly support VS compat pixel formats. TODO: - check whether VS can change format midstream - test vfr mode, return proper timestamps when using it - drop "lib" prefix? --- configure | 4 + libavformat/Makefile | 1 + libavformat/allformats.c | 1 + libavformat/libvapoursynth.c | 379 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 385 insertions(+) create mode 100644 libavformat/libvapoursynth.c