/* * 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 "config.h" #include #include #include #include "buffer.h" #include "buffer_internal.h" #include "common.h" #include "hwcontext.h" #include "hwcontext_internal.h" #include "hwcontext_videotoolbox.h" #include "mem.h" #include "pixfmt.h" #include "pixdesc.h" typedef struct VTFramesContext { /** * The public AVVTFramesContext. See hwcontext_videotoolbox.h for it. */ AVVTFramesContext p; CVPixelBufferPoolRef pool; } VTFramesContext; static const struct { uint32_t cv_fmt; bool full_range; enum AVPixelFormat pix_fmt; } cv_pix_fmts[] = { { kCVPixelFormatType_420YpCbCr8Planar, false, AV_PIX_FMT_YUV420P }, { kCVPixelFormatType_420YpCbCr8PlanarFullRange, true, AV_PIX_FMT_YUV420P }, { kCVPixelFormatType_422YpCbCr8, false, AV_PIX_FMT_UYVY422 }, { kCVPixelFormatType_32BGRA, true, AV_PIX_FMT_BGRA }, #ifdef kCFCoreFoundationVersionNumber10_7 { kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, false, AV_PIX_FMT_NV12 }, { kCVPixelFormatType_420YpCbCr8BiPlanarFullRange, true, AV_PIX_FMT_NV12 }, { kCVPixelFormatType_4444AYpCbCr8, false, AV_PIX_FMT_AYUV }, { kCVPixelFormatType_4444AYpCbCr16, false, AV_PIX_FMT_AYUV64 }, #endif #if HAVE_KCVPIXELFORMATTYPE_420YPCBCR10BIPLANARVIDEORANGE { kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange, false, AV_PIX_FMT_P010 }, { kCVPixelFormatType_420YpCbCr10BiPlanarFullRange, true, AV_PIX_FMT_P010 }, #endif #if HAVE_KCVPIXELFORMATTYPE_422YPCBCR8BIPLANARVIDEORANGE { kCVPixelFormatType_422YpCbCr8BiPlanarVideoRange, false, AV_PIX_FMT_NV16 }, { kCVPixelFormatType_422YpCbCr8BiPlanarFullRange, true, AV_PIX_FMT_NV16 }, #endif #if HAVE_KCVPIXELFORMATTYPE_422YPCBCR10BIPLANARVIDEORANGE { kCVPixelFormatType_422YpCbCr10BiPlanarVideoRange, false, AV_PIX_FMT_P210 }, { kCVPixelFormatType_422YpCbCr10BiPlanarFullRange, true, AV_PIX_FMT_P210 }, #endif #if HAVE_KCVPIXELFORMATTYPE_422YPCBCR16BIPLANARVIDEORANGE { kCVPixelFormatType_422YpCbCr16BiPlanarVideoRange, false, AV_PIX_FMT_P216 }, #endif #if HAVE_KCVPIXELFORMATTYPE_444YPCBCR8BIPLANARVIDEORANGE { kCVPixelFormatType_444YpCbCr8BiPlanarVideoRange, false, AV_PIX_FMT_NV24 }, { kCVPixelFormatType_444YpCbCr8BiPlanarFullRange, true, AV_PIX_FMT_NV24 }, #endif #if HAVE_KCVPIXELFORMATTYPE_444YPCBCR10BIPLANARVIDEORANGE { kCVPixelFormatType_444YpCbCr10BiPlanarVideoRange, false, AV_PIX_FMT_P410 }, { kCVPixelFormatType_444YpCbCr10BiPlanarFullRange, true, AV_PIX_FMT_P410 }, #endif #if HAVE_KCVPIXELFORMATTYPE_444YPCBCR16BIPLANARVIDEORANGE { kCVPixelFormatType_444YpCbCr16BiPlanarVideoRange, false, AV_PIX_FMT_P416 }, #endif }; static const enum AVPixelFormat supported_formats[] = { #ifdef kCFCoreFoundationVersionNumber10_7 AV_PIX_FMT_NV12, AV_PIX_FMT_AYUV, AV_PIX_FMT_AYUV64, #endif AV_PIX_FMT_YUV420P, AV_PIX_FMT_UYVY422, #if HAVE_KCVPIXELFORMATTYPE_420YPCBCR10BIPLANARVIDEORANGE AV_PIX_FMT_P010, #endif #if HAVE_KCVPIXELFORMATTYPE_422YPCBCR8BIPLANARVIDEORANGE AV_PIX_FMT_NV16, #endif #if HAVE_KCVPIXELFORMATTYPE_422YPCBCR10BIPLANARVIDEORANGE AV_PIX_FMT_P210, #endif #if HAVE_KCVPIXELFORMATTYPE_422YPCBCR16BIPLANARVIDEORANGE AV_PIX_FMT_P216, #endif #if HAVE_KCVPIXELFORMATTYPE_444YPCBCR8BIPLANARVIDEORANGE AV_PIX_FMT_NV24, #endif #if HAVE_KCVPIXELFORMATTYPE_444YPCBCR10BIPLANARVIDEORANGE AV_PIX_FMT_P410, #endif #if HAVE_KCVPIXELFORMATTYPE_444YPCBCR16BIPLANARVIDEORANGE AV_PIX_FMT_P416, #endif AV_PIX_FMT_BGRA, }; static int vt_frames_get_constraints(AVHWDeviceContext *ctx, const void *hwconfig, AVHWFramesConstraints *constraints) { int i; constraints->valid_sw_formats = av_malloc_array(FF_ARRAY_ELEMS(supported_formats) + 1, sizeof(*constraints->valid_sw_formats)); if (!constraints->valid_sw_formats) return AVERROR(ENOMEM); for (i = 0; i < FF_ARRAY_ELEMS(supported_formats); i++) constraints->valid_sw_formats[i] = supported_formats[i]; constraints->valid_sw_formats[FF_ARRAY_ELEMS(supported_formats)] = AV_PIX_FMT_NONE; constraints->valid_hw_formats = av_malloc_array(2, sizeof(*constraints->valid_hw_formats)); if (!constraints->valid_hw_formats) return AVERROR(ENOMEM); constraints->valid_hw_formats[0] = AV_PIX_FMT_VIDEOTOOLBOX; constraints->valid_hw_formats[1] = AV_PIX_FMT_NONE; return 0; } enum AVPixelFormat av_map_videotoolbox_format_to_pixfmt(uint32_t cv_fmt) { int i; for (i = 0; i < FF_ARRAY_ELEMS(cv_pix_fmts); i++) { if (cv_pix_fmts[i].cv_fmt == cv_fmt) return cv_pix_fmts[i].pix_fmt; } return AV_PIX_FMT_NONE; } static uint32_t vt_format_from_pixfmt(enum AVPixelFormat pix_fmt, enum AVColorRange range) { for (int i = 0; i < FF_ARRAY_ELEMS(cv_pix_fmts); i++) { if (cv_pix_fmts[i].pix_fmt == pix_fmt) { int full_range = (range == AVCOL_RANGE_JPEG); // Don't care if unspecified if (range == AVCOL_RANGE_UNSPECIFIED) return cv_pix_fmts[i].cv_fmt; if (cv_pix_fmts[i].full_range == full_range) return cv_pix_fmts[i].cv_fmt; } } return 0; } uint32_t av_map_videotoolbox_format_from_pixfmt(enum AVPixelFormat pix_fmt) { return av_map_videotoolbox_format_from_pixfmt2(pix_fmt, false); } uint32_t av_map_videotoolbox_format_from_pixfmt2(enum AVPixelFormat pix_fmt, bool full_range) { return vt_format_from_pixfmt(pix_fmt, full_range ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG); } static int vt_pool_alloc(AVHWFramesContext *ctx) { VTFramesContext *fctx = ctx->hwctx; AVVTFramesContext *hw_ctx = &fctx->p; CVReturn err; CFNumberRef w, h, pixfmt; uint32_t cv_pixfmt; CFMutableDictionaryRef attributes, iosurface_properties; attributes = CFDictionaryCreateMutable( NULL, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); cv_pixfmt = vt_format_from_pixfmt(ctx->sw_format, hw_ctx->color_range); pixfmt = CFNumberCreate(NULL, kCFNumberSInt32Type, &cv_pixfmt); CFDictionarySetValue( attributes, kCVPixelBufferPixelFormatTypeKey, pixfmt); CFRelease(pixfmt); iosurface_properties = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDictionarySetValue(attributes, kCVPixelBufferIOSurfacePropertiesKey, iosurface_properties); CFRelease(iosurface_properties); w = CFNumberCreate(NULL, kCFNumberSInt32Type, &ctx->width); h = CFNumberCreate(NULL, kCFNumberSInt32Type, &ctx->height); CFDictionarySetValue(attributes, kCVPixelBufferWidthKey, w); CFDictionarySetValue(attributes, kCVPixelBufferHeightKey, h); CFRelease(w); CFRelease(h); err = CVPixelBufferPoolCreate( NULL, NULL, attributes, &fctx->pool); CFRelease(attributes); if (err == kCVReturnSuccess) return 0; av_log(ctx, AV_LOG_ERROR, "Error creating CVPixelBufferPool: %d\n", err); return AVERROR_EXTERNAL; } static void videotoolbox_buffer_release(void *opaque, uint8_t *data) { CVPixelBufferRelease((CVPixelBufferRef)data); } static AVBufferRef *vt_pool_alloc_buffer(void *opaque, size_t size) { CVPixelBufferRef pixbuf; AVBufferRef *buf; CVReturn err; AVHWFramesContext *ctx = opaque; VTFramesContext *fctx = ctx->hwctx; err = CVPixelBufferPoolCreatePixelBuffer( NULL, fctx->pool, &pixbuf ); if (err != kCVReturnSuccess) { av_log(ctx, AV_LOG_ERROR, "Failed to create pixel buffer from pool: %d\n", err); return NULL; } buf = av_buffer_create((uint8_t *)pixbuf, size, videotoolbox_buffer_release, NULL, 0); if (!buf) { CVPixelBufferRelease(pixbuf); return NULL; } return buf; } static void vt_frames_uninit(AVHWFramesContext *ctx) { VTFramesContext *fctx = ctx->hwctx; if (fctx->pool) { CVPixelBufferPoolRelease(fctx->pool); fctx->pool = NULL; } } static int vt_frames_init(AVHWFramesContext *ctx) { int i, ret; for (i = 0; i < FF_ARRAY_ELEMS(supported_formats); i++) { if (ctx->sw_format == supported_formats[i]) break; } if (i == FF_ARRAY_ELEMS(supported_formats)) { av_log(ctx, AV_LOG_ERROR, "Pixel format '%s' is not supported\n", av_get_pix_fmt_name(ctx->sw_format)); return AVERROR(ENOSYS); } if (!ctx->pool) { ffhwframesctx(ctx)->pool_internal = av_buffer_pool_init2( sizeof(CVPixelBufferRef), ctx, vt_pool_alloc_buffer, NULL); if (!ffhwframesctx(ctx)->pool_internal) return AVERROR(ENOMEM); } ret = vt_pool_alloc(ctx); if (ret < 0) return ret; return 0; } static int vt_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_VIDEOTOOLBOX; frame->width = ctx->width; frame->height = ctx->height; return 0; } static int vt_transfer_get_formats(AVHWFramesContext *ctx, enum AVHWFrameTransferDirection dir, enum AVPixelFormat **formats) { enum AVPixelFormat *fmts = av_malloc_array(2, sizeof(*fmts)); if (!fmts) return AVERROR(ENOMEM); fmts[0] = ctx->sw_format; fmts[1] = AV_PIX_FMT_NONE; *formats = fmts; return 0; } static void vt_unmap(AVHWFramesContext *ctx, HWMapDescriptor *hwmap) { CVPixelBufferRef pixbuf = (CVPixelBufferRef)hwmap->source->data[3]; CVPixelBufferUnlockBaseAddress(pixbuf, (uintptr_t)hwmap->priv); } static int vt_pixbuf_set_par(void *log_ctx, CVPixelBufferRef pixbuf, const AVFrame *src) { CFMutableDictionaryRef par = NULL; CFNumberRef num = NULL, den = NULL; AVRational avpar = src->sample_aspect_ratio; if (avpar.num == 0) { CVBufferRemoveAttachment(pixbuf, kCVImageBufferPixelAspectRatioKey); return 0; } av_reduce(&avpar.num, &avpar.den, avpar.num, avpar.den, 0xFFFFFFFF); num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &avpar.num); den = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &avpar.den); par = CFDictionaryCreateMutable(kCFAllocatorDefault, 2, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (!par || !num || !den) { if (par) CFRelease(par); if (num) CFRelease(num); if (den) CFRelease(den); return AVERROR(ENOMEM); } CFDictionarySetValue( par, kCVImageBufferPixelAspectRatioHorizontalSpacingKey, num); CFDictionarySetValue( par, kCVImageBufferPixelAspectRatioVerticalSpacingKey, den); CVBufferSetAttachment( pixbuf, kCVImageBufferPixelAspectRatioKey, par, kCVAttachmentMode_ShouldPropagate ); CFRelease(par); CFRelease(num); CFRelease(den); return 0; } CFStringRef av_map_videotoolbox_chroma_loc_from_av(enum AVChromaLocation loc) { switch (loc) { case AVCHROMA_LOC_LEFT: return kCVImageBufferChromaLocation_Left; case AVCHROMA_LOC_CENTER: return kCVImageBufferChromaLocation_Center; case AVCHROMA_LOC_TOP: return kCVImageBufferChromaLocation_Top; case AVCHROMA_LOC_BOTTOM: return kCVImageBufferChromaLocation_Bottom; case AVCHROMA_LOC_TOPLEFT: return kCVImageBufferChromaLocation_TopLeft; case AVCHROMA_LOC_BOTTOMLEFT: return kCVImageBufferChromaLocation_BottomLeft; default: return NULL; } } static int vt_pixbuf_set_chromaloc(void *log_ctx, CVPixelBufferRef pixbuf, const AVFrame *src) { CFStringRef loc = av_map_videotoolbox_chroma_loc_from_av(src->chroma_location); if (loc) { CVBufferSetAttachment( pixbuf, kCVImageBufferChromaLocationTopFieldKey, loc, kCVAttachmentMode_ShouldPropagate); } else CVBufferRemoveAttachment( pixbuf, kCVImageBufferChromaLocationTopFieldKey); return 0; } CFStringRef av_map_videotoolbox_color_matrix_from_av(enum AVColorSpace space) { switch (space) { case AVCOL_SPC_BT2020_CL: case AVCOL_SPC_BT2020_NCL: #if HAVE_KCVIMAGEBUFFERYCBCRMATRIX_ITU_R_2020 if (__builtin_available(macOS 10.11, iOS 9, *)) return kCVImageBufferYCbCrMatrix_ITU_R_2020; #endif return CFSTR("ITU_R_2020"); case AVCOL_SPC_BT470BG: case AVCOL_SPC_SMPTE170M: return kCVImageBufferYCbCrMatrix_ITU_R_601_4; case AVCOL_SPC_BT709: return kCVImageBufferYCbCrMatrix_ITU_R_709_2; case AVCOL_SPC_SMPTE240M: return kCVImageBufferYCbCrMatrix_SMPTE_240M_1995; default: #if HAVE_KCVIMAGEBUFFERTRANSFERFUNCTION_ITU_R_2100_HLG if (__builtin_available(macOS 10.13, iOS 11, tvOS 11, watchOS 4, *)) return CVYCbCrMatrixGetStringForIntegerCodePoint(space); #endif case AVCOL_SPC_UNSPECIFIED: return NULL; } } CFStringRef av_map_videotoolbox_color_primaries_from_av(enum AVColorPrimaries pri) { switch (pri) { case AVCOL_PRI_BT2020: #if HAVE_KCVIMAGEBUFFERCOLORPRIMARIES_ITU_R_2020 if (__builtin_available(macOS 10.11, iOS 9, *)) return kCVImageBufferColorPrimaries_ITU_R_2020; #endif return CFSTR("ITU_R_2020"); case AVCOL_PRI_BT709: return kCVImageBufferColorPrimaries_ITU_R_709_2; case AVCOL_PRI_SMPTE170M: return kCVImageBufferColorPrimaries_SMPTE_C; case AVCOL_PRI_BT470BG: return kCVImageBufferColorPrimaries_EBU_3213; default: #if HAVE_KCVIMAGEBUFFERTRANSFERFUNCTION_ITU_R_2100_HLG if (__builtin_available(macOS 10.13, iOS 11, tvOS 11, watchOS 4, *)) return CVColorPrimariesGetStringForIntegerCodePoint(pri); #endif case AVCOL_PRI_UNSPECIFIED: return NULL; } } CFStringRef av_map_videotoolbox_color_trc_from_av(enum AVColorTransferCharacteristic trc) { switch (trc) { case AVCOL_TRC_SMPTE2084: #if HAVE_KCVIMAGEBUFFERTRANSFERFUNCTION_SMPTE_ST_2084_PQ if (__builtin_available(macOS 10.13, iOS 11, *)) return kCVImageBufferTransferFunction_SMPTE_ST_2084_PQ; #endif return CFSTR("SMPTE_ST_2084_PQ"); case AVCOL_TRC_BT2020_10: case AVCOL_TRC_BT2020_12: #if HAVE_KCVIMAGEBUFFERTRANSFERFUNCTION_ITU_R_2020 if (__builtin_available(macOS 10.11, iOS 9, *)) return kCVImageBufferTransferFunction_ITU_R_2020; #endif return CFSTR("ITU_R_2020"); case AVCOL_TRC_BT709: return kCVImageBufferTransferFunction_ITU_R_709_2; case AVCOL_TRC_SMPTE240M: return kCVImageBufferTransferFunction_SMPTE_240M_1995; case AVCOL_TRC_SMPTE428: #if HAVE_KCVIMAGEBUFFERTRANSFERFUNCTION_SMPTE_ST_428_1 if (__builtin_available(macOS 10.12, iOS 10, *)) return kCVImageBufferTransferFunction_SMPTE_ST_428_1; #endif return CFSTR("SMPTE_ST_428_1"); case AVCOL_TRC_ARIB_STD_B67: #if HAVE_KCVIMAGEBUFFERTRANSFERFUNCTION_ITU_R_2100_HLG if (__builtin_available(macOS 10.13, iOS 11, *)) return kCVImageBufferTransferFunction_ITU_R_2100_HLG; #endif return CFSTR("ITU_R_2100_HLG"); case AVCOL_TRC_GAMMA22: return kCVImageBufferTransferFunction_UseGamma; case AVCOL_TRC_GAMMA28: return kCVImageBufferTransferFunction_UseGamma; default: #if HAVE_KCVIMAGEBUFFERTRANSFERFUNCTION_ITU_R_2100_HLG if (__builtin_available(macOS 10.13, iOS 11, tvOS 11, watchOS 4, *)) return CVTransferFunctionGetStringForIntegerCodePoint(trc); #endif case AVCOL_TRC_UNSPECIFIED: return NULL; } } /** * Copy all attachments for the specified mode from the given buffer. */ static CFDictionaryRef vt_cv_buffer_copy_attachments(CVBufferRef buffer, CVAttachmentMode attachment_mode) { CFDictionaryRef dict; // Check that our SDK is at least macOS 12 / iOS 15 / tvOS 15 #if (TARGET_OS_OSX && defined(__MAC_12_0) && __MAC_OS_X_VERSION_MAX_ALLOWED >= __MAC_12_0) || \ (TARGET_OS_IOS && defined(__IPHONE_15_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_15_0) || \ (TARGET_OS_TV && defined(__TVOS_15_0) && __TV_OS_VERSION_MAX_ALLOWED >= __TVOS_15_0) // On recent enough versions, just use the respective API if (__builtin_available(macOS 12.0, iOS 15.0, tvOS 15.0, *)) return CVBufferCopyAttachments(buffer, attachment_mode); #endif // Check that the target is lower than macOS 12 / iOS 15 / tvOS 15 // else this would generate a deprecation warning and anyway never run because // the runtime availability check above would be always true. #if (TARGET_OS_OSX && (!defined(__MAC_12_0) || __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_12_0)) || \ (TARGET_OS_IOS && (!defined(__IPHONE_15_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_15_0)) || \ (TARGET_OS_TV && (!defined(__TVOS_15_0) || __TV_OS_VERSION_MIN_REQUIRED < __TVOS_15_0)) // Fallback on SDKs or runtime versions < macOS 12 / iOS 15 / tvOS 15 dict = CVBufferGetAttachments(buffer, attachment_mode); return (dict) ? CFDictionaryCreateCopy(NULL, dict) : NULL; #else return NULL; // Impossible, just make the compiler happy #endif } static int vt_pixbuf_set_colorspace(void *log_ctx, CVPixelBufferRef pixbuf, const AVFrame *src) { CGColorSpaceRef colorspace = NULL; CFStringRef colormatrix = NULL, colorpri = NULL, colortrc = NULL; Float32 gamma = 0; colormatrix = av_map_videotoolbox_color_matrix_from_av(src->colorspace); if (colormatrix) CVBufferSetAttachment(pixbuf, kCVImageBufferYCbCrMatrixKey, colormatrix, kCVAttachmentMode_ShouldPropagate); else { CVBufferRemoveAttachment(pixbuf, kCVImageBufferYCbCrMatrixKey); if (src->colorspace != AVCOL_SPC_UNSPECIFIED && src->colorspace != AVCOL_SPC_RGB) av_log(log_ctx, AV_LOG_WARNING, "Color space %s is not supported.\n", av_color_space_name(src->colorspace)); } colorpri = av_map_videotoolbox_color_primaries_from_av(src->color_primaries); if (colorpri) CVBufferSetAttachment(pixbuf, kCVImageBufferColorPrimariesKey, colorpri, kCVAttachmentMode_ShouldPropagate); else { CVBufferRemoveAttachment(pixbuf, kCVImageBufferColorPrimariesKey); if (src->color_primaries != AVCOL_SPC_UNSPECIFIED) av_log(log_ctx, AV_LOG_WARNING, "Color primaries %s is not supported.\n", av_color_primaries_name(src->color_primaries)); } colortrc = av_map_videotoolbox_color_trc_from_av(src->color_trc); if (colortrc) CVBufferSetAttachment(pixbuf, kCVImageBufferTransferFunctionKey, colortrc, kCVAttachmentMode_ShouldPropagate); else { CVBufferRemoveAttachment(pixbuf, kCVImageBufferTransferFunctionKey); if (src->color_trc != AVCOL_TRC_UNSPECIFIED) av_log(log_ctx, AV_LOG_WARNING, "Color transfer function %s is not supported.\n", av_color_transfer_name(src->color_trc)); } if (src->color_trc == AVCOL_TRC_GAMMA22) gamma = 2.2; else if (src->color_trc == AVCOL_TRC_GAMMA28) gamma = 2.8; if (gamma != 0) { CFNumberRef gamma_level = CFNumberCreate(NULL, kCFNumberFloat32Type, &gamma); CVBufferSetAttachment(pixbuf, kCVImageBufferGammaLevelKey, gamma_level, kCVAttachmentMode_ShouldPropagate); CFRelease(gamma_level); } else CVBufferRemoveAttachment(pixbuf, kCVImageBufferGammaLevelKey); #if (TARGET_OS_OSX && __MAC_OS_X_VERSION_MAX_ALLOWED >= 100800) || \ (TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000) if (__builtin_available(macOS 10.8, iOS 10, *)) { CFDictionaryRef attachments = vt_cv_buffer_copy_attachments(pixbuf, kCVAttachmentMode_ShouldPropagate); if (attachments) { colorspace = CVImageBufferCreateColorSpaceFromAttachments(attachments); CFRelease(attachments); } } #endif // Done outside the above preprocessor code and if's so that // in any case a wrong kCVImageBufferCGColorSpaceKey is removed // if the above code is not used or fails. if (colorspace) { CVBufferSetAttachment(pixbuf, kCVImageBufferCGColorSpaceKey, colorspace, kCVAttachmentMode_ShouldPropagate); CFRelease(colorspace); } else CVBufferRemoveAttachment(pixbuf, kCVImageBufferCGColorSpaceKey); return 0; } static int vt_pixbuf_set_attachments(void *log_ctx, CVPixelBufferRef pixbuf, const AVFrame *src) { int ret; ret = vt_pixbuf_set_par(log_ctx, pixbuf, src); if (ret < 0) return ret; ret = vt_pixbuf_set_colorspace(log_ctx, pixbuf, src); if (ret < 0) return ret; ret = vt_pixbuf_set_chromaloc(log_ctx, pixbuf, src); if (ret < 0) return ret; return 0; } int av_vt_pixbuf_set_attachments(void *log_ctx, CVPixelBufferRef pixbuf, const AVFrame *src) { return vt_pixbuf_set_attachments(log_ctx, pixbuf, src); } static int vt_map_frame(AVHWFramesContext *ctx, AVFrame *dst, const AVFrame *src, int flags) { CVPixelBufferRef pixbuf = (CVPixelBufferRef)src->data[3]; OSType pixel_format = CVPixelBufferGetPixelFormatType(pixbuf); CVReturn err; uint32_t map_flags = 0; int ret; int i; enum AVPixelFormat format; format = av_map_videotoolbox_format_to_pixfmt(pixel_format); if (dst->format != format) { av_log(ctx, AV_LOG_ERROR, "Unsupported or mismatching pixel format: %s\n", av_fourcc2str(pixel_format)); return AVERROR_UNKNOWN; } if (CVPixelBufferGetWidth(pixbuf) != ctx->width || CVPixelBufferGetHeight(pixbuf) != ctx->height) { av_log(ctx, AV_LOG_ERROR, "Inconsistent frame dimensions.\n"); return AVERROR_UNKNOWN; } if (flags == AV_HWFRAME_MAP_READ) map_flags = kCVPixelBufferLock_ReadOnly; err = CVPixelBufferLockBaseAddress(pixbuf, map_flags); if (err != kCVReturnSuccess) { av_log(ctx, AV_LOG_ERROR, "Error locking the pixel buffer.\n"); return AVERROR_UNKNOWN; } if (CVPixelBufferIsPlanar(pixbuf)) { int planes = CVPixelBufferGetPlaneCount(pixbuf); for (i = 0; i < planes; i++) { dst->data[i] = CVPixelBufferGetBaseAddressOfPlane(pixbuf, i); dst->linesize[i] = CVPixelBufferGetBytesPerRowOfPlane(pixbuf, i); } } else { dst->data[0] = CVPixelBufferGetBaseAddress(pixbuf); dst->linesize[0] = CVPixelBufferGetBytesPerRow(pixbuf); } ret = ff_hwframe_map_create(src->hw_frames_ctx, dst, src, vt_unmap, (void *)(uintptr_t)map_flags); if (ret < 0) goto unlock; return 0; unlock: CVPixelBufferUnlockBaseAddress(pixbuf, map_flags); return ret; } static int vt_transfer_data_from(AVHWFramesContext *hwfc, AVFrame *dst, const AVFrame *src) { AVFrame *map; int err; if (dst->width > hwfc->width || dst->height > hwfc->height) return AVERROR(EINVAL); map = av_frame_alloc(); if (!map) return AVERROR(ENOMEM); map->format = dst->format; err = vt_map_frame(hwfc, map, src, AV_HWFRAME_MAP_READ); if (err) goto fail; map->width = dst->width; map->height = dst->height; err = av_frame_copy(dst, map); if (err) goto fail; err = 0; fail: av_frame_free(&map); return err; } static int vt_transfer_data_to(AVHWFramesContext *hwfc, AVFrame *dst, const AVFrame *src) { AVFrame *map; int err; if (src->width > hwfc->width || src->height > hwfc->height) return AVERROR(EINVAL); map = av_frame_alloc(); if (!map) return AVERROR(ENOMEM); map->format = src->format; err = vt_map_frame(hwfc, map, dst, AV_HWFRAME_MAP_WRITE | AV_HWFRAME_MAP_OVERWRITE); if (err) goto fail; map->width = src->width; map->height = src->height; err = av_frame_copy(map, src); if (err) goto fail; err = vt_pixbuf_set_attachments(hwfc, (CVPixelBufferRef)dst->data[3], src); if (err) goto fail; err = 0; fail: av_frame_free(&map); return err; } static int vt_map_from(AVHWFramesContext *hwfc, AVFrame *dst, const AVFrame *src, int flags) { int err; if (dst->format == AV_PIX_FMT_NONE) dst->format = hwfc->sw_format; else if (dst->format != hwfc->sw_format) return AVERROR(ENOSYS); err = vt_map_frame(hwfc, dst, src, flags); if (err) return err; dst->width = src->width; dst->height = src->height; err = av_frame_copy_props(dst, src); if (err) return err; return 0; } static int vt_device_create(AVHWDeviceContext *ctx, const char *device, AVDictionary *opts, int flags) { if (device && device[0]) { av_log(ctx, AV_LOG_ERROR, "Device selection unsupported.\n"); return AVERROR_UNKNOWN; } return 0; } const HWContextType ff_hwcontext_type_videotoolbox = { .type = AV_HWDEVICE_TYPE_VIDEOTOOLBOX, .name = "videotoolbox", .frames_hwctx_size = sizeof(VTFramesContext), .device_create = vt_device_create, .frames_init = vt_frames_init, .frames_get_buffer = vt_get_buffer, .frames_get_constraints = vt_frames_get_constraints, .frames_uninit = vt_frames_uninit, .transfer_get_formats = vt_transfer_get_formats, .transfer_data_to = vt_transfer_data_to, .transfer_data_from = vt_transfer_data_from, .map_from = vt_map_from, .pix_fmts = (const enum AVPixelFormat[]){ AV_PIX_FMT_VIDEOTOOLBOX, AV_PIX_FMT_NONE }, };