2 * Copyright (c) 2023 Zhao Zhili <zhilizhao@tencent.com>
4 * This file is part of FFmpeg.
6 * FFmpeg is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * FFmpeg is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with FFmpeg; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 #include <VideoToolbox/VideoToolbox.h>
23 #include "libavutil/hwcontext.h"
24 #include "libavutil/hwcontext_videotoolbox.h"
25 #include "libavutil/opt.h"
26 #include "libavutil/pixdesc.h"
28 #include "transpose.h"
31 typedef struct TransposeVtContext {
34 VTPixelRotationSessionRef session;
39 static av_cold int transpose_vt_init(AVFilterContext *avctx)
41 TransposeVtContext *s = avctx->priv;
44 ret = VTPixelRotationSessionCreate(kCFAllocatorDefault, &s->session);
46 av_log(avctx, AV_LOG_ERROR, "Rotation session create failed, %d\n", ret);
47 return AVERROR_EXTERNAL;
53 static av_cold void transpose_vt_uninit(AVFilterContext *avctx)
55 TransposeVtContext *s = avctx->priv;
58 VTPixelRotationSessionInvalidate(s->session);
59 CFRelease(s->session);
64 static int transpose_vt_filter_frame(AVFilterLink *link, AVFrame *in)
67 AVFilterContext *ctx = link->dst;
68 TransposeVtContext *s = ctx->priv;
69 AVFilterLink *outlink = ctx->outputs[0];
75 return ff_filter_frame(outlink, in);
77 out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
79 ret = AVERROR(ENOMEM);
83 ret = av_frame_copy_props(out, in);
87 src = (CVPixelBufferRef)in->data[3];
88 dst = (CVPixelBufferRef)out->data[3];
89 ret = VTPixelRotationSessionRotateImage(s->session, src, dst);
91 av_log(ctx, AV_LOG_ERROR, "transfer image failed, %d\n", ret);
92 ret = AVERROR_EXTERNAL;
98 return ff_filter_frame(outlink, out);
106 static int transpose_vt_recreate_hw_ctx(AVFilterLink *outlink)
108 AVFilterContext *avctx = outlink->src;
109 AVFilterLink *inlink = outlink->src->inputs[0];
110 AVHWFramesContext *hw_frame_ctx_in;
111 AVHWFramesContext *hw_frame_ctx_out;
114 av_buffer_unref(&outlink->hw_frames_ctx);
116 hw_frame_ctx_in = (AVHWFramesContext *)inlink->hw_frames_ctx->data;
117 outlink->hw_frames_ctx = av_hwframe_ctx_alloc(hw_frame_ctx_in->device_ref);
118 hw_frame_ctx_out = (AVHWFramesContext *)outlink->hw_frames_ctx->data;
119 hw_frame_ctx_out->format = AV_PIX_FMT_VIDEOTOOLBOX;
120 hw_frame_ctx_out->sw_format = hw_frame_ctx_in->sw_format;
121 hw_frame_ctx_out->width = outlink->w;
122 hw_frame_ctx_out->height = outlink->h;
124 err = ff_filter_init_hw_frames(avctx, outlink, 1);
128 err = av_hwframe_ctx_init(outlink->hw_frames_ctx);
130 av_log(avctx, AV_LOG_ERROR,
131 "Failed to init videotoolbox frame context, %s\n",
139 static int transpose_vt_config_output(AVFilterLink *outlink)
142 AVFilterContext *avctx = outlink->src;
143 TransposeVtContext *s = avctx->priv;
144 AVFilterLink *inlink = outlink->src->inputs[0];
145 CFStringRef rotation = kVTRotation_0;
146 CFBooleanRef vflip = kCFBooleanFalse;
147 CFBooleanRef hflip = kCFBooleanFalse;
150 av_buffer_unref(&outlink->hw_frames_ctx);
151 outlink->hw_frames_ctx = av_buffer_ref(inlink->hw_frames_ctx);
153 if ((inlink->w >= inlink->h && s->passthrough == TRANSPOSE_PT_TYPE_LANDSCAPE) ||
154 (inlink->w <= inlink->h && s->passthrough == TRANSPOSE_PT_TYPE_PORTRAIT)) {
155 av_log(avctx, AV_LOG_VERBOSE,
156 "w:%d h:%d -> w:%d h:%d (passthrough mode)\n",
157 inlink->w, inlink->h, inlink->w, inlink->h);
161 s->passthrough = TRANSPOSE_PT_TYPE_NONE;
164 case TRANSPOSE_CCLOCK_FLIP:
165 rotation = kVTRotation_CCW90;
166 vflip = kCFBooleanTrue;
169 case TRANSPOSE_CCLOCK:
170 rotation = kVTRotation_CCW90;
173 case TRANSPOSE_CLOCK:
174 rotation = kVTRotation_CW90;
177 case TRANSPOSE_CLOCK_FLIP:
178 rotation = kVTRotation_CW90;
179 vflip = kCFBooleanTrue;
182 case TRANSPOSE_REVERSAL:
183 rotation = kVTRotation_180;
185 case TRANSPOSE_HFLIP:
186 hflip = kCFBooleanTrue;
188 case TRANSPOSE_VFLIP:
189 vflip = kCFBooleanTrue;
192 av_log(avctx, AV_LOG_ERROR, "Failed to set direction to %d\n", s->dir);
193 return AVERROR(EINVAL);
196 err = VTSessionSetProperty(s->session, kVTPixelRotationPropertyKey_Rotation,
199 av_log(avctx, AV_LOG_ERROR, "Set rotation property failed, %d\n", err);
200 return AVERROR_EXTERNAL;
202 err = VTSessionSetProperty(s->session, kVTPixelRotationPropertyKey_FlipVerticalOrientation,
205 av_log(avctx, AV_LOG_ERROR, "Set vertical flip property failed, %d\n", err);
206 return AVERROR_EXTERNAL;
208 err = VTSessionSetProperty(s->session, kVTPixelRotationPropertyKey_FlipHorizontalOrientation,
211 av_log(avctx, AV_LOG_ERROR, "Set horizontal flip property failed, %d\n", err);
212 return AVERROR_EXTERNAL;
218 outlink->w = inlink->h;
219 outlink->h = inlink->w;
220 return transpose_vt_recreate_hw_ctx(outlink);
223 #define OFFSET(x) offsetof(TransposeVtContext, x)
224 #define FLAGS (AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM)
225 static const AVOption transpose_vt_options[] = {
226 { "dir", "set transpose direction",
227 OFFSET(dir), AV_OPT_TYPE_INT, { .i64 = TRANSPOSE_CCLOCK_FLIP }, 0, 6, FLAGS, "dir" },
228 { "cclock_flip", "rotate counter-clockwise with vertical flip",
229 0, AV_OPT_TYPE_CONST, { .i64 = TRANSPOSE_CCLOCK_FLIP }, .flags=FLAGS, .unit = "dir" },
230 { "clock", "rotate clockwise",
231 0, AV_OPT_TYPE_CONST, { .i64 = TRANSPOSE_CLOCK }, .flags=FLAGS, .unit = "dir" },
232 { "cclock", "rotate counter-clockwise",
233 0, AV_OPT_TYPE_CONST, { .i64 = TRANSPOSE_CCLOCK }, .flags=FLAGS, .unit = "dir" },
234 { "clock_flip", "rotate clockwise with vertical flip",
235 0, AV_OPT_TYPE_CONST, { .i64 = TRANSPOSE_CLOCK_FLIP }, .flags=FLAGS, .unit = "dir" },
236 { "reversal", "rotate by half-turn",
237 0, AV_OPT_TYPE_CONST, { .i64 = TRANSPOSE_REVERSAL }, .flags=FLAGS, .unit = "dir" },
238 { "hflip", "flip horizontally",
239 0, AV_OPT_TYPE_CONST, { .i64 = TRANSPOSE_HFLIP }, .flags=FLAGS, .unit = "dir" },
240 { "vflip", "flip vertically",
241 0, AV_OPT_TYPE_CONST, { .i64 = TRANSPOSE_VFLIP }, .flags=FLAGS, .unit = "dir" },
243 { "passthrough", "do not apply transposition if the input matches the specified geometry",
244 OFFSET(passthrough), AV_OPT_TYPE_INT, { .i64=TRANSPOSE_PT_TYPE_NONE }, 0, INT_MAX, FLAGS, "passthrough" },
245 { "none", "always apply transposition",
246 0, AV_OPT_TYPE_CONST, { .i64 = TRANSPOSE_PT_TYPE_NONE }, INT_MIN, INT_MAX, FLAGS, "passthrough" },
247 { "portrait", "preserve portrait geometry",
248 0, AV_OPT_TYPE_CONST, { .i64 = TRANSPOSE_PT_TYPE_PORTRAIT }, INT_MIN, INT_MAX, FLAGS, "passthrough" },
249 { "landscape", "preserve landscape geometry",
250 0, AV_OPT_TYPE_CONST, { .i64 = TRANSPOSE_PT_TYPE_LANDSCAPE }, INT_MIN, INT_MAX, FLAGS, "passthrough" },
255 AVFILTER_DEFINE_CLASS(transpose_vt);
257 static const AVFilterPad transpose_vt_inputs[] = {
260 .type = AVMEDIA_TYPE_VIDEO,
261 .filter_frame = &transpose_vt_filter_frame,
265 static const AVFilterPad transpose_vt_outputs[] = {
268 .type = AVMEDIA_TYPE_VIDEO,
269 .config_props = &transpose_vt_config_output,
273 const AVFilter ff_vf_transpose_vt = {
274 .name = "transpose_vt",
275 .description = NULL_IF_CONFIG_SMALL("Transpose Videotoolbox frames"),
276 .priv_size = sizeof(TransposeVtContext),
277 .init = transpose_vt_init,
278 .uninit = transpose_vt_uninit,
279 FILTER_INPUTS(transpose_vt_inputs),
280 FILTER_OUTPUTS(transpose_vt_outputs),
281 FILTER_SINGLE_PIXFMT(AV_PIX_FMT_VIDEOTOOLBOX),
282 .priv_class = &transpose_vt_class,
283 .flags = AVFILTER_FLAG_HWDEVICE,
284 .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE,