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

wm4 nfxjfg at googlemail.com
Mon Feb 8 13:08:24 CET 2016


On Sun, 7 Feb 2016 21:53:39 +0000
Mark Thompson <sw at jkqxz.net> wrote:

> ---
>  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;

No alloc function for this struct? (Although I can't think of anything
that could possibly added to it in the future. Maybe not needed?)

> +
> +/** 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);
> +

Any particular reason why alloc and init are separate functions?

> +/** 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().
> + */

Doxygen should make explicit that av_vaapi_alloc_pipeline_context must
be used to allocate the struct.

> +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.
> + */

Does the function preallocate the pool itself? If so, how can you set
the number of additional surfaces? (The user might need them for
whatever reasons.)

> +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);

I know I've stared at the implementation for this in the few previous
patch iterations, but just looking at this it's not clear to me at all
what the input and output is.

So the input is a PIX_FMT_VAAPI AVFrame. And on output, this is
replaced with a "software" AVFrame or what? What happens on unmap? Is
it magically undone? What happens with the vaapi frame reference?

Maybe it would be better to add a separate AVFrame argument, into which
the surface is mapped.

> +
> +/** 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 */



More information about the ffmpeg-devel mailing list