/* * 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 #include #include #include #include #include #include #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); }