2 * filter_telecide.c -- Donald Graft's Inverse Telecine Filter
3 * Copyright (C) 2003 Donald A. Graft
4 * Copyright (C) 2008 Dan Dennedy <dan@dennedy.org>
5 * Author: Dan Dennedy <dan@dennedy.org>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program 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
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software Foundation,
19 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 #include <framework/mlt.h>
29 //#define DEBUG_PATTERN_GUIDANCE
33 #define BLKSIZE_TIMES2 (2 * BLKSIZE)
39 #define POST_METRICS 1
41 #define POST_FULL_MAP 3
42 #define POST_FULL_NOMATCH 4
43 #define POST_FULL_NOMATCH_MAP 5
44 #define CACHE_SIZE 100000
52 #define BACK_ON_COMBED 1
58 unsigned int metrics
[5];
66 unsigned int predicted
;
67 unsigned int predicted_metric
;
72 mlt_properties image_cache
;
75 int tff
, chroma
, blend
, hints
, show
, debug
;
76 float dthresh
, gthresh
, vthresh
, vthresh_saved
, bthresh
;
77 int y0
, y1
, nt
, guide
, post
, back
, back_saved
;
78 int pitch
, dpitch
, pitchover2
, pitchtimes4
;
79 int w
, h
, wover2
, hover2
, hplus1over2
, hminus2
;
82 unsigned int *matchc
, *matchp
, highest_matchc
, highest_matchp
;
84 unsigned int *sumc
, *sump
, highest_sumc
, highest_sump
;
86 unsigned int *overrides
, *overrides_p
;
87 int film
, override
, inpattern
, found
;
90 // Used by field matching.
91 unsigned char *fprp
, *fcrp
, *fcrp_saved
, *fnrp
;
92 // unsigned char *fprpU, *fcrpU, *fcrp_savedU, *fnrpU;
93 // unsigned char *fprpV, *fcrpV, *fcrp_savedV, *fnrpV;
94 unsigned char *dstp
, *finalp
;
95 // unsigned char *dstpU, *dstpV;
97 unsigned int p
, c
, pblock
, cblock
, lowest
, predicted
, predicted_metric
;
98 unsigned int np
, nc
, npblock
, ncblock
, nframe
;
101 unsigned char *crp
, *prp
;
102 unsigned char *crpU
, *prpU
;
103 unsigned char *crpV
, *prpV
;
108 struct CACHE_ENTRY
*cache
;
110 // Pattern guidance data.
112 struct PREDICTION pred
[MAX_CYCLE
+1];
114 typedef struct context_s
*context
;
118 void BitBlt(uint8_t* dstp
, int dst_pitch
, const uint8_t* srcp
,
119 int src_pitch
, int row_size
, int height
)
122 for(y
=0;y
<height
;y
++)
124 memcpy(dstp
,srcp
,row_size
);
130 static void Show(context cx
, int frame
, mlt_properties properties
)
135 if (cx
->chosen
== P
) use
= 'p';
136 else if (cx
->chosen
== C
) use
= 'c';
138 snprintf(buf
, sizeof(buf
), "Telecide: frame %d: matches: %d %d %d\n", frame
, cx
->p
, cx
->c
, cx
->np
);
140 snprintf(buf
, sizeof(buf
), "%sTelecide: frame %d: vmetrics: %d %d %d [chosen=%d]\n", buf
, frame
, cx
->pblock
, cx
->cblock
, cx
->npblock
, cx
->vmetric
);
142 snprintf(buf
, sizeof(buf
), "%spattern mismatch=%0.2f%%\n", buf
, cx
->mismatch
);
143 snprintf(buf
, sizeof(buf
), "%sTelecide: frame %d: [%s %c]%s %s\n", buf
, frame
, cx
->found ?
"forcing" : "using", use
,
144 cx
->post ?
(cx
->film ?
" [progressive]" : " [interlaced]") : "",
145 cx
->guide ? cx
->status
: "");
146 mlt_properties_set( properties
, "meta.attr.telecide.markup", buf
);
149 static void Debug(context cx
, int frame
)
153 if (cx
->chosen
== P
) use
= 'p';
154 else if (cx
->chosen
== C
) use
= 'c';
156 fprintf(stderr
, "Telecide: frame %d: matches: %d %d %d\n", frame
, cx
->p
, cx
->c
, cx
->np
);
158 fprintf(stderr
, "Telecide: frame %d: vmetrics: %d %d %d [chosen=%d]\n", frame
, cx
->pblock
, cx
->cblock
, cx
->npblock
, cx
->vmetric
);
160 fprintf(stderr
, "pattern mismatch=%0.2f%%\n", cx
->mismatch
);
161 fprintf(stderr
, "Telecide: frame %d: [%s %c]%s %s\n", frame
, cx
->found ?
"forcing" : "using", use
,
162 cx
->post ?
(cx
->film ?
" [progressive]" : " [interlaced]") : "",
163 cx
->guide ? cx
->status
: "");
166 static void WriteHints(int film
, int inpattern
, mlt_properties frame_properties
)
168 mlt_properties_set_int( frame_properties
, "telecide.progressive", film
);
169 mlt_properties_set_int( frame_properties
, "telecide.in_pattern", inpattern
);
172 static void PutChosen(context cx
, int frame
, unsigned int chosen
)
174 int f
= frame
% CACHE_SIZE
;
175 if (frame
< 0 || frame
> cx
->out
|| cx
->cache
[f
].frame
!= frame
)
177 cx
->cache
[f
].chosen
= chosen
;
180 static void CacheInsert(context cx
, int frame
, unsigned int p
, unsigned int pblock
,
181 unsigned int c
, unsigned int cblock
)
183 int f
= frame
% CACHE_SIZE
;
184 if (frame
< 0 || frame
> cx
->out
)
185 fprintf( stderr
, "%s: internal error: invalid frame %d for CacheInsert", __FUNCTION__
, frame
);
186 cx
->cache
[f
].frame
= frame
;
187 cx
->cache
[f
].metrics
[P
] = p
;
188 if (f
) cx
->cache
[f
-1].metrics
[N
] = p
;
189 cx
->cache
[f
].metrics
[C
] = c
;
190 cx
->cache
[f
].metrics
[PBLOCK
] = pblock
;
191 cx
->cache
[f
].metrics
[CBLOCK
] = cblock
;
192 cx
->cache
[f
].chosen
= 0xff;
195 static int CacheQuery(context cx
, int frame
, unsigned int *p
, unsigned int *pblock
,
196 unsigned int *c
, unsigned int *cblock
)
200 f
= frame
% CACHE_SIZE
;
201 if (frame
< 0 || frame
> cx
->out
)
202 fprintf( stderr
, "%s: internal error: invalid frame %d for CacheQuery", __FUNCTION__
, frame
);
203 if (cx
->cache
[f
].frame
!= frame
)
207 *p
= cx
->cache
[f
].metrics
[P
];
208 *c
= cx
->cache
[f
].metrics
[C
];
209 *pblock
= cx
->cache
[f
].metrics
[PBLOCK
];
210 *cblock
= cx
->cache
[f
].metrics
[CBLOCK
];
214 static int PredictHardYUY2(context cx
, int frame
, unsigned int *predicted
, unsigned int *predicted_metric
)
216 // Look for pattern in the actual delivered matches of the previous cycle of frames.
217 // If a pattern is found, use that to predict the current match.
218 if ( cx
->guide
== GUIDE_22
)
220 if (cx
->cache
[(frame
- cx
->cycle
)%CACHE_SIZE
].chosen
== 0xff ||
221 cx
->cache
[(frame
- cx
->cycle
+1)%CACHE_SIZE
].chosen
== 0xff)
223 switch ((cx
->cache
[(frame
- cx
->cycle
)%CACHE_SIZE
].chosen
<< 4) +
224 (cx
->cache
[(frame
- cx
->cycle
+1)%CACHE_SIZE
].chosen
))
228 *predicted_metric
= cx
->cache
[frame
%CACHE_SIZE
].metrics
[C
];
232 *predicted_metric
= cx
->cache
[frame
%CACHE_SIZE
].metrics
[N
];
237 else if ( cx
->guide
== GUIDE_32
)
239 if (cx
->cache
[(frame
-cx
->cycle
)%CACHE_SIZE
].chosen
== 0xff ||
240 cx
->cache
[(frame
-cx
->cycle
+1)%CACHE_SIZE
].chosen
== 0xff ||
241 cx
->cache
[(frame
-cx
->cycle
+2)%CACHE_SIZE
].chosen
== 0xff ||
242 cx
->cache
[(frame
-cx
->cycle
+3)%CACHE_SIZE
].chosen
== 0xff ||
243 cx
->cache
[(frame
-cx
->cycle
+4)%CACHE_SIZE
].chosen
== 0xff)
246 switch ((cx
->cache
[(frame
-cx
->cycle
)%CACHE_SIZE
].chosen
<< 16) +
247 (cx
->cache
[(frame
-cx
->cycle
+1)%CACHE_SIZE
].chosen
<< 12) +
248 (cx
->cache
[(frame
-cx
->cycle
+2)%CACHE_SIZE
].chosen
<< 8) +
249 (cx
->cache
[(frame
-cx
->cycle
+3)%CACHE_SIZE
].chosen
<< 4) +
250 (cx
->cache
[(frame
-cx
->cycle
+4)%CACHE_SIZE
].chosen
))
259 *predicted_metric
= cx
->cache
[frame
%CACHE_SIZE
].metrics
[C
];
266 *predicted_metric
= cx
->cache
[frame
%CACHE_SIZE
].metrics
[N
];
271 else if ( cx
->guide
== GUIDE_32322
)
273 if (cx
->cache
[(frame
- cx
->cycle
)%CACHE_SIZE
].chosen
== 0xff ||
274 cx
->cache
[(frame
- cx
->cycle
+1)%CACHE_SIZE
].chosen
== 0xff ||
275 cx
->cache
[(frame
- cx
->cycle
+2)%CACHE_SIZE
].chosen
== 0xff ||
276 cx
->cache
[(frame
- cx
->cycle
+3)%CACHE_SIZE
].chosen
== 0xff ||
277 cx
->cache
[(frame
- cx
->cycle
+4)%CACHE_SIZE
].chosen
== 0xff ||
278 cx
->cache
[(frame
- cx
->cycle
+5)%CACHE_SIZE
].chosen
== 0xff)
281 switch ((cx
->cache
[(frame
- cx
->cycle
)%CACHE_SIZE
].chosen
<< 20) +
282 (cx
->cache
[(frame
- cx
->cycle
+1)%CACHE_SIZE
].chosen
<< 16) +
283 (cx
->cache
[(frame
- cx
->cycle
+2)%CACHE_SIZE
].chosen
<< 12) +
284 (cx
->cache
[(frame
- cx
->cycle
+3)%CACHE_SIZE
].chosen
<< 8) +
285 (cx
->cache
[(frame
- cx
->cycle
+4)%CACHE_SIZE
].chosen
<< 4) +
286 (cx
->cache
[(frame
- cx
->cycle
+5)%CACHE_SIZE
].chosen
))
297 *predicted_metric
= cx
->cache
[frame
%CACHE_SIZE
].metrics
[C
];
305 *predicted_metric
= cx
->cache
[frame
%CACHE_SIZE
].metrics
[N
];
310 #ifdef DEBUG_PATTERN_GUIDANCE
311 fprintf( stderr
, "%s: pos=%d HARD: predicted=%d\n", __FUNCTION__
, frame
, *predicted
);
316 static struct PREDICTION
*PredictSoftYUY2(context cx
, int frame
)
318 // Use heuristics to look forward for a match.
319 int i
, j
, y
, c
, n
, phase
;
322 cx
->pred
[0].metric
= 0xffffffff;
323 if (frame
< 0 || frame
> cx
->out
- cx
->cycle
) return cx
->pred
;
325 // Look at the next cycle of frames.
326 for (y
= frame
+ 1; y
<= frame
+ cx
->cycle
; y
++)
328 // Look for a frame where the current and next match values are
329 // very close. Those are candidates to predict the phase, because
330 // that condition should occur only once per cycle. Store the candidate
331 // phases and predictions in a list sorted by goodness. The list will
332 // be used by the caller to try the phases in order.
333 c
= cx
->cache
[y
%CACHE_SIZE
].metrics
[C
];
334 n
= cx
->cache
[y
%CACHE_SIZE
].metrics
[N
];
336 metric
= (100 * abs (c
- n
)) / c
;
337 phase
= y
% cx
->cycle
;
340 // Place the new candidate phase in sorted order in the list.
341 // Find the insertion point.
343 while (metric
> cx
->pred
[i
].metric
) i
++;
344 // Find the end-of-list marker.
346 while (cx
->pred
[j
].metric
!= 0xffffffff) j
++;
347 // Shift all items below the insertion point down by one to make
348 // room for the insertion.
352 cx
->pred
[j
].metric
= cx
->pred
[j
-1].metric
;
353 cx
->pred
[j
].phase
= cx
->pred
[j
-1].phase
;
354 cx
->pred
[j
].predicted
= cx
->pred
[j
-1].predicted
;
355 cx
->pred
[j
].predicted_metric
= cx
->pred
[j
-1].predicted_metric
;
357 // Insert the new candidate data.
358 cx
->pred
[j
].metric
= metric
;
359 cx
->pred
[j
].phase
= phase
;
360 if ( cx
->guide
== GUIDE_32
)
362 switch ((frame
% cx
->cycle
) - phase
)
364 case -4: cx
->pred
[j
].predicted
= N
; cx
->pred
[j
].predicted_metric
= cx
->cache
[frame
%CACHE_SIZE
].metrics
[N
]; break;
365 case -3: cx
->pred
[j
].predicted
= N
; cx
->pred
[j
].predicted_metric
= cx
->cache
[frame
%CACHE_SIZE
].metrics
[N
]; break;
366 case -2: cx
->pred
[j
].predicted
= C
; cx
->pred
[j
].predicted_metric
= cx
->cache
[frame
%CACHE_SIZE
].metrics
[C
]; break;
367 case -1: cx
->pred
[j
].predicted
= C
; cx
->pred
[j
].predicted_metric
= cx
->cache
[frame
%CACHE_SIZE
].metrics
[C
]; break;
368 case 0: cx
->pred
[j
].predicted
= C
; cx
->pred
[j
].predicted_metric
= cx
->cache
[frame
%CACHE_SIZE
].metrics
[C
]; break;
369 case +1: cx
->pred
[j
].predicted
= N
; cx
->pred
[j
].predicted_metric
= cx
->cache
[frame
%CACHE_SIZE
].metrics
[N
]; break;
370 case +2: cx
->pred
[j
].predicted
= N
; cx
->pred
[j
].predicted_metric
= cx
->cache
[frame
%CACHE_SIZE
].metrics
[N
]; break;
371 case +3: cx
->pred
[j
].predicted
= C
; cx
->pred
[j
].predicted_metric
= cx
->cache
[frame
%CACHE_SIZE
].metrics
[C
]; break;
372 case +4: cx
->pred
[j
].predicted
= C
; cx
->pred
[j
].predicted_metric
= cx
->cache
[frame
%CACHE_SIZE
].metrics
[C
]; break;
375 else if ( cx
->guide
== GUIDE_32322
)
377 switch ((frame
% cx
->cycle
) - phase
)
379 case -5: cx
->pred
[j
].predicted
= N
; cx
->pred
[j
].predicted_metric
= cx
->cache
[frame
%CACHE_SIZE
].metrics
[N
]; break;
380 case -4: cx
->pred
[j
].predicted
= N
; cx
->pred
[j
].predicted_metric
= cx
->cache
[frame
%CACHE_SIZE
].metrics
[N
]; break;
381 case -3: cx
->pred
[j
].predicted
= C
; cx
->pred
[j
].predicted_metric
= cx
->cache
[frame
%CACHE_SIZE
].metrics
[C
]; break;
382 case -2: cx
->pred
[j
].predicted
= C
; cx
->pred
[j
].predicted_metric
= cx
->cache
[frame
%CACHE_SIZE
].metrics
[C
]; break;
383 case -1: cx
->pred
[j
].predicted
= C
; cx
->pred
[j
].predicted_metric
= cx
->cache
[frame
%CACHE_SIZE
].metrics
[C
]; break;
384 case 0: cx
->pred
[j
].predicted
= C
; cx
->pred
[j
].predicted_metric
= cx
->cache
[frame
%CACHE_SIZE
].metrics
[C
]; break;
385 case +1: cx
->pred
[j
].predicted
= N
; cx
->pred
[j
].predicted_metric
= cx
->cache
[frame
%CACHE_SIZE
].metrics
[N
]; break;
386 case +2: cx
->pred
[j
].predicted
= N
; cx
->pred
[j
].predicted_metric
= cx
->cache
[frame
%CACHE_SIZE
].metrics
[N
]; break;
387 case +3: cx
->pred
[j
].predicted
= C
; cx
->pred
[j
].predicted_metric
= cx
->cache
[frame
%CACHE_SIZE
].metrics
[C
]; break;
388 case +4: cx
->pred
[j
].predicted
= C
; cx
->pred
[j
].predicted_metric
= cx
->cache
[frame
%CACHE_SIZE
].metrics
[C
]; break;
389 case +5: cx
->pred
[j
].predicted
= C
; cx
->pred
[j
].predicted_metric
= cx
->cache
[frame
%CACHE_SIZE
].metrics
[C
]; break;
393 #ifdef DEBUG_PATTERN_GUIDANCE
394 fprintf( stderr
, "%s: pos=%d metric=%d phase=%d\n", __FUNCTION__
, frame
, metric
, phase
);
401 void CalculateMetrics(context cx
, int frame
, unsigned char *fcrp
, unsigned char *fcrpU
, unsigned char *fcrpV
,
402 unsigned char *fprp
, unsigned char *fprpU
, unsigned char *fprpV
)
404 int x
, y
, p
, c
, tmp1
, tmp2
, skip
;
406 unsigned char *currbot0
, *currbot2
, *prevbot0
, *prevbot2
;
407 unsigned char *prevtop0
, *prevtop2
, *prevtop4
, *currtop0
, *currtop2
, *currtop4
;
408 unsigned char *a0
, *a2
, *b0
, *b2
, *b4
;
409 unsigned int diff
, index
;
412 /* Clear the block sums. */
413 for (y
= 0; y
< cx
->yblocks
; y
++)
415 for (x
= 0; x
< cx
->xblocks
; x
++)
417 #ifdef WINDOWED_MATCH
418 matchp
[y
*xblocks
+x
] = 0;
419 matchc
[y
*xblocks
+x
] = 0;
421 cx
->sump
[y
* cx
->xblocks
+ x
] = 0;
422 cx
->sumc
[y
* cx
->xblocks
+ x
] = 0;
426 /* Find the best field match. Subsample the frames for speed. */
427 currbot0
= fcrp
+ cx
->pitch
;
428 currbot2
= fcrp
+ 3 * cx
->pitch
;
430 currtop2
= fcrp
+ 2 * cx
->pitch
;
431 currtop4
= fcrp
+ 4 * cx
->pitch
;
432 prevbot0
= fprp
+ cx
->pitch
;
433 prevbot2
= fprp
+ 3 * cx
->pitch
;
435 prevtop2
= fprp
+ 2 * cx
->pitch
;
436 prevtop4
= fprp
+ 4 * cx
->pitch
;
455 // Calculate the field match and film/video metrics.
456 // if (vi.IsYV12()) skip = 1;
458 skip
= 1 + ( !cx
->chroma
);
459 for (y
= 0, index
= 0; y
< cx
->h
- 4; y
+=4)
461 /* Exclusion band. Good for ignoring subtitles. */
462 if (cx
->y0
== cx
->y1
|| y
< cx
->y0
|| y
> cx
->y1
)
464 for (x
= 0; x
< cx
->w
;)
467 // index = (y/BLKSIZE)*xblocks + x/BLKSIZE;
469 index
= (y
/BLKSIZE
) * cx
->xblocks
+ x
/BLKSIZE_TIMES2
;
471 // Test combination with current frame.
472 tmp1
= ((long)currbot0
[x
] + (long)currbot2
[x
]);
473 // diff = abs((long)currtop0[x] - (tmp1 >> 1));
474 diff
= abs((((long)currtop0
[x
] + (long)currtop2
[x
] + (long)currtop4
[x
])) - (tmp1
>> 1) - tmp1
);
478 #ifdef WINDOWED_MATCH
479 matchc
[index
] += diff
;
483 tmp1
= currbot0
[x
] + T
;
484 tmp2
= currbot0
[x
] - T
;
485 vc
= (tmp1
< currtop0
[x
] && tmp1
< currtop2
[x
]) ||
486 (tmp2
> currtop0
[x
] && tmp2
> currtop2
[x
]);
492 // Test combination with previous frame.
493 tmp1
= ((long)a0
[x
] + (long)a2
[x
]);
494 diff
= abs((((long)b0
[x
] + (long)b2
[x
] + (long)b4
[x
])) - (tmp1
>> 1) - tmp1
);
498 #ifdef WINDOWED_MATCH
499 matchp
[index
] += diff
;
505 vc
= (tmp1
< b0
[x
] && tmp1
< b2
[x
]) ||
506 (tmp2
> b0
[x
] && tmp2
> b2
[x
]);
516 currbot0
+= cx
->pitchtimes4
;
517 currbot2
+= cx
->pitchtimes4
;
518 currtop0
+= cx
->pitchtimes4
;
519 currtop2
+= cx
->pitchtimes4
;
520 currtop4
+= cx
->pitchtimes4
;
521 a0
+= cx
->pitchtimes4
;
522 a2
+= cx
->pitchtimes4
;
523 b0
+= cx
->pitchtimes4
;
524 b2
+= cx
->pitchtimes4
;
525 b4
+= cx
->pitchtimes4
;
528 // if (vi.IsYV12() && chroma == true)
532 // for (z = 0; z < 2; z++)
534 // // Do the same for the U plane.
537 // currbot0 = fcrpU + pitchover2;
538 // currbot2 = fcrpU + 3 * pitchover2;
540 // currtop2 = fcrpU + 2 * pitchover2;
541 // currtop4 = fcrpU + 4 * pitchover2;
542 // prevbot0 = fprpU + pitchover2;
543 // prevbot2 = fprpU + 3 * pitchover2;
545 // prevtop2 = fprpU + 2 * pitchover2;
546 // prevtop4 = fprpU + 4 * pitchover2;
550 // currbot0 = fcrpV + pitchover2;
551 // currbot2 = fcrpV + 3 * pitchover2;
553 // currtop2 = fcrpV + 2 * pitchover2;
554 // currtop4 = fcrpV + 4 * pitchover2;
555 // prevbot0 = fprpV + pitchover2;
556 // prevbot2 = fprpV + 3 * pitchover2;
558 // prevtop2 = fprpV + 2 * pitchover2;
559 // prevtop4 = fprpV + 4 * pitchover2;
578 // for (y = 0, index = 0; y < hover2 - 4; y+=4)
580 // /* Exclusion band. Good for ignoring subtitles. */
581 // if (y0 == y1 || y < y0/2 || y > y1/2)
583 // for (x = 0; x < wover2;)
586 // index = (y/BLKSIZE)*xblocks + x/BLKSIZE;
588 // index = (y/BLKSIZE)*xblocks + x/BLKSIZE_TIMES2;
590 // // Test combination with current frame.
591 // tmp1 = ((long)currbot0[x] + (long)currbot2[x]);
592 // diff = abs((((long)currtop0[x] + (long)currtop2[x] + (long)currtop4[x])) - (tmp1 >> 1) - tmp1);
596 //#ifdef WINDOWED_MATCH
597 // matchc[index] += diff;
601 // tmp1 = currbot0[x] + T;
602 // tmp2 = currbot0[x] - T;
603 // vc = (tmp1 < currtop0[x] && tmp1 < currtop2[x]) ||
604 // (tmp2 > currtop0[x] && tmp2 > currtop2[x]);
610 // // Test combination with previous frame.
611 // tmp1 = ((long)a0[x] + (long)a2[x]);
612 // diff = abs((((long)b0[x] + (long)b2[x] + (long)b4[x])) - (tmp1 >> 1) - tmp1);
616 //#ifdef WINDOWED_MATCH
617 // matchp[index] += diff;
623 // vc = (tmp1 < b0[x] && tmp1 < b2[x]) ||
624 // (tmp2 > b0[x] && tmp2 > b2[x]);
631 // if (!(x&3)) x += 4;
634 // currbot0 += 4*pitchover2;
635 // currbot2 += 4*pitchover2;
636 // currtop0 += 4*pitchover2;
637 // currtop2 += 4*pitchover2;
638 // currtop4 += 4*pitchover2;
639 // a0 += 4*pitchover2;
640 // a2 += 4*pitchover2;
641 // b0 += 4*pitchover2;
642 // b2 += 4*pitchover2;
643 // b4 += 4*pitchover2;
648 // // Now find the blocks that have the greatest differences.
649 //#ifdef WINDOWED_MATCH
650 // highest_matchp = 0;
651 // for (y = 0; y < yblocks; y++)
653 // for (x = 0; x < xblocks; x++)
655 //if (frame == 45 && matchp[y * xblocks + x] > 2500)
657 // sprintf(buf, "%d/%d = %d\n", x, y, matchp[y * xblocks + x]);
658 // OutputDebugString(buf);
660 // if (matchp[y * xblocks + x] > highest_matchp)
662 // highest_matchp = matchp[y * xblocks + x];
666 // highest_matchc = 0;
667 // for (y = 0; y < yblocks; y++)
669 // for (x = 0; x < xblocks; x++)
671 //if (frame == 44 && matchc[y * xblocks + x] > 2500)
673 // sprintf(buf, "%d/%d = %d\n", x, y, matchc[y * xblocks + x]);
674 // OutputDebugString(buf);
676 // if (matchc[y * xblocks + x] > highest_matchc)
678 // highest_matchc = matchc[y * xblocks + x];
685 cx
->highest_sump
= 0;
686 for (y
= 0; y
< cx
->yblocks
; y
++)
688 for (x
= 0; x
< cx
->xblocks
; x
++)
690 if (cx
->sump
[y
* cx
->xblocks
+ x
] > cx
->highest_sump
)
692 cx
->highest_sump
= cx
->sump
[y
* cx
->xblocks
+ x
];
696 cx
->highest_sumc
= 0;
697 for (y
= 0; y
< cx
->yblocks
; y
++)
699 for (x
= 0; x
< cx
->xblocks
; x
++)
701 if (cx
->sumc
[y
* cx
->xblocks
+ x
] > cx
->highest_sumc
)
703 cx
->highest_sumc
= cx
->sumc
[y
* cx
->xblocks
+ x
];
708 #ifdef WINDOWED_MATCH
709 CacheInsert(frame
, highest_matchp
, highest_sump
, highest_matchc
, highest_sumc
);
711 CacheInsert( cx
, frame
, p
, cx
->highest_sump
, c
, cx
->highest_sumc
);
716 /** Process the image.
719 static int get_image( mlt_frame frame
, uint8_t **image
, mlt_image_format
*format
, int *width
, int *height
, int writable
)
721 // Get the filter service
722 mlt_filter filter
= mlt_frame_pop_service( frame
);
723 mlt_properties properties
= MLT_FILTER_PROPERTIES( filter
);
724 mlt_properties frame_properties
= mlt_frame_properties( frame
);
725 context cx
= mlt_properties_get_data( properties
, "context", NULL
);
726 mlt_service producer
= mlt_service_producer( mlt_filter_service( filter
) );
727 cx
->out
= producer?
mlt_producer_get_playtime( MLT_PRODUCER( producer
) ) : 999999;
729 if ( ! cx
->is_configured
)
731 cx
->back
= mlt_properties_get_int( properties
, "back" );
732 cx
->chroma
= mlt_properties_get_int( properties
, "chroma" );
733 cx
->guide
= mlt_properties_get_int( properties
, "guide" );
734 cx
->gthresh
= mlt_properties_get_double( properties
, "gthresh" );
735 cx
->post
= mlt_properties_get_int( properties
, "post" );
736 cx
->vthresh
= mlt_properties_get_double( properties
, "vthresh" );
737 cx
->bthresh
= mlt_properties_get_double( properties
, "bthresh" );
738 cx
->dthresh
= mlt_properties_get_double( properties
, "dthresh" );
739 cx
->blend
= mlt_properties_get_int( properties
, "blend" );
740 cx
->nt
= mlt_properties_get_int( properties
, "nt" );
741 cx
->y0
= mlt_properties_get_int( properties
, "y0" );
742 cx
->y1
= mlt_properties_get_int( properties
, "y1" );
743 cx
->hints
= mlt_properties_get_int( properties
, "hints" );
744 cx
->debug
= mlt_properties_get_int( properties
, "debug" );
745 cx
->show
= mlt_properties_get_int( properties
, "show" );
749 int error
= mlt_frame_get_image( frame
, image
, format
, width
, height
, 1 );
753 int guide
= mlt_properties_get_int( properties
, "guide" );
755 if ( guide
== GUIDE_32
)
757 // 24fps to 30 fps telecine.
760 else if ( guide
== GUIDE_22
)
762 // PAL guidance (expect the current match to be continued).
765 else if ( guide
== GUIDE_32322
)
767 // 25fps to 30 fps telecine.
771 cx
->xblocks
= (*width
+BLKSIZE
-1) / BLKSIZE
;
772 cx
->yblocks
= (*height
+BLKSIZE
-1) / BLKSIZE
;
773 cx
->sump
= (unsigned int *) mlt_pool_alloc( cx
->xblocks
* cx
->yblocks
* sizeof(unsigned int) );
774 cx
->sumc
= (unsigned int *) mlt_pool_alloc( cx
->xblocks
* cx
->yblocks
* sizeof(unsigned int) );
775 mlt_properties_set_data( properties
, "sump", cx
->sump
, cx
->xblocks
* cx
->yblocks
* sizeof(unsigned int), (mlt_destructor
)mlt_pool_release
, NULL
);
776 mlt_properties_set_data( properties
, "sumc", cx
->sumc
, cx
->xblocks
* cx
->yblocks
* sizeof(unsigned int), (mlt_destructor
)mlt_pool_release
, NULL
);
777 cx
->tff
= mlt_properties_get_int( frame_properties
, "top_field_first" );
778 // fprintf(stderr, "%s: TOP FIELD FIRST %d\n", __FUNCTION__, cx->tff );
781 // Only process if we have no error and a valid colour space
782 if ( error
== 0 && *format
== mlt_image_yuv422
)
784 // Put the current image into the image cache, keyed on position
785 size_t image_size
= (*width
* *height
) << 1;
786 mlt_position pos
= mlt_frame_get_position( frame
);
787 uint8_t *image_copy
= mlt_pool_alloc( image_size
);
788 memcpy( image_copy
, *image
, image_size
);
790 sprintf( key
, "%d", pos
);
791 mlt_properties_set_data( cx
->image_cache
, key
, image_copy
, image_size
, (mlt_destructor
)mlt_pool_release
, NULL
);
793 // Only if we have enough frame images cached
794 if ( pos
> 1 && pos
> cx
->cycle
+ 1 )
796 pos
-= cx
->cycle
+ 1;
797 // Get the current frame image
798 sprintf( key
, "%d", pos
);
799 cx
->fcrp
= mlt_properties_get_data( cx
->image_cache
, key
, NULL
);
800 if (!cx
->fcrp
) return error
;
802 // Get the previous frame image
803 cx
->pframe
= pos
== 0 ?
0 : pos
- 1;
804 sprintf( key
, "%d", cx
->pframe
);
805 cx
->fprp
= mlt_properties_get_data( cx
->image_cache
, key
, NULL
);
806 if (!cx
->fprp
) return error
;
808 // Get the next frame image
809 cx
->nframe
= pos
> cx
->out ? cx
->out
: pos
+ 1;
810 sprintf( key
, "%d", cx
->nframe
);
811 cx
->fnrp
= mlt_properties_get_data( cx
->image_cache
, key
, NULL
);
812 if (!cx
->fnrp
) return error
;
814 cx
->pitch
= *width
<< 1;
815 cx
->pitchover2
= cx
->pitch
>> 1;
816 cx
->pitchtimes4
= cx
->pitch
<< 2;
820 fprintf( stderr
, "%s: width must be a multiple of 2\n", __FUNCTION__
);
822 fprintf( stderr
, "%s: height must be a multiple of 2\n", __FUNCTION__
);
823 cx
->wover2
= cx
->w
/2;
824 cx
->hover2
= cx
->h
/2;
825 cx
->hplus1over2
= (cx
->h
+1)/2;
826 cx
->hminus2
= cx
->h
- 2;
827 cx
->dpitch
= cx
->pitch
;
829 // Ensure that the metrics for the frames
830 // after the current frame are in the cache. They will be used for
834 for ( cx
->y
= pos
+ 1; (cx
->y
<= pos
+ cx
->cycle
+ 1) && (cx
->y
<= cx
->out
); cx
->y
++ )
836 if ( ! CacheQuery( cx
, cx
->y
, &cx
->p
, &cx
->pblock
, &cx
->c
, &cx
->cblock
) )
838 sprintf( key
, "%d", cx
->y
);
839 cx
->crp
= (unsigned char *) mlt_properties_get_data( cx
->image_cache
, key
, NULL
);
840 sprintf( key
, "%d", cx
->y ? cx
->y
- 1 : 1 );
841 cx
->prp
= (unsigned char *) mlt_properties_get_data( cx
->image_cache
, key
, NULL
);
842 CalculateMetrics( cx
, cx
->y
, cx
->crp
, NULL
, NULL
, cx
->prp
, NULL
, NULL
); }
846 // Check for manual overrides of the field matching.
851 cx
->vthresh
= cx
->vthresh
;
852 cx
->back
= cx
->back_saved
;
854 // Get the metrics for the current-previous (p), current-current (c), and current-next (n) match candidates.
855 if ( ! CacheQuery( cx
, pos
, &cx
->p
, &cx
->pblock
, &cx
->c
, &cx
->cblock
) )
857 CalculateMetrics( cx
, pos
, cx
->fcrp
, NULL
, NULL
, cx
->fprp
, NULL
, NULL
);
858 CacheQuery( cx
, pos
, &cx
->p
, &cx
->pblock
, &cx
->c
, &cx
->cblock
);
860 if ( ! CacheQuery( cx
, cx
->nframe
, &cx
->np
, &cx
->npblock
, &cx
->nc
, &cx
->ncblock
) )
862 CalculateMetrics( cx
, cx
->nframe
, cx
->fnrp
, NULL
, NULL
, cx
->fcrp
, NULL
, NULL
);
863 CacheQuery( cx
, cx
->nframe
, &cx
->np
, &cx
->npblock
, &cx
->nc
, &cx
->ncblock
);
866 // Determine the best candidate match.
871 if ( cx
->back
== ALWAYS_BACK
&& cx
->p
< cx
->lowest
)
876 if ( cx
->np
< cx
->lowest
)
882 if ((pos
== 0 && cx
->chosen
== P
) || (pos
== cx
->out
&& cx
->chosen
== N
))
888 // See if we can apply pattern guidance.
889 cx
->mismatch
= 100.0;
893 if ( pos
>= cx
->cycle
&& PredictHardYUY2( cx
, pos
, &cx
->predicted
, &cx
->predicted_metric
) )
898 if ( cx
->chosen
!= cx
->predicted
)
900 // The chosen frame doesn't match the prediction.
901 if ( cx
->predicted_metric
== 0 )
904 cx
->mismatch
= (100.0 * abs( cx
->predicted_metric
- cx
->lowest
) ) / cx
->predicted_metric
;
905 if ( cx
->mismatch
< cx
->gthresh
)
907 // It's close enough, so use the predicted one.
910 cx
->chosen
= cx
->predicted
;
922 if ( !cx
->hard
&& cx
->guide
!= GUIDE_22
)
925 struct PREDICTION
*pred
= PredictSoftYUY2( cx
, pos
);
927 if ( ( pos
<= cx
->out
- cx
->cycle
) && ( pred
[0].metric
!= 0xffffffff ) )
929 // Apply pattern guidance.
930 // If the predicted match metric is within defined percentage of the
931 // best calculated one, then override the calculated match with the
934 while ( pred
[i
].metric
!= 0xffffffff )
936 cx
->predicted
= pred
[i
].predicted
;
937 cx
->predicted_metric
= pred
[i
].predicted_metric
;
938 #ifdef DEBUG_PATTERN_GUIDANCE
939 fprintf(stderr
, "%s: pos=%d predicted=%d\n", __FUNCTION__
, pos
, cx
->predicted
);
941 if ( cx
->chosen
!= cx
->predicted
)
943 // The chosen frame doesn't match the prediction.
944 if ( cx
->predicted_metric
== 0 )
947 cx
->mismatch
= (100.0 * abs( cx
->predicted_metric
- cx
->lowest
)) / cx
->predicted_metric
;
948 if ( (int) cx
->mismatch
<= cx
->gthresh
)
950 // It's close enough, so use the predicted one.
953 cx
->chosen
= cx
->predicted
;
961 // Looks like we're not in a predictable pattern.
977 // Check the match for progressive versus interlaced.
980 if (cx
->chosen
== P
) cx
->vmetric
= cx
->pblock
;
981 else if (cx
->chosen
== C
) cx
->vmetric
= cx
->cblock
;
982 else if (cx
->chosen
== N
) cx
->vmetric
= cx
->npblock
;
984 if ( !cx
->found
&& cx
->back
== BACK_ON_COMBED
&& cx
->vmetric
> cx
->bthresh
&& cx
->p
< cx
->lowest
)
987 cx
->vmetric
= cx
->pblock
;
992 if ( cx
->vmetric
> cx
->vthresh
)
994 // After field matching and pattern guidance the frame is still combed.
996 if ( !cx
->found
&& ( cx
->post
== POST_FULL_NOMATCH
|| cx
->post
== POST_FULL_NOMATCH_MAP
) )
999 cx
->vmetric
= cx
->cblock
;
1005 cx
->vthresh
= cx
->vthresh_saved
;
1007 // Setup strings for debug info.
1008 if ( cx
->inpattern
&& !cx
->override
) strcpy( cx
->status
, "[in-pattern]" );
1009 else if ( cx
->inpattern
&& cx
->override
) strcpy( cx
->status
, "[in-pattern*]" );
1010 else strcpy( cx
->status
, "[out-of-pattern]" );
1012 // Assemble and output the reconstructed frame according to the final match.
1014 if ( cx
->chosen
== N
)
1016 // The best match was with the next frame.
1019 BitBlt( cx
->dstp
, 2 * cx
->dpitch
, cx
->fnrp
, 2 * cx
->pitch
, cx
->w
, cx
->hover2
);
1020 BitBlt( cx
->dstp
+ cx
->dpitch
, 2 * cx
->dpitch
, cx
->fcrp
+ cx
->pitch
, 2 * cx
->pitch
, cx
->w
, cx
->hover2
);
1024 BitBlt( cx
->dstp
, 2 * cx
->dpitch
, cx
->fcrp
, 2 * cx
->pitch
, cx
->w
, cx
->hplus1over2
);
1025 BitBlt( cx
->dstp
+ cx
->dpitch
, 2 * cx
->dpitch
, cx
->fnrp
+ cx
->pitch
, 2 * cx
->pitch
, cx
->w
, cx
->hover2
);
1028 else if ( cx
->chosen
== C
)
1030 // The best match was with the current frame.
1031 BitBlt( cx
->dstp
, 2 * cx
->dpitch
, cx
->fcrp
, 2 * cx
->pitch
, cx
->w
, cx
->hplus1over2
);
1032 BitBlt( cx
->dstp
+ cx
->dpitch
, 2 * cx
->dpitch
, cx
->fcrp
+ cx
->pitch
, 2 * cx
->pitch
, cx
->w
, cx
->hover2
);
1034 else if ( ! cx
->tff
)
1036 // The best match was with the previous frame.
1037 BitBlt( cx
->dstp
, 2 * cx
->dpitch
, cx
->fprp
, 2 * cx
->pitch
, cx
->w
, cx
->hplus1over2
);
1038 BitBlt( cx
->dstp
+ cx
->dpitch
, 2 * cx
->dpitch
, cx
->fcrp
+ cx
->pitch
, 2 * cx
->pitch
, cx
->w
, cx
->hover2
);
1042 // The best match was with the previous frame.
1043 BitBlt( cx
->dstp
, 2 * cx
->dpitch
, cx
->fcrp
, 2 * cx
->pitch
, cx
->w
, cx
->hplus1over2
);
1044 BitBlt( cx
->dstp
+ cx
->dpitch
, 2 * cx
->dpitch
, cx
->fprp
+ cx
->pitch
, 2 * cx
->pitch
, cx
->w
, cx
->hover2
);
1047 PutChosen( cx
, pos
, cx
->chosen
);
1049 if ( !cx
->post
|| cx
->post
== POST_METRICS
)
1051 if ( cx
->force
== '+') cx
->film
= 0;
1052 else if ( cx
->force
== '-' ) cx
->film
= 1;
1054 else if ((cx
->force
== '+') ||
1055 ((cx
->post
== POST_FULL
|| cx
->post
== POST_FULL_MAP
|| cx
->post
== POST_FULL_NOMATCH
|| cx
->post
== POST_FULL_NOMATCH_MAP
)
1056 && (cx
->film
== 0 && cx
->force
!= '-')))
1058 unsigned char *dstpp
, *dstpn
;
1063 // Do first and last lines.
1064 uint8_t *final
= mlt_pool_alloc( image_size
);
1066 mlt_properties_set_data( frame_properties
, "image", final
, image_size
, (mlt_destructor
)mlt_pool_release
, NULL
);
1067 dstpn
= cx
->dstp
+ cx
->dpitch
;
1068 for ( cx
->x
= 0; cx
->x
< cx
->w
; cx
->x
++ )
1070 cx
->finalp
[cx
->x
] = (((int)cx
->dstp
[cx
->x
] + (int)dstpn
[cx
->x
]) >> 1);
1072 cx
->finalp
= final
+ (cx
->h
-1)*cx
->dpitch
;
1073 cx
->dstp
= *image
+ (cx
->h
-1)*cx
->dpitch
;
1074 dstpp
= cx
->dstp
- cx
->dpitch
;
1075 for ( cx
->x
= 0; cx
->x
< cx
->w
; cx
->x
++ )
1077 cx
->finalp
[cx
->x
] = (((int)cx
->dstp
[cx
->x
] + (int)dstpp
[cx
->x
]) >> 1);
1080 cx
->dstp
= *image
+ cx
->dpitch
;
1081 dstpp
= cx
->dstp
- cx
->dpitch
;
1082 dstpn
= cx
->dstp
+ cx
->dpitch
;
1083 cx
->finalp
= final
+ cx
->dpitch
;
1084 for ( cx
->y
= 1; cx
->y
< cx
->h
- 1; cx
->y
++ )
1086 for ( cx
->x
= 0; cx
->x
< cx
->w
; cx
->x
++ )
1088 v1
= (int) cx
->dstp
[cx
->x
] - cx
->dthresh
;
1091 v2
= (int) cx
->dstp
[cx
->x
] + cx
->dthresh
;
1092 if (v2
> 235) v2
= 235;
1093 if ((v1
> dstpp
[cx
->x
] && v1
> dstpn
[cx
->x
]) || (v2
< dstpp
[cx
->x
] && v2
< dstpn
[cx
->x
]))
1095 if ( cx
->post
== POST_FULL_MAP
|| cx
->post
== POST_FULL_NOMATCH_MAP
)
1097 if (cx
->x
& 1) cx
->finalp
[cx
->x
] = 128;
1098 else cx
->finalp
[cx
->x
] = 235;
1101 cx
->finalp
[cx
->x
] = ((int)dstpp
[cx
->x
] + (int)dstpn
[cx
->x
] + (int)cx
->dstp
[cx
->x
] + (int)cx
->dstp
[cx
->x
]) >> 2;
1103 else cx
->finalp
[cx
->x
] = cx
->dstp
[cx
->x
];
1105 cx
->finalp
+= cx
->dpitch
;
1106 cx
->dstp
+= cx
->dpitch
;
1107 dstpp
+= cx
->dpitch
;
1108 dstpn
+= cx
->dpitch
;
1112 if (cx
->show
) Show( cx
, pos
, frame_properties
);
1113 if (cx
->debug
) Debug(cx
, pos
);
1114 if (cx
->hints
) WriteHints(cx
->film
, cx
->inpattern
, frame_properties
);
1118 // Interpolate mode.
1119 cx
->dstp
= *image
+ cx
->dpitch
;
1120 dstpp
= cx
->dstp
- cx
->dpitch
;
1121 dstpn
= cx
->dstp
+ cx
->dpitch
;
1122 for ( cx
->y
= 1; cx
->y
< cx
->h
- 1; cx
->y
+=2 )
1124 for ( cx
->x
= 0; cx
->x
< cx
->w
; cx
->x
++ )
1126 v1
= (int) cx
->dstp
[cx
->x
] - cx
->dthresh
;
1128 v2
= (int) cx
->dstp
[cx
->x
] + cx
->dthresh
;
1129 if (v2
> 235) v2
= 235;
1130 if ((v1
> dstpp
[cx
->x
] && v1
> dstpn
[cx
->x
]) || (v2
< dstpp
[cx
->x
] && v2
< dstpn
[cx
->x
]))
1132 if ( cx
->post
== POST_FULL_MAP
|| cx
->post
== POST_FULL_NOMATCH_MAP
)
1134 if (cx
->x
& 1) cx
->dstp
[cx
->x
] = 128;
1135 else cx
->dstp
[cx
->x
] = 235;
1138 cx
->dstp
[cx
->x
] = (dstpp
[cx
->x
] + dstpn
[cx
->x
]) >> 1;
1141 cx
->dstp
+= 2 * cx
->dpitch
;
1142 dstpp
+= 2 * cx
->dpitch
;
1143 dstpn
+= 2 * cx
->dpitch
;
1146 if (cx
->show
) Show( cx
, pos
, frame_properties
);
1147 if (cx
->debug
) Debug(cx
, pos
);
1148 if (cx
->hints
) WriteHints(cx
->film
, cx
->inpattern
, frame_properties
);
1151 // Flush frame at tail of period from the cache
1152 sprintf( key
, "%d", pos
- 1 );
1153 mlt_properties_set_data( cx
->image_cache
, key
, NULL
, 0, NULL
, NULL
);
1157 // Signal the first {cycle} frames as invalid
1158 mlt_properties_set_int( frame_properties
, "garbage", 1 );
1161 else if ( error
== 0 && *format
== mlt_image_yuv420p
)
1163 fprintf(stderr
,"%s: %d pos %d\n", __FUNCTION__
, *width
* *height
* 3/2, mlt_frame_get_position(frame
) );
1169 /** Process the frame object.
1172 static mlt_frame
process( mlt_filter
this, mlt_frame frame
)
1174 // Push the filter on to the stack
1175 mlt_frame_push_service( frame
, this );
1177 // Push the frame filter
1178 mlt_frame_push_get_image( frame
, get_image
);
1183 /** Constructor for the filter.
1186 mlt_filter
filter_telecide_init( mlt_profile profile
, mlt_service_type type
, const char *id
, char *arg
)
1188 mlt_filter
this = mlt_filter_new( );
1191 this->process
= process
;
1193 // Allocate the context and set up for garbage collection
1194 context cx
= (context
) mlt_pool_alloc( sizeof(struct context_s
) );
1195 memset( cx
, 0, sizeof( struct context_s
) );
1196 mlt_properties properties
= MLT_FILTER_PROPERTIES( this );
1197 mlt_properties_set_data( properties
, "context", cx
, sizeof(struct context_s
), (mlt_destructor
)mlt_pool_release
, NULL
);
1199 // Allocate the metrics cache and set up for garbage collection
1200 cx
->cache
= (struct CACHE_ENTRY
*) mlt_pool_alloc(CACHE_SIZE
* sizeof(struct CACHE_ENTRY
));
1201 mlt_properties_set_data( properties
, "cache", cx
->cache
, CACHE_SIZE
* sizeof(struct CACHE_ENTRY
), (mlt_destructor
)mlt_pool_release
, NULL
);
1203 for (i
= 0; i
< CACHE_SIZE
; i
++)
1205 cx
->cache
[i
].frame
= 0xffffffff;
1206 cx
->cache
[i
].chosen
= 0xff;
1209 // Allocate the image cache and set up for garbage collection
1210 cx
->image_cache
= mlt_properties_new();
1211 mlt_properties_set_data( properties
, "image_cache", cx
->image_cache
, 0, (mlt_destructor
)mlt_properties_close
, NULL
);
1213 // Initialize the parameter defaults
1214 mlt_properties_set_int( properties
, "guide", 0 );
1215 mlt_properties_set_int( properties
, "back", 0 );
1216 mlt_properties_set_int( properties
, "chroma", 0 );
1217 mlt_properties_set_int( properties
, "post", POST_FULL
);
1218 mlt_properties_set_double( properties
, "gthresh", 10.0 );
1219 mlt_properties_set_double( properties
, "vthresh", 50.0 );
1220 mlt_properties_set_double( properties
, "bthresh", 50.0 );
1221 mlt_properties_set_double( properties
, "dthresh", 7.0 );
1222 mlt_properties_set_int( properties
, "blend", 0 );
1223 mlt_properties_set_int( properties
, "nt", 10 );
1224 mlt_properties_set_int( properties
, "y0", 0 );
1225 mlt_properties_set_int( properties
, "y1", 0 );
1226 mlt_properties_set_int( properties
, "hints", 1 );