vaapi.c 16.7 KB
Newer Older
1 2 3
/*****************************************************************************
 * vaapi.c: VAAPI helpers for the ffmpeg decoder
 *****************************************************************************
4
 * Copyright (C) 2009-2011 Laurent Aimar
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
 * $Id$
 *
 * Authors: Laurent Aimar <fenrir_AT_ videolan _DOT_ org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <vlc_common.h>
Laurent Aimar's avatar
Laurent Aimar committed
29
#include <vlc_fourcc.h>
30
#include <vlc_picture.h>
31
#include <assert.h>
32
#include <vlc_arrays.h>
33 34 35 36 37 38 39 40

#ifdef HAVE_LIBAVCODEC_AVCODEC_H
#   include <libavcodec/avcodec.h>
#else
#   include <avcodec.h>
#endif

#include "avcodec.h"
41 42 43 44 45 46
#ifdef HAVE_AVCODEC_VAAPI
#  include <vlc_xlib.h>
#  include <libavcodec/vaapi.h>
#  include <va/va.h>
#  include <va/va_x11.h>
#endif
47
#include "va.h"
48
#include "vaapi.h"
49
#include "copy.h"
50 51 52

#ifdef HAVE_AVCODEC_VAAPI

53
typedef struct
54
{
55 56
    vlc_va_t     va;

57
    /* */
58
    vlc_va_conn_t *conn;
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74

    VAConfigID    i_config_id;
    VAContextID   i_context_id;

    struct vaapi_context hw_ctx;

    /* */
    int          i_surface_count;
    unsigned int i_surface_order;
    int          i_surface_width;
    int          i_surface_height;
    vlc_fourcc_t i_surface_chroma;

    vlc_va_surface_t *p_surface;

    VAImage      image;
75
    copy_cache_t image_cache;
76

77
} vlc_va_vaapi_t;
78

79
static vlc_va_vaapi_t *vlc_va_vaapi_Get( void *p_va )
80 81 82 83
{
    return p_va;
}

84 85
/* */
static int Open( vlc_va_vaapi_t *p_va, int i_codec_id )
86 87 88 89
{
    VAProfile i_profile;
    int i_surface_count;

90 91 92 93 94 95 96
    /* NOTE: The number of surfaces requested is calculated
       based on the amount of pictures vout core needs for
       direct rendering. In general that is 20/30 pictures.

       i_surface_count = requested_count - (codec_needs + 1)
                         + codec_needs + 1;
     */
97 98 99 100 101
    switch( i_codec_id )
    {
    case CODEC_ID_MPEG1VIDEO:
    case CODEC_ID_MPEG2VIDEO:
        i_profile = VAProfileMPEG2Main;
102
        i_surface_count = 17+2+1;
103 104 105
        break;
    case CODEC_ID_MPEG4:
        i_profile = VAProfileMPEG4AdvancedSimple;
106
        i_surface_count = 17+2+1;
107 108 109
        break;
    case CODEC_ID_WMV3:
        i_profile = VAProfileVC1Main;
110
        i_surface_count = 17+2+1;
111 112 113
        break;
    case CODEC_ID_VC1:
        i_profile = VAProfileVC1Advanced;
114
        i_surface_count = 17+2+1;
115 116 117
        break;
    case CODEC_ID_H264:
        i_profile = VAProfileH264High;
118
        i_surface_count = 13+16+1;
119 120 121 122 123 124 125
        break;
    default:
        return VLC_EGENERIC;
    }

    /* */
    memset( p_va, 0, sizeof(*p_va) );
126 127
    p_va->i_config_id  = VA_INVALID_ID;
    p_va->i_context_id = VA_INVALID_ID;
128
    p_va->image.image_id = VA_INVALID_ID;
129 130

    /* Create a VA display */
131 132
    p_va->conn = vlc_va_Initialize(NULL);
    if (!p_va->conn)
133 134 135 136 137 138
        goto error;

    /* Create a VA configuration */
    VAConfigAttrib attrib;
    memset( &attrib, 0, sizeof(attrib) );
    attrib.type = VAConfigAttribRTFormat;
139
    if( vaGetConfigAttributes( p_va->conn->p_display,
140 141 142 143 144 145
                               i_profile, VAEntrypointVLD, &attrib, 1 ) )
        goto error;

    /* Not sure what to do if not, I don't have a way to test */
    if( (attrib.value & VA_RT_FORMAT_YUV420) == 0 )
        goto error;
146
    if( vaCreateConfig( p_va->conn->p_display,
147 148
                        i_profile, VAEntrypointVLD, &attrib, 1, &p_va->i_config_id ) )
    {
149
        p_va->i_config_id = VA_INVALID_ID;
150 151 152 153 154
        goto error;
    }

    p_va->i_surface_count = i_surface_count;

155
    if( asprintf( &p_va->va.description, "VA API version %d.%d",
156
                  p_va->conn->i_version_major, p_va->conn->i_version_minor ) < 0 )
157 158
        p_va->va.description = NULL;

159 160 161 162 163
    return VLC_SUCCESS;

error:
    return VLC_EGENERIC;
}
164 165

