libavutil/hwcontext_vaapi.c
551c6775
 /*
28abb216
  * This file is part of FFmpeg.
551c6775
  *
28abb216
  * FFmpeg is free software; you can redistribute it and/or
551c6775
  * modify it under the terms of the GNU Lesser General Public
  * License as published by the Free Software Foundation; either
  * version 2.1 of the License, or (at your option) any later version.
  *
28abb216
  * FFmpeg is distributed in the hope that it will be useful,
551c6775
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public
28abb216
  * License along with FFmpeg; if not, write to the Free Software
551c6775
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */
 
b8bf9194
 #include "config.h"
 
 #if HAVE_VAAPI_X11
 #   include <va/va_x11.h>
 #endif
 #if HAVE_VAAPI_DRM
 #   include <va/va_drm.h>
 #endif
 
170c6533
 #if CONFIG_LIBDRM
 #   include <va/va_drmcommon.h>
6b6b8a63
 #   include <xf86drm.h>
170c6533
 #   include <drm_fourcc.h>
c6bbb8cc
 #   ifndef DRM_FORMAT_MOD_INVALID
 #       define DRM_FORMAT_MOD_INVALID ((1ULL << 56) - 1)
 #   endif
170c6533
 #endif
 
b8bf9194
 #include <fcntl.h>
 #if HAVE_UNISTD_H
 #   include <unistd.h>
 #endif
 
 
551c6775
 #include "avassert.h"
 #include "buffer.h"
 #include "common.h"
 #include "hwcontext.h"
0f3d1c69
 #include "hwcontext_drm.h"
551c6775
 #include "hwcontext_internal.h"
 #include "hwcontext_vaapi.h"
 #include "mem.h"
 #include "pixdesc.h"
 #include "pixfmt.h"
 
170c6533
 
b8bf9194
 typedef struct VAAPIDevicePriv {
 #if HAVE_VAAPI_X11
     Display *x11_display;
 #endif
 
     int drm_fd;
 } VAAPIDevicePriv;
 
551c6775
 typedef struct VAAPISurfaceFormat {
     enum AVPixelFormat pix_fmt;
     VAImageFormat image_format;
 } VAAPISurfaceFormat;
 
 typedef struct VAAPIDeviceContext {
     // Surface formats which can be used with this device.
     VAAPISurfaceFormat *formats;
     int              nb_formats;
 } VAAPIDeviceContext;
 
 typedef struct VAAPIFramesContext {
     // Surface attributes set at create time.
     VASurfaceAttrib *attributes;
     int           nb_attributes;
     // RT format of the underlying surface (Intel driver ignores this anyway).
     unsigned int rt_format;
     // Whether vaDeriveImage works.
     int derive_works;
 } VAAPIFramesContext;
 
8ad9f9d6
 typedef struct VAAPIMapping {
551c6775
     // Handle to the derived or copied image which is mapped.
     VAImage image;
8ad9f9d6
     // The mapping flags actually used.
     int flags;
 } VAAPIMapping;
551c6775
 
40724026
 typedef struct VAAPIFormat {
     unsigned int fourcc;
     unsigned int rt_format;
     enum AVPixelFormat pix_fmt;
     int chroma_planes_swapped;
 } VAAPIFormatDescriptor;
 
 #define MAP(va, rt, av, swap_uv) { \
551c6775
         VA_FOURCC_ ## va, \
         VA_RT_FORMAT_ ## rt, \
40724026
         AV_PIX_FMT_ ## av, \
         swap_uv, \
551c6775
     }
 // The map fourcc <-> pix_fmt isn't bijective because of the annoying U/V
 // plane swap cases.  The frame handling below tries to hide these.
40724026
 static const VAAPIFormatDescriptor vaapi_format_map[] = {
     MAP(NV12, YUV420,  NV12,    0),
3db59617
 #ifdef VA_FOURCC_I420
40724026
     MAP(I420, YUV420,  YUV420P, 0),
3db59617
 #endif
40724026
     MAP(YV12, YUV420,  YUV420P, 1),
     MAP(IYUV, YUV420,  YUV420P, 0),
     MAP(422H, YUV422,  YUV422P, 0),
551c6775
 #ifdef VA_FOURCC_YV16
40724026
     MAP(YV16, YUV422,  YUV422P, 1),
551c6775
 #endif
40724026
     MAP(UYVY, YUV422,  UYVY422, 0),
     MAP(YUY2, YUV422,  YUYV422, 0),
f4cd4017
 #ifdef VA_FOURCC_Y210
     MAP(Y210, YUV422_10,  Y210, 0),
 #endif
40724026
     MAP(411P, YUV411,  YUV411P, 0),
     MAP(422V, YUV422,  YUV440P, 0),
     MAP(444P, YUV444,  YUV444P, 0),
     MAP(Y800, YUV400,  GRAY8,   0),
551c6775
 #ifdef VA_FOURCC_P010
40724026
     MAP(P010, YUV420_10BPP, P010, 0),
551c6775
 #endif
40724026
     MAP(BGRA, RGB32,   BGRA, 0),
     MAP(BGRX, RGB32,   BGR0, 0),
     MAP(RGBA, RGB32,   RGBA, 0),
     MAP(RGBX, RGB32,   RGB0, 0),
5aede051
 #ifdef VA_FOURCC_ABGR
40724026
     MAP(ABGR, RGB32,   ABGR, 0),
     MAP(XBGR, RGB32,   0BGR, 0),
5aede051
 #endif
40724026
     MAP(ARGB, RGB32,   ARGB, 0),
     MAP(XRGB, RGB32,   0RGB, 0),
551c6775
 };
 #undef MAP
 
40724026
 static const VAAPIFormatDescriptor *
     vaapi_format_from_fourcc(unsigned int fourcc)
