]> git.sesse.net Git - vlc/blob - modules/codec/avcodec/vaapi.c
c85e122d45d48a95ffa574564ce8d449709f0931
[vlc] / modules / codec / avcodec / vaapi.c
1 /*****************************************************************************
2  * vaapi.c: VAAPI helpers for the libavcodec decoder
3  *****************************************************************************
4  * Copyright (C) 2009-2010 Laurent Aimar
5  * Copyright (C) 2012-2014 RĂ©mi Denis-Courmont
6  *
7  * Authors: Laurent Aimar <fenrir_AT_ videolan _DOT_ org>
8  *
9  * This program is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software Foundation,
21  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
27
28 #include <assert.h>
29
30 #include <vlc_common.h>
31 #include <vlc_plugin.h>
32 #include <vlc_fourcc.h>
33 #include <vlc_xlib.h>
34
35 #include <libavcodec/avcodec.h>
36 #include <libavcodec/vaapi.h>
37 #include <X11/Xlib.h>
38 #include <va/va_x11.h>
39
40 #include "avcodec.h"
41 #include "va.h"
42 #include "../../video_chroma/copy.h"
43
44 #ifndef VA_SURFACE_ATTRIB_SETTABLE
45 #define vaCreateSurfaces(d, f, w, h, s, ns, a, na) \
46     vaCreateSurfaces(d, w, h, f, ns, s)
47 #endif
48
49 static int Create( vlc_va_t *, AVCodecContext *, const es_format_t * );
50 static void Delete( vlc_va_t * );
51
52 vlc_module_begin ()
53     set_description( N_("Video Acceleration (VA) API") )
54     set_capability( "hw decoder", 0 )
55     set_category( CAT_INPUT )
56     set_subcategory( SUBCAT_INPUT_VCODEC )
57     set_callbacks( Create, Delete )
58 vlc_module_end ()
59
60 typedef struct
61 {
62     VASurfaceID  i_id;
63     int          i_refcount;
64     unsigned int i_order;
65     vlc_mutex_t *p_lock;
66 } vlc_va_surface_t;
67
68 struct vlc_va_sys_t
69 {
70     Display      *p_display_x11;
71     VADisplay     p_display;
72
73     VAConfigID    i_config_id;
74     VAContextID   i_context_id;
75
76     struct vaapi_context hw_ctx;
77
78     /* */
79     vlc_mutex_t  lock;
80     int          i_surface_count;
81     unsigned int i_surface_order;
82     int          i_surface_width;
83     int          i_surface_height;
84     vlc_fourcc_t i_surface_chroma;
85
86     vlc_va_surface_t *p_surface;
87
88     VAImage      image;
89     copy_cache_t image_cache;
90
91     bool b_supports_derive;
92 };
93
94 /* */
95 static int Open( vlc_va_t *va, int i_codec_id, int i_thread_count )
96 {
97     vlc_va_sys_t *sys = calloc( 1, sizeof(*sys) );
98     if ( unlikely(sys == NULL) )
99        return VLC_ENOMEM;
100
101     VAProfile i_profile, *p_profiles_list;
102     bool b_supported_profile = false;
103     int i_profiles_nb = 0;
104     int i_surface_count;
105
106     /* */
107     switch( i_codec_id )
108     {
109     case AV_CODEC_ID_MPEG1VIDEO:
110     case AV_CODEC_ID_MPEG2VIDEO:
111         i_profile = VAProfileMPEG2Main;
112         i_surface_count = 2 + 2;
113         break;
114     case AV_CODEC_ID_MPEG4:
115         i_profile = VAProfileMPEG4AdvancedSimple;
116         i_surface_count = 2+1;
117         break;
118     case AV_CODEC_ID_WMV3:
119         i_profile = VAProfileVC1Main;
120         i_surface_count = 2+1;
121         break;
122     case AV_CODEC_ID_VC1:
123         i_profile = VAProfileVC1Advanced;
124         i_surface_count = 2+1;
125         break;
126     case AV_CODEC_ID_H264:
127         i_profile = VAProfileH264High;
128         i_surface_count = 16 + i_thread_count + 2;
129         break;;
130     default:
131         return VLC_EGENERIC;
132     }
133
134     /* */
135     va->description = NULL;
136     sys->i_config_id  = VA_INVALID_ID;
137     sys->i_context_id = VA_INVALID_ID;
138     sys->image.image_id = VA_INVALID_ID;
139
140     /* Create a VA display */
141     sys->p_display_x11 = XOpenDisplay(NULL);
142     if( !sys->p_display_x11 )
143     {
144         msg_Err( va, "Could not connect to X server" );
145         goto error;
146     }
147
148     sys->p_display = vaGetDisplay( sys->p_display_x11 );
149     if( !sys->p_display )
150     {
151         msg_Err( va, "Could not get a VAAPI device" );
152         goto error;
153     }
154
155     int major, minor;
156
157     if( vaInitialize( sys->p_display, &major, &minor ) )
158     {
159         msg_Err( va, "Failed to initialize the VAAPI device" );
160         goto error;
161     }
162
163     if( asprintf( &va->description, "VA API v%d.%d", major, minor ) < 0 )
164         va->description = NULL;
165
166     /* Check if the selected profile is supported */
167     i_profiles_nb = vaMaxNumProfiles( sys->p_display );
168     p_profiles_list = calloc( i_profiles_nb, sizeof( VAProfile ) );
169     if( !p_profiles_list )
170         goto error;
171
172     VAStatus i_status = vaQueryConfigProfiles( sys->p_display, p_profiles_list, &i_profiles_nb );
173     if ( i_status == VA_STATUS_SUCCESS )
174     {
175         for( int i = 0; i < i_profiles_nb; i++ )
176         {
177             if ( p_profiles_list[i] == i_profile )
178             {
179                 b_supported_profile = true;
180                 break;
181             }
182         }
183     }
184     free( p_profiles_list );
185     if ( !b_supported_profile )
186     {
187         msg_Dbg( va, "Codec and profile not supported by the hardware" );
188         goto error;
189     }
190
191     /* Create a VA configuration */
192     VAConfigAttrib attrib;
193     memset( &attrib, 0, sizeof(attrib) );
194     attrib.type = VAConfigAttribRTFormat;
195     if( vaGetConfigAttributes( sys->p_display,
196                                i_profile, VAEntrypointVLD, &attrib, 1 ) )
197         goto error;
198
199     /* Not sure what to do if not, I don't have a way to test */
200     if( (attrib.value & VA_RT_FORMAT_YUV420) == 0 )
201         goto error;
202     if( vaCreateConfig( sys->p_display,
203                         i_profile, VAEntrypointVLD, &attrib, 1, &sys->i_config_id ) )
204     {
205         sys->i_config_id = VA_INVALID_ID;
206         goto error;
207     }
208
209     sys->i_surface_count = i_surface_count;
210
211     sys->b_supports_derive = false;
212
213     vlc_mutex_init(&sys->lock);
214
215     va->sys = sys;
216     return VLC_SUCCESS;
217
218 error:
219     free( va->description );
220     if( sys->p_display != NULL )
221         vaTerminate( sys->p_display );
222     if( sys->p_display_x11 != NULL )
223         XCloseDisplay( sys->p_display_x11 );
224     free( sys );
225     return VLC_EGENERIC;
226 }
227
228 static void DestroySurfaces( vlc_va_sys_t *sys )
229 {
230     if( sys->image.image_id != VA_INVALID_ID )
231     {
232         CopyCleanCache( &sys->image_cache );
233         vaDestroyImage( sys->p_display, sys->image.image_id );
234     }
235     else if(sys->b_supports_derive)
236     {
237         CopyCleanCache( &sys->image_cache );
238     }
239
240     if( sys->i_context_id != VA_INVALID_ID )
241         vaDestroyContext( sys->p_display, sys->i_context_id );
242
243     for( int i = 0; i < sys->i_surface_count && sys->p_surface; i++ )
244     {
245         vlc_va_surface_t *p_surface = &sys->p_surface[i];
246
247         if( p_surface->i_id != VA_INVALID_SURFACE )
248             vaDestroySurfaces( sys->p_display, &p_surface->i_id, 1 );
249     }
250     free( sys->p_surface );
251
252     /* */
253     sys->image.image_id = VA_INVALID_ID;
254     sys->i_context_id = VA_INVALID_ID;
255     sys->p_surface = NULL;
256     sys->i_surface_width = 0;
257     sys->i_surface_height = 0;
258     vlc_mutex_destroy(&sys->lock);
259 }
260
261 static int CreateSurfaces( vlc_va_sys_t *sys, void **pp_hw_ctx, vlc_fourcc_t *pi_chroma,
262                            int i_width, int i_height )
263 {
264     assert( i_width > 0 && i_height > 0 );
265
266     /* */
267     sys->p_surface = calloc( sys->i_surface_count, sizeof(*sys->p_surface) );
268     if( !sys->p_surface )
269         return VLC_EGENERIC;
270     sys->image.image_id = VA_INVALID_ID;
271     sys->i_context_id   = VA_INVALID_ID;
272
273     /* Create surfaces */
274     VASurfaceID pi_surface_id[sys->i_surface_count];
275     if( vaCreateSurfaces( sys->p_display, VA_RT_FORMAT_YUV420, i_width, i_height,
276                           pi_surface_id, sys->i_surface_count, NULL, 0 ) )
277     {
278         for( int i = 0; i < sys->i_surface_count; i++ )
279             sys->p_surface[i].i_id = VA_INVALID_SURFACE;
280         goto error;
281     }
282
283     for( int i = 0; i < sys->i_surface_count; i++ )
284     {
285         vlc_va_surface_t *p_surface = &sys->p_surface[i];
286
287         p_surface->i_id = pi_surface_id[i];
288         p_surface->i_refcount = 0;
289         p_surface->i_order = 0;
290         p_surface->p_lock = &sys->lock;
291     }
292
293     /* Create a context */
294     if( vaCreateContext( sys->p_display, sys->i_config_id,
295                          i_width, i_height, VA_PROGRESSIVE,
296                          pi_surface_id, sys->i_surface_count, &sys->i_context_id ) )
297     {
298         sys->i_context_id = VA_INVALID_ID;
299         goto error;
300     }
301
302     /* Find and create a supported image chroma */
303     int i_fmt_count = vaMaxNumImageFormats( sys->p_display );
304     VAImageFormat *p_fmt = calloc( i_fmt_count, sizeof(*p_fmt) );
305     if( !p_fmt )
306         goto error;
307
308     if( vaQueryImageFormats( sys->p_display, p_fmt, &i_fmt_count ) )
309     {
310         free( p_fmt );
311         goto error;
312     }
313
314     VAImage test_image;
315     if(vaDeriveImage(sys->p_display, pi_surface_id[0], &test_image) == VA_STATUS_SUCCESS)
316     {
317         sys->b_supports_derive = true;
318         vaDestroyImage(sys->p_display, test_image.image_id);
319     }
320
321     vlc_fourcc_t  i_chroma = 0;
322     VAImageFormat fmt;
323     for( int i = 0; i < i_fmt_count; i++ )
324     {
325         if( p_fmt[i].fourcc == VA_FOURCC( 'Y', 'V', '1', '2' ) ||
326             p_fmt[i].fourcc == VA_FOURCC( 'I', '4', '2', '0' ) ||
327             p_fmt[i].fourcc == VA_FOURCC( 'N', 'V', '1', '2' ) )
328         {
329             if( vaCreateImage(  sys->p_display, &p_fmt[i], i_width, i_height, &sys->image ) )
330             {
331                 sys->image.image_id = VA_INVALID_ID;
332                 continue;
333             }
334             /* Validate that vaGetImage works with this format */
335             if( vaGetImage( sys->p_display, pi_surface_id[0],
336                             0, 0, i_width, i_height,
337                             sys->image.image_id) )
338             {
339                 vaDestroyImage( sys->p_display, sys->image.image_id );
340                 sys->image.image_id = VA_INVALID_ID;
341                 continue;
342             }
343
344             i_chroma = VLC_CODEC_YV12;
345             fmt = p_fmt[i];
346             break;
347         }
348     }
349     free( p_fmt );
350     if( !i_chroma )
351         goto error;
352     *pi_chroma = i_chroma;
353
354     if(sys->b_supports_derive)
355     {
356         vaDestroyImage( sys->p_display, sys->image.image_id );
357         sys->image.image_id = VA_INVALID_ID;
358     }
359
360     if( unlikely(CopyInitCache( &sys->image_cache, i_width )) )
361         goto error;
362
363     /* Setup the ffmpeg hardware context */
364     *pp_hw_ctx = &sys->hw_ctx;
365
366     memset( &sys->hw_ctx, 0, sizeof(sys->hw_ctx) );
367     sys->hw_ctx.display    = sys->p_display;
368     sys->hw_ctx.config_id  = sys->i_config_id;
369     sys->hw_ctx.context_id = sys->i_context_id;
370
371     /* */
372     sys->i_surface_chroma = i_chroma;
373     sys->i_surface_width = i_width;
374     sys->i_surface_height = i_height;
375     return VLC_SUCCESS;
376
377 error:
378     DestroySurfaces( sys );
379     return VLC_EGENERIC;
380 }
381
382 static int Setup( vlc_va_t *va, void **pp_hw_ctx, vlc_fourcc_t *pi_chroma,
383                   int i_width, int i_height )
384 {
385     vlc_va_sys_t *sys = va->sys;
386
387     if( sys->i_surface_width == i_width &&
388         sys->i_surface_height == i_height )
389     {
390         *pp_hw_ctx = &sys->hw_ctx;
391         *pi_chroma = sys->i_surface_chroma;
392         return VLC_SUCCESS;
393     }
394
395     *pp_hw_ctx = NULL;
396     *pi_chroma = 0;
397     if( sys->i_surface_width || sys->i_surface_height )
398         DestroySurfaces( sys );
399
400     if( i_width > 0 && i_height > 0 )
401         return CreateSurfaces( sys, pp_hw_ctx, pi_chroma, i_width, i_height );
402
403     return VLC_EGENERIC;
404 }
405
406 static int Extract( vlc_va_t *va, picture_t *p_picture, void *opaque,
407                     uint8_t *data )
408 {
409     vlc_va_sys_t *sys = va->sys;
410     VASurfaceID i_surface_id = (VASurfaceID)(uintptr_t)data;
411
412 #if VA_CHECK_VERSION(0,31,0)
413     if( vaSyncSurface( sys->p_display, i_surface_id ) )
414 #else
415     if( vaSyncSurface( sys->p_display, sys->i_context_id, i_surface_id ) )
416 #endif
417         return VLC_EGENERIC;
418
419     if(sys->b_supports_derive)
420     {
421         if(vaDeriveImage(sys->p_display, i_surface_id, &(sys->image)) != VA_STATUS_SUCCESS)
422             return VLC_EGENERIC;
423     }
424     else
425     {
426         if( vaGetImage( sys->p_display, i_surface_id,
427                         0, 0, sys->i_surface_width, sys->i_surface_height,
428                         sys->image.image_id) )
429             return VLC_EGENERIC;
430     }
431
432     void *p_base;
433     if( vaMapBuffer( sys->p_display, sys->image.buf, &p_base ) )
434         return VLC_EGENERIC;
435
436     const uint32_t i_fourcc = sys->image.format.fourcc;
437     if( i_fourcc == VA_FOURCC('Y','V','1','2') ||
438         i_fourcc == VA_FOURCC('I','4','2','0') )
439     {
440         bool b_swap_uv = i_fourcc == VA_FOURCC('I','4','2','0');
441         uint8_t *pp_plane[3];
442         size_t  pi_pitch[3];
443
444         for( int i = 0; i < 3; i++ )
445         {
446             const int i_src_plane = (b_swap_uv && i != 0) ?  (3 - i) : i;
447             pp_plane[i] = (uint8_t*)p_base + sys->image.offsets[i_src_plane];
448             pi_pitch[i] = sys->image.pitches[i_src_plane];
449         }
450         CopyFromYv12( p_picture, pp_plane, pi_pitch,
451                       sys->i_surface_width,
452                       sys->i_surface_height,
453                       &sys->image_cache );
454     }
455     else
456     {
457         assert( i_fourcc == VA_FOURCC('N','V','1','2') );
458         uint8_t *pp_plane[2];
459         size_t  pi_pitch[2];
460
461         for( int i = 0; i < 2; i++ )
462         {
463             pp_plane[i] = (uint8_t*)p_base + sys->image.offsets[i];
464             pi_pitch[i] = sys->image.pitches[i];
465         }
466         CopyFromNv12( p_picture, pp_plane, pi_pitch,
467                       sys->i_surface_width,
468                       sys->i_surface_height,
469                       &sys->image_cache );
470     }
471
472     if( vaUnmapBuffer( sys->p_display, sys->image.buf ) )
473         return VLC_EGENERIC;
474
475     if(sys->b_supports_derive)
476     {
477         vaDestroyImage( sys->p_display, sys->image.image_id );
478         sys->image.image_id = VA_INVALID_ID;
479     }
480     (void) opaque;
481     return VLC_SUCCESS;
482 }
483
484 static int Get( vlc_va_t *va, void **opaque, uint8_t **data )
485 {
486     vlc_va_sys_t *sys = va->sys;
487     int i_old;
488     int i;
489
490     vlc_mutex_lock( &sys->lock );
491     /* Grab an unused surface, in case none are, try the oldest
492      * XXX using the oldest is a workaround in case a problem happens with ffmpeg */
493     for( i = 0, i_old = 0; i < sys->i_surface_count; i++ )
494     {
495         vlc_va_surface_t *p_surface = &sys->p_surface[i];
496
497         if( !p_surface->i_refcount )
498             break;
499
500         if( p_surface->i_order < sys->p_surface[i_old].i_order )
501             i_old = i;
502     }
503     if( i >= sys->i_surface_count )
504         i = i_old;
505     vlc_mutex_unlock( &sys->lock );
506
507     vlc_va_surface_t *p_surface = &sys->p_surface[i];
508
509     p_surface->i_refcount = 1;
510     p_surface->i_order = sys->i_surface_order++;
511     *data = (void *)(uintptr_t)p_surface->i_id;
512     *opaque = p_surface;
513     return VLC_SUCCESS;
514 }
515
516 static void Release( void *opaque, uint8_t *data )
517 {
518     vlc_va_surface_t *p_surface = opaque;
519
520     vlc_mutex_lock( p_surface->p_lock );
521     p_surface->i_refcount--;
522     vlc_mutex_unlock( p_surface->p_lock );
523     (void) data;
524 }
525
526 static void Close( vlc_va_sys_t *sys )
527 {
528     if( sys->i_surface_width || sys->i_surface_height )
529         DestroySurfaces( sys );
530
531     if( sys->i_config_id != VA_INVALID_ID )
532         vaDestroyConfig( sys->p_display, sys->i_config_id );
533     vaTerminate( sys->p_display );
534     XCloseDisplay( sys->p_display_x11 );
535 }
536
537 static void Delete( vlc_va_t *va )
538 {
539     vlc_va_sys_t *sys = va->sys;
540     Close( sys );
541     free( va->description );
542     free( sys );
543 }
544
545 static int Create( vlc_va_t *p_va, AVCodecContext *ctx,
546                    const es_format_t *fmt )
547 {
548     if( !vlc_xlib_init( VLC_OBJECT(p_va) ) )
549     {
550         msg_Warn( p_va, "Ignoring VA API" );
551         return VLC_EGENERIC;
552     }
553
554     (void) fmt;
555
556     int err = Open( p_va, ctx->codec_id, ctx->thread_count );
557     if( err )
558         return err;
559
560     /* Only VLD supported */
561     p_va->pix_fmt = PIX_FMT_VAAPI_VLD;
562     p_va->setup = Setup;
563     p_va->get = Get;
564     p_va->release = Release;
565     p_va->extract = Extract;
566     return VLC_SUCCESS;
567 }