/* cropscale_vt.c
Copyright (c) 2003-2025 HandBrake Team
This file is part of the HandBrake source code
Homepage: .
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
#include "handbrake/handbrake.h"
#include "cv_utils.h"
struct hb_filter_private_s
{
VTPixelTransferSessionRef session;
CVPixelBufferPoolRef pool;
CFDictionaryRef attachments;
hb_filter_init_t input;
hb_filter_init_t output;
};
static int crop_scale_vt_init(hb_filter_object_t *filter,
hb_filter_init_t *init);
static int crop_scale_vt_work(hb_filter_object_t *filter,
hb_buffer_t **buf_in,
hb_buffer_t **buf_out);
static void crop_scale_vt_close(hb_filter_object_t *filter);
static const char crop_scale_vt_template[] =
"width=^"HB_INT_REG"$:height=^"HB_INT_REG"$:"
"crop-top=^"HB_INT_REG"$:crop-bottom=^"HB_INT_REG"$:"
"crop-left=^"HB_INT_REG"$:crop-right=^"HB_INT_REG"$:"
"format=^"HB_INT_REG"$:color-range=^"HB_INT_REG"$";
hb_filter_object_t hb_filter_crop_scale_vt =
{
.id = HB_FILTER_CROP_SCALE_VT,
.enforce_order = 1,
.name = "Crop and Scale (VideoToolbox)",
.settings = NULL,
.init = crop_scale_vt_init,
.work = crop_scale_vt_work,
.close = crop_scale_vt_close,
.settings_template = crop_scale_vt_template,
};
static int crop_scale_vt_init(hb_filter_object_t *filter,
hb_filter_init_t *init)
{
filter->private_data = calloc(sizeof(struct hb_filter_private_s), 1);
if (filter->private_data == NULL)
{
hb_error("cropscale_vt: calloc failed");
return -1;
}
hb_filter_private_t *pv = filter->private_data;
pv->input = *init;
hb_dict_t *settings = filter->settings;
int width, height;
int top = 0, bottom = 0, left = 0, right = 0;
int crop_width, crop_height;
int crop_offset_left, crop_offset_top;
hb_dict_extract_int(&width, settings, "width");
hb_dict_extract_int(&height, settings, "height");
// Convert crop settings to 'crop'
hb_dict_extract_int(&top, settings, "crop-top");
hb_dict_extract_int(&bottom, settings, "crop-bottom");
hb_dict_extract_int(&left, settings, "crop-left");
hb_dict_extract_int(&right, settings, "crop-right");
crop_width = init->geometry.width - left - right;
crop_height = init->geometry.height - top - bottom;
crop_offset_left = left / 2 - right / 2;
crop_offset_top = top / 2 - bottom / 2;
// Set up the source clean aperture dictionary
// VTPixelTransferSessionRef will use it to crop the source buffer
// before resizing it to fit the destination buffer
CFNumberRef crop_width_num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &crop_width);
CFNumberRef crop_height_num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &crop_height);
CFNumberRef crop_offset_left_num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &crop_offset_left);
CFNumberRef crop_offset_top_num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &crop_offset_top);
const void *clean_aperture_keys[4] =
{
kCVImageBufferCleanApertureWidthKey, kCVImageBufferCleanApertureHeightKey,
kCVImageBufferCleanApertureHorizontalOffsetKey, kCVImageBufferCleanApertureVerticalOffsetKey
};
const void *source_clean_aperture_values[4] =
{
crop_width_num, crop_height_num, crop_offset_left_num, crop_offset_top_num
};
CFDictionaryRef source_clean_aperture = CFDictionaryCreate(kCFAllocatorDefault,
clean_aperture_keys,
source_clean_aperture_values,
4,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFMutableDictionaryRef attachments = CFDictionaryCreateMutable(NULL, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(attachments, kCVImageBufferCleanApertureKey, source_clean_aperture);
CFRelease(source_clean_aperture);
hb_cv_add_color_tag(attachments,
init->color_prim, init->color_transfer,
init->color_matrix, init->chroma_location);
pv->attachments = attachments;
CFRelease(crop_width_num);
CFRelease(crop_height_num);
CFRelease(crop_offset_left_num);
CFRelease(crop_offset_top_num);
// Sessions initialization
OSStatus err = noErr;
err = VTPixelTransferSessionCreate(kCFAllocatorDefault, &pv->session);
if (err != noErr)
{
hb_log("cropscale_vt: err=%"PRId64"", (int64_t)err);
return err;
}
err = VTSessionSetProperty(pv->session,
kVTPixelTransferPropertyKey_ScalingMode,
kVTScalingMode_CropSourceToCleanAperture);
if (err != noErr)
{
hb_log("cropscale_vt: kVTPixelTransferPropertyKey_ScalingMode failed");
}
// Set the destination clean aperture dictionary
int zero = 0;
CFNumberRef width_num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &width);
CFNumberRef height_num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &height);
CFNumberRef offset_left_num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &zero);
CFNumberRef offset_top_num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &zero);
const void *destination_clean_aperture_values[4] =
{
width_num, height_num, offset_left_num, offset_top_num
};
CFDictionaryRef destination_clean_aperture = CFDictionaryCreate(kCFAllocatorDefault,
clean_aperture_keys,
destination_clean_aperture_values,
4,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFRelease(width_num);
CFRelease(height_num);
CFRelease(offset_left_num);
CFRelease(offset_top_num);
err = VTSessionSetProperty(pv->session,
kVTPixelTransferPropertyKey_DestinationCleanAperture,
destination_clean_aperture);
CFRelease(destination_clean_aperture);
if (err != noErr)
{
hb_log("cropscale_vt: kVTPixelTransferPropertyKey_DestinationCleanAperture failed");
return err;
}
int format = AV_PIX_FMT_NONE;
hb_dict_extract_int(&format, settings, "format");
if (format == AV_PIX_FMT_NONE)
{
format = init->pix_fmt;
}
int color_range = AVCOL_RANGE_UNSPECIFIED;
hb_dict_extract_int(&color_range, settings, "color-range");
if (color_range == AVCOL_RANGE_UNSPECIFIED)
{
color_range = init->color_range;
}
pv->pool = hb_cv_create_pixel_buffer_pool(width, height, format, color_range);
if (pv->pool == NULL)
{
hb_log("cropscale_vt: CVPixelBufferPoolCreate failed");
return -1;
}
err = VTSessionSetProperty(pv->session,
kVTPixelTransferPropertyKey_DownsamplingMode,
kVTDownsamplingMode_Average);
if (err != noErr)
{
hb_log("cropscale_vt: kVTPixelTransferPropertyKey_DownsamplingMode failed");
return err;
}
init->crop[0] = top;
init->crop[1] = bottom;
init->crop[2] = left;
init->crop[3] = right;
hb_limit_rational(&init->geometry.par.num, &init->geometry.par.den,
(int64_t)init->geometry.par.num * height * crop_width,
(int64_t)init->geometry.par.den * width * crop_height, 65535);
init->geometry.width = width;
init->geometry.height = height;
init->pix_fmt = format;
pv->output = *init;
return 0;
}
static void crop_scale_vt_close(hb_filter_object_t *filter)
{
hb_filter_private_t *pv = filter->private_data;
if (pv == NULL)
{
return;
}
if (pv->session)
{
VTPixelTransferSessionInvalidate(pv->session);
CFRelease(pv->session);
}
if (pv->pool)
{
CVPixelBufferPoolRelease(pv->pool);
}
if (pv->attachments)
{
CFRelease(pv->attachments);
}
free(pv);
filter->private_data = NULL;
}
static int crop_scale_vt_work(hb_filter_object_t *filter,
hb_buffer_t **buf_in,
hb_buffer_t **buf_out)
{
hb_filter_private_t *pv = filter->private_data;
hb_buffer_t *in = *buf_in, *out;
if (in->s.flags & HB_BUF_FLAG_EOF)
{
*buf_out = in;
*buf_in = NULL;
return HB_FILTER_DONE;
}
// Setup buffers
OSStatus err = noErr;
CVPixelBufferRef source_buf = hb_cv_get_pixel_buffer(in);
if (source_buf == NULL)
{
hb_log("cropscale_vt: extract_buf failed");
return HB_FILTER_FAILED;
}
hb_cv_set_attachments(source_buf, pv->attachments);
CVPixelBufferRef dest_buf = NULL;
err = CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pv->pool, &dest_buf);
if (err != noErr)
{
hb_log("cropscale_vt: CVPixelBufferPoolCreatePixelBuffer failed");
return HB_FILTER_FAILED;
}
// Do work
err = VTPixelTransferSessionTransferImage(pv->session, source_buf, dest_buf);
if (err != noErr)
{
hb_log("cropscale_vt: VTPixelTransferSessionTransferImage failed");
return HB_FILTER_FAILED;
}
out = hb_buffer_wrapper_init();
out->storage_type = COREMEDIA;
out->storage = dest_buf;
out->f.width = pv->output.geometry.width;
out->f.height = pv->output.geometry.height;
out->f.fmt = pv->output.pix_fmt;
out->f.color_prim = pv->output.color_prim;
out->f.color_transfer = pv->output.color_transfer;
out->f.color_matrix = pv->output.color_matrix;
out->f.color_range = pv->output.color_range;
out->f.chroma_location = pv->output.chroma_location;
hb_buffer_copy_props(out, in);
*buf_out = out;
return HB_FILTER_OK;
}