551c6775
 {
     int i;
     for (i = 0; i < FF_ARRAY_ELEMS(vaapi_format_map); i++)
         if (vaapi_format_map[i].fourcc == fourcc)
40724026
             return &vaapi_format_map[i];
     return NULL;
 }
 
 static const VAAPIFormatDescriptor *
     vaapi_format_from_pix_fmt(enum AVPixelFormat pix_fmt)
 {
     int i;
     for (i = 0; i < FF_ARRAY_ELEMS(vaapi_format_map); i++)
         if (vaapi_format_map[i].pix_fmt == pix_fmt)
             return &vaapi_format_map[i];
     return NULL;
 }
 
 static enum AVPixelFormat vaapi_pix_fmt_from_fourcc(unsigned int fourcc)
 {
     const VAAPIFormatDescriptor *desc;
     desc = vaapi_format_from_fourcc(fourcc);
     if (desc)
         return desc->pix_fmt;
     else
         return AV_PIX_FMT_NONE;
551c6775
 }
 
 static int vaapi_get_image_format(AVHWDeviceContext *hwdev,
                                   enum AVPixelFormat pix_fmt,
                                   VAImageFormat **image_format)
 {
     VAAPIDeviceContext *ctx = hwdev->internal->priv;
     int i;
 
     for (i = 0; i < ctx->nb_formats; i++) {
         if (ctx->formats[i].pix_fmt == pix_fmt) {
8ad9f9d6
             if (image_format)
                 *image_format = &ctx->formats[i].image_format;
551c6775
             return 0;
         }
     }
     return AVERROR(EINVAL);
 }
 
 static int vaapi_frames_get_constraints(AVHWDeviceContext *hwdev,
                                         const void *hwconfig,
                                         AVHWFramesConstraints *constraints)
 {
     AVVAAPIDeviceContext *hwctx = hwdev->hwctx;
     const AVVAAPIHWConfig *config = hwconfig;
fe498ef5
     VAAPIDeviceContext *ctx = hwdev->internal->priv;
551c6775
     VASurfaceAttrib *attr_list = NULL;
     VAStatus vas;
     enum AVPixelFormat pix_fmt;
     unsigned int fourcc;
     int err, i, j, attr_count, pix_fmt_count;
 
f2e4fb61
     if (config &&
         !(hwctx->driver_quirks & AV_VAAPI_DRIVER_QUIRK_SURFACE_ATTRIBUTES)) {
fe498ef5
         attr_count = 0;
         vas = vaQuerySurfaceAttributes(hwctx->display, config->config_id,
                                        0, &attr_count);
551c6775
         if (vas != VA_STATUS_SUCCESS) {
fe498ef5
             av_log(hwdev, AV_LOG_ERROR, "Failed to query surface attributes: "
                    "%d (%s).\n", vas, vaErrorStr(vas));
             err = AVERROR(ENOSYS);
             goto fail;
551c6775
         }
 
fe498ef5
         attr_list = av_malloc(attr_count * sizeof(*attr_list));
         if (!attr_list) {
             err = AVERROR(ENOMEM);
             goto fail;
         }
551c6775
 
fe498ef5
         vas = vaQuerySurfaceAttributes(hwctx->display, config->config_id,
                                        attr_list, &attr_count);
         if (vas != VA_STATUS_SUCCESS) {
             av_log(hwdev, AV_LOG_ERROR, "Failed to query surface attributes: "
                    "%d (%s).\n", vas, vaErrorStr(vas));
             err = AVERROR(ENOSYS);
             goto fail;
         }
551c6775
 
fe498ef5
         pix_fmt_count = 0;
         for (i = 0; i < attr_count; i++) {
             switch (attr_list[i].type) {
             case VASurfaceAttribPixelFormat:
                 fourcc = attr_list[i].value.value.i;
                 pix_fmt = vaapi_pix_fmt_from_fourcc(fourcc);
                 if (pix_fmt != AV_PIX_FMT_NONE) {
                     ++pix_fmt_count;
                 } else {
                     // Something unsupported - ignore.
                 }
                 break;
             case VASurfaceAttribMinWidth:
                 constraints->min_width  = attr_list[i].value.value.i;
                 break;
             case VASurfaceAttribMinHeight:
                 constraints->min_height = attr_list[i].value.value.i;
                 break;
             case VASurfaceAttribMaxWidth:
                 constraints->max_width  = attr_list[i].value.value.i;
                 break;
             case VASurfaceAttribMaxHeight:
                 constraints->max_height = attr_list[i].value.value.i;
                 break;
             }
         }
         if (pix_fmt_count == 0) {
             // Nothing usable found.  Presumably there exists something which
             // works, so leave the set null to indicate unknown.
             constraints->valid_sw_formats = NULL;
         } else {
             constraints->valid_sw_formats = av_malloc_array(pix_fmt_count + 1,
                                                             sizeof(pix_fmt));
             if (!constraints->valid_sw_formats) {
                 err = AVERROR(ENOMEM);
                 goto fail;
             }
551c6775
 
fe498ef5
             for (i = j = 0; i < attr_count; i++) {
                 if (attr_list[i].type != VASurfaceAttribPixelFormat)
                     continue;
                 fourcc = attr_list[i].value.value.i;
                 pix_fmt = vaapi_pix_fmt_from_fourcc(fourcc);
                 if (pix_fmt != AV_PIX_FMT_NONE)
                     constraints->valid_sw_formats[j++] = pix_fmt;
551c6775
             }
fe498ef5
             av_assert0(j == pix_fmt_count);
             constraints->valid_sw_formats[j] = AV_PIX_FMT_NONE;
551c6775
         }
     } else {
fe498ef5
         // No configuration supplied.
         // Return the full set of image formats known by the implementation.
         constraints->valid_sw_formats = av_malloc_array(ctx->nb_formats + 1,
551c6775
                                                         sizeof(pix_fmt));
         if (!constraints->valid_sw_formats) {
             err = AVERROR(ENOMEM);
             goto fail;
         }
fe498ef5
         for (i = 0; i < ctx->nb_formats; i++)
             constraints->valid_sw_formats[i] = ctx->formats[i].pix_fmt;
         constraints->valid_sw_formats[i] = AV_PIX_FMT_NONE;
551c6775
     }
 
     constraints->valid_hw_formats = av_malloc_array(2, sizeof(pix_fmt));
     if (!constraints->valid_hw_formats) {
         err = AVERROR(ENOMEM);
         goto fail;
     }
     constraints->valid_hw_formats[0] = AV_PIX_FMT_VAAPI;
     constraints->valid_hw_formats[1] = AV_PIX_FMT_NONE;
 
     err = 0;
 fail:
     av_freep(&attr_list);
     return err;
 }
 
a8d51bb4
 static const struct {
     const char *friendly_name;
     const char *match_string;
     unsigned int quirks;
 } vaapi_driver_quirks_table[] = {
852c7ba3
 #if !VA_CHECK_VERSION(1, 0, 0)
     // The i965 driver did not conform before version 2.0.
a8d51bb4
     {
         "Intel i965 (Quick Sync)",
         "i965",
         AV_VAAPI_DRIVER_QUIRK_RENDER_PARAM_BUFFERS,
     },
852c7ba3
 #endif
775a8477
     {
         "Intel iHD",
         "ubit",
         AV_VAAPI_DRIVER_QUIRK_ATTRIB_MEMTYPE,
     },
f2e4fb61
     {
         "VDPAU wrapper",
         "Splitted-Desktop Systems VDPAU backend for VA-API",
         AV_VAAPI_DRIVER_QUIRK_SURFACE_ATTRIBUTES,
     },
a8d51bb4
 };
 
