1 /*****************************************************************************
2  * chroma.c: VLC picture import into VDPAU
3  *****************************************************************************
4  * Copyright (C) 2013 Rémi Denis-Courmont
5  *
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.
10  *
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.
15  *
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  *****************************************************************************/
20 
21 #ifdef HAVE_CONFIG_H
22 # include "config.h"
23 #endif
24 
25 #include <stdlib.h>
26 #include <inttypes.h>
27 #include <assert.h>
28 
29 #include <vlc_common.h>
30 #include <vlc_plugin.h>
31 #include <vlc_filter.h>
32 #include <vlc_picture.h>
33 #include "vlc_vdpau.h"
34 
35 /* Picture history as recommended by VDPAU documentation */
36 #define MAX_PAST   2
37 #define MAX_FUTURE 1
38 
39 struct filter_sys_t
40 {
41     vdp_t *vdp;
42     VdpDevice device;
43     VdpVideoMixer mixer;
44     VdpChromaType chroma;
45     VdpYCbCrFormat format;
46 
47     struct
48     {
49         vlc_vdp_video_field_t *field;
50         mtime_t date;
51         bool force;
52     } history[MAX_PAST + 1 + MAX_FUTURE];
53 
54     struct
55     {
56         float brightness;
57         float contrast;
58         float saturation;
59         float hue;
60     } procamp;
61 };
62 
63 /** Initialize the colour space conversion matrix */
MixerSetupColors(filter_t * filter,const VdpProcamp * procamp,VdpCSCMatrix * restrict csc)64 static VdpStatus MixerSetupColors(filter_t *filter, const VdpProcamp *procamp,
65                                   VdpCSCMatrix *restrict csc)
66 {
67     filter_sys_t *sys = filter->p_sys;
68     VdpStatus err;
69     /* XXX: add some margin for padding... */
70     VdpColorStandard std;
71 
72     switch (filter->fmt_in.video.space)
73     {
74         case COLOR_SPACE_BT601:
75             std = VDP_COLOR_STANDARD_ITUR_BT_601;
76             break;
77         case COLOR_SPACE_BT709:
78             std = VDP_COLOR_STANDARD_ITUR_BT_709;
79             break;
80         default:
81             if (filter->fmt_in.video.i_height >= 720)
82                 std = VDP_COLOR_STANDARD_ITUR_BT_709;
83             else
84                 std = VDP_COLOR_STANDARD_ITUR_BT_601;
85     }
86 
87     err = vdp_generate_csc_matrix(sys->vdp, procamp, std, csc);
88     if (err != VDP_STATUS_OK)
89     {
90         msg_Err(filter, "video %s failure: %s", "color space matrix",
91                 vdp_get_error_string(sys->vdp, err));
92         return err;
93     }
94 
95     if (procamp != NULL)
96     {
97         sys->procamp.brightness = procamp->brightness;
98         sys->procamp.contrast = procamp->contrast;
99         sys->procamp.saturation = procamp->saturation;
100         sys->procamp.hue = procamp->hue;
101     }
102     else
103     {
104         sys->procamp.brightness = 0.f;
105         sys->procamp.contrast = 1.f;
106         sys->procamp.saturation = 1.f;
107         sys->procamp.hue = 0.f;
108     }
109     return VDP_STATUS_OK;
110 }
111 
112 /** Create VDPAU video mixer */
MixerCreate(filter_t * filter,bool import)113 static VdpVideoMixer MixerCreate(filter_t *filter, bool import)
114 {
115     filter_sys_t *sys = filter->p_sys;
116     VdpVideoMixer mixer;
117     VdpStatus err;
118     VdpBool ok;
119 
120     /* Check for potentially useful features */
121     VdpVideoMixerFeature featv[5];
122     unsigned featc = 0;
123 
124     int algo = var_InheritInteger(filter, "vdpau-deinterlace");
125     bool ivtc = false;
126     if (algo == VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL_SPATIAL)
127     {
128         err = vdp_video_mixer_query_feature_support(sys->vdp, sys->device,
129                                                     algo, &ok);
130         if (err == VDP_STATUS_OK && ok == VDP_TRUE)
131             msg_Dbg(filter, "using video mixer %s feature",
132                     "temporal-spatial deinterlace");
133         else
134             algo = VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL; /* fallback */
135     }
136     if (algo == VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL)
137     {
138         err = vdp_video_mixer_query_feature_support(sys->vdp, sys->device,
139                                                     algo, &ok);
140         if (err == VDP_STATUS_OK && ok == VDP_TRUE)
141             msg_Dbg(filter, "using video mixer %s feature",
142                     "temporal deinterlace");
143         else
144             algo = -1;
145     }
146     if (algo >= 0)
147     {
148         featv[featc++] = algo;
149         ivtc = var_InheritBool(filter, "vdpau-ivtc");
150         if (ivtc)
151         {
152             err = vdp_video_mixer_query_feature_support(sys->vdp, sys->device,
153                                 VDP_VIDEO_MIXER_FEATURE_INVERSE_TELECINE, &ok);
154             if (err == VDP_STATUS_OK && ok == VDP_TRUE)
155                 msg_Dbg(filter, "using video mixer %s feature",
156                         "inverse telecine");
157             featv[featc++] = VDP_VIDEO_MIXER_FEATURE_INVERSE_TELECINE;
158         }
159     }
160 
161     const float noise = var_InheritFloat(filter, "vdpau-noise-reduction");
162     if (noise > 0.f)
163     {
164         err = vdp_video_mixer_query_feature_support(sys->vdp, sys->device,
165                                  VDP_VIDEO_MIXER_FEATURE_NOISE_REDUCTION, &ok);
166         if (err == VDP_STATUS_OK && ok == VDP_TRUE)
167         {
168             msg_Dbg(filter, "using video mixer %s feature", "noise reduction");
169             featv[featc++] = VDP_VIDEO_MIXER_FEATURE_NOISE_REDUCTION;
170         }
171     }
172 
173     err = vdp_video_mixer_query_feature_support(sys->vdp, sys->device,
174                                        VDP_VIDEO_MIXER_FEATURE_SHARPNESS, &ok);
175     if (err == VDP_STATUS_OK && ok == VDP_TRUE)
176     {
177         msg_Dbg(filter, "using video mixer %s feature", "sharpness");
178         featv[featc++] = VDP_VIDEO_MIXER_FEATURE_SHARPNESS;
179     }
180 
181     const int offset = VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L1 - 1;
182     unsigned level = var_InheritInteger(filter, "vdpau-scaling");
183     while (level > 0)
184     {
185 
186         err = vdp_video_mixer_query_feature_support(sys->vdp, sys->device,
187                                                     offset + level, &ok);
188         if (err == VDP_STATUS_OK && ok == VDP_TRUE)
189         {
190             msg_Dbg(filter, "using video mixer high quality scaling L%u",
191                     level);
192             featv[featc++] = offset + level;
193             break;
194         }
195         level--; /* fallback to lower quality */
196     }
197 
198     /* Create the mixer */
199     VdpVideoMixerParameter parms[3] = {
200         VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_WIDTH,
201         VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_HEIGHT,
202         VDP_VIDEO_MIXER_PARAMETER_CHROMA_TYPE,
203     };
204     uint32_t width = filter->fmt_in.video.i_width;
205     uint32_t height = import ? filter->fmt_in.video.i_visible_height
206                              : filter->fmt_in.video.i_height;
207     const void *values[3] = { &width, &height, &sys->chroma, };
208 
209     err = vdp_video_mixer_create(sys->vdp, sys->device, featc, featv,
210                                  3, parms, values, &mixer);
211     if (err != VDP_STATUS_OK)
212     {
213         msg_Err(filter, "video %s %s failure: %s", "mixer", "creation",
214                 vdp_get_error_string(sys->vdp, err));
215         return VDP_INVALID_HANDLE;
216     }
217 
218     msg_Dbg(filter, "using video mixer %"PRIu32, mixer);
219 
220     /* Set initial features and attributes */
221     VdpVideoMixerAttribute attrv[3];
222     const void *valv[3];
223     unsigned attrc = 0;
224     VdpCSCMatrix csc;
225     uint8_t chroma_skip;
226 
227     featc = 0;
228 
229     if (MixerSetupColors(filter, NULL, &csc) == VDP_STATUS_OK)
230     {
231         attrv[attrc] = VDP_VIDEO_MIXER_ATTRIBUTE_CSC_MATRIX;
232         valv[attrc] = &csc;
233         attrc++;
234     }
235 
236     if (algo >= 0)
237     {
238         featv[featc++] = algo;
239         if (ivtc)
240             featv[featc++] = VDP_VIDEO_MIXER_FEATURE_INVERSE_TELECINE;
241 
242         chroma_skip = var_InheritBool(filter, "vdpau-chroma-skip");
243         attrv[attrc] = VDP_VIDEO_MIXER_ATTRIBUTE_SKIP_CHROMA_DEINTERLACE;
244         valv[attrc] = &chroma_skip;
245         attrc++;
246     }
247 
248     if (noise > 0.f)
249     {
250         featv[featc++] = VDP_VIDEO_MIXER_FEATURE_NOISE_REDUCTION;
251 
252         attrv[attrc] = VDP_VIDEO_MIXER_ATTRIBUTE_NOISE_REDUCTION_LEVEL;
253         valv[attrc] = &noise;
254         attrc++;
255     }
256 
257     if (level > 0)
258         featv[featc++] = offset + level;
259 
260     if (featc > 0)
261     {
262         VdpBool enablev[featc];
263 
264         for (unsigned i = 0; i < featc; i++)
265             enablev[i] = VDP_TRUE;
266 
267         err = vdp_video_mixer_set_feature_enables(sys->vdp, mixer,
268                                                   featc, featv, enablev);
269         if (err != VDP_STATUS_OK)
270             msg_Err(filter, "video %s %s failure: %s", "mixer", "features",
271                     vdp_get_error_string(sys->vdp, err));
272     }
273 
274     if (attrc > 0)
275     {
276         err = vdp_video_mixer_set_attribute_values(sys->vdp, mixer,
277                                                    attrc, attrv, valv);
278         if (err != VDP_STATUS_OK)
279             msg_Err(filter, "video %s %s failure: %s", "mixer", "attributes",
280                     vdp_get_error_string(sys->vdp, err));
281     }
282 
283     return mixer;
284 }
285 
Flush(filter_t * filter)286 static void Flush(filter_t *filter)
287 {
288     filter_sys_t *sys = filter->p_sys;
289 
290     for (unsigned i = 0; i < MAX_PAST + MAX_FUTURE; i++)
291         if (sys->history[i].field != NULL)
292         {
293             vlc_vdp_video_destroy(sys->history[i].field);
294             sys->history[i].field = NULL;
295         }
296 }
297 
298 /** Export a VDPAU video surface picture to a normal VLC picture */
VideoExport(filter_t * filter,picture_t * src,picture_t * dst)299 static picture_t *VideoExport(filter_t *filter, picture_t *src, picture_t *dst)
300 {
301     filter_sys_t *sys = filter->p_sys;
302     vlc_vdp_video_field_t *field = (vlc_vdp_video_field_t *)src->context;
303     vlc_vdp_video_frame_t *psys = field->frame;
304     VdpStatus err;
305     VdpVideoSurface surface = psys->surface;
306     void *planes[3];
307     uint32_t pitches[3];
308 
309     picture_CopyProperties(dst, src);
310 
311     for (int i = 0; i < dst->i_planes; i++)
312     {
313         planes[i] = dst->p[i].p_pixels;
314         pitches[i] = dst->p[i].i_pitch;
315     }
316     if (dst->format.i_chroma == VLC_CODEC_I420
317      || dst->format.i_chroma == VLC_CODEC_I422
318      || dst->format.i_chroma == VLC_CODEC_I444)
319     {
320         planes[1] = dst->p[2].p_pixels;
321         planes[2] = dst->p[1].p_pixels;
322         pitches[1] = dst->p[2].i_pitch;
323         pitches[2] = dst->p[1].i_pitch;
324     }
325     err = vdp_video_surface_get_bits_y_cb_cr(psys->vdp, surface, sys->format,
326                                              planes, pitches);
327     if (err != VDP_STATUS_OK)
328     {
329         msg_Err(filter, "video %s %s failure: %s", "surface", "export",
330                 vdp_get_error_string(psys->vdp, err));
331         picture_Release(dst);
332         dst = NULL;
333     }
334     picture_Release(src);
335     return dst;
336 }
337 
338 /** Import VLC picture into VDPAU video surface */
VideoImport(filter_t * filter,picture_t * src)339 static picture_t *VideoImport(filter_t *filter, picture_t *src)
340 {
341     filter_sys_t *sys = filter->p_sys;
342     VdpVideoSurface surface;
343     VdpStatus err;
344 
345     if (sys->vdp == NULL)
346         goto drop;
347 
348     /* Create surface (TODO: reuse?) */
349     err = vdp_video_surface_create(sys->vdp, sys->device, sys->chroma,
350                                    filter->fmt_in.video.i_width,
351                                    filter->fmt_in.video.i_visible_height,
352                                    &surface);
353     if (err != VDP_STATUS_OK)
354     {
355         msg_Err(filter, "video %s %s failure: %s", "surface", "creation",
356                 vdp_get_error_string(sys->vdp, err));
357         goto drop;
358     }
359 
360     /* Put bits */
361     const void *planes[3];
362     uint32_t pitches[3];
363     for (int i = 0; i < src->i_planes; i++)
364     {
365         planes[i] = src->p[i].p_pixels
366                   + filter->fmt_in.video.i_y_offset * src->p[i].i_pitch;
367         pitches[i] = src->p[i].i_pitch;
368     }
369     if (src->format.i_chroma == VLC_CODEC_I420
370      || src->format.i_chroma == VLC_CODEC_I422
371      || src->format.i_chroma == VLC_CODEC_I444)
372     {
373         planes[1] = src->p[2].p_pixels;
374         planes[2] = src->p[1].p_pixels;
375         pitches[1] = src->p[2].i_pitch;
376         pitches[2] = src->p[1].i_pitch;
377     }
378     if (src->format.i_chroma == VLC_CODEC_I420
379      || src->format.i_chroma == VLC_CODEC_YV12
380      || src->format.i_chroma == VLC_CODEC_NV12)
381     {
382         for (int i = 1; i < src->i_planes; i++)
383             planes[i] = ((const uint8_t *)planes[i])
384                 + (filter->fmt_in.video.i_y_offset / 2) * src->p[i].i_pitch;
385     }
386 
387     err = vdp_video_surface_put_bits_y_cb_cr(sys->vdp, surface, sys->format,
388                                              planes, pitches);
389     if (err != VDP_STATUS_OK)
390     {
391         msg_Err(filter, "video %s %s failure: %s", "surface", "import",
392                 vdp_get_error_string(sys->vdp, err));
393         goto error;
394     }
395 
396     /* Wrap surface into a picture */
397     video_format_t fmt = src->format;
398 
399     switch (sys->chroma)
400     {
401         case VDP_CHROMA_TYPE_420:
402             fmt.i_chroma = VLC_CODEC_VDPAU_VIDEO_420;
403             break;
404         case VDP_CHROMA_TYPE_422:
405             fmt.i_chroma = VLC_CODEC_VDPAU_VIDEO_422;
406             break;
407         case VDP_CHROMA_TYPE_444:
408             fmt.i_chroma = VLC_CODEC_VDPAU_VIDEO_444;
409             break;
410         default:
411             vlc_assert_unreachable();
412     }
413 
414 
415     picture_t *dst = picture_NewFromFormat(&fmt);
416     if (unlikely(dst == NULL))
417         goto error;
418     picture_CopyProperties(dst, src);
419     picture_Release(src);
420 
421     err = vlc_vdp_video_attach(sys->vdp, surface, dst);
422     if (unlikely(err != VDP_STATUS_OK))
423     {
424         picture_Release(dst);
425         dst = NULL;
426     }
427     return dst;
428 error:
429     vdp_video_surface_destroy(sys->vdp, surface);
430 drop:
431     picture_Release(src);
432     return NULL;
433 }
434 
Render(filter_t * filter,picture_t * src,bool import)435 static picture_t *Render(filter_t *filter, picture_t *src, bool import)
436 {
437     filter_sys_t *sys = filter->p_sys;
438     picture_t *dst = NULL;
439     VdpStatus err;
440 
441     if (unlikely(src->context == NULL))
442     {
443         msg_Err(filter, "corrupt VDPAU video surface %p", (void *)src);
444         picture_Release(src);
445         return NULL;
446     }
447 
448     /* Corner case: different VDPAU instances decoding and rendering */
449     vlc_vdp_video_field_t *field = (vlc_vdp_video_field_t *)src->context;
450     if (field->frame->vdp != sys->vdp)
451     {
452         video_format_t fmt = src->format;
453         switch (sys->chroma)
454         {
455              case VDP_CHROMA_TYPE_420: fmt.i_chroma = VLC_CODEC_NV12; break;
456              case VDP_CHROMA_TYPE_422: fmt.i_chroma = VLC_CODEC_UYVY; break;
457              case VDP_CHROMA_TYPE_444: fmt.i_chroma = VLC_CODEC_NV24; break;
458              default: vlc_assert_unreachable();
459         }
460 
461         picture_t *pic = picture_NewFromFormat(&fmt);
462         if (likely(pic != NULL))
463         {
464             pic = VideoExport(filter, src, pic);
465             if (pic != NULL)
466                 src = VideoImport(filter, pic);
467             else
468                 src = NULL;
469         }
470         else
471         {
472             picture_Release(src);
473             src = NULL;
474         }
475     }
476 
477     /* Update history and take "present" picture field */
478     if (likely(src != NULL))
479     {
480         sys->history[MAX_PAST + MAX_FUTURE].field =
481             vlc_vdp_video_copy((vlc_vdp_video_field_t *)src->context);
482         sys->history[MAX_PAST + MAX_FUTURE].date = src->date;
483         sys->history[MAX_PAST + MAX_FUTURE].force = src->b_force;
484         picture_Release(src);
485     }
486     else
487     {
488         sys->history[MAX_PAST + MAX_FUTURE].field = NULL;
489         sys->history[MAX_PAST + MAX_FUTURE].force = false;
490     }
491 
492     vlc_vdp_video_field_t *f = sys->history[MAX_PAST].field;
493     if (f == NULL)
494     {   /* There is no present field, probably just starting playback. */
495         if (!sys->history[MAX_PAST + MAX_FUTURE].force)
496             goto skip;
497 
498         /* If the picture is forced, ignore deinterlacing and fast forward. */
499         /* FIXME: Remove the forced hack pictures in video output core and
500          * allow the last field of a video to be rendered properly. */
501         while (sys->history[MAX_PAST].field == NULL)
502         {
503             f = sys->history[0].field;
504             if (f != NULL)
505                 vlc_vdp_video_destroy(f);
506 
507             memmove(sys->history, sys->history + 1,
508                     sizeof (sys->history[0]) * (MAX_PAST + MAX_FUTURE));
509             sys->history[MAX_PAST + MAX_FUTURE].field = NULL;
510         }
511         f = sys->history[MAX_PAST].field;
512     }
513 
514     /* Get a VLC picture for a VDPAU output surface */
515     dst = filter_NewPicture(filter);
516     if (dst == NULL)
517         goto skip;
518 
519     assert(dst->p_sys != NULL && dst->p_sys->vdp ==sys->vdp);
520     dst->date = sys->history[MAX_PAST].date;
521     dst->b_force = sys->history[MAX_PAST].force;
522 
523     /* Enable/Disable features */
524     const VdpVideoMixerFeature features[] = {
525         VDP_VIDEO_MIXER_FEATURE_SHARPNESS,
526     };
527     const VdpBool enables[] = {
528         f->sharpen != 0.f,
529     };
530 
531     err = vdp_video_mixer_set_feature_enables(sys->vdp, sys->mixer,
532                   sizeof (features) / sizeof (features[0]), features, enables);
533     if (err != VDP_STATUS_OK)
534         msg_Err(filter, "video %s %s failure: %s", "mixer", "features",
535                 vdp_get_error_string(sys->vdp, err));
536 
537     /* Configure mixer depending on upstream video filters */
538     VdpVideoMixerAttribute attrs[2] = {
539         VDP_VIDEO_MIXER_ATTRIBUTE_SHARPNESS_LEVEL,
540     };
541     const void *values[2] = {
542         &f->sharpen,
543     };
544     unsigned count = 1;
545     VdpCSCMatrix csc;
546 
547     if ((sys->procamp.brightness != f->procamp.brightness
548       || sys->procamp.contrast != f->procamp.contrast
549       || sys->procamp.saturation != f->procamp.saturation
550       || sys->procamp.hue != f->procamp.hue)
551      && (MixerSetupColors(filter, &f->procamp, &csc) == VDP_STATUS_OK))
552     {
553         attrs[count] = VDP_VIDEO_MIXER_ATTRIBUTE_CSC_MATRIX;
554         values[count] = &csc;
555         count++;
556     }
557 
558     err = vdp_video_mixer_set_attribute_values(sys->vdp, sys->mixer,
559                                                count, attrs, values);
560     if (err != VDP_STATUS_OK)
561         msg_Err(filter, "video %s %s failure: %s", "mixer", "attributes",
562                 vdp_get_error_string(sys->vdp, err));
563 
564     /* Check video orientation, allocate intermediate surface if needed */
565     bool swap = false;
566     bool hflip = false, vflip = false;
567 
568     if (filter->fmt_in.video.orientation != filter->fmt_out.video.orientation)
569     {
570         assert(filter->fmt_out.video.orientation == ORIENT_TOP_LEFT);
571         swap = ORIENT_IS_SWAP(filter->fmt_in.video.orientation);
572         switch (filter->fmt_in.video.orientation)
573         {
574             case ORIENT_TOP_LEFT:
575             case ORIENT_RIGHT_TOP:
576                 break;
577             case ORIENT_TOP_RIGHT:
578             case ORIENT_RIGHT_BOTTOM:
579                 hflip = true;
580                 break;
581             case ORIENT_BOTTOM_LEFT:
582             case ORIENT_LEFT_TOP:
583                 vflip = true;
584                 break;
585             case ORIENT_BOTTOM_RIGHT:
586             case ORIENT_LEFT_BOTTOM:
587                 vflip = hflip = true;
588                 break;
589         }
590     }
591 
592     VdpOutputSurface output = dst->p_sys->surface;
593 
594     if (swap)
595     {
596         VdpRGBAFormat fmt;
597         uint32_t width, height;
598 
599         err = vdp_output_surface_get_parameters(sys->vdp, output,
600                                                 &fmt, &width, &height);
601         if (err != VDP_STATUS_OK)
602         {
603             msg_Err(filter, "output %s %s failure: %s", "surface", "query",
604                     vdp_get_error_string(sys->vdp, err));
605             goto error;
606         }
607 
608         err = vdp_output_surface_create(sys->vdp, sys->device,
609                                         fmt, height, width, &output);
610         if (err != VDP_STATUS_OK)
611         {
612             msg_Err(filter, "output %s %s failure: %s", "surface", "creation",
613                     vdp_get_error_string(sys->vdp, err));
614             goto error;
615         }
616     }
617 
618     /* Render video into output */
619     VdpVideoMixerPictureStructure structure = f->structure;
620     VdpVideoSurface past[MAX_PAST];
621     VdpVideoSurface surface = f->frame->surface;
622     VdpVideoSurface future[MAX_FUTURE];
623     VdpRect src_rect = {
624         filter->fmt_in.video.i_x_offset, filter->fmt_in.video.i_y_offset,
625         filter->fmt_in.video.i_x_offset, filter->fmt_in.video.i_y_offset,
626     };
627 
628     if (import)
629         src_rect.y0 = src_rect.y1 = 0;
630     if (hflip)
631         src_rect.x0 += filter->fmt_in.video.i_visible_width;
632     else
633         src_rect.x1 += filter->fmt_in.video.i_visible_width;
634     if (vflip)
635         src_rect.y0 += filter->fmt_in.video.i_visible_height;
636     else
637         src_rect.y1 += filter->fmt_in.video.i_visible_height;
638 
639     VdpRect dst_rect = {
640         0, 0,
641         swap ? filter->fmt_out.video.i_visible_height
642              : filter->fmt_out.video.i_visible_width,
643         swap ? filter->fmt_out.video.i_visible_width
644              : filter->fmt_out.video.i_visible_height,
645     };
646 
647     for (unsigned i = 0; i < MAX_PAST; i++)
648     {
649         f = sys->history[(MAX_PAST - 1) - i].field;
650         past[i] = (f != NULL) ? f->frame->surface : VDP_INVALID_HANDLE;
651     }
652     for (unsigned i = 0; i < MAX_FUTURE; i++)
653     {
654         f = sys->history[(MAX_PAST + 1) + i].field;
655         future[i] = (f != NULL) ? f->frame->surface : VDP_INVALID_HANDLE;
656     }
657 
658     err = vdp_video_mixer_render(sys->vdp, sys->mixer, VDP_INVALID_HANDLE,
659                                  NULL, structure,
660                                  MAX_PAST, past, surface, MAX_FUTURE, future,
661                                  &src_rect, output, &dst_rect, &dst_rect, 0,
662                                  NULL);
663     if (err != VDP_STATUS_OK)
664     {
665         msg_Err(filter, "video %s %s failure: %s", "mixer", "rendering",
666                 vdp_get_error_string(sys->vdp, err));
667         goto error;
668     }
669 
670     if (swap)
671     {
672         err = vdp_output_surface_render_output_surface(sys->vdp,
673             dst->p_sys->surface, NULL, output, NULL, NULL, NULL,
674             VDP_OUTPUT_SURFACE_RENDER_ROTATE_90);
675         vdp_output_surface_destroy(sys->vdp, output);
676         if (err != VDP_STATUS_OK)
677         {
678             msg_Err(filter, "output %s %s failure: %s", "surface", "render",
679                     vdp_get_error_string(sys->vdp, err));
680             goto error;
681         }
682     }
683 
684 skip:
685     f = sys->history[0].field;
686     if (f != NULL)
687         vlc_vdp_video_destroy(f); /* Release oldest field */
688     memmove(sys->history, sys->history + 1, /* Advance history */
689             sizeof (sys->history[0]) * (MAX_PAST + MAX_FUTURE));
690 
691     return dst;
692 error:
693     picture_Release(dst);
694     dst = NULL;
695     goto skip;
696 }
697 
VideoRender(filter_t * filter,picture_t * src)698 static picture_t *VideoRender(filter_t *filter, picture_t *src)
699 {
700     return Render(filter, src, false);
701 }
702 
703 
YCbCrRender(filter_t * filter,picture_t * src)704 static picture_t *YCbCrRender(filter_t *filter, picture_t *src)
705 {
706     src = VideoImport(filter, src);
707     return (src != NULL) ? Render(filter, src, true) : NULL;
708 }
709 
OutputOpen(vlc_object_t * obj)710 static int OutputOpen(vlc_object_t *obj)
711 {
712     filter_t *filter = (filter_t *)obj;
713 
714     if (filter->fmt_out.video.i_chroma != VLC_CODEC_VDPAU_OUTPUT)
715         return VLC_EGENERIC;
716 
717     assert(filter->fmt_out.video.orientation == ORIENT_TOP_LEFT
718         || filter->fmt_in.video.orientation == filter->fmt_out.video.orientation);
719 
720     filter_sys_t *sys = malloc(sizeof (*sys));
721     if (unlikely(sys == NULL))
722         return VLC_ENOMEM;
723 
724     filter->p_sys = sys;
725 
726     picture_t *(*video_filter)(filter_t *, picture_t *) = VideoRender;
727 
728     if (filter->fmt_in.video.i_chroma == VLC_CODEC_VDPAU_VIDEO_444)
729     {
730         sys->chroma = VDP_CHROMA_TYPE_444;
731         sys->format = VDP_YCBCR_FORMAT_NV12;
732     }
733     else
734     if (filter->fmt_in.video.i_chroma == VLC_CODEC_VDPAU_VIDEO_422)
735     {
736         sys->chroma = VDP_CHROMA_TYPE_422;
737         /* TODO: check if the drivery supports NV12 or UYVY */
738         sys->format = VDP_YCBCR_FORMAT_UYVY;
739     }
740     else
741     if (filter->fmt_in.video.i_chroma == VLC_CODEC_VDPAU_VIDEO_420)
742     {
743         sys->chroma = VDP_CHROMA_TYPE_420;
744         sys->format = VDP_YCBCR_FORMAT_NV12;
745     }
746     else
747     if (vlc_fourcc_to_vdp_ycc(filter->fmt_in.video.i_chroma,
748                               &sys->chroma, &sys->format))
749         video_filter = YCbCrRender;
750     else
751         goto error;
752 
753     /* Get the context and allocate the mixer (through *ahem* picture) */
754     picture_t *pic = filter_NewPicture(filter);
755     if (pic == NULL)
756         goto error;
757 
758     picture_sys_t *picsys = pic->p_sys;
759     assert(picsys != NULL && picsys->vdp != NULL);
760 
761     sys->vdp = vdp_hold_x11(picsys->vdp, NULL);
762     sys->device = picsys->device;
763     picture_Release(pic);
764 
765     sys->mixer = MixerCreate(filter, video_filter == YCbCrRender);
766     if (sys->mixer == VDP_INVALID_HANDLE)
767     {
768         vdp_release_x11(sys->vdp);
769         goto error;
770     }
771 
772     /* NOTE: The video mixer capabilities should be checked here, and the
773      * then video mixer set up. But:
774      * 1) The VDPAU back-end is accessible only once the first picture
775      *    gets filtered. Thus the video mixer is created later.
776      * 2) Bailing out due to insufficient capabilities would break the
777      *    video pipeline. Thus capabilities should be checked earlier. */
778 
779     for (unsigned i = 0; i < MAX_PAST + MAX_FUTURE; i++)
780         sys->history[i].field = NULL;
781 
782     sys->procamp.brightness = 0.f;
783     sys->procamp.contrast = 1.f;
784     sys->procamp.saturation = 1.f;
785     sys->procamp.hue = 0.f;
786 
787     filter->pf_video_filter = video_filter;
788     filter->pf_flush = Flush;
789     return VLC_SUCCESS;
790 error:
791     free(sys);
792     return VLC_EGENERIC;
793 }
794 
OutputClose(vlc_object_t * obj)795 static void OutputClose(vlc_object_t *obj)
796 {
797     filter_t *filter = (filter_t *)obj;
798     filter_sys_t *sys = filter->p_sys;
799 
800     Flush(filter);
801     vdp_video_mixer_destroy(sys->vdp, sys->mixer);
802     vdp_release_x11(sys->vdp);
803     free(sys);
804 }
805 
VideoExport_Filter(filter_t * filter,picture_t * src)806 static picture_t *VideoExport_Filter(filter_t *filter, picture_t *src)
807 {
808     if (unlikely(src->context == NULL))
809     {
810         msg_Err(filter, "corrupt VDPAU video surface %p", src);
811         picture_Release(src);
812         return NULL;
813     }
814 
815     picture_t *dst = filter_NewPicture(filter);
816     if (dst == NULL)
817         return NULL;
818 
819     return VideoExport(filter, src, dst);
820 }
821 
ChromaMatches(VdpChromaType vdp_type,vlc_fourcc_t vlc_chroma)822 static bool ChromaMatches(VdpChromaType vdp_type, vlc_fourcc_t vlc_chroma)
823 {
824     switch (vlc_chroma)
825     {
826         case VLC_CODEC_VDPAU_VIDEO_420:
827             return vdp_type == VDP_CHROMA_TYPE_420;
828         case VLC_CODEC_VDPAU_VIDEO_422:
829             return vdp_type == VDP_CHROMA_TYPE_422;
830         case VLC_CODEC_VDPAU_VIDEO_444:
831             return vdp_type == VDP_CHROMA_TYPE_444;
832         default:
833             return false;
834     }
835 }
836 
YCbCrOpen(vlc_object_t * obj)837 static int YCbCrOpen(vlc_object_t *obj)
838 {
839     filter_t *filter = (filter_t *)obj;
840     VdpChromaType type;
841     VdpYCbCrFormat format;
842 
843     if (!vlc_fourcc_to_vdp_ycc(filter->fmt_out.video.i_chroma, &type, &format)
844       || !ChromaMatches(type, filter->fmt_in.video.i_chroma))
845         return VLC_EGENERIC;
846 
847     if (filter->fmt_in.video.i_visible_width
848                                        != filter->fmt_out.video.i_visible_width
849      || filter->fmt_in.video.i_visible_height
850                                       != filter->fmt_out.video.i_visible_height
851      || filter->fmt_in.video.i_x_offset != filter->fmt_out.video.i_x_offset
852      || filter->fmt_in.video.i_y_offset != filter->fmt_out.video.i_y_offset
853      || (filter->fmt_in.video.i_sar_num * filter->fmt_out.video.i_sar_den
854           != filter->fmt_in.video.i_sar_den * filter->fmt_out.video.i_sar_num))
855         return VLC_EGENERIC;
856 
857     filter_sys_t *sys = malloc(sizeof (*sys));
858     if (unlikely(sys == NULL))
859         return VLC_ENOMEM;
860     sys->chroma = type;
861     sys->format = format;
862 
863     filter->pf_video_filter = VideoExport_Filter;
864     filter->p_sys = sys;
865     return VLC_SUCCESS;
866 }
867 
YCbCrClose(vlc_object_t * obj)868 static void YCbCrClose(vlc_object_t *obj)
869 {
870     filter_t *filter = (filter_t *)obj;
871     filter_sys_t *sys = filter->p_sys;
872 
873     free(sys);
874 }
875 
876 static const int algo_values[] = {
877     -1,
878     VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL,
879     VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL_SPATIAL,
880 };
881 
882 static const char *const algo_names[] = {
883     N_("Bob"), N_("Temporal"), N_("Temporal-spatial"),
884 };
885 
886 vlc_module_begin()
887     set_shortname(N_("VDPAU"))
888     set_description(N_("VDPAU surface conversions"))
889     set_capability("video converter", 10)
890     set_category(CAT_VIDEO)
891     set_subcategory(SUBCAT_VIDEO_VFILTER)
892     set_callbacks(OutputOpen, OutputClose)
893 
894     add_integer("vdpau-deinterlace",
895                 VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL_SPATIAL,
896                 N_("Deinterlace"), N_("Deinterlacing algorithm"), true)
897         change_integer_list(algo_values, algo_names)
898     add_bool("vdpau-ivtc", false,
899              N_("Inverse telecine"), N_("Inverse telecine"), true)
900     add_bool("vdpau-chroma-skip", false,
901              N_("Deinterlace chroma skip"),
902              N_("Whether temporal deinterlacing applies to luma only"), true)
903     add_float_with_range("vdpau-noise-reduction", 0., 0., 1.,
904         N_("Noise reduction level"), N_("Noise reduction level"), true)
905     add_integer_with_range("vdpau-scaling", 0, 0, 9,
906        N_("Scaling quality"), N_("High quality scaling level"), true)
907 
908     add_submodule()
909     set_callbacks(YCbCrOpen, YCbCrClose)
910 vlc_module_end()
911