summaryrefslogtreecommitdiffstats
path: root/video/filter/vf.c
diff options
context:
space:
mode:
Diffstat (limited to 'video/filter/vf.c')
-rw-r--r--video/filter/vf.c830
1 files changed, 830 insertions, 0 deletions
diff --git a/video/filter/vf.c b/video/filter/vf.c
new file mode 100644
index 0000000000..10b9fa546f
--- /dev/null
+++ b/video/filter/vf.c
@@ -0,0 +1,830 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <libavutil/common.h>
+#include <libavutil/mem.h>
+
+#include "config.h"
+
+#include "mp_msg.h"
+#include "m_option.h"
+#include "m_struct.h"
+
+
+#include "img_format.h"
+#include "mp_image.h"
+#include "vf.h"
+
+#include "libvo/fastmemcpy.h"
+
+extern const vf_info_t vf_info_vo;
+extern const vf_info_t vf_info_crop;
+extern const vf_info_t vf_info_expand;
+extern const vf_info_t vf_info_pp;
+extern const vf_info_t vf_info_scale;
+extern const vf_info_t vf_info_format;
+extern const vf_info_t vf_info_noformat;
+extern const vf_info_t vf_info_flip;
+extern const vf_info_t vf_info_rotate;
+extern const vf_info_t vf_info_mirror;
+extern const vf_info_t vf_info_noise;
+extern const vf_info_t vf_info_eq2;
+extern const vf_info_t vf_info_gradfun;
+extern const vf_info_t vf_info_unsharp;
+extern const vf_info_t vf_info_swapuv;
+extern const vf_info_t vf_info_down3dright;
+extern const vf_info_t vf_info_hqdn3d;
+extern const vf_info_t vf_info_ilpack;
+extern const vf_info_t vf_info_dsize;
+extern const vf_info_t vf_info_softpulldown;
+extern const vf_info_t vf_info_pullup;
+extern const vf_info_t vf_info_delogo;
+extern const vf_info_t vf_info_phase;
+extern const vf_info_t vf_info_divtc;
+extern const vf_info_t vf_info_softskip;
+extern const vf_info_t vf_info_screenshot;
+extern const vf_info_t vf_info_screenshot_force;
+extern const vf_info_t vf_info_sub;
+extern const vf_info_t vf_info_yadif;
+extern const vf_info_t vf_info_stereo3d;
+extern const vf_info_t vf_info_dlopen;
+
+// list of available filters:
+static const vf_info_t *const filter_list[] = {
+ &vf_info_crop,
+ &vf_info_expand,
+ &vf_info_scale,
+ &vf_info_vo,
+ &vf_info_format,
+ &vf_info_noformat,
+ &vf_info_flip,
+ &vf_info_rotate,
+ &vf_info_mirror,
+
+#ifdef CONFIG_LIBPOSTPROC
+ &vf_info_pp,
+#endif
+
+ &vf_info_screenshot,
+ &vf_info_screenshot_force,
+
+ &vf_info_noise,
+ &vf_info_eq2,
+ &vf_info_gradfun,
+ &vf_info_unsharp,
+ &vf_info_swapuv,
+ &vf_info_down3dright,
+ &vf_info_hqdn3d,
+ &vf_info_ilpack,
+ &vf_info_dsize,
+ &vf_info_softpulldown,
+ &vf_info_pullup,
+ &vf_info_delogo,
+ &vf_info_phase,
+ &vf_info_divtc,
+ &vf_info_sub,
+ &vf_info_yadif,
+ &vf_info_stereo3d,
+ &vf_info_dlopen,
+ NULL
+};
+
+// For the vf option
+const m_obj_list_t vf_obj_list = {
+ (void **)filter_list,
+ M_ST_OFF(vf_info_t, name),
+ M_ST_OFF(vf_info_t, info),
+ M_ST_OFF(vf_info_t, opts)
+};
+
+//============================================================================
+// mpi stuff:
+
+void vf_mpi_clear(mp_image_t *mpi, int x0, int y0, int w, int h)
+{
+ int y;
+ if (mpi->flags & MP_IMGFLAG_PLANAR) {
+ y0 &= ~1;
+ h += h & 1;
+ if (x0 == 0 && w == mpi->width) {
+ // full width clear:
+ memset(mpi->planes[0] + mpi->stride[0] * y0, 0, mpi->stride[0] * h);
+ memset(mpi->planes[1] + mpi->stride[1] *(y0 >> mpi->chroma_y_shift),
+ 128, mpi->stride[1] * (h >> mpi->chroma_y_shift));
+ memset(mpi->planes[2] + mpi->stride[2] *(y0 >> mpi->chroma_y_shift),
+ 128, mpi->stride[2] * (h >> mpi->chroma_y_shift));
+ } else
+ for (y = y0; y < y0 + h; y += 2) {
+ memset(mpi->planes[0] + x0 + mpi->stride[0] * y, 0, w);
+ memset(mpi->planes[0] + x0 + mpi->stride[0] * (y + 1), 0, w);
+ memset(mpi->planes[1] + (x0 >> mpi->chroma_x_shift) +
+ mpi->stride[1] * (y >> mpi->chroma_y_shift),
+ 128, (w >> mpi->chroma_x_shift));
+ memset(mpi->planes[2] + (x0 >> mpi->chroma_x_shift) +
+ mpi->stride[2] * (y >> mpi->chroma_y_shift),
+ 128, (w >> mpi->chroma_x_shift));
+ }
+ return;
+ }
+ // packed:
+ for (y = y0; y < y0 + h; y++) {
+ unsigned char *dst = mpi->planes[0] + mpi->stride[0] * y +
+ (mpi->bpp >> 3) * x0;
+ if (mpi->flags & MP_IMGFLAG_YUV) {
+ unsigned int *p = (unsigned int *) dst;
+ int size = (mpi->bpp >> 3) * w / 4;
+ int i;
+#ifdef BIG_ENDIAN
+#define CLEAR_PACKEDYUV_PATTERN 0x00800080
+#define CLEAR_PACKEDYUV_PATTERN_SWAPPED 0x80008000
+#else
+#define CLEAR_PACKEDYUV_PATTERN 0x80008000
+#define CLEAR_PACKEDYUV_PATTERN_SWAPPED 0x00800080
+#endif
+ if (mpi->flags & MP_IMGFLAG_SWAPPED) {
+ for (i = 0; i < size - 3; i += 4)
+ p[i] = p[i + 1] = p[i + 2] = p[i + 3] = CLEAR_PACKEDYUV_PATTERN_SWAPPED;
+ for (; i < size; i++)
+ p[i] = CLEAR_PACKEDYUV_PATTERN_SWAPPED;
+ } else {
+ for (i = 0; i < size - 3; i += 4)
+ p[i] = p[i + 1] = p[i + 2] = p[i + 3] = CLEAR_PACKEDYUV_PATTERN;
+ for (; i < size; i++)
+ p[i] = CLEAR_PACKEDYUV_PATTERN;
+ }
+ } else
+ memset(dst, 0, (mpi->bpp >> 3) * w);
+ }
+}
+
+mp_image_t *vf_get_image(vf_instance_t *vf, unsigned int outfmt,
+ int mp_imgtype, int mp_imgflag, int w, int h)
+{
+ mp_image_t *mpi = NULL;
+ int w2;
+ int number = mp_imgtype >> 16;
+
+ assert(w == -1 || w >= vf->w);
+ assert(h == -1 || h >= vf->h);
+ assert(vf->w > 0);
+ assert(vf->h > 0);
+
+ if (w == -1)
+ w = vf->w;
+ if (h == -1)
+ h = vf->h;
+
+ w2 = (mp_imgflag & MP_IMGFLAG_ACCEPT_ALIGNED_STRIDE) ? FFALIGN(w, 32) : w;
+
+ if (vf->put_image == vf_next_put_image) {
+ // passthru mode, if the filter uses the fallback/default put_image()
+ mpi = vf_get_image(vf->next,outfmt,mp_imgtype,mp_imgflag,w,h);
+ mpi->usage_count++;
+ return mpi;
+ }
+
+ // Note: we should call libvo first to check if it supports direct rendering
+ // and if not, then fallback to software buffers:
+ switch (mp_imgtype & 0xff) {
+ case MP_IMGTYPE_EXPORT:
+ if (!vf->imgctx.export_images[0])
+ vf->imgctx.export_images[0] = new_mp_image(w2, h);
+ mpi = vf->imgctx.export_images[0];
+ break;
+ case MP_IMGTYPE_STATIC:
+ if (!vf->imgctx.static_images[0])
+ vf->imgctx.static_images[0] = new_mp_image(w2, h);
+ mpi = vf->imgctx.static_images[0];
+ break;
+ case MP_IMGTYPE_TEMP:
+ if (!vf->imgctx.temp_images[0])
+ vf->imgctx.temp_images[0] = new_mp_image(w2, h);
+ mpi = vf->imgctx.temp_images[0];
+ break;
+ case MP_IMGTYPE_IPB:
+ if (!(mp_imgflag & MP_IMGFLAG_READABLE)) { // B frame:
+ if (!vf->imgctx.temp_images[0])
+ vf->imgctx.temp_images[0] = new_mp_image(w2, h);
+ mpi = vf->imgctx.temp_images[0];
+ break;
+ }
+ case MP_IMGTYPE_IP:
+ if (!vf->imgctx.static_images[vf->imgctx.static_idx])
+ vf->imgctx.static_images[vf->imgctx.static_idx] = new_mp_image(w2, h);
+ mpi = vf->imgctx.static_images[vf->imgctx.static_idx];
+ vf->imgctx.static_idx ^= 1;
+ break;
+ case MP_IMGTYPE_NUMBERED:
+ if (number == -1) {
+ int i;
+ for (i = 0; i < NUM_NUMBERED_MPI; i++)
+ if (!vf->imgctx.numbered_images[i] ||
+ !vf->imgctx.numbered_images[i]->usage_count)
+ break;
+ number = i;
+ }
+ if (number < 0 || number >= NUM_NUMBERED_MPI)
+ return NULL;
+ if (!vf->imgctx.numbered_images[number])
+ vf->imgctx.numbered_images[number] = new_mp_image(w2, h);
+ mpi = vf->imgctx.numbered_images[number];
+ mpi->number = number;
+ break;
+ }
+ if (mpi) {
+ int missing_palette = !(mpi->flags & MP_IMGFLAG_RGB_PALETTE) &&
+ (mp_imgflag & MP_IMGFLAG_RGB_PALETTE);
+ mpi->type = mp_imgtype;
+ mpi->w = vf->w;
+ mpi->h = vf->h;
+ // keep buffer allocation status & color flags only:
+ mpi->flags &= MP_IMGFLAG_ALLOCATED | MP_IMGFLAG_TYPE_DISPLAYED |
+ MP_IMGFLAGMASK_COLORS;
+ // accept restrictions, draw_slice and palette flags only:
+ mpi->flags |= mp_imgflag & (MP_IMGFLAGMASK_RESTRICTIONS |
+ MP_IMGFLAG_DRAW_CALLBACK | MP_IMGFLAG_RGB_PALETTE);
+ if (!vf->draw_slice)
+ mpi->flags &= ~MP_IMGFLAG_DRAW_CALLBACK;
+ if (mpi->width != w2 || mpi->height != h || missing_palette) {
+ if (mpi->flags & MP_IMGFLAG_ALLOCATED) {
+ if (mpi->width < w2 || mpi->height < h || missing_palette) {
+ // need to re-allocate buffer memory:
+ av_free(mpi->planes[0]);
+ if (mpi->flags & MP_IMGFLAG_RGB_PALETTE)
+ av_free(mpi->planes[1]);
+ for (int n = 0; n < MP_MAX_PLANES; n++)
+ mpi->planes[n] = NULL;
+ mpi->flags &= ~MP_IMGFLAG_ALLOCATED;
+ mp_msg(MSGT_VFILTER, MSGL_V,
+ "vf.c: have to REALLOCATE buffer memory :(\n");
+ }
+ }
+ mpi->width = w2;
+ mpi->chroma_width = (w2 + (1 << mpi->chroma_x_shift) - 1) >>
+ mpi->chroma_x_shift;
+ mpi->height = h;
+ mpi->chroma_height = (h + (1 << mpi->chroma_y_shift) - 1) >>
+ mpi->chroma_y_shift;
+ }
+ if (!mpi->bpp)
+ mp_image_setfmt(mpi, outfmt);
+ if (!(mpi->flags & MP_IMGFLAG_ALLOCATED) &&
+ mpi->type > MP_IMGTYPE_EXPORT) {
+ // check libvo first!
+ if (vf->get_image)
+ vf->get_image(vf, mpi);
+
+ if (!(mpi->flags & MP_IMGFLAG_DIRECT)) {
+ // non-direct and not yet allocated image. allocate it!
+ if (!mpi->bpp) { // no way we can allocate this
+ mp_msg(MSGT_DECVIDEO, MSGL_FATAL,
+ "vf_get_image: Tried to allocate a format that "
+ "can not be allocated!\n");
+ return NULL;
+ }
+
+ // check if codec prefer aligned stride:
+ if (mp_imgflag & MP_IMGFLAG_PREFER_ALIGNED_STRIDE) {
+ int align = (mpi->flags & MP_IMGFLAG_PLANAR &&
+ mpi->flags & MP_IMGFLAG_YUV) ?
+ (16 << mpi->chroma_x_shift) - 1 : 32; // OK?
+ w2 = FFALIGN(w, align);
+ if (mpi->width != w2) {
+ // we have to change width... check if we CAN co it:
+ int flags = vf->query_format(vf, outfmt);
+ // should not fail
+ if (!(flags & (VFCAP_CSP_SUPPORTED |
+ VFCAP_CSP_SUPPORTED_BY_HW)))
+ mp_msg(MSGT_DECVIDEO, MSGL_WARN,
+ "??? vf_get_image{vf->query_format(outfmt)} "
+ "failed!\n");
+ if (flags & VFCAP_ACCEPT_STRIDE) {
+ mpi->width = w2;
+ mpi->chroma_width =
+ (w2 + (1 << mpi->chroma_x_shift) - 1) >>
+ mpi->chroma_x_shift;
+ }
+ }
+ }
+
+ mp_image_alloc_planes(mpi);
+ vf_mpi_clear(mpi, 0, 0, mpi->width, mpi->height);
+ }
+ }
+ if (mpi->flags & MP_IMGFLAG_DRAW_CALLBACK)
+ if (vf->start_slice)
+ vf->start_slice(vf, mpi);
+ if (!(mpi->flags & MP_IMGFLAG_TYPE_DISPLAYED)) {
+ mp_msg(MSGT_DECVIDEO, MSGL_V,
+ "*** [%s] %s%s mp_image_t, %dx%dx%dbpp %s %s, %d bytes\n",
+ vf->info->name,
+ (mpi->type == MP_IMGTYPE_EXPORT) ? "Exporting" :
+ ((mpi->flags & MP_IMGFLAG_DIRECT) ?
+ "Direct Rendering" : "Allocating"),
+ (mpi->flags & MP_IMGFLAG_DRAW_CALLBACK) ? " (slices)" : "",
+ mpi->width, mpi->height, mpi->bpp,
+ (mpi->flags & MP_IMGFLAG_YUV) ? "YUV" :
+ ((mpi->flags & MP_IMGFLAG_SWAPPED) ? "BGR" : "RGB"),
+ (mpi->flags & MP_IMGFLAG_PLANAR) ? "planar" : "packed",
+ mpi->bpp * mpi->width * mpi->height / 8);
+ mp_msg(MSGT_DECVIDEO, MSGL_DBG2, "(imgfmt: %x, planes: %p,%p,%p "
+ "strides: %d,%d,%d, chroma: %dx%d, shift: h:%d,v:%d)\n",
+ mpi->imgfmt, mpi->planes[0], mpi->planes[1], mpi->planes[2],
+ mpi->stride[0], mpi->stride[1], mpi->stride[2],
+ mpi->chroma_width, mpi->chroma_height,
+ mpi->chroma_x_shift, mpi->chroma_y_shift);
+ mpi->flags |= MP_IMGFLAG_TYPE_DISPLAYED;
+ }
+ mpi->qscale = NULL;
+ mpi->usage_count++;
+ }
+ return mpi;
+}
+
+//============================================================================
+
+static int vf_default_query_format(struct vf_instance *vf, unsigned int fmt)
+{
+ return vf_next_query_format(vf, fmt);
+}
+
+struct vf_instance *vf_open_plugin_noerr(struct MPOpts *opts,
+ const vf_info_t *const *filter_list,
+ vf_instance_t *next, const char *name,
+ char **args, int *retcode)
+{
+ vf_instance_t *vf;
+ int i;
+ for (i = 0;; i++) {
+ if (!filter_list[i]) {
+ mp_tmsg(MSGT_VFILTER, MSGL_ERR,
+ "Couldn't find video filter '%s'.\n", name);
+ return NULL; // no such filter!
+ }
+ if (!strcmp(filter_list[i]->name, name))
+ break;
+ }
+ vf = calloc(1, sizeof *vf);
+ vf->opts = opts;
+ vf->info = filter_list[i];
+ vf->next = next;
+ vf->config = vf_next_config;
+ vf->control = vf_next_control;
+ vf->query_format = vf_default_query_format;
+ vf->put_image = vf_next_put_image;
+ vf->default_caps = VFCAP_ACCEPT_STRIDE;
+ vf->default_reqs = 0;
+ if (vf->info->opts) { // vf_vo get some special argument
+ const m_struct_t *st = vf->info->opts;
+ void *vf_priv = m_struct_alloc(st);
+ int n;
+ for (n = 0; args && args[2 * n]; n++)
+ m_struct_set(st, vf_priv, args[2 * n], bstr0(args[2 * n + 1]));
+ vf->priv = vf_priv;
+ args = NULL;
+ } else // Otherwise we should have the '_oldargs_'
+ if (args && !strcmp(args[0], "_oldargs_"))
+ args = (char **)args[1];
+ else
+ args = NULL;
+ *retcode = vf->info->vf_open(vf, (char *)args);
+ if (*retcode > 0)
+ return vf;
+ free(vf);
+ return NULL;
+}
+
+struct vf_instance *vf_open_plugin(struct MPOpts *opts,
+ const vf_info_t *const *filter_list,
+ vf_instance_t *next, const char *name,
+ char **args)
+{
+ struct vf_instance *vf = vf_open_plugin_noerr(opts, filter_list, next,
+ name, args, &(int){0});
+ if (!vf)
+ mp_tmsg(MSGT_VFILTER, MSGL_ERR, "Couldn't open video filter '%s'.\n",
+ name);
+ return vf;
+}
+
+vf_instance_t *vf_open_filter(struct MPOpts *opts, vf_instance_t *next,
+ const char *name, char **args)
+{
+ if (args && strcmp(args[0], "_oldargs_")) {
+ int i, l = 0;
+ for (i = 0; args && args[2 * i]; i++)
+ l += 1 + strlen(args[2 * i]) + 1 + strlen(args[2 * i + 1]);
+ l += strlen(name);
+ {
+ char str[l + 1];
+ char *p = str;
+ p += sprintf(str, "%s", name);
+ for (i = 0; args && args[2 * i]; i++)
+ p += sprintf(p, " %s=%s", args[2 * i], args[2 * i + 1]);
+ mp_msg(MSGT_VFILTER, MSGL_INFO, "%s[%s]\n",
+ mp_gtext("Opening video filter: "), str);
+ }
+ } else if (strcmp(name, "vo")) {
+ if (args && strcmp(args[0], "_oldargs_") == 0)
+ mp_msg(MSGT_VFILTER, MSGL_INFO, "%s[%s=%s]\n",
+ mp_gtext("Opening video filter: "), name, args[1]);
+ else
+ mp_msg(MSGT_VFILTER, MSGL_INFO, "%s[%s]\n",
+ mp_gtext("Opening video filter: "), name);
+ }
+ return vf_open_plugin(opts, filter_list, next, name, args);
+}
+
+/**
+ * \brief adds a filter before the last one (which should be the vo filter).
+ * \param vf start of the filter chain.
+ * \param name name of the filter to add.
+ * \param args argument list for the filter.
+ * \return pointer to the filter instance that was created.
+ */
+vf_instance_t *vf_add_before_vo(vf_instance_t **vf, char *name, char **args)
+{
+ struct MPOpts *opts = (*vf)->opts;
+ vf_instance_t *vo, *prev = NULL, *new;
+ // Find the last filter (should be vf_vo)
+ for (vo = *vf; vo->next; vo = vo->next)
+ prev = vo;
+ new = vf_open_filter(opts, vo, name, args);
+ if (prev)
+ prev->next = new;
+ else
+ *vf = new;
+ return new;
+}
+
+//============================================================================
+
+unsigned int vf_match_csp(vf_instance_t **vfp, const unsigned int *list,
+ unsigned int preferred)
+{
+ vf_instance_t *vf = *vfp;
+ struct MPOpts *opts = vf->opts;
+ const unsigned int *p;
+ unsigned int best = 0;
+ int ret;
+ if ((p = list))
+ while (*p) {
+ ret = vf->query_format(vf, *p);
+ mp_msg(MSGT_VFILTER, MSGL_V, "[%s] query(%s) -> %x\n",
+ vf->info->name, vo_format_name(*p), ret);
+ if (ret & VFCAP_CSP_SUPPORTED_BY_HW) {
+ best = *p;
+ break;
+ }
+ if (ret & VFCAP_CSP_SUPPORTED && !best)
+ best = *p;
+ ++p;
+ }
+ if (best)
+ return best; // bingo, they have common csp!
+ // ok, then try with scale:
+ if (vf->info == &vf_info_scale)
+ return 0; // avoid infinite recursion!
+ vf = vf_open_filter(opts, vf, "scale", NULL);
+ if (!vf)
+ return 0; // failed to init "scale"
+ // try the preferred csp first:
+ if (preferred && vf->query_format(vf, preferred))
+ best = preferred;
+ else
+ // try the list again, now with "scaler" :
+ if ((p = list))
+ while (*p) {
+ ret = vf->query_format(vf, *p);
+ mp_msg(MSGT_VFILTER, MSGL_V, "[%s] query(%s) -> %x\n",
+ vf->info->name, vo_format_name(*p), ret);
+ if (ret & VFCAP_CSP_SUPPORTED_BY_HW) {
+ best = *p;
+ break;
+ }
+ if (ret & VFCAP_CSP_SUPPORTED && !best)
+ best = *p;
+ ++p;
+ }
+ if (best)
+ *vfp = vf; // else uninit vf !FIXME!
+ return best;
+}
+
+void vf_clone_mpi_attributes(mp_image_t *dst, mp_image_t *src)
+{
+ dst->pict_type = src->pict_type;
+ dst->fields = src->fields;
+ dst->qscale_type = src->qscale_type;
+ if (dst->width == src->width && dst->height == src->height) {
+ dst->qstride = src->qstride;
+ dst->qscale = src->qscale;
+ dst->display_w = src->display_w;
+ dst->display_h = src->display_h;
+ }
+ if ((dst->flags & MP_IMGFLAG_YUV) == (src->flags & MP_IMGFLAG_YUV)) {
+ dst->colorspace = src->colorspace;
+ dst->levels = src->levels;
+ }
+}
+
+void vf_queue_frame(vf_instance_t *vf, int (*func)(vf_instance_t *))
+{
+ vf->continue_buffered_image = func;
+}
+
+// Output the next buffered image (if any) from the filter chain.
+// The queue could be kept as a simple stack/list instead avoiding the
+// looping here, but there's currently no good context variable where
+// that could be stored so this was easier to implement.
+
+int vf_output_queued_frame(vf_instance_t *vf)
+{
+ while (1) {
+ int ret;
+ vf_instance_t *current;
+ vf_instance_t *last = NULL;
+ int (*tmp)(vf_instance_t *);
+ for (current = vf; current; current = current->next)
+ if (current->continue_buffered_image)
+ last = current;
+ if (!last)
+ return 0;
+ tmp = last->continue_buffered_image;
+ last->continue_buffered_image = NULL;
+ ret = tmp(last);
+ if (ret)
+ return ret;
+ }
+}
+
+
+/**
+ * \brief Video config() function wrapper
+ *
+ * Blocks config() calls with different size or format for filters
+ * with VFCAP_CONSTANT
+ *
+ * First call is redirected to vf->config.
+ *
+ * In following calls, it verifies that the configuration parameters
+ * are unchanged, and returns either success or error.
+ *
+ */
+int vf_config_wrapper(struct vf_instance *vf,
+ int width, int height, int d_width, int d_height,
+ unsigned int flags, unsigned int outfmt)
+{
+ vf->fmt.have_configured = 1;
+ vf->fmt.orig_height = height;
+ vf->fmt.orig_width = width;
+ vf->fmt.orig_fmt = outfmt;
+ int r = vf->config(vf, width, height, d_width, d_height, flags, outfmt);
+ if (!r)
+ vf->fmt.have_configured = 0;
+ return r;
+}
+
+int vf_next_config(struct vf_instance *vf,
+ int width, int height, int d_width, int d_height,
+ unsigned int voflags, unsigned int outfmt)
+{
+ struct MPOpts *opts = vf->opts;
+ int miss;
+ int flags = vf->next->query_format(vf->next, outfmt);
+ if (!flags) {
+ // hmm. colorspace mismatch!!!
+ // let's insert the 'scale' filter, it does the job for us:
+ vf_instance_t *vf2;
+ if (vf->next->info == &vf_info_scale)
+ return 0; // scale->scale
+ vf2 = vf_open_filter(opts, vf->next, "scale", NULL);
+ if (!vf2)
+ return 0; // shouldn't happen!
+ vf->next = vf2;
+ flags = vf->next->query_format(vf->next, outfmt);
+ if (!flags) {
+ mp_tmsg(MSGT_VFILTER, MSGL_ERR, "Cannot find matching colorspace, "
+ "even by inserting 'scale' :(\n");
+ return 0; // FAIL
+ }
+ }
+ mp_msg(MSGT_VFILTER, MSGL_V, "REQ: flags=0x%X req=0x%X \n",
+ flags, vf->default_reqs);
+ miss = vf->default_reqs - (flags & vf->default_reqs);
+ if (miss & VFCAP_ACCEPT_STRIDE) {
+ // vf requires stride support but vf->next doesn't support it!
+ // let's insert the 'expand' filter, it does the job for us:
+ vf_instance_t *vf2 = vf_open_filter(opts, vf->next, "expand", NULL);
+ if (!vf2)
+ return 0; // shouldn't happen!
+ vf->next = vf2;
+ }
+ vf->next->w = width;
+ vf->next->h = height;
+ return vf_config_wrapper(vf->next, width, height, d_width, d_height,
+ voflags, outfmt);
+}
+
+int vf_next_control(struct vf_instance *vf, int request, void *data)
+{
+ return vf->next->control(vf->next, request, data);
+}
+
+int vf_next_query_format(struct vf_instance *vf, unsigned int fmt)
+{
+ int flags = vf->next->query_format(vf->next, fmt);
+ if (flags)
+ flags |= vf->default_caps;
+ return flags;
+}
+
+int vf_next_put_image(struct vf_instance *vf, mp_image_t *mpi, double pts)
+{
+ return vf->next->put_image(vf->next, mpi, pts);
+}
+
+void vf_next_draw_slice(struct vf_instance *vf, unsigned char **src,
+ int *stride, int w, int h, int x, int y)
+{
+ if (vf->next->draw_slice) {
+ vf->next->draw_slice(vf->next, src, stride, w, h, x, y);
+ return;
+ }
+ if (!vf->dmpi) {
+ mp_msg(MSGT_VFILTER, MSGL_ERR,
+ "draw_slice: dmpi not stored by vf_%s\n", vf->info->name);
+ return;
+ }
+ if (!(vf->dmpi->flags & MP_IMGFLAG_PLANAR)) {
+ memcpy_pic(vf->dmpi->planes[0] + y * vf->dmpi->stride[0] +
+ vf->dmpi->bpp / 8 * x,
+ src[0], vf->dmpi->bpp / 8 * w, h, vf->dmpi->stride[0],
+ stride[0]);
+ return;
+ }
+ memcpy_pic(vf->dmpi->planes[0] + y * vf->dmpi->stride[0] + x, src[0],
+ w, h, vf->dmpi->stride[0], stride[0]);
+ memcpy_pic(vf->dmpi->planes[1]
+ + (y >> vf->dmpi->chroma_y_shift) * vf->dmpi->stride[1]
+ + (x >> vf->dmpi->chroma_x_shift),
+ src[1], w >> vf->dmpi->chroma_x_shift,
+ h >> vf->dmpi->chroma_y_shift, vf->dmpi->stride[1], stride[1]);
+ memcpy_pic(vf->dmpi->planes[2]
+ + (y >> vf->dmpi->chroma_y_shift) * vf->dmpi->stride[2]
+ + (x >> vf->dmpi->chroma_x_shift),
+ src[2], w >> vf->dmpi->chroma_x_shift,
+ h >> vf->dmpi->chroma_y_shift, vf->dmpi->stride[2], stride[2]);
+}
+
+//============================================================================
+
+vf_instance_t *append_filters(vf_instance_t *last,
+ struct m_obj_settings *vf_settings)
+{
+ struct MPOpts *opts = last->opts;
+ vf_instance_t *vf;
+ int i;
+
+ if (vf_settings) {
+ // We want to add them in the 'right order'
+ for (i = 0; vf_settings[i].name; i++)
+ /* NOP */;
+ for (i--; i >= 0; i--) {
+ //printf("Open filter %s\n",vf_settings[i].name);
+ vf = vf_open_filter(opts, last, vf_settings[i].name,
+ vf_settings[i].attribs);
+ if (vf)
+ last = vf;
+ }
+ }
+ return last;
+}
+
+//============================================================================
+
+void vf_uninit_filter(vf_instance_t *vf)
+{
+ if (vf->uninit)
+ vf->uninit(vf);
+ free_mp_image(vf->imgctx.static_images[0]);
+ free_mp_image(vf->imgctx.static_images[1]);
+ free_mp_image(vf->imgctx.temp_images[0]);
+ free_mp_image(vf->imgctx.export_images[0]);
+ for (int i = 0; i < NUM_NUMBERED_MPI; i++)
+ free_mp_image(vf->imgctx.numbered_images[i]);
+ free(vf);
+}
+
+void vf_uninit_filter_chain(vf_instance_t *vf)
+{
+ while (vf) {
+ vf_instance_t *next = vf->next;
+ vf_uninit_filter(vf);
+ vf = next;
+ }
+}
+
+void vf_detc_init_pts_buf(struct vf_detc_pts_buf *p)
+{
+ p->inpts_prev = MP_NOPTS_VALUE;
+ p->outpts_prev = MP_NOPTS_VALUE;
+ p->lastdelta = 0;
+}
+
+static double vf_detc_adjust_pts_internal(struct vf_detc_pts_buf *p,
+ double pts, bool reset_pattern,
+ bool skip_frame, double delta,
+ double boundfactor_minus,
+ double increasefactor,
+ double boundfactor_plus)
+{
+ double newpts;
+
+ if (pts == MP_NOPTS_VALUE)
+ return pts;
+
+ if (delta <= 0) {
+ if (p->inpts_prev == MP_NOPTS_VALUE)
+ delta = 0;
+ else if (pts == p->inpts_prev)
+ delta = p->lastdelta;
+ else
+ delta = pts - p->inpts_prev;
+ }
+ p->inpts_prev = pts;
+ p->lastdelta = delta;
+
+ if (skip_frame)
+ return MP_NOPTS_VALUE;
+
+ /* detect bogus deltas and then passthru pts (possibly caused by seeking,
+ * or bad input) */
+ if (p->outpts_prev == MP_NOPTS_VALUE || reset_pattern || delta <= 0.0 ||
+ delta >= 0.5)
+ newpts = pts;
+ else {
+ // turn 5 frames into 4
+ newpts = p->outpts_prev + delta * increasefactor;
+
+ // bound to input pts in a sensible way; these numbers come because we
+ // map frames the following way when ivtc'ing:
+ // 0/30 -> 0/24 diff=0
+ // 1/30 -> 1/24 diff=1/120
+ // 2/30 -> -
+ // 3/30 -> 2/24 diff=-1/60
+ // 4/30 -> 3/24 diff=-1/120
+ if (newpts < pts - delta * boundfactor_minus)
+ newpts = pts - delta * boundfactor_minus;
+ if (newpts > pts + delta * boundfactor_plus)
+ newpts = pts + delta * boundfactor_plus;
+ if (newpts < p->outpts_prev)
+ newpts = p->outpts_prev; // damage control
+ }
+ p->outpts_prev = newpts;
+
+ return newpts;
+}
+
+double vf_detc_adjust_pts(struct vf_detc_pts_buf *p, double pts,
+ bool reset_pattern, bool skip_frame)
+{
+ // standard telecine (see above)
+ return vf_detc_adjust_pts_internal(p, pts, reset_pattern, skip_frame,
+ 0, 0.5, 1.25, 0.25);
+}
+
+double vf_softpulldown_adjust_pts(struct vf_detc_pts_buf *p, double pts,
+ bool reset_pattern, bool skip_frame,
+ int last_frame_duration)
+{
+ // for the softpulldown filter we get:
+ // 0/60 -> 0/30
+ // 2/60 -> 1/30
+ // 5/60 -> 2/30
+ // 7/60 -> 3/30, 4/30
+ return vf_detc_adjust_pts_internal(p, pts, reset_pattern, skip_frame,
+ 0, 1.0 / last_frame_duration,
+ 2.0 / last_frame_duration,
+ 1.0 / last_frame_duration);
+}