551c6775
 static int vaapi_device_init(AVHWDeviceContext *hwdev)
 {
     VAAPIDeviceContext *ctx = hwdev->internal->priv;
     AVVAAPIDeviceContext *hwctx = hwdev->hwctx;
     VAImageFormat *image_list = NULL;
     VAStatus vas;
a8d51bb4
     const char *vendor_string;
fe498ef5
     int err, i, image_count;
551c6775
     enum AVPixelFormat pix_fmt;
     unsigned int fourcc;
 
     image_count = vaMaxNumImageFormats(hwctx->display);
     if (image_count <= 0) {
         err = AVERROR(EIO);
         goto fail;
     }
     image_list = av_malloc(image_count * sizeof(*image_list));
     if (!image_list) {
         err = AVERROR(ENOMEM);
         goto fail;
     }
     vas = vaQueryImageFormats(hwctx->display, image_list, &image_count);
     if (vas != VA_STATUS_SUCCESS) {
         err = AVERROR(EIO);
         goto fail;
     }
 
     ctx->formats  = av_malloc(image_count * sizeof(*ctx->formats));
     if (!ctx->formats) {
         err = AVERROR(ENOMEM);
         goto fail;
     }
     ctx->nb_formats = 0;
     for (i = 0; i < image_count; i++) {
         fourcc  = image_list[i].fourcc;
         pix_fmt = vaapi_pix_fmt_from_fourcc(fourcc);
fe498ef5
         if (pix_fmt == AV_PIX_FMT_NONE) {
             av_log(hwdev, AV_LOG_DEBUG, "Format %#x -> unknown.\n",
                    fourcc);
         } else {
551c6775
             av_log(hwdev, AV_LOG_DEBUG, "Format %#x -> %s.\n",
                    fourcc, av_get_pix_fmt_name(pix_fmt));
             ctx->formats[ctx->nb_formats].pix_fmt      = pix_fmt;
             ctx->formats[ctx->nb_formats].image_format = image_list[i];
             ++ctx->nb_formats;
         }
     }
 
852c7ba3
     vendor_string = vaQueryVendorString(hwctx->display);
     if (vendor_string)
         av_log(hwdev, AV_LOG_VERBOSE, "VAAPI driver: %s.\n", vendor_string);
 
a8d51bb4
     if (hwctx->driver_quirks & AV_VAAPI_DRIVER_QUIRK_USER_SET) {
852c7ba3
         av_log(hwdev, AV_LOG_VERBOSE, "Using quirks set by user (%#x).\n",
                hwctx->driver_quirks);
a8d51bb4
     } else {
         // Detect the driver in use and set quirk flags if necessary.
         hwctx->driver_quirks = 0;
         if (vendor_string) {
             for (i = 0; i < FF_ARRAY_ELEMS(vaapi_driver_quirks_table); i++) {
                 if (strstr(vendor_string,
                            vaapi_driver_quirks_table[i].match_string)) {
852c7ba3
                     av_log(hwdev, AV_LOG_VERBOSE, "Matched driver string "
                            "as known nonstandard driver \"%s\", setting "
                            "quirks (%#x).\n",
                            vaapi_driver_quirks_table[i].friendly_name,
                            vaapi_driver_quirks_table[i].quirks);
a8d51bb4
                     hwctx->driver_quirks |=
                         vaapi_driver_quirks_table[i].quirks;
                     break;
                 }
             }
             if (!(i < FF_ARRAY_ELEMS(vaapi_driver_quirks_table))) {
852c7ba3
                 av_log(hwdev, AV_LOG_VERBOSE, "Driver not found in known "
                        "nonstandard list, using standard behaviour.\n");
a8d51bb4
             }
852c7ba3
         } else {
             av_log(hwdev, AV_LOG_VERBOSE, "Driver has no vendor string, "
                    "assuming standard behaviour.\n");
a8d51bb4
         }
     }
 
551c6775
     av_free(image_list);
     return 0;
 fail:
     av_freep(&ctx->formats);
     av_free(image_list);
     return err;
 }
 
 static void vaapi_device_uninit(AVHWDeviceContext *hwdev)
 {
     VAAPIDeviceContext *ctx = hwdev->internal->priv;
 
     av_freep(&ctx->formats);
 }
 
 static void vaapi_buffer_free(void *opaque, uint8_t *data)
 {
     AVHWFramesContext     *hwfc = opaque;
     AVVAAPIDeviceContext *hwctx = hwfc->device_ctx->hwctx;
     VASurfaceID surface_id;
     VAStatus vas;
 
     surface_id = (VASurfaceID)(uintptr_t)data;
 
     vas = vaDestroySurfaces(hwctx->display, &surface_id, 1);
     if (vas != VA_STATUS_SUCCESS) {
         av_log(hwfc, AV_LOG_ERROR, "Failed to destroy surface %#x: "
                "%d (%s).\n", surface_id, vas, vaErrorStr(vas));
     }
 }
 
 static AVBufferRef *vaapi_pool_alloc(void *opaque, int size)
 {
     AVHWFramesContext     *hwfc = opaque;
     VAAPIFramesContext     *ctx = hwfc->internal->priv;
     AVVAAPIDeviceContext *hwctx = hwfc->device_ctx->hwctx;
     AVVAAPIFramesContext  *avfc = hwfc->hwctx;
     VASurfaceID surface_id;
     VAStatus vas;
     AVBufferRef *ref;
 
2b8151c8
     if (hwfc->initial_pool_size > 0 &&
         avfc->nb_surfaces >= hwfc->initial_pool_size)
         return NULL;
 
551c6775
     vas = vaCreateSurfaces(hwctx->display, ctx->rt_format,
                            hwfc->width, hwfc->height,
                            &surface_id, 1,
                            ctx->attributes, ctx->nb_attributes);
     if (vas != VA_STATUS_SUCCESS) {
         av_log(hwfc, AV_LOG_ERROR, "Failed to create surface: "
                "%d (%s).\n", vas, vaErrorStr(vas));
         return NULL;
     }
     av_log(hwfc, AV_LOG_DEBUG, "Created surface %#x.\n", surface_id);
 
     ref = av_buffer_create((uint8_t*)(uintptr_t)surface_id,
                            sizeof(surface_id), &vaapi_buffer_free,
                            hwfc, AV_BUFFER_FLAG_READONLY);
     if (!ref) {
         vaDestroySurfaces(hwctx->display, &surface_id, 1);
         return NULL;
     }
 
     if (hwfc->initial_pool_size > 0) {
         // This is a fixed-size pool, so we must still be in the initial
         // allocation sequence.
         av_assert0(avfc->nb_surfaces < hwfc->initial_pool_size);
         avfc->surface_ids[avfc->nb_surfaces] = surface_id;
         ++avfc->nb_surfaces;
     }
 
     return ref;
 }
 
 static int vaapi_frames_init(AVHWFramesContext *hwfc)
 {
     AVVAAPIFramesContext  *avfc = hwfc->hwctx;
     VAAPIFramesContext     *ctx = hwfc->internal->priv;
     AVVAAPIDeviceContext *hwctx = hwfc->device_ctx->hwctx;
40724026
     const VAAPIFormatDescriptor *desc;
551c6775
     VAImageFormat *expected_format;
     AVBufferRef *test_surface = NULL;
     VASurfaceID test_surface_id;
     VAImage test_image;
     VAStatus vas;
     int err, i;
 
40724026
     desc = vaapi_format_from_pix_fmt(hwfc->sw_format);
     if (!desc) {
551c6775
         av_log(hwfc, AV_LOG_ERROR, "Unsupported format: %s.\n",
                av_get_pix_fmt_name(hwfc->sw_format));
         return AVERROR(EINVAL);
     }
 
     if (!hwfc->pool) {
f2e4fb61
         if (!(hwctx->driver_quirks & AV_VAAPI_DRIVER_QUIRK_SURFACE_ATTRIBUTES)) {
             int need_memory_type = !(hwctx->driver_quirks & AV_VAAPI_DRIVER_QUIRK_ATTRIB_MEMTYPE);
             int need_pixel_format = 1;
             for (i = 0; i < avfc->nb_attributes; i++) {
193e43e6
                 if (avfc->attributes[i].type == VASurfaceAttribMemoryType)
f2e4fb61
                     need_memory_type  = 0;
193e43e6
                 if (avfc->attributes[i].type == VASurfaceAttribPixelFormat)
f2e4fb61
                     need_pixel_format = 0;
             }
             ctx->nb_attributes =
                 avfc->nb_attributes + need_memory_type + need_pixel_format;
551c6775
 
f2e4fb61
             ctx->attributes = av_malloc(ctx->nb_attributes *
551c6775
                                         sizeof(*ctx->attributes));
f2e4fb61
             if (!ctx->attributes) {
                 err = AVERROR(ENOMEM);
                 goto fail;
             }
551c6775
 
f2e4fb61
             for (i = 0; i < avfc->nb_attributes; i++)
                 ctx->attributes[i] = avfc->attributes[i];
             if (need_memory_type) {
                 ctx->attributes[i++] = (VASurfaceAttrib) {
                     .type          = VASurfaceAttribMemoryType,
                     .flags         = VA_SURFACE_ATTRIB_SETTABLE,
                     .value.type    = VAGenericValueTypeInteger,
                     .value.value.i = VA_SURFACE_ATTRIB_MEM_TYPE_VA,
                 };
             }
             if (need_pixel_format) {
                 ctx->attributes[i++] = (VASurfaceAttrib) {
                     .type          = VASurfaceAttribPixelFormat,
                     .flags         = VA_SURFACE_ATTRIB_SETTABLE,
                     .value.type    = VAGenericValueTypeInteger,
40724026
                     .value.value.i = desc->fourcc,
f2e4fb61
                 };
             }
             av_assert0(i == ctx->nb_attributes);
         } else {
             ctx->attributes = NULL;
             ctx->nb_attributes = 0;
551c6775
         }
 
40724026
         ctx->rt_format = desc->rt_format;
551c6775
 
         if (hwfc->initial_pool_size > 0) {
             // This pool will be usable as a render target, so we need to store
             // all of the surface IDs somewhere that vaCreateContext() calls
             // will be able to access them.
             avfc->nb_surfaces = 0;
             avfc->surface_ids = av_malloc(hwfc->initial_pool_size *
                                           sizeof(*avfc->surface_ids));
             if (!avfc->surface_ids) {
                 err = AVERROR(ENOMEM);
                 goto fail;
             }
         } else {
             // This pool allows dynamic sizing, and will not be usable as a
             // render target.
             avfc->nb_surfaces = 0;
             avfc->surface_ids = NULL;
         }
 
         hwfc->internal->pool_internal =
             av_buffer_pool_init2(sizeof(VASurfaceID), hwfc,
                                  &vaapi_pool_alloc, NULL);
         if (!hwfc->internal->pool_internal) {
             av_log(hwfc, AV_LOG_ERROR, "Failed to create VAAPI surface pool.\n");
             err = AVERROR(ENOMEM);
             goto fail;
         }
     }
 
     // Allocate a single surface to test whether vaDeriveImage() is going
     // to work for the specific configuration.
     if (hwfc->pool) {
         test_surface = av_buffer_pool_get(hwfc->pool);
         if (!test_surface) {
             av_log(hwfc, AV_LOG_ERROR, "Unable to allocate a surface from "
                    "user-configured buffer pool.\n");
             err = AVERROR(ENOMEM);
             goto fail;
         }
     } else {
         test_surface = av_buffer_pool_get(hwfc->internal->pool_internal);
         if (!test_surface) {
             av_log(hwfc, AV_LOG_ERROR, "Unable to allocate a surface from "
                    "internal buffer pool.\n");
             err = AVERROR(ENOMEM);
             goto fail;
         }
     }
     test_surface_id = (VASurfaceID)(uintptr_t)test_surface->data;
 
     ctx->derive_works = 0;
 
     err = vaapi_get_image_format(hwfc->device_ctx,
                                  hwfc->sw_format, &expected_format);
     if (err == 0) {
         vas = vaDeriveImage(hwctx->display, test_surface_id, &test_image);
         if (vas == VA_STATUS_SUCCESS) {
             if (expected_format->fourcc == test_image.format.fourcc) {
                 av_log(hwfc, AV_LOG_DEBUG, "Direct mapping possible.\n");
                 ctx->derive_works = 1;
             } else {
                 av_log(hwfc, AV_LOG_DEBUG, "Direct mapping disabled: "
                        "derived image format %08x does not match "
                        "expected format %08x.\n",
                        expected_format->fourcc, test_image.format.fourcc);
             }
             vaDestroyImage(hwctx->display, test_image.image_id);
         } else {
             av_log(hwfc, AV_LOG_DEBUG, "Direct mapping disabled: "
                    "deriving image does not work: "
                    "%d (%s).\n", vas, vaErrorStr(vas));
         }
     } else {
         av_log(hwfc, AV_LOG_DEBUG, "Direct mapping disabled: "
                "image format is not supported.\n");
     }
 
     av_buffer_unref(&test_surface);
     return 0;
 
 fail:
     av_buffer_unref(&test_surface);
     av_freep(&avfc->surface_ids);
     av_freep(&ctx->attributes);
     return err;
 }
 
 static void vaapi_frames_uninit(AVHWFramesContext *hwfc)
 {
     AVVAAPIFramesContext *avfc = hwfc->hwctx;
     VAAPIFramesContext    *ctx = hwfc->internal->priv;
 
     av_freep(&avfc->surface_ids);
     av_freep(&ctx->attributes);
 }
 
 static int vaapi_get_buffer(AVHWFramesContext *hwfc, AVFrame *frame)
 {
     frame->buf[0] = av_buffer_pool_get(hwfc->pool);
     if (!frame->buf[0])
         return AVERROR(ENOMEM);
 
     frame->data[3] = frame->buf[0]->data;
     frame->format  = AV_PIX_FMT_VAAPI;
     frame->width   = hwfc->width;
     frame->height  = hwfc->height;
 
     return 0;
 }
 
 static int vaapi_transfer_get_formats(AVHWFramesContext *hwfc,
                                       enum AVHWFrameTransferDirection dir,
                                       enum AVPixelFormat **formats)
 {
     VAAPIDeviceContext *ctx = hwfc->device_ctx->internal->priv;
6e0e3e1d
     enum AVPixelFormat *pix_fmts;
     int i, k, sw_format_available;
551c6775
 
6e0e3e1d
     sw_format_available = 0;
     for (i = 0; i < ctx->nb_formats; i++) {
         if (ctx->formats[i].pix_fmt == hwfc->sw_format)
             sw_format_available = 1;
     }
551c6775
 
     pix_fmts = av_malloc((ctx->nb_formats + 1) * sizeof(*pix_fmts));
     if (!pix_fmts)
         return AVERROR(ENOMEM);
 
6e0e3e1d
     if (sw_format_available) {
         pix_fmts[0] = hwfc->sw_format;
         k = 1;
     } else {
         k = 0;
     }
551c6775
     for (i = 0; i < ctx->nb_formats; i++) {
6e0e3e1d
         if (ctx->formats[i].pix_fmt == hwfc->sw_format)
551c6775
             continue;
         av_assert0(k < ctx->nb_formats);
         pix_fmts[k++] = ctx->formats[i].pix_fmt;
     }
     pix_fmts[k] = AV_PIX_FMT_NONE;
 
     *formats = pix_fmts;
     return 0;
 }
 
