From 67afcefb35932b420998f6f3fda46c7c85848a3f Mon Sep 17 00:00:00 2001 From: Anton Khirnov Date: Mon, 24 Mar 2014 01:59:02 +0100 Subject: [PATCH] lavc: Add new VDA hwaccel It leverages the new hwaccel 1.2 features: - get_buffer2 is never called - the internal context is automatically initialized/deinitialized Signed-off-by: Luca Barbato --- configure | 2 + libavcodec/Makefile | 1 + libavcodec/allcodecs.c | 1 + libavcodec/h264_slice.c | 2 + libavcodec/vda.c | 72 +++++++++++ libavcodec/vda.h | 53 ++++++++ libavcodec/vda_h264.c | 257 ++++++++++++++++++++++++++++++++++++-- libavcodec/vda_internal.h | 33 +++++ 8 files changed, 414 insertions(+), 7 deletions(-) create mode 100644 libavcodec/vda.c create mode 100644 libavcodec/vda_internal.h diff --git a/configure b/configure index 47fd690a4f..da77f694cc 100755 --- a/configure +++ b/configure @@ -1896,6 +1896,8 @@ h264_vaapi_hwaccel_deps="vaapi" h264_vaapi_hwaccel_select="h264_decoder" h264_vda_hwaccel_deps="vda" h264_vda_hwaccel_select="h264_decoder" +h264_vda_old_hwaccel_deps="vda" +h264_vda_old_hwaccel_select="h264_decoder" h264_vdpau_hwaccel_deps="vdpau" h264_vdpau_hwaccel_select="h264_decoder" mpeg1_vdpau_hwaccel_deps="vdpau" diff --git a/libavcodec/Makefile b/libavcodec/Makefile index 2bce954995..e8f12539b9 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -68,6 +68,7 @@ OBJS-$(CONFIG_RDFT) += rdft.o $(RDFT-OBJS-yes) OBJS-$(CONFIG_SINEWIN) += sinewin.o OBJS-$(CONFIG_TPELDSP) += tpeldsp.o OBJS-$(CONFIG_VAAPI) += vaapi.o +OBJS-$(CONFIG_VDA) += vda.o OBJS-$(CONFIG_VDPAU) += vdpau.o OBJS-$(CONFIG_VIDEODSP) += videodsp.o OBJS-$(CONFIG_VP3DSP) += vp3dsp.o diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c index dcd6c4e607..9f4fd2531e 100644 --- a/libavcodec/allcodecs.c +++ b/libavcodec/allcodecs.c @@ -79,6 +79,7 @@ void avcodec_register_all(void) REGISTER_HWACCEL(H264_DXVA2, h264_dxva2); REGISTER_HWACCEL(H264_VAAPI, h264_vaapi); REGISTER_HWACCEL(H264_VDA, h264_vda); + REGISTER_HWACCEL(H264_VDA_OLD, h264_vda_old); REGISTER_HWACCEL(H264_VDPAU, h264_vdpau); REGISTER_HWACCEL(MPEG1_VDPAU, mpeg1_vdpau); REGISTER_HWACCEL(MPEG2_DXVA2, mpeg2_dxva2); diff --git a/libavcodec/h264_slice.c b/libavcodec/h264_slice.c index e727233c58..25f0f4d4af 100644 --- a/libavcodec/h264_slice.c +++ b/libavcodec/h264_slice.c @@ -153,6 +153,7 @@ static const enum AVPixelFormat h264_hwaccel_pixfmt_list_420[] = { #endif #if CONFIG_H264_VDA_HWACCEL AV_PIX_FMT_VDA_VLD, + AV_PIX_FMT_VDA, #endif #if CONFIG_H264_VDPAU_HWACCEL AV_PIX_FMT_VDPAU, @@ -170,6 +171,7 @@ static const enum AVPixelFormat h264_hwaccel_pixfmt_list_jpeg_420[] = { #endif #if CONFIG_H264_VDA_HWACCEL AV_PIX_FMT_VDA_VLD, + AV_PIX_FMT_VDA, #endif #if CONFIG_H264_VDPAU_HWACCEL AV_PIX_FMT_VDPAU, diff --git a/libavcodec/vda.c b/libavcodec/vda.c new file mode 100644 index 0000000000..f71fb16488 --- /dev/null +++ b/libavcodec/vda.c @@ -0,0 +1,72 @@ +/* + * 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 "config.h" + +#include "libavutil/mem.h" + +#include "vda.h" +#include "vda_internal.h" + +#if CONFIG_H264_VDA_HWACCEL +AVVDAContext *av_vda_alloc_context(void) +{ + AVVDAContext *ret = av_mallocz(sizeof(*ret)); + + if (ret) + ret->output_callback = ff_vda_output_callback; + + return ret; +} + +int av_vda_default_init(AVCodecContext *avctx) +{ + avctx->hwaccel_context = av_vda_alloc_context(); + if (!avctx->hwaccel_context) + return AVERROR(ENOMEM); + return ff_vda_default_init(avctx); +} + +void av_vda_default_free(AVCodecContext *avctx) +{ + ff_vda_default_free(avctx); + av_freep(&avctx->hwaccel_context); +} + +void ff_vda_default_free(AVCodecContext *avctx) +{ + AVVDAContext *vda = avctx->hwaccel_context; + if (vda && vda->decoder) + VDADecoderDestroy(vda->decoder); +} + +#else +AVVDAContext *av_vda_alloc_context(void) +{ + return NULL; +} + +int av_vda_default_init(AVCodecContext *avctx) +{ + return AVERROR(ENOSYS); +} + +void av_vda_default_free(AVCodecContext *ctx) +{ +} +#endif diff --git a/libavcodec/vda.h b/libavcodec/vda.h index 1189e41fb5..9aa5d297fb 100644 --- a/libavcodec/vda.h +++ b/libavcodec/vda.h @@ -29,6 +29,7 @@ * Public libavcodec VDA header. */ +#include "libavcodec/avcodec.h" #include "libavcodec/version.h" #include @@ -135,6 +136,58 @@ int ff_vda_create_decoder(struct vda_context *vda_ctx, /** Destroy the video decoder. */ int ff_vda_destroy_decoder(struct vda_context *vda_ctx); +/** + * This struct holds all the information that needs to be passed + * between the caller and libavcodec for initializing VDA decoding. + * Its size is not a part of the public ABI, it must be allocated with + * av_vda_alloc_context() and freed with av_free(). + */ +typedef struct AVVDAContext { + /** + * VDA decoder object. Created and freed by the caller. + */ + VDADecoder decoder; + + /** + * The output callback that must be passed to VDADecoderCreate. + * Set by av_vda_alloc_context(). + */ + VDADecoderOutputCallback output_callback; +} AVVDAContext; + +/** + * Allocate and initialize a VDA context. + * + * This function should be called from the get_format() callback when the caller + * selects the AV_PIX_FMT_VDA format. The caller must then create the decoder + * object (using the output callback provided by libavcodec) that will be used + * for VDA-accelerated decoding. + * + * When decoding with VDA is finished, the caller must destroy the decoder + * object and free the VDA context using av_free(). + * + * @return the newly allocated context or NULL on failure + */ +AVVDAContext *av_vda_alloc_context(void); + +/** + * This is a convenience function that creates and sets up the VDA context using + * an internal implementation. + * + * @param avctx the corresponding codec context + * + * @return >= 0 on success, a negative AVERROR code on failure + */ +int av_vda_default_init(AVCodecContext *avctx); + +/** + * This function must be called to free the VDA context initialized with + * av_vda_default_init(). + * + * @param avctx the corresponding codec context + */ +void av_vda_default_free(AVCodecContext *avctx); + /** * @} */ diff --git a/libavcodec/vda_h264.c b/libavcodec/vda_h264.c index 72b0c78fc9..c7f6a7432e 100644 --- a/libavcodec/vda_h264.c +++ b/libavcodec/vda_h264.c @@ -26,7 +26,9 @@ #include "libavutil/avutil.h" #include "h264.h" +#include "internal.h" #include "vda.h" +#include "vda_internal.h" typedef struct VDAContext { // The current bitstream buffer. @@ -37,6 +39,8 @@ typedef struct VDAContext { // The reference size used for fast reallocation. int allocated_size; + + CVImageBufferRef frame; } VDAContext; /* Decoder callback that adds the VDA frame to the queue in display order. */ @@ -78,7 +82,7 @@ static int vda_sync_decode(VDAContext *ctx, struct vda_context *vda_ctx) } -static int vda_h264_start_frame(AVCodecContext *avctx, +static int vda_old_h264_start_frame(AVCodecContext *avctx, av_unused const uint8_t *buffer, av_unused uint32_t size) { @@ -93,7 +97,7 @@ static int vda_h264_start_frame(AVCodecContext *avctx, return 0; } -static int vda_h264_decode_slice(AVCodecContext *avctx, +static int vda_old_h264_decode_slice(AVCodecContext *avctx, const uint8_t *buffer, uint32_t size) { @@ -120,7 +124,7 @@ static int vda_h264_decode_slice(AVCodecContext *avctx, return 0; } -static int vda_h264_end_frame(AVCodecContext *avctx) +static int vda_old_h264_end_frame(AVCodecContext *avctx) { H264Context *h = avctx->priv_data; VDAContext *vda = avctx->internal->hwaccel_priv_data; @@ -208,7 +212,7 @@ int ff_vda_create_decoder(struct vda_context *vda_ctx, status = VDADecoderCreate(config_info, buffer_attributes, - vda_decoder_callback, + (VDADecoderOutputCallback *)vda_decoder_callback, vda_ctx, &vda_ctx->decoder); @@ -234,17 +238,256 @@ int ff_vda_destroy_decoder(struct vda_context *vda_ctx) return status; } -static void vda_h264_uninit(AVCodecContext *avctx) +static int vda_h264_uninit(AVCodecContext *avctx) { - VDAContext *vda = avctx->internal->priv_data; + VDAContext *vda = avctx->internal->hwaccel_priv_data; av_freep(&vda->bitstream); + if (vda->frame) + CVPixelBufferRelease(vda->frame); + return 0; +} + +AVHWAccel ff_h264_vda_old_hwaccel = { + .name = "h264_vda", + .type = AVMEDIA_TYPE_VIDEO, + .id = AV_CODEC_ID_H264, + .pix_fmt = AV_PIX_FMT_VDA_VLD, + .start_frame = vda_old_h264_start_frame, + .decode_slice = vda_old_h264_decode_slice, + .end_frame = vda_old_h264_end_frame, + .uninit = vda_h264_uninit, + .priv_data_size = sizeof(VDAContext), +}; + +void ff_vda_output_callback(void *opaque, + CFDictionaryRef user_info, + OSStatus status, + uint32_t infoFlags, + CVImageBufferRef image_buffer) +{ + AVCodecContext *ctx = opaque; + VDAContext *vda = ctx->internal->hwaccel_priv_data; + + + if (vda->frame) { + CVPixelBufferRelease(vda->frame); + vda->frame = NULL; + } + + if (!image_buffer) + return; + + vda->frame = CVPixelBufferRetain(image_buffer); +} + +static int vda_h264_start_frame(AVCodecContext *avctx, + const uint8_t *buffer, + uint32_t size) +{ + VDAContext *vda = avctx->internal->hwaccel_priv_data; + + vda->bitstream_size = 0; + + return 0; +} + +static int vda_h264_decode_slice(AVCodecContext *avctx, + const uint8_t *buffer, + uint32_t size) +{ + VDAContext *vda = avctx->internal->hwaccel_priv_data; + void *tmp; + + tmp = av_fast_realloc(vda->bitstream, + &vda->allocated_size, + vda->bitstream_size + size + 4); + if (!tmp) + return AVERROR(ENOMEM); + + vda->bitstream = tmp; + + AV_WB32(vda->bitstream + vda->bitstream_size, size); + memcpy(vda->bitstream + vda->bitstream_size + 4, buffer, size); + + vda->bitstream_size += size + 4; + + return 0; +} + +static void release_buffer(void *opaque, uint8_t *data) +{ + CVImageBufferRef frame = (CVImageBufferRef)data; + CVPixelBufferRelease(frame); +} + +static int vda_h264_end_frame(AVCodecContext *avctx) +{ + H264Context *h = avctx->priv_data; + VDAContext *vda = avctx->internal->hwaccel_priv_data; + AVVDAContext *vda_ctx = avctx->hwaccel_context; + AVFrame *frame = &h->cur_pic_ptr->f; + uint32_t flush_flags = 1 << 0; ///< kVDADecoderFlush_emitFrames + CFDataRef coded_frame; + OSStatus status; + + if (!vda->bitstream_size) + return AVERROR_INVALIDDATA; + + + coded_frame = CFDataCreate(kCFAllocatorDefault, + vda->bitstream, + vda->bitstream_size); + + status = VDADecoderDecode(vda_ctx->decoder, 0, coded_frame, NULL); + + if (status == kVDADecoderNoErr) + status = VDADecoderFlush(vda_ctx->decoder, flush_flags); + + CFRelease(coded_frame); + + if (status != kVDADecoderNoErr) { + av_log(avctx, AV_LOG_ERROR, "Failed to decode frame (%d)\n", status); + return AVERROR_UNKNOWN; + } + + if (vda->frame) { + av_buffer_unref(&frame->buf[0]); + + frame->buf[0] = av_buffer_create((uint8_t*)vda->frame, + sizeof(vda->frame), + release_buffer, NULL, + AV_BUFFER_FLAG_READONLY); + if (!frame->buf) + return AVERROR(ENOMEM); + + frame->data[3] = (uint8_t*)vda->frame; + vda->frame = NULL; + } + + return 0; +} + +int ff_vda_default_init(AVCodecContext *avctx) +{ + AVVDAContext *vda_ctx = avctx->hwaccel_context; + OSStatus status = kVDADecoderNoErr; + CFNumberRef height; + CFNumberRef width; + CFNumberRef format; + CFDataRef avc_data; + CFMutableDictionaryRef config_info; + CFMutableDictionaryRef buffer_attributes; + CFMutableDictionaryRef io_surface_properties; + CFNumberRef cv_pix_fmt; + int32_t fmt = 'avc1', pix_fmt = kCVPixelFormatType_422YpCbCr8; + + // kCVPixelFormatType_420YpCbCr8Planar; + + /* Each VCL NAL in the bistream sent to the decoder + * is preceded by a 4 bytes length header. + * Change the avcC atom header if needed, to signal headers of 4 bytes. */ + if (avctx->extradata_size >= 4 && (avctx->extradata[4] & 0x03) != 0x03) { + uint8_t *rw_extradata; + + if (!(rw_extradata = av_malloc(avctx->extradata_size))) + return AVERROR(ENOMEM); + + memcpy(rw_extradata, avctx->extradata, avctx->extradata_size); + + rw_extradata[4] |= 0x03; + + avc_data = CFDataCreate(kCFAllocatorDefault, rw_extradata, avctx->extradata_size); + + av_freep(&rw_extradata); + } else { + avc_data = CFDataCreate(kCFAllocatorDefault, + avctx->extradata, avctx->extradata_size); + } + + config_info = CFDictionaryCreateMutable(kCFAllocatorDefault, + 4, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + height = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &avctx->height); + width = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &avctx->width); + format = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &fmt); + CFDictionarySetValue(config_info, kVDADecoderConfiguration_Height, height); + CFDictionarySetValue(config_info, kVDADecoderConfiguration_Width, width); + CFDictionarySetValue(config_info, kVDADecoderConfiguration_avcCData, avc_data); + CFDictionarySetValue(config_info, kVDADecoderConfiguration_SourceFormat, format); + + buffer_attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, + 2, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + io_surface_properties = CFDictionaryCreateMutable(kCFAllocatorDefault, + 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + cv_pix_fmt = CFNumberCreate(kCFAllocatorDefault, + kCFNumberSInt32Type, + &pix_fmt); + + CFDictionarySetValue(buffer_attributes, + kCVPixelBufferPixelFormatTypeKey, + cv_pix_fmt); + CFDictionarySetValue(buffer_attributes, + kCVPixelBufferIOSurfacePropertiesKey, + io_surface_properties); + + status = VDADecoderCreate(config_info, + buffer_attributes, + (VDADecoderOutputCallback *)ff_vda_output_callback, + avctx, + &vda_ctx->decoder); + + CFRelease(format); + CFRelease(height); + CFRelease(width); + CFRelease(avc_data); + CFRelease(config_info); + CFRelease(cv_pix_fmt); + CFRelease(io_surface_properties); + CFRelease(buffer_attributes); + + if (status != kVDADecoderNoErr) { + av_log(avctx, AV_LOG_ERROR, "Cannot initialize VDA %d\n", status); + } + + switch (status) { + case kVDADecoderHardwareNotSupportedErr: + case kVDADecoderFormatNotSupportedErr: + return AVERROR(ENOSYS); + case kVDADecoderConfigurationError: + return AVERROR(EINVAL); + case kVDADecoderDecoderFailedErr: + return AVERROR_INVALIDDATA; + case kVDADecoderNoErr: + return 0; + default: + return AVERROR_UNKNOWN; + } +} + +static int vda_h264_alloc_frame(AVCodecContext *avctx, AVFrame *frame) +{ + frame->width = avctx->width; + frame->height = avctx->height; + frame->format = avctx->pix_fmt; + frame->buf[0] = av_buffer_alloc(1); + + if (!frame->buf[0]) + return AVERROR(ENOMEM); + return 0; } AVHWAccel ff_h264_vda_hwaccel = { .name = "h264_vda", .type = AVMEDIA_TYPE_VIDEO, .id = AV_CODEC_ID_H264, - .pix_fmt = AV_PIX_FMT_VDA_VLD, + .pix_fmt = AV_PIX_FMT_VDA, + .alloc_frame = vda_h264_alloc_frame, .start_frame = vda_h264_start_frame, .decode_slice = vda_h264_decode_slice, .end_frame = vda_h264_end_frame, diff --git a/libavcodec/vda_internal.h b/libavcodec/vda_internal.h new file mode 100644 index 0000000000..9d0ed807bf --- /dev/null +++ b/libavcodec/vda_internal.h @@ -0,0 +1,33 @@ +/* + * 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 AVCODEC_VDA_INTERNAL_H +#define AVCODEC_VDA_INTERNAL_H + +#include "vda.h" + +void ff_vda_output_callback(void *vda_hw_ctx, + CFDictionaryRef user_info, + OSStatus status, + uint32_t infoFlags, + CVImageBufferRef image_buffer); + +int ff_vda_default_init(AVCodecContext *avctx); +void ff_vda_default_free(AVCodecContext *avctx); + +#endif /* AVCODEC_VDA_INTERNAL_H */