1 /*****************************************************************************
2 * chroma.c: VLC picture import into VDPAU
3 *****************************************************************************
4 * Copyright (C) 2013 Rémi Denis-Courmont
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; either version 2.1 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19 *****************************************************************************/
29 #include <vlc_common.h>
30 #include <vlc_plugin.h>
31 #include <vlc_filter.h>
32 #include "vlc_vdpau.h"
34 /* Picture history as recommended by VDPAU documentation */
44 VdpYCbCrFormat format;
45 picture_t *(*import)(filter_t *, picture_t *);
49 vlc_vdp_video_field_t *field;
52 } history[MAX_PAST + 1 + MAX_FUTURE];
63 /** Initialize the colour space conversion matrix */
64 static VdpStatus MixerSetupColors(filter_t *filter, const VdpProcamp *procamp,
65 VdpCSCMatrix *restrict csc)
67 filter_sys_t *sys = filter->p_sys;
69 VdpColorStandard std = (filter->fmt_in.video.i_height > 576)
70 ? VDP_COLOR_STANDARD_ITUR_BT_709
71 : VDP_COLOR_STANDARD_ITUR_BT_601;
73 err = vdp_generate_csc_matrix(sys->vdp, procamp, std, csc);
74 if (err != VDP_STATUS_OK)
76 msg_Err(filter, "video %s failure: %s", "color space matrix",
77 vdp_get_error_string(sys->vdp, err));
83 sys->procamp.brightness = procamp->brightness;
84 sys->procamp.contrast = procamp->contrast;
85 sys->procamp.saturation = procamp->saturation;
86 sys->procamp.hue = procamp->hue;
90 sys->procamp.brightness = 0.f;
91 sys->procamp.contrast = 1.f;
92 sys->procamp.saturation = 1.f;
93 sys->procamp.hue = 0.f;
98 /** Create VDPAU video mixer */
99 static VdpVideoMixer MixerCreate(filter_t *filter)
101 filter_sys_t *sys = filter->p_sys;
106 /* Check for potentially useful features */
107 VdpVideoMixerFeature featv[5];
110 int algo = var_InheritInteger(filter, "vdpau-deinterlace");
112 if (algo == VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL_SPATIAL)
114 err = vdp_video_mixer_query_feature_support(sys->vdp, sys->device,
116 if (err == VDP_STATUS_OK && ok == VDP_TRUE)
117 msg_Dbg(filter, "using video mixer %s feature",
118 "temporal-spatial deinterlace");
120 algo = VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL; /* fallback */
122 if (algo == VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL)
124 err = vdp_video_mixer_query_feature_support(sys->vdp, sys->device,
126 if (err == VDP_STATUS_OK && ok == VDP_TRUE)
127 msg_Dbg(filter, "using video mixer %s feature",
128 "temporal deinterlace");
134 featv[featc++] = algo;
135 ivtc = var_InheritBool(filter, "vdpau-ivtc");
138 err = vdp_video_mixer_query_feature_support(sys->vdp, sys->device,
139 VDP_VIDEO_MIXER_FEATURE_INVERSE_TELECINE, &ok);
140 if (err == VDP_STATUS_OK && ok == VDP_TRUE)
141 msg_Dbg(filter, "using video mixer %s feature",
143 featv[featc++] = VDP_VIDEO_MIXER_FEATURE_INVERSE_TELECINE;
147 const float noise = var_InheritFloat(filter, "vdpau-noise-reduction");
150 err = vdp_video_mixer_query_feature_support(sys->vdp, sys->device,
151 VDP_VIDEO_MIXER_FEATURE_NOISE_REDUCTION, &ok);
152 if (err == VDP_STATUS_OK && ok == VDP_TRUE)
154 msg_Dbg(filter, "using video mixer %s feature", "noise reduction");
155 featv[featc++] = VDP_VIDEO_MIXER_FEATURE_NOISE_REDUCTION;
159 err = vdp_video_mixer_query_feature_support(sys->vdp, sys->device,
160 VDP_VIDEO_MIXER_FEATURE_SHARPNESS, &ok);
161 if (err == VDP_STATUS_OK && ok == VDP_TRUE)
163 msg_Dbg(filter, "using video mixer %s feature", "sharpness");
164 featv[featc++] = VDP_VIDEO_MIXER_FEATURE_SHARPNESS;
167 const int offset = VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L1 - 1;
168 unsigned level = var_InheritInteger(filter, "vdpau-scaling");
172 err = vdp_video_mixer_query_feature_support(sys->vdp, sys->device,
173 offset + level, &ok);
174 if (err == VDP_STATUS_OK && ok == VDP_TRUE)
176 msg_Dbg(filter, "using video mixer high quality scaling L%u",
178 featv[featc++] = offset + level;
181 level--; /* fallback to lower quality */
184 /* Create the mixer */
185 VdpVideoMixerParameter parms[3] = {
186 VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_WIDTH,
187 VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_HEIGHT,
188 VDP_VIDEO_MIXER_PARAMETER_CHROMA_TYPE,
190 uint32_t width = filter->fmt_in.video.i_width;
191 uint32_t height = filter->fmt_in.video.i_height;
192 const void *values[3] = { &width, &height, &sys->chroma, };
194 err = vdp_video_mixer_create(sys->vdp, sys->device, featc, featv,
195 3, parms, values, &mixer);
196 if (err != VDP_STATUS_OK)
198 msg_Err(filter, "video %s %s failure: %s", "mixer", "creation",
199 vdp_get_error_string(sys->vdp, err));
200 return VDP_INVALID_HANDLE;
203 msg_Dbg(filter, "using video mixer %"PRIu32, mixer);
205 /* Set initial features and attributes */
206 VdpVideoMixerAttribute attrv[3];
214 if (MixerSetupColors(filter, NULL, &csc) == VDP_STATUS_OK)
216 attrv[attrc] = VDP_VIDEO_MIXER_ATTRIBUTE_CSC_MATRIX;
223 featv[featc++] = algo;
225 featv[featc++] = VDP_VIDEO_MIXER_FEATURE_INVERSE_TELECINE;
227 chroma_skip = var_InheritBool(filter, "vdpau-chroma-skip");
228 attrv[attrc] = VDP_VIDEO_MIXER_ATTRIBUTE_SKIP_CHROMA_DEINTERLACE;
229 valv[attrc] = &chroma_skip;
235 featv[featc++] = VDP_VIDEO_MIXER_FEATURE_NOISE_REDUCTION;
237 attrv[attrc] = VDP_VIDEO_MIXER_ATTRIBUTE_NOISE_REDUCTION_LEVEL;
238 valv[attrc] = &noise;
243 featv[featc++] = offset + level;
247 VdpBool enablev[featc];
249 for (unsigned i = 0; i < featc; i++)
250 enablev[i] = VDP_TRUE;
252 err = vdp_video_mixer_set_feature_enables(sys->vdp, mixer,
253 featc, featv, enablev);
254 if (err != VDP_STATUS_OK)
255 msg_Err(filter, "video %s %s failure: %s", "mixer", "features",
256 vdp_get_error_string(sys->vdp, err));
261 err = vdp_video_mixer_set_attribute_values(sys->vdp, mixer,
263 if (err != VDP_STATUS_OK)
264 msg_Err(filter, "video %s %s failure: %s", "mixer", "attributes",
265 vdp_get_error_string(sys->vdp, err));
271 static void Flush(filter_t *filter)
273 filter_sys_t *sys = filter->p_sys;
275 for (unsigned i = 0; i < MAX_PAST + MAX_FUTURE; i++)
276 if (sys->history[i].field != NULL)
278 sys->history[i].field->destroy(sys->history[i].field);
279 sys->history[i].field = NULL;
283 /** Get a VLC picture for a VDPAU output surface */
284 static picture_t *OutputAllocate(filter_t *filter)
286 filter_sys_t *sys = filter->p_sys;
288 picture_t *pic = filter_NewPicture(filter);
292 picture_sys_t *psys = pic->p_sys;
293 assert(psys->vdp != NULL);
295 if (likely(sys->mixer != VDP_INVALID_HANDLE))
296 { /* Not the first output picture */
297 assert(psys->vdp == sys->vdp);
301 /* First picture: get the context and allocate the mixer */
302 sys->vdp = vdp_hold_x11(psys->vdp, NULL);
303 sys->device = psys->device;
304 sys->mixer = MixerCreate(filter);
305 if (sys->mixer != VDP_INVALID_HANDLE)
308 vdp_release_x11(psys->vdp);
310 picture_Release(pic);
314 /** Export a VDPAU video surface picture to a normal VLC picture */
315 static picture_t *VideoExport(filter_t *filter, picture_t *src, picture_t *dst)
317 filter_sys_t *sys = filter->p_sys;
318 vlc_vdp_video_field_t *field = src->context;
319 vlc_vdp_video_frame_t *psys = field->frame;
321 VdpVideoSurface surface = psys->surface;
325 picture_CopyProperties(dst, src);
327 for (int i = 0; i < dst->i_planes; i++)
329 planes[i] = dst->p[i].p_pixels;
330 pitches[i] = dst->p[i].i_pitch;
332 if (dst->format.i_chroma == VLC_CODEC_I420
333 || dst->format.i_chroma == VLC_CODEC_I422)
335 planes[1] = dst->p[2].p_pixels;
336 planes[2] = dst->p[1].p_pixels;
337 pitches[1] = dst->p[2].i_pitch;
338 pitches[2] = dst->p[1].i_pitch;
340 err = vdp_video_surface_get_bits_y_cb_cr(psys->vdp, surface, sys->format,
342 if (err != VDP_STATUS_OK)
344 msg_Err(filter, "video %s %s failure: %s", "surface", "export",
345 vdp_get_error_string(psys->vdp, err));
346 picture_Release(dst);
349 picture_Release(src);
353 /** Import VLC picture into VDPAU video surface */
354 static picture_t *VideoImport(filter_t *filter, picture_t *src)
356 filter_sys_t *sys = filter->p_sys;
357 VdpVideoSurface surface;
360 if (sys->vdp == NULL)
363 /* Create surface (TODO: reuse?) */
364 err = vdp_video_surface_create(sys->vdp, sys->device, sys->chroma,
365 filter->fmt_in.video.i_width,
366 filter->fmt_in.video.i_height, &surface);
367 if (err != VDP_STATUS_OK)
369 msg_Err(filter, "video %s %s failure: %s", "surface", "creation",
370 vdp_get_error_string(sys->vdp, err));
375 const void *planes[3];
377 for (int i = 0; i < src->i_planes; i++)
379 planes[i] = src->p[i].p_pixels;
380 pitches[i] = src->p[i].i_pitch;
382 if (src->format.i_chroma == VLC_CODEC_I420)
384 planes[1] = src->p[2].p_pixels;
385 planes[2] = src->p[1].p_pixels;
386 pitches[1] = src->p[2].i_pitch;
387 pitches[2] = src->p[1].i_pitch;
389 err = vdp_video_surface_put_bits_y_cb_cr(sys->vdp, surface, sys->format,
391 if (err != VDP_STATUS_OK)
393 msg_Err(filter, "video %s %s failure: %s", "surface", "import",
394 vdp_get_error_string(sys->vdp, err));
398 /* Wrap surface into a picture */
399 video_format_t fmt = src->format;
400 fmt.i_chroma = (sys->chroma == VDP_CHROMA_TYPE_420)
401 ? VLC_CODEC_VDPAU_VIDEO_420 : VLC_CODEC_VDPAU_VIDEO_422;
403 picture_t *dst = picture_NewFromFormat(&fmt);
404 if (unlikely(dst == NULL))
406 picture_CopyProperties(dst, src);
407 picture_Release(src);
409 err = vlc_vdp_video_attach(sys->vdp, surface, dst);
410 if (unlikely(err != VDP_STATUS_OK))
412 picture_Release(dst);
417 vdp_video_surface_destroy(sys->vdp, surface);
419 picture_Release(src);
423 static picture_t *VideoPassthrough(filter_t *filter, picture_t *src)
425 filter_sys_t *sys = filter->p_sys;
426 vlc_vdp_video_field_t *field = src->context;
428 if (unlikely(field == NULL))
430 msg_Err(filter, "corrupt VDPAU video surface");
434 vlc_vdp_video_frame_t *psys = field->frame;
436 /* Corner case: different VDPAU instances decoding and rendering */
437 if (psys->vdp != sys->vdp)
439 video_format_t fmt = src->format;
442 case VDP_CHROMA_TYPE_420: fmt.i_chroma = VLC_CODEC_NV12; break;
443 case VDP_CHROMA_TYPE_422: fmt.i_chroma = VLC_CODEC_UYVY; break;
444 case VDP_CHROMA_TYPE_444: fmt.i_chroma = VLC_CODEC_NV24; break;
448 picture_t *pic = picture_NewFromFormat(&fmt);
449 if (unlikely(pic == NULL))
452 pic = VideoExport(filter, src, pic);
456 src = VideoImport(filter, pic);
460 picture_Release(src);
464 static inline VdpVideoSurface picture_GetVideoSurface(const picture_t *pic)
466 vlc_vdp_video_field_t *field = pic->context;
467 return field->frame->surface;
470 static picture_t *MixerRender(filter_t *filter, picture_t *src)
472 filter_sys_t *sys = filter->p_sys;
475 picture_t *dst = OutputAllocate(filter);
477 src = sys->import(filter, src);
478 /* Update history and take "present" picture field */
479 if (likely(src != NULL))
481 sys->history[MAX_PAST + MAX_FUTURE].field =
482 vlc_vdp_video_copy(src->context);
483 sys->history[MAX_PAST + MAX_FUTURE].date = src->date;
484 sys->history[MAX_PAST + MAX_FUTURE].force = src->b_force;
485 picture_Release(src);
488 sys->history[MAX_PAST + MAX_FUTURE].field = NULL;
493 vlc_vdp_video_field_t *f = sys->history[MAX_PAST].field;
496 picture_Release(dst);
501 dst->date = sys->history[MAX_PAST].date;
502 dst->b_force = sys->history[MAX_PAST].force;
504 /* Enable/Disable features */
505 const VdpVideoMixerFeature features[] = {
506 VDP_VIDEO_MIXER_FEATURE_SHARPNESS,
508 const VdpBool enables[] = {
512 err = vdp_video_mixer_set_feature_enables(sys->vdp, sys->mixer,
513 sizeof (features) / sizeof (features[0]), features, enables);
514 if (err != VDP_STATUS_OK)
515 msg_Err(filter, "video %s %s failure: %s", "mixer", "features",
516 vdp_get_error_string(sys->vdp, err));
518 /* Configure mixer depending on upstream video filters */
519 VdpVideoMixerAttribute attrs[2] = {
520 VDP_VIDEO_MIXER_ATTRIBUTE_SHARPNESS_LEVEL,
522 const void *values[2] = {
528 if ((sys->procamp.brightness != f->procamp.brightness
529 || sys->procamp.contrast != f->procamp.contrast
530 || sys->procamp.saturation != f->procamp.saturation
531 || sys->procamp.hue != f->procamp.hue)
532 && (MixerSetupColors(filter, &f->procamp, &csc) == VDP_STATUS_OK))
534 attrs[count] = VDP_VIDEO_MIXER_ATTRIBUTE_CSC_MATRIX;
535 values[count] = &csc;
539 err = vdp_video_mixer_set_attribute_values(sys->vdp, sys->mixer,
540 count, attrs, values);
541 if (err != VDP_STATUS_OK)
542 msg_Err(filter, "video %s %s failure: %s", "mixer", "attributes",
543 vdp_get_error_string(sys->vdp, err));
545 /* Render video into output */
546 VdpVideoMixerPictureStructure structure = f->structure;
547 VdpVideoSurface past[MAX_PAST];
548 VdpVideoSurface surface = f->frame->surface;
549 VdpVideoSurface future[MAX_FUTURE];
551 filter->fmt_in.video.i_x_offset, filter->fmt_in.video.i_y_offset,
552 filter->fmt_in.video.i_visible_width + filter->fmt_in.video.i_x_offset,
553 filter->fmt_in.video.i_visible_height + filter->fmt_in.video.i_y_offset
555 VdpOutputSurface output = dst->p_sys->surface;
558 filter->fmt_out.video.i_visible_width,
559 filter->fmt_out.video.i_visible_height
562 for (unsigned i = 0; i < MAX_PAST; i++)
564 f = sys->history[(MAX_PAST - 1) - i].field;
565 past[i] = (f != NULL) ? f->frame->surface : VDP_INVALID_HANDLE;
567 for (unsigned i = 0; i < MAX_FUTURE; i++)
569 f = sys->history[(MAX_PAST + 1) + i].field;
570 future[i] = (f != NULL) ? f->frame->surface : VDP_INVALID_HANDLE;
573 err = vdp_video_mixer_render(sys->vdp, sys->mixer, VDP_INVALID_HANDLE,
575 MAX_PAST, past, surface, MAX_FUTURE, future,
576 &src_rect, output, &dst_rect, NULL, 0, NULL);
577 if (err != VDP_STATUS_OK)
579 msg_Err(filter, "video %s %s failure: %s", "mixer", "rendering",
580 vdp_get_error_string(sys->vdp, err));
581 picture_Release(dst);
586 f = sys->history[0].field;
588 f->destroy(f); /* Release oldest field */
589 memmove(sys->history, sys->history + 1, /* Advance history */
590 sizeof (sys->history[0]) * (MAX_PAST + MAX_FUTURE));
595 static int OutputOpen(vlc_object_t *obj)
597 filter_t *filter = (filter_t *)obj;
598 if (filter->fmt_out.video.i_chroma != VLC_CODEC_VDPAU_OUTPUT)
601 filter_sys_t *sys = malloc(sizeof (*sys));
602 if (unlikely(sys == NULL))
606 sys->mixer = VDP_INVALID_HANDLE;
608 if (filter->fmt_in.video.i_chroma == VLC_CODEC_VDPAU_VIDEO_444)
610 sys->chroma = VDP_CHROMA_TYPE_444;
611 sys->format = VDP_YCBCR_FORMAT_NV12;
612 sys->import = VideoPassthrough;
615 if (filter->fmt_in.video.i_chroma == VLC_CODEC_VDPAU_VIDEO_422)
617 sys->chroma = VDP_CHROMA_TYPE_422;
618 /* TODO: check if the drivery supports NV12 or UYVY */
619 sys->format = VDP_YCBCR_FORMAT_UYVY;
620 sys->import = VideoPassthrough;
623 if (filter->fmt_in.video.i_chroma == VLC_CODEC_VDPAU_VIDEO_420)
625 sys->chroma = VDP_CHROMA_TYPE_420;
626 sys->format = VDP_YCBCR_FORMAT_NV12;
627 sys->import = VideoPassthrough;
630 if (vlc_fourcc_to_vdp_ycc(filter->fmt_in.video.i_chroma,
631 &sys->chroma, &sys->format))
632 sys->import = VideoImport;
639 /* NOTE: The video mixer capabilities should be checked here, and the
640 * then video mixer set up. But:
641 * 1) The VDPAU back-end is accessible only once the first picture
642 * gets filtered. Thus the video mixer is created later.
643 * 2) Bailing out due to insufficient capabilities would break the
644 * video pipeline. Thus capabilities should be checked earlier. */
646 for (unsigned i = 0; i < MAX_PAST + MAX_FUTURE; i++)
647 sys->history[i].field = NULL;
649 sys->procamp.brightness = 0.f;
650 sys->procamp.contrast = 1.f;
651 sys->procamp.saturation = 1.f;
652 sys->procamp.hue = 0.f;
654 filter->pf_video_filter = MixerRender;
655 filter->pf_video_flush = Flush;
660 static void OutputClose(vlc_object_t *obj)
662 filter_t *filter = (filter_t *)obj;
663 filter_sys_t *sys = filter->p_sys;
666 if (sys->mixer != VDP_INVALID_HANDLE)
668 vdp_video_mixer_destroy(sys->vdp, sys->mixer);
669 vdp_release_x11(sys->vdp);
674 static picture_t *VideoExport_Filter(filter_t *filter, picture_t *src)
676 if (unlikely(src->context == NULL))
678 msg_Err(filter, "corrupt VDPAU video surface %p", src);
679 picture_Release(src);
683 picture_t *dst = filter_NewPicture(filter);
687 return VideoExport(filter, src, dst);
690 static int YCbCrOpen(vlc_object_t *obj)
692 filter_t *filter = (filter_t *)obj;
693 if (filter->fmt_in.video.i_chroma != VLC_CODEC_VDPAU_VIDEO_420
694 && filter->fmt_in.video.i_chroma != VLC_CODEC_VDPAU_VIDEO_422
695 && filter->fmt_in.video.i_chroma != VLC_CODEC_VDPAU_VIDEO_444)
698 if (filter->fmt_in.video.i_visible_width
699 != filter->fmt_out.video.i_visible_width
700 || filter->fmt_in.video.i_visible_height
701 != filter->fmt_out.video.i_visible_height
702 || filter->fmt_in.video.i_x_offset != filter->fmt_out.video.i_x_offset
703 || filter->fmt_in.video.i_y_offset != filter->fmt_out.video.i_y_offset
704 || (filter->fmt_in.video.i_sar_num * filter->fmt_out.video.i_sar_den
705 != filter->fmt_in.video.i_sar_den * filter->fmt_out.video.i_sar_num))
708 filter_sys_t *sys = malloc(sizeof (*sys));
709 if (unlikely(sys == NULL))
712 if (!vlc_fourcc_to_vdp_ycc(filter->fmt_out.video.i_chroma,
713 &sys->chroma, &sys->format))
719 filter->pf_video_filter = VideoExport_Filter;
724 static void YCbCrClose(vlc_object_t *obj)
726 filter_t *filter = (filter_t *)obj;
727 filter_sys_t *sys = filter->p_sys;
732 static const int algo_values[] = {
734 VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL,
735 VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL_SPATIAL,
738 static const char *const algo_names[] = {
739 N_("Bob"), N_("Temporal"), N_("Temporal-spatial"),
743 set_shortname(N_("VDPAU"))
744 set_description(N_("VDPAU surface conversions"))
745 set_capability("video filter2", 10)
746 set_category(CAT_VIDEO)
747 set_subcategory(SUBCAT_VIDEO_VFILTER)
748 set_callbacks(OutputOpen, OutputClose)
750 add_integer("vdpau-deinterlace",
751 VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL_SPATIAL,
752 N_("Deinterlace"), N_("Deinterlacing algorithm"), true)
753 change_integer_list(algo_values, algo_names)
754 add_bool("vdpau-ivtc", false,
755 N_("Inverse telecine"), N_("Inverse telecine"), true)
756 add_bool("vdpau-chroma-skip", false,
757 N_("Deinterlace chroma skip"),
758 N_("Whether temporal deinterlacing applies to luma only"), true)
759 add_float_with_range("vdpau-noise-reduction", 0., 0., 1.,
760 N_("Noise reduction level"), N_("Noise reduction level"), true)
761 add_integer_with_range("vdpau-scaling", 0, 0, 9,
762 N_("Scaling quality"), N_("High quality scaling level"), true)
765 set_callbacks(YCbCrOpen, YCbCrClose)