8ad9f9d6
 static void vaapi_unmap_frame(AVHWFramesContext *hwfc,
                               HWMapDescriptor *hwmap)
551c6775
 {
     AVVAAPIDeviceContext *hwctx = hwfc->device_ctx->hwctx;
8ad9f9d6
     VAAPIMapping           *map = hwmap->priv;
551c6775
     VASurfaceID surface_id;
     VAStatus vas;
 
8ad9f9d6
     surface_id = (VASurfaceID)(uintptr_t)hwmap->source->data[3];
551c6775
     av_log(hwfc, AV_LOG_DEBUG, "Unmap surface %#x.\n", surface_id);
 
     vas = vaUnmapBuffer(hwctx->display, map->image.buf);
     if (vas != VA_STATUS_SUCCESS) {
         av_log(hwfc, AV_LOG_ERROR, "Failed to unmap image from surface "
                "%#x: %d (%s).\n", surface_id, vas, vaErrorStr(vas));
     }
 
8ad9f9d6
     if ((map->flags & AV_HWFRAME_MAP_WRITE) &&
         !(map->flags & AV_HWFRAME_MAP_DIRECT)) {
551c6775
         vas = vaPutImage(hwctx->display, surface_id, map->image.image_id,
                          0, 0, hwfc->width, hwfc->height,
                          0, 0, hwfc->width, hwfc->height);
         if (vas != VA_STATUS_SUCCESS) {
             av_log(hwfc, AV_LOG_ERROR, "Failed to write image to surface "
                    "%#x: %d (%s).\n", surface_id, vas, vaErrorStr(vas));
         }
     }
 
     vas = vaDestroyImage(hwctx->display, map->image.image_id);
     if (vas != VA_STATUS_SUCCESS) {
         av_log(hwfc, AV_LOG_ERROR, "Failed to destroy image from surface "
                "%#x: %d (%s).\n", surface_id, vas, vaErrorStr(vas));
     }
 
     av_free(map);
 }
 
 static int vaapi_map_frame(AVHWFramesContext *hwfc,
                            AVFrame *dst, const AVFrame *src, int flags)
 {
     AVVAAPIDeviceContext *hwctx = hwfc->device_ctx->hwctx;
     VAAPIFramesContext *ctx = hwfc->internal->priv;
     VASurfaceID surface_id;
40724026
     const VAAPIFormatDescriptor *desc;
551c6775
     VAImageFormat *image_format;
8ad9f9d6
     VAAPIMapping *map;
551c6775
     VAStatus vas;
     void *address = NULL;
     int err, i;
 
     surface_id = (VASurfaceID)(uintptr_t)src->data[3];
     av_log(hwfc, AV_LOG_DEBUG, "Map surface %#x.\n", surface_id);
 
8ad9f9d6
     if (!ctx->derive_works && (flags & AV_HWFRAME_MAP_DIRECT)) {
551c6775
         // Requested direct mapping but it is not possible.
         return AVERROR(EINVAL);
     }
     if (dst->format == AV_PIX_FMT_NONE)
         dst->format = hwfc->sw_format;
8ad9f9d6
     if (dst->format != hwfc->sw_format && (flags & AV_HWFRAME_MAP_DIRECT)) {
551c6775
         // Requested direct mapping but the formats do not match.
         return AVERROR(EINVAL);
     }
 
     err = vaapi_get_image_format(hwfc->device_ctx, dst->format, &image_format);
     if (err < 0) {
         // Requested format is not a valid output format.
         return AVERROR(EINVAL);
     }
 
8ad9f9d6
     map = av_malloc(sizeof(*map));
551c6775
     if (!map)
         return AVERROR(ENOMEM);
8ad9f9d6
     map->flags = flags;
551c6775
     map->image.image_id = VA_INVALID_ID;
 
     vas = vaSyncSurface(hwctx->display, surface_id);
     if (vas != VA_STATUS_SUCCESS) {
         av_log(hwfc, AV_LOG_ERROR, "Failed to sync surface "
                "%#x: %d (%s).\n", surface_id, vas, vaErrorStr(vas));
         err = AVERROR(EIO);
         goto fail;
     }
 
     // The memory which we map using derive need not be connected to the CPU
     // in a way conducive to fast access.  On Gen7-Gen9 Intel graphics, the
     // memory is mappable but not cached, so normal memcpy()-like access is
     // very slow to read it (but writing is ok).  It is possible to read much
     // faster with a copy routine which is aware of the limitation, but we
     // assume for now that the user is not aware of that and would therefore
     // prefer not to be given direct-mapped memory if they request read access.
8ad9f9d6
     if (ctx->derive_works && dst->format == hwfc->sw_format &&
         ((flags & AV_HWFRAME_MAP_DIRECT) || !(flags & AV_HWFRAME_MAP_READ))) {
551c6775
         vas = vaDeriveImage(hwctx->display, surface_id, &map->image);
         if (vas != VA_STATUS_SUCCESS) {
             av_log(hwfc, AV_LOG_ERROR, "Failed to derive image from "
                    "surface %#x: %d (%s).\n",
                    surface_id, vas, vaErrorStr(vas));
             err = AVERROR(EIO);
             goto fail;
         }
         if (map->image.format.fourcc != image_format->fourcc) {
             av_log(hwfc, AV_LOG_ERROR, "Derive image of surface %#x "
                    "is in wrong format: expected %#08x, got %#08x.\n",
                    surface_id, image_format->fourcc, map->image.format.fourcc);
             err = AVERROR(EIO);
             goto fail;
         }
8ad9f9d6
         map->flags |= AV_HWFRAME_MAP_DIRECT;
551c6775
     } else {
         vas = vaCreateImage(hwctx->display, image_format,
                             hwfc->width, hwfc->height, &map->image);
         if (vas != VA_STATUS_SUCCESS) {
             av_log(hwfc, AV_LOG_ERROR, "Failed to create image for "
                    "surface %#x: %d (%s).\n",
                    surface_id, vas, vaErrorStr(vas));
             err = AVERROR(EIO);
             goto fail;
         }
8ad9f9d6
         if (!(flags & AV_HWFRAME_MAP_OVERWRITE)) {
551c6775
             vas = vaGetImage(hwctx->display, surface_id, 0, 0,
                              hwfc->width, hwfc->height, map->image.image_id);
             if (vas != VA_STATUS_SUCCESS) {
                 av_log(hwfc, AV_LOG_ERROR, "Failed to read image from "
                        "surface %#x: %d (%s).\n",
                        surface_id, vas, vaErrorStr(vas));
                 err = AVERROR(EIO);
                 goto fail;
             }
         }
     }
 
     vas = vaMapBuffer(hwctx->display, map->image.buf, &address);
     if (vas != VA_STATUS_SUCCESS) {
         av_log(hwfc, AV_LOG_ERROR, "Failed to map image from surface "
                "%#x: %d (%s).\n", surface_id, vas, vaErrorStr(vas));
         err = AVERROR(EIO);
         goto fail;
     }
 
8ad9f9d6
     err = ff_hwframe_map_create(src->hw_frames_ctx,
                                 dst, src, &vaapi_unmap_frame, map);
     if (err < 0)
         goto fail;
 
551c6775
     dst->width  = src->width;
     dst->height = src->height;
 
     for (i = 0; i < map->image.num_planes; i++) {
         dst->data[i] = (uint8_t*)address + map->image.offsets[i];
         dst->linesize[i] = map->image.pitches[i];
     }
40724026
 
     desc = vaapi_format_from_fourcc(map->image.format.fourcc);
     if (desc && desc->chroma_planes_swapped) {
551c6775
         // Chroma planes are YVU rather than YUV, so swap them.
         FFSWAP(uint8_t*, dst->data[1], dst->data[2]);
     }
 
     return 0;
 
 fail:
     if (map) {
         if (address)
             vaUnmapBuffer(hwctx->display, map->image.buf);
         if (map->image.image_id != VA_INVALID_ID)
             vaDestroyImage(hwctx->display, map->image.image_id);
         av_free(map);
     }
     return err;
 }
 
 static int vaapi_transfer_data_from(AVHWFramesContext *hwfc,
                                     AVFrame *dst, const AVFrame *src)
 {
     AVFrame *map;
     int err;
 
f62bb216
     if (dst->width > hwfc->width || dst->height > hwfc->height)
         return AVERROR(EINVAL);
 
551c6775
     map = av_frame_alloc();
     if (!map)
         return AVERROR(ENOMEM);
     map->format = dst->format;
 
8ad9f9d6
     err = vaapi_map_frame(hwfc, map, src, AV_HWFRAME_MAP_READ);
551c6775
     if (err)
         goto fail;
 
f62bb216
     map->width  = dst->width;
     map->height = dst->height;
 
551c6775
     err = av_frame_copy(dst, map);
     if (err)
         goto fail;
 
     err = 0;
 fail:
     av_frame_free(&map);
     return err;
 }
 
 static int vaapi_transfer_data_to(AVHWFramesContext *hwfc,
                                   AVFrame *dst, const AVFrame *src)
 {
     AVFrame *map;
     int err;
 
f62bb216
     if (src->width > hwfc->width || src->height > hwfc->height)
         return AVERROR(EINVAL);
 
551c6775
     map = av_frame_alloc();
     if (!map)
         return AVERROR(ENOMEM);
     map->format = src->format;
 
8ad9f9d6
     err = vaapi_map_frame(hwfc, map, dst, AV_HWFRAME_MAP_WRITE | AV_HWFRAME_MAP_OVERWRITE);
551c6775
     if (err)
         goto fail;
 
f62bb216
     map->width  = src->width;
     map->height = src->height;
 
551c6775
     err = av_frame_copy(map, src);
     if (err)
         goto fail;
 
     err = 0;
 fail:
     av_frame_free(&map);
     return err;
 }
 
