--- configure | 5 + libavutil/Makefile | 1 + libavutil/vaapi.c | 546 +++++++++++++++++++++++++++++++++++++++++++++++++++++ libavutil/vaapi.h | 115 +++++++++++ 4 files changed, 667 insertions(+) create mode 100644 libavutil/vaapi.c create mode 100644 libavutil/vaapi.h
diff --git a/configure b/configure index 8f4642b..cd386b4 100755 --- a/configure +++ b/configure @@ -2042,6 +2042,7 @@ CONFIG_EXTRA=" texturedsp texturedspenc tpeldsp + vaapi_recent videodsp vp3dsp vp56dsp @@ -5740,6 +5741,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/libavutil/Makefile b/libavutil/Makefile index 65b2d25..9d6a313 100644 --- a/libavutil/Makefile +++ b/libavutil/Makefile @@ -148,6 +148,7 @@ OBJS-$(!HAVE_ATOMICS_NATIVE) += atomic.o \ OBJS-$(CONFIG_LZO) += lzo.o OBJS-$(CONFIG_OPENCL) += opencl.o opencl_internal.o +OBJS-$(CONFIG_VAAPI_RECENT) += vaapi.o OBJS += $(COMPAT_OBJS:%=../compat/%) diff --git a/libavutil/vaapi.c b/libavutil/vaapi.c new file mode 100644 index 0000000..d5e67c4 --- /dev/null +++ b/libavutil/vaapi.c @@ -0,0 +1,546 @@ +/* + * 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.h" + +#include "avassert.h" +#include "imgutils.h" +#include "pixfmt.h" + + +AVVAAPIHardwareContext *av_vaapi_alloc_hardware_context(void) +{ + return av_mallocz(sizeof(AVVAAPIHardwareContext)); +} + +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 AVVAAPISurface { + VASurfaceID id; + AVVAAPIHardwareContext *hardware_context; + + VAImage image; + 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(0, 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, + AVVAAPIHardwareContext *hw_ctx, + AVVAAPISurfaceConfig *config, + int frame_count) +{ + AVBufferRef *config_buffer; + AVVAAPISurface *surface; + AVFrame *frame; + VAStatus vas; + int i, err; + + memset(pool, 0, sizeof(*pool)); + + pool->hardware_context = hw_ctx; + pool->frame_count = frame_count; + + config_buffer = av_buffer_alloc(sizeof(*config)); + if(!config_buffer) + return AVERROR(ENOMEM); + memcpy(config_buffer->data, config, sizeof(*config)); + config = (AVVAAPISurfaceConfig*)config_buffer->data; + + av_vaapi_lock_hardware_context(hw_ctx); + + for(i = 0; i < 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, config->rt_format, + config->width, config->height, + &surface->id, 1, + config->attributes, config->attribute_count); + if(vas != VA_STATUS_SUCCESS) { + av_log(0, AV_LOG_ERROR, "Failed to create surface: " + "%d (%s).\n", vas, vaErrorStr(vas)); + err = AVERROR_EXTERNAL; + goto fail; + } + + 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; + } + + 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; + } + + for(; i < FF_ARRAY_ELEMS(pool->frames); i++) + pool->frames[i] = 0; + + av_buffer_unref(&config_buffer); + + av_log(0, AV_LOG_DEBUG, "Surface pool initialised: %u surfaces of %ux%u.\n", + pool->frame_count, config->width, config->height); + + err = 0; + fail: + 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) { + err = av_frame_ref(target, frame); + } else { + err = AVERROR(ENOMEM); + } + + av_vaapi_unlock_hardware_context(pool->hardware_context); + + return err; +} + +int av_vaapi_map_frame(AVFrame *frame, int get) +{ + 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; + void *address; + // On current Intel drivers, derive gives you memory which is very slow + // to read (uncached?). It can be better for write-only cases, but for + // now play it safe and never use derive. + int derive = 0; + + if(surface->mapped_address) { + av_log(0, AV_LOG_ERROR, "Surface %#x already mapped.\n", + surface->id); + // This could refcount somehow to allow multiple mappings? + return AVERROR(EINVAL); + } + + av_vaapi_lock_hardware_context(hw_ctx); + + vas = vaSyncSurface(hw_ctx->display, surface->id); + if(vas != VA_STATUS_SUCCESS) { + av_log(0, AV_LOG_ERROR, "Failed to sync surface " + "%#x: %d (%s).\n", surface->id, vas, vaErrorStr(vas)); + err = AVERROR_EXTERNAL; + goto fail; + } + + if(derive) { + vas = vaDeriveImage(hw_ctx->display, + surface->id, image); + if(vas != VA_STATUS_SUCCESS) { + av_log(0, AV_LOG_ERROR, "Failed to derive image from surface " + "%#x: %d (%s).\n", surface->id, vas, vaErrorStr(vas)); + derive = 0; + } + } + if(!derive) { + vas = vaCreateImage(hw_ctx->display, &config->image_format, + config->width, config->height, image); + if(vas != VA_STATUS_SUCCESS) { + av_log(0, AV_LOG_ERROR, "Failed to create image for surface " + "%#x: %d (%s).\n", surface->id, vas, vaErrorStr(vas)); + err = AVERROR_EXTERNAL; + goto fail; + } + + if(get) { + vas = vaGetImage(hw_ctx->display, surface->id, 0, 0, + config->width, config->height, image->image_id); + if(vas != VA_STATUS_SUCCESS) { + av_log(0, AV_LOG_ERROR, "Failed to get image for surface " + "%#x: %d (%s).\n", surface->id, vas, vaErrorStr(vas)); + err = AVERROR_EXTERNAL; + goto fail_image; + } + } + } + + av_assert0(image->format.fourcc == config->image_format.fourcc); + + vas = vaMapBuffer(hw_ctx->display, image->buf, &address); + if(vas != VA_STATUS_SUCCESS) { + av_log(0, 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->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]; + } + + 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(0, 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, int put) +{ + 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 derive = 0; + + 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(0, AV_LOG_ERROR, "Failed to unmap image from surface " + "%#x: %d (%s).\n", surface->id, vas, vaErrorStr(vas)); + } + + if(!derive && put) { + 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(0, 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(0, AV_LOG_ERROR, "Failed to destroy image for surface " + "%#x: %d (%s).\n", surface->id, vas, vaErrorStr(vas)); + } + + return 0; +} + +static enum AVPixelFormat vaapi_pix_fmt(unsigned int fourcc) +{ + switch(fourcc) { + case VA_FOURCC_NV12: return AV_PIX_FMT_NV12; + case VA_FOURCC_IYUV: return AV_PIX_FMT_YUV420P; + case VA_FOURCC_YV12: return AV_PIX_FMT_YUV420P; // U/V planes swapped. + case VA_FOURCC_BGRA: return AV_PIX_FMT_BGRA; + case VA_FOURCC_BGRX: return AV_PIX_FMT_BGR0; + case VA_FOURCC_RGBA: return AV_PIX_FMT_RGBA; + case VA_FOURCC_RGBX: return AV_PIX_FMT_RGB0; + default: + return AV_PIX_FMT_NONE; + } +} + +static AVFrame *vaapi_make_proxy_frame(const AVFrame *src) +{ + AVVAAPISurface *surface = vaapi_get_surface(src); + VAImage *image = &surface->image; + AVFrame *dst; + int i; + + if(!surface->mapped_address) { + av_log(0, 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->width = src->width; + dst->height = src->height; + + dst->format = vaapi_pix_fmt(image->format.fourcc); + if(image->format.fourcc == VA_FOURCC_YV12) { + uint8_t *tmp; + tmp = dst->data[1]; + dst->data[1] = dst->data[2]; + dst->data[2] = tmp; + } + + 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, 0); + if(err) + 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, 1); + + return 0; +} + +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, 1); + if(err) + 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, 0); + + return err; +} + +static const AVClass vaapi_pipeline_class = { + .class_name = "VAAPI/pipeline", + .item_name = av_default_item_name, + .version = LIBAVUTIL_VERSION_INT, +}; + +int av_vaapi_pipeline_init(AVVAAPIPipelineContext *ctx, + AVVAAPIHardwareContext *hw_ctx, + AVVAAPIPipelineConfig *config, + AVVAAPISurfacePool *pool) +{ + VASurfaceID output_surface_ids[AV_VAAPI_MAX_SURFACES]; + int output_surface_count; + VAStatus vas; + int i, err; + + av_vaapi_lock_hardware_context(hw_ctx); + + memset(ctx, 0, sizeof(*ctx)); + ctx->class = &vaapi_pipeline_class; + + ctx->hardware_context = hw_ctx; + + if(pool) { + 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(EINVAL); + goto fail; + } + + 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(EINVAL); + goto fail; + } + + av_log(ctx, AV_LOG_DEBUG, "VAAPI pipeline initialised: config %#x " + "context %#x.\n", ctx->config_id, ctx->context_id); + + err = 0; + 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; +} diff --git a/libavutil/vaapi.h b/libavutil/vaapi.h new file mode 100644 index 0000000..53c4c7c --- /dev/null +++ b/libavutil/vaapi.h @@ -0,0 +1,115 @@ +/* + * 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 LIBAVUTIL_VAAPI_H_ +#define LIBAVUTIL_VAAPI_H_ + +#include <va/va.h> + +#include "pixfmt.h" +#include "frame.h" + + +typedef struct AVVAAPIHardwareContext { + VADisplay display; + + VAConfigID decoder_pipeline_config_id; + VAContextID decoder_pipeline_context_id; + + void (*lock)(void *user_context); + void (*unlock)(void *user_context); + void *lock_user_context; +} AVVAAPIHardwareContext; + +AVVAAPIHardwareContext *av_vaapi_alloc_hardware_context(void); + +void av_vaapi_lock_hardware_context(AVVAAPIHardwareContext *ctx); +void av_vaapi_unlock_hardware_context(AVVAAPIHardwareContext *ctx); + + +#define AV_VAAPI_MAX_SURFACES 64 + +typedef struct AVVAAPISurfaceConfig { + enum AVPixelFormat av_format; + unsigned int rt_format; + VAImageFormat image_format; + + unsigned int width; + unsigned int height; + + unsigned int attribute_count; + VASurfaceAttrib *attributes; +} AVVAAPISurfaceConfig; + +typedef struct AVVAAPISurfacePool { + AVVAAPIHardwareContext *hardware_context; + + int frame_count; + AVFrame *frames[AV_VAAPI_MAX_SURFACES]; +} AVVAAPISurfacePool; + +int av_vaapi_surface_pool_init(AVVAAPISurfacePool *pool, + AVVAAPIHardwareContext *hw_ctx, + AVVAAPISurfaceConfig *config, + int frame_count); + +int av_vaapi_surface_pool_uninit(AVVAAPISurfacePool *pool); + +int av_vaapi_surface_pool_get(AVVAAPISurfacePool *pool, AVFrame *target); + + +typedef struct AVVAAPIPipelineConfig { + VAProfile profile; + VAEntrypoint entrypoint; + + unsigned int width; + unsigned int height; + + unsigned int attribute_count; + VAConfigAttrib *attributes; +} AVVAAPIPipelineConfig; + +typedef struct AVVAAPIPipelineContext { + const AVClass *class; + + AVVAAPIHardwareContext *hardware_context; + + VAConfigID config_id; + VAContextID context_id; +} AVVAAPIPipelineContext; + +int av_vaapi_pipeline_init(AVVAAPIPipelineContext *ctx, + AVVAAPIHardwareContext *hw_ctx, + AVVAAPIPipelineConfig *config, + AVVAAPISurfacePool *pool); + +int av_vaapi_pipeline_uninit(AVVAAPIPipelineContext *ctx); + + +int av_vaapi_map_frame(AVFrame *frame, int get); +int av_vaapi_unmap_frame(AVFrame *frame, int put); + +int av_vaapi_copy_to_surface(AVFrame *dst, const AVFrame *src); +int av_vaapi_copy_from_surface(AVFrame *dst, AVFrame *src); + + +#endif /* LIBAVUTIL_VAAPI_H_ */ -- 2.6.4 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel