summaryrefslogtreecommitdiffstats
path: root/video/filter/vf_vapoursynth.c
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2018-01-16 11:53:44 +0100
committerKevin Mitchell <kevmitch@gmail.com>2018-01-30 03:10:27 -0800
commit76276c92104c31ee936ba5c76a76072f09978c5f (patch)
tree5f514978ef0326ed76adc9e82ac276daac9e257f /video/filter/vf_vapoursynth.c
parentedb4970ca8b9bee8e54222a1e119af9d355d451a (diff)
downloadmpv-76276c92104c31ee936ba5c76a76072f09978c5f.tar.bz2
mpv-76276c92104c31ee936ba5c76a76072f09978c5f.tar.xz
video: rewrite filtering glue code
Get rid of the old vf.c code. Replace it with a generic filtering framework, which can potentially handle more than just --vf. At least reimplementing --af with this code is planned. This changes some --vf semantics (including runtime behavior and the "vf" command). The most important ones are listed in interface-changes. vf_convert.c is renamed to f_swscale.c. It is now an internal filter that can not be inserted by the user manually. f_lavfi.c is a refactor of player/lavfi.c. The latter will be removed once --lavfi-complex is reimplemented on top of f_lavfi.c. (which is conceptually easy, but a big mess due to the data flow changes). The existing filters are all changed heavily. The data flow of the new filter framework is different. Especially EOF handling changes - EOF is now a "frame" rather than a state, and must be passed through exactly once. Another major thing is that all filters must support dynamic format changes. The filter reconfig() function goes away. (This sounds complex, but since all filters need to handle EOF draining anyway, they can use the same code, and it removes the mess with reconfig() having to predict the output format, which completely breaks with libavfilter anyway.) In addition, there is no automatic format negotiation or conversion. libavfilter's primitive and insufficient API simply doesn't allow us to do this in a reasonable way. Instead, filters can use f_autoconvert as sub-filter, and tell it which formats they support. This filter will in turn add actual conversion filters, such as f_swscale, to perform necessary format changes. vf_vapoursynth.c uses the same basic principle of operation as before, but with worryingly different details in data flow. Still appears to work. The hardware deint filters (vf_vavpp.c, vf_d3d11vpp.c, vf_vdpaupp.c) are heavily changed. Fortunately, they all used refqueue.c, which is for sharing the data flow logic (especially for managing future/past surfaces and such). It turns out it can be used to factor out most of the data flow. Some of these filters accepted software input. Instead of having ad-hoc upload code in each filter, surface upload is now delegated to f_autoconvert, which can use f_hwupload to perform this. Exporting VO capabilities is still a big mess (mp_stream_info stuff). The D3D11 code drops the redundant image formats, and all code uses the hw_subfmt (sw_format in FFmpeg) instead. Although that too seems to be a big mess for now. f_async_queue is unused.
Diffstat (limited to 'video/filter/vf_vapoursynth.c')
-rw-r--r--video/filter/vf_vapoursynth.c682
1 files changed, 346 insertions, 336 deletions
diff --git a/video/filter/vf_vapoursynth.c b/video/filter/vf_vapoursynth.c
index b5aad7abbf..4de09794e6 100644
--- a/video/filter/vf_vapoursynth.c
+++ b/video/filter/vf_vapoursynth.c
@@ -34,13 +34,27 @@
#include "common/msg.h"
#include "options/m_option.h"
#include "options/path.h"
-
+#include "filters/f_autoconvert.h"
+#include "filters/f_utils.h"
+#include "filters/filter.h"
+#include "filters/filter_internal.h"
+#include "filters/user_filters.h"
#include "video/img_format.h"
#include "video/mp_image.h"
#include "video/sws_utils.h"
-#include "vf.h"
-struct vf_priv_s {
+struct vapoursynth_opts {
+ char *file;
+ int maxbuffer;
+ int maxrequests;
+
+ const struct script_driver *drv;
+};
+
+struct priv {
+ struct mp_log *log;
+ struct vapoursynth_opts *opts;
+
VSCore *vscore;
const VSAPI *vsapi;
VSNodeRef *out_node;
@@ -56,16 +70,20 @@ struct vf_priv_s {
VSMap **gc_map;
int num_gc_map;
+ struct mp_filter *f;
+ struct mp_pin *in_pin;
+
+ // Format for which VS is currently configured.
struct mp_image_params fmt_in;
pthread_mutex_t lock;
pthread_cond_t wakeup;
// --- the following members are all protected by lock
- struct mp_image *next_image;// used to compute frame duration of oldest image
struct mp_image **buffered; // oldest image first
int num_buffered;
int in_frameno; // frame number of buffered[0] (the oldest)
+ int requested_frameno; // last frame number for which we woke up core
int out_frameno; // frame number of first requested/ready frame
double out_pts; // pts corresponding to first requested/ready frame
struct mp_image **requested;// frame callback results (can point to dummy_img)
@@ -74,25 +92,25 @@ struct vf_priv_s {
bool failed; // frame callback returned with an error
bool shutdown; // ask node to return
bool eof; // drain remaining data
- int64_t frames_sent;
+ int64_t frames_sent; // total nr. of frames ever added to input queue
bool initializing; // filters are being built
bool in_node_active; // node might still be called
-
- // --- options
- char *cfg_file;
- int cfg_maxbuffer;
- int cfg_maxrequests;
};
// priv->requested[n] points to this if a request for frame n is in-progress
static const struct mp_image dummy_img;
+// or if a request failed during EOF/reinit draining
+static const struct mp_image dummy_img_eof;
+
+static void destroy_vs(struct priv *p);
+static int reinit_vs(struct priv *p);
struct script_driver {
- int (*init)(struct vf_instance *vf); // first time init
- void (*uninit)(struct vf_instance *vf); // last time uninit
- int (*load_core)(struct vf_instance *vf); // make vsapi/vscore available
- int (*load)(struct vf_instance *vf, VSMap *vars); // also set p->out_node
- void (*unload)(struct vf_instance *vf); // unload script and maybe vs
+ int (*init)(struct priv *p); // first time init
+ void (*uninit)(struct priv *p); // last time uninit
+ int (*load_core)(struct priv *p); // make vsapi/vscore available
+ int (*load)(struct priv *p, VSMap *vars); // also sets p->out_node
+ void (*unload)(struct priv *p); // unload script and maybe vs
};
struct mpvs_fmt {
@@ -166,7 +184,7 @@ static int mp_from_vs(VSPresetFormat vs)
return 0;
}
-static void copy_mp_to_vs_frame_props_map(struct vf_priv_s *p, VSMap *map,
+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;
@@ -197,7 +215,7 @@ static void copy_mp_to_vs_frame_props_map(struct vf_priv_s *p, VSMap *map,
p->vsapi->propSetInt(map, "_FieldBased", field, 0);
}
-static int set_vs_frame_props(struct vf_priv_s *p, VSFrameRef *frame,
+static int set_vs_frame_props(struct priv *p, VSFrameRef *frame,
struct mp_image *img, int dur_num, int dur_den)
{
VSMap *map = p->vsapi->getFramePropsRW(frame);
@@ -209,14 +227,14 @@ static int set_vs_frame_props(struct vf_priv_s *p, VSFrameRef *frame,
return 0;
}
-static VSFrameRef *alloc_vs_frame(struct vf_priv_s *p, struct mp_image_params *fmt)
+static VSFrameRef *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);
}
-static struct mp_image map_vs_frame(struct vf_priv_s *p, const VSFrameRef *ref,
+static struct mp_image map_vs_frame(struct priv *p, const VSFrameRef *ref,
bool w)
{
const VSFormat *fmt = p->vsapi->getFrameFormat(ref);
@@ -238,7 +256,7 @@ static struct mp_image map_vs_frame(struct vf_priv_s *p, const VSFrameRef *ref,
return img;
}
-static void drain_oldest_buffered_frame(struct vf_priv_s *p)
+static void drain_oldest_buffered_frame(struct priv *p)
{
if (!p->num_buffered)
return;
@@ -252,185 +270,170 @@ static void drain_oldest_buffered_frame(struct vf_priv_s *p)
static void VS_CC vs_frame_done(void *userData, const VSFrameRef *f, int n,
VSNodeRef *node, const char *errorMsg)
{
- struct vf_instance *vf = userData;
- struct vf_priv_s *p = vf->priv;
+ struct priv *p = userData;
pthread_mutex_lock(&p->lock);
// If these assertions fail, n is an unrequested frame (or filtered twice).
assert(n >= p->out_frameno && n < p->out_frameno + p->max_requests);
int index = n - p->out_frameno;
- MP_TRACE(vf, "filtered frame %d (%d)\n", n, index);
+ MP_TRACE(p, "filtered frame %d (%d)\n", n, index);
assert(p->requested[index] == &dummy_img);
struct mp_image *res = NULL;
if (f) {
struct mp_image img = map_vs_frame(p, f, false);
- img.pts = MP_NOPTS_VALUE;
+ img.pkt_duration = -1;
const VSMap *map = p->vsapi->getFramePropsRO(f);
if (map) {
int err1, err2;
int num = p->vsapi->propGetInt(map, "_DurationNum", 0, &err1);
int den = p->vsapi->propGetInt(map, "_DurationDen", 0, &err2);
if (!err1 && !err2)
- img.pts = num / (double)den; // abusing pts for frame length
+ img.pkt_duration = num / (double)den;
}
- if (img.pts == MP_NOPTS_VALUE)
- MP_ERR(vf, "No PTS after filter at frame %d!\n", n);
+ if (img.pkt_duration < 0)
+ MP_ERR(p, "No PTS after filter at frame %d!\n", n);
res = mp_image_new_copy(&img);
p->vsapi->freeFrame(f);
}
- if (!res) {
- p->failed = true;
- MP_ERR(vf, "Filter error at frame %d: %s\n", n, errorMsg);
+ if (!res && !p->shutdown) {
+ if (p->eof) {
+ res = (struct mp_image *)&dummy_img_eof;
+ } else {
+ p->failed = true;
+ MP_ERR(p, "Filter error at frame %d: %s\n", n, errorMsg);
+ }
}
p->requested[index] = res;
pthread_cond_broadcast(&p->wakeup);
pthread_mutex_unlock(&p->lock);
+ mp_filter_wakeup(p->f);
}
-static bool locked_need_input(struct vf_instance *vf)
+static void vf_vapoursynth_process(struct mp_filter *f)
{
- struct vf_priv_s *p = vf->priv;
- return p->num_buffered < MP_TALLOC_AVAIL(p->buffered);
-}
+ struct priv *p = f->priv;
-// Return true if progress was made.
-static bool locked_read_output(struct vf_instance *vf)
-{
- struct vf_priv_s *p = vf->priv;
- bool r = false;
+ pthread_mutex_lock(&p->lock);
- // Move finished frames from the request slots to the vf output queue.
- while (p->requested[0] && p->requested[0] != &dummy_img) {
- struct mp_image *out = p->requested[0];
- if (out->pts != MP_NOPTS_VALUE) {
- double duration = out->pts;
- out->pts = p->out_pts;
- p->out_pts += duration;
- }
- vf_add_output_frame(vf, out);
- for (int n = 0; n < p->max_requests - 1; n++)
- p->requested[n] = p->requested[n + 1];
- p->requested[p->max_requests - 1] = NULL;
- p->out_frameno++;
- r = true;
+ if (p->failed) {
+ // Not sure what we do on errors, but at least don't deadlock.
+ MP_ERR(f, "failed, no action taken\n");
+ mp_filter_internal_mark_failed(f);
+ goto done;
}
- // Don't request frames if we haven't sent any input yet.
- if (p->num_buffered + p->in_frameno == 0)
- return r;
-
- // Request new future frames as far as possible.
- for (int n = 0; n < p->max_requests; n++) {
- if (!p->requested[n]) {
- // Note: this assumes getFrameAsync() will never call
- // infiltGetFrame (if it does, we would deadlock)
- p->requested[n] = (struct mp_image *)&dummy_img;
- p->failed = false;
- MP_TRACE(vf, "requesting frame %d (%d)\n", p->out_frameno + n, n);
- p->vsapi->getFrameAsync(p->out_frameno + n, p->out_node,
- vs_frame_done, vf);
+ // Read input and pass it to the input queue VS reads.
+ if (p->num_buffered < MP_TALLOC_AVAIL(p->buffered) && !p->eof) {
+ // Note: this requests new input frames even if no output was ever
+ // requested. Normally this is not how mp_filter works, but since VS
+ // works asynchronously, it's probably ok.
+ struct mp_frame frame = mp_pin_out_read(p->in_pin);
+ if (frame.type == MP_FRAME_EOF) {
+ if (p->out_node) {
+ MP_VERBOSE(p, "initiate EOF\n");
+ p->eof = true;
+ pthread_cond_broadcast(&p->wakeup);
+ } else if (mp_pin_in_needs_data(f->ppins[1])) {
+ MP_VERBOSE(p, "return EOF\n");
+ mp_pin_in_write(f->ppins[1], frame);
+ frame = MP_NO_FRAME;
+ }
+ // Keep it until we can propagate it.
+ mp_pin_out_unread(p->in_pin, frame);
+ } else if (frame.type == MP_FRAME_VIDEO) {
+ struct mp_image *mpi = frame.data;
+ // Init VS script, or reinit it to change video format. (This
+ // includes derived parameters we pass manually to the script.)
+ if (!p->out_node || mpi->imgfmt != p->fmt_in.imgfmt ||
+ mpi->w != p->fmt_in.w || mpi->h != p->fmt_in.h ||
+ mpi->params.p_w != p->fmt_in.p_w ||
+ mpi->params.p_h != p->fmt_in.p_h)
+ {
+ if (p->out_node) {
+ // Drain still buffered frames.
+ MP_VERBOSE(p, "draining VS for format change\n");
+ mp_pin_out_unread(p->in_pin, frame);
+ p->eof = true;
+ pthread_cond_broadcast(&p->wakeup);
+ mp_filter_internal_mark_progress(f);
+ goto done;
+ }
+ pthread_mutex_unlock(&p->lock);
+ if (p->out_node)
+ destroy_vs(p);
+ p->fmt_in = mpi->params;
+ if (reinit_vs(p) < 0) {
+ MP_ERR(p, "could not init VS\n");
+ mp_frame_unref(&frame);
+ return;
+ }
+ }
+ if (p->out_pts == MP_NOPTS_VALUE)
+ p->out_pts = mpi->pts;
+ p->frames_sent++;
+ p->buffered[p->num_buffered++] = mpi;
+ pthread_cond_broadcast(&p->wakeup);
+ } else if (frame.type != MP_FRAME_NONE) {
+ MP_ERR(p, "discarding unknown frame type\n");
+ goto done;
}
}
- return r;
-}
+ // Read output and return them from the VS output queue.
+ if (mp_pin_in_needs_data(f->ppins[1]) && p->requested[0] &&
+ p->requested[0] != &dummy_img &&
+ p->requested[0] != &dummy_img_eof)
+ {
+ struct mp_image *out = p->requested[0];
-static int filter_ext(struct vf_instance *vf, struct mp_image *mpi)
-{
- struct vf_priv_s *p = vf->priv;
- int ret = 0;
- bool eof = !mpi;
+ out->pts = p->out_pts;
+ if (p->out_pts != MP_NOPTS_VALUE && out->pkt_duration >= 0)
+ p->out_pts += out->pkt_duration;
- if (!p->out_node) {
- talloc_free(mpi);
- return -1;
- }
+ mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_VIDEO, out));
- MPSWAP(struct mp_image *, p->next_image, mpi);
-
- if (mpi) {
- // Turn PTS into frame duration (the pts field is abused for storing it)
- if (p->out_pts == MP_NOPTS_VALUE)
- p->out_pts = mpi->pts;
- mpi->pts = p->next_image ? p->next_image->pts - mpi->pts : 0;
+ for (int n = 0; n < p->max_requests - 1; n++)
+ p->requested[n] = p->requested[n + 1];
+ p->requested[p->max_requests - 1] = NULL;
+ p->out_frameno++;
}
- // Try to get new frames until we get rid of the input mpi.
- pthread_mutex_lock(&p->lock);
- while (1) {
- // Not sure what we do on errors, but at least don't deadlock.
- if (p->failed) {
- p->failed = false;
- talloc_free(mpi);
- ret = -1;
- break;
- }
-
- // Make the input frame available to infiltGetFrame().
- if (mpi && locked_need_input(vf)) {
- p->frames_sent++;
- p->buffered[p->num_buffered++] = talloc_steal(p->buffered, mpi);
- mpi = NULL;
- pthread_cond_broadcast(&p->wakeup);
- }
-
- locked_read_output(vf);
-
- if (!mpi) {
- if (eof && p->frames_sent && !p->eof) {
- MP_VERBOSE(vf, "input EOF\n");
- p->eof = true;
- pthread_cond_broadcast(&p->wakeup);
- }
- break;
- }
- pthread_cond_wait(&p->wakeup, &p->lock);
+ // This happens on EOF draining and format changes.
+ if (p->requested[0] == &dummy_img_eof) {
+ MP_VERBOSE(p, "finishing up\n");
+ assert(p->eof);
+ pthread_mutex_unlock(&p->lock);
+ destroy_vs(p);
+ mp_filter_internal_mark_progress(f);
+ return;
}
- pthread_mutex_unlock(&p->lock);
- return ret;
-}
-// Fetch 1 outout frame, or 0 if we probably need new input.
-static int filter_out(struct vf_instance *vf)
-{
- struct vf_priv_s *p = vf->priv;
- int ret = 0;
- pthread_mutex_lock(&p->lock);
- while (1) {
- if (p->failed) {
- ret = -1;
- break;
+ // Don't request frames if we haven't sent any input yet.
+ if (p->frames_sent && p->out_node) {
+ // Request new future frames as far as possible.
+ for (int n = 0; n < p->max_requests; n++) {
+ if (!p->requested[n]) {
+ // Note: this assumes getFrameAsync() will never call
+ // infiltGetFrame (if it does, we would deadlock)
+ p->requested[n] = (struct mp_image *)&dummy_img;
+ p->failed = false;
+ MP_TRACE(p, "requesting frame %d (%d)\n", p->out_frameno + n, n);
+ p->vsapi->getFrameAsync(p->out_frameno + n, p->out_node,
+ vs_frame_done, p);
+ }
}
- if (locked_read_output(vf))
- break;
- // If the VS filter wants new input, there's no guarantee that we can
- // actually finish any time soon without feeding new input.
- if (!p->eof && locked_need_input(vf))
- break;
- pthread_cond_wait(&p->wakeup, &p->lock);
}
- pthread_mutex_unlock(&p->lock);
- return ret;
-}
-static bool needs_input(struct vf_instance *vf)
-{
- struct vf_priv_s *p = vf->priv;
- bool r = false;
- pthread_mutex_lock(&p->lock);
- locked_read_output(vf);
- r = vf->num_out_queued < p->max_requests && locked_need_input(vf);
+done:
pthread_mutex_unlock(&p->lock);
- return r;
}
static void VS_CC infiltInit(VSMap *in, VSMap *out, void **instanceData,
VSNode *node, VSCore *core, const VSAPI *vsapi)
{
- struct vf_instance *vf = *instanceData;
- struct vf_priv_s *p = vf->priv;
+ 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.
@@ -458,30 +461,29 @@ static const VSFrameRef *VS_CC infiltGetFrame(int frameno, int activationReason,
VSFrameContext *frameCtx, VSCore *core,
const VSAPI *vsapi)
{
- struct vf_instance *vf = *instanceData;
- struct vf_priv_s *p = vf->priv;
+ struct priv *p = *instanceData;
VSFrameRef *ret = NULL;
pthread_mutex_lock(&p->lock);
- MP_TRACE(vf, "VS asking for frame %d (at %d)\n", frameno, p->in_frameno);
+ MP_TRACE(p, "VS asking for frame %d (at %d)\n", frameno, p->in_frameno);
while (1) {
if (p->shutdown) {
- p->vsapi->setFilterError("EOF or filter reinit/uninit", frameCtx);
- MP_DBG(vf, "returning error on EOF/reset\n");
+ p->vsapi->setFilterError("EOF or filter reset/uninit", frameCtx);
+ MP_DBG(p, "returning error on reset/uninit\n");
break;
}
if (p->initializing) {
- MP_WARN(vf, "Frame requested during init! This is unsupported.\n"
+ MP_WARN(p, "Frame requested during init! This is unsupported.\n"
"Returning black dummy frame with 0 duration.\n");
- ret = alloc_vs_frame(p, &vf->fmt_in);
+ ret = alloc_vs_frame(p, &p->fmt_in);
if (!ret) {
p->vsapi->setFilterError("Could not allocate VS frame", frameCtx);
break;
}
struct mp_image vsframe = map_vs_frame(p, ret, true);
- mp_image_clear(&vsframe, 0, 0, vf->fmt_in.w, vf->fmt_in.h);
+ mp_image_clear(&vsframe, 0, 0, p->fmt_in.w, p->fmt_in.h);
struct mp_image dummy = {0};
- mp_image_set_params(&dummy, &vf->fmt_in);
+ mp_image_set_params(&dummy, &p->fmt_in);
set_vs_frame_props(p, ret, &dummy, 0, 1);
break;
}
@@ -491,7 +493,7 @@ static const VSFrameRef *VS_CC infiltGetFrame(int frameno, int activationReason,
"Frame %d requested, but only have frames starting from %d. "
"Try increasing the buffered-frames suboption.",
frameno, p->in_frameno);
- MP_FATAL(vf, "%s\n", msg);
+ MP_FATAL(p, "%s\n", msg);
p->vsapi->setFilterError(msg, frameCtx);
break;
}
@@ -501,20 +503,23 @@ static const VSFrameRef *VS_CC infiltGetFrame(int frameno, int activationReason,
if (p->num_buffered) {
drain_oldest_buffered_frame(p);
pthread_cond_broadcast(&p->wakeup);
- if (vf->chain->wakeup_callback)
- vf->chain->wakeup_callback(vf->chain->wakeup_callback_ctx);
+ mp_filter_wakeup(p->f);
continue;
}
}
if (frameno >= p->in_frameno + p->num_buffered) {
- // If we think EOF was reached, don't wait for new input, and assume
- // the VS filter has reached EOF.
+ // If there won't be any new frames, abort the request.
if (p->eof) {
- p->shutdown = true;
- continue;
+ p->vsapi->setFilterError("EOF or filter EOF/reinit", frameCtx);
+ MP_DBG(p, "returning error on EOF/reinit\n");
+ break;
}
- }
- if (frameno < p->in_frameno + p->num_buffered) {
+ // Request more frames.
+ if (p->requested_frameno <= p->in_frameno + p->num_buffered) {
+ p->requested_frameno = p->in_frameno + p->num_buffered + 1;
+ mp_filter_wakeup(p->f);
+ }
+ } else {
struct mp_image *img = p->buffered[frameno - p->in_frameno];
ret = alloc_vs_frame(p, &img->params);
if (!ret) {
@@ -524,7 +529,7 @@ static const VSFrameRef *VS_CC infiltGetFrame(int frameno, int activationReason,
struct mp_image vsframe = map_vs_frame(p, ret, true);
mp_image_copy(&vsframe, img);
int res = 1e6;
- int dur = img->pts * res + 0.5;
+ int dur = img->pkt_duration * res + 0.5;
set_vs_frame_props(p, ret, img, dur, res);
break;
}
@@ -537,8 +542,7 @@ static const VSFrameRef *VS_CC infiltGetFrame(int frameno, int activationReason,
static void VS_CC infiltFree(void *instanceData, VSCore *core, const VSAPI *vsapi)
{
- struct vf_instance *vf = instanceData;
- struct vf_priv_s *p = vf->priv;
+ struct priv *p = instanceData;
pthread_mutex_lock(&p->lock);
p->in_node_active = false;
@@ -548,7 +552,7 @@ static void VS_CC infiltFree(void *instanceData, VSCore *core, const VSAPI *vsap
// number of getAsyncFrame calls in progress
// must be called with p->lock held
-static int num_requested(struct vf_priv_s *p)
+static int num_requested(struct priv *p)
{
int r = 0;
for (int n = 0; n < p->max_requests; n++)
@@ -556,11 +560,12 @@ static int num_requested(struct vf_priv_s *p)
return r;
}
-static void destroy_vs(struct vf_instance *vf)
+static void destroy_vs(struct priv *p)
{
- struct vf_priv_s *p = vf->priv;
+ if (!p->out_node && !p->initializing)
+ return;
- MP_DBG(vf, "destroying VS filters\n");
+ MP_DBG(p, "destroying VS filters\n");
// Wait until our frame callbacks return.
pthread_mutex_lock(&p->lock);
@@ -571,7 +576,7 @@ static void destroy_vs(struct vf_instance *vf)
pthread_cond_wait(&p->wakeup, &p->lock);
pthread_mutex_unlock(&p->lock);
- MP_DBG(vf, "all requests terminated\n");
+ MP_DBG(p, "all requests terminated\n");
if (p->in_node)
p->vsapi->freeNode(p->in_node);
@@ -579,7 +584,7 @@ static void destroy_vs(struct vf_instance *vf)
p->vsapi->freeNode(p->out_node);
p->in_node = p->out_node = NULL;
- p->drv->unload(vf);
+ p->drv->unload(p);
assert(!p->in_node_active);
assert(num_requested(p) == 0); // async callback didn't return?
@@ -588,34 +593,42 @@ static void destroy_vs(struct vf_instance *vf)
p->eof = false;
p->frames_sent = 0;
// Kill filtered images that weren't returned yet
- for (int n = 0; n < p->max_requests; n++)
- mp_image_unrefp(&p->requested[n]);
+ for (int n = 0; n < p->max_requests; n++) {
+ if (p->requested[n] != &dummy_img_eof)
+ mp_image_unrefp(&p->requested[n]);
+ p->requested[n] = NULL;
+ }
// Kill queued frames too
for (int n = 0; n < p->num_buffered; n++)
talloc_free(p->buffered[n]);
p->num_buffered = 0;
- talloc_free(p->next_image);
- p->next_image = NULL;
p->out_pts = MP_NOPTS_VALUE;
p->out_frameno = p->in_frameno = 0;
+ p->requested_frameno = 0;
p->failed = false;
- MP_DBG(vf, "uninitialized.\n");
+ MP_DBG(p, "uninitialized.\n");
}
-static int reinit_vs(struct vf_instance *vf)
+static int reinit_vs(struct priv *p)
{
- struct vf_priv_s *p = vf->priv;
VSMap *vars = NULL, *in = NULL, *out = NULL;
int res = -1;
- destroy_vs(vf);
+ destroy_vs(p);
+
+ MP_DBG(p, "initializing...\n");
+
+ struct mp_imgfmt_desc desc = mp_imgfmt_get_desc(p->fmt_in.imgfmt);
+ if (p->fmt_in.w % desc.align_x || p->fmt_in.h % desc.align_y) {
+ MP_FATAL(p, "VapourSynth does not allow unaligned/cropped video sizes.\n");
+ return -1;
+ }
- MP_DBG(vf, "initializing...\n");
p->initializing = true;
- if (p->drv->load_core(vf) < 0 || !p->vsapi || !p->vscore) {
- MP_FATAL(vf, "Could not get vapoursynth API handle.\n");
+ if (p->drv->load_core(p) < 0 || !p->vsapi || !p->vscore) {
+ MP_FATAL(p, "Could not get vapoursynth API handle.\n");
goto error;
}
@@ -626,11 +639,11 @@ static int reinit_vs(struct vf_instance *vf)
goto error;
p->vsapi->createFilter(in, out, "Input", infiltInit, infiltGetFrame,
- infiltFree, fmSerial, 0, vf, p->vscore);
+ infiltFree, fmSerial, 0, p, p->vscore);
int vserr;
p->in_node = p->vsapi->propGetNode(out, "clip", 0, &vserr);
if (!p->in_node) {
- MP_FATAL(vf, "Could not get our own input node.\n");
+ MP_FATAL(p, "Could not get our own input node.\n");
goto error;
}
@@ -642,26 +655,36 @@ static int reinit_vs(struct vf_instance *vf)
p->vsapi->propSetInt(vars, "video_in_dw", d_w, 0);
p->vsapi->propSetInt(vars, "video_in_dh", d_h, 0);
- p->vsapi->propSetFloat(vars, "container_fps", vf->chain->container_fps, 0);
- p->vsapi->propSetFloat(vars, "display_fps", vf->chain->display_fps, 0);
- if (p->drv->load(vf, vars) < 0)
+ struct mp_stream_info *info = mp_filter_find_stream_info(p->f);
+ double container_fps = 0;
+ double display_fps = 0;
+ if (info) {
+ if (info->get_container_fps)
+ container_fps = info->get_container_fps(info);
+ if (info->get_display_fps)
+ display_fps = info->get_display_fps(info);
+ }
+ p->vsapi->propSetFloat(vars, "container_fps", container_fps, 0);
+ p->vsapi->propSetFloat(vars, "display_fps", display_fps, 0);
+
+ if (p->drv->load(p, vars) < 0)
goto error;
if (!p->out_node) {
- MP_FATAL(vf, "Could not get script output node.\n");
+ MP_FATAL(p, "Could not get script output node.\n");
goto error;
}
const VSVideoInfo *vi = p->vsapi->getVideoInfo(p->out_node);
- if (!isConstantFormat(vi)) {
- MP_FATAL(vf, "Video format is required to be constant.\n");
+ if (!mp_from_vs(vi->format->id)) {
+ MP_FATAL(p, "Unsupported output format.\n");
goto error;
}
pthread_mutex_lock(&p->lock);
p->initializing = false;
pthread_mutex_unlock(&p->lock);
- MP_DBG(vf, "initialized.\n");
+ MP_DBG(p, "initialized.\n");
res = 0;
error:
if (p->vsapi) {
@@ -670,104 +693,110 @@ error:
p->vsapi->freeMap(vars);
}
if (res < 0)
- destroy_vs(vf);
+ destroy_vs(p);
return res;
}
-static int reconfig(struct vf_instance *vf, struct mp_image_params *in,
- struct mp_image_params *out)
+static void vf_vapoursynth_reset(struct mp_filter *f)
{
- struct vf_priv_s *p = vf->priv;
+ struct priv *p = f->priv;
- *out = *in;
- p->fmt_in = *in;
+ destroy_vs(p);
+}
- if (reinit_vs(vf) < 0)
- return -1;
+static void vf_vapoursynth_destroy(struct mp_filter *f)
+{
+ struct priv *p = f->priv;
- const VSVideoInfo *vi = p->vsapi->getVideoInfo(p->out_node);
- out->w = vi->width;
- out->h = vi->height;
- out->imgfmt = mp_from_vs(vi->format->id);
- if (!out->imgfmt) {
- MP_FATAL(vf, "Unsupported output format.\n");
- destroy_vs(vf);
- return -1;
- }
+ destroy_vs(p);
+ p->drv->uninit(p);
- struct mp_imgfmt_desc desc = mp_imgfmt_get_desc(in->imgfmt);
- if (in->w % desc.align_x || in->h % desc.align_y) {
- MP_FATAL(vf, "VapourSynth does not allow unaligned/cropped video sizes.\n");
- destroy_vs(vf);
- return -1;
- }
+ pthread_cond_destroy(&p->wakeup);
+ pthread_mutex_destroy(&p->lock);
- return 0;
+ mp_filter_free_children(f);
}
-static int query_format(struct vf_instance *vf, unsigned int fmt)
-{
- return mp_to_vs(fmt) != pfNone;
-}
+static const struct mp_filter_info vf_vapoursynth_filter = {
+ .name = "vapoursynth",
+ .process = vf_vapoursynth_process,
+ .reset = vf_vapoursynth_reset,
+ .destroy = vf_vapoursynth_destroy,
+ .priv_size = sizeof(struct priv),
+};
-static int control(vf_instance_t *vf, int request, void *data)
+static struct mp_filter *vf_vapoursynth_create(struct mp_filter *parent,
+ void *options)
{
- struct vf_priv_s *p = vf->priv;
- switch (request) {
- case VFCTRL_SEEK_RESET:
- if (p->out_node && reinit_vs(vf) < 0)
- return CONTROL_ERROR;
- return CONTROL_OK;
+ struct mp_filter *f = mp_filter_create(parent, &vf_vapoursynth_filter);
+ if (!f) {
+ talloc_free(options);
+ return NULL;
}
- return CONTROL_UNKNOWN;
-}
-static void uninit(struct vf_instance *vf)
-{
- struct vf_priv_s *p = vf->priv;
+ // In theory, we could allow multiple inputs and outputs, but since this
+ // wrapper is for --vf only, we don't.
+ mp_filter_add_pin(f, MP_PIN_IN, "in");
+ mp_filter_add_pin(f, MP_PIN_OUT, "out");
- destroy_vs(vf);
- p->drv->uninit(vf);
-
- pthread_cond_destroy(&p->wakeup);
- pthread_mutex_destroy(&p->lock);
-}
-static int vf_open(vf_instance_t *vf)
-{
- struct vf_priv_s *p = vf->priv;
- if (p->drv->init(vf) < 0)
- return 0;
- if (!p->cfg_file || !p->cfg_file[0]) {
- MP_FATAL(vf, "'file' parameter must be set.\n");
- return 0;
- }
- talloc_steal(vf, p->cfg_file);
- p->cfg_file = mp_get_user_path(vf, vf->chain->global, p->cfg_file);
+ struct priv *p = f->priv;
+ p->opts = talloc_steal(p, options);
+ p->log = f->log;
+ p->drv = p->opts->drv;
+ p->f = f;
pthread_mutex_init(&p->lock, NULL);
pthread_cond_init(&p->wakeup, NULL);
- vf->reconfig = reconfig;
- vf->filter_ext = filter_ext;
- vf->filter_out = filter_out;
- vf->needs_input = needs_input;
- vf->query_format = query_format;
- vf->control = control;
- vf->uninit = uninit;
- p->max_requests = p->cfg_maxrequests;
+
+ if (!p->opts->file || !p->opts->file[0]) {
+ MP_FATAL(p, "'file' parameter must be set.\n");
+ goto error;
+ }
+ talloc_steal(p, p->opts->file);
+ p->opts->file = mp_get_user_path(p, f->global, p->opts->file);
+
+ p->max_requests = p->opts->maxrequests;
if (p->max_requests < 0)
p->max_requests = av_cpu_count();
- MP_VERBOSE(vf, "using %d concurrent requests.\n", p->max_requests);
- int maxbuffer = p->cfg_maxbuffer * p->max_requests;
- p->buffered = talloc_array(vf, struct mp_image *, maxbuffer);
- p->requested = talloc_zero_array(vf, struct mp_image *, p->max_requests);
- return 1;
+ MP_VERBOSE(p, "using %d concurrent requests.\n", p->max_requests);
+ int maxbuffer = p->opts->maxbuffer * p->max_requests;
+ p->buffered = talloc_array(p, struct mp_image *, maxbuffer);
+ p->requested = talloc_zero_array(p, struct mp_image *, p->max_requests);
+
+ struct mp_autoconvert *conv = mp_autoconvert_create(f);
+ 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)
+ mp_autoconvert_add_imgfmt(conv, imgfmt, 0);
+ }
+
+ struct mp_filter *dur = mp_compute_frame_duration_create(f);
+ if (!dur)
+ goto error;
+
+ mp_pin_connect(conv->f->pins[0], f->ppins[0]);
+ mp_pin_connect(dur->pins[0], conv->f->pins[1]);
+ p->in_pin = dur->pins[1];
+
+ if (p->drv->init(p) < 0)
+ goto error;
+
+ return f;
+
+error:
+ talloc_free(f);
+ return NULL;
}
-#define OPT_BASE_STRUCT struct vf_priv_s
+
+#define OPT_BASE_STRUCT struct vapoursynth_opts
static const m_option_t vf_opts_fields[] = {
- OPT_STRING("file", cfg_file, M_OPT_FILE),
- OPT_INTRANGE("buffered-frames", cfg_maxbuffer, 0, 1, 9999, OPTDEF_INT(4)),
- OPT_CHOICE_OR_INT("concurrent-frames", cfg_maxrequests, 0, 1, 99,
+ OPT_STRING("file", file, M_OPT_FILE),
+ OPT_INTRANGE("buffered-frames", maxbuffer, 0, 1, 9999, OPTDEF_INT(4)),
+ OPT_CHOICE_OR_INT("concurrent-frames", maxrequests, 0, 1, 99,
({"auto", -1}), OPTDEF_INT(-1)),
{0}
};
@@ -776,24 +805,22 @@ static const m_option_t vf_opts_fields[] = {
#include <VSScript.h>
-static int drv_vss_init(struct vf_instance *vf)
+static int drv_vss_init(struct priv *p)
{
if (!vsscript_init()) {
- MP_FATAL(vf, "Could not initialize VapourSynth scripting.\n");
+ MP_FATAL(p, "Could not initialize VapourSynth scripting.\n");
return -1;
}
return 0;
}
-static