b2f256a9
 static int vaapi_map_to_memory(AVHWFramesContext *hwfc, AVFrame *dst,
                                const AVFrame *src, int flags)
8ad9f9d6
 {
     int err;
 
     if (dst->format != AV_PIX_FMT_NONE) {
         err = vaapi_get_image_format(hwfc->device_ctx, dst->format, NULL);
         if (err < 0)
             return AVERROR(ENOSYS);
     }
 
     err = vaapi_map_frame(hwfc, dst, src, flags);
     if (err)
         return err;
 
     err = av_frame_copy_props(dst, src);
     if (err)
         return err;
 
     return 0;
 }
 
170c6533
 #if CONFIG_LIBDRM
 
 #define DRM_MAP(va, layers, ...) { \
         VA_FOURCC_ ## va, \
         layers, \
         { __VA_ARGS__ } \
     }
 static const struct {
     uint32_t va_fourcc;
     int   nb_layer_formats;
     uint32_t layer_formats[AV_DRM_MAX_PLANES];
 } vaapi_drm_format_map[] = {
197d298a
 #ifdef DRM_FORMAT_R8
170c6533
     DRM_MAP(NV12, 2, DRM_FORMAT_R8,  DRM_FORMAT_RG88),
197d298a
 #endif
170c6533
     DRM_MAP(NV12, 1, DRM_FORMAT_NV12),
197d298a
 #if defined(VA_FOURCC_P010) && defined(DRM_FORMAT_R16)
170c6533
     DRM_MAP(P010, 2, DRM_FORMAT_R16, DRM_FORMAT_RG1616),
 #endif
b0ece2b3
     DRM_MAP(BGRA, 1, DRM_FORMAT_ARGB8888),
     DRM_MAP(BGRX, 1, DRM_FORMAT_XRGB8888),
     DRM_MAP(RGBA, 1, DRM_FORMAT_ABGR8888),
     DRM_MAP(RGBX, 1, DRM_FORMAT_XBGR8888),
170c6533
 #ifdef VA_FOURCC_ABGR
b0ece2b3
     DRM_MAP(ABGR, 1, DRM_FORMAT_RGBA8888),
     DRM_MAP(XBGR, 1, DRM_FORMAT_RGBX8888),
170c6533
 #endif
b0ece2b3
     DRM_MAP(ARGB, 1, DRM_FORMAT_BGRA8888),
     DRM_MAP(XRGB, 1, DRM_FORMAT_BGRX8888),
170c6533
 };
 #undef DRM_MAP
 
 static void vaapi_unmap_from_drm(AVHWFramesContext *dst_fc,
                                  HWMapDescriptor *hwmap)
 {
     AVVAAPIDeviceContext *dst_dev = dst_fc->device_ctx->hwctx;
 
     VASurfaceID surface_id = (VASurfaceID)(uintptr_t)hwmap->priv;
 
     av_log(dst_fc, AV_LOG_DEBUG, "Destroy surface %#x.\n", surface_id);
 
     vaDestroySurfaces(dst_dev->display, &surface_id, 1);
 }
 
 static int vaapi_map_from_drm(AVHWFramesContext *src_fc, AVFrame *dst,
                               const AVFrame *src, int flags)
 {
     AVHWFramesContext      *dst_fc =
         (AVHWFramesContext*)dst->hw_frames_ctx->data;
     AVVAAPIDeviceContext  *dst_dev = dst_fc->device_ctx->hwctx;
     const AVDRMFrameDescriptor *desc;
40724026
     const VAAPIFormatDescriptor *format_desc;
170c6533
     VASurfaceID surface_id;
     VAStatus vas;
40724026
     uint32_t va_fourcc;
170c6533
     int err, i, j, k;
 
     unsigned long buffer_handle;
     VASurfaceAttribExternalBuffers buffer_desc;
     VASurfaceAttrib attrs[2] = {
         {
             .type  = VASurfaceAttribMemoryType,
             .flags = VA_SURFACE_ATTRIB_SETTABLE,
             .value.type    = VAGenericValueTypeInteger,
             .value.value.i = VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME,
         },
         {
             .type  = VASurfaceAttribExternalBufferDescriptor,
             .flags = VA_SURFACE_ATTRIB_SETTABLE,
             .value.type    = VAGenericValueTypePointer,
             .value.value.p = &buffer_desc,
         }
     };
 
     desc = (AVDRMFrameDescriptor*)src->data[0];
 
     if (desc->nb_objects != 1) {
         av_log(dst_fc, AV_LOG_ERROR, "VAAPI can only map frames "
                "made from a single DRM object.\n");
         return AVERROR(EINVAL);
     }
 
     va_fourcc = 0;
     for (i = 0; i < FF_ARRAY_ELEMS(vaapi_drm_format_map); i++) {
         if (desc->nb_layers != vaapi_drm_format_map[i].nb_layer_formats)
             continue;
         for (j = 0; j < desc->nb_layers; j++) {
             if (desc->layers[j].format !=
                 vaapi_drm_format_map[i].layer_formats[j])
                 break;
         }
         if (j != desc->nb_layers)
             continue;
         va_fourcc = vaapi_drm_format_map[i].va_fourcc;
         break;
     }
     if (!va_fourcc) {
         av_log(dst_fc, AV_LOG_ERROR, "DRM format not supported "
                "by VAAPI.\n");
         return AVERROR(EINVAL);
     }
 
     av_log(dst_fc, AV_LOG_DEBUG, "Map DRM object %d to VAAPI as "
            "%08x.\n", desc->objects[0].fd, va_fourcc);
 
40724026
     format_desc = vaapi_format_from_fourcc(va_fourcc);
     av_assert0(format_desc);
bed670a1
 
170c6533
     buffer_handle = desc->objects[0].fd;
     buffer_desc.pixel_format = va_fourcc;
     buffer_desc.width        = src_fc->width;
     buffer_desc.height       = src_fc->height;
     buffer_desc.data_size    = desc->objects[0].size;
     buffer_desc.buffers      = &buffer_handle;
     buffer_desc.num_buffers  = 1;
     buffer_desc.flags        = 0;
 
     k = 0;
     for (i = 0; i < desc->nb_layers; i++) {
         for (j = 0; j < desc->layers[i].nb_planes; j++) {
             buffer_desc.pitches[k] = desc->layers[i].planes[j].pitch;
             buffer_desc.offsets[k] = desc->layers[i].planes[j].offset;
             ++k;
         }
     }
     buffer_desc.num_planes = k;
 
40724026
     if (format_desc->chroma_planes_swapped &&
         buffer_desc.num_planes == 3) {
         FFSWAP(uint32_t, buffer_desc.pitches[1], buffer_desc.pitches[2]);
         FFSWAP(uint32_t, buffer_desc.offsets[1], buffer_desc.offsets[2]);
     }
 
     vas = vaCreateSurfaces(dst_dev->display, format_desc->rt_format,
170c6533
                            src->width, src->height,
                            &surface_id, 1,
                            attrs, FF_ARRAY_ELEMS(attrs));
     if (vas != VA_STATUS_SUCCESS) {
         av_log(dst_fc, AV_LOG_ERROR, "Failed to create surface from DRM "
                "object: %d (%s).\n", vas, vaErrorStr(vas));
         return AVERROR(EIO);
     }
     av_log(dst_fc, AV_LOG_DEBUG, "Create surface %#x.\n", surface_id);
 
     err = ff_hwframe_map_create(dst->hw_frames_ctx, dst, src,
                                 &vaapi_unmap_from_drm,
                                 (void*)(uintptr_t)surface_id);
     if (err < 0)
         return err;
 
     dst->width   = src->width;
     dst->height  = src->height;
     dst->data[3] = (uint8_t*)(uintptr_t)surface_id;
 
     av_log(dst_fc, AV_LOG_DEBUG, "Mapped DRM object %d to "
            "surface %#x.\n", desc->objects[0].fd, surface_id);
 
     return 0;
 }
