diff --git a/DOCS/interface-changes/vapoursynth-api-v4.txt.txt b/DOCS/interface-changes/vapoursynth-api-v4.txt.txt new file mode 100644 index 0000000000000..f19b5322efe0c --- /dev/null +++ b/DOCS/interface-changes/vapoursynth-api-v4.txt.txt @@ -0,0 +1,3 @@ +Bump dependency of VapourSynth to utilize its API version 4. New minimum VapourSynth version for runtime is R56. + +Some functions and plugins are changed or removed. For details, refer to VapourSynth documentation http://www.vapoursynth.com/2021/09/r55-audio-support-and-improved-performance/ and https://github.com/vapoursynth/vapoursynth/blob/R68/APIV4%20changes.txt diff --git a/meson.build b/meson.build index f5cf2255c9ae7..d071e32180d3e 100644 --- a/meson.build +++ b/meson.build @@ -764,8 +764,8 @@ if not features['lavu-uuid'] sources += files('misc/uuid.c') endif -vapoursynth = dependency('vapoursynth', version: '>= 26', required: get_option('vapoursynth')) -vapoursynth_script = dependency('vapoursynth-script', version: '>= 26', +vapoursynth = dependency('vapoursynth', version: '>= 56', required: get_option('vapoursynth')) +vapoursynth_script = dependency('vapoursynth-script', version: '>= 56', required: get_option('vapoursynth')) features += {'vapoursynth': vapoursynth.found() and vapoursynth_script.found()} if features['vapoursynth'] diff --git a/video/filter/vf_vapoursynth.c b/video/filter/vf_vapoursynth.c index 5446fc5ce5175..0c706fde26094 100644 --- a/video/filter/vf_vapoursynth.c +++ b/video/filter/vf_vapoursynth.c @@ -22,8 +22,8 @@ #include #include -#include -#include +#include +#include #include #include @@ -58,13 +58,13 @@ struct priv { VSCore *vscore; const VSAPI *vsapi; - VSNodeRef *out_node; - VSNodeRef *in_node; + VSNode *out_node; + VSNode *in_node; const struct script_driver *drv; // drv_vss - bool vs_initialized; - struct VSScript *se; + const VSSCRIPTAPI *vs_script_api; + VSScript *vs_script; struct mp_filter *f; struct mp_pin *in_pin; @@ -109,72 +109,50 @@ struct script_driver { void (*unload)(struct priv *p); // unload script and maybe vs }; -struct mpvs_fmt { - VSPresetFormat vs; - int bits, xs, ys; -}; - -static const struct mpvs_fmt mpvs_fmt_table[] = { - {pfYUV420P8, 8, 1, 1}, - {pfYUV420P9, 9, 1, 1}, - {pfYUV420P10, 10, 1, 1}, - {pfYUV420P16, 16, 1, 1}, - {pfYUV422P8, 8, 1, 0}, - {pfYUV422P9, 9, 1, 0}, - {pfYUV422P10, 10, 1, 0}, - {pfYUV422P16, 16, 1, 0}, - {pfYUV410P8, 8, 2, 2}, - {pfYUV411P8, 8, 2, 0}, - {pfYUV440P8, 8, 0, 1}, - {pfYUV444P8, 8, 0, 0}, - {pfYUV444P9, 9, 0, 0}, - {pfYUV444P10, 10, 0, 0}, - {pfYUV444P16, 16, 0, 0}, - {pfNone} -}; - -static bool compare_fmt(int imgfmt, const struct mpvs_fmt *vs) -{ - struct mp_regular_imgfmt rfmt; - if (!mp_get_regular_imgfmt(&rfmt, imgfmt)) - return false; - if (rfmt.component_pad > 0) - return false; - if (rfmt.chroma_xs != vs->xs || rfmt.chroma_ys != vs->ys) +static bool get_valid_mp_regular_imgfmt(struct mp_regular_imgfmt *reg_fmt, int imgfmt) { + if (!mp_get_regular_imgfmt(reg_fmt, imgfmt)) return false; - if (rfmt.component_size * 8 + rfmt.component_pad != vs->bits) + if (reg_fmt->component_pad > 0) return false; - if (rfmt.num_planes != 3) + if (reg_fmt->num_planes != 3) return false; for (int n = 0; n < 3; n++) { - if (rfmt.planes[n].num_components != 1) + if (reg_fmt->planes[n].num_components != 1) return false; - if (rfmt.planes[n].components[0] != n + 1) + if (reg_fmt->planes[n].components[0] != n + 1) return false; } return true; } -static VSPresetFormat mp_to_vs(int imgfmt) +static bool mp_to_vs(struct priv *p, VSVideoFormat *vsfmt, int imgfmt) { - for (int n = 0; mpvs_fmt_table[n].bits; n++) { - const struct mpvs_fmt *vsentry = &mpvs_fmt_table[n]; - if (compare_fmt(imgfmt, vsentry)) - return vsentry->vs; - } - return pfNone; + struct mp_regular_imgfmt reg_fmt; + if (!get_valid_mp_regular_imgfmt(®_fmt, imgfmt)) + return false; + + int rfmt_bits = reg_fmt.component_size * 8 + reg_fmt.component_pad; + return p->vsapi->queryVideoFormat(vsfmt, cfYUV, + reg_fmt.component_type == MP_COMPONENT_TYPE_FLOAT ? stFloat : stInteger, + rfmt_bits, reg_fmt.chroma_xs, reg_fmt.chroma_ys, p->vscore); } -static int mp_from_vs(VSPresetFormat vs) +static int mp_from_vs(const VSVideoFormat *vsfmt) { - for (int n = 0; mpvs_fmt_table[n].bits; n++) { - const struct mpvs_fmt *vsentry = &mpvs_fmt_table[n]; - if (vsentry->vs == vs) { - for (int imgfmt = IMGFMT_START; imgfmt < IMGFMT_END; imgfmt++) { - if (compare_fmt(imgfmt, vsentry)) - return imgfmt; + if (vsfmt->colorFamily == cfYUV) { + for (int imgfmt = IMGFMT_START + 1; imgfmt < IMGFMT_END; imgfmt++) { + struct mp_regular_imgfmt reg_fmt; + if (!get_valid_mp_regular_imgfmt(®_fmt, imgfmt)) + continue; + + int rfmt_bits = reg_fmt.component_size * 8 + reg_fmt.component_pad; + if ((reg_fmt.component_type == MP_COMPONENT_TYPE_FLOAT) == (vsfmt->sampleType == stFloat) && + rfmt_bits == vsfmt->bitsPerSample && + reg_fmt.chroma_xs == vsfmt->subSamplingW && + reg_fmt.chroma_ys == vsfmt->subSamplingH) + { + return imgfmt; } - break; } } return 0; @@ -184,18 +162,18 @@ static void copy_mp_to_vs_frame_props_map(struct priv *p, VSMap *map, struct mp_image *img) { struct mp_image_params *params = &img->params; - p->vsapi->propSetInt(map, "_SARNum", params->p_w, 0); - p->vsapi->propSetInt(map, "_SARDen", params->p_h, 0); + p->vsapi->mapSetInt(map, "_SARNum", params->p_w, 0); + p->vsapi->mapSetInt(map, "_SARDen", params->p_h, 0); if (params->repr.levels) { - p->vsapi->propSetInt(map, "_ColorRange", + p->vsapi->mapSetInt(map, "_ColorRange", params->repr.levels == PL_COLOR_LEVELS_LIMITED, 0); } // The docs explicitly say it uses libavcodec values. - p->vsapi->propSetInt(map, "_ColorSpace", + p->vsapi->mapSetInt(map, "_ColorSpace", pl_system_to_av(params->repr.sys), 0); if (params->chroma_location) { // 0=left, 1=center, 2=topleft, 3=top, 4=bottomleft, 5=bottom. - p->vsapi->propSetInt(map, "_ChromaLocation", + p->vsapi->mapSetInt(map, "_ChromaLocation", params->chroma_location - 1, 0); } char pict_type = 0; @@ -205,51 +183,53 @@ static void copy_mp_to_vs_frame_props_map(struct priv *p, VSMap *map, case 3: pict_type = 'B'; break; } if (pict_type) - p->vsapi->propSetData(map, "_PictType", &pict_type, 1, 0); + p->vsapi->mapSetData(map, "_PictType", &pict_type, 1, dtUtf8, 0); int field = 0; if (img->fields & MP_IMGFIELD_INTERLACED) field = img->fields & MP_IMGFIELD_TOP_FIRST ? 2 : 1; - p->vsapi->propSetInt(map, "_FieldBased", field, 0); + p->vsapi->mapSetInt(map, "_FieldBased", field, 0); // Don't increase the reference count. It is not intended to be read externally, // and we know it will be alive when we retrieve it. - p->vsapi->propSetData(map, "_MP_IMAGE", (const char *)img, sizeof(*img), 0); + p->vsapi->mapSetData(map, "_MP_IMAGE", (const char *)img, sizeof(*img), dtBinary, 0); } -static int set_vs_frame_props(struct priv *p, VSFrameRef *frame, +static int set_vs_frame_props(struct priv *p, VSFrame *frame, struct mp_image *img, int dur_num, int dur_den) { - VSMap *map = p->vsapi->getFramePropsRW(frame); + VSMap *map = p->vsapi->getFramePropertiesRW(frame); if (!map) return -1; - p->vsapi->propSetInt(map, "_DurationNum", dur_num, 0); - p->vsapi->propSetInt(map, "_DurationDen", dur_den, 0); + p->vsapi->mapSetInt(map, "_DurationNum", dur_num, 0); + p->vsapi->mapSetInt(map, "_DurationDen", dur_den, 0); copy_mp_to_vs_frame_props_map(p, map, img); return 0; } -static VSFrameRef *alloc_vs_frame(struct priv *p, struct mp_image_params *fmt) +static VSFrame *alloc_vs_frame(struct priv *p, struct mp_image_params *fmt) { - const VSFormat *vsfmt = - p->vsapi->getFormatPreset(mp_to_vs(fmt->imgfmt), p->vscore); - return p->vsapi->newVideoFrame(vsfmt, fmt->w, fmt->h, NULL, p->vscore); + VSVideoFormat vsfmt; + if (mp_to_vs(p, &vsfmt, fmt->imgfmt)) + return p->vsapi->newVideoFrame(&vsfmt, fmt->w, fmt->h, NULL, p->vscore); + + return NULL; } -static struct mp_image map_vs_frame(struct priv *p, const VSFrameRef *ref, +static struct mp_image map_vs_frame(struct priv *p, const VSFrame *ref, bool w, struct mp_image *ref_image) { - const VSFormat *fmt = p->vsapi->getFrameFormat(ref); + const VSVideoFormat *fmt = p->vsapi->getVideoFrameFormat(ref); struct mp_image img = {0}; if (ref_image) img = *ref_image; - mp_image_setfmt(&img, mp_from_vs(fmt->id)); + mp_image_setfmt(&img, mp_from_vs(fmt)); mp_image_set_size(&img, p->vsapi->getFrameWidth(ref, 0), p->vsapi->getFrameHeight(ref, 0)); for (int n = 0; n < img.num_planes; n++) { if (w) { - img.planes[n] = p->vsapi->getWritePtr((VSFrameRef *)ref, n); + img.planes[n] = p->vsapi->getWritePtr((VSFrame *)ref, n); } else { img.planes[n] = (uint8_t *)p->vsapi->getReadPtr(ref, n); } @@ -270,19 +250,19 @@ static void drain_oldest_buffered_frame(struct priv *p) p->in_frameno++; } -static void VS_CC vs_frame_done(void *userData, const VSFrameRef *f, int n, - VSNodeRef *node, const char *errorMsg) +static void VS_CC vs_frame_done(void *userData, const VSFrame *f, int n, + VSNode *node, const char *errorMsg) { struct priv *p = userData; struct mp_image *res = NULL; if (f) { - const VSMap *map = p->vsapi->getFramePropsRO(f); + const VSMap *map = p->vsapi->getFramePropertiesRO(f); if (!map) MP_ERR(p, "Failed to get frame properties!"); struct mp_image *mpi = NULL; if (map) { - mpi = (void *)p->vsapi->propGetData(map, "_MP_IMAGE", 0, NULL); + mpi = (void *)p->vsapi->mapGetData(map, "_MP_IMAGE", 0, NULL); if (!mpi) MP_ERR(p, "Failed to get mp_image attributes!"); } @@ -292,8 +272,8 @@ static void VS_CC vs_frame_done(void *userData, const VSFrameRef *f, int n, img.params.crop = (struct mp_rect){0, 0, img.w, img.h}; if (map) { int err1, err2; - int num = p->vsapi->propGetInt(map, "_DurationNum", 0, &err1); - int den = p->vsapi->propGetInt(map, "_DurationDen", 0, &err2); + int num = p->vsapi->mapGetInt(map, "_DurationNum", 0, &err1); + int den = p->vsapi->mapGetInt(map, "_DurationDen", 0, &err2); if (!err1 && !err2) img.pkt_duration = num / (double)den; } @@ -454,39 +434,13 @@ static void vf_vapoursynth_process(struct mp_filter *f) mp_mutex_unlock(&p->lock); } -static void VS_CC infiltInit(VSMap *in, VSMap *out, void **instanceData, - VSNode *node, VSCore *core, const VSAPI *vsapi) -{ - struct priv *p = *instanceData; - // The number of frames of our input node is obviously unknown. The user - // could for example seek any time, randomly "ending" the clip. - // This specific value was suggested by the VapourSynth developer. - int enough_for_everyone = INT_MAX / 16; - - // Note: this is called from createFilter, so no need for locking. - - VSVideoInfo fmt = { - .format = p->vsapi->getFormatPreset(mp_to_vs(p->fmt_in.imgfmt), p->vscore), - .width = p->fmt_in.w, - .height = p->fmt_in.h, - .numFrames = enough_for_everyone, - }; - if (!fmt.format) { - p->vsapi->setError(out, "Unsupported input format.\n"); - return; - } - - p->vsapi->setVideoInfo(&fmt, 1, node); - p->in_node_active = true; -} - -static const VSFrameRef *VS_CC infiltGetFrame(int frameno, int activationReason, - void **instanceData, void **frameData, +static const VSFrame *VS_CC infiltGetFrame(int frameno, int activationReason, + void *instanceData, void **frameData, VSFrameContext *frameCtx, VSCore *core, const VSAPI *vsapi) { - struct priv *p = *instanceData; - VSFrameRef *ret = NULL; + struct priv *p = instanceData; + VSFrame *ret = NULL; mp_mutex_lock(&p->lock); MP_TRACE(p, "VS asking for frame %d (at %d)\n", frameno, p->in_frameno); @@ -638,7 +592,7 @@ static void destroy_vs(struct priv *p) static int reinit_vs(struct priv *p, struct mp_image *input) { - VSMap *vars = NULL, *in = NULL, *out = NULL; + VSMap *vars = NULL; int res = -1; destroy_vs(p); @@ -659,29 +613,41 @@ static int reinit_vs(struct priv *p, struct mp_image *input) goto error; } - in = p->vsapi->createMap(); - out = p->vsapi->createMap(); - vars = p->vsapi->createMap(); - if (!in || !out || !vars) + // The number of frames of our input node is obviously unknown. The user + // could for example seek any time, randomly "ending" the clip. + // This specific value was suggested by the VapourSynth developer. + int enough_for_everyone = INT_MAX / 16; + + VSVideoInfo vi_in = { + .width = p->fmt_in.w, + .height = p->fmt_in.h, + .numFrames = enough_for_everyone, + }; + if (!mp_to_vs(p, &vi_in.format, p->fmt_in.imgfmt)) { + MP_FATAL(p, "Unsupported input format.\n"); goto error; + } - p->vsapi->createFilter(in, out, "Input", infiltInit, infiltGetFrame, - infiltFree, fmSerial, 0, p, p->vscore); - int vserr; - p->in_node = p->vsapi->propGetNode(out, "clip", 0, &vserr); + p->in_node = p->vsapi->createVideoFilter2("Input", &vi_in, infiltGetFrame, infiltFree, + fmParallelRequests, NULL, 0, p, p->vscore); if (!p->in_node) { MP_FATAL(p, "Could not get our own input node.\n"); goto error; } + p->in_node_active = true; + + vars = p->vsapi->createMap(); + if (!vars) + goto error; - if (p->vsapi->propSetNode(vars, "video_in", p->in_node, 0)) + if (p->vsapi->mapSetNode(vars, "video_in", p->in_node, 0)) goto error; int d_w, d_h; mp_image_params_get_dsize(&p->fmt_in, &d_w, &d_h); - p->vsapi->propSetInt(vars, "video_in_dw", d_w, 0); - p->vsapi->propSetInt(vars, "video_in_dh", d_h, 0); + p->vsapi->mapSetInt(vars, "video_in_dw", d_w, 0); + p->vsapi->mapSetInt(vars, "video_in_dh", d_h, 0); struct mp_stream_info *info = mp_filter_find_stream_info(p->f); double container_fps = input->nominal_fps; @@ -697,10 +663,10 @@ static int reinit_vs(struct priv *p, struct mp_image *input) display_res[1] = tmp[1]; } } - p->vsapi->propSetFloat(vars, "container_fps", container_fps, 0); - p->vsapi->propSetFloat(vars, "display_fps", display_fps, 0); - p->vsapi->propSetIntArray(vars, "display_res", display_res, 2); - p->vsapi->propSetData(vars, "user_data", p->opts->user_data, -1, 0); + p->vsapi->mapSetFloat(vars, "container_fps", container_fps, 0); + p->vsapi->mapSetFloat(vars, "display_fps", display_fps, 0); + p->vsapi->mapSetIntArray(vars, "display_res", display_res, 2); + p->vsapi->mapSetData(vars, "user_data", p->opts->user_data, -1, dtUtf8, 0); if (p->drv->load(p, vars) < 0) goto error; @@ -709,8 +675,8 @@ static int reinit_vs(struct priv *p, struct mp_image *input) goto error; } - const VSVideoInfo *vi = p->vsapi->getVideoInfo(p->out_node); - if (!mp_from_vs(vi->format->id)) { + const VSVideoInfo *vi_out = p->vsapi->getVideoInfo(p->out_node); + if (!mp_from_vs(&vi_out->format)) { MP_FATAL(p, "Unsupported output format.\n"); goto error; } @@ -722,8 +688,6 @@ static int reinit_vs(struct priv *p, struct mp_image *input) res = 0; error: if (p->vsapi) { - p->vsapi->freeMap(in); - p->vsapi->freeMap(out); p->vsapi->freeMap(vars); } if (res < 0) @@ -800,9 +764,11 @@ static struct mp_filter *vf_vapoursynth_create(struct mp_filter *parent, if (!conv) goto error; - for (int n = 0; mpvs_fmt_table[n].bits; n++) { - int imgfmt = mp_from_vs(mpvs_fmt_table[n].vs); - if (imgfmt) + for (int imgfmt = IMGFMT_START + 1; imgfmt < IMGFMT_END; imgfmt++) { + // due to the lack of access to VapourSynth at this point, the formats + // added to autoconvert is a superset of what's actually needed + struct mp_regular_imgfmt reg_fmt; + if (get_valid_mp_regular_imgfmt(®_fmt, imgfmt)) mp_autoconvert_add_imgfmt(conv, imgfmt, 0); } @@ -836,55 +802,52 @@ static const m_option_t vf_opts_fields[] = { {0} }; -#include - static int drv_vss_init(struct priv *p) { - if (!vsscript_init()) { + p->vs_script_api = getVSScriptAPI(VSSCRIPT_API_VERSION); + if (!p->vs_script_api) { MP_FATAL(p, "Could not initialize VapourSynth scripting.\n"); return -1; } - p->vs_initialized = true; return 0; } static void drv_vss_uninit(struct priv *p) { - if (p->vs_initialized) - vsscript_finalize(); - p->vs_initialized = false; + p->vs_script_api = NULL; } static int drv_vss_load_core(struct priv *p) { // First load an empty script to get a VSScript, so that we get the vsapi // and vscore. - if (vsscript_createScript(&p->se)) + p->vs_script = p->vs_script_api->createScript(NULL); + if (!p->vs_script) return -1; - p->vsapi = vsscript_getVSApi(); - p->vscore = vsscript_getCore(p->se); + p->vsapi = p->vs_script_api->getVSAPI(VAPOURSYNTH_API_VERSION); + p->vscore = p->vs_script_api->getCore(p->vs_script); return 0; } static int drv_vss_load(struct priv *p, VSMap *vars) { - vsscript_setVariable(p->se, vars); + p->vs_script_api->setVariables(p->vs_script, vars); - if (vsscript_evaluateFile(&p->se, p->script_path, 0)) { - MP_FATAL(p, "Script evaluation failed:\n%s\n", vsscript_getError(p->se)); + if (p->vs_script_api->evaluateFile(p->vs_script, p->script_path)) { + MP_FATAL(p, "Script evaluation failed:\n%s\n", p->vs_script_api->getError(p->vs_script)); return -1; } - p->out_node = vsscript_getOutput(p->se, 0); + p->out_node = p->vs_script_api->getOutputNode(p->vs_script, 0); return 0; } static void drv_vss_unload(struct priv *p) { - if (p->se) - vsscript_freeScript(p->se); - p->se = NULL; + if (p->vs_script) + p->vs_script_api->freeScript(p->vs_script); p->vsapi = NULL; p->vscore = NULL; + p->vs_script = NULL; } static const struct script_driver drv_vss = {