From a001ce31bc2bcf875a39b5fb22dae49120293b42 Mon Sep 17 00:00:00 2001 From: Anton Khirnov Date: Mon, 7 Dec 2015 15:04:57 +0100 Subject: [PATCH] hwcontext: add a VDPAU implementation --- doc/APIchanges | 2 + libavutil/Makefile | 2 + libavutil/hwcontext.c | 3 + libavutil/hwcontext_internal.h | 2 + libavutil/hwcontext_vdpau.c | 408 +++++++++++++++++++++++++++++++++ libavutil/hwcontext_vdpau.h | 44 ++++ 6 files changed, 461 insertions(+) create mode 100644 libavutil/hwcontext_vdpau.c create mode 100644 libavutil/hwcontext_vdpau.h diff --git a/doc/APIchanges b/doc/APIchanges index 84d5e90b20..e17f02cb0b 100644 --- a/doc/APIchanges +++ b/doc/APIchanges @@ -17,6 +17,8 @@ API changes, most recent first: xxxxxxx buffer.h - Add av_buffer_pool_init2(). xxxxxxx hwcontext.h - Add a new installed header hwcontext.h with a new API for handling hwaccel frames. + xxxxxxx hwcontext_vdpau.h - Add a new installed header hwcontext_vdpau.h with + VDPAU-specific hwcontext definitions. 2016-xx-xx - xxxxxxx - lavf 57.3.0 - avformat.h Add AVFormatContext.opaque, io_open and io_close, allowing custom IO diff --git a/libavutil/Makefile b/libavutil/Makefile index 2c0b7b54aa..180c37eb49 100644 --- a/libavutil/Makefile +++ b/libavutil/Makefile @@ -24,6 +24,7 @@ HEADERS = adler32.h \ frame.h \ hmac.h \ hwcontext.h \ + hwcontext_vdpau.h \ imgutils.h \ intfloat.h \ intreadwrite.h \ @@ -105,6 +106,7 @@ OBJS = adler32.o \ xtea.o \ OBJS-$(CONFIG_LZO) += lzo.o +OBJS-$(CONFIG_VDPAU) += hwcontext_vdpau.o OBJS += $(COMPAT_OBJS:%=../compat/%) diff --git a/libavutil/hwcontext.c b/libavutil/hwcontext.c index bec8f61e0e..2aa712e963 100644 --- a/libavutil/hwcontext.c +++ b/libavutil/hwcontext.c @@ -29,6 +29,9 @@ #include "pixfmt.h" static const HWContextType *hw_table[] = { +#if CONFIG_VDPAU + &ff_hwcontext_type_vdpau, +#endif NULL, }; diff --git a/libavutil/hwcontext_internal.h b/libavutil/hwcontext_internal.h index acc775ccc5..54f8d1050e 100644 --- a/libavutil/hwcontext_internal.h +++ b/libavutil/hwcontext_internal.h @@ -86,4 +86,6 @@ struct AVHWFramesInternal { AVBufferPool *pool_internal; }; +extern const HWContextType ff_hwcontext_type_vdpau; + #endif /* AVUTIL_HWCONTEXT_INTERNAL_H */ diff --git a/libavutil/hwcontext_vdpau.c b/libavutil/hwcontext_vdpau.c new file mode 100644 index 0000000000..faae5f8641 --- /dev/null +++ b/libavutil/hwcontext_vdpau.c @@ -0,0 +1,408 @@ +/* + * This file is part of Libav. + * + * Libav 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. + * + * Libav 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 Libav; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include + +#include + +#include "buffer.h" +#include "common.h" +#include "hwcontext.h" +#include "hwcontext_internal.h" +#include "hwcontext_vdpau.h" +#include "mem.h" +#include "pixfmt.h" +#include "pixdesc.h" + +typedef struct VDPAUDeviceContext { + VdpVideoSurfaceQueryGetPutBitsYCbCrCapabilities *get_transfer_caps; + VdpVideoSurfaceGetBitsYCbCr *get_data; + VdpVideoSurfacePutBitsYCbCr *put_data; + VdpVideoSurfaceCreate *surf_create; + VdpVideoSurfaceDestroy *surf_destroy; + + enum AVPixelFormat *pix_fmts[3]; + int nb_pix_fmts[3]; +} VDPAUDeviceContext; + +typedef struct VDPAUFramesContext { + VdpVideoSurfaceGetBitsYCbCr *get_data; + VdpVideoSurfacePutBitsYCbCr *put_data; + VdpChromaType chroma_type; + int chroma_idx; + + const enum AVPixelFormat *pix_fmts; + int nb_pix_fmts; +} VDPAUFramesContext; + +typedef struct VDPAUPixFmtMap { + VdpYCbCrFormat vdpau_fmt; + enum AVPixelFormat pix_fmt; +} VDPAUPixFmtMap; + +static const VDPAUPixFmtMap pix_fmts_420[] = { + { VDP_YCBCR_FORMAT_NV12, AV_PIX_FMT_NV12 }, + { VDP_YCBCR_FORMAT_YV12, AV_PIX_FMT_YUV420P }, + { 0, AV_PIX_FMT_NONE, }, +}; + +static const VDPAUPixFmtMap pix_fmts_422[] = { + { VDP_YCBCR_FORMAT_NV12, AV_PIX_FMT_NV16 }, + { VDP_YCBCR_FORMAT_YV12, AV_PIX_FMT_YUV422P }, + { VDP_YCBCR_FORMAT_UYVY, AV_PIX_FMT_UYVY422 }, + { VDP_YCBCR_FORMAT_YUYV, AV_PIX_FMT_YUYV422 }, + { 0, AV_PIX_FMT_NONE, }, +}; + +static const VDPAUPixFmtMap pix_fmts_444[] = { + { VDP_YCBCR_FORMAT_YV12, AV_PIX_FMT_YUV444P }, + { 0, AV_PIX_FMT_NONE, }, +}; + +static const struct { + VdpChromaType chroma_type; + const VDPAUPixFmtMap *map; +} vdpau_pix_fmts[] = { + { VDP_CHROMA_TYPE_420, pix_fmts_420 }, + { VDP_CHROMA_TYPE_422, pix_fmts_422 }, + { VDP_CHROMA_TYPE_444, pix_fmts_444 }, +}; + +static int count_pixfmts(const VDPAUPixFmtMap *map) +{ + int count = 0; + while (map->pix_fmt != AV_PIX_FMT_NONE) { + map++; + count++; + } + return count; +} + +static int vdpau_init_pixmfts(AVHWDeviceContext *ctx) +{ + AVVDPAUDeviceContext *hwctx = ctx->hwctx; + VDPAUDeviceContext *priv = ctx->internal->priv; + int i; + + for (i = 0; i < FF_ARRAY_ELEMS(priv->pix_fmts); i++) { + const VDPAUPixFmtMap *map = vdpau_pix_fmts[i].map; + int nb_pix_fmts; + + nb_pix_fmts = count_pixfmts(map); + priv->pix_fmts[i] = av_malloc_array(nb_pix_fmts + 1, sizeof(*priv->pix_fmts[i])); + if (!priv->pix_fmts[i]) + return AVERROR(ENOMEM); + + nb_pix_fmts = 0; + while (map->pix_fmt != AV_PIX_FMT_NONE) { + VdpBool supported; + VdpStatus err = priv->get_transfer_caps(hwctx->device, vdpau_pix_fmts[i].chroma_type, + map->vdpau_fmt, &supported); + if (err == VDP_STATUS_OK && supported) + priv->pix_fmts[i][nb_pix_fmts++] = map->pix_fmt; + map++; + } + priv->pix_fmts[i][nb_pix_fmts++] = AV_PIX_FMT_NONE; + priv->nb_pix_fmts[i] = nb_pix_fmts; + } + + return 0; +} + +static int vdpau_device_init(AVHWDeviceContext *ctx) +{ + AVVDPAUDeviceContext *hwctx = ctx->hwctx; + VDPAUDeviceContext *priv = ctx->internal->priv; + VdpStatus err; + int ret; + +#define GET_CALLBACK(id, result) \ +do { \ + void *tmp; \ + err = hwctx->get_proc_address(hwctx->device, id, &tmp); \ + if (err != VDP_STATUS_OK) { \ + av_log(ctx, AV_LOG_ERROR, "Error getting the " #id " callback.\n"); \ + return AVERROR_UNKNOWN; \ + } \ + priv->result = tmp; \ +} while (0) + + GET_CALLBACK(VDP_FUNC_ID_VIDEO_SURFACE_QUERY_GET_PUT_BITS_Y_CB_CR_CAPABILITIES, + get_transfer_caps); + GET_CALLBACK(VDP_FUNC_ID_VIDEO_SURFACE_GET_BITS_Y_CB_CR, get_data); + GET_CALLBACK(VDP_FUNC_ID_VIDEO_SURFACE_PUT_BITS_Y_CB_CR, put_data); + GET_CALLBACK(VDP_FUNC_ID_VIDEO_SURFACE_CREATE, surf_create); + GET_CALLBACK(VDP_FUNC_ID_VIDEO_SURFACE_DESTROY, surf_destroy); + + ret = vdpau_init_pixmfts(ctx); + if (ret < 0) { + av_log(ctx, AV_LOG_ERROR, "Error querying the supported pixel formats\n"); + return ret; + } + + return 0; +} + +static void vdpau_device_uninit(AVHWDeviceContext *ctx) +{ + VDPAUDeviceContext *priv = ctx->internal->priv; + int i; + + for (i = 0; i < FF_ARRAY_ELEMS(priv->pix_fmts); i++) + av_freep(&priv->pix_fmts[i]); +} + +static void vdpau_buffer_free(void *opaque, uint8_t *data) +{ + AVHWFramesContext *ctx = opaque; + VDPAUDeviceContext *device_priv = ctx->device_ctx->internal->priv; + VdpVideoSurface surf = (VdpVideoSurface)(uintptr_t)data; + + device_priv->surf_destroy(surf); +} + +static AVBufferRef *vdpau_pool_alloc(void *opaque, int size) +{ + AVHWFramesContext *ctx = opaque; + VDPAUFramesContext *priv = ctx->internal->priv; + AVVDPAUDeviceContext *device_hwctx = ctx->device_ctx->hwctx; + VDPAUDeviceContext *device_priv = ctx->device_ctx->internal->priv; + + AVBufferRef *ret; + VdpVideoSurface surf; + VdpStatus err; + + err = device_priv->surf_create(device_hwctx->device, priv->chroma_type, + ctx->width, ctx->height, &surf); + if (err != VDP_STATUS_OK) { + av_log(ctx, AV_LOG_ERROR, "Error allocating a VDPAU video surface\n"); + return NULL; + } + + ret = av_buffer_create((uint8_t*)(uintptr_t)surf, sizeof(surf), + vdpau_buffer_free, ctx, AV_BUFFER_FLAG_READONLY); + if (!ret) { + device_priv->surf_destroy(surf); + return NULL; + } + + return ret; +} + +static int vdpau_frames_init(AVHWFramesContext *ctx) +{ + VDPAUDeviceContext *device_priv = ctx->device_ctx->internal->priv; + VDPAUFramesContext *priv = ctx->internal->priv; + + int i; + + switch (ctx->sw_format) { + case AV_PIX_FMT_YUV420P: priv->chroma_type = VDP_CHROMA_TYPE_420; break; + case AV_PIX_FMT_YUV422P: priv->chroma_type = VDP_CHROMA_TYPE_422; break; + case AV_PIX_FMT_YUV444P: priv->chroma_type = VDP_CHROMA_TYPE_444; break; + default: + av_log(ctx, AV_LOG_ERROR, "Unsupported data layout: %s\n", + av_get_pix_fmt_name(ctx->sw_format)); + return AVERROR(ENOSYS); + } + + for (i = 0; i < FF_ARRAY_ELEMS(vdpau_pix_fmts); i++) { + if (vdpau_pix_fmts[i].chroma_type == priv->chroma_type) { + priv->chroma_idx = i; + priv->pix_fmts = device_priv->pix_fmts[i]; + priv->nb_pix_fmts = device_priv->nb_pix_fmts[i]; + break; + } + } + if (!priv->pix_fmts) { + av_log(ctx, AV_LOG_ERROR, "Unsupported chroma type: %d\n", priv->chroma_type); + return AVERROR(ENOSYS); + } + + if (!ctx->pool) { + ctx->internal->pool_internal = av_buffer_pool_init2(sizeof(VdpVideoSurface), ctx, + vdpau_pool_alloc, NULL); + if (!ctx->internal->pool_internal) + return AVERROR(ENOMEM); + } + + priv->get_data = device_priv->get_data; + priv->put_data = device_priv->put_data; + + return 0; +} + +static int vdpau_get_buffer(AVHWFramesContext *ctx, AVFrame *frame) +{ + frame->buf[0] = av_buffer_pool_get(ctx->pool); + if (!frame->buf[0]) + return AVERROR(ENOMEM); + + frame->data[3] = frame->buf[0]->data; + frame->format = AV_PIX_FMT_VDPAU; + frame->width = ctx->width; + frame->height = ctx->height; + + return 0; +} + +static int vdpau_transfer_get_formats(AVHWFramesContext *ctx, + enum AVHWFrameTransferDirection dir, + enum AVPixelFormat **formats) +{ + VDPAUFramesContext *priv = ctx->internal->priv; + + enum AVPixelFormat *fmts; + + if (priv->nb_pix_fmts == 1) { + av_log(ctx, AV_LOG_ERROR, + "No target formats are supported for this chroma type\n"); + return AVERROR(ENOSYS); + } + + fmts = av_malloc_array(priv->nb_pix_fmts, sizeof(*fmts)); + if (!fmts) + return AVERROR(ENOMEM); + + memcpy(fmts, priv->pix_fmts, sizeof(*fmts) * (priv->nb_pix_fmts)); + *formats = fmts; + + return 0; +} + +static int vdpau_transfer_data_from(AVHWFramesContext *ctx, AVFrame *dst, + const AVFrame *src) +{ + VDPAUFramesContext *priv = ctx->internal->priv; + VdpVideoSurface surf = (VdpVideoSurface)(uintptr_t)src->data[3]; + + void *data[3]; + uint32_t linesize[3]; + + const VDPAUPixFmtMap *map; + VdpYCbCrFormat vdpau_format; + VdpStatus err; + int i; + + for (i = 0; i< FF_ARRAY_ELEMS(data) && dst->data[i]; i++) { + data[i] = dst->data[i]; + if (dst->linesize[i] < 0 || (uint64_t)dst->linesize > UINT32_MAX) { + av_log(ctx, AV_LOG_ERROR, + "The linesize %d cannot be represented as uint32\n", + dst->linesize[i]); + return AVERROR(ERANGE); + } + linesize[i] = dst->linesize[i]; + } + + map = vdpau_pix_fmts[priv->chroma_idx].map; + for (i = 0; map[i].pix_fmt != AV_PIX_FMT_NONE; i++) { + if (map[i].pix_fmt == dst->format) { + vdpau_format = map[i].vdpau_fmt; + break; + } + } + if (map[i].pix_fmt == AV_PIX_FMT_NONE) { + av_log(ctx, AV_LOG_ERROR, + "Unsupported target pixel format: %s\n", + av_get_pix_fmt_name(dst->format)); + return AVERROR(EINVAL); + } + + if (vdpau_format == VDP_YCBCR_FORMAT_YV12) + FFSWAP(void*, data[1], data[2]); + + err = priv->get_data(surf, vdpau_format, data, linesize); + if (err != VDP_STATUS_OK) { + av_log(ctx, AV_LOG_ERROR, "Error retrieving the data from a VDPAU surface\n"); + return AVERROR_UNKNOWN; + } + + return 0; +} + +static int vdpau_transfer_data_to(AVHWFramesContext *ctx, AVFrame *dst, + const AVFrame *src) +{ + VDPAUFramesContext *priv = ctx->internal->priv; + VdpVideoSurface surf = (VdpVideoSurface)(uintptr_t)dst->data[3]; + + const void *data[3]; + uint32_t linesize[3]; + + const VDPAUPixFmtMap *map; + VdpYCbCrFormat vdpau_format; + VdpStatus err; + int i; + + for (i = 0; i< FF_ARRAY_ELEMS(data) && src->data[i]; i++) { + data[i] = src->data[i]; + if (src->linesize[i] < 0 || (uint64_t)src->linesize > UINT32_MAX) { + av_log(ctx, AV_LOG_ERROR, + "The linesize %d cannot be represented as uint32\n", + src->linesize[i]); + return AVERROR(ERANGE); + } + linesize[i] = src->linesize[i]; + } + + map = vdpau_pix_fmts[priv->chroma_idx].map; + for (i = 0; map[i].pix_fmt != AV_PIX_FMT_NONE; i++) { + if (map[i].pix_fmt == src->format) { + vdpau_format = map[i].vdpau_fmt; + break; + } + } + if (map[i].pix_fmt == AV_PIX_FMT_NONE) { + av_log(ctx, AV_LOG_ERROR, + "Unsupported source pixel format: %s\n", + av_get_pix_fmt_name(src->format)); + return AVERROR(EINVAL); + } + + if (vdpau_format == VDP_YCBCR_FORMAT_YV12) + FFSWAP(const void*, data[1], data[2]); + + err = priv->put_data(surf, vdpau_format, data, linesize); + if (err != VDP_STATUS_OK) { + av_log(ctx, AV_LOG_ERROR, "Error uploading the data to a VDPAU surface\n"); + return AVERROR_UNKNOWN; + } + + return 0; +} + +const HWContextType ff_hwcontext_type_vdpau = { + .type = AV_HWDEVICE_TYPE_VDPAU, + .name = "VDPAU", + + .device_hwctx_size = sizeof(AVVDPAUDeviceContext), + .device_priv_size = sizeof(VDPAUDeviceContext), + .frames_priv_size = sizeof(VDPAUFramesContext), + + .device_init = vdpau_device_init, + .device_uninit = vdpau_device_uninit, + .frames_init = vdpau_frames_init, + .frames_get_buffer = vdpau_get_buffer, + .transfer_get_formats = vdpau_transfer_get_formats, + .transfer_data_to = vdpau_transfer_data_to, + .transfer_data_from = vdpau_transfer_data_from, + + .pix_fmts = (const enum AVPixelFormat[]){ AV_PIX_FMT_VDPAU, AV_PIX_FMT_NONE }, +}; diff --git a/libavutil/hwcontext_vdpau.h b/libavutil/hwcontext_vdpau.h new file mode 100644 index 0000000000..358e942fca --- /dev/null +++ b/libavutil/hwcontext_vdpau.h @@ -0,0 +1,44 @@ +/* + * This file is part of Libav. + * + * Libav 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. + * + * Libav 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 Libav; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_HWCONTEXT_VDPAU_H +#define AVUTIL_HWCONTEXT_VDPAU_H + +#include + +/** + * @file + * An API-specific header for AV_HWDEVICE_TYPE_VDPAU. + * + * This API supports dynamic frame pools. AVHWFramesContext.pool must return + * AVBufferRefs whose data pointer is a VdpVideoSurface. + */ + +/** + * This struct is allocated as AVHWDeviceContext.hwctx + */ +typedef struct AVVDPAUDeviceContext { + VdpDevice device; + VdpGetProcAddress *get_proc_address; +} AVVDPAUDeviceContext; + +/** + * AVHWFramesContext.hwctx is currently not used + */ + +#endif /* AVUTIL_HWCONTEXT_VDPAU_H */