b2f256a9
 
c6bbb8cc
 #if VA_CHECK_VERSION(1, 1, 0)
 static void vaapi_unmap_to_drm_esh(AVHWFramesContext *hwfc,
                                    HWMapDescriptor *hwmap)
b2f256a9
 {
     AVDRMFrameDescriptor *drm_desc = hwmap->priv;
     int i;
 
     for (i = 0; i < drm_desc->nb_objects; i++)
         close(drm_desc->objects[i].fd);
 
     av_freep(&drm_desc);
 }
 
c6bbb8cc
 static int vaapi_map_to_drm_esh(AVHWFramesContext *hwfc, AVFrame *dst,
                                 const AVFrame *src, int flags)
b2f256a9
 {
     AVVAAPIDeviceContext *hwctx = hwfc->device_ctx->hwctx;
     VASurfaceID surface_id;
     VAStatus vas;
     VADRMPRIMESurfaceDescriptor va_desc;
     AVDRMFrameDescriptor *drm_desc = NULL;
ca9f13bb
     uint32_t export_flags;
b2f256a9
     int err, i, j;
 
     surface_id = (VASurfaceID)(uintptr_t)src->data[3];
 
ca9f13bb
     export_flags = VA_EXPORT_SURFACE_SEPARATE_LAYERS;
     if (flags & AV_HWFRAME_MAP_READ)
         export_flags |= VA_EXPORT_SURFACE_READ_ONLY;
     if (flags & AV_HWFRAME_MAP_WRITE)
         export_flags |= VA_EXPORT_SURFACE_WRITE_ONLY;
 
b2f256a9
     vas = vaExportSurfaceHandle(hwctx->display, surface_id,
                                 VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2,
ca9f13bb
                                 export_flags, &va_desc);
b2f256a9
     if (vas != VA_STATUS_SUCCESS) {
         if (vas == VA_STATUS_ERROR_UNIMPLEMENTED)
             return AVERROR(ENOSYS);
         av_log(hwfc, AV_LOG_ERROR, "Failed to export surface %#x: "
                "%d (%s).\n", surface_id, vas, vaErrorStr(vas));
         return AVERROR(EIO);
     }
 
     drm_desc = av_mallocz(sizeof(*drm_desc));
     if (!drm_desc) {
         err = AVERROR(ENOMEM);
         goto fail;
     }
 
     // By some bizarre coincidence, these structures are very similar...
     drm_desc->nb_objects = va_desc.num_objects;
     for (i = 0; i < va_desc.num_objects; i++) {
         drm_desc->objects[i].fd   = va_desc.objects[i].fd;
         drm_desc->objects[i].size = va_desc.objects[i].size;
         drm_desc->objects[i].format_modifier =
             va_desc.objects[i].drm_format_modifier;
     }
     drm_desc->nb_layers = va_desc.num_layers;
     for (i = 0; i < va_desc.num_layers; i++) {
         drm_desc->layers[i].format    = va_desc.layers[i].drm_format;
         drm_desc->layers[i].nb_planes = va_desc.layers[i].num_planes;
         for (j = 0; j < va_desc.layers[i].num_planes; j++) {
             drm_desc->layers[i].planes[j].object_index =
                 va_desc.layers[i].object_index[j];
             drm_desc->layers[i].planes[j].offset =
                 va_desc.layers[i].offset[j];
             drm_desc->layers[i].planes[j].pitch =
                 va_desc.layers[i].pitch[j];
         }
     }
 
     err = ff_hwframe_map_create(src->hw_frames_ctx, dst, src,
c6bbb8cc
                                 &vaapi_unmap_to_drm_esh, drm_desc);
b2f256a9
     if (err < 0)
         goto fail;
 
     dst->width   = src->width;
     dst->height  = src->height;
     dst->data[0] = (uint8_t*)drm_desc;
 
     return 0;
 
 fail:
     for (i = 0; i < va_desc.num_objects; i++)
         close(va_desc.objects[i].fd);
     av_freep(&drm_desc);
     return err;
c6bbb8cc
 }
b2f256a9
 #endif
c6bbb8cc
 
92a0a6be
 #if VA_CHECK_VERSION(0, 36, 0)
