1 /*
2 * Copyright (c) 2012 Fredrik Mellbin
3 * Copyright (c) 2013 Clément Bœsch
4 *
5 * This file is part of FFmpeg.
6 *
7 * FFmpeg is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * FFmpeg is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with FFmpeg; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22 /**
23 * @file
24 * Fieldmatching filter, ported from VFM filter (VapourSynth) by Clément.
25 * Fredrik Mellbin is the author of the VIVTC/VFM filter, which is itself a
26 * light clone of the TIVTC/TFM (AviSynth) filter written by Kevin Stone
27 * (tritical), the original author.
28 *
29 * @see http://bengal.missouri.edu/~kes25c/
30 * @see http://www.vapoursynth.com/about/
31 */
32
33 #include <inttypes.h>
34
44
46 #define INPUT_CLEANSRC 1
47
52 };
53
62 };
63
69 };
70
76 };
77
80
83 int got_frame[2];
///< frame request flag for each input stream
84 int hsub[2],
vsub[2];
///< chroma subsampling values
85 int bpc;
///< bytes per component
86 uint32_t
eof;
///< bitmask for end of stream
89
90 /* options */
93 int mode;
///< matching_mode
105
106 /* misc buffers */
115
116 #define OFFSET(x) offsetof(FieldMatchContext, x)
117 #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
118
127 {
"pc_u",
"2-way match + 3rd match (same order) on combed (p/c + u)", 0,
AV_OPT_TYPE_CONST, {.i64=
MODE_PC_U}, INT_MIN, INT_MAX,
FLAGS, .unit =
"mode" },
128 {
"pc_n_ub",
"2-way match + 3rd match on combed + 4th/5th matches if still combed (p/c + u + u/b)", 0,
AV_OPT_TYPE_CONST, {.i64=
MODE_PC_N_UB}, INT_MIN, INT_MAX,
FLAGS, .unit =
"mode" },
131 {
"ppsrc",
"mark main input as a pre-processed input and activate clean source input stream",
OFFSET(ppsrc),
AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1,
FLAGS },
136 {
"mchroma",
"set whether or not chroma is included during the match comparisons",
OFFSET(mchroma),
AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1,
FLAGS },
137 {
"y0",
"define an exclusion band which excludes the lines between y0 and y1 from the field matching decision",
OFFSET(y0),
AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX,
FLAGS },
138 {
"y1",
"define an exclusion band which excludes the lines between y0 and y1 from the field matching decision",
OFFSET(y1),
AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX,
FLAGS },
148 {
"cthresh",
"set the area combing threshold used for combed frame detection",
OFFSET(cthresh),
AV_OPT_TYPE_INT, {.i64= 9}, -1, 0xff,
FLAGS },
150 {
"blockx",
"set the x-axis size of the window used during combed frame detection",
OFFSET(blockx),
AV_OPT_TYPE_INT, {.i64=16}, 4, 1<<9,
FLAGS },
151 {
"blocky",
"set the y-axis size of the window used during combed frame detection",
OFFSET(blocky),
AV_OPT_TYPE_INT, {.i64=16}, 4, 1<<9,
FLAGS },
152 {
"combpel",
"set the number of combed pixels inside any of the blocky by blockx size blocks on the frame for the frame to be detected as combed",
OFFSET(combpel),
AV_OPT_TYPE_INT, {.i64=80}, 0, INT_MAX,
FLAGS },
154 };
155
157
159 {
161 }
162
164 {
166 }
167
169 {
170 int x, y;
171 const uint8_t *srcp1 = f1->
data[0];
172 const uint8_t *srcp2 = f2->
data[0];
173 const int src1_linesize = f1->
linesize[0];
174 const int src2_linesize = f2->
linesize[0];
178
179 for (y = 0; y <
height; y++) {
180 for (x = 0; x <
width; x++)
181 acc +=
abs(srcp1[x] - srcp2[x]);
182 srcp1 += src1_linesize;
183 srcp2 += src2_linesize;
184 }
185 return acc;
186 }
187
189 {
190 int y;
191
192 for (y = 0; y <
h; y++) {
195 }
196 }
197
199 {
200 int x, y, plane, max_v = 0;
201 const int cthresh = fm->
cthresh;
202 const int cthresh6 = cthresh * 6;
203
204 for (plane = 0; plane < (fm->
chroma ? 3 : 1); plane++) {
205 const uint8_t *srcp =
src->data[plane];
206 const int src_linesize =
src->linesize[plane];
211
212 if (cthresh < 0) {
214 continue;
215 }
217
218 /* [1 -3 4 -3 1] vertical filter */
219 #define FILTER(xm2, xm1, xp1, xp2) \
220 abs( 4 * srcp[x] \
221 -3 * (srcp[x + (xm1)*src_linesize] + srcp[x + (xp1)*src_linesize]) \
222 + (srcp[x + (xm2)*src_linesize] + srcp[x + (xp2)*src_linesize])) > cthresh6
223
224 /* first line */
225 for (x = 0; x <
width; x++) {
226 const int s1 =
abs(srcp[x] - srcp[x + src_linesize]);
227 if (s1 > cthresh &&
FILTER(2, 1, 1, 2))
228 cmkp[x] = 0xff;
229 }
230 srcp += src_linesize;
231 cmkp += cmk_linesize;
232
233 /* second line */
234 for (x = 0; x <
width; x++) {
235 const int s1 =
abs(srcp[x] - srcp[x - src_linesize]);
236 const int s2 =
abs(srcp[x] - srcp[x + src_linesize]);
237 if (s1 > cthresh && s2 > cthresh &&
FILTER(2, -1, 1, 2))
238 cmkp[x] = 0xff;
239 }
240 srcp += src_linesize;
241 cmkp += cmk_linesize;
242
243 /* all lines minus first two and last two */
244 for (y = 2; y <
height-2; y++) {
245 for (x = 0; x <
width; x++) {
246 const int s1 =
abs(srcp[x] - srcp[x - src_linesize]);
247 const int s2 =
abs(srcp[x] - srcp[x + src_linesize]);
248 if (s1 > cthresh && s2 > cthresh &&
FILTER(-2, -1, 1, 2))
249 cmkp[x] = 0xff;
250 }
251 srcp += src_linesize;
252 cmkp += cmk_linesize;
253 }
254
255 /* before-last line */
256 for (x = 0; x <
width; x++) {
257 const int s1 =
abs(srcp[x] - srcp[x - src_linesize]);
258 const int s2 =
abs(srcp[x] - srcp[x + src_linesize]);
259 if (s1 > cthresh && s2 > cthresh &&
FILTER(-2, -1, 1, -2))
260 cmkp[x] = 0xff;
261 }
262 srcp += src_linesize;
263 cmkp += cmk_linesize;
264
265 /* last line */
266 for (x = 0; x <
width; x++) {
267 const int s1 =
abs(srcp[x] - srcp[x - src_linesize]);
268 if (s1 > cthresh &&
FILTER(-2, -1, -1, -2))
269 cmkp[x] = 0xff;
270 }
271 }
272
281 uint8_t *cmkpp = cmkp - (cmk_linesize>>1);
282 uint8_t *cmkpn = cmkp + (cmk_linesize>>1);
283 uint8_t *cmkpnn = cmkp + cmk_linesize;
284 for (y = 1; y <
height - 1; y++) {
285 cmkpp += cmk_linesize;
286 cmkp += cmk_linesize;
287 cmkpn += cmk_linesize;
288 cmkpnn += cmk_linesize;
289 cmkpV += cmk_linesizeUV;
290 cmkpU += cmk_linesizeUV;
291 for (x = 1; x <
width - 1; x++) {
292 #define HAS_FF_AROUND(p, lz) (p[(x)-1 - (lz)] == 0xff || p[(x) - (lz)] == 0xff || p[(x)+1 - (lz)] == 0xff || \
293 p[(x)-1 ] == 0xff || p[(x)+1 ] == 0xff || \
294 p[(x)-1 + (lz)] == 0xff || p[(x) + (lz)] == 0xff || p[(x)+1 + (lz)] == 0xff)
295 if ((cmkpV[x] == 0xff &&
HAS_FF_AROUND(cmkpV, cmk_linesizeUV)) ||
296 (cmkpU[x] == 0xff &&
HAS_FF_AROUND(cmkpU, cmk_linesizeUV))) {
297 ((uint16_t*)cmkp)[x] = 0xffff;
298 ((uint16_t*)cmkpn)[x] = 0xffff;
299 if (y&1) ((uint16_t*)cmkpp)[x] = 0xffff;
300 else ((uint16_t*)cmkpnn)[x] = 0xffff;
301 }
302 }
303 }
304 }
305
306 {
307 const int blockx = fm->
blockx;
308 const int blocky = fm->
blocky;
309 const int xhalf = blockx/2;
310 const int yhalf = blocky/2;
312 const uint8_t *cmkp = fm->
cmask_data[0] + cmk_linesize;
315 const int xblocks = ((
width+xhalf)/blockx) + 1;
316 const int xblocks4 = xblocks<<2;
317 const int yblocks = ((
height+yhalf)/blocky) + 1;
319 const int arraysize = (xblocks*yblocks)<<2;
320 int heighta = (
height/(blocky/2))*(blocky/2);
321 const int widtha = (
width /(blockx/2))*(blockx/2);
324 memset(c_array, 0, arraysize * sizeof(*c_array));
325
326 #define C_ARRAY_ADD(v) do { \
327 const int box1 = (x / blockx) * 4; \
328 const int box2 = ((x + xhalf) / blockx) * 4; \
329 c_array[temp1 + box1 ] += v; \
330 c_array[temp1 + box2 + 1] += v; \
331 c_array[temp2 + box1 + 2] += v; \
332 c_array[temp2 + box2 + 3] += v; \
333 } while (0)
334
335 #define VERTICAL_HALF(y_start, y_end) do { \
336 for (y = y_start; y < y_end; y++) { \
337 const int temp1 = (y / blocky) * xblocks4; \
338 const int temp2 = ((y + yhalf) / blocky) * xblocks4; \
339 for (x = 0; x < width; x++) \
340 if (cmkp[x - cmk_linesize] == 0xff && \
341 cmkp[x ] == 0xff && \
342 cmkp[x + cmk_linesize] == 0xff) \
343 C_ARRAY_ADD(1); \
344 cmkp += cmk_linesize; \
345 } \
346 } while (0)
347
349
350 for (y = yhalf; y < heighta; y += yhalf) {
351 const int temp1 = (y / blocky) * xblocks4;
352 const int temp2 = ((y + yhalf) / blocky) * xblocks4;
353
354 for (x = 0; x < widtha; x += xhalf) {
355 const uint8_t *cmkp_tmp = cmkp + x;
357 for (
u = 0;
u < yhalf;
u++) {
358 for (v = 0; v < xhalf; v++)
359 if (cmkp_tmp[v - cmk_linesize] == 0xff &&
360 cmkp_tmp[v ] == 0xff &&
361 cmkp_tmp[v + cmk_linesize] == 0xff)
362 sum++;
363 cmkp_tmp += cmk_linesize;
364 }
365 if (sum)
367 }
368
369 for (x = widtha; x <
width; x++) {
370 const uint8_t *cmkp_tmp = cmkp + x;
372 for (
u = 0;
u < yhalf;
u++) {
373 if (cmkp_tmp[-cmk_linesize] == 0xff &&
374 cmkp_tmp[ 0] == 0xff &&
375 cmkp_tmp[ cmk_linesize] == 0xff)
376 sum++;
377 cmkp_tmp += cmk_linesize;
378 }
379 if (sum)
381 }
382
383 cmkp += cmk_linesize * yhalf;
384 }
385
387
388 for (x = 0; x < arraysize; x++)
389 if (c_array[x] > max_v)
390 max_v = c_array[x];
391 }
392 return max_v;
393 }
394
395 // the secret is that tbuffer is an interlaced, offset subset of all the lines
397 const uint8_t *nxtp, int nxt_linesize,
398 uint8_t *tbuffer, int tbuf_linesize,
400 {
401 int y, x;
402
403 prvp -= prv_linesize;
404 nxtp -= nxt_linesize;
405 for (y = 0; y <
height; y++) {
406 for (x = 0; x <
width; x++)
407 tbuffer[x] =
FFABS(prvp[x] - nxtp[x]);
408 prvp += prv_linesize;
409 nxtp += nxt_linesize;
410 tbuffer += tbuf_linesize;
411 }
412 }
413
414 /**
415 * Build a map over which pixels differ a lot/a little
416 */
418 const uint8_t *prvp, int prv_linesize,
419 const uint8_t *nxtp, int nxt_linesize,
420 uint8_t *dstp,
int dst_linesize,
int height,
421 int width,
int plane)
422 {
423 int x, y,
u,
diff, count;
425 const uint8_t *dp = fm->
tbuffer + tpitch;
426
429
430 for (y = 2; y <
height - 2; y += 2) {
431 for (x = 1; x <
width - 1; x++) {
434 for (count = 0,
u = x-1;
u < x+2 && count < 2;
u++) {
435 count += dp[
u-tpitch] > 3;
437 count += dp[
u+tpitch] > 3;
438 }
439 if (count > 1) {
440 dstp[x] = 1;
442 int upper = 0, lower = 0;
443 for (count = 0,
u = x-1;
u < x+2 && count < 6;
u++) {
444 if (dp[
u-tpitch] > 19) { count++; upper = 1; }
445 if (dp[
u ] > 19) count++;
446 if (dp[
u+tpitch] > 19) { count++; lower = 1; }
447 }
448 if (count > 3) {
449 if (upper && lower) {
450 dstp[x] |= 1<<1;
451 } else {
452 int upper2 = 0, lower2 = 0;
454 if (y != 2 && dp[
u-2*tpitch] > 19) upper2 = 1;
455 if ( dp[
u- tpitch] > 19) upper = 1;
456 if ( dp[
u+ tpitch] > 19) lower = 1;
457 if (y !=
height-4 && dp[
u+2*tpitch] > 19) lower2 = 1;
458 }
459 if ((upper && (lower || upper2)) ||
460 (lower && (upper || lower2)))
461 dstp[x] |= 1<<1;
462 else if (count > 5)
463 dstp[x] |= 1<<2;
464 }
465 }
466 }
467 }
468 }
469 }
470 dp += tpitch;
471 dstp += dst_linesize;
472 }
473 }
474
476
478 {
480 }
481
483 {
484 if (match ==
mP || match ==
mB)
return fm->
prv;
485 else if (match ==
mN || match ==
mU)
return fm->
nxt;
486 else /* match == mC */ return fm->
src;
487 }
488
490 {
492 uint64_t accumPc = 0, accumPm = 0, accumPml = 0;
493 uint64_t accumNc = 0, accumNm = 0, accumNml = 0;
494 int norm1, norm2, mtn1, mtn2;
497
498 for (plane = 0; plane < (fm->
mchroma ? 3 : 1); plane++) {
499 int x, y, temp1, temp2, fbase;
501 uint8_t *mapp = fm->
map_data[plane];
503 const uint8_t *srcp =
src->data[plane];
504 const int src_linesize =
src->linesize[plane];
505 const int srcf_linesize = src_linesize << 1;
506 int prv_linesize, nxt_linesize;
507 int prvf_linesize, nxtf_linesize;
513 const int stopx =
width - startx;
514 const uint8_t *srcpf, *srcf, *srcnf;
515 const uint8_t *prvpf, *prvnf, *nxtpf, *nxtnf;
516
518
519 /* match1 */
521 srcf = srcp + (fbase + 1) * src_linesize;
522 srcpf = srcf - srcf_linesize;
523 srcnf = srcf + srcf_linesize;
524 mapp = mapp + fbase * map_linesize;
526 prv_linesize = prev->
linesize[plane];
527 prvf_linesize = prv_linesize << 1;
528 prvpf = prev->
data[plane] + fbase * prv_linesize;
// previous frame, previous field
529 prvnf = prvpf + prvf_linesize; // previous frame, next field
530
531 /* match2 */
534 nxt_linesize = next->
linesize[plane];
535 nxtf_linesize = nxt_linesize << 1;
536 nxtpf = next->
data[plane] + fbase * nxt_linesize;
// next frame, previous field
537 nxtnf = nxtpf + nxtf_linesize; // next frame, next field
538
539 map_linesize <<= 1;
540 if ((match1 >= 3 &&
field == 1) || (match1 < 3 &&
field != 1))
543 else
545 mapp + map_linesize, map_linesize,
height,
width, plane);
546
547 for (y = 2; y <
height - 2; y += 2) {
548 if (y0a == y1a || y < y0a || y > y1a) {
549 for (x = startx; x < stopx; x++) {
550 if (mapp[x] > 0 || mapp[x + map_linesize] > 0) {
551 temp1 = srcpf[x] + (srcf[x] << 2) + srcnf[x]; // [1 4 1]
552
553 temp2 =
abs(3 * (prvpf[x] + prvnf[x]) - temp1);
554 if (temp2 > 23 && ((mapp[x]&1) || (mapp[x + map_linesize]&1)))
555 accumPc += temp2;
556 if (temp2 > 42) {
557 if ((mapp[x]&2) || (mapp[x + map_linesize]&2))
558 accumPm += temp2;
559 if ((mapp[x]&4) || (mapp[x + map_linesize]&4))
560 accumPml += temp2;
561 }
562
563 temp2 =
abs(3 * (nxtpf[x] + nxtnf[x]) - temp1);
564 if (temp2 > 23 && ((mapp[x]&1) || (mapp[x + map_linesize]&1)))
565 accumNc += temp2;
566 if (temp2 > 42) {
567 if ((mapp[x]&2) || (mapp[x + map_linesize]&2))
568 accumNm += temp2;
569 if ((mapp[x]&4) || (mapp[x + map_linesize]&4))
570 accumNml += temp2;
571 }
572 }
573 }
574 }
575 prvpf += prvf_linesize;
576 prvnf += prvf_linesize;
577 srcpf += srcf_linesize;
578 srcf += srcf_linesize;
579 srcnf += srcf_linesize;
580 nxtpf += nxtf_linesize;
581 nxtnf += nxtf_linesize;
582 mapp += map_linesize;
583 }
584 }
585
586 if (accumPm < 500 && accumNm < 500 && (accumPml >= 500 || accumNml >= 500) &&
587 FFMAX(accumPml,accumNml) > 3*
FFMIN(accumPml,accumNml)) {
588 accumPm = accumPml;
589 accumNm = accumNml;
590 }
591
592 norm1 = (int)((accumPc / 6.0
f) + 0.5f);
593 norm2 = (int)((accumNc / 6.0
f) + 0.5f);
594 mtn1 = (int)((accumPm / 6.0
f) + 0.5f);
595 mtn2 = (int)((accumNm / 6.0
f) + 0.5f);
599 if (((mtn1 >= 500 || mtn2 >= 500) && (mtn1*2 < mtn2*1 || mtn2*2 < mtn1*1)) ||
600 ((mtn1 >= 1000 || mtn2 >= 1000) && (mtn1*3 < mtn2*2 || mtn2*3 < mtn1*2)) ||
601 ((mtn1 >= 2000 || mtn2 >= 2000) && (mtn1*5 < mtn2*4 || mtn2*5 < mtn1*4)) ||
602 ((mtn1 >= 4000 || mtn2 >= 4000) &&
c2 >
c1))
603 ret = mtn1 > mtn2 ? match2 : match1;
604 else if (mr > 0.005 &&
FFMAX(mtn1, mtn2) > 150 && (mtn1*2 < mtn2*1 || mtn2*2 < mtn1*1))
605 ret = mtn1 > mtn2 ? match2 : match1;
606 else
607 ret = norm1 > norm2 ? match2 : match1;
609 }
610
613 {
614 int plane;
615 for (plane = 0; plane < 4 &&
src->data[plane] &&
src->linesize[plane]; plane++) {
617 const int nb_copy_fields = (plane_h >> 1) + (
field ? 0 : (plane_h & 1));
619 src->data[plane] +
field*
src->linesize[plane],
src->linesize[plane] << 1,
621 }
622 }
623
626 {
629
632 } else {
634
639
640 switch (match) {
646 }
647 }
649 }
650
653 {
655
656 #define LOAD_COMB(mid) do { \
657 if (combs[mid] < 0) { \
658 if (!gen_frames[mid]) \
659 gen_frames[mid] = create_weave_frame(ctx, mid, field, \
660 fm->prv, fm->src, fm->nxt, \
661 INPUT_MAIN); \
662 combs[mid] = calc_combed_score(fm, gen_frames[mid]); \
663 } \
664 } while (0)
665
668
669 if ((combs[m2] * 3 < combs[m1] || (combs[m2] * 2 < combs[m1] && combs[m1] > fm->
combpel)) &&
670 abs(combs[m2] - combs[m1]) >= 30 && combs[m2] < fm->
combpel)
671 return m2;
672 else
673 return m1;
674 }
675
678
680 {
685 int combs[] = { -1, -1, -1, -1, -1 };
686 int order,
field,
i, match, interlaced_frame, sc = 0,
ret = 0;
687 const int *fxo;
690
691 /* update frames queue(s) */
692 #define SLIDING_FRAME_WINDOW(prv, src, nxt) do { \
693 if (prv != src) /* 2nd loop exception (1st has prv==src and we don't want to loose src) */ \
694 av_frame_free(&prv); \
695 prv = src; \
696 src = nxt; \
697 if (in) \
698 nxt = in; \
699 if (!prv) \
700 prv = src; \
701 if (!prv) /* received only one frame at that point */ \
702 return 0; \
703 av_assert0(prv && src && nxt); \
704 } while (0)
709 } else {
713 }
715 return 0;
718
719 /* parity */
725
726 /* debug mode: we generate all the fields combinations and their associated
727 * combed score. XXX: inject as frame metadata? */
731 break;
733 if (!gen_frames[
i]) {
736 }
738 }
740 combs[0], combs[1], combs[2], combs[3], combs[4]);
741 } else {
743 if (!gen_frames[
mC]) {
746 }
747 }
748
749 /* p/c selection and optional 3-way p/c/n matches */
753
754 /* scene change check */
758 sc = 1;
760 sc = 1;
761 }
762
763 if (!sc) {
767 }
768 }
769
772 /* 2-way p/c matches */
775 break;
778 break;
781 break;
786 break;
787 /* 3-way p/c/n matches */
790 break;
794 break;
795 default:
797 }
798 }
799
800 /* keep fields as-is if not matched properly */
801 interlaced_frame = combs[match] >= fm->
combpel;
804 }
805
806 /* get output frame and drop the others */
808 /* field matching was based on a filtered/post-processed input, we now
809 * pick the untouched fields from the clean source */
811 } else {
812 if (!gen_frames[match]) { // XXX: is that possible?
814 } else {
815 dst = gen_frames[match];
816 gen_frames[match] =
NULL;
817 }
818 }
822 }
823
824 /* mark the frame we are unable to match properly as interlaced so a proper
825 * de-interlacer can take the relay */
826 if (interlaced_frame) {
832 else
834 } else
836
838 " match=%d combed=%s\n", sc, combs[0], combs[1], combs[2], combs[3], combs[4],
840
844
848 }
849
851 {
856
858
864 }
873 }
880 }
887 }
890 } else {
896 }
897 return 0;
898 }
899 }
900
904 {
906
911 };
927 };
929
931 if (!fmts_list)
935 }
936
940 if (!fmts_list)
946 return 0;
947 }
948
950 {
957
959
963
970 }
971
974
981
982 return 0;
983 }
984
986 {
992 };
994
997
999 pad.
name =
"clean_src";
1003 }
1004
1009 }
1010
1014 }
1015
1016 return 0;
1017 }
1018
1020 {
1022
1037 }
1038
1040 {
1048
1049 fm->
bpc = (
desc->comp[0].depth + 7) / 8;
1055 return 0;
1056 }
1057
1059 {
1063 },
1064 };
1065
1067 .
p.
name =
"fieldmatch",
1069 .p.priv_class = &fieldmatch_class,
1077 };