HandBrake/libhb/platform/macosx/vt_common.c

544 lines
16 KiB
C

/* vt_common.c
Copyright (c) 2003-2025 HandBrake Team
This file is part of the HandBrake source code
Homepage: <http://handbrake.fr/>.
It may be used under the terms of the GNU General Public License v2.
For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html
*/
#include "vt_common.h"
#include "cv_utils.h"
#include "handbrake/hbffmpeg.h"
#include <VideoToolbox/VideoToolbox.h>
#include <CoreMedia/CoreMedia.h>
#include <CoreVideo/CoreVideo.h>
#pragma mark - Availability
static OSStatus encoder_properties(CMVideoCodecType codecType, CFStringRef *encoderIDOut, CFDictionaryRef *supportedPropertiesOut)
{
const void *keys[1] = { kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder };
const void *values[1] = { kCFBooleanTrue };
CFDictionaryRef specification = CFDictionaryCreate(NULL, keys, values, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
OSStatus err = VTCopySupportedPropertyDictionaryForEncoder(1920, 1080,
codecType, specification,
encoderIDOut,
supportedPropertiesOut);
CFRelease(specification);
return err;
}
static int is_hardware_encoder_available(CMVideoCodecType codecType, CFStringRef level)
{
Boolean found = false;
CFStringRef encoderIDOut;
CFDictionaryRef supportedPropertiesOut;
OSStatus err = encoder_properties(codecType, &encoderIDOut, &supportedPropertiesOut);
if (err == noErr)
{
if (level != NULL)
{
CFDictionaryRef profiles = CFDictionaryGetValue(supportedPropertiesOut, kVTCompressionPropertyKey_ProfileLevel);
if (profiles != NULL)
{
CFArrayRef listOfValues = CFDictionaryGetValue(profiles, kVTPropertySupportedValueListKey);
if (listOfValues != NULL)
{
found = CFArrayContainsValue(listOfValues, CFRangeMake(0, CFArrayGetCount(listOfValues)), level);
}
}
}
else
{
found = true;
}
CFRelease(encoderIDOut);
CFRelease(supportedPropertiesOut);
}
return found;
}
static int vt_h264_available;
static int vt_h265_available;
static int vt_h265_10bit_available;
static int vt_h265_422_10bit_available;
int hb_vt_is_encoder_available(int encoder)
{
switch (encoder)
{
case HB_VCODEC_VT_H264:
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
vt_h264_available = is_hardware_encoder_available(kCMVideoCodecType_H264, NULL);
});
return vt_h264_available;
}
case HB_VCODEC_VT_H265:
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
vt_h265_available = is_hardware_encoder_available(kCMVideoCodecType_HEVC, NULL);
});
return vt_h265_available;
}
case HB_VCODEC_VT_H265_10BIT:
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (__builtin_available (macOS 11, *))
{
vt_h265_10bit_available = is_hardware_encoder_available(kCMVideoCodecType_HEVC, kVTProfileLevel_HEVC_Main10_AutoLevel);
vt_h265_422_10bit_available = is_hardware_encoder_available(kCMVideoCodecType_HEVC, CFSTR("HEVC_Main42210_AutoLevel"));
}
else
{
vt_h265_10bit_available = 0;
}
});
return vt_h265_10bit_available;
}
}
return 0;
}
#pragma mark - Constant Quality
static int is_constant_quality_available(CMVideoCodecType codecType)
{
#if defined(__aarch64__)
if (__builtin_available (macOS 11, *))
{
CFStringRef encoderIDOut;
CFDictionaryRef supportedPropertiesOut;
OSStatus err = encoder_properties(codecType, &encoderIDOut, &supportedPropertiesOut);
if (err == noErr)
{
Boolean keyExists;
CFBooleanRef value = kCFBooleanFalse;
keyExists = CFDictionaryGetValueIfPresent(supportedPropertiesOut,
kVTCompressionPropertyKey_Quality,
(const void **)&value);
CFRelease(encoderIDOut);
CFRelease(supportedPropertiesOut);
if (keyExists)
{
return 1;
}
}
}
#endif
return 0;
}
static int vt_h264_constant_quality;
static int vt_h265_constant_quality;
int hb_vt_is_constant_quality_available(int encoder)
{
switch (encoder)
{
case HB_VCODEC_VT_H264:
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
vt_h264_constant_quality = is_constant_quality_available(kCMVideoCodecType_H264);
});
return vt_h264_constant_quality;
}
case HB_VCODEC_VT_H265:
case HB_VCODEC_VT_H265_10BIT:
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
vt_h265_constant_quality = is_constant_quality_available(kCMVideoCodecType_HEVC);
});
return vt_h265_constant_quality;
}
}
return 0;
}
int hb_vt_is_multipass_available(int encoder)
{
switch (encoder)
{
case HB_VCODEC_VT_H264:
{
return 1;
}
case HB_VCODEC_VT_H265:
case HB_VCODEC_VT_H265_10BIT:
{
#if defined(__aarch64__)
if (__builtin_available (macOS 12, *))
{
return 1;
}
#endif
return 0;
}
}
return 0;
}
#pragma mark - Settings
static const char * const vt_h26x_preset_name[] =
{
"speed", "quality", NULL
};
static const char * const vt_h264_profile_name[] =
{
"auto", "baseline", "main", "high", NULL
};
static const char * const vt_h265_profile_name[] =
{
"auto", "main", NULL
};
static const char * const vt_h265_10_profile_name[] =
{
"auto", "main10", NULL
};
static const char * const vt_h265_422_10_profile_name[] =
{
"auto", "main10", "main422-10", NULL
};
static const char * vt_h264_level_names[] =
{
"auto", "1.3", "3.0", "3.1", "3.2", "4.0", "4.1", "4.2", "5.0", "5.1", "5.2", NULL
};
static const char * const vt_h265_level_names[] =
{
"auto", NULL,
};
static const enum AVPixelFormat vt_h26x_pix_fmts[] =
{
AV_PIX_FMT_P410, AV_PIX_FMT_NV24, AV_PIX_FMT_P210, AV_PIX_FMT_NV16, AV_PIX_FMT_P010, AV_PIX_FMT_YUV420P, AV_PIX_FMT_NV12, AV_PIX_FMT_NONE
};
static const enum AVPixelFormat vt_h265_10bit_pix_fmts[] =
{
AV_PIX_FMT_P410, AV_PIX_FMT_NV24, AV_PIX_FMT_P210, AV_PIX_FMT_NV16, AV_PIX_FMT_P010, AV_PIX_FMT_NV12, AV_PIX_FMT_NONE
};
const int* hb_vt_get_pix_fmts(int encoder)
{
switch (encoder)
{
case HB_VCODEC_VT_H264:
case HB_VCODEC_VT_H265:
return vt_h26x_pix_fmts;
case HB_VCODEC_VT_H265_10BIT:
return vt_h265_10bit_pix_fmts;
}
return NULL;
}
int hb_vt_get_best_pix_fmt(int encoder, const char *profile)
{
switch (encoder)
{
case HB_VCODEC_VT_H264:
case HB_VCODEC_VT_H265:
return AV_PIX_FMT_NV12;
case HB_VCODEC_VT_H265_10BIT:
if (profile != NULL && !strcasecmp(profile, "main422-10"))
{
return AV_PIX_FMT_P210;
}
else
{
return AV_PIX_FMT_P010;
}
}
return AV_PIX_FMT_NV12;
}
const char* const* hb_vt_preset_get_names(int encoder)
{
return vt_h26x_preset_name;
}
const char* const* hb_vt_profile_get_names(int encoder)
{
switch (encoder)
{
case HB_VCODEC_VT_H264:
return vt_h264_profile_name;
case HB_VCODEC_VT_H265:
return vt_h265_profile_name;
case HB_VCODEC_VT_H265_10BIT:
{
if (vt_h265_422_10bit_available)
{
return vt_h265_422_10_profile_name;
}
else
{
return vt_h265_10_profile_name;
}
}
}
return NULL;
}
const char* const* hb_vt_level_get_names(int encoder)
{
switch (encoder)
{
case HB_VCODEC_VT_H264:
return vt_h264_level_names;
case HB_VCODEC_VT_H265:
case HB_VCODEC_VT_H265_10BIT:
return vt_h265_level_names;
}
return NULL;
}
hb_buffer_t * hb_vt_buffer_dup(const hb_buffer_t *src)
{
CVPixelBufferRef pix_buf = hb_cv_get_pixel_buffer(src);
if (pix_buf == NULL)
{
return NULL;
}
CFRetain(pix_buf);
hb_buffer_t *out = hb_buffer_wrapper_init();
out->storage_type = COREMEDIA;
out->storage = pix_buf;
out->f = src->f;
hb_buffer_copy_props(out, src);
return out;
}
hb_buffer_t * copy_video_buffer_to_hw_video_buffer(void *hw_frames_ctx, hb_buffer_t **in)
{
hb_buffer_t *buf = *in;
OSType cv_pix_fmt = hb_cv_get_pixel_format(buf->f.fmt, buf->f.color_range);
CFNumberRef pix_fmt_num = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &cv_pix_fmt);
CFNumberRef width_num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &buf->f.width);
CFNumberRef height_num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &buf->f.height);
const void *attrs_keys[4] =
{
kCVPixelBufferWidthKey, kCVPixelBufferHeightKey,
kCVPixelBufferPixelFormatTypeKey, kCVPixelBufferMetalCompatibilityKey
};
const void *attrs_values[4] =
{
width_num, height_num, pix_fmt_num, kCFBooleanTrue
};
CFDictionaryRef attrs = CFDictionaryCreate(kCFAllocatorDefault,
attrs_keys, attrs_values, 4,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFRelease(width_num);
CFRelease(height_num);
CFRelease(pix_fmt_num);
CVPixelBufferRef pix_buf = NULL;
CVReturn ret = CVPixelBufferCreate(kCFAllocatorDefault,
buf->f.width, buf->f.height,
cv_pix_fmt, attrs,
&pix_buf);
CFRelease(attrs);
if (ret != kCVReturnSuccess)
{
hb_buffer_close(&buf);
return NULL;
}
CVPixelBufferLockBaseAddress(pix_buf, 0);
for (int pp = 0; pp <= buf->f.max_plane; pp++)
{
void *dst = CVPixelBufferGetBaseAddressOfPlane(pix_buf, pp);
size_t dst_stride = CVPixelBufferGetBytesPerRowOfPlane(pix_buf, pp);
void *src = buf->plane[pp].data;
size_t src_height = buf->plane[pp].height;
size_t src_stride = buf->plane[pp].stride;
size_t stride = MIN(dst_stride, src_stride);
for (int y = 0; y < src_height; y++)
{
memcpy(dst, src, stride);
src += src_stride;
dst += dst_stride;
}
}
CVPixelBufferUnlockBaseAddress(pix_buf, 0);
hb_buffer_t *out = hb_buffer_wrapper_init();
out->storage_type = COREMEDIA;
out->storage = pix_buf;
out->f = buf->f;
hb_buffer_copy_props(out, buf);
hb_buffer_close(&buf);
return out;
}
static int are_filters_supported(hb_list_t *filters)
{
int ret = 1;
for (int i = 0; i < hb_list_count(filters); i++)
{
int supported = 1;
hb_filter_object_t *filter = hb_list_item(filters, i);
switch (filter->id)
{
case HB_FILTER_DETELECINE:
case HB_FILTER_DECOMB:
case HB_FILTER_DEBLOCK:
case HB_FILTER_DENOISE:
case HB_FILTER_NLMEANS:
case HB_FILTER_COLORSPACE:
case HB_FILTER_FORMAT:
supported = 0;
break;
default:
break;
}
if (supported == 0)
{
hb_deep_log(2, "videotoolbox: %s isn't yet supported for hw video frames", filter->name);
ret = 0;
}
}
return ret;
}
static void fix_prores_pix_fmt(hb_job_t *job)
{
// TODO: Find a better way
// VideoToolbox ProRes decoder uses an higher bitdepth
// than FFmpeg software decoder. We get only the pixel format
// from the software decoder in decavcodec.c, so set
// a better one here
if (job->title->video_codec_param == AV_CODEC_ID_PRORES)
{
switch (job->title->video_codec_profile)
{
case AV_PROFILE_PRORES_XQ:
case AV_PROFILE_PRORES_4444:
job->input_pix_fmt = AV_PIX_FMT_P416;
break;
default:
break;
}
}
}
static void replace_filter(hb_job_t *job, int prev_filter_id, int new_filter_id)
{
hb_list_t *list = job->list_filter;
hb_filter_object_t *filter = hb_filter_find(list, prev_filter_id);
if (filter != NULL)
{
hb_dict_t *settings = filter->settings;
if (settings != NULL)
{
hb_list_rem(list, filter);
hb_filter_object_t *new_filter = hb_filter_init(new_filter_id);
hb_add_filter_dict(job, new_filter, settings);
hb_filter_close(&filter);
}
}
}
void hb_vt_setup_hw_filters(hb_job_t *job)
{
if (job->hw_pix_fmt == AV_PIX_FMT_VIDEOTOOLBOX)
{
fix_prores_pix_fmt(job);
// Add adapter
hb_filter_object_t *filter = hb_filter_init(HB_FILTER_PRE_VT);
char *settings = hb_strdup_printf("rotation=%d", job->title->rotation);
hb_add_filter(job, filter, settings);
free(settings);
replace_filter(job, HB_FILTER_COMB_DETECT, HB_FILTER_COMB_DETECT_VT);
replace_filter(job, HB_FILTER_YADIF, HB_FILTER_YADIF_VT);
replace_filter(job, HB_FILTER_BWDIF, HB_FILTER_BWDIF_VT);
replace_filter(job, HB_FILTER_CROP_SCALE, HB_FILTER_CROP_SCALE_VT);
replace_filter(job, HB_FILTER_CHROMA_SMOOTH, HB_FILTER_CHROMA_SMOOTH_VT);
replace_filter(job, HB_FILTER_ROTATE, HB_FILTER_ROTATE_VT);
replace_filter(job, HB_FILTER_PAD, HB_FILTER_PAD_VT);
replace_filter(job, HB_FILTER_GRAYSCALE, HB_FILTER_GRAYSCALE_VT);
replace_filter(job, HB_FILTER_LAPSHARP, HB_FILTER_LAPSHARP_VT);
replace_filter(job, HB_FILTER_UNSHARP, HB_FILTER_UNSHARP_VT);
int count = hb_list_count(job->list_filter);
if (count)
{
// Avoid an additional VTPixelTransferSession, when possible
// do the scale and pixel format conversion in one pass
hb_filter_object_t *last = hb_list_item(job->list_filter, count - 1);
if (last->id == HB_FILTER_CROP_SCALE_VT)
{
int pix_fmt = hb_vt_get_best_pix_fmt(job->vcodec, job->encoder_profile);
hb_dict_set(last->settings, "format", hb_value_int(pix_fmt));
}
}
}
}
static const int vt_encoders[] =
{
HB_VCODEC_VT_H264,
HB_VCODEC_VT_H265,
HB_VCODEC_VT_H265_10BIT,
HB_VCODEC_INVALID
};
hb_hwaccel_t hb_hwaccel_videotoolbox =
{
.id = HB_DECODE_VIDEOTOOLBOX,
.name = "videotoolbox hwaccel",
.encoders = vt_encoders,
.type = AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
.hw_pix_fmt = AV_PIX_FMT_VIDEOTOOLBOX,
.can_filter = are_filters_supported,
.upload = copy_video_buffer_to_hw_video_buffer,
.caps = HB_HWACCEL_CAP_SCAN | HB_HWACCEL_CAP_ROTATE | HB_HWACCEL_CAP_COLOR_RANGE
};