1 /*
2 * Copyright (c) 2016 Paul B Mahol
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 "libavutil/avassert.h"
22 #include "libavutil/imgutils.h"
23 #include "libavutil/internal.h"
24 #include "libavutil/opt.h"
25 #include "libavutil/pixdesc.h"
26 #include "avfilter.h"
27 #include "internal.h"
28 #include "video.h"
29
30 enum HintModes {
31 ABSOLUTE_HINT,
32 RELATIVE_HINT,
33 PATTERN_HINT,
34 NB_HINTS,
35 };
36
37 typedef struct FieldHintContext {
38 const AVClass *class;
39
40 char *hint_file_str;
41 FILE *hint;
42 int mode;
43
44 AVFrame *frame[3];
45
46 int64_t line;
47 int nb_planes;
48 int eof;
49 int planewidth[4];
50 int planeheight[4];
51 } FieldHintContext;
52
53 #define OFFSET(x) offsetof(FieldHintContext, x)
54 #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
55
56 static const AVOption fieldhint_options[] = {
57 { "hint", "set hint file", OFFSET(hint_file_str), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS },
58 { "mode", "set hint mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=0}, 0, NB_HINTS-1, FLAGS, "mode" },
59 { "absolute", 0, 0, AV_OPT_TYPE_CONST, {.i64=ABSOLUTE_HINT}, 0, 0, FLAGS, "mode" },
60 { "relative", 0, 0, AV_OPT_TYPE_CONST, {.i64=RELATIVE_HINT}, 0, 0, FLAGS, "mode" },
61 { "pattern", 0, 0, AV_OPT_TYPE_CONST, {.i64=PATTERN_HINT}, 0, 0, FLAGS, "mode" },
62 { NULL }
63 };
64
65 AVFILTER_DEFINE_CLASS(fieldhint);
66
init(AVFilterContext * ctx)67 static av_cold int init(AVFilterContext *ctx)
68 {
69 FieldHintContext *s = ctx->priv;
70 int ret;
71
72 if (!s->hint_file_str) {
73 av_log(ctx, AV_LOG_ERROR, "Hint file must be set.\n");
74 return AVERROR(EINVAL);
75 }
76 s->hint = avpriv_fopen_utf8(s->hint_file_str, "r");
77 if (!s->hint) {
78 ret = AVERROR(errno);
79 av_log(ctx, AV_LOG_ERROR, "%s: %s\n", s->hint_file_str, av_err2str(ret));
80 return ret;
81 }
82
83 return 0;
84 }
85
query_formats(AVFilterContext * ctx)86 static int query_formats(AVFilterContext *ctx)
87 {
88 int reject_flags = AV_PIX_FMT_FLAG_BITSTREAM |
89 AV_PIX_FMT_FLAG_HWACCEL |
90 AV_PIX_FMT_FLAG_PAL;
91
92 return ff_set_common_formats(ctx, ff_formats_pixdesc_filter(0, reject_flags));
93 }
94
config_input(AVFilterLink * inlink)95 static int config_input(AVFilterLink *inlink)
96 {
97 FieldHintContext *s = inlink->dst->priv;
98 const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
99 int ret;
100
101 if ((ret = av_image_fill_linesizes(s->planewidth, inlink->format, inlink->w)) < 0)
102 return ret;
103
104 s->planeheight[1] = s->planeheight[2] = AV_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h);
105 s->planeheight[0] = s->planeheight[3] = inlink->h;
106
107 s->nb_planes = av_pix_fmt_count_planes(inlink->format);
108
109 return 0;
110 }
111
filter_frame(AVFilterLink * inlink,AVFrame * in)112 static int filter_frame(AVFilterLink *inlink, AVFrame *in)
113 {
114 AVFilterContext *ctx = inlink->dst;
115 AVFilterLink *outlink = ctx->outputs[0];
116 FieldHintContext *s = ctx->priv;
117 AVFrame *out, *top, *bottom;
118 char buf[1024] = { 0 };
119 int64_t tf, bf;
120 int tfactor = 0, bfactor = 1;
121 char hint = '=', field = '=';
122 int p;
123
124 av_frame_free(&s->frame[0]);
125 s->frame[0] = s->frame[1];
126 s->frame[1] = s->frame[2];
127 s->frame[2] = in;
128 if (!s->frame[1])
129 return 0;
130 else if (!s->frame[0]) {
131 s->frame[0] = av_frame_clone(s->frame[1]);
132 if (!s->frame[0])
133 return AVERROR(ENOMEM);
134 }
135
136 while (1) {
137 if (fgets(buf, sizeof(buf)-1, s->hint)) {
138 s->line++;
139 if (buf[0] == '#' || buf[0] == ';') {
140 continue;
141 } else if (sscanf(buf, "%"PRId64",%"PRId64" %c %c", &tf, &bf, &hint, &field) == 4) {
142 ;
143 } else if (sscanf(buf, "%"PRId64",%"PRId64" %c", &tf, &bf, &hint) == 3) {
144 ;
145 } else if (sscanf(buf, "%"PRId64",%"PRId64"", &tf, &bf) == 2) {
146 ;
147 } else {
148 av_log(ctx, AV_LOG_ERROR, "Invalid entry at line %"PRId64".\n", s->line);
149 return AVERROR_INVALIDDATA;
150 }
151 switch (s->mode) {
152 case ABSOLUTE_HINT:
153 if (tf > outlink->frame_count_in + 1 || tf < FFMAX(0, outlink->frame_count_in - 1) ||
154 bf > outlink->frame_count_in + 1 || bf < FFMAX(0, outlink->frame_count_in - 1)) {
155 av_log(ctx, AV_LOG_ERROR, "Out of range frames %"PRId64" and/or %"PRId64" on line %"PRId64" for %"PRId64". input frame.\n", tf, bf, s->line, inlink->frame_count_out);
156 return AVERROR_INVALIDDATA;
157 }
158 break;
159 case PATTERN_HINT:
160 case RELATIVE_HINT:
161 if (tf > 1 || tf < -1 ||
162 bf > 1 || bf < -1) {
163 av_log(ctx, AV_LOG_ERROR, "Out of range %"PRId64" and/or %"PRId64" on line %"PRId64" for %"PRId64". input frame.\n", tf, bf, s->line, inlink->frame_count_out);
164 return AVERROR_INVALIDDATA;
165 }
166 break;
167 default:
168 return AVERROR_BUG;
169 };
170 break;
171 } else {
172 if (s->mode == PATTERN_HINT) {
173 fseek(s->hint, 0, SEEK_SET);
174 continue;
175 }
176 av_log(ctx, AV_LOG_ERROR, "Missing entry for %"PRId64". input frame.\n", inlink->frame_count_out);
177 return AVERROR_INVALIDDATA;
178 }
179 }
180
181 out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
182 if (!out)
183 return AVERROR(ENOMEM);
184 av_frame_copy_props(out, s->frame[1]);
185
186 switch (s->mode) {
187 case ABSOLUTE_HINT:
188 top = s->frame[tf - outlink->frame_count_in + 1];
189 bottom = s->frame[bf - outlink->frame_count_in + 1];
190 break;
191 case PATTERN_HINT:
192 case RELATIVE_HINT:
193 top = s->frame[1 + tf];
194 bottom = s->frame[1 + bf];
195 break;
196 default:
197 av_assert0(0);
198 }
199
200 switch (field) {
201 case 'b':
202 tfactor = 1;
203 top = bottom;
204 break;
205 case 't':
206 bfactor = 0;
207 bottom = top;
208 break;
209 case '=':
210 break;
211 default:
212 av_log(ctx, AV_LOG_ERROR, "Invalid field: %c.\n", field);
213 av_frame_free(&out);
214 return AVERROR(EINVAL);
215 }
216
217 switch (hint) {
218 case '+':
219 out->interlaced_frame = 1;
220 break;
221 case '-':
222 out->interlaced_frame = 0;
223 break;
224 case '=':
225 break;
226 case 'b':
227 tfactor = 1;
228 top = bottom;
229 break;
230 case 't':
231 bfactor = 0;
232 bottom = top;
233 break;
234 default:
235 av_log(ctx, AV_LOG_ERROR, "Invalid hint: %c.\n", hint);
236 av_frame_free(&out);
237 return AVERROR(EINVAL);
238 }
239
240 for (p = 0; p < s->nb_planes; p++) {
241 av_image_copy_plane(out->data[p],
242 out->linesize[p] * 2,
243 top->data[p] + tfactor * top->linesize[p],
244 top->linesize[p] * 2,
245 s->planewidth[p],
246 (s->planeheight[p] + 1) / 2);
247 av_image_copy_plane(out->data[p] + out->linesize[p],
248 out->linesize[p] * 2,
249 bottom->data[p] + bfactor * bottom->linesize[p],
250 bottom->linesize[p] * 2,
251 s->planewidth[p],
252 (s->planeheight[p] + 1) / 2);
253 }
254
255 return ff_filter_frame(outlink, out);
256 }
257
request_frame(AVFilterLink * outlink)258 static int request_frame(AVFilterLink *outlink)
259 {
260 AVFilterContext *ctx = outlink->src;
261 FieldHintContext *s = ctx->priv;
262 int ret;
263
264 if (s->eof)
265 return AVERROR_EOF;
266
267 ret = ff_request_frame(ctx->inputs[0]);
268 if (ret == AVERROR_EOF && s->frame[2]) {
269 AVFrame *next = av_frame_clone(s->frame[2]);
270 if (!next)
271 return AVERROR(ENOMEM);
272 ret = filter_frame(ctx->inputs[0], next);
273 s->eof = 1;
274 }
275
276 return ret;
277 }
278
uninit(AVFilterContext * ctx)279 static av_cold void uninit(AVFilterContext *ctx)
280 {
281 FieldHintContext *s = ctx->priv;
282
283 if (s->hint)
284 fclose(s->hint);
285 s->hint = NULL;
286
287 av_frame_free(&s->frame[0]);
288 av_frame_free(&s->frame[1]);
289 av_frame_free(&s->frame[2]);
290 }
291
292 static const AVFilterPad inputs[] = {
293 {
294 .name = "default",
295 .type = AVMEDIA_TYPE_VIDEO,
296 .config_props = config_input,
297 .filter_frame = filter_frame,
298 },
299 };
300
301 static const AVFilterPad outputs[] = {
302 {
303 .name = "default",
304 .type = AVMEDIA_TYPE_VIDEO,
305 .request_frame = request_frame,
306 },
307 };
308
309 const AVFilter ff_vf_fieldhint = {
310 .name = "fieldhint",
311 .description = NULL_IF_CONFIG_SMALL("Field matching using hints."),
312 .priv_size = sizeof(FieldHintContext),
313 .priv_class = &fieldhint_class,
314 .init = init,
315 .uninit = uninit,
316 FILTER_INPUTS(inputs),
317 FILTER_OUTPUTS(outputs),
318 FILTER_QUERY_FUNC(query_formats),
319 };
320