static void DestroySurfaces( vlc_va_vaapi_t *p_va )
166
{
167
    if( p_va->image.image_id != VA_INVALID_ID )
168 169
    {
        CopyCleanCache( &p_va->image_cache );
170
        vaDestroyImage( p_va->conn->p_display, p_va->image.image_id );
171
    }
172

173
    if( p_va->i_context_id != VA_INVALID_ID )
174
        vaDestroyContext( p_va->conn->p_display, p_va->i_context_id );
175

176 177 178 179
    for( int i = 0; i < p_va->i_surface_count && p_va->p_surface; i++ )
    {
        vlc_va_surface_t *p_surface = &p_va->p_surface[i];
        if( p_surface->i_id != VA_INVALID_SURFACE )
180
            vaDestroySurfaces( p_va->conn->p_display, &p_surface->i_id, 1 );
181 182 183 184
    }
    free( p_va->p_surface );

    /* */
185
    p_va->image.image_id = VA_INVALID_ID;
186
    p_va->i_context_id = VA_INVALID_ID;
187 188 189 190 191 192
    p_va->p_surface = NULL;
    p_va->i_surface_width = 0;
    p_va->i_surface_height = 0;
}
static int CreateSurfaces( vlc_va_vaapi_t *p_va, void **pp_hw_ctx, vlc_fourcc_t *pi_chroma,
                           int i_width, int i_height )
193 194 195 196 197 198 199
{
    assert( i_width > 0 && i_height > 0 );

    /* */
    p_va->p_surface = calloc( p_va->i_surface_count, sizeof(*p_va->p_surface) );
    if( !p_va->p_surface )
        return VLC_EGENERIC;
200
    p_va->image.image_id = VA_INVALID_ID;
201
    p_va->i_context_id   = VA_INVALID_ID;
202 203 204

    /* Create surfaces */
    VASurfaceID pi_surface_id[p_va->i_surface_count];
205
    if( vaCreateSurfaces( p_va->conn->p_display, i_width, i_height, VA_RT_FORMAT_YUV420,
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
                          p_va->i_surface_count, pi_surface_id ) )
    {
        for( int i = 0; i < p_va->i_surface_count; i++ )
            p_va->p_surface[i].i_id = VA_INVALID_SURFACE;
        goto error;
    }

    for( int i = 0; i < p_va->i_surface_count; i++ )
    {
        vlc_va_surface_t *p_surface = &p_va->p_surface[i];

        p_surface->i_id = pi_surface_id[i];
        p_surface->i_refcount = 0;
        p_surface->i_order = 0;
    }

    /* Create a context */
223
    if( vaCreateContext( p_va->conn->p_display, p_va->i_config_id,
224 225 226
                         i_width, i_height, VA_PROGRESSIVE,
                         pi_surface_id, p_va->i_surface_count, &p_va->i_context_id ) )
    {
227
        p_va->i_context_id = VA_INVALID_ID;
228 229 230
        goto error;
    }

231
    /* Find and create a supported image chroma */
232
    int i_fmt_count = vaMaxNumImageFormats( p_va->conn->p_display );
233 234 235 236
    VAImageFormat *p_fmt = calloc( i_fmt_count, sizeof(*p_fmt) );
    if( !p_fmt )
        goto error;

237
    if( vaQueryImageFormats( p_va->conn->p_display, p_fmt, &i_fmt_count ) )
238 239 240 241 242 243 244 245 246 247
    {
        free( p_fmt );
        goto error;
    }

    vlc_fourcc_t  i_chroma = 0;
    VAImageFormat fmt;
    for( int i = 0; i < i_fmt_count; i++ )
    {
        if( p_fmt[i].fourcc == VA_FOURCC( 'Y', 'V', '1', '2' ) ||
248 249
            p_fmt[i].fourcc == VA_FOURCC( 'I', '4', '2', '0' ) ||
            p_fmt[i].fourcc == VA_FOURCC( 'N', 'V', '1', '2' ) )
250
        {
251
            if( vaCreateImage( p_va->conn->p_display, &p_fmt[i], i_width, i_height, &p_va->image ) )
252
            {
253
                p_va->image.image_id = VA_INVALID_ID;
254 255 256
                continue;
            }
            /* Validate that vaGetImage works with this format */
257
            if( vaGetImage( p_va->conn->p_display, pi_surface_id[0],
258 259 260
                            0, 0, i_width, i_height,
                            p_va->image.image_id) )
            {
261
                vaDestroyImage( p_va->conn->p_display, p_va->image.image_id );
262
                p_va->image.image_id = VA_INVALID_ID;
263 264 265
                continue;
            }

266
            i_chroma = VLC_CODEC_YV12;
267
            fmt = p_fmt[i];
268
            break;
269 270 271 272 273 274 275
        }
    }
    free( p_fmt );
    if( !i_chroma )
        goto error;
    *pi_chroma = i_chroma;

276
    CopyInitCache( &p_va->image_cache, i_width );
277 278 279 280 281

    /* Setup the ffmpeg hardware context */
    *pp_hw_ctx = &p_va->hw_ctx;

    memset( &p_va->hw_ctx, 0, sizeof(p_va->hw_ctx) );
282
    p_va->hw_ctx.display    = p_va->conn->p_display;
283 284 285 286 287 288 289 290 291 292
    p_va->hw_ctx.config_id  = p_va->i_config_id;
    p_va->hw_ctx.context_id = p_va->i_context_id;

    /* */
    p_va->i_surface_chroma = i_chroma;
    p_va->i_surface_width = i_width;
    p_va->i_surface_height = i_height;
    return VLC_SUCCESS;

error:
293
    DestroySurfaces( p_va );
294 295
    return VLC_EGENERIC;
}
296 297 298

