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"
29 #include "transpose.h"
32 typedef struct TransposeVtContext
{
35 VTPixelRotationSessionRef session
;
40 static av_cold
int transpose_vt_init(AVFilterContext
*avctx
)
42 TransposeVtContext
*s
= avctx
->priv
;
45 ret
= VTPixelRotationSessionCreate(kCFAllocatorDefault
, &s
->session
);
47 av_log(avctx
, AV_LOG_ERROR
, "Rotation session create failed, %d\n", ret
);
48 return AVERROR_EXTERNAL
;
54 static av_cold
void transpose_vt_uninit(AVFilterContext
*avctx
)
56 TransposeVtContext
*s
= avctx
->priv
;
59 VTPixelRotationSessionInvalidate(s
->session
);
60 CFRelease(s
->session
);
65 static int transpose_vt_filter_frame(AVFilterLink
*link
, AVFrame
*in
)
68 AVFilterContext
*ctx
= link
->dst
;
69 TransposeVtContext
*s
= ctx
->priv
;
70 AVFilterLink
*outlink
= ctx
->outputs
[0];
76 return ff_filter_frame(outlink
, in
);
78 out
= ff_get_video_buffer(outlink
, outlink
->w
, outlink
->h
);
80 ret
= AVERROR(ENOMEM
);
84 ret
= av_frame_copy_props(out
, in
);
88 src
= (CVPixelBufferRef
)in
->data
[3];
89 dst
= (CVPixelBufferRef
)out
->data
[3];
90 ret
= VTPixelRotationSessionRotateImage(s
->session
, src
, dst
);
92 av_log(ctx
, AV_LOG_ERROR
, "transfer image failed, %d\n", ret
);
93 ret
= AVERROR_EXTERNAL
;
99 return ff_filter_frame(outlink
, out
);
107 static int transpose_vt_recreate_hw_ctx(AVFilterLink
*outlink
)
109 FilterLink
*outl
= ff_filter_link(outlink
);
110 AVFilterContext
*avctx
= outlink
->src
;
111 AVFilterLink
*inlink
= outlink
->src
->inputs
[0];
112 FilterLink
*inl
= ff_filter_link(inlink
);
113 AVHWFramesContext
*hw_frame_ctx_in
;
114 AVHWFramesContext
*hw_frame_ctx_out
;
117 av_buffer_unref(&outl
->hw_frames_ctx
);
119 hw_frame_ctx_in
= (AVHWFramesContext
*)inl
->hw_frames_ctx
->data
;
120 outl
->hw_frames_ctx
= av_hwframe_ctx_alloc(hw_frame_ctx_in
->device_ref
);
121 hw_frame_ctx_out
= (AVHWFramesContext
*)outl
->hw_frames_ctx
->data
;
122 hw_frame_ctx_out
->format
= AV_PIX_FMT_VIDEOTOOLBOX
;
123 hw_frame_ctx_out
->sw_format
= hw_frame_ctx_in
->sw_format
;
124 hw_frame_ctx_out
->width
= outlink
->w
;
125 hw_frame_ctx_out
->height
= outlink
->h
;
126 ((AVVTFramesContext
*)hw_frame_ctx_out
->hwctx
)->color_range
= ((AVVTFramesContext
*)hw_frame_ctx_in
->hwctx
)->color_range
;
128 err
= ff_filter_init_hw_frames(avctx
, outlink
, 1);
132 err
= av_hwframe_ctx_init(outl
->hw_frames_ctx
);
134 av_log(avctx
, AV_LOG_ERROR
,
135 "Failed to init videotoolbox frame context, %s\n",
143 static int transpose_vt_config_output(AVFilterLink
*outlink
)
146 FilterLink
*outl
= ff_filter_link(outlink
);
147 AVFilterContext
*avctx
= outlink
->src
;
148 TransposeVtContext
*s
= avctx
->priv
;
149 AVFilterLink
*inlink
= outlink
->src
->inputs
[0];
150 FilterLink
*inl
= ff_filter_link(inlink
);
151 CFStringRef rotation
= kVTRotation_0
;
152 CFBooleanRef vflip
= kCFBooleanFalse
;
153 CFBooleanRef hflip
= kCFBooleanFalse
;
156 av_buffer_unref(&outl
->hw_frames_ctx
);
157 outl
->hw_frames_ctx
= av_buffer_ref(inl
->hw_frames_ctx
);
159 if ((inlink
->w
>= inlink
->h
&& s
->passthrough
== TRANSPOSE_PT_TYPE_LANDSCAPE
) ||
160 (inlink
->w
<= inlink
->h
&& s
->passthrough
== TRANSPOSE_PT_TYPE_PORTRAIT
)) {
161 av_log(avctx
, AV_LOG_VERBOSE
,
162 "w:%d h:%d -> w:%d h:%d (passthrough mode)\n",
163 inlink
->w
, inlink
->h
, inlink
->w
, inlink
->h
);
167 s
->passthrough
= TRANSPOSE_PT_TYPE_NONE
;
170 case TRANSPOSE_CCLOCK_FLIP
:
171 rotation
= kVTRotation_CCW90
;
172 vflip
= kCFBooleanTrue
;
175 case TRANSPOSE_CCLOCK
:
176 rotation
= kVTRotation_CCW90
;
179 case TRANSPOSE_CLOCK
:
180 rotation
= kVTRotation_CW90
;
183 case TRANSPOSE_CLOCK_FLIP
:
184 rotation
= kVTRotation_CW90
;
185 vflip
= kCFBooleanTrue
;
188 case TRANSPOSE_REVERSAL
:
189 rotation
= kVTRotation_180
;
191 case TRANSPOSE_HFLIP
:
192 hflip
= kCFBooleanTrue
;
194 case TRANSPOSE_VFLIP
:
195 vflip
= kCFBooleanTrue
;
198 av_log(avctx
, AV_LOG_ERROR
, "Failed to set direction to %d\n", s
->dir
);
199 return AVERROR(EINVAL
);
202 err
= VTSessionSetProperty(s
->session
, kVTPixelRotationPropertyKey_Rotation
,
205 av_log(avctx
, AV_LOG_ERROR
, "Set rotation property failed, %d\n", err
);
206 return AVERROR_EXTERNAL
;
208 err
= VTSessionSetProperty(s
->session
, kVTPixelRotationPropertyKey_FlipVerticalOrientation
,
211 av_log(avctx
, AV_LOG_ERROR
, "Set vertical flip property failed, %d\n", err
);
212 return AVERROR_EXTERNAL
;
214 err
= VTSessionSetProperty(s
->session
, kVTPixelRotationPropertyKey_FlipHorizontalOrientation
,
217 av_log(avctx
, AV_LOG_ERROR
, "Set horizontal flip property failed, %d\n", err
);
218 return AVERROR_EXTERNAL
;
224 outlink
->w
= inlink
->h
;
225 outlink
->h
= inlink
->w
;
226 return transpose_vt_recreate_hw_ctx(outlink
);
229 #define OFFSET(x) offsetof(TransposeVtContext, x)
230 #define FLAGS (AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM)
231 static const AVOption transpose_vt_options
[] = {
232 { "dir", "set transpose direction",
233 OFFSET(dir
), AV_OPT_TYPE_INT
, { .i64
= TRANSPOSE_CCLOCK_FLIP
}, 0, 6, FLAGS
, .unit
= "dir" },
234 { "cclock_flip", "rotate counter-clockwise with vertical flip",
235 0, AV_OPT_TYPE_CONST
, { .i64
= TRANSPOSE_CCLOCK_FLIP
}, .flags
=FLAGS
, .unit
= "dir" },
236 { "clock", "rotate clockwise",
237 0, AV_OPT_TYPE_CONST
, { .i64
= TRANSPOSE_CLOCK
}, .flags
=FLAGS
, .unit
= "dir" },
238 { "cclock", "rotate counter-clockwise",
239 0, AV_OPT_TYPE_CONST
, { .i64
= TRANSPOSE_CCLOCK
}, .flags
=FLAGS
, .unit
= "dir" },
240 { "clock_flip", "rotate clockwise with vertical flip",
241 0, AV_OPT_TYPE_CONST
, { .i64
= TRANSPOSE_CLOCK_FLIP
}, .flags
=FLAGS
, .unit
= "dir" },
242 { "reversal", "rotate by half-turn",
243 0, AV_OPT_TYPE_CONST
, { .i64
= TRANSPOSE_REVERSAL
}, .flags
=FLAGS
, .unit
= "dir" },
244 { "hflip", "flip horizontally",
245 0, AV_OPT_TYPE_CONST
, { .i64
= TRANSPOSE_HFLIP
}, .flags
=FLAGS
, .unit
= "dir" },
246 { "vflip", "flip vertically",
247 0, AV_OPT_TYPE_CONST
, { .i64
= TRANSPOSE_VFLIP
}, .flags
=FLAGS
, .unit
= "dir" },
249 { "passthrough", "do not apply transposition if the input matches the specified geometry",
250 OFFSET(passthrough
), AV_OPT_TYPE_INT
, { .i64
=TRANSPOSE_PT_TYPE_NONE
}, 0, INT_MAX
, FLAGS
, .unit
= "passthrough" },
251 { "none", "always apply transposition",
252 0, AV_OPT_TYPE_CONST
, { .i64
= TRANSPOSE_PT_TYPE_NONE
}, INT_MIN
, INT_MAX
, FLAGS
, .unit
= "passthrough" },
253 { "portrait", "preserve portrait geometry",
254 0, AV_OPT_TYPE_CONST
, { .i64
= TRANSPOSE_PT_TYPE_PORTRAIT
}, INT_MIN
, INT_MAX
, FLAGS
, .unit
= "passthrough" },
255 { "landscape", "preserve landscape geometry",
256 0, AV_OPT_TYPE_CONST
, { .i64
= TRANSPOSE_PT_TYPE_LANDSCAPE
}, INT_MIN
, INT_MAX
, FLAGS
, .unit
= "passthrough" },
261 AVFILTER_DEFINE_CLASS(transpose_vt
);
263 static const AVFilterPad transpose_vt_inputs
[] = {
266 .type
= AVMEDIA_TYPE_VIDEO
,
267 .filter_frame
= &transpose_vt_filter_frame
,
271 static const AVFilterPad transpose_vt_outputs
[] = {
274 .type
= AVMEDIA_TYPE_VIDEO
,
275 .config_props
= &transpose_vt_config_output
,
279 const FFFilter ff_vf_transpose_vt
= {
280 .p
.name
= "transpose_vt",
281 .p
.description
= NULL_IF_CONFIG_SMALL("Transpose Videotoolbox frames"),
282 .p
.priv_class
= &transpose_vt_class
,
283 .p
.flags
= AVFILTER_FLAG_HWDEVICE
,
284 .priv_size
= sizeof(TransposeVtContext
),
285 .init
= transpose_vt_init
,
286 .uninit
= transpose_vt_uninit
,
287 FILTER_INPUTS(transpose_vt_inputs
),
288 FILTER_OUTPUTS(transpose_vt_outputs
),
289 FILTER_SINGLE_PIXFMT(AV_PIX_FMT_VIDEOTOOLBOX
),
290 .flags_internal
= FF_FILTER_FLAG_HWFRAME_AWARE
,