]> git.sesse.net Git - vlc/blob - modules/codec/avcodec/vaapi.c
avcodec: map HNM4
[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_("VA-API video decoder via X11") )
63 #elif defined (VLC_VA_BACKEND_DRM)
64     set_description( N_("VA-API video decoder via 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         free( sys );
150         return VLC_EGENERIC;
151     }
152
153     /* */
154     sys->i_config_id  = VA_INVALID_ID;
155     sys->i_context_id = VA_INVALID_ID;
156     sys->image.image_id = VA_INVALID_ID;
157
158     /* Create a VA display */
159 #ifdef VLC_VA_BACKEND_XLIB
160     sys->p_display_x11 = XOpenDisplay(NULL);
161     if( !sys->p_display_x11 )
162     {
163         msg_Err( va, "Could not connect to X server" );
164         goto error;
165     }
166
167     sys->p_display = vaGetDisplay( sys->p_display_x11 );
168 #endif
169 #ifdef VLC_VA_BACKEND_DRM
170     sys->drm_fd = vlc_open("/dev/dri/card0", O_RDWR);
171     if( sys->drm_fd == -1 )
172     {
173         msg_Err( va, "Could not access rendering device: %m" );
174         goto error;
175     }
176
177     sys->p_display = vaGetDisplayDRM( sys->drm_fd );
178 #endif
179     if( !sys->p_display )
180     {
181         msg_Err( va, "Could not get a VAAPI device" );
182         goto error;
183     }
184
185     int major, minor;
186
187     if( vaInitialize( sys->p_display, &major, &minor ) )
188     {
189         msg_Err( va, "Failed to initialize the VAAPI device" );
190         goto error;
191     }
192
193     /* Check if the selected profile is supported */
194     i_profiles_nb = vaMaxNumProfiles( sys->p_display );
195     p_profiles_list = calloc( i_profiles_nb, sizeof( VAProfile ) );
196     if( !p_profiles_list )
197         goto error;
198
199     VAStatus i_status = vaQueryConfigProfiles( sys->p_display, p_profiles_list, &i_profiles_nb );
200     if ( i_status == VA_STATUS_SUCCESS )
201     {
202         for( int i = 0; i < i_profiles_nb; i++ )
203         {
204             if ( p_profiles_list[i] == i_profile )
205             {
206                 b_supported_profile = true;
207                 break;
208             }
209         }
210     }
211     free( p_profiles_list );
212     if ( !b_supported_profile )
213     {
214         msg_Dbg( va, "Codec and profile not supported by the hardware" );
215         goto error;
216     }
217
218     /* Create a VA configuration */
219     VAConfigAttrib attrib;
220     memset( &attrib, 0, sizeof(attrib) );
221     attrib.type = VAConfigAttribRTFormat;
222     if( vaGetConfigAttributes( sys->p_display,
223                                i_profile, VAEntrypointVLD, &attrib, 1 ) )
224         goto error;
225
226     /* Not sure what to do if not, I don't have a way to test */
227     if( (attrib.value & VA_RT_FORMAT_YUV420) == 0 )
228         goto error;
229     if( vaCreateConfig( sys->p_display,
230                         i_profile, VAEntrypointVLD, &attrib, 1, &sys->i_config_id ) )
231     {
232         sys->i_config_id = VA_INVALID_ID;
233         goto error;
234     }
235
236     sys->i_surface_count = i_surface_count;
237
238     sys->b_supports_derive = false;
239
240     vlc_mutex_init(&sys->lock);
241
242     va->sys = sys;
243     va->description = vaQueryVendorString( sys->p_display );
244     return VLC_SUCCESS;
245
246 error:
247     if( sys->p_display != NULL )
248         vaTerminate( sys->p_display );
249 #ifdef VLC_VA_BACKEND_XLIB
250     if( sys->p_display_x11 != NULL )
251         XCloseDisplay( sys->p_display_x11 );
252 #endif
253 #ifdef VLC_VA_BACKEND_DRM
254     if( sys->drm_fd != -1 )
255         close( sys->drm_fd );
256 #endif
257     free( sys );
258     return VLC_EGENERIC;
259 }
260
261 static void DestroySurfaces( vlc_va_sys_t *sys )
262 {
263     if( sys->image.image_id != VA_INVALID_ID )
264     {
265         CopyCleanCache( &sys->image_cache );
266         vaDestroyImage( sys->p_display, sys->image.image_id );
267     }
268     else if(sys->b_supports_derive)
269     {
270         CopyCleanCache( &sys->image_cache );
271     }
272
273     if( sys->i_context_id != VA_INVALID_ID )
274         vaDestroyContext( sys->p_display, sys->i_context_id );
275
276     for( int i = 0; i < sys->i_surface_count && sys->p_surface; i++ )
277     {
278         vlc_va_surface_t *p_surface = &sys->p_surface[i];
279
280         if( p_surface->i_id != VA_INVALID_SURFACE )
281             vaDestroySurfaces( sys->p_display, &p_surface->i_id, 1 );
282     }
283     free( sys->p_surface );
284
285     /* */
286     sys->image.image_id = VA_INVALID_ID;
287     sys->i_context_id = VA_INVALID_ID;
288     sys->p_surface = NULL;
289     sys->i_surface_width = 0;
290     sys->i_surface_height = 0;
291     vlc_mutex_destroy(&sys->lock);
292 }
293
294 static int CreateSurfaces( vlc_va_sys_t *sys, void **pp_hw_ctx, vlc_fourcc_t *pi_chroma,
295                            int i_width, int i_height )
296 {
297     assert( i_width > 0 && i_height > 0 );
298
299     /* */
300     sys->p_surface = calloc( sys->i_surface_count, sizeof(*sys->p_surface) );
301     if( !sys->p_surface )
302         return VLC_EGENERIC;
303     sys->image.image_id = VA_INVALID_ID;
304     sys->i_context_id   = VA_INVALID_ID;
305
306     /* Create surfaces */
307     VASurfaceID pi_surface_id[sys->i_surface_count];
308     if( vaCreateSurfaces( sys->p_display, VA_RT_FORMAT_YUV420, i_width, i_height,
309                           pi_surface_id, sys->i_surface_count, NULL, 0 ) )
310     {
311         for( int i = 0; i < sys->i_surface_count; i++ )
312             sys->p_surface[i].i_id = VA_INVALID_SURFACE;
313         goto error;
314     }
315
316     for( int i = 0; i < sys->i_surface_count; i++ )
317     {
318         vlc_va_surface_t *p_surface = &sys->p_surface[i];
319
320         p_surface->i_id = pi_surface_id[i];
321         p_surface->i_refcount = 0;
322         p_surface->i_order = 0;
323         p_surface->p_lock = &sys->lock;
324     }
325
326     /* Create a context */
327     if( vaCreateContext( sys->p_display, sys->i_config_id,
328                          i_width, i_height, VA_PROGRESSIVE,
329                          pi_surface_id, sys->i_surface_count, &sys->i_context_id ) )
330     {
331         sys->i_context_id = VA_INVALID_ID;
332         goto error;
333     }
334
335     /* Find and create a supported image chroma */
336     int i_fmt_count = vaMaxNumImageFormats( sys->p_display );
337     VAImageFormat *p_fmt = calloc( i_fmt_count, sizeof(*p_fmt) );
338     if( !p_fmt )
339         goto error;
340
341     if( vaQueryImageFormats( sys->p_display, p_fmt, &i_fmt_count ) )
342     {
343         free( p_fmt );
344         goto error;
345     }
346
347     VAImage test_image;
348     vlc_fourcc_t  deriveImageFormat = 0;
349     if(vaDeriveImage(sys->p_display, pi_surface_id[0], &test_image) == VA_STATUS_SUCCESS)
350     {
351         sys->b_supports_derive = true;
352         deriveImageFormat = test_image.format.fourcc;
353         vaDestroyImage(sys->p_display, test_image.image_id);
354     }
355
356     vlc_fourcc_t  i_chroma = 0;
357     int nv12support = -1;
358     for( int i = 0; i < i_fmt_count; i++ )
359     {
360         if( p_fmt[i].fourcc == VA_FOURCC_YV12 ||
361             p_fmt[i].fourcc == VA_FOURCC_IYUV ||
362             p_fmt[i].fourcc == VA_FOURCC_NV12 )
363         {
364             if( vaCreateImage(  sys->p_display, &p_fmt[i], i_width, i_height, &sys->image ) )
365             {
366                 sys->image.image_id = VA_INVALID_ID;
367                 continue;
368             }
369             /* Validate that vaGetImage works with this format */
370             if( vaGetImage( sys->p_display, pi_surface_id[0],
371                             0, 0, i_width, i_height,
372                             sys->image.image_id) )
373             {
374                 vaDestroyImage( sys->p_display, sys->image.image_id );
375                 sys->image.image_id = VA_INVALID_ID;
376                 continue;
377             }
378
379             if( p_fmt[i].fourcc == VA_FOURCC_NV12 )
380             {
381                 /* Mark NV12 as supported, but favor other formats first */
382                 nv12support = i;
383                 vaDestroyImage( sys->p_display, sys->image.image_id );
384                 sys->image.image_id = VA_INVALID_ID;
385                 continue;
386             }
387             i_chroma = VLC_CODEC_YV12;
388             break;
389         }
390     }
391
392     if( !i_chroma && nv12support >= 0 )
393     {
394         /* only nv12 is supported, so use that format */
395         if( vaCreateImage(  sys->p_display, &p_fmt[nv12support], i_width, i_height, &sys->image ) )
396         {
397             sys->image.image_id = VA_INVALID_ID;
398         }
399         i_chroma = VLC_CODEC_YV12;
400     }
401     else if( sys->b_supports_derive && deriveImageFormat != sys->image.format.fourcc )
402     {
403         /* only use vaDerive if it's giving us a format we handle natively */
404         sys->b_supports_derive = false;
405     }
406
407     free( p_fmt );
408     if( !i_chroma )
409         goto error;
410     *pi_chroma = i_chroma;
411
412     if(sys->b_supports_derive)
413     {
414         vaDestroyImage( sys->p_display, sys->image.image_id );
415         sys->image.image_id = VA_INVALID_ID;
416     }
417
418     if( unlikely(CopyInitCache( &sys->image_cache, i_width )) )
419         goto error;
420
421     /* Setup the ffmpeg hardware context */
422     *pp_hw_ctx = &sys->hw_ctx;
423
424     memset( &sys->hw_ctx, 0, sizeof(sys->hw_ctx) );
425     sys->hw_ctx.display    = sys->p_display;
426     sys->hw_ctx.config_id  = sys->i_config_id;
427     sys->hw_ctx.context_id = sys->i_context_id;
428
429     /* */
430     sys->i_surface_chroma = i_chroma;
431     sys->i_surface_width = i_width;
432     sys->i_surface_height = i_height;
433     return VLC_SUCCESS;
434
435 error:
436     DestroySurfaces( sys );
437     return VLC_EGENERIC;
438 }
439
440 static int Setup( vlc_va_t *va, void **pp_hw_ctx, vlc_fourcc_t *pi_chroma,
441                   int i_width, int i_height )
442 {
443     vlc_va_sys_t *sys = va->sys;
444
445     if( sys->i_surface_width == i_width &&
446         sys->i_surface_height == i_height )
447     {
448         *pp_hw_ctx = &sys->hw_ctx;
449         *pi_chroma = sys->i_surface_chroma;
450         return VLC_SUCCESS;
451     }
452
453     *pp_hw_ctx = NULL;
454     *pi_chroma = 0;
455     if( sys->i_surface_width || sys->i_surface_height )
456         DestroySurfaces( sys );
457
458     if( i_width > 0 && i_height > 0 )
459         return CreateSurfaces( sys, pp_hw_ctx, pi_chroma, i_width, i_height );
460
461     return VLC_EGENERIC;
462 }
463
464 static int Extract( vlc_va_t *va, picture_t *p_picture, void *opaque,
465                     uint8_t *data )
466 {
467     vlc_va_sys_t *sys = va->sys;
468     VASurfaceID i_surface_id = (VASurfaceID)(uintptr_t)data;
469
470 #if VA_CHECK_VERSION(0,31,0)
471     if( vaSyncSurface( sys->p_display, i_surface_id ) )
472 #else
473     if( vaSyncSurface( sys->p_display, sys->i_context_id, i_surface_id ) )
474 #endif
475         return VLC_EGENERIC;
476
477     if(sys->b_supports_derive)
478     {
479         if(vaDeriveImage(sys->p_display, i_surface_id, &(sys->image)) != VA_STATUS_SUCCESS)
480             return VLC_EGENERIC;
481     }
482     else
483     {
484         if( vaGetImage( sys->p_display, i_surface_id,
485                         0, 0, sys->i_surface_width, sys->i_surface_height,
486                         sys->image.image_id) )
487             return VLC_EGENERIC;
488     }
489
490     void *p_base;
491     if( vaMapBuffer( sys->p_display, sys->image.buf, &p_base ) )
492         return VLC_EGENERIC;
493
494     const uint32_t i_fourcc = sys->image.format.fourcc;
495     if( i_fourcc == VA_FOURCC_YV12 ||
496         i_fourcc == VA_FOURCC_IYUV )
497     {
498         bool b_swap_uv = i_fourcc == VA_FOURCC_IYUV;
499         uint8_t *pp_plane[3];
500         size_t  pi_pitch[3];
501
502         for( int i = 0; i < 3; i++ )
503         {
504             const int i_src_plane = (b_swap_uv && i != 0) ?  (3 - i) : i;
505             pp_plane[i] = (uint8_t*)p_base + sys->image.offsets[i_src_plane];
506             pi_pitch[i] = sys->image.pitches[i_src_plane];
507         }
508         CopyFromYv12( p_picture, pp_plane, pi_pitch,
509                       sys->i_surface_width,
510                       sys->i_surface_height,
511                       &sys->image_cache );
512     }
513     else
514     {
515         assert( i_fourcc == VA_FOURCC_NV12 );
516         uint8_t *pp_plane[2];
517         size_t  pi_pitch[2];
518
519         for( int i = 0; i < 2; i++ )
520         {
521             pp_plane[i] = (uint8_t*)p_base + sys->image.offsets[i];
522             pi_pitch[i] = sys->image.pitches[i];
523         }
524         CopyFromNv12( p_picture, pp_plane, pi_pitch,
525                       sys->i_surface_width,
526                       sys->i_surface_height,
527                       &sys->image_cache );
528     }
529
530     if( vaUnmapBuffer( sys->p_display, sys->image.buf ) )
531         return VLC_EGENERIC;
532
533     if(sys->b_supports_derive)
534     {
535         vaDestroyImage( sys->p_display, sys->image.image_id );
536         sys->image.image_id = VA_INVALID_ID;
537     }
538     (void) opaque;
539     return VLC_SUCCESS;
540 }
541
542 static int Get( vlc_va_t *va, void **opaque, uint8_t **data )
543 {
544     vlc_va_sys_t *sys = va->sys;
545     int i_old;
546     int i;
547
548     vlc_mutex_lock( &sys->lock );
549     /* Grab an unused surface, in case none are, try the oldest
550      * XXX using the oldest is a workaround in case a problem happens with ffmpeg */
551     for( i = 0, i_old = 0; i < sys->i_surface_count; i++ )
552     {
553         vlc_va_surface_t *p_surface = &sys->p_surface[i];
554
555         if( !p_surface->i_refcount )
556             break;
557
558         if( p_surface->i_order < sys->p_surface[i_old].i_order )
559             i_old = i;
560     }
561     if( i >= sys->i_surface_count )
562         i = i_old;
563     vlc_mutex_unlock( &sys->lock );
564
565     vlc_va_surface_t *p_surface = &sys->p_surface[i];
566
567     p_surface->i_refcount = 1;
568     p_surface->i_order = sys->i_surface_order++;
569     *data = (void *)(uintptr_t)p_surface->i_id;
570     *opaque = p_surface;
571     return VLC_SUCCESS;
572 }
573
574 static void Release( void *opaque, uint8_t *data )
575 {
576     vlc_va_surface_t *p_surface = opaque;
577
578     vlc_mutex_lock( p_surface->p_lock );
579     p_surface->i_refcount--;
580     vlc_mutex_unlock( p_surface->p_lock );
581     (void) data;
582 }
583
584 static void Close( vlc_va_sys_t *sys )
585 {
586     if( sys->i_surface_width || sys->i_surface_height )
587         DestroySurfaces( sys );
588
589     if( sys->i_config_id != VA_INVALID_ID )
590         vaDestroyConfig( sys->p_display, sys->i_config_id );
591     vaTerminate( sys->p_display );
592 #ifdef VLC_VA_BACKEND_XLIB
593     XCloseDisplay( sys->p_display_x11 );
594 #endif
595 #ifdef VLC_VA_BACKEND_DRM
596     close( sys->drm_fd );
597 #endif
598 }
599
600 static void Delete( vlc_va_t *va )
601 {
602     vlc_va_sys_t *sys = va->sys;
603     Close( sys );
604     free( sys );
605 }
606
607 static int Create( vlc_va_t *p_va, AVCodecContext *ctx,
608                    const es_format_t *fmt )
609 {
610 #ifdef VLC_VA_BACKEND_XLIB
611     if( !vlc_xlib_init( VLC_OBJECT(p_va) ) )
612     {
613         msg_Warn( p_va, "Ignoring VA API" );
614         return VLC_EGENERIC;
615     }
616 #endif
617
618     (void) fmt;
619
620     int err = Open( p_va, ctx->codec_id, ctx->thread_count );
621     if( err )
622         return err;
623
624     /* Only VLD supported */
625     p_va->pix_fmt = PIX_FMT_VAAPI_VLD;
626     p_va->setup = Setup;
627     p_va->get = Get;
628     p_va->release = Release;
629     p_va->extract = Extract;
630     return VLC_SUCCESS;
631 }