diff mbox

[FFmpeg-devel] avformat: add vapoursynth wrapper

Message ID 20180427193723.8361-1-nfxjfg@googlemail.com
State Superseded
Headers show

Commit Message

wm4 April 27, 2018, 7:37 p.m. UTC
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

Comments

James Almer April 28, 2018, 1:27 a.m. UTC | #1
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,
> +};
>
Liu Steven April 28, 2018, 3:08 a.m. UTC | #2
> 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
wm4 April 28, 2018, 11:51 a.m. UTC | #3
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.
wm4 April 28, 2018, 12:07 p.m. UTC | #4
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_?)
James Almer April 28, 2018, 2:58 p.m. UTC | #5
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
>
wm4 April 28, 2018, 3:04 p.m. UTC | #6
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 mbox

Patch

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,
+};