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