CVPixelBuffers attachments are set on the underlying IOSurface, and requires an IPC call, Set them with a single call instead of multiple calls, and cache the attachments dictionary.
249 lines
7.2 KiB
C
249 lines
7.2 KiB
C
/* rotate_vt.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 <VideoToolbox/VideoToolbox.h>
|
|
#include "handbrake/handbrake.h"
|
|
#include "cv_utils.h"
|
|
|
|
struct hb_filter_private_s
|
|
{
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wunguarded-availability-new"
|
|
VTPixelRotationSessionRef session;
|
|
#pragma GCC diagnostic pop
|
|
CVPixelBufferPoolRef pool;
|
|
CFDictionaryRef attachments;
|
|
|
|
hb_filter_init_t input;
|
|
hb_filter_init_t output;
|
|
};
|
|
|
|
static int rotate_vt_init(hb_filter_object_t *filter,
|
|
hb_filter_init_t *init);
|
|
|
|
static int rotate_vt_work(hb_filter_object_t *filter,
|
|
hb_buffer_t **buf_in,
|
|
hb_buffer_t **buf_out);
|
|
|
|
static void rotate_vt_close(hb_filter_object_t *filter);
|
|
|
|
static const char rotate_vt_template[] =
|
|
"angle=^(0|90|180|270)$:hflip=^"HB_BOOL_REG"$:disable=^"HB_BOOL_REG"$";
|
|
|
|
hb_filter_object_t hb_filter_rotate_vt =
|
|
{
|
|
.id = HB_FILTER_ROTATE_VT,
|
|
.enforce_order = 1,
|
|
.name = "Rotate (VideoToolbox)",
|
|
.settings = NULL,
|
|
.init = rotate_vt_init,
|
|
.work = rotate_vt_work,
|
|
.close = rotate_vt_close,
|
|
.settings_template = rotate_vt_template,
|
|
};
|
|
|
|
static int rotate_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("rotate_vt: calloc failed");
|
|
return -1;
|
|
}
|
|
hb_filter_private_t *pv = filter->private_data;
|
|
pv->input = *init;
|
|
|
|
hb_dict_t *settings = filter->settings;
|
|
hb_rational_t par = init->geometry.par;
|
|
int width = init->geometry.width;
|
|
int height = init->geometry.height;
|
|
int angle = 0, hflip = 0;
|
|
|
|
if (__builtin_available (macOS 13, *))
|
|
{
|
|
CFStringRef trans = kVTRotation_0;
|
|
|
|
hb_dict_extract_int(&angle, settings, "angle");
|
|
hb_dict_extract_bool(&hflip, settings, "hflip");
|
|
|
|
switch (angle)
|
|
{
|
|
case 0:
|
|
break;
|
|
case 90:
|
|
trans = kVTRotation_CW90;
|
|
width = init->geometry.height;
|
|
height = init->geometry.width;
|
|
par.num = init->geometry.par.den;
|
|
par.den = init->geometry.par.num;
|
|
break;
|
|
case 180:
|
|
trans = kVTRotation_180;
|
|
break;
|
|
case 270:
|
|
trans = kVTRotation_CCW90;
|
|
width = init->geometry.height;
|
|
height = init->geometry.width;
|
|
par.num = init->geometry.par.den;
|
|
par.den = init->geometry.par.num;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Session init
|
|
OSStatus err = noErr;
|
|
err = VTPixelRotationSessionCreate(kCFAllocatorDefault, &pv->session);
|
|
|
|
if (err != noErr)
|
|
{
|
|
hb_log("rotate_vt: err=%"PRId64"", (int64_t)err);
|
|
return err;
|
|
}
|
|
|
|
err = VTSessionSetProperty(pv->session,
|
|
kVTPixelRotationPropertyKey_Rotation,
|
|
trans);
|
|
|
|
if (err != noErr)
|
|
{
|
|
hb_log("rotate_vt: kVTPixelRotationPropertyKey_Rotation failed");
|
|
}
|
|
|
|
if (hflip)
|
|
{
|
|
err = VTSessionSetProperty(pv->session,
|
|
kVTPixelRotationPropertyKey_FlipHorizontalOrientation,
|
|
kCFBooleanTrue);
|
|
|
|
if (err != noErr)
|
|
{
|
|
hb_log("rotate_vt: kVTPixelRotationPropertyKey_FlipHorizontalOrientation failed");
|
|
}
|
|
}
|
|
}
|
|
|
|
pv->pool = hb_cv_create_pixel_buffer_pool(width, height, init->pix_fmt, init->color_range);
|
|
if (pv->pool == NULL)
|
|
{
|
|
hb_log("rotate_vt: CVPixelBufferPoolCreate failed");
|
|
return -1;
|
|
}
|
|
|
|
CFMutableDictionaryRef attachments = CFDictionaryCreateMutable(NULL, 0,
|
|
&kCFTypeDictionaryKeyCallBacks,
|
|
&kCFTypeDictionaryValueCallBacks);
|
|
hb_cv_add_color_tag(attachments,
|
|
init->color_prim, init->color_transfer,
|
|
init->color_matrix, init->chroma_location);
|
|
pv->attachments = attachments;
|
|
|
|
|
|
init->geometry.width = width;
|
|
init->geometry.height = height;
|
|
init->geometry.par = par;
|
|
pv->output = *init;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void rotate_vt_close(hb_filter_object_t *filter)
|
|
{
|
|
hb_filter_private_t *pv = filter->private_data;
|
|
|
|
if (pv == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (__builtin_available (macOS 13, *))
|
|
{
|
|
if (pv->session)
|
|
{
|
|
VTPixelRotationSessionInvalidate(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 rotate_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("rotate_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("rotate_vt: CVPixelBufferPoolCreatePixelBuffer failed");
|
|
return HB_FILTER_FAILED;
|
|
}
|
|
|
|
// Do work
|
|
if (__builtin_available (macOS 13, *))
|
|
{
|
|
err = VTPixelRotationSessionRotateImage(pv->session, source_buf, dest_buf);
|
|
if (err != noErr)
|
|
{
|
|
hb_log("rotate_vt: VTPixelRotationSessionRotateImage 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;
|
|
}
|