c6bbb8cc
 typedef struct VAAPIDRMImageBufferMapping {
     VAImage      image;
     VABufferInfo buffer_info;
 
     AVDRMFrameDescriptor drm_desc;
 } VAAPIDRMImageBufferMapping;
 
 static void vaapi_unmap_to_drm_abh(AVHWFramesContext *hwfc,
                                   HWMapDescriptor *hwmap)
 {
     AVVAAPIDeviceContext *hwctx = hwfc->device_ctx->hwctx;
     VAAPIDRMImageBufferMapping *mapping = hwmap->priv;
     VASurfaceID surface_id;
     VAStatus vas;
 
     surface_id = (VASurfaceID)(uintptr_t)hwmap->source->data[3];
     av_log(hwfc, AV_LOG_DEBUG, "Unmap VAAPI surface %#x from DRM.\n",
            surface_id);
 
     // DRM PRIME file descriptors are closed by vaReleaseBufferHandle(),
     // so we shouldn't close them separately.
 
     vas = vaReleaseBufferHandle(hwctx->display, mapping->image.buf);
     if (vas != VA_STATUS_SUCCESS) {
         av_log(hwfc, AV_LOG_ERROR, "Failed to release buffer "
                "handle of image %#x (derived from surface %#x): "
                "%d (%s).\n", mapping->image.buf, surface_id,
                vas, vaErrorStr(vas));
     }
 
     vas = vaDestroyImage(hwctx->display, mapping->image.image_id);
     if (vas != VA_STATUS_SUCCESS) {
         av_log(hwfc, AV_LOG_ERROR, "Failed to destroy image "
                "derived from surface %#x: %d (%s).\n",
                surface_id, vas, vaErrorStr(vas));
     }
 
     av_free(mapping);
 }
 
 static int vaapi_map_to_drm_abh(AVHWFramesContext *hwfc, AVFrame *dst,
                                 const AVFrame *src, int flags)
 {
     AVVAAPIDeviceContext *hwctx = hwfc->device_ctx->hwctx;
     VAAPIDRMImageBufferMapping *mapping = NULL;
     VASurfaceID surface_id;
     VAStatus vas;
     int err, i, p;
 
     surface_id = (VASurfaceID)(uintptr_t)src->data[3];
     av_log(hwfc, AV_LOG_DEBUG, "Map VAAPI surface %#x to DRM.\n",
            surface_id);
 
     mapping = av_mallocz(sizeof(*mapping));
     if (!mapping)
         return AVERROR(ENOMEM);
 
     vas = vaDeriveImage(hwctx->display, surface_id,
                         &mapping->image);
     if (vas != VA_STATUS_SUCCESS) {
         av_log(hwfc, AV_LOG_ERROR, "Failed to derive image from "
                "surface %#x: %d (%s).\n",
                surface_id, vas, vaErrorStr(vas));
         err = AVERROR(EIO);
         goto fail;
     }
 
     for (i = 0; i < FF_ARRAY_ELEMS(vaapi_drm_format_map); i++) {
         if (vaapi_drm_format_map[i].va_fourcc ==
             mapping->image.format.fourcc)
             break;
     }
     if (i >= FF_ARRAY_ELEMS(vaapi_drm_format_map)) {
         av_log(hwfc, AV_LOG_ERROR, "No matching DRM format for "
                "VAAPI format %#x.\n", mapping->image.format.fourcc);
         err = AVERROR(EINVAL);
         goto fail_derived;
     }
 
     mapping->buffer_info.mem_type =
         VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME;
 
     mapping->drm_desc.nb_layers =
         vaapi_drm_format_map[i].nb_layer_formats;
     if (mapping->drm_desc.nb_layers > 1) {
         if (mapping->drm_desc.nb_layers != mapping->image.num_planes) {
             av_log(hwfc, AV_LOG_ERROR, "Image properties do not match "
                    "expected format: got %d planes, but expected %d.\n",
                    mapping->image.num_planes, mapping->drm_desc.nb_layers);
             err = AVERROR(EINVAL);
             goto fail_derived;
         }
 
         for(p = 0; p < mapping->drm_desc.nb_layers; p++) {
             mapping->drm_desc.layers[p] = (AVDRMLayerDescriptor) {
                 .format    = vaapi_drm_format_map[i].layer_formats[p],
                 .nb_planes = 1,
                 .planes[0] = {
                     .object_index = 0,
                     .offset       = mapping->image.offsets[p],
                     .pitch        = mapping->image.pitches[p],
                 },
             };
         }
     } else {
         mapping->drm_desc.layers[0].format =
             vaapi_drm_format_map[i].layer_formats[0];
         mapping->drm_desc.layers[0].nb_planes = mapping->image.num_planes;
         for (p = 0; p < mapping->image.num_planes; p++) {
             mapping->drm_desc.layers[0].planes[p] = (AVDRMPlaneDescriptor) {
                 .object_index = 0,
                 .offset       = mapping->image.offsets[p],
                 .pitch        = mapping->image.pitches[p],
             };
         }
     }
 
     vas = vaAcquireBufferHandle(hwctx->display, mapping->image.buf,
                                 &mapping->buffer_info);
     if (vas != VA_STATUS_SUCCESS) {
         av_log(hwfc, AV_LOG_ERROR, "Failed to get buffer "
                "handle from image %#x (derived from surface %#x): "
                "%d (%s).\n", mapping->image.buf, surface_id,
                vas, vaErrorStr(vas));
         err = AVERROR(EIO);
         goto fail_derived;
     }
 
     av_log(hwfc, AV_LOG_DEBUG, "DRM PRIME fd is %ld.\n",
            mapping->buffer_info.handle);
 
     mapping->drm_desc.nb_objects = 1;
     mapping->drm_desc.objects[0] = (AVDRMObjectDescriptor) {
         .fd   = mapping->buffer_info.handle,
         .size = mapping->image.data_size,
         // There is no way to get the format modifier with this API.
         .format_modifier = DRM_FORMAT_MOD_INVALID,
     };
 
     err = ff_hwframe_map_create(src->hw_frames_ctx,
                                 dst, src, &vaapi_unmap_to_drm_abh,
                                 mapping);
     if (err < 0)
         goto fail_mapped;
 
     dst->data[0] = (uint8_t*)&mapping->drm_desc;
     dst->width   = src->width;
     dst->height  = src->height;
 
     return 0;
 
 fail_mapped:
     vaReleaseBufferHandle(hwctx->display, mapping->image.buf);
 fail_derived:
     vaDestroyImage(hwctx->display, mapping->image.image_id);
 fail:
     av_freep(&mapping);
     return err;
b2f256a9
 }
92a0a6be
 #endif
c6bbb8cc
 
 static int vaapi_map_to_drm(AVHWFramesContext *hwfc, AVFrame *dst,
                             const AVFrame *src, int flags)
 {
 #if VA_CHECK_VERSION(1, 1, 0)
     int err;
     err = vaapi_map_to_drm_esh(hwfc, dst, src, flags);
     if (err != AVERROR(ENOSYS))
         return err;
170c6533
 #endif
92a0a6be
 #if VA_CHECK_VERSION(0, 36, 0)
c6bbb8cc
     return vaapi_map_to_drm_abh(hwfc, dst, src, flags);
92a0a6be
 #endif
     return AVERROR(ENOSYS);
c6bbb8cc
 }
 
 #endif /* CONFIG_LIBDRM */
170c6533
 
 static int vaapi_map_to(AVHWFramesContext *hwfc, AVFrame *dst,
                         const AVFrame *src, int flags)
 {
     switch (src->format) {
 #if CONFIG_LIBDRM
     case AV_PIX_FMT_DRM_PRIME:
         return vaapi_map_from_drm(hwfc, dst, src, flags);
 #endif
     default:
         return AVERROR(ENOSYS);
     }
 }
 
b2f256a9
 static int vaapi_map_from(AVHWFramesContext *hwfc, AVFrame *dst,
                           const AVFrame *src, int flags)
 {
     switch (dst->format) {
 #if CONFIG_LIBDRM
     case AV_PIX_FMT_DRM_PRIME:
         return vaapi_map_to_drm(hwfc, dst, src, flags);
 #endif
     default:
         return vaapi_map_to_memory(hwfc, dst, src, flags);
     }
 }
 
b8bf9194
 static void vaapi_device_free(AVHWDeviceContext *ctx)
 {
     AVVAAPIDeviceContext *hwctx = ctx->hwctx;
     VAAPIDevicePriv      *priv  = ctx->user_opaque;
 
     if (hwctx->display)
         vaTerminate(hwctx->display);
 
 #if HAVE_VAAPI_X11
     if (priv->x11_display)
         XCloseDisplay(priv->x11_display);
 #endif
 
     if (priv->drm_fd >= 0)
         close(priv->drm_fd);
 
     av_freep(&priv);
 }
 
f3602875
 #if CONFIG_VAAPI_1
 static void vaapi_device_log_error(void *context, const char *message)
 {
     AVHWDeviceContext *ctx = context;
 
     av_log(ctx, AV_LOG_ERROR, "libva: %s", message);
 }
 
 static void vaapi_device_log_info(void *context, const char *message)
 {
     AVHWDeviceContext *ctx = context;
 
     av_log(ctx, AV_LOG_VERBOSE, "libva: %s", message);
 }
 #endif
 
5f397886
 static int vaapi_device_connect(AVHWDeviceContext *ctx,
                                 VADisplay display)
 {
     AVVAAPIDeviceContext *hwctx = ctx->hwctx;
     int major, minor;
     VAStatus vas;
 
f3602875
 #if CONFIG_VAAPI_1
     vaSetErrorCallback(display, &vaapi_device_log_error, ctx);
     vaSetInfoCallback (display, &vaapi_device_log_info,  ctx);
 #endif
 
5f397886
     hwctx->display = display;
 
     vas = vaInitialize(display, &major, &minor);
     if (vas != VA_STATUS_SUCCESS) {
         av_log(ctx, AV_LOG_ERROR, "Failed to initialise VAAPI "
                "connection: %d (%s).\n", vas, vaErrorStr(vas));
         return AVERROR(EIO);
     }
     av_log(ctx, AV_LOG_VERBOSE, "Initialised VAAPI connection: "
            "version %d.%d\n", major, minor);
 
     return 0;
 }
 
