[FFmpeg-devel] [PATCH v6 1/6] libavcodec: VAAPI support infrastructure

Mark Thompson sw at jkqxz.net
Sun Feb 7 22:53:39 CET 2016


---
 configure                  |   5 +
 libavcodec/Makefile        |   1 +
 libavcodec/vaapi_support.c | 852 +++++++++++++++++++++++++++++++++++++++++++++
 libavcodec/vaapi_support.h | 284 +++++++++++++++
 4 files changed, 1142 insertions(+)
 create mode 100644 libavcodec/vaapi_support.c
 create mode 100644 libavcodec/vaapi_support.h

diff --git a/configure b/configure
index 1000cb1..0880569 100755
--- a/configure
+++ b/configure
@@ -2037,6 +2037,7 @@ CONFIG_EXTRA="
     texturedsp
     texturedspenc
     tpeldsp
+    vaapi_recent
     videodsp
     vp3dsp
     vp56dsp
@@ -5770,6 +5771,10 @@ enabled vaapi &&
     check_lib va/va.h vaInitialize -lva ||
     disable vaapi

+enabled vaapi &&
+    check_code cc va/va.h "vaCreateSurfaces(0, 0, 0, 0, 0, 0, 0, 0)" &&
+    enable vaapi_recent
+
 enabled vaapi && enabled xlib &&
     check_lib2 "va/va.h va/va_x11.h" vaGetDisplay -lva -lva-x11 &&
     enable vaapi_x11
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 941057b..64c68b7 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -719,6 +719,7 @@ OBJS-$(CONFIG_ADPCM_YAMAHA_ENCODER)       += adpcmenc.o adpcm_data.o
 OBJS-$(CONFIG_D3D11VA)                    += dxva2.o
 OBJS-$(CONFIG_DXVA2)                      += dxva2.o
 OBJS-$(CONFIG_VAAPI)                      += vaapi.o
+OBJS-$(CONFIG_VAAPI_RECENT)               += vaapi_support.o
 OBJS-$(CONFIG_VDA)                        += vda.o videotoolbox.o
 OBJS-$(CONFIG_VIDEOTOOLBOX)               += videotoolbox.o
 OBJS-$(CONFIG_VDPAU)                      += vdpau.o
diff --git a/libavcodec/vaapi_support.c b/libavcodec/vaapi_support.c
new file mode 100644
index 0000000..6be4669
--- /dev/null
+++ b/libavcodec/vaapi_support.c
@@ -0,0 +1,852 @@
+/*
+ * VAAPI helper functions.
+ *
+ * Copyright (C) 2016 Mark Thompson <mrt at jkqxz.net>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * 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.
+ *
+ * FFmpeg 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "vaapi_support.h"
+
+#include "libavutil/avassert.h"
+#include "libavutil/imgutils.h"
+#include "libavutil/pixfmt.h"
+
+#define AV_VAAPI_MAX_SURFACES 64
+
+
+static const AVClass vaapi_class = {
+    .class_name = "vaapi",
+    .item_name  = av_default_item_name,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+AVVAAPIHardwareContext *av_vaapi_alloc_hardware_context(void)
+{
+    AVVAAPIHardwareContext *hw_ctx =
+        av_mallocz(sizeof(AVVAAPIHardwareContext));
+    if(!hw_ctx)
+        return NULL;
+
+    hw_ctx->class = &vaapi_class;
+    return hw_ctx;
+}
+
+void av_vaapi_lock_hardware_context(AVVAAPIHardwareContext *ctx)
+{
+    if(ctx->lock)
+        ctx->lock(ctx->lock_user_context);
+}
+
+void av_vaapi_unlock_hardware_context(AVVAAPIHardwareContext *ctx)
+{
+    if(ctx->unlock)
+        ctx->unlock(ctx->lock_user_context);
+}
+
+
+typedef struct AVVAAPISurfacePool {
+    AVVAAPIHardwareContext *hardware_context;
+
+    int frame_count;
+    AVFrame *frames[AV_VAAPI_MAX_SURFACES];
+} AVVAAPISurfacePool;
+
+AVVAAPISurfacePool *av_vaapi_alloc_surface_pool(AVVAAPIHardwareContext *hw_ctx)
+{
+    AVVAAPISurfacePool *pool = av_mallocz(sizeof(AVVAAPISurfacePool));
+    if(!pool)
+        return NULL;
+
+    pool->hardware_context = hw_ctx;
+    return pool;
+}
+
+
+typedef struct AVVAAPIPipelineContext {
+    const AVClass *class;
+
+    AVVAAPIHardwareContext *hardware_context;
+
+    VAConfigID config_id;
+    VAContextID context_id;
+} AVVAAPIPipelineContext;
+
+AVVAAPIPipelineContext *av_vaapi_alloc_pipeline_context(AVVAAPIHardwareContext *hw_ctx)
+{
+    AVVAAPIPipelineContext *ctx =
+        av_mallocz(sizeof(AVVAAPIPipelineContext));
+    if(!ctx)
+        return NULL;
+
+    ctx->class = &vaapi_class;
+    ctx->hardware_context = hw_ctx;
+    return ctx;
+}
+
+
+typedef struct AVVAAPISurface {
+    VASurfaceID id;
+    AVVAAPIHardwareContext *hardware_context;
+
+    VAImage image;
+    int derive_doesnt_work;
+    int mapping_flags;
+    void *mapped_address;
+} AVVAAPISurface;
+
+static AVVAAPISurface *vaapi_get_surface(const AVFrame *frame)
+{
+    av_assert0(frame);
+    av_assert0(frame->buf[0]);
+    av_assert0(frame->buf[0]->data);
+    return (AVVAAPISurface*)frame->buf[0]->data;
+}
+
+static AVVAAPISurfaceConfig *vaapi_get_surface_config(const AVFrame *frame)
+{
+    av_assert0(frame);
+    av_assert0(frame->buf[1]);
+    av_assert0(frame->buf[1]->data);
+    return (AVVAAPISurfaceConfig*)frame->buf[1]->data;
+}
+
+static void vaapi_surface_free(void *opaque, uint8_t *data)
+{
+    AVVAAPISurface *surface = (AVVAAPISurface*)data;
+    AVVAAPIHardwareContext *hw_ctx = surface->hardware_context;
+    VAStatus vas;
+
+    av_vaapi_lock_hardware_context(hw_ctx);
+
+    vas = vaDestroySurfaces(surface->hardware_context->display,
+                            &surface->id, 1);
+    if(vas != VA_STATUS_SUCCESS) {
+        av_log(hw_ctx, AV_LOG_ERROR, "Failed to destroy surface: "
+               "%d (%s).\n", vas, vaErrorStr(vas));
+    }
+
+    av_free(surface);
+
+    av_vaapi_unlock_hardware_context(hw_ctx);
+}
+
+int av_vaapi_surface_pool_init(AVVAAPISurfacePool *pool,
+                               AVVAAPISurfaceConfig *config,
+                               int surface_count)
+{
+    AVVAAPIHardwareContext *hw_ctx = pool->hardware_context;
+    AVBufferRef *config_buffer;
+    AVVAAPISurface *surface;
+    AVFrame *frame;
+    VASurfaceID surface_id;
+    VAStatus vas;
+    VASurfaceAttrib *attr_list;
+    int attr_count, extra_attributes = 2;
+    int have_memory_attribute = 0, have_format_attribute = 0;
+    int i, err;
+    unsigned int rt_format;
+
+    if(surface_count > AV_VAAPI_MAX_SURFACES)
+        return AVERROR(EINVAL);
+
+    rt_format = av_vaapi_rt_format(config->va_format.fourcc);
+    if(!rt_format) {
+        av_log(hw_ctx, AV_LOG_ERROR, "VA fourcc #%08x is not supported.\n",
+               config->va_format.fourcc);
+        return AVERROR(EINVAL);
+    }
+
+    for(i = 0; i < config->attribute_count; i++) {
+        if(config->attributes[i].type == VASurfaceAttribMemoryType) {
+            have_memory_attribute = 1;
+            --extra_attributes;
+        }
+        if(config->attributes[i].type == VASurfaceAttribPixelFormat) {
+            have_format_attribute = 1;
+            --extra_attributes;
+        }
+    }
+
+    pool->frame_count = surface_count;
+
+    config_buffer = av_buffer_alloc(sizeof(*config) +
+        sizeof(VASurfaceAttrib) * (config->attribute_count + extra_attributes));
+    if(!config_buffer)
+        return AVERROR(ENOMEM);
+    memcpy(config_buffer->data, config, sizeof(*config));
+    config = (AVVAAPISurfaceConfig*)config_buffer->data;
+    attr_list = (VASurfaceAttrib*)(config + 1);
+    for(i = 0; i < config->attribute_count; i++)
+        attr_list[i] = config->attributes[i];
+    if(!have_memory_attribute) {
+        attr_list[i].type = VASurfaceAttribMemoryType;
+        attr_list[i].flags = VA_SURFACE_ATTRIB_SETTABLE;
+        attr_list[i].value.type = VAGenericValueTypeInteger;
+        attr_list[i].value.value.i = VA_SURFACE_ATTRIB_MEM_TYPE_VA;
+        ++i;
+    }
+    if(!have_format_attribute) {
+        attr_list[i].type = VASurfaceAttribPixelFormat;
+        attr_list[i].flags = VA_SURFACE_ATTRIB_SETTABLE;
+        attr_list[i].value.type = VAGenericValueTypeInteger;
+        attr_list[i].value.value.i = config->va_format.fourcc;
+        ++i;
+    }
+    attr_count = i;
+
+    av_vaapi_lock_hardware_context(hw_ctx);
+
+    for(i = 0; i < pool->frame_count; i++) {
+        frame = av_frame_alloc();
+        if(!frame) {
+            err = AVERROR(ENOMEM);
+            goto fail;
+        }
+        surface = av_mallocz(sizeof(*surface));
+        if(!surface) {
+            err = AVERROR(ENOMEM);
+            goto fail;
+        }
+
+        surface->hardware_context = hw_ctx;
+
+        vas = vaCreateSurfaces(hw_ctx->display, rt_format,
+                               config->width, config->height,
+                               &surface_id, 1, attr_list, attr_count);
+        if(vas != VA_STATUS_SUCCESS) {
+            av_log(hw_ctx, AV_LOG_ERROR, "Failed to create surface: "
+                   "%d (%s).\n", vas, vaErrorStr(vas));
+            err = AVERROR_EXTERNAL;
+            goto fail;
+        }
+
+        surface->id = surface_id;
+        frame->buf[0] = av_buffer_create((uint8_t*)surface, sizeof(*surface),
+                                         &vaapi_surface_free,
+                                         0, AV_BUFFER_FLAG_READONLY);
+        if(!frame->buf[0]) {
+            err = AVERROR(ENOMEM);
+            goto fail;
+        }
+        surface = 0;
+
+        frame->buf[1] = av_buffer_ref(config_buffer);
+        if(!frame->buf[1]) {
+            err = AVERROR(ENOMEM);
+            goto fail;
+        }
+
+        frame->data[3] = (uint8_t*)(uintptr_t)surface_id;
+
+        frame->format = AV_PIX_FMT_VAAPI;
+        frame->width  = config->width;
+        frame->height = config->height;
+
+        pool->frames[i] = frame;
+        frame = 0;
+    }
+
+    for(; i < FF_ARRAY_ELEMS(pool->frames); i++)
+        pool->frames[i] = 0;
+
+    av_buffer_unref(&config_buffer);
+
+    av_log(hw_ctx, AV_LOG_DEBUG, "Surface pool initialised: %u surfaces "
+           "of %ux%u.\n", pool->frame_count, config->width, config->height);
+    av_vaapi_unlock_hardware_context(hw_ctx);
+    return 0;
+
+  fail:
+    if(surface) {
+        if(surface->id)
+            vaDestroySurfaces(hw_ctx->display, &surface->id, 1);
+        av_free(surface);
+    }
+    if(frame)
+        av_frame_free(&frame);
+    for(--i; i >= 0; i--)
+        av_frame_free(&pool->frames[i]);
+    av_vaapi_unlock_hardware_context(hw_ctx);
+    return err;
+}
+
+int av_vaapi_surface_pool_uninit(AVVAAPISurfacePool *pool)
+
+{
+    int i;
+
+    av_vaapi_lock_hardware_context(pool->hardware_context);
+
+    for(i = 0; i < FF_ARRAY_ELEMS(pool->frames); i++) {
+        if(pool->frames[i])
+            av_frame_free(&pool->frames[i]);
+    }
+
+    av_vaapi_unlock_hardware_context(pool->hardware_context);
+
+    return 0;
+}
+
+int av_vaapi_surface_pool_get(AVVAAPISurfacePool *pool, AVFrame *target)
+{
+    AVFrame *frame = 0;
+    int i, err;
+
+    av_vaapi_lock_hardware_context(pool->hardware_context);
+
+    for(i = 0; i < FF_ARRAY_ELEMS(pool->frames); i++) {
+        if(!pool->frames[i])
+            break;
+
+        if(av_buffer_get_ref_count(pool->frames[i]->buf[0]) == 1) {
+            frame = pool->frames[i];
+            break;
+        }
+    }
+
+    if(frame) {
+        target->data[3] = frame->data[3];
+        target->buf[0] = av_buffer_ref(frame->buf[0]);
+        target->buf[1] = av_buffer_ref(frame->buf[1]);
+        if(!target->buf[0] || !target->buf[1])
+            err = AVERROR(ENOMEM);
+        else
+            err = 0;
+    } else {
+        err = AVERROR(ENOMEM);
+    }
+
+    av_vaapi_unlock_hardware_context(pool->hardware_context);
+
+    return err;
+}
+
+int av_vaapi_map_frame(AVFrame *frame, int flags)
+{
+    AVVAAPISurface *surface = vaapi_get_surface(frame);
+    AVVAAPISurfaceConfig *config = vaapi_get_surface_config(frame);
+    AVVAAPIHardwareContext *hw_ctx = surface->hardware_context;
+    VAImage *image = &surface->image;
+    VAStatus vas;
+    int i, err;
+    int derive;
+    void *address;
+
+    if(surface->mapped_address) {
+        av_log(hw_ctx, AV_LOG_ERROR, "Surface %#x already mapped.\n",
+               surface->id);
+        return AVERROR(EINVAL);
+    }
+
+    av_vaapi_lock_hardware_context(hw_ctx);
+
+    vas = vaSyncSurface(hw_ctx->display, surface->id);
+    if(vas != VA_STATUS_SUCCESS) {
+        av_log(hw_ctx, AV_LOG_ERROR, "Failed to sync surface "
+               "%#x: %d (%s).\n", surface->id, vas, vaErrorStr(vas));
+        err = AVERROR_EXTERNAL;
+        goto fail;
+    }
+
+    // On current Intel drivers, derive gives you memory which is very slow
+    // to read normally.  Assume for now that a user who asks for read access
+    // but doesn't explicitly request direct mapping is not going to be
+    // optimised for such, so don't use derive in that case.
+    if(!surface->derive_doesnt_work &&
+       (flags & (AV_VAAPI_MAP_DIRECT | AV_VAAPI_MAP_WRITE))) {
+        derive = 1;
+
+        vas = vaDeriveImage(hw_ctx->display,
+                            surface->id, image);
+        if(vas != VA_STATUS_SUCCESS) {
+            av_log(hw_ctx, AV_LOG_VERBOSE, "Failed to derive image from "
+                   "surface %#x: %d (%s).\n",
+                   surface->id, vas, vaErrorStr(vas));
+            derive = 0;
+        } else if(image->format.fourcc != config->va_format.fourcc) {
+            av_log(hw_ctx, AV_LOG_WARNING, "Derived image of surface %#x "
+                   "is in wrong format: expected %#08x, got %#08x.\n",
+                   surface->id, config->va_format.fourcc,
+                   image->format.fourcc);
+            vas = vaDestroyImage(hw_ctx->display, image->image_id);
+            if(vas != VA_STATUS_SUCCESS) {
+                av_log(hw_ctx, AV_LOG_ERROR, "Failed to destroy incorrect "
+                       "image for surface %#x: %d (%s).\n",
+                       surface->id, vas, vaErrorStr(vas));
+            }
+            derive = 0;
+        }
+        if(!derive) {
+            if(flags & AV_VAAPI_MAP_DIRECT) {
+                // User requested direct mapping, but we can't do it.
+                err = AVERROR_EXTERNAL;
+                goto fail;
+            }
+            surface->derive_doesnt_work = 1;
+        }
+    } else {
+        derive = 0;
+    }
+    if(!derive) {
+        vas = vaCreateImage(hw_ctx->display, &config->va_format,
+                            config->width, config->height, image);
+        if(vas != VA_STATUS_SUCCESS) {
+            av_log(hw_ctx, AV_LOG_ERROR, "Failed to create image for "
+                   "surface %#x: %d (%s).\n",
+                   surface->id, vas, vaErrorStr(vas));
+            err = AVERROR_EXTERNAL;
+            goto fail;
+        }
+
+        if(flags & AV_VAAPI_MAP_READ) {
+            vas = vaGetImage(hw_ctx->display, surface->id, 0, 0,
+                             config->width, config->height, image->image_id);
+            if(vas != VA_STATUS_SUCCESS) {
+                av_log(hw_ctx, AV_LOG_ERROR, "Failed to get image for "
+                       "surface %#x: %d (%s).\n",
+                       surface->id, vas, vaErrorStr(vas));
+                err = AVERROR_EXTERNAL;
+                goto fail_image;
+            }
+        }
+    }
+
+    vas = vaMapBuffer(hw_ctx->display, image->buf, &address);
+    if(vas != VA_STATUS_SUCCESS) {
+        av_log(hw_ctx, AV_LOG_ERROR, "Failed to map image from surface "
+               "%#x: %d (%s).\n", surface->id, vas, vaErrorStr(vas));
+        err = AVERROR_EXTERNAL;
+        goto fail_image;
+    }
+
+    surface->mapping_flags = flags | (derive ? AV_VAAPI_MAP_DIRECT : 0);
+    surface->mapped_address = address;
+
+    for(i = 0; i < image->num_planes; i++) {
+        frame->data[i] = (uint8_t*)address + image->offsets[i];
+        frame->linesize[i] = image->pitches[i];
+    }
+
+    if(image->format.fourcc == VA_FOURCC_YV12) {
+        uint8_t *tmp;
+        // Chroma planes are YVU rather than YUV, so swap them.
+        tmp = frame->data[1];
+        frame->data[1] = frame->data[2];
+        frame->data[2] = tmp;
+    }
+
+    av_vaapi_unlock_hardware_context(hw_ctx);
+    return 0;
+
+  fail_image:
+    vas = vaDestroyImage(hw_ctx->display, surface->image.image_id);
+    if(vas != VA_STATUS_SUCCESS) {
+        av_log(hw_ctx, AV_LOG_ERROR, "Failed to destroy image for surface "
+               "%#x: %d (%s).\n", surface->id, vas, vaErrorStr(vas));
+    }
+  fail:
+    av_vaapi_unlock_hardware_context(hw_ctx);
+    return err;
+}
+
+int av_vaapi_unmap_frame(AVFrame *frame)
+{
+    AVVAAPISurface *surface = vaapi_get_surface(frame);
+    AVVAAPISurfaceConfig *config = vaapi_get_surface_config(frame);
+    AVVAAPIHardwareContext *hw_ctx = surface->hardware_context;
+    VAImage *image = &surface->image;
+    VAStatus vas;
+    int i;
+    int flags = surface->mapping_flags;
+
+    surface->mapped_address = 0;
+
+    for(i = 0; i < image->num_planes; i++) {
+        frame->data[i] = 0;
+        frame->linesize[i] = 0;
+    }
+
+    vas = vaUnmapBuffer(hw_ctx->display, image->buf);
+    if(vas != VA_STATUS_SUCCESS) {
+        av_log(hw_ctx, AV_LOG_ERROR, "Failed to unmap image from surface "
+               "%#x: %d (%s).\n", surface->id, vas, vaErrorStr(vas));
+    }
+
+    if((flags & AV_VAAPI_MAP_WRITE) && !(flags & AV_VAAPI_MAP_DIRECT)) {
+        vas = vaPutImage(hw_ctx->display, surface->id, image->image_id,
+                         0, 0, config->width, config->height,
+                         0, 0, config->width, config->height);
+        if(vas != VA_STATUS_SUCCESS) {
+            av_log(hw_ctx, AV_LOG_ERROR, "Failed to put image for surface "
+                   "%#x: %d (%s).\n", surface->id, vas, vaErrorStr(vas));
+        }
+    }
+
+    vas = vaDestroyImage(hw_ctx->display,
+                         surface->image.image_id);
+    if(vas != VA_STATUS_SUCCESS) {
+        av_log(hw_ctx, AV_LOG_ERROR, "Failed to destroy image for surface "
+               "%#x: %d (%s).\n", surface->id, vas, vaErrorStr(vas));
+    }
+
+    return 0;
+}
+
+static AVFrame *vaapi_make_proxy_frame(const AVFrame *src)
+{
+    AVVAAPISurface *surface = vaapi_get_surface(src);
+    AVVAAPIHardwareContext *hw_ctx = surface->hardware_context;
+    VAImage *image = &surface->image;
+    AVFrame *dst;
+    int i;
+
+    if(!surface->mapped_address) {
+        av_log(hw_ctx, AV_LOG_ERROR, "Surface %#x is not mapped.",
+               surface->id);
+        return 0;
+    }
+
+    dst = av_frame_alloc();
+    if(!dst)
+        return 0;
+
+    for(i = 0; i < image->num_planes; i++) {
+        dst->data[i] = src->data[i];
+        dst->linesize[i] = src->linesize[i];
+    }
+
+    dst->format = av_vaapi_pix_fmt(image->format.fourcc);
+    dst->width  = src->width;
+    dst->height = src->height;
+
+    av_frame_copy_props(dst, src);
+
+    return dst;
+}
+
+int av_vaapi_copy_to_surface(AVFrame *dst, const AVFrame *src)
+{
+    AVFrame *proxy;
+    int err;
+
+    if(dst->format != AV_PIX_FMT_VAAPI)
+        return AVERROR(EINVAL);
+
+    err = av_vaapi_map_frame(dst, AV_VAAPI_MAP_WRITE | AV_VAAPI_MAP_DIRECT);
+    if(err < 0)
+        return err;
+
+    proxy = vaapi_make_proxy_frame(dst);
+    if(proxy)
+        err = av_frame_copy(proxy, src);
+    else
+        err = AVERROR(ENOMEM);
+
+    av_vaapi_unmap_frame(dst);
+
+    return err;
+}
+
+int av_vaapi_copy_from_surface(AVFrame *dst, AVFrame *src)
+{
+    AVFrame *proxy;
+    int err;
+
+    if(src->format != AV_PIX_FMT_VAAPI)
+        return AVERROR(EINVAL);
+
+    err = av_vaapi_map_frame(src, AV_VAAPI_MAP_READ);
+    if(err < 0)
+        return err;
+
+    proxy = vaapi_make_proxy_frame(src);
+    if(proxy)
+        err = av_frame_copy(dst, proxy);
+    else
+        err = AVERROR(ENOMEM);
+
+    av_vaapi_unmap_frame(src);
+
+    av_frame_free(&proxy);
+
+    return err;
+}
+
+int av_vaapi_pipeline_init(AVVAAPIPipelineContext *ctx,
+                           AVVAAPIPipelineConfig *config,
+                           AVVAAPISurfacePool *pool)
+{
+    AVVAAPIHardwareContext *hw_ctx = ctx->hardware_context;
+    VASurfaceID output_surface_ids[AV_VAAPI_MAX_SURFACES];
+    int output_surface_count;
+    VAStatus vas;
+    int i, err;
+    int attr_count;
+    VASurfaceAttrib *attr_list;
+    int min_width, max_width, min_height, max_height;
+
+    av_vaapi_lock_hardware_context(hw_ctx);
+
+    if(pool) {
+        if(pool->hardware_context != hw_ctx) {
+            av_log(ctx, AV_LOG_ERROR, "Pipeline and surface pool are not "
+                   "using the same hardware context.\n");
+            goto fail;
+        }
+
+        output_surface_count = pool->frame_count;
+        for(i = 0; i < output_surface_count; i++)
+            output_surface_ids[i] = vaapi_get_surface(pool->frames[i])->id;
+    } else {
+        // An output surface pool need not be supplied if the pipeline
+        // produces no image output (an intra-only codec like JPEG, say).
+
+        output_surface_count = 0;
+    }
+
+    vas = vaCreateConfig(hw_ctx->display, config->profile,
+                         config->entrypoint, config->attributes,
+                         config->attribute_count, &ctx->config_id);
+    if(vas != VA_STATUS_SUCCESS) {
+        av_log(ctx, AV_LOG_ERROR, "Failed to create pipeline configuration: "
+               "%d (%s).\n", vas, vaErrorStr(vas));
+        err = AVERROR_EXTERNAL;
+        goto fail;
+    }
+
+    attr_count = 0;
+    vas = vaQuerySurfaceAttributes(hw_ctx->display, ctx->config_id,
+                                   0, &attr_count);
+    if(vas != VA_STATUS_SUCCESS) {
+        av_log(ctx, AV_LOG_ERROR, "Failed to get surface attribute count: "
+               "%d (%s).\n", vas, vaErrorStr(vas));
+        err = AVERROR_EXTERNAL;
+        goto fail_config;
+    }
+
+    attr_list = av_calloc(attr_count, sizeof(VASurfaceAttrib));
+    if(!attr_list) {
+        err = AVERROR(ENOMEM);
+        goto fail_config;
+    }
+    min_width = min_height = 0; // Min sizes need not be returned.
+    max_width = max_height = INT_MIN; // Max sizes should be.
+
+    vas = vaQuerySurfaceAttributes(hw_ctx->display, ctx->config_id,
+                                   attr_list, &attr_count);
+    if(vas != VA_STATUS_SUCCESS)
+        attr_count = 0;
+    for(i = 0; i < attr_count; i++) {
+        switch(attr_list[i].type) {
+        case VASurfaceAttribMinWidth:
+            min_width = attr_list[i].value.value.i;
+            break;
+        case VASurfaceAttribMaxWidth:
+            max_width = attr_list[i].value.value.i;
+            break;
+        case VASurfaceAttribMinHeight:
+            min_height = attr_list[i].value.value.i;
+            break;
+        case VASurfaceAttribMaxHeight:
+            max_height = attr_list[i].value.value.i;
+            break;
+        }
+    }
+    av_free(attr_list);
+    if(attr_count == 0) {
+        av_log(ctx, AV_LOG_ERROR, "Failed to get surface attributes: "
+               "%d (%s).\n", vas, vaErrorStr(vas));
+        err = AVERROR_EXTERNAL;
+        goto fail_config;
+    }
+    if(min_width  > config->width  || max_width  < config->width ||
+       min_height > config->height || max_height < config->height) {
+        av_log(ctx, AV_LOG_ERROR, "Pipeline does not support "
+               "image size %dx%d (width %d-%d height %d-%d).\n",
+               config->width, config->height,
+               min_width, max_width, min_height, max_height);
+        err = AVERROR(EINVAL);
+        goto fail_config;
+    }
+
+    vas = vaCreateContext(hw_ctx->display, ctx->config_id,
+                          config->width, config->height,
+                          VA_PROGRESSIVE,
+                          output_surface_ids, output_surface_count,
+                          &ctx->context_id);
+    if(vas != VA_STATUS_SUCCESS) {
+        av_log(ctx, AV_LOG_ERROR, "Failed to create pipeline context: "
+               "%d (%s).\n", vas, vaErrorStr(vas));
+        err = AVERROR_EXTERNAL;
+        goto fail_config;
+    }
+
+    av_log(ctx, AV_LOG_DEBUG, "VAAPI pipeline initialised: config %#x "
+           "context %#x.\n", ctx->config_id, ctx->context_id);
+    av_vaapi_unlock_hardware_context(hw_ctx);
+    return 0;
+
+  fail_config:
+    vaDestroyConfig(hw_ctx->display, ctx->config_id);
+  fail:
+    av_vaapi_unlock_hardware_context(hw_ctx);
+    return err;
+}
+
+int av_vaapi_pipeline_uninit(AVVAAPIPipelineContext *ctx)
+{
+    VAStatus vas;
+
+    av_vaapi_lock_hardware_context(ctx->hardware_context);
+
+    av_assert0(ctx->hardware_context);
+
+    vas = vaDestroyContext(ctx->hardware_context->display, ctx->context_id);
+    if(vas != VA_STATUS_SUCCESS) {
+        av_log(ctx, AV_LOG_ERROR, "Failed to destroy pipeline context: "
+               "%d (%s).\n", vas, vaErrorStr(vas));
+    }
+
+    vaDestroyConfig(ctx->hardware_context->display, ctx->config_id);
+    if(vas != VA_STATUS_SUCCESS) {
+        av_log(ctx, AV_LOG_ERROR, "Failed to destroy pipeline configuration: "
+               "%d (%s).\n", vas, vaErrorStr(vas));
+    }
+
+    av_vaapi_unlock_hardware_context(ctx->hardware_context);
+
+    return 0;
+}
+
+int av_vaapi_fill_vaapi_context(AVVAAPIPipelineContext *ctx,
+                                struct vaapi_context *vaapi_context)
+{
+    if(!ctx->hardware_context->display ||
+       ctx->config_id == VA_INVALID_ID ||
+       ctx->context_id == VA_INVALID_ID)
+        return AVERROR(EINVAL);
+
+    vaapi_context->display    = ctx->hardware_context->display;
+    vaapi_context->config_id  = ctx->config_id;
+    vaapi_context->context_id = ctx->context_id;
+
+    return 0;
+}
+
+VAContextID av_vaapi_get_pipeline_context_id(AVVAAPIPipelineContext *ctx)
+{
+    return ctx->context_id;
+}
+
+
+static struct {
+    unsigned int fourcc;
+    unsigned int rt_format;
+    enum AVPixelFormat pix_fmt;
+} vaapi_formats[] = {
+#define MAP(va, rt, av) { \
+        VA_FOURCC_ ## va, \
+        VA_RT_FORMAT_ ## rt, \
+        AV_PIX_FMT_ ## av \
+    }
+    MAP(NV12, YUV420,  NV12),
+    MAP(IYUV, YUV420,  YUV420P),
+    MAP(YV12, YUV420,  YUV420P), // With U/V planes swapped.
+    MAP(YV16, YUV422,  YUV422P),
+    MAP(UYVY, YUV422,  UYVY422),
+    MAP(P010, YUV420_10BPP, P010),
+    MAP(Y800, YUV400,  GRAY8),
+    MAP(BGRA, RGB32,   BGRA),
+    MAP(BGRX, RGB32,   BGR0),
+    MAP(RGBA, RGB32,   RGBA),
+    MAP(RGBX, RGB32,   RGB0),
+#undef MAP
+};
+
+enum AVPixelFormat av_vaapi_pix_fmt(unsigned int fourcc)
+{
+    int i;
+    for(i = 0; i < FF_ARRAY_ELEMS(vaapi_formats); i++)
+        if(vaapi_formats[i].fourcc == fourcc)
+            return vaapi_formats[i].pix_fmt;
+    return AV_PIX_FMT_NONE;
+}
+
+unsigned int av_vaapi_fourcc(enum AVPixelFormat pix_fmt)
+{
+    int i;
+    for(i = 0; i < FF_ARRAY_ELEMS(vaapi_formats); i++)
+        if(vaapi_formats[i].pix_fmt == pix_fmt)
+            return vaapi_formats[i].fourcc;
+    return 0;
+}
+
+unsigned int av_vaapi_rt_format(unsigned int fourcc)
+{
+    int i;
+    for(i = 0; i < FF_ARRAY_ELEMS(vaapi_formats); i++)
+        if(vaapi_formats[i].fourcc == fourcc)
+            return vaapi_formats[i].rt_format;
+    return 0;
+}
+
+int av_vaapi_get_image_format(AVVAAPIHardwareContext *hw_ctx,
+                              enum AVPixelFormat pix_fmt,
+                              VAImageFormat *image_format)
+{
+    VAStatus vas;
+    VAImageFormat *format_list;
+    int format_count, i, err;
+
+    av_vaapi_lock_hardware_context(hw_ctx);
+
+    format_count = vaMaxNumImageFormats(hw_ctx->display);
+    if(format_count <= 0) {
+        err = AVERROR_EXTERNAL;
+        goto fail;
+    }
+
+    format_list = av_calloc(format_count, sizeof(VAImageFormat));
+    if(!format_list) {
+        err = AVERROR(ENOMEM);
+        goto fail;
+    }
+
+    vas = vaQueryImageFormats(hw_ctx->display, format_list, &format_count);
+    if(vas != VA_STATUS_SUCCESS) {
+        av_log(hw_ctx, AV_LOG_ERROR, "Failed to enumerate VAAPI image "
+               "formats: %d (%s).\n", vas, vaErrorStr(vas));
+        err = AVERROR_EXTERNAL;
+        goto fail;
+    }
+
+    for(i = 0; i < format_count; i++) {
+        if(av_vaapi_pix_fmt(format_list[i].fourcc) == pix_fmt) {
+            memcpy(image_format, &format_list[i], sizeof(VAImageFormat));
+            break;
+        }
+    }
+
+    if(i < format_count)
+        err = 0;
+    else
+        err = AVERROR(EINVAL);
+  fail:
+    av_vaapi_unlock_hardware_context(hw_ctx);
+    return err;
+}
diff --git a/libavcodec/vaapi_support.h b/libavcodec/vaapi_support.h
new file mode 100644
index 0000000..c159ee7
--- /dev/null
+++ b/libavcodec/vaapi_support.h
@@ -0,0 +1,284 @@
+/*
+ * VAAPI helper functions.
+ *
+ * Copyright (C) 2016 Mark Thompson <mrt at jkqxz.net>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * 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.
+ *
+ * FFmpeg 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVCODEC_VAAPI_SUPPORT_H
+#define AVCODEC_VAAPI_SUPPORT_H
+
+/**
+ * @defgroup lavc_vaapi VAAPI support
+ * @ingroup lavc_misc
+ * @{
+ */
+
+#include <va/va.h>
+
+#include "libavutil/pixfmt.h"
+#include "libavutil/frame.h"
+
+#include "vaapi.h"
+
+
+/**
+ * Structure defining a VAAPI hardware context.
+ *
+ * This is managed by the user, and must be passed to all components which
+ * require it.
+ *
+ * Allocated using @ref av_vaapi_alloc_hardware_context().
+ */
+typedef struct AVVAAPIHardwareContext {
+    /** Logging class.
+     * @ref av_vaapi_alloc_hardware_context sets this to a default value,
+     * and the user can override it if desired.
+     */
+    const AVClass *class;
+    /** libva display handle.
+     * Initialised by the user.
+     */
+    VADisplay display;
+    /** Context lock function.
+     * This should be set by the user to allow suitable mutual exclusion if
+     * multiple components might use this context simultaneously.
+     * Note that recursive locking must be supported.
+     */
+    void (*lock)(void *user_context);
+    /** Context unlock function. */
+    void (*unlock)(void *user_context);
+    /** User data passed as argument to lock and unlock. */
+    void *lock_user_context;
+} AVVAAPIHardwareContext;
+
+/** Allocate a new @ref AVVAAPIHardwareContext.
+ *
+ * Also fills in a default logging class.
+ * Use @ref av_free() to free it after use.
+ *
+ * @return Newly-allocated context.
+ */
+AVVAAPIHardwareContext *av_vaapi_alloc_hardware_context(void);
+
+/** Lock the context, using the user-supplied lock function.
+ */
+void av_vaapi_lock_hardware_context(AVVAAPIHardwareContext *ctx);
+/** Unlock the context, using the user-supplied lock function.
+ */
+void av_vaapi_unlock_hardware_context(AVVAAPIHardwareContext *ctx);
+
+
+/** Surface configuration structure.
+ *
+ * Defines the properties of surfaces to be allocated in an
+ * @ref AVVAAPISurfacePool.
+ */
+typedef struct AVVAAPISurfaceConfig {
+    /** Pixel format of the surfaces. */
+    enum AVPixelFormat av_format;
+
+    /** libva image format of the surfaces.
+     *
+     * In most cases, this is obtainable by @ref av_vaapi_get_image_format()
+     * on the av_format.
+     */
+    VAImageFormat va_format;
+
+    /** Width of the surface. */
+    unsigned int width;
+    /** Height of the surface. */
+    unsigned int height;
+
+    /** Number of extra attributes to be passed to vaCreateSurfaces(). */
+    unsigned int attribute_count;
+    /** Pointer to extra attributes (or null if attribute_count is zero). */
+    VASurfaceAttrib *attributes;
+} AVVAAPISurfaceConfig;
+
+/** Opaque surface pool structure.
+ *
+ * Manages a set of VAAPI surfaces contained in AVFrames.  Individual frames
+ * use reference-counting to track lifetime.
+ */
+typedef struct AVVAAPISurfacePool AVVAAPISurfacePool;
+
+/** Allocate a new @ref AVVAAPISurfacePool.
+ *
+ * @param hw_ctx VAAPI hardware context this pool will create surfaces in.
+ * @return Newly-allocated surface pool.
+ */
+AVVAAPISurfacePool *av_vaapi_alloc_surface_pool(AVVAAPIHardwareContext *hw_ctx);
+
+/** Initialise an @ref AVVAAPISurfacePool and allocate the surfaces in it.
+ *
+ * @param pool Pool to initialise (allocated by
+ *     @ref av_vaapi_alloc_surface_pool()).
+ * @param config Surface configuration for the allocated surfaces.  It need
+ *     not last beyond this call (can be on the stack).
+ * @param surface_count Number of surfaces to allocate in the pool.
+ * @return Zero on success, or negative error code.
+ */
+int av_vaapi_surface_pool_init(AVVAAPISurfacePool *pool,
+                               AVVAAPISurfaceConfig *config,
+                               int surface_count);
+
+/** Uninitialise an @ref AVVAAPISurfacePool.
+ *
+ * Frees all surfaces which are not currently referenced.  Surfaces which are
+ * still referenced elsewhere by AVFrames will be freed when their last
+ * reference disappears.
+ */
+int av_vaapi_surface_pool_uninit(AVVAAPISurfacePool *pool);
+
+/** Allocates a free surface from the surface pool.
+ *
+ * @param pool Pool to allocate from.
+ * @param target AVFrame to attach references to.  Most properties of the frame
+ *     itself will not be changed.
+ * @return Zero on success, or negative error code.
+ */
+int av_vaapi_surface_pool_get(AVVAAPISurfacePool *pool, AVFrame *target);
+
+
+/** Pipeline configuration structure.
+ *
+ * Defines the properties of a pipeline, to be passed as an argument to
+ * @ref av_vaapi_pipeline_init().
+ */
+typedef struct AVVAAPIPipelineConfig {
+    /** Codec profile to use (or VAProfileNone for non-codec operations). */
+    VAProfile profile;
+    /** Processing entry point. */
+    VAEntrypoint entrypoint;
+
+    /** Width of the output surfaces of this pipeline. */
+    unsigned int width;
+    /** Height of the output surfaces of this pipeline. */
+    unsigned int height;
+
+    /** Number of extra attributes to be passed to vaCreateContext(). */
+    unsigned int attribute_count;
+    /** Pointer to extra attribues (or null if attribute_count is zero). */
+    VAConfigAttrib *attributes;
+} AVVAAPIPipelineConfig;
+
+/** Opaque pipeline context structure.
+ *
+ * Manages a VAAPI pipeline context with an associated output surface pool.
+ */
+typedef struct AVVAAPIPipelineContext AVVAAPIPipelineContext;
+
+/** Allocate a new @ref AVVAAPIPipelineContext.
+ *
+ * @param hw_ctx VAAPI hardware context this pipeline will be created in.
+ * @return Newly-allocated pipeline context.
+ */
+AVVAAPIPipelineContext *av_vaapi_alloc_pipeline_context(AVVAAPIHardwareContext *hw_ctx);
+
+/** Initialise an @ref AVVAAPIPipelineContext to be ready to process surfaces.
+ *
+ * @param ctx Pipeline context to initialised (allocated by
+ *     @ref av_vaapi_alloc_pipeline_context()).
+ * @param config Pipeline configuration.
+ * @param pool Output surface pool.  If the pipeline has no output surfaces
+ *     (for example, an intra-only encoder), this can be null.
+ * @return Zero on success, or negative error code.
+ */
+int av_vaapi_pipeline_init(AVVAAPIPipelineContext *ctx,
+                           AVVAAPIPipelineConfig *config,
+                           AVVAAPISurfacePool *pool);
+
+/** Uninitialise an @ref AVVAAPIPipelineContext.
+ *
+ * No operations should be outstanding on the pipeline when this is called.
+ * The output surface pool is not affected.
+ */
+int av_vaapi_pipeline_uninit(AVVAAPIPipelineContext *ctx);
+
+/** Fill a @ref vaapi_context for use with a VAAPI hwaccel decoder. */
+int av_vaapi_fill_vaapi_context(AVVAAPIPipelineContext *ctx,
+                                struct vaapi_context *vaapi_context);
+
+/** Return the context ID of the pipeline, for use in va*Picture calls. */
+VAContextID av_vaapi_get_pipeline_context_id(AVVAAPIPipelineContext *ctx);
+
+
+enum {
+    /** Map surface for read access. */
+    AV_VAAPI_MAP_READ   = 0x01,
+    /** Map surface for write access. */
+    AV_VAAPI_MAP_WRITE  = 0x02,
+    /** Map surface directly.  Fail if direct access is not possible. */
+    AV_VAAPI_MAP_DIRECT = 0x04,
+};
+
+/** Map a surface so that it can be operated on by the CPU.
+ *
+ * This may need to copy the frame data between some sort of external memory
+ * and normal CPU-addressable memory.  The copy-in will only take place if
+ * read access was requested.
+ *
+ * @param frame AVFrame to map.  Must have been allocated in an
+ *     @ref AVVAAPISurfacePool
+ * @param flags A set of AV_VAAPI_MAP flags, ored together.
+ * @return Zero on success, or negative error code.
+ */
+int av_vaapi_map_frame(AVFrame *frame, int flags);
+
+/** Unmap a surface after operating on it with the CPU.
+ *
+ * If the frame data was copied and write access was requested, this will copy
+ * back to external memory.
+ */
+int av_vaapi_unmap_frame(AVFrame *frame);
+
+/** Copy data from memory to a surface.
+ *
+ * The size and format of the source must match the underlying format of the
+ * surface.
+ */
+int av_vaapi_copy_to_surface(AVFrame *dst, const AVFrame *src);
+
+/** Copy data from a surface to memory.
+ *
+ * The size and format of the destination must match the underlying format of
+ * the surface.
+ */
+int av_vaapi_copy_from_surface(AVFrame *dst, AVFrame *src);
+
+
+/** Given a libva fourcc, return the corresponding pixel format. */
+enum AVPixelFormat av_vaapi_pix_fmt(unsigned int fourcc);
+/** Given a pixel format, return the corresponding libva fourcc. */
+unsigned int av_vaapi_fourcc(enum AVPixelFormat pix_fmt);
+/** Given a libva fourcc, return the associated pipeline chroma format. */
+unsigned int av_vaapi_rt_format(unsigned int fourcc);
+
+/** Given a pixel format, return the corresponding libva image format.
+ *
+ * This uses vaQueryImageFormats(), so results need not be exactly equivalent
+ * between different drivers.
+ */
+int av_vaapi_get_image_format(AVVAAPIHardwareContext *hw_ctx,
+                              enum AVPixelFormat pix_fmt,
+                              VAImageFormat *image_format);
+
+/** @} */
+
+#endif /* AVCODEC_VAAPI_SUPPORT_H */
-- 
2.7.0




More information about the ffmpeg-devel mailing list