--- 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 <m...@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 <m...@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 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel