From cdb9de5f3ac5470341e8e04978e7790d17be1622 Mon Sep 17 00:00:00 2001 From: Helenerineium Date: Sun, 30 Jun 2019 20:13:49 +0800 Subject: [PATCH] Add Alternative VapourSynth demuxer Usage: ffmpeg -f vapoursynth_alt --- configure | 1 + libavformat/Makefile | 1 + libavformat/allformats.c | 1 + libavformat/vapoursynth_alt.c | 363 ++++++++++++++++++++++++++++++++++ 4 files changed, 366 insertions(+) create mode 100644 libavformat/vapoursynth_alt.c diff --git a/configure b/configure index 1535dc3c5b..c9cf045003 100755 --- a/configure +++ b/configure @@ -3376,6 +3376,7 @@ libxavs2_encoder_deps="libxavs2" libxvid_encoder_deps="libxvid" libzvbi_teletext_decoder_deps="libzvbi" vapoursynth_demuxer_deps="vapoursynth" +vapoursynth_alt_demuxer_deps="vapoursynth" videotoolbox_suggest="coreservices" videotoolbox_deps="corefoundation coremedia corevideo" videotoolbox_encoder_deps="videotoolbox VTCompressionSessionPrepareToEncodeFrames" diff --git a/libavformat/Makefile b/libavformat/Makefile index 6566e40cac..4e75a9384e 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -618,6 +618,7 @@ OBJS-$(CONFIG_LIBGME_DEMUXER) += libgme.o OBJS-$(CONFIG_LIBMODPLUG_DEMUXER) += libmodplug.o OBJS-$(CONFIG_LIBOPENMPT_DEMUXER) += libopenmpt.o OBJS-$(CONFIG_VAPOURSYNTH_DEMUXER) += vapoursynth.o +OBJS-$(CONFIG_VAPOURSYNTH_ALT_DEMUXER) += vapoursynth_alt.o # protocols I/O OBJS-$(CONFIG_ASYNC_PROTOCOL) += async.o diff --git a/libavformat/allformats.c b/libavformat/allformats.c index d066a7745b..490d16aaa5 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -535,6 +535,7 @@ extern const AVInputFormat ff_libgme_demuxer; extern const AVInputFormat ff_libmodplug_demuxer; extern const AVInputFormat ff_libopenmpt_demuxer; extern const AVInputFormat ff_vapoursynth_demuxer; +extern const AVInputFormat ff_vapoursynth_alt_demuxer; #include "libavformat/muxer_list.c" #include "libavformat/demuxer_list.c" diff --git a/libavformat/vapoursynth_alt.c b/libavformat/vapoursynth_alt.c new file mode 100644 index 0000000000..efbf90f588 --- /dev/null +++ b/libavformat/vapoursynth_alt.c @@ -0,0 +1,363 @@ +/* + * 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 + +#include +#include + +#include "libavutil/cpu.h" +#include "libavutil/internal.h" +#include "libavutil/pixdesc.h" +#include "libavutil/time.h" +#include "libavcodec/internal.h" +#include "avformat.h" +#include "internal.h" + +typedef struct VSContext { + const VSAPI *vsapi; + const VSVideoInfo *vi; + VSNodeRef *node; + VSScript *script; + atomic_int async_pending; + int current_frame; + int ncpu; +} VSContext; + +static void VS_CC async_callback(void *user_data, const VSFrameRef *f, int n, VSNodeRef *node, const char *error_msg) +{ + AVFormatContext *s = user_data; + VSContext *vs = s->priv_data; + + if (!f) + av_log(s, AV_LOG_WARNING, "Async frame request failed: %s\n", error_msg); + + vs->vsapi->freeFrame(f); + atomic_fetch_sub(&vs->async_pending, 1); +} + +static av_cold int map_vsformat(AVFormatContext *s, AVStream *st, const VSFormat *vsformat) +{ + if (vsformat->subSamplingW > 2 || vsformat->subSamplingH > 2) + return AV_PIX_FMT_NONE; + + switch (vsformat->colorFamily) { + case cmYUV: + case cmYCoCg: + switch ((vsformat->subSamplingW << 2) | vsformat->subSamplingH) { + case ((0 << 2) | 0): /* 4:4:4 */ + switch (vsformat->bitsPerSample) { + case 8: return AV_PIX_FMT_YUV444P; + case 9: return AV_PIX_FMT_YUV444P9; + case 10: return AV_PIX_FMT_YUV444P10; + case 12: return AV_PIX_FMT_YUV444P12; + case 14: return AV_PIX_FMT_YUV444P14; + case 16: return AV_PIX_FMT_YUV444P16; + default: return AV_PIX_FMT_NONE; + } + case ((0 << 2) | 1): /* 4:4:0 */ + switch (vsformat->bitsPerSample) { + case 8: return AV_PIX_FMT_YUV440P; + case 10: return AV_PIX_FMT_YUV440P10; + case 12: return AV_PIX_FMT_YUV440P12; + default: return AV_PIX_FMT_NONE; + } + case ((1 << 2) | 0): /* 4:2:2 */ + switch (vsformat->bitsPerSample) { + case 8: return AV_PIX_FMT_YUV422P; + case 9: return AV_PIX_FMT_YUV422P9; + case 10: return AV_PIX_FMT_YUV422P10; + case 12: return AV_PIX_FMT_YUV422P12; + case 14: return AV_PIX_FMT_YUV422P14; + case 16: return AV_PIX_FMT_YUV422P16; + default: return AV_PIX_FMT_NONE; + } + case ((1 << 2) | 1): /* 4:2:0 */ + switch (vsformat->bitsPerSample) { + case 8: return AV_PIX_FMT_YUV420P; + case 9: return AV_PIX_FMT_YUV420P9; + case 10: return AV_PIX_FMT_YUV420P10; + case 12: return AV_PIX_FMT_YUV420P12; + case 14: return AV_PIX_FMT_YUV420P14; + case 16: return AV_PIX_FMT_YUV420P16; + default: return AV_PIX_FMT_NONE; + } + case ((2 << 2) | 0): /* 4:1:1 */ + return vsformat->bitsPerSample == 8 ? AV_PIX_FMT_YUV411P : AV_PIX_FMT_NONE; + case ((2 << 2) | 2): /* 4:1:0 */ + return vsformat->bitsPerSample == 8 ? AV_PIX_FMT_YUV410P : AV_PIX_FMT_NONE; + } + case cmRGB: + switch (vsformat->bitsPerSample) { + case 8: return AV_PIX_FMT_GBRP; + case 9: return AV_PIX_FMT_GBRP9; + case 10: return AV_PIX_FMT_GBRP10; + case 12: return AV_PIX_FMT_GBRP12; + case 14: return AV_PIX_FMT_GBRP14; + case 16: return AV_PIX_FMT_GBRP16; + case 32: return AV_PIX_FMT_GBRPF32; + default: return AV_PIX_FMT_NONE; + } + case cmGray: + switch (vsformat->bitsPerSample) { + case 8: return AV_PIX_FMT_GRAY8; + case 9: return AV_PIX_FMT_GRAY9; + case 10: return AV_PIX_FMT_GRAY10; + case 12: return AV_PIX_FMT_GRAY12; + case 14: return AV_PIX_FMT_GRAY14; + case 16: return AV_PIX_FMT_GRAY16; + case 32: return AV_PIX_FMT_GRAYF32; + default: return AV_PIX_FMT_NONE; + } + case cmCompat: + switch (vsformat->id) { + case pfCompatBGR32: return AV_PIX_FMT_RGB32; + case pfCompatYUY2: return AV_PIX_FMT_YUYV422; + default: return AV_PIX_FMT_NONE; + } + default: + return AV_PIX_FMT_NONE; + } +} + +static av_cold int create_video_stream(AVFormatContext *s) +{ + AVStream *st; + VSContext *vs = s->priv_data; + const VSVideoInfo *vi = vs->vi; + + if (!isConstantFormat(vi)) { + av_log(s, AV_LOG_ERROR, "Non-constant input format not supported\n"); + return AVERROR_PATCHWELCOME; + } + + if (!(st = avformat_new_stream(s, NULL))) + return AVERROR_UNKNOWN; + + st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; + st->codecpar->codec_id = AV_CODEC_ID_RAWVIDEO; + st->codecpar->width = vi->width; + st->codecpar->height = vi->height; + st->codecpar->format = map_vsformat(s, st, vi->format); + + st->avg_frame_rate = (AVRational) { vi->fpsNum, vi->fpsDen }; + st->start_time = 0; + st->duration = vi->numFrames; + st->nb_frames = vi->numFrames; + avpriv_set_pts_info(st, 64, vi->fpsDen, vi->fpsNum); + + if (st->codecpar->format == AV_PIX_FMT_NONE) { + av_log(s, AV_LOG_ERROR, "Unsupported VS pixel format %s\n", vi->format->name); + return AVERROR_EXTERNAL; + } + + av_log(s, AV_LOG_VERBOSE, "VS format %s -> pixfmt %s\n", vi->format->name, + av_get_pix_fmt_name(st->codecpar->format)); + + if (vi->format->colorFamily == cmYCoCg) + st->codecpar->color_space = AVCOL_SPC_YCGCO; + + return 0; +} + +static av_cold int open_script(AVFormatContext *s) +{ + VSContext *vs = s->priv_data; + int ret; + + if (vsscript_evaluateFile(&vs->script, s->url, 0)) { + av_log(s, AV_LOG_ERROR, "VapourSynth script evaluation failed.\n"); + return AVERROR_EXTERNAL; + } + + if (!(vs->node = vsscript_getOutput(vs->script, 0))) { + av_log(s, AV_LOG_ERROR, "Could not get script output node.\n"); + ret = AVERROR_EXTERNAL; + goto fail; + } + + vs->vi = vs->vsapi->getVideoInfo(vs->node); + if (ret = create_video_stream(s)) + goto fail; + + return 0; +fail: + vs->vsapi->freeNode(vs->node); + vsscript_freeScript(vs->script); + return ret; +} + +static av_cold int read_header(AVFormatContext *s) +{ + VSContext *vs = s->priv_data; + int ret; + + if (!vsscript_init()) { + av_log(s, AV_LOG_ERROR, "Failed to initialize VapourSynth environment.\n"); + return AVERROR_EXTERNAL; + } + + if ((ret = vsscript_getApiVersion()) < VSSCRIPT_API_VERSION) { + av_log(s, AV_LOG_ERROR, "VSScript API too old: %d versus %d.\n", ret, VSSCRIPT_API_VERSION); + ret = AVERROR_EXTERNAL; + goto fail; + } + + if (!(vs->vsapi = vsscript_getVSApi2(VAPOURSYNTH_API_VERSION))) { + av_log(s, AV_LOG_ERROR, "VapourSynth API too old: %d required.\n", VAPOURSYNTH_API_VERSION); + ret = AVERROR_EXTERNAL; + goto fail; + } + + if (ret = open_script(s)) + goto fail; + + vs->ncpu = av_cpu_count(); + return 0; +fail: + vsscript_finalize(); + return ret; +} + +static int read_packet(AVFormatContext *s, AVPacket *pkt) +{ + static const int gbr_order[3] = { 1, 2, 0 }; + + AVStream *st = s->streams[0]; + VSContext *vs = s->priv_data; + const VSFrameRef *vs_frame = NULL; + uint8_t *dst_ptr; + char vserr[256]; + int i, n, plane, ret; + + if (vs->current_frame >= vs->vi->numFrames) + return AVERROR_EOF; + + n = vs->current_frame++; + if (st->discard == AVDISCARD_ALL) + return 0; + + pkt->size = 0; + for (plane = 0; plane < vs->vi->format->numPlanes; ++plane) { + int width = vs->vi->width; + int height = vs->vi->height; + + if (plane == 1 || plane == 2) { + width >>= vs->vi->format->subSamplingW; + height >>= vs->vi->format->subSamplingH; + } + + pkt->size += (int64_t)width * height * vs->vi->format->bytesPerSample; + } + + if ((ret = av_new_packet(pkt, pkt->size)) < 0) + return ret; + + pkt->pts = n; + pkt->dts = n; + pkt->duration = 1; + pkt->stream_index = 0; + + vs_frame = vs->vsapi->getFrame(n, vs->node, vserr, sizeof(vserr)); + if (!vs_frame) { + av_log(s, AV_LOG_ERROR, "Error retrieving frame %d: %s\n", n, vserr); + ret = AVERROR_EXTERNAL; + goto fail; + } + + /* Prefetch the subsequent frames. */ + for (i = 0; i < vs->ncpu; ++i) { + if (i >= vs->vi->numFrames - n) + break; + vs->vsapi->getFrameAsync(n + i, vs->node, async_callback, s); + atomic_fetch_add(&vs->async_pending, 1); + } + + dst_ptr = pkt->data; + for (plane = 0; plane < vs->vi->format->numPlanes; ++plane) { + int src_plane = vs->vi->format->colorFamily == cmRGB ? gbr_order[plane] : plane; + const uint8_t *src_ptr = vs->vsapi->getReadPtr(vs_frame, src_plane); + int width = vs->vsapi->getFrameWidth(vs_frame, src_plane); + int height = vs->vsapi->getFrameHeight(vs_frame, src_plane); + int stride = vs->vsapi->getStride(vs_frame, src_plane); + int row_size = width * vs->vi->format->bytesPerSample; + + if (vs->vi->format->id == pfCompatBGR32) { + src_ptr += (int64_t)(height - 1) * stride; + stride = -stride; + } + + vs_bitblt(dst_ptr, row_size, src_ptr, stride, row_size, height); + dst_ptr += (int64_t)row_size * height; + } + + vs->vsapi->freeFrame(vs_frame); + return 0; +fail: + vs->vsapi->freeFrame(vs_frame); + av_packet_unref(pkt); + return ret; +} + +static av_cold int read_close(AVFormatContext *s) +{ + VSContext *vs = s->priv_data; + + /* Wait for any async requests to complete. */ + while (atomic_load(&vs->async_pending)) { + av_usleep(1000); + } + + vs->vsapi->freeNode(vs->node); + vsscript_freeScript(vs->script); + vsscript_finalize(); + return 0; +} + +static int read_seek(AVFormatContext *s, int stream_index, int64_t timestamp, int flags) +{ + VSContext *vs = s->priv_data; + + vs->current_frame = FFMIN(FFMAX(0, timestamp), s->streams[0]->duration); + return 0; +} + +static av_cold int read_probe(const 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; +} + +const AVInputFormat ff_vapoursynth_alt_demuxer = { + .name = "vapoursynth_alt", + .long_name = NULL_IF_CONFIG_SMALL("Alternative VapourSynth demuxer"), + .priv_data_size = sizeof(VSContext), + .read_probe = read_probe, + .read_header = read_header, + .read_packet = read_packet, + .read_close = read_close, + .read_seek = read_seek, +}; -- 2.35.1