static int Setup( vlc_va_t *p_external, void **pp_hw_ctx, vlc_fourcc_t *pi_chroma,
                  int i_width, int i_height )
299
{
300
    vlc_va_vaapi_t *p_va = vlc_va_vaapi_Get(p_external);
301

302 303
    if( p_va->i_surface_width == i_width &&
        p_va->i_surface_height == i_height )
Laurent Aimar's avatar
Laurent Aimar committed
304 305 306
    {
        *pp_hw_ctx = &p_va->hw_ctx;
        *pi_chroma = p_va->i_surface_chroma;
307
        return VLC_SUCCESS;
Laurent Aimar's avatar
Laurent Aimar committed
308
    }
309

310 311 312 313
    *pp_hw_ctx = NULL;
    *pi_chroma = 0;
    if( p_va->i_surface_width || p_va->i_surface_height )
        DestroySurfaces( p_va );
314

315 316
    if( i_width > 0 && i_height > 0 )
        return CreateSurfaces( p_va, pp_hw_ctx, pi_chroma, i_width, i_height );
317

318
    return VLC_EGENERIC;
319
}
320
static int Extract( vlc_va_t *p_external, picture_t *p_picture, AVFrame *p_ff )
321
{
322 323
    vlc_va_vaapi_t *p_va = vlc_va_vaapi_Get(p_external);

324 325 326
    if( !p_va->image_cache.buffer )
        return VLC_EGENERIC;

327 328
    VASurfaceID i_surface_id = (VASurfaceID)(uintptr_t)p_ff->data[3];

329
#if VA_CHECK_VERSION(0,31,0)
330
    if( vaSyncSurface( p_va->conn->p_display, i_surface_id ) )
331
#else
332
    if( vaSyncSurface( p_va->conn->p_display, p_va->i_context_id, i_surface_id ) )
333
#endif
334 335 336 337 338 339
        return VLC_EGENERIC;

    /* XXX vaDeriveImage may be better but it is not supported by
     * my setup.
     */

340
    if( vaGetImage( p_va->conn->p_display, i_surface_id,
341 342 343 344 345
                    0, 0, p_va->i_surface_width, p_va->i_surface_height,
                    p_va->image.image_id) )
        return VLC_EGENERIC;

    void *p_base;
346
    if( vaMapBuffer( p_va->conn->p_display, p_va->image.buf, &p_base ) )
347 348
        return VLC_EGENERIC;

349 350 351
    const uint32_t i_fourcc = p_va->image.format.fourcc;
    if( i_fourcc == VA_FOURCC('Y','V','1','2') ||
        i_fourcc == VA_FOURCC('I','4','2','0') )
352
    {
353 354 355
        bool b_swap_uv = i_fourcc == VA_FOURCC('I','4','2','0');
        uint8_t *pp_plane[3];
        size_t  pi_pitch[3];
356

357
        for( int i = 0; i < 3; i++ )
358
        {
359 360 361
            const int i_src_plane = (b_swap_uv && i != 0) ?  (3 - i) : i;
            pp_plane[i] = (uint8_t*)p_base + p_va->image.offsets[i_src_plane];
            pi_pitch[i] = p_va->image.pitches[i_src_plane];
362
        }
363 364 365 366 367 368 369 370 371 372 373 374
        CopyFromYv12( p_picture, pp_plane, pi_pitch,
                      p_va->i_surface_width,
                      p_va->i_surface_height,
                      &p_va->image_cache );
    }
    else
    {
        assert( i_fourcc == VA_FOURCC('N','V','1','2') );
        uint8_t *pp_plane[2];
        size_t  pi_pitch[2];

        for( int i = 0; i < 2; i++ )
375
        {
376 377
            pp_plane[i] = (uint8_t*)p_base + p_va->image.offsets[i];
            pi_pitch[i] = p_va->image.pitches[i];
378
        }
379 380 381 382
        CopyFromNv12( p_picture, pp_plane, pi_pitch,
                      p_va->i_surface_width,
                      p_va->i_surface_height,
                      &p_va->image_cache );
383 384
    }

385
    if( vaUnmapBuffer( p_va->conn->p_display, p_va->image.buf ) )
386 387 388 389
        return VLC_EGENERIC;

    return VLC_SUCCESS;
}
390 391

