Added (but disabled at compilation time) VA-API support to our avcodec wrapper.
[vlc.git] / modules / codec / avcodec / vaapi.c
1 /*****************************************************************************
2  * vaapi.c: VAAPI helpers for the ffmpeg decoder
3  *****************************************************************************
4  * Copyright (C) 2009 Laurent Aimar
5  * $Id$
6  *
7  * Authors: Laurent Aimar <fenrir_AT_ videolan _DOT_ org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 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 General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, 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 <vlc_common.h>
29 #include <vlc_vout.h>
30 #include <assert.h>
31
32 #ifdef HAVE_LIBAVCODEC_AVCODEC_H
33 #   include <libavcodec/avcodec.h>
34 #   include <libavcodec/vaapi.h>
35 #elif defined(HAVE_FFMPEG_AVCODEC_H)
36 #   include <ffmpeg/avcodec.h>
37 #else
38 #   include <avcodec.h>
39 #endif
40
41 #include "avcodec.h"
42 #include "vaapi.h"
43
44 #ifdef HAVE_AVCODEC_VAAPI
45
46 #include <X11/Xlib.h>
47 #include <va/va_x11.h>
48
49
50 typedef struct
51 {
52     VASurfaceID  i_id;
53     int          i_refcount;
54     unsigned int i_order;
55
56 } vlc_va_surface_t;
57
58 struct vlc_va_t
59 {
60     /* */
61     Display      *p_display_x11;
62     VADisplay     p_display;
63
64     VAConfigID    i_config_id;
65     VAContextID   i_context_id;
66
67     struct vaapi_context hw_ctx;
68
69     /* */
70     int i_version_major;
71     int i_version_minor;
72
73     /* */
74     int          i_surface_count;
75     unsigned int i_surface_order;
76     int          i_surface_width;
77     int          i_surface_height;
78     vlc_fourcc_t i_surface_chroma;
79
80     vlc_va_surface_t *p_surface;
81
82     VAImage      image;
83
84 };
85
86 static int VaOpen( vlc_va_t *p_va, int i_codec_id );
87 static void VaClose( vlc_va_t *p_va );
88
89 static int VaCreateSurfaces( vlc_va_t *p_va, void **pp_hw_ctx, vlc_fourcc_t *pi_chroma,
90                              int i_width, int i_height );
91 static void VaDestroySurfaces( vlc_va_t *p_va );
92
93 vlc_va_t *VaNew( int i_codec_id )
94 {
95     vlc_va_t *p_va = calloc( 1, sizeof(*p_va) );
96     if( !p_va )
97         return NULL;
98
99     if( VaOpen( p_va, i_codec_id ) )
100     {
101         free( p_va );
102         return NULL;
103     }
104     return p_va;
105 }
106 void VaDelete( vlc_va_t *p_va )
107 {
108     VaClose( p_va );
109     free( p_va );
110 }
111 int VaSetup( vlc_va_t *p_va, void **pp_hw_ctx, vlc_fourcc_t *pi_chroma,
112              int i_width, int i_height )
113 {
114     if( p_va->i_surface_width == i_width &&
115         p_va->i_surface_height == i_height )
116         return VLC_SUCCESS;
117
118     *pp_hw_ctx = NULL;
119     *pi_chroma = 0;
120     if( p_va->i_surface_width || p_va->i_surface_height )
121         VaDestroySurfaces( p_va );
122
123     if( i_width > 0 && i_height > 0 )
124         return VaCreateSurfaces( p_va, pp_hw_ctx, pi_chroma, i_width, i_height );
125
126     return VLC_EGENERIC;
127 }
128 void VaVersion( vlc_va_t *p_va, char *psz_version, size_t i_version )
129 {
130     snprintf( psz_version, i_version, "%d.%d", p_va->i_version_major, p_va->i_version_minor );
131 }
132
133 static int VaOpen( vlc_va_t *p_va, int i_codec_id )
134 {
135     VAProfile i_profile;
136     int i_surface_count;
137
138     /* */
139     switch( i_codec_id )
140     {
141     case CODEC_ID_MPEG1VIDEO:
142     case CODEC_ID_MPEG2VIDEO:
143         i_profile = VAProfileMPEG2Main;
144         i_surface_count = 2+1;
145         break;
146     case CODEC_ID_MPEG4:
147         i_profile = VAProfileMPEG4AdvancedSimple;
148         i_surface_count = 2+1;
149         break;
150     case CODEC_ID_WMV3:
151         i_profile = VAProfileVC1Main;
152         i_surface_count = 2+1;
153         break;
154     case CODEC_ID_VC1:
155         i_profile = VAProfileVC1Advanced;
156         i_surface_count = 2+1;
157         break;
158     case CODEC_ID_H264:
159         i_profile = VAProfileH264High;
160         i_surface_count = 16+1;
161         break;
162     default:
163         return VLC_EGENERIC;
164     }
165
166     /* */
167     memset( p_va, 0, sizeof(*p_va) );
168
169     /* Create a VA display */
170     p_va->p_display_x11 = XOpenDisplay(NULL);
171     if( !p_va->p_display_x11 )
172         goto error;
173
174     p_va->p_display = vaGetDisplay( p_va->p_display_x11 );
175     if( !p_va->p_display )
176         goto error;
177
178     if( vaInitialize( p_va->p_display, &p_va->i_version_major, &p_va->i_version_minor ) )
179         goto error;
180
181     /* Create a VA configuration */
182     VAConfigAttrib attrib;
183     memset( &attrib, 0, sizeof(attrib) );
184     attrib.type = VAConfigAttribRTFormat;
185     if( vaGetConfigAttributes( p_va->p_display,
186                                i_profile, VAEntrypointVLD, &attrib, 1 ) )
187         goto error;
188
189     /* Not sure what to do if not, I don't have a way to test */
190     if( (attrib.value & VA_RT_FORMAT_YUV420) == 0 )
191         goto error;
192     if( vaCreateConfig( p_va->p_display,
193                         i_profile, VAEntrypointVLD, &attrib, 1, &p_va->i_config_id ) )
194     {
195         p_va->i_config_id = 0;
196         goto error;
197     }
198
199     p_va->i_surface_count = i_surface_count;
200
201     return VLC_SUCCESS;
202
203 error:
204     return VLC_EGENERIC;
205 }
206 static void VaClose( vlc_va_t *p_va )
207 {
208     if( p_va->i_surface_width || p_va->i_surface_height )
209         VaDestroySurfaces( p_va );
210
211     if( p_va->i_config_id )
212         vaDestroyConfig( p_va->p_display, p_va->i_config_id );
213     if( p_va->p_display )
214         vaTerminate( p_va->p_display );
215     if( p_va->p_display_x11 )
216         XCloseDisplay( p_va->p_display_x11 );
217 }
218
219 static int VaCreateSurfaces( vlc_va_t *p_va, void **pp_hw_ctx, vlc_fourcc_t *pi_chroma,
220                              int i_width, int i_height )
221 {
222     assert( i_width > 0 && i_height > 0 );
223
224     /* */
225     p_va->p_surface = calloc( p_va->i_surface_count, sizeof(*p_va->p_surface) );
226     if( !p_va->p_surface )
227         return VLC_EGENERIC;
228
229     /* Create surfaces */
230     VASurfaceID pi_surface_id[p_va->i_surface_count];
231     if( vaCreateSurfaces( p_va->p_display, i_width, i_height, VA_RT_FORMAT_YUV420,
232                           p_va->i_surface_count, pi_surface_id ) )
233     {
234         for( int i = 0; i < p_va->i_surface_count; i++ )
235             p_va->p_surface[i].i_id = VA_INVALID_SURFACE;
236         goto error;
237     }
238
239     for( int i = 0; i < p_va->i_surface_count; i++ )
240     {
241         vlc_va_surface_t *p_surface = &p_va->p_surface[i];
242
243         p_surface->i_id = pi_surface_id[i];
244         p_surface->i_refcount = 0;
245         p_surface->i_order = 0;
246     }
247
248     /* Create a context */
249     if( vaCreateContext( p_va->p_display, p_va->i_config_id,
250                          i_width, i_height, VA_PROGRESSIVE,
251                          pi_surface_id, p_va->i_surface_count, &p_va->i_context_id ) )
252     {
253         p_va->i_context_id = 0;
254         goto error;
255     }
256
257     /* Find a supported image chroma */
258     int i_fmt_count = vaMaxNumImageFormats( p_va->p_display );
259     VAImageFormat *p_fmt = calloc( i_fmt_count, sizeof(*p_fmt) );
260     if( !p_fmt )
261         goto error;
262
263     if( vaQueryImageFormats( p_va->p_display, p_fmt, &i_fmt_count ) )
264     {
265         free( p_fmt );
266         goto error;
267     }
268
269     vlc_fourcc_t  i_chroma = 0;
270     VAImageFormat fmt;
271     for( int i = 0; i < i_fmt_count; i++ )
272     {
273         if( p_fmt[i].fourcc == VA_FOURCC( 'Y', 'V', '1', '2' ) ||
274             p_fmt[i].fourcc == VA_FOURCC( 'I', '4', '2', '0' ) )
275         {
276             i_chroma = VLC_FOURCC( 'I', '4', '2', '0' );
277             fmt = p_fmt[i];
278         }
279         /* TODO: It seems that these may also be available (but not
280          * with my setup):
281          * VA_FOURCC( 'N', 'V', '1', '2')
282          * VA_FOURCC( 'U', 'Y', 'V', 'Y')
283          * VA_FOURCC( 'Y', 'U', 'Y', 'V')
284          */
285     }
286     free( p_fmt );
287     if( !i_chroma )
288         goto error;
289     *pi_chroma = i_chroma;
290
291     /* Create an image for surface extraction */
292     if( vaCreateImage(  p_va->p_display, &fmt, i_width, i_height, &p_va->image ) )
293     {
294         p_va->image.image_id = 0;
295         goto error;
296     }
297
298     /* Setup the ffmpeg hardware context */
299     *pp_hw_ctx = &p_va->hw_ctx;
300
301     memset( &p_va->hw_ctx, 0, sizeof(p_va->hw_ctx) );
302     p_va->hw_ctx.display    = p_va->p_display;
303     p_va->hw_ctx.config_id  = p_va->i_config_id;
304     p_va->hw_ctx.context_id = p_va->i_context_id;
305
306     /* */
307     p_va->i_surface_chroma = i_chroma;
308     p_va->i_surface_width = i_width;
309     p_va->i_surface_height = i_height;
310     return VLC_SUCCESS;
311
312 error:
313     VaDestroySurfaces( p_va );
314     return VLC_EGENERIC;
315 }
316 static void VaDestroySurfaces( vlc_va_t *p_va )
317 {
318     if( p_va->image.image_id )
319         vaDestroyImage( p_va->p_display, p_va->image.image_id );
320
321     if( p_va->i_context_id )
322         vaDestroyContext( p_va->p_display, p_va->i_context_id );
323
324     for( int i = 0; i < p_va->i_surface_count && p_va->p_surface; i++ )
325     {
326         vlc_va_surface_t *p_surface = &p_va->p_surface[i];
327
328         if( p_surface->i_id != VA_INVALID_SURFACE )
329             vaDestroySurfaces( p_va->p_display, &p_surface->i_id, 1 );
330     }
331     free( p_va->p_surface );
332
333     /* */
334     p_va->image.image_id = 0;
335     p_va->i_context_id = 0;
336     p_va->p_surface = NULL;
337     p_va->i_surface_width = 0;
338     p_va->i_surface_height = 0;
339 }
340
341 int VaExtract( vlc_va_t *p_va, picture_t *p_picture, AVFrame *p_ff )
342 {
343     VASurfaceID i_surface_id = (VASurfaceID)(uintptr_t)p_ff->data[3];
344
345     if( vaSyncSurface( p_va->p_display, p_va->i_context_id, i_surface_id ) )
346         return VLC_EGENERIC;
347
348     /* XXX vaDeriveImage may be better but it is not supported by
349      * my setup.
350      */
351
352     if( vaGetImage( p_va->p_display, i_surface_id,
353                     0, 0, p_va->i_surface_width, p_va->i_surface_height,
354                     p_va->image.image_id) )
355         return VLC_EGENERIC;
356
357     void *p_base;
358     if( vaMapBuffer( p_va->p_display, p_va->image.buf, &p_base ) )
359         return VLC_EGENERIC;
360
361     for( int i_plane = 0; i_plane < p_picture->i_planes; i_plane++ )
362     {
363         const int i_src_plane = ((p_va->image.format.fourcc == VA_FOURCC('Y','V','1','2' )) && i_plane != 0) ?  (3 - i_plane) : i_plane;
364         const uint8_t *p_src = (uint8_t*)p_base + p_va->image.offsets[i_src_plane];
365         const int i_src_stride = p_va->image.pitches[i_src_plane];
366
367         uint8_t *p_dst = p_picture->p[i_plane].p_pixels;
368         const int i_dst_stride = p_picture->p[i_plane].i_pitch;
369
370         if( i_src_stride != i_dst_stride )
371         {
372             for( int i = 0; i < p_picture->p[i_plane].i_visible_lines; i++ )
373             {
374                 vlc_memcpy( p_dst, p_src, __MIN( i_src_stride, i_dst_stride ) );
375                 p_src += i_src_stride;
376                 p_dst += i_dst_stride;
377             }
378         }
379         else
380         {
381             vlc_memcpy( p_dst, p_src, p_picture->p[i_plane].i_visible_lines * i_src_stride );
382         }
383     }
384
385     if( vaUnmapBuffer( p_va->p_display, p_va->image.buf ) )
386         return VLC_EGENERIC;
387
388     return VLC_SUCCESS;
389 }
390 int VaGrabSurface( vlc_va_t *p_va, AVFrame *p_ff )
391 {
392     int i_old;
393     int i;
394
395     /* Grab an unused surface, in case none are, try the oldest
396      * XXX using the oldest is a workaround in case a problem happens with ffmpeg */
397     for( i = 0, i_old = 0; i < p_va->i_surface_count; i++ )
398     {
399         vlc_va_surface_t *p_surface = &p_va->p_surface[i];
400
401         if( !p_surface->i_refcount )
402             break;
403
404         if( p_surface->i_order < p_va->p_surface[i_old].i_order )
405             i_old = i;
406     }
407     if( i >= p_va->i_surface_count )
408         i = i_old;
409
410     vlc_va_surface_t *p_surface = &p_va->p_surface[i];
411
412     p_surface->i_refcount = 1;
413     p_surface->i_order = p_va->i_surface_order++;
414
415     /* */
416     for( int i = 0; i < 4; i++ )
417     {
418         p_ff->data[i] = NULL;
419         p_ff->linesize[i] = 0;
420
421         if( i == 0 || i == 3 )
422             p_ff->data[i] = (void*)(uintptr_t)p_surface->i_id;/* Yummie */
423     }
424     return VLC_SUCCESS;
425 }
426 void VaUngrabSurface( vlc_va_t *p_va, AVFrame *p_ff )
427 {
428     VASurfaceID i_surface_id = (VASurfaceID)(uintptr_t)p_ff->data[3];
429
430     for( int i = 0; i < p_va->i_surface_count; i++ )
431     {
432         vlc_va_surface_t *p_surface = &p_va->p_surface[i];
433
434         if( p_surface->i_id == i_surface_id )
435             p_surface->i_refcount--;
436     }
437 }
438
439 #else
440
441 vlc_va_t *VaNew( int i_codec_id )
442 {
443     VLC_UNUSED(i_codec_id);
444     return NULL;
445 }
446 void VaDelete( vlc_va_t *p_va )
447 {
448     VLC_UNUSED(p_va);
449     assert( 0 );
450 }
451
452 void VaVersion( vlc_va_t *p_va, char *psz_version, size_t i_version )
453 {
454     VLC_UNUSED(p_va); VLC_UNUSED(psz_version); VLC_UNUSED(i_version);
455     assert(0);
456 }
457
458 int VaSetup( vlc_va_t *p_va, void **pp_hw_ctx, vlc_fourcc_t *pi_chroma,
459              int i_width, int i_height )
460 {
461     VLC_UNUSED(p_va); VLC_UNUSED(pp_hw_ctx); VLC_UNUSED(pi_chroma);
462     VLC_UNUSED(i_width); VLC_UNUSED(i_height);
463     assert(0);
464     return -1;
465 }
466
467 int VaExtract( vlc_va_t *p_va, picture_t *p_picture, AVFrame *p_ff )
468 {
469     VLC_UNUSED(p_va); VLC_UNUSED(p_picture); VLC_UNUSED(p_ff);
470     assert(0);
471     return -1;
472 }
473
474 int VaGrabSurface( vlc_va_t *p_va, AVFrame *p_ff )
475 {
476     VLC_UNUSED(p_va); VLC_UNUSED(p_ff);
477     assert(0);
478     return -1;
479 }
480
481 void VaUngrabSurface( vlc_va_t *p_va, AVFrame *p_ff )
482 {
483     VLC_UNUSED(p_va); VLC_UNUSED(p_ff);
484     assert(0);
485 }
486
487 #endif