blob: 654477c6f2395d865251508192468d92d5976353
1 | /* |
2 | * This file is part of FFmpeg. |
3 | * |
4 | * FFmpeg is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU Lesser General Public |
6 | * License as published by the Free Software Foundation; either |
7 | * version 2.1 of the License, or (at your option) any later version. |
8 | * |
9 | * FFmpeg is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | * Lesser General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU Lesser General Public |
15 | * License along with FFmpeg; if not, write to the Free Software |
16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
17 | */ |
18 | |
19 | #include "libavutil/buffer.h" |
20 | #include "libavutil/hwcontext.h" |
21 | #include "libavutil/log.h" |
22 | #include "libavutil/opt.h" |
23 | #include "libavutil/pixdesc.h" |
24 | |
25 | #include "avfilter.h" |
26 | #include "formats.h" |
27 | #include "internal.h" |
28 | #include "video.h" |
29 | |
30 | typedef struct HWMapContext { |
31 | const AVClass *class; |
32 | |
33 | AVBufferRef *hwdevice_ref; |
34 | AVBufferRef *hwframes_ref; |
35 | |
36 | int mode; |
37 | int map_backwards; |
38 | } HWMapContext; |
39 | |
40 | static int hwmap_query_formats(AVFilterContext *avctx) |
41 | { |
42 | int ret; |
43 | |
44 | if ((ret = ff_formats_ref(ff_all_formats(AVMEDIA_TYPE_VIDEO), |
45 | &avctx->inputs[0]->out_formats)) < 0 || |
46 | (ret = ff_formats_ref(ff_all_formats(AVMEDIA_TYPE_VIDEO), |
47 | &avctx->outputs[0]->in_formats)) < 0) |
48 | return ret; |
49 | |
50 | return 0; |
51 | } |
52 | |
53 | static int hwmap_config_output(AVFilterLink *outlink) |
54 | { |
55 | AVFilterContext *avctx = outlink->src; |
56 | HWMapContext *ctx = avctx->priv; |
57 | AVFilterLink *inlink = avctx->inputs[0]; |
58 | AVHWFramesContext *hwfc; |
59 | const AVPixFmtDescriptor *desc; |
60 | int err; |
61 | |
62 | av_log(avctx, AV_LOG_DEBUG, "Configure hwmap %s -> %s.\n", |
63 | av_get_pix_fmt_name(inlink->format), |
64 | av_get_pix_fmt_name(outlink->format)); |
65 | |
66 | if (inlink->hw_frames_ctx) { |
67 | hwfc = (AVHWFramesContext*)inlink->hw_frames_ctx->data; |
68 | |
69 | desc = av_pix_fmt_desc_get(outlink->format); |
70 | if (!desc) |
71 | return AVERROR(EINVAL); |
72 | |
73 | if (inlink->format == hwfc->format && |
74 | (desc->flags & AV_PIX_FMT_FLAG_HWACCEL)) { |
75 | // Map between two hardware formats (including the case of |
76 | // undoing an existing mapping). |
77 | |
78 | ctx->hwdevice_ref = av_buffer_ref(avctx->hw_device_ctx); |
79 | if (!ctx->hwdevice_ref) { |
80 | err = AVERROR(ENOMEM); |
81 | goto fail; |
82 | } |
83 | |
84 | err = av_hwframe_ctx_create_derived(&ctx->hwframes_ref, |
85 | outlink->format, |
86 | ctx->hwdevice_ref, |
87 | inlink->hw_frames_ctx, 0); |
88 | if (err < 0) |
89 | goto fail; |
90 | |
91 | } else if ((outlink->format == hwfc->format && |
92 | inlink->format == hwfc->sw_format) || |
93 | inlink->format == hwfc->format) { |
94 | // Map from a hardware format to a software format, or |
95 | // undo an existing such mapping. |
96 | |
97 | ctx->hwdevice_ref = NULL; |
98 | |
99 | ctx->hwframes_ref = av_buffer_ref(inlink->hw_frames_ctx); |
100 | if (!ctx->hwframes_ref) { |
101 | err = AVERROR(ENOMEM); |
102 | goto fail; |
103 | } |
104 | |
105 | } else { |
106 | // Non-matching formats - not supported. |
107 | |
108 | av_log(avctx, AV_LOG_ERROR, "Unsupported formats for " |
109 | "hwmap: from %s (%s) to %s.\n", |
110 | av_get_pix_fmt_name(inlink->format), |
111 | av_get_pix_fmt_name(hwfc->format), |
112 | av_get_pix_fmt_name(outlink->format)); |
113 | err = AVERROR(EINVAL); |
114 | goto fail; |
115 | } |
116 | } else if (avctx->hw_device_ctx) { |
117 | // Map from a software format to a hardware format. This |
118 | // creates a new hwframe context like hwupload, but then |
119 | // returns frames mapped from that to the previous link in |
120 | // order to fill them without an additional copy. |
121 | |
122 | ctx->map_backwards = 1; |
123 | |
124 | ctx->hwdevice_ref = av_buffer_ref(avctx->hw_device_ctx); |
125 | if (!ctx->hwdevice_ref) { |
126 | err = AVERROR(ENOMEM); |
127 | goto fail; |
128 | } |
129 | |
130 | ctx->hwframes_ref = av_hwframe_ctx_alloc(ctx->hwdevice_ref); |
131 | if (!ctx->hwframes_ref) { |
132 | err = AVERROR(ENOMEM); |
133 | goto fail; |
134 | } |
135 | hwfc = (AVHWFramesContext*)ctx->hwframes_ref->data; |
136 | |
137 | hwfc->format = outlink->format; |
138 | hwfc->sw_format = inlink->format; |
139 | hwfc->width = inlink->w; |
140 | hwfc->height = inlink->h; |
141 | |
142 | err = av_hwframe_ctx_init(ctx->hwframes_ref); |
143 | if (err < 0) { |
144 | av_log(avctx, AV_LOG_ERROR, "Failed to create frame " |
145 | "context for backward mapping: %d.\n", err); |
146 | goto fail; |
147 | } |
148 | |
149 | } else { |
150 | av_log(avctx, AV_LOG_ERROR, "Mapping requires a hardware " |
151 | "context (a device, or frames on input).\n"); |
152 | return AVERROR(EINVAL); |
153 | } |
154 | |
155 | outlink->hw_frames_ctx = av_buffer_ref(ctx->hwframes_ref); |
156 | if (!outlink->hw_frames_ctx) { |
157 | err = AVERROR(ENOMEM); |
158 | goto fail; |
159 | } |
160 | |
161 | outlink->w = inlink->w; |
162 | outlink->h = inlink->h; |
163 | |
164 | return 0; |
165 | |
166 | fail: |
167 | av_buffer_unref(&ctx->hwframes_ref); |
168 | av_buffer_unref(&ctx->hwdevice_ref); |
169 | return err; |
170 | } |
171 | |
172 | static AVFrame *hwmap_get_buffer(AVFilterLink *inlink, int w, int h) |
173 | { |
174 | AVFilterContext *avctx = inlink->dst; |
175 | AVFilterLink *outlink = avctx->outputs[0]; |
176 | HWMapContext *ctx = avctx->priv; |
177 | |
178 | if (ctx->map_backwards) { |
179 | AVFrame *src, *dst; |
180 | int err; |
181 | |
182 | src = ff_get_video_buffer(outlink, w, h); |
183 | if (!src) { |
184 | av_log(avctx, AV_LOG_ERROR, "Failed to allocate source " |
185 | "frame for software mapping.\n"); |
186 | return NULL; |
187 | } |
188 | |
189 | dst = av_frame_alloc(); |
190 | if (!dst) { |
191 | av_frame_free(&src); |
192 | return NULL; |
193 | } |
194 | |
195 | err = av_hwframe_map(dst, src, ctx->mode); |
196 | if (err) { |
197 | av_log(avctx, AV_LOG_ERROR, "Failed to map frame to " |
198 | "software: %d.\n", err); |
199 | av_frame_free(&src); |
200 | av_frame_free(&dst); |
201 | return NULL; |
202 | } |
203 | |
204 | av_frame_free(&src); |
205 | return dst; |
206 | } else { |
207 | return ff_default_get_video_buffer(inlink, w, h); |
208 | } |
209 | } |
210 | |
211 | static int hwmap_filter_frame(AVFilterLink *link, AVFrame *input) |
212 | { |
213 | AVFilterContext *avctx = link->dst; |
214 | AVFilterLink *outlink = avctx->outputs[0]; |
215 | HWMapContext *ctx = avctx->priv; |
216 | AVFrame *map = NULL; |
217 | int err; |
218 | |
219 | av_log(ctx, AV_LOG_DEBUG, "Filter input: %s, %ux%u (%"PRId64").\n", |
220 | av_get_pix_fmt_name(input->format), |
221 | input->width, input->height, input->pts); |
222 | |
223 | map = av_frame_alloc(); |
224 | if (!map) { |
225 | err = AVERROR(ENOMEM); |
226 | goto fail; |
227 | } |
228 | |
229 | map->format = outlink->format; |
230 | map->hw_frames_ctx = av_buffer_ref(ctx->hwframes_ref); |
231 | if (!map->hw_frames_ctx) { |
232 | err = AVERROR(ENOMEM); |
233 | goto fail; |
234 | } |
235 | |
236 | if (ctx->map_backwards && !input->hw_frames_ctx) { |
237 | // If we mapped backwards from hardware to software, we need |
238 | // to attach the hardware frame context to the input frame to |
239 | // make the mapping visible to av_hwframe_map(). |
240 | input->hw_frames_ctx = av_buffer_ref(ctx->hwframes_ref); |
241 | if (!input->hw_frames_ctx) { |
242 | err = AVERROR(ENOMEM); |
243 | goto fail; |
244 | } |
245 | } |
246 | |
247 | err = av_hwframe_map(map, input, ctx->mode); |
248 | if (err < 0) { |
249 | av_log(avctx, AV_LOG_ERROR, "Failed to map frame: %d.\n", err); |
250 | goto fail; |
251 | } |
252 | |
253 | err = av_frame_copy_props(map, input); |
254 | if (err < 0) |
255 | goto fail; |
256 | |
257 | av_frame_free(&input); |
258 | |
259 | av_log(ctx, AV_LOG_DEBUG, "Filter output: %s, %ux%u (%"PRId64").\n", |
260 | av_get_pix_fmt_name(map->format), |
261 | map->width, map->height, map->pts); |
262 | |
263 | return ff_filter_frame(outlink, map); |
264 | |
265 | fail: |
266 | av_frame_free(&input); |
267 | av_frame_free(&map); |
268 | return err; |
269 | } |
270 | |
271 | static av_cold void hwmap_uninit(AVFilterContext *avctx) |
272 | { |
273 | HWMapContext *ctx = avctx->priv; |
274 | |
275 | av_buffer_unref(&ctx->hwframes_ref); |
276 | av_buffer_unref(&ctx->hwdevice_ref); |
277 | } |
278 | |
279 | #define OFFSET(x) offsetof(HWMapContext, x) |
280 | #define FLAGS (AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM) |
281 | static const AVOption hwmap_options[] = { |
282 | { "mode", "Frame mapping mode", |
283 | OFFSET(mode), AV_OPT_TYPE_FLAGS, |
284 | { .i64 = AV_HWFRAME_MAP_READ | AV_HWFRAME_MAP_WRITE }, |
285 | 0, INT_MAX, FLAGS, "mode" }, |
286 | |
287 | { "read", "Mapping should be readable", |
288 | 0, AV_OPT_TYPE_CONST, { .i64 = AV_HWFRAME_MAP_READ }, |
289 | INT_MIN, INT_MAX, FLAGS, "mode" }, |
290 | { "write", "Mapping should be writeable", |
291 | 0, AV_OPT_TYPE_CONST, { .i64 = AV_HWFRAME_MAP_WRITE }, |
292 | INT_MIN, INT_MAX, FLAGS, "mode" }, |
293 | { "overwrite", "Mapping will always overwrite the entire frame", |
294 | 0, AV_OPT_TYPE_CONST, { .i64 = AV_HWFRAME_MAP_OVERWRITE }, |
295 | INT_MIN, INT_MAX, FLAGS, "mode" }, |
296 | { "direct", "Mapping should not involve any copying", |
297 | 0, AV_OPT_TYPE_CONST, { .i64 = AV_HWFRAME_MAP_DIRECT }, |
298 | INT_MIN, INT_MAX, FLAGS, "mode" }, |
299 | |
300 | { NULL } |
301 | }; |
302 | |
303 | AVFILTER_DEFINE_CLASS(hwmap); |
304 | |
305 | static const AVFilterPad hwmap_inputs[] = { |
306 | { |
307 | .name = "default", |
308 | .type = AVMEDIA_TYPE_VIDEO, |
309 | .get_video_buffer = hwmap_get_buffer, |
310 | .filter_frame = hwmap_filter_frame, |
311 | }, |
312 | { NULL } |
313 | }; |
314 | |
315 | static const AVFilterPad hwmap_outputs[] = { |
316 | { |
317 | .name = "default", |
318 | .type = AVMEDIA_TYPE_VIDEO, |
319 | .config_props = hwmap_config_output, |
320 | }, |
321 | { NULL } |
322 | }; |
323 | |
324 | AVFilter ff_vf_hwmap = { |
325 | .name = "hwmap", |
326 | .description = NULL_IF_CONFIG_SMALL("Map hardware frames"), |
327 | .uninit = hwmap_uninit, |
328 | .priv_size = sizeof(HWMapContext), |
329 | .priv_class = &hwmap_class, |
330 | .query_formats = hwmap_query_formats, |
331 | .inputs = hwmap_inputs, |
332 | .outputs = hwmap_outputs, |
333 | .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE, |
334 | }; |
335 |