b8bf9194
 static int vaapi_device_create(AVHWDeviceContext *ctx, const char *device,
                                AVDictionary *opts, int flags)
 {
     VAAPIDevicePriv *priv;
5f397886
     VADisplay display = NULL;
d2141a9b
     const AVDictionaryEntry *ent;
     int try_drm, try_x11, try_all;
b8bf9194
 
     priv = av_mallocz(sizeof(*priv));
     if (!priv)
         return AVERROR(ENOMEM);
 
     priv->drm_fd = -1;
 
     ctx->user_opaque = priv;
     ctx->free        = vaapi_device_free;
 
d2141a9b
     ent = av_dict_get(opts, "connection_type", NULL, 0);
     if (ent) {
         try_all = try_drm = try_x11 = 0;
         if (!strcmp(ent->value, "drm")) {
             try_drm = 1;
         } else if (!strcmp(ent->value, "x11")) {
             try_x11 = 1;
         } else {
             av_log(ctx, AV_LOG_ERROR, "Invalid connection type %s.\n",
                    ent->value);
             return AVERROR(EINVAL);
         }
     } else {
         try_all = 1;
         try_drm = HAVE_VAAPI_DRM;
         try_x11 = HAVE_VAAPI_X11;
     }
 
b8bf9194
 #if HAVE_VAAPI_DRM
6b6b8a63
     while (!display && try_drm) {
         // If the device is specified, try to open it as a DRM device node.
         // If not, look for a usable render node, possibly restricted to those
         // using a specified kernel driver.
         int loglevel = try_all ? AV_LOG_VERBOSE : AV_LOG_ERROR;
         if (device) {
             priv->drm_fd = open(device, O_RDWR);
             if (priv->drm_fd < 0) {
                 av_log(ctx, loglevel, "Failed to open %s as "
                        "DRM device node.\n", device);
                 break;
             }
b8bf9194
         } else {
6b6b8a63
             char path[64];
             int n, max_devices = 8;
1498e394
 #if CONFIG_LIBDRM
             const AVDictionaryEntry *kernel_driver;
6b6b8a63
             kernel_driver = av_dict_get(opts, "kernel_driver", NULL, 0);
1498e394
 #endif
6b6b8a63
             for (n = 0; n < max_devices; n++) {
                 snprintf(path, sizeof(path),
                          "/dev/dri/renderD%d", 128 + n);
                 priv->drm_fd = open(path, O_RDWR);
                 if (priv->drm_fd < 0) {
                     av_log(ctx, AV_LOG_VERBOSE, "Cannot open "
                            "DRM render node for device %d.\n", n);
                     break;
                 }
 #if CONFIG_LIBDRM
                 if (kernel_driver) {
                     drmVersion *info;
                     info = drmGetVersion(priv->drm_fd);
                     if (strcmp(kernel_driver->value, info->name)) {
                         av_log(ctx, AV_LOG_VERBOSE, "Ignoring device %d "
                                "with non-matching kernel driver (%s).\n",
                                n, info->name);
                         drmFreeVersion(info);
                         close(priv->drm_fd);
                         priv->drm_fd = -1;
                         continue;
                     }
                     av_log(ctx, AV_LOG_VERBOSE, "Trying to use "
                            "DRM render node for device %d, "
                            "with matching kernel driver (%s).\n",
                            n, info->name);
                     drmFreeVersion(info);
                 } else
 #endif
                 {
                     av_log(ctx, AV_LOG_VERBOSE, "Trying to use "
                            "DRM render node for device %d.\n", n);
                 }
                 break;
b8bf9194
             }
6b6b8a63
             if (n >= max_devices)
                 break;
         }
b8bf9194
 
6b6b8a63
         display = vaGetDisplayDRM(priv->drm_fd);
         if (!display) {
             av_log(ctx, AV_LOG_VERBOSE, "Cannot open a VA display "
                    "from DRM device %s.\n", device);
             return AVERROR_EXTERNAL;
b8bf9194
         }
6b6b8a63
         break;
b8bf9194
     }
 #endif
 
0b4696fb
 #if HAVE_VAAPI_X11
     if (!display && try_x11) {
         // Try to open the device as an X11 display.
         priv->x11_display = XOpenDisplay(device);
         if (!priv->x11_display) {
             av_log(ctx, AV_LOG_VERBOSE, "Cannot open X11 display "
                    "%s.\n", XDisplayName(device));
         } else {
             display = vaGetDisplay(priv->x11_display);
             if (!display) {
                 av_log(ctx, AV_LOG_ERROR, "Cannot open a VA display "
                        "from X11 display %s.\n", XDisplayName(device));
                 return AVERROR_UNKNOWN;
             }
 
             av_log(ctx, AV_LOG_VERBOSE, "Opened VA display via "
                    "X11 display %s.\n", XDisplayName(device));
         }
     }
 #endif
 
b8bf9194
     if (!display) {
d2141a9b
         if (device)
             av_log(ctx, AV_LOG_ERROR, "No VA display found for "
                    "device %s.\n", device);
         else
             av_log(ctx, AV_LOG_ERROR, "No VA display found for "
                    "any default device.\n");
b8bf9194
         return AVERROR(EINVAL);
     }
 
7f3f5a24
     ent = av_dict_get(opts, "driver", NULL, 0);
     if (ent) {
 #if VA_CHECK_VERSION(0, 38, 0)
         VAStatus vas;
         vas = vaSetDriverName(display, ent->value);
         if (vas != VA_STATUS_SUCCESS) {
             av_log(ctx, AV_LOG_ERROR, "Failed to set driver name to "
                    "%s: %d (%s).\n", ent->value, vas, vaErrorStr(vas));
             vaTerminate(display);
             return AVERROR_EXTERNAL;
         }
 #else
         av_log(ctx, AV_LOG_WARNING, "Driver name setting is not "
                "supported with this VAAPI version.\n");
 #endif
     }
 
5f397886
     return vaapi_device_connect(ctx, display);
b8bf9194
 }
 
170c6533
 static int vaapi_device_derive(AVHWDeviceContext *ctx,
2e08b394
                                AVHWDeviceContext *src_ctx,
                                AVDictionary *opts, int flags)
170c6533
 {
389f4c3e
 #if HAVE_VAAPI_DRM
170c6533
     if (src_ctx->type == AV_HWDEVICE_TYPE_DRM) {
         AVDRMDeviceContext *src_hwctx = src_ctx->hwctx;
         VADisplay *display;
         VAAPIDevicePriv *priv;
bc9b6358
         int fd;
170c6533
 
         if (src_hwctx->fd < 0) {
             av_log(ctx, AV_LOG_ERROR, "DRM instance requires an associated "
                    "device to derive a VA display from.\n");
             return AVERROR(EINVAL);
         }
 
bc9b6358
 #if CONFIG_LIBDRM
         {
             int node_type = drmGetNodeTypeFromFd(src_hwctx->fd);
             char *render_node;
             if (node_type < 0) {
                 av_log(ctx, AV_LOG_ERROR, "DRM instance fd does not appear "
                        "to refer to a DRM device.\n");
                 return AVERROR(EINVAL);
             }
             if (node_type == DRM_NODE_RENDER) {
                 fd = src_hwctx->fd;
             } else {
                 render_node = drmGetRenderDeviceNameFromFd(src_hwctx->fd);
                 if (!render_node) {
                     av_log(ctx, AV_LOG_ERROR, "Failed to find a render node "
                            "matching the DRM device.\n");
                     return AVERROR(ENODEV);
                 }
                 fd = open(render_node, O_RDWR);
                 if (fd < 0) {
                     av_log(ctx, AV_LOG_ERROR, "Failed to open render node %s"
                            "matching the DRM device.\n", render_node);
                     free(render_node);
                     return AVERROR(errno);
                 }
                 av_log(ctx, AV_LOG_VERBOSE, "Using render node %s in place "
                        "of non-render DRM device.\n", render_node);
                 free(render_node);
             }
         }
 #else
         fd = src_hwctx->fd;
 #endif
 
170c6533
         priv = av_mallocz(sizeof(*priv));
         if (!priv)
             return AVERROR(ENOMEM);
 
bc9b6358
         if (fd == src_hwctx->fd) {
             // The fd is inherited from the source context and we are holding
             // a reference to that, we don't want to close it from here.
             priv->drm_fd = -1;
         } else {
             priv->drm_fd = fd;
         }
170c6533
 
         ctx->user_opaque = priv;
         ctx->free        = &vaapi_device_free;
 
bc9b6358
         display = vaGetDisplayDRM(fd);
170c6533
         if (!display) {
             av_log(ctx, AV_LOG_ERROR, "Failed to open a VA display from "
                    "DRM device.\n");
             return AVERROR(EIO);
         }
 
5f397886
         return vaapi_device_connect(ctx, display);
170c6533
     }
 #endif
     return AVERROR(ENOSYS);
 }
 
551c6775
 const HWContextType ff_hwcontext_type_vaapi = {
     .type                   = AV_HWDEVICE_TYPE_VAAPI,
     .name                   = "VAAPI",
 
     .device_hwctx_size      = sizeof(AVVAAPIDeviceContext),
     .device_priv_size       = sizeof(VAAPIDeviceContext),
     .device_hwconfig_size   = sizeof(AVVAAPIHWConfig),
     .frames_hwctx_size      = sizeof(AVVAAPIFramesContext),
     .frames_priv_size       = sizeof(VAAPIFramesContext),
 
b8bf9194
     .device_create          = &vaapi_device_create,
170c6533
     .device_derive          = &vaapi_device_derive,
551c6775
     .device_init            = &vaapi_device_init,
     .device_uninit          = &vaapi_device_uninit,
     .frames_get_constraints = &vaapi_frames_get_constraints,
     .frames_init            = &vaapi_frames_init,
     .frames_uninit          = &vaapi_frames_uninit,
     .frames_get_buffer      = &vaapi_get_buffer,
     .transfer_get_formats   = &vaapi_transfer_get_formats,
     .transfer_data_to       = &vaapi_transfer_data_to,
     .transfer_data_from     = &vaapi_transfer_data_from,
170c6533
     .map_to                 = &vaapi_map_to,
8ad9f9d6
     .map_from               = &vaapi_map_from,
551c6775
 
     .pix_fmts = (const enum AVPixelFormat[]) {
         AV_PIX_FMT_VAAPI,
         AV_PIX_FMT_NONE
     },
 };