From ec60669cd10d726054bb5472cfe4eedf6010d154 Mon Sep 17 00:00:00 2001 From: wm4 Date: Tue, 29 Apr 2014 15:07:21 +0200 Subject: vdpau: add a postprocessing pseudo-filter This factors out some code from vo_vdpau.c, especially deinterlacing handling. The intention is to use this for vo_vdpau.c to make the logic significantly easier, and to use it for vo_opengl (gl_hwdec_vdpau.c) to allow selecting deinterlace and postprocessing modes. As of this commit, the filter actually does nothing, since both vo_vdpau and vo_opengl treat the generated images as normal vdpau images. This will change in the following commits. --- video/filter/vf.c | 4 + video/filter/vf_vdpaupp.c | 235 ++++++++++++++++++++++++++++++++++++++++++ video/vdpau_mixer.c | 254 ++++++++++++++++++++++++++++++++++++++++++++++ video/vdpau_mixer.h | 57 +++++++++++ 4 files changed, 550 insertions(+) create mode 100644 video/filter/vf_vdpaupp.c create mode 100644 video/vdpau_mixer.c create mode 100644 video/vdpau_mixer.h (limited to 'video') diff --git a/video/filter/vf.c b/video/filter/vf.c index 03afae6b13..2b9e82096a 100644 --- a/video/filter/vf.c +++ b/video/filter/vf.c @@ -70,6 +70,7 @@ extern const vf_info_t vf_info_dlopen; extern const vf_info_t vf_info_lavfi; extern const vf_info_t vf_info_vaapi; extern const vf_info_t vf_info_vapoursynth; +extern const vf_info_t vf_info_vdpaupp; // list of available filters: static const vf_info_t *const filter_list[] = { @@ -115,6 +116,9 @@ static const vf_info_t *const filter_list[] = { #endif #if HAVE_VAAPI_VPP &vf_info_vaapi, +#endif +#if HAVE_VDPAU + &vf_info_vdpaupp, #endif NULL }; diff --git a/video/filter/vf_vdpaupp.c b/video/filter/vf_vdpaupp.c new file mode 100644 index 0000000000..2e3a298855 --- /dev/null +++ b/video/filter/vf_vdpaupp.c @@ -0,0 +1,235 @@ +/* + * This file is part of mpv. + * + * Parts based on fragments of vo_vdpau.c: Copyright (C) 2009 Uoti Urpala + * + * mpv 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. + * + * mpv 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 mpv. If not, see . + */ + +#include +#include +#include +#include +#include + +#include "common/common.h" +#include "common/msg.h" +#include "options/m_option.h" + +#include "video/img_format.h" +#include "video/mp_image.h" +#include "video/hwdec.h" +#include "video/vdpau.h" +#include "video/vdpau_mixer.h" +#include "vf.h" + +// Note: this filter does no actual filtering; it merely sets appropriate +// flags on vdpau images (mp_vdpau_mixer_frame) to do the appropriate +// processing on the final rendering process in the VO. + +struct vf_priv_s { + struct mp_vdpau_ctx *ctx; + + // This is needed to supply past/future fields and to calculate the + // interpolated timestamp. + struct mp_image *buffered[3]; + int num_buffered; + + int prev_pos; // last field that was output + + struct mp_vdpau_mixer_opts opts; +}; + +static void forget_frames(struct vf_instance *vf) +{ + struct vf_priv_s *p = vf->priv; + for (int n = 0; n < p->num_buffered; n++) + talloc_free(p->buffered[n]); + p->num_buffered = 0; + p->prev_pos = 0; +} + +#define FIELD_VALID(p, f) ((f) >= 0 && (f) < (p)->num_buffered * 2) + +static VdpVideoSurface ref_frame(struct vf_priv_s *p, + struct mp_vdpau_mixer_frame *frame, int pos) +{ + if (!FIELD_VALID(p, pos)) + return VDP_INVALID_HANDLE; + struct mp_image *mpi = mp_image_new_ref(p->buffered[pos / 2]); + talloc_steal(frame, mpi); + return (uintptr_t)mpi->planes[3]; +} + +// pos==0 means last field of latest frame, 1 earlier field of latest frame, +// 2 last field of previous frame and so on +static bool output_field(struct vf_instance *vf, int pos) +{ + struct vf_priv_s *p = vf->priv; + + if (!FIELD_VALID(p, pos)) + return false; + + struct mp_image *mpi = mp_vdpau_mixed_frame_create(p->buffered[pos / 2]); + struct mp_vdpau_mixer_frame *frame = mp_vdpau_mixed_frame_get(mpi); + + frame->field = VDP_VIDEO_MIXER_PICTURE_STRUCTURE_FRAME; + if (p->opts.deint) { + int top_field_first = 1; + if (mpi->fields & MP_IMGFIELD_ORDERED) + top_field_first = !!(mpi->fields & MP_IMGFIELD_TOP_FIRST); + frame->field = top_field_first ^ (pos & 1) ? + VDP_VIDEO_MIXER_PICTURE_STRUCTURE_BOTTOM_FIELD: + VDP_VIDEO_MIXER_PICTURE_STRUCTURE_TOP_FIELD; + } + + frame->future[0] = ref_frame(p, frame, pos - 1); + frame->current = ref_frame(p, frame, pos); + frame->past[0] = ref_frame(p, frame, pos + 1); + frame->past[1] = ref_frame(p, frame, pos + 2); + + frame->opts = p->opts; + + mpi->planes[3] = (void *)(uintptr_t)frame->current; + + // Interpolate timestamps of extra fields (these always have even indexes) + int idx = pos / 2; + if (idx > 0 && !(pos & 1) && p->opts.deint >= 2) { + double pts1 = p->buffered[idx - 1]->pts; + double pts2 = p->buffered[idx]->pts; + double diff = pts1 - pts2; + mpi->pts = diff > 0 && diff < 0.5 ? (pts1 + pts2) / 2 : pts2; + } + + vf_add_output_frame(vf, mpi); + return true; +} + +static int filter_ext(struct vf_instance *vf, struct mp_image *mpi) +{ + struct vf_priv_s *p = vf->priv; + int maxbuffer = p->opts.deint ? MP_ARRAY_SIZE(p->buffered) : 1; + bool eof = !mpi; + + if (mpi->planes[2]) { + MP_ERR(vf, "Can't apply vdpaupp filter multiple times.\n"); + vf_add_output_frame(vf, mpi); + return -1; + } + + if (mpi) { + if (p->num_buffered == maxbuffer) { + talloc_free(p->buffered[p->num_buffered - 1]); + p->num_buffered--; + } + for (int n = p->num_buffered; n > 0; n--) + p->buffered[n] = p->buffered[n - 1]; + p->buffered[0] = mpi; + p->num_buffered++; + p->prev_pos += 2; + } + + while (1) { + int current = p->prev_pos - 1; + if (current < 0) + break; + // Wait for enough future frames being buffered. + // (Past frames should be around if available at all.) + if (p->opts.deint && !eof && !FIELD_VALID(p, current - 1)) + break; + // No field-splitting deinterlace -> only output first field (odd index) + if (p->opts.deint >= 2 || (current & 1)) { + if (!output_field(vf, current)) + break; + } + p->prev_pos = current; + } + + return 0; +} + +static int reconfig(struct vf_instance *vf, struct mp_image_params *in, + struct mp_image_params *out) +{ + forget_frames(vf); + *out = *in; + out->imgfmt = IMGFMT_VDPAU; + return 0; +} + +static int query_format(struct vf_instance *vf, unsigned int fmt) +{ + if (fmt == IMGFMT_VDPAU) + return vf_next_query_format(vf, IMGFMT_VDPAU); + return 0; +} + +static int control(vf_instance_t *vf, int request, void *data) +{ + switch (request) { + case VFCTRL_SEEK_RESET: + forget_frames(vf); + return CONTROL_OK; + } + return CONTROL_UNKNOWN; +} + +static void uninit(struct vf_instance *vf) +{ + forget_frames(vf); +} + +static int vf_open(vf_instance_t *vf) +{ + struct vf_priv_s *p = vf->priv; + + vf->reconfig = reconfig; + vf->filter_ext = filter_ext; + vf->filter = NULL; + vf->query_format = query_format; + vf->control = control; + vf->uninit = uninit; + + hwdec_request_api(vf->hwdec, "vdpau"); + p->ctx = vf->hwdec ? vf->hwdec->vdpau_ctx : NULL; + if (!p->ctx) + return 0; + + return 1; +} + +#define OPT_BASE_STRUCT struct vf_priv_s +static const m_option_t vf_opts_fields[] = { + OPT_CHOICE("deint", opts.deint, 0, + ({"no", 0}, + {"first-field", 1}, + {"bob", 2}, + {"temporal", 3}, + {"temporal-spatial", 4}), + OPTDEF_INT(3)), + OPT_FLAG("chroma-deint", opts.chroma_deint, 0, OPTDEF_INT(1)), + OPT_FLAG("pullup", opts.pullup, 0), + OPT_FLOATRANGE("denoise", opts.denoise, 0, 0, 1), + OPT_FLOATRANGE("sharpen", opts.sharpen, 0, -1, 1), + OPT_INTRANGE("hqscaling", opts.hqscaling, 0, 0, 9), + {0} +}; + +const vf_info_t vf_info_vdpaupp = { + .description = "vdpau postprocessing", + .name = "vdpaupp", + .open = vf_open, + .priv_size = sizeof(struct vf_priv_s), + .options = vf_opts_fields, +}; diff --git a/video/vdpau_mixer.c b/video/vdpau_mixer.c new file mode 100644 index 0000000000..f5743c5db3 --- /dev/null +++ b/video/vdpau_mixer.c @@ -0,0 +1,254 @@ +/* + * This file is part of mpv. + * + * Parts of video mixer creation code: + * Copyright (C) 2008 NVIDIA (Rajib Mahapatra ) + * Copyright (C) 2009 Uoti Urpala + * + * mpv 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. + * + * mpv 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 mpv. If not, see . + */ + +#include + +#include "vdpau_mixer.h" + +static void free_mixed_frame(void *arg) +{ + struct mp_vdpau_mixer_frame *frame = arg; + talloc_free(frame); +} + +// This creates an image of format IMGFMT_VDPAU with a mp_vdpau_mixer_frame +// struct. Use mp_vdpau_mixed_frame_get() to retrieve the struct and to +// initialize it. +// "base" is used only to set parameters, no image data is referenced. +struct mp_image *mp_vdpau_mixed_frame_create(struct mp_image *base) +{ + assert(base->imgfmt == IMGFMT_VDPAU); + + struct mp_vdpau_mixer_frame *frame = + talloc_zero(NULL, struct mp_vdpau_mixer_frame); + for (int n = 0; n < MP_VDP_HISTORY_FRAMES; n++) + frame->past[n] = frame->future[n] = VDP_INVALID_HANDLE; + frame->current = VDP_INVALID_HANDLE; + frame->field = VDP_VIDEO_MIXER_PICTURE_STRUCTURE_FRAME; + + struct mp_image *mpi = mp_image_new_custom_ref(base, frame, free_mixed_frame); + mpi->planes[2] = (void *)frame; + mpi->planes[3] = (void *)(uintptr_t)VDP_INVALID_HANDLE; + return mpi; +} + +struct mp_vdpau_mixer_frame *mp_vdpau_mixed_frame_get(struct mp_image *mpi) +{ + if (mpi->imgfmt != IMGFMT_VDPAU) + return NULL; + return (void *)mpi->planes[2]; +} + +struct mp_vdpau_mixer *mp_vdpau_mixer_create(struct mp_vdpau_ctx *vdp_ctx, + struct mp_log *log) +{ + struct mp_vdpau_mixer *mixer = talloc_ptrtype(NULL, mixer); + *mixer = (struct mp_vdpau_mixer){ + .ctx = vdp_ctx, + .log = log, + .video_mixer = VDP_INVALID_HANDLE, + .chroma_type = VDP_CHROMA_TYPE_420, + .video_eq = { + .capabilities = MP_CSP_EQ_CAPS_COLORMATRIX, + }, + }; + return mixer; +} + +void mp_vdpau_mixer_destroy(struct mp_vdpau_mixer *mixer) +{ + struct vdp_functions *vdp = &mixer->ctx->vdp; + VdpStatus vdp_st; + if (mixer->video_mixer != VDP_INVALID_HANDLE) { + vdp_st = vdp->video_mixer_destroy(mixer->video_mixer); + CHECK_VDP_WARNING(mixer, "Error when calling vdp_video_mixer_destroy"); + } + talloc_free(mixer); +} + +static bool opts_equal(const struct mp_vdpau_mixer_opts *a, + const struct mp_vdpau_mixer_opts *b) +{ + return a->deint == b->deint && a->chroma_deint == b->chroma_deint && + a->pullup == b->pullup && a->hqscaling == b->hqscaling && + a->sharpen == b->sharpen && a->denoise == b->denoise; +} + +static int set_video_attribute(struct mp_vdpau_mixer *mixer, + VdpVideoMixerAttribute attr, + const void *value, char *attr_name) +{ + struct vdp_functions *vdp = &mixer->ctx->vdp; + VdpStatus vdp_st; + + vdp_st = vdp->video_mixer_set_attribute_values(mixer->video_mixer, 1, + &attr, &value); + if (vdp_st != VDP_STATUS_OK) { + MP_ERR(mixer, "Error setting video mixer attribute %s: %s\n", attr_name, + vdp->get_error_string(vdp_st)); + return -1; + } + return 0; +} + +#define SET_VIDEO_ATTR(attr_name, attr_type, value) set_video_attribute(mixer, \ + VDP_VIDEO_MIXER_ATTRIBUTE_ ## attr_name, &(attr_type){value},\ + # attr_name) +static int create_vdp_mixer(struct mp_vdpau_mixer *mixer) +{ + struct vdp_functions *vdp = &mixer->ctx->vdp; + VdpDevice vdp_device = mixer->ctx->vdp_device; + struct mp_vdpau_mixer_opts *opts = &mixer->opts; +#define VDP_NUM_MIXER_PARAMETER 3 +#define MAX_NUM_FEATURES 6 + int i; + VdpStatus vdp_st; + + MP_VERBOSE(mixer, "Recreating vdpau video mixer.\n"); + + int feature_count = 0; + VdpVideoMixerFeature features[MAX_NUM_FEATURES]; + VdpBool feature_enables[MAX_NUM_FEATURES]; + static const VdpVideoMixerParameter parameters[VDP_NUM_MIXER_PARAMETER] = { + VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_WIDTH, + VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_HEIGHT, + VDP_VIDEO_MIXER_PARAMETER_CHROMA_TYPE, + }; + const void *const parameter_values[VDP_NUM_MIXER_PARAMETER] = { + &(uint32_t){mixer->image_params.w}, + &(uint32_t){mixer->image_params.h}, + &(VdpChromaType){mixer->chroma_type}, + }; + if (opts->deint >= 3) + features[feature_count++] = VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL; + if (opts->deint == 4) + features[feature_count++] = + VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL_SPATIAL; + if (opts->pullup) + features[feature_count++] = VDP_VIDEO_MIXER_FEATURE_INVERSE_TELECINE; + if (opts->denoise) + features[feature_count++] = VDP_VIDEO_MIXER_FEATURE_NOISE_REDUCTION; + if (opts->sharpen) + features[feature_count++] = VDP_VIDEO_MIXER_FEATURE_SHARPNESS; + if (opts->hqscaling) { + VdpVideoMixerFeature hqscaling_feature = + VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L1 + opts->hqscaling - 1; + VdpBool hqscaling_available; + vdp_st = vdp->video_mixer_query_feature_support(vdp_device, + hqscaling_feature, + &hqscaling_available); + CHECK_VDP_ERROR(mixer, "Error when calling video_mixer_query_feature_support"); + if (hqscaling_available) { + features[feature_count++] = hqscaling_feature; + } else { + MP_ERR(mixer, "Your hardware or VDPAU library does not support " + "requested hqscaling.\n"); + } + } + + vdp_st = vdp->video_mixer_create(vdp_device, feature_count, features, + VDP_NUM_MIXER_PARAMETER, + parameters, parameter_values, + &mixer->video_mixer); + CHECK_VDP_ERROR(mixer, "Error when calling vdp_video_mixer_create"); + + mixer->initialized = true; + + for (i = 0; i < feature_count; i++) + feature_enables[i] = VDP_TRUE; + if (feature_count) { + vdp_st = vdp->video_mixer_set_feature_enables(mixer->video_mixer, + feature_count, features, + feature_enables); + CHECK_VDP_WARNING(mixer, "Error calling vdp_video_mixer_set_feature_enables"); + } + if (opts->denoise) + SET_VIDEO_ATTR(NOISE_REDUCTION_LEVEL, float, opts->denoise); + if (opts->sharpen) + SET_VIDEO_ATTR(SHARPNESS_LEVEL, float, opts->sharpen); + if (!opts->chroma_deint) + SET_VIDEO_ATTR(SKIP_CHROMA_DEINTERLACE, uint8_t, 1); + + // VdpCSCMatrix happens to be compatible with mpv's CSC matrix type + // both are float[3][4] + VdpCSCMatrix matrix; + + struct mp_csp_params cparams = MP_CSP_PARAMS_DEFAULTS; + cparams.colorspace.format = mixer->image_params.colorspace; + cparams.colorspace.levels_in = mixer->image_params.colorlevels; + cparams.colorspace.levels_out = mixer->image_params.outputlevels; + mp_csp_copy_equalizer_values(&cparams, &mixer->video_eq); + mp_get_yuv2rgb_coeffs(&cparams, matrix); + + set_video_attribute(mixer, VDP_VIDEO_MIXER_ATTRIBUTE_CSC_MATRIX, + &matrix, "CSC matrix"); + + return 0; +} + +int mp_vdpau_mixer_render(struct mp_vdpau_mixer *mixer, + VdpOutputSurface output, VdpRect *output_rect, + struct mp_image *video, VdpRect *video_rect) +{ + struct vdp_functions *vdp = &mixer->ctx->vdp; + VdpStatus vdp_st; + + assert(video->imgfmt == IMGFMT_VDPAU); + + struct mp_vdpau_mixer_frame *frame = mp_vdpau_mixed_frame_get(video); + struct mp_vdpau_mixer_frame fallback = {{0}}; + if (!frame) { + frame = &fallback; + frame->current = (uintptr_t)video->planes[3]; + for (int n = 0; n < MP_VDP_HISTORY_FRAMES; n++) + frame->past[n] = frame->future[n] = VDP_INVALID_HANDLE; + frame->field = VDP_VIDEO_MIXER_PICTURE_STRUCTURE_FRAME; + } + + if (mixer->video_mixer == VDP_INVALID_HANDLE) + mixer->initialized = false; + + if (!mixer->initialized || !opts_equal(&frame->opts, &mixer->opts) || + !mp_image_params_equals(&video->params, &mixer->image_params)) + { + mixer->opts = frame->opts; + mixer->image_params = video->params; + if (mixer->video_mixer != VDP_INVALID_HANDLE) { + vdp_st = vdp->video_mixer_destroy(mixer->video_mixer); + CHECK_VDP_WARNING(mixer, "Error when calling vdp_video_mixer_destroy"); + } + mixer->video_mixer = VDP_INVALID_HANDLE; + mixer->initialized = false; + if (create_vdp_mixer(mixer) < 0) + return -1; + } + + vdp_st = vdp->video_mixer_render(mixer->video_mixer, VDP_INVALID_HANDLE, + 0, frame->field, + MP_VDP_HISTORY_FRAMES, frame->past, + frame->current, + MP_VDP_HISTORY_FRAMES, frame->future, + video_rect, + output, NULL, output_rect, + 0, NULL); + CHECK_VDP_WARNING(mixer, "Error when calling vdp_video_mixer_render"); + return 0; +} diff --git a/video/vdpau_mixer.h b/video/vdpau_mixer.h new file mode 100644 index 0000000000..80ba978373 --- /dev/null +++ b/video/vdpau_mixer.h @@ -0,0 +1,57 @@ +#ifndef MP_VDPAU_MIXER_H_ +#define MP_VDPAU_MIXER_H_ + +#include + +#include "mp_image.h" +#include "vdpau.h" + +struct mp_vdpau_mixer_opts { + int deint; + int chroma_deint; + int pullup; + float denoise; + float sharpen; + int hqscaling; +}; + +#define MP_VDP_HISTORY_FRAMES 2 + +struct mp_vdpau_mixer_frame { + // settings + struct mp_vdpau_mixer_opts opts; + // video data + VdpVideoMixerPictureStructure field; + VdpVideoSurface past[MP_VDP_HISTORY_FRAMES]; + VdpVideoSurface current; + VdpVideoSurface future[MP_VDP_HISTORY_FRAMES]; +}; + +struct mp_vdpau_mixer { + struct mp_log *log; + struct mp_vdpau_ctx *ctx; + bool initialized; + + struct mp_image_params image_params; + struct mp_vdpau_mixer_opts opts; + VdpChromaType chroma_type; + + // set initialized=false to force reinit when changed + struct mp_csp_equalizer video_eq; + + VdpVideoMixer video_mixer; +}; + +struct mp_image *mp_vdpau_mixed_frame_create(struct mp_image *base); + +struct mp_vdpau_mixer_frame *mp_vdpau_mixed_frame_get(struct mp_image *mpi); + +struct mp_vdpau_mixer *mp_vdpau_mixer_create(struct mp_vdpau_ctx *vdp_ctx, + struct mp_log *log); +void mp_vdpau_mixer_destroy(struct mp_vdpau_mixer *mixer); + +int mp_vdpau_mixer_render(struct mp_vdpau_mixer *mixer, + VdpOutputSurface output, VdpRect *output_rect, + struct mp_image *video, VdpRect *video_rect); + +#endif -- cgit v1.2.3