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