mirror of https://github.com/FFmpeg/FFmpeg.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
845 lines
27 KiB
845 lines
27 KiB
/* |
|
* 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 <stdint.h> |
|
#include <string.h> |
|
|
|
#include <VideoToolbox/VideoToolbox.h> |
|
|
|
#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_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_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 }, |
|
};
|
|
|