Imported Upstream version 6.1
[platform/upstream/ffmpeg.git] / libavfilter / vf_transpose_vt.c
1 /*
2  * Copyright (c) 2023 Zhao Zhili <zhilizhao@tencent.com>
3  *
4  * This file is part of FFmpeg.
5  *
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.
10  *
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.
15  *
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
19  */
20
21 #include <VideoToolbox/VideoToolbox.h>
22
23 #include "libavutil/hwcontext.h"
24 #include "libavutil/hwcontext_videotoolbox.h"
25 #include "libavutil/opt.h"
26 #include "libavutil/pixdesc.h"
27 #include "internal.h"
28 #include "transpose.h"
29 #include "video.h"
30
31 typedef struct TransposeVtContext {
32     AVClass *class;
33
34     VTPixelRotationSessionRef session;
35     int dir;
36     int passthrough;
37 } TransposeVtContext;
38
39 static av_cold int transpose_vt_init(AVFilterContext *avctx)
40 {
41     TransposeVtContext *s = avctx->priv;
42     int ret;
43
44     ret = VTPixelRotationSessionCreate(kCFAllocatorDefault, &s->session);
45     if (ret != noErr) {
46         av_log(avctx, AV_LOG_ERROR, "Rotation session create failed, %d\n", ret);
47         return AVERROR_EXTERNAL;
48     }
49
50     return 0;
51 }
52
53 static av_cold void transpose_vt_uninit(AVFilterContext *avctx)
54 {
55     TransposeVtContext *s = avctx->priv;
56
57     if (s->session) {
58         VTPixelRotationSessionInvalidate(s->session);
59         CFRelease(s->session);
60         s->session = NULL;
61     }
62 }
63
64 static int transpose_vt_filter_frame(AVFilterLink *link, AVFrame *in)
65 {
66     int ret;
67     AVFilterContext *ctx = link->dst;
68     TransposeVtContext *s = ctx->priv;
69     AVFilterLink *outlink = ctx->outputs[0];
70     CVPixelBufferRef src;
71     CVPixelBufferRef dst;
72     AVFrame *out;
73
74     if (s->passthrough)
75         return ff_filter_frame(outlink, in);
76
77     out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
78     if (!out) {
79         ret = AVERROR(ENOMEM);
80         goto fail;
81     }
82
83     ret = av_frame_copy_props(out, in);
84     if (ret < 0)
85         goto fail;
86
87     src = (CVPixelBufferRef)in->data[3];
88     dst = (CVPixelBufferRef)out->data[3];
89     ret = VTPixelRotationSessionRotateImage(s->session, src, dst);
90     if (ret != noErr) {
91         av_log(ctx, AV_LOG_ERROR, "transfer image failed, %d\n", ret);
92         ret = AVERROR_EXTERNAL;
93         goto fail;
94     }
95
96     av_frame_free(&in);
97
98     return ff_filter_frame(outlink, out);
99
100 fail:
101     av_frame_free(&in);
102     av_frame_free(&out);
103     return ret;
104 }
105
106 static int transpose_vt_recreate_hw_ctx(AVFilterLink *outlink)
107 {
108     AVFilterContext *avctx = outlink->src;
109     AVFilterLink *inlink = outlink->src->inputs[0];
110     AVHWFramesContext *hw_frame_ctx_in;
111     AVHWFramesContext *hw_frame_ctx_out;
112     int err;
113
114     av_buffer_unref(&outlink->hw_frames_ctx);
115
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;
123
124     err = ff_filter_init_hw_frames(avctx, outlink, 1);
125     if (err < 0)
126         return err;
127
128     err = av_hwframe_ctx_init(outlink->hw_frames_ctx);
129     if (err < 0) {
130         av_log(avctx, AV_LOG_ERROR,
131                "Failed to init videotoolbox frame context, %s\n",
132                av_err2str(err));
133         return err;
134     }
135
136     return 0;
137 }
138
139 static int transpose_vt_config_output(AVFilterLink *outlink)
140 {
141     int err;
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;
148     int swap_w_h = 0;
149
150     av_buffer_unref(&outlink->hw_frames_ctx);
151     outlink->hw_frames_ctx = av_buffer_ref(inlink->hw_frames_ctx);
152
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);
158         return 0;
159     }
160
161     s->passthrough = TRANSPOSE_PT_TYPE_NONE;
162
163     switch (s->dir) {
164     case TRANSPOSE_CCLOCK_FLIP:
165         rotation = kVTRotation_CCW90;
166         vflip = kCFBooleanTrue;
167         swap_w_h = 1;
168         break;
169     case TRANSPOSE_CCLOCK:
170         rotation = kVTRotation_CCW90;
171         swap_w_h = 1;
172         break;
173     case TRANSPOSE_CLOCK:
174         rotation = kVTRotation_CW90;
175         swap_w_h = 1;
176         break;
177     case TRANSPOSE_CLOCK_FLIP:
178         rotation = kVTRotation_CW90;
179         vflip = kCFBooleanTrue;
180         swap_w_h = 1;
181         break;
182     case TRANSPOSE_REVERSAL:
183         rotation = kVTRotation_180;
184         break;
185     case TRANSPOSE_HFLIP:
186         hflip = kCFBooleanTrue;
187         break;
188     case TRANSPOSE_VFLIP:
189         vflip = kCFBooleanTrue;
190         break;
191     default:
192         av_log(avctx, AV_LOG_ERROR, "Failed to set direction to %d\n", s->dir);
193         return AVERROR(EINVAL);
194     }
195
196     err = VTSessionSetProperty(s->session, kVTPixelRotationPropertyKey_Rotation,
197                                rotation);
198     if (err != noErr) {
199         av_log(avctx, AV_LOG_ERROR, "Set rotation property failed, %d\n", err);
200         return AVERROR_EXTERNAL;
201     }
202     err = VTSessionSetProperty(s->session, kVTPixelRotationPropertyKey_FlipVerticalOrientation,
203                                vflip);
204     if (err != noErr) {
205         av_log(avctx, AV_LOG_ERROR, "Set vertical flip property failed, %d\n", err);
206         return AVERROR_EXTERNAL;
207     }
208     err = VTSessionSetProperty(s->session, kVTPixelRotationPropertyKey_FlipHorizontalOrientation,
209                                hflip);
210     if (err != noErr) {
211         av_log(avctx, AV_LOG_ERROR, "Set horizontal flip property failed, %d\n", err);
212         return AVERROR_EXTERNAL;
213     }
214
215     if (!swap_w_h)
216         return 0;
217
218     outlink->w = inlink->h;
219     outlink->h = inlink->w;
220     return transpose_vt_recreate_hw_ctx(outlink);
221 }
222
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" },
242
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" },
251
252     { NULL }
253 };
254
255 AVFILTER_DEFINE_CLASS(transpose_vt);
256
257 static const AVFilterPad transpose_vt_inputs[] = {
258     {
259         .name         = "default",
260         .type         = AVMEDIA_TYPE_VIDEO,
261         .filter_frame = &transpose_vt_filter_frame,
262     },
263 };
264
265 static const AVFilterPad transpose_vt_outputs[] = {
266     {
267         .name = "default",
268         .type = AVMEDIA_TYPE_VIDEO,
269         .config_props = &transpose_vt_config_output,
270     },
271 };
272
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,
285 };