static vlc_va_surface_t *FindSurface( vlc_va_t *p_external, const VASurfaceID i_surface_id )
392 393
{
    vlc_va_vaapi_t *p_va = vlc_va_vaapi_Get(p_external);
394
    vlc_va_surface_t *p_surface = NULL;
395

396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414
    for( int i = 0; i < p_va->i_surface_count; i++ )
    {
        vlc_va_surface_t *p_tmp = &p_va->p_surface[i];
        if( p_tmp->i_id == i_surface_id )
        {
            /* NOTE: p_tmp->i_refcount can be greater then 1, when surfaces are being reclaimed
             * this usually only happens when the vout vaapi-x11 is not instantiated yet.
             */
            p_tmp->i_refcount++;
            p_surface = p_tmp;
            break;
        }
    }
    return p_surface;
}

static int DisplayPicture( vlc_va_t *p_external, picture_t *p_picture, AVFrame *p_ff )
{
    vlc_va_vaapi_t *p_va = vlc_va_vaapi_Get(p_external);
415 416 417 418 419 420 421 422 423 424 425

    VASurfaceID i_surface_id = (VASurfaceID)(uintptr_t)p_ff->data[3];

#if VA_CHECK_VERSION(0,31,0)
    if( vaSyncSurface( p_va->conn->p_display, i_surface_id ) )
#else
    if( vaSyncSurface( p_va->conn->p_display, p_va->i_context_id, i_surface_id ) )
#endif
        return VLC_EGENERIC;

    if (!p_picture->p_sys)
426
        abort();
427

428 429 430
    /* FindSurface */
    p_picture->p_sys->surface = FindSurface( p_external, i_surface_id );
    if( !p_picture->p_sys->surface )
431
        abort();
432

433 434
    assert(p_picture->p_sys->subpicture == NULL);

435 436 437
    return VLC_SUCCESS;
}

438
static int Get( vlc_va_t *p_external, AVFrame *p_ff )
439
{
440
    vlc_va_vaapi_t *p_va = vlc_va_vaapi_Get(p_external);
441 442 443 444 445 446 447 448 449
    int i_old;
    int i;

    /* Grab an unused surface, in case none are, try the oldest
     * XXX using the oldest is a workaround in case a problem happens with ffmpeg */
    for( i = 0, i_old = 0; i < p_va->i_surface_count; i++ )
    {
        vlc_va_surface_t *p_surface = &p_va->p_surface[i];

450
        if( p_surface->i_refcount == 0 )
451 452 453 454 455 456 457 458 459
            break;

        if( p_surface->i_order < p_va->p_surface[i_old].i_order )
            i_old = i;
    }
    if( i >= p_va->i_surface_count )
        i = i_old;

    vlc_va_surface_t *p_surface = &p_va->p_surface[i];
460 461 462 463
    /* NOTE: when the surface is in use and not consumed by vout vaapi-x11,
     * then p_surface->i_refcount can be greater then 0. Thus always increment.
     */
    p_surface->i_refcount++;
464 465 466 467 468 469 470 471 472 473 474 475 476
    p_surface->i_order = p_va->i_surface_order++;

    /* */
    for( int i = 0; i < 4; i++ )
    {
        p_ff->data[i] = NULL;
        p_ff->linesize[i] = 0;

        if( i == 0 || i == 3 )
            p_ff->data[i] = (void*)(uintptr_t)p_surface->i_id;/* Yummie */
    }
    return VLC_SUCCESS;
}
477
static void Release( vlc_va_t *p_external, AVFrame *p_ff )
478
{
479 480
    vlc_va_vaapi_t *p_va = vlc_va_vaapi_Get(p_external);

481 482 483 484 485 486 487 488 489 490 491
    VASurfaceID i_surface_id = (VASurfaceID)(uintptr_t)p_ff->data[3];

    for( int i = 0; i < p_va->i_surface_count; i++ )
    {
        vlc_va_surface_t *p_surface = &p_va->p_surface[i];

        if( p_surface->i_id == i_surface_id )
            p_surface->i_refcount--;
    }
}

492
static void Close( vlc_va_vaapi_t *p_va )
493
{
494 495
    if( p_va->i_surface_width || p_va->i_surface_height )
        DestroySurfaces( p_va );
496

497
    if( p_va->i_config_id != VA_INVALID_ID )
498
        vaDestroyConfig( p_va->conn->p_display, p_va->i_config_id );
499
    vlc_va_Terminate( p_va->conn );
500
}
501

502
static void Delete( vlc_va_t *p_external )
503
{
504 505 506 507
    vlc_va_vaapi_t *p_va = vlc_va_vaapi_Get(p_external);
    Close( p_va );
    free( p_va->va.description );
    free( p_va );
508 509
}

510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548
/* */
void SubpictureDestroy( vlc_va_conn_t *p_connection, VASurfaceID *i_surface_id,
                        vlc_va_subpicture_t *subpicture, const int i_sub )
{
    VAStatus status;

    for( int i = 0; i < i_sub; i++ )
    {
        if( ((*i_surface_id) != VA_INVALID_SURFACE) &&
            (subpicture[i].i_id != VA_INVALID_ID) )
        {
            status = vaDeassociateSubpicture( p_connection->p_display, subpicture[i].i_id,
                                              i_surface_id, 1) ;
            assert( status == VA_STATUS_SUCCESS );
        }

        if( subpicture[i].i_id != VA_INVALID_ID )
        {
            status = vaDestroySubpicture( p_connection->p_display,
                                          subpicture[i].i_id );
            assert( status == VA_STATUS_SUCCESS );
        }

        if( subpicture[i].image.image_id != VA_INVALID_ID )
        {
            status = vaDestroyImage( p_connection->p_display,
                                     subpicture[i].image.image_id );
            assert( status == VA_STATUS_SUCCESS );
        }

        subpicture[i].image.image_id = VA_INVALID_ID;
        subpicture[i].image.buf = VA_INVALID_ID;
        subpicture[i].i_id = VA_INVALID_ID;
    }

    /* */
    free( subpicture );
}

549
/* */
550
vlc_va_t *vlc_va_NewVaapi( vlc_object_t *obj, int i_codec_id )
551
{
552
    if( !vlc_xlib_init( obj ) )
553 554
        return NULL;

555 556 557
    vlc_va_vaapi_t *p_va = calloc( 1, sizeof(*p_va) );
    if( !p_va )
        return NULL;
558

559 560 561 562 563
    if( Open( p_va, i_codec_id ) )
    {
        free( p_va );
        return NULL;
    }
564

565 566 567 568 569
    /* */
    p_va->va.setup = Setup;
    p_va->va.get = Get;
    p_va->va.release = Release;
    p_va->va.extract = Extract;
570
    p_va->va.display = DisplayPicture;
571 572 573
    p_va->va.close = Delete;
    return &p_va->va;
}
574

575
#else
Pierre Ynard's avatar
Pierre Ynard committed
576
vlc_va_t *vlc_va_NewVaapi( vlc_object_t *obj, int i_codec_id )
577
{
Pierre Ynard's avatar
Pierre Ynard committed
578
    VLC_UNUSED( obj );
579 580
    VLC_UNUSED( i_codec_id );
    return NULL;
581 582
}
#endif