1 /*****************************************************************************
2 * chroma.c: VLC picture to VAAPI surface or vice versa
3 *****************************************************************************
4 * Copyright (C) 2017 VLC authors, VideoLAN and VideoLabs
5 *
6 * Author: Victorien Le Couviour--Tuffet <victorien.lecouviour.tuffet@gmail.com>
7 *
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation; either version 2.1 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this program; if not, write to the Free Software Foundation,
20 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21 *****************************************************************************/
22
23 #ifdef HAVE_CONFIG_H
24 # include "config.h"
25 #endif
26
27 #include <assert.h>
28
29 #include <va/va.h>
30
31 #include <vlc_common.h>
32 #include <vlc_filter.h>
33 #include <vlc_plugin.h>
34
35 #include "../../video_chroma/copy.h"
36 #include "filters.h"
37
38 # define DEST_PICS_POOL_SZ 3
39
40 struct filter_sys_t
41 {
42 struct vlc_vaapi_instance *va_inst;
43 VADisplay dpy;
44 picture_pool_t * dest_pics;
45 VASurfaceID * va_surface_ids;
46 copy_cache_t cache;
47
48 bool derive_failed;
49 bool image_fallback_failed;
50 };
51
CreateFallbackImage(filter_t * filter,picture_t * src_pic,VADisplay va_dpy,VAImage * image_fallback)52 static int CreateFallbackImage(filter_t *filter, picture_t *src_pic,
53 VADisplay va_dpy, VAImage *image_fallback)
54 {
55 int count = vaMaxNumImageFormats(va_dpy);
56
57 VAImageFormat *fmts = vlc_alloc(count, sizeof (*fmts));
58 if (unlikely(fmts == NULL))
59 return VLC_ENOMEM;
60
61 if (vaQueryImageFormats(va_dpy, fmts, &count))
62 {
63 free(fmts);
64 return VLC_EGENERIC;
65 }
66
67 int i;
68 for (i = 0; i < count; i++)
69 if (fmts[i].fourcc == VA_FOURCC_NV12
70 || fmts[i].fourcc == VA_FOURCC_P010)
71 break;
72
73 int ret;
74 if ((fmts[i].fourcc == VA_FOURCC_NV12 || fmts[i].fourcc == VA_FOURCC_P010)
75 && !vlc_vaapi_CreateImage(VLC_OBJECT(filter), va_dpy, &fmts[i],
76 src_pic->format.i_width, src_pic->format.i_height,
77 image_fallback))
78 ret = VLC_SUCCESS;
79 else
80 ret = VLC_EGENERIC;
81
82 free(fmts);
83
84 return ret;
85 }
86
87 static inline void
FillPictureFromVAImage(picture_t * dest,VAImage * src_img,uint8_t * src_buf,copy_cache_t * cache)88 FillPictureFromVAImage(picture_t *dest,
89 VAImage *src_img, uint8_t *src_buf, copy_cache_t *cache)
90 {
91 const uint8_t * src_planes[2] = { src_buf + src_img->offsets[0],
92 src_buf + src_img->offsets[1] };
93 const size_t src_pitches[2] = { src_img->pitches[0],
94 src_img->pitches[1] };
95
96 switch (src_img->format.fourcc)
97 {
98 case VA_FOURCC_NV12:
99 {
100 assert(dest->format.i_chroma == VLC_CODEC_I420);
101 Copy420_SP_to_P(dest, src_planes, src_pitches, src_img->height, cache);
102 break;
103 }
104 case VA_FOURCC_P010:
105 switch (dest->format.i_chroma)
106 {
107 case VLC_CODEC_P010:
108 Copy420_SP_to_SP(dest, src_planes, src_pitches, src_img->height,
109 cache);
110 break;
111 case VLC_CODEC_I420_10L:
112 Copy420_16_SP_to_P(dest, src_planes, src_pitches,
113 src_img->height, 6, cache);
114 break;
115 default:
116 vlc_assert_unreachable();
117 }
118 break;
119 default:
120 vlc_assert_unreachable();
121 break;
122 }
123 }
124
125 static picture_t *
DownloadSurface(filter_t * filter,picture_t * src_pic)126 DownloadSurface(filter_t *filter, picture_t *src_pic)
127 {
128 filter_sys_t *const filter_sys = filter->p_sys;
129 VADisplay va_dpy = vlc_vaapi_PicGetDisplay(src_pic);
130 VAImage src_img;
131 void * src_buf;
132
133 picture_t *dest = filter_NewPicture(filter);
134 if (!dest)
135 {
136 msg_Err(filter, "filter_NewPicture failed");
137 goto ret;
138 }
139
140 VAImageID image_fallback_id = VA_INVALID_ID;
141 VASurfaceID surface = vlc_vaapi_PicGetSurface(src_pic);
142 if (vaSyncSurface(va_dpy, surface))
143 goto error;
144
145 if (filter_sys->derive_failed ||
146 vlc_vaapi_DeriveImage(VLC_OBJECT(filter), va_dpy, surface, &src_img))
147 {
148 if (filter_sys->image_fallback_failed)
149 goto error;
150
151 filter_sys->derive_failed = true;
152
153 VAImage image_fallback;
154 if (CreateFallbackImage(filter, src_pic, va_dpy, &image_fallback))
155 {
156 filter_sys->image_fallback_failed = true;
157 goto error;
158 }
159 image_fallback_id = image_fallback.image_id;
160
161 if (vaGetImage(va_dpy, surface, 0, 0, src_pic->format.i_width,
162 src_pic->format.i_height, image_fallback_id))
163 {
164 filter_sys->image_fallback_failed = true;
165 goto error;
166 }
167 src_img = image_fallback;
168 }
169
170 if (vlc_vaapi_MapBuffer(VLC_OBJECT(filter), va_dpy, src_img.buf, &src_buf))
171 goto error;
172
173 FillPictureFromVAImage(dest, &src_img, src_buf, &filter->p_sys->cache);
174
175 vlc_vaapi_UnmapBuffer(VLC_OBJECT(filter), va_dpy, src_img.buf);
176 vlc_vaapi_DestroyImage(VLC_OBJECT(filter), va_dpy, src_img.image_id);
177
178 picture_CopyProperties(dest, src_pic);
179 ret:
180 picture_Release(src_pic);
181 return dest;
182
183 error:
184 if (image_fallback_id != VA_INVALID_ID)
185 vlc_vaapi_DestroyImage(VLC_OBJECT(filter), va_dpy, image_fallback_id);
186
187 picture_Release(dest);
188 dest = NULL;
189 goto ret;
190 }
191
192 static inline void
FillVAImageFromPicture(VAImage * dest_img,uint8_t * dest_buf,picture_t * dest_pic,picture_t * src,copy_cache_t * cache)193 FillVAImageFromPicture(VAImage *dest_img, uint8_t *dest_buf,
194 picture_t *dest_pic, picture_t *src,
195 copy_cache_t *cache)
196 {
197 const uint8_t * src_planes[3] = { src->p[Y_PLANE].p_pixels,
198 src->p[U_PLANE].p_pixels,
199 src->p[V_PLANE].p_pixels };
200 const size_t src_pitches[3] = { src->p[Y_PLANE].i_pitch,
201 src->p[U_PLANE].i_pitch,
202 src->p[V_PLANE].i_pitch };
203 void *const tmp[2] = { dest_pic->p[0].p_pixels,
204 dest_pic->p[1].p_pixels };
205
206 dest_pic->p[0].p_pixels = dest_buf + dest_img->offsets[0];
207 dest_pic->p[1].p_pixels = dest_buf + dest_img->offsets[1];
208 dest_pic->p[0].i_pitch = dest_img->pitches[0];
209 dest_pic->p[1].i_pitch = dest_img->pitches[1];
210
211 switch (src->format.i_chroma)
212 {
213 case VLC_CODEC_I420:
214 assert(dest_pic->format.i_chroma == VLC_CODEC_VAAPI_420);
215 Copy420_P_to_SP(dest_pic, src_planes, src_pitches,
216 src->format.i_height, cache);
217
218 break;
219 case VLC_CODEC_I420_10L:
220 assert(dest_pic->format.i_chroma == VLC_CODEC_VAAPI_420_10BPP);
221 Copy420_16_P_to_SP(dest_pic, src_planes, src_pitches,
222 src->format.i_height, -6, cache);
223 break;
224 case VLC_CODEC_P010:
225 {
226 assert(dest_pic->format.i_chroma == VLC_CODEC_VAAPI_420_10BPP);
227 Copy420_SP_to_SP(dest_pic, src_planes, src_pitches,
228 src->format.i_height, cache);
229 break;
230 }
231 default:
232 vlc_assert_unreachable();
233 }
234
235 dest_pic->p[0].p_pixels = tmp[0];
236 dest_pic->p[1].p_pixels = tmp[1];
237 }
238
239 static picture_t *
UploadSurface(filter_t * filter,picture_t * src)240 UploadSurface(filter_t *filter, picture_t *src)
241 {
242 VADisplay const va_dpy = filter->p_sys->dpy;
243 VAImage dest_img;
244 void * dest_buf;
245 picture_t * dest_pic = picture_pool_Wait(filter->p_sys->dest_pics);
246
247 if (!dest_pic)
248 {
249 msg_Err(filter, "cannot retrieve picture from the dest pics pool");
250 goto ret;
251 }
252 vlc_vaapi_PicAttachContext(dest_pic);
253 picture_CopyProperties(dest_pic, src);
254
255 if (vlc_vaapi_DeriveImage(VLC_OBJECT(filter), va_dpy,
256 vlc_vaapi_PicGetSurface(dest_pic), &dest_img)
257 || vlc_vaapi_MapBuffer(VLC_OBJECT(filter), va_dpy,
258 dest_img.buf, &dest_buf))
259 goto error;
260
261 FillVAImageFromPicture(&dest_img, dest_buf, dest_pic,
262 src, &filter->p_sys->cache);
263
264 if (vlc_vaapi_UnmapBuffer(VLC_OBJECT(filter), va_dpy, dest_img.buf)
265 || vlc_vaapi_DestroyImage(VLC_OBJECT(filter),
266 va_dpy, dest_img.image_id))
267 goto error;
268
269 ret:
270 picture_Release(src);
271 return dest_pic;
272
273 error:
274 picture_Release(dest_pic);
275 dest_pic = NULL;
276 goto ret;
277 }
278
CheckFmt(const video_format_t * in,const video_format_t * out,bool * upload,uint8_t * pixel_bytes)279 static int CheckFmt(const video_format_t *in, const video_format_t *out,
280 bool *upload, uint8_t *pixel_bytes)
281 {
282 *pixel_bytes = 1;
283 *upload = false;
284 switch (in->i_chroma)
285 {
286 case VLC_CODEC_VAAPI_420:
287 if (out->i_chroma == VLC_CODEC_I420)
288 return VLC_SUCCESS;
289 break;
290 case VLC_CODEC_VAAPI_420_10BPP:
291 if (out->i_chroma == VLC_CODEC_P010
292 || out->i_chroma == VLC_CODEC_I420_10L)
293 {
294 *pixel_bytes = 2;
295 return VLC_SUCCESS;
296 }
297 break;
298 }
299
300 *upload = true;
301 switch (out->i_chroma)
302 {
303 case VLC_CODEC_VAAPI_420:
304 if (in->i_chroma == VLC_CODEC_I420)
305 return VLC_SUCCESS;
306 break;
307 case VLC_CODEC_VAAPI_420_10BPP:
308 if (in->i_chroma == VLC_CODEC_P010
309 || in->i_chroma == VLC_CODEC_I420_10L)
310 {
311 *pixel_bytes = 2;
312 return VLC_SUCCESS;
313 }
314 break;
315 }
316 return VLC_EGENERIC;
317 }
318
319 int
vlc_vaapi_OpenChroma(vlc_object_t * obj)320 vlc_vaapi_OpenChroma(vlc_object_t *obj)
321 {
322 filter_t *const filter = (filter_t *)obj;
323 filter_sys_t * filter_sys;
324
325 if (filter->fmt_in.video.i_height != filter->fmt_out.video.i_height
326 || filter->fmt_in.video.i_width != filter->fmt_out.video.i_width
327 || filter->fmt_in.video.orientation != filter->fmt_out.video.orientation)
328 return VLC_EGENERIC;
329
330 bool is_upload;
331 uint8_t pixel_bytes;
332 if (CheckFmt(&filter->fmt_in.video, &filter->fmt_out.video, &is_upload,
333 &pixel_bytes))
334 return VLC_EGENERIC;
335
336 filter->pf_video_filter = is_upload ? UploadSurface : DownloadSurface;
337
338 if (!(filter_sys = calloc(1, sizeof(filter_sys_t))))
339 {
340 msg_Err(obj, "unable to allocate memory");
341 return VLC_ENOMEM;
342 }
343 filter_sys->derive_failed = false;
344 filter_sys->image_fallback_failed = false;
345 if (is_upload)
346 {
347 filter_sys->va_inst = vlc_vaapi_FilterHoldInstance(filter,
348 &filter_sys->dpy);
349
350 if (filter_sys->va_inst == NULL)
351 {
352 free(filter_sys);
353 return VLC_EGENERIC;
354 }
355
356 filter_sys->dest_pics =
357 vlc_vaapi_PoolNew(obj, filter_sys->va_inst, filter_sys->dpy,
358 DEST_PICS_POOL_SZ, &filter_sys->va_surface_ids,
359 &filter->fmt_out.video, true);
360 if (!filter_sys->dest_pics)
361 {
362 vlc_vaapi_FilterReleaseInstance(filter, filter_sys->va_inst);
363 free(filter_sys);
364 return VLC_EGENERIC;
365 }
366 }
367 else
368 {
369 /* Don't fetch the vaapi instance since it may be not created yet at
370 * this point (in case of cpu rendering) */
371 filter_sys->va_inst = NULL;
372 filter_sys->dpy = NULL;
373 filter_sys->dest_pics = NULL;
374 }
375
376 if (CopyInitCache(&filter_sys->cache, filter->fmt_in.video.i_width
377 * pixel_bytes))
378 {
379 if (is_upload)
380 {
381 picture_pool_Release(filter_sys->dest_pics);
382 vlc_vaapi_FilterReleaseInstance(filter, filter_sys->va_inst);
383 }
384 free(filter_sys);
385 return VLC_EGENERIC;
386 }
387
388 filter->p_sys = filter_sys;
389 msg_Warn(obj, "Using SW chroma filter for %dx%d %4.4s -> %4.4s",
390 filter->fmt_in.video.i_width,
391 filter->fmt_in.video.i_height,
392 (const char *) &filter->fmt_in.video.i_chroma,
393 (const char *) &filter->fmt_out.video.i_chroma);
394
395 return VLC_SUCCESS;
396 }
397
398 void
vlc_vaapi_CloseChroma(vlc_object_t * obj)399 vlc_vaapi_CloseChroma(vlc_object_t *obj)
400 {
401 filter_t *filter = (filter_t *)obj;
402 filter_sys_t *const filter_sys = filter->p_sys;
403
404 if (filter_sys->dest_pics)
405 picture_pool_Release(filter_sys->dest_pics);
406 if (filter_sys->va_inst != NULL)
407 vlc_vaapi_FilterReleaseInstance(filter, filter_sys->va_inst);
408 CopyCleanCache(&filter_sys->cache);
409
410 free(filter_sys);
411 }
412