mirror of
https://code.videolan.org/videolan/vlc
synced 2024-09-04 09:11:33 +02:00
1613 lines
63 KiB
C
1613 lines
63 KiB
C
/*****************************************************************************
|
|
* algo_ivtc.c : IVTC (inverse telecine) algorithm for the VLC deinterlacer
|
|
*****************************************************************************
|
|
* Copyright (C) 2010-2011 the VideoLAN team
|
|
* $Id$
|
|
*
|
|
* Author: Juha Jeronen <juha.jeronen@jyu.fi>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
|
|
*****************************************************************************/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#ifdef CAN_COMPILE_MMXEXT
|
|
# include "mmx.h"
|
|
#endif
|
|
|
|
#include <stdint.h>
|
|
#include <assert.h>
|
|
|
|
#include <vlc_common.h>
|
|
#include <vlc_cpu.h>
|
|
#include <vlc_picture.h>
|
|
#include <vlc_filter.h>
|
|
|
|
#include "deinterlace.h" /* filter_sys_t */
|
|
#include "helpers.h"
|
|
|
|
#include "algo_ivtc.h"
|
|
|
|
/*****************************************************************************
|
|
* Local data
|
|
*****************************************************************************/
|
|
|
|
/* Fasten your seatbelt - lots of IVTC constants follow... */
|
|
|
|
/**
|
|
* IVTC filter modes.
|
|
*
|
|
* Hard telecine: burned into video stream.
|
|
* Soft telecine: stream consists of progressive frames;
|
|
* telecining handled by stream flags.
|
|
*
|
|
* @see ivtc_sys_t
|
|
* @see RenderIVTC()
|
|
*/
|
|
typedef enum { IVTC_MODE_DETECTING = 0,
|
|
IVTC_MODE_TELECINED_NTSC_HARD = 1,
|
|
IVTC_MODE_TELECINED_NTSC_SOFT = 2 } ivtc_mode;
|
|
|
|
/**
|
|
* Field pair combinations from successive frames in the PCN stencil.
|
|
* T = top, B = bottom, P = previous, C = current, N = next
|
|
* These are used as array indices; hence the explicit numbering.
|
|
*/
|
|
typedef enum { FIELD_PAIR_TPBP = 0, FIELD_PAIR_TPBC = 1,
|
|
FIELD_PAIR_TCBP = 2, FIELD_PAIR_TCBC = 3,
|
|
FIELD_PAIR_TCBN = 4, FIELD_PAIR_TNBC = 5,
|
|
FIELD_PAIR_TNBN = 6 } ivtc_field_pair;
|
|
|
|
/* Note: only valid ones count for NUM */
|
|
#define NUM_CADENCE_POS 9
|
|
/**
|
|
* Cadence positions for the PCN stencil (PCN, Previous Current Next).
|
|
*
|
|
* Note that "dea" in both cadence tables and a pure progressive signal
|
|
* are indistinguishable.
|
|
*
|
|
* Used as array indices except the -1.
|
|
*
|
|
* This is a combined raw position containing both i_cadence_pos
|
|
* and telecine field dominance.
|
|
* @see pi_detected_pos_to_cadence_pos
|
|
* @see pi_detected_pos_to_tfd
|
|
*/
|
|
typedef enum { CADENCE_POS_INVALID = -1,
|
|
CADENCE_POS_PROGRESSIVE = 0,
|
|
CADENCE_POS_TFF_ABC = 1,
|
|
CADENCE_POS_TFF_BCD = 2,
|
|
CADENCE_POS_TFF_CDE = 3,
|
|
CADENCE_POS_TFF_EAB = 4,
|
|
CADENCE_POS_BFF_ABC = 5,
|
|
CADENCE_POS_BFF_BCD = 6,
|
|
CADENCE_POS_BFF_CDE = 7,
|
|
CADENCE_POS_BFF_EAB = 8 } ivtc_cadence_pos;
|
|
/* First and one-past-end for TFF-only and BFF-only raw positions. */
|
|
#define CADENCE_POS_TFF_FIRST 1
|
|
#define CADENCE_POS_TFF_END 5
|
|
#define CADENCE_POS_BFF_FIRST 5
|
|
#define CADENCE_POS_BFF_END 9
|
|
|
|
/**
|
|
* For the "vektor" cadence detector algorithm.
|
|
*
|
|
* The algorithm produces a set of possible positions instead of a unique
|
|
* position, until it locks on. The set is represented as a bitmask.
|
|
*
|
|
* The bitmask is stored in a word, and its layout is:
|
|
* blank blank BFF_CARRY BFF4 BFF3 BFF2 BFF1 BFF0 (high byte)
|
|
* blank blank TFF_CARRY TFF4 TFF3 TFF2 TFF1 TFF0 (low byte)
|
|
*
|
|
* This allows predicting the next position by left-shifting the previous
|
|
* result by one bit, copying the CARRY bits to the respective zeroth position,
|
|
* and ANDing with 0x1F1F.
|
|
*
|
|
* This table is indexed with a valid ivtc_cadence_pos.
|
|
* @see ivtc_cadence_pos
|
|
*/
|
|
const int pi_detected_pos_to_bitmask[NUM_CADENCE_POS] = { 0x0808, /* prog. */
|
|
0x0001, /* TFF ABC */
|
|
0x0002, /* TFF BCD */
|
|
0x0004, /* TFF CDE */
|
|
0x0010, /* TFF EAB */
|
|
0x0100, /* BFF ABC */
|
|
0x0200, /* BFF BCD */
|
|
0x0400, /* BFF CDE */
|
|
0x1000, /* BFF EAB */
|
|
};
|
|
#define VEKTOR_CADENCE_POS_ALL 0x1F1F
|
|
#define VEKTOR_CADENCE_POS_TFF 0x00FF
|
|
#define VEKTOR_CADENCE_POS_BFF 0xFF00
|
|
#define VEKTOR_CADENCE_POS_TFF_HIGH 0x0010
|
|
#define VEKTOR_CADENCE_POS_TFF_LOW 0x0001
|
|
#define VEKTOR_CADENCE_POS_BFF_HIGH 0x1000
|
|
#define VEKTOR_CADENCE_POS_BFF_LOW 0x0100
|
|
|
|
/* Telecine field dominance */
|
|
typedef enum { TFD_INVALID = -1, TFD_TFF = 0, TFD_BFF = 1 } ivtc_tfd;
|
|
|
|
/**
|
|
* Position detection table for the "scores" cadence detector algorithm.
|
|
*
|
|
* These are the (only) field pair combinations that should give progressive
|
|
* frames. There are three for each position.
|
|
*
|
|
* First index: ivtc_cadence_pos
|
|
*/
|
|
static const ivtc_field_pair pi_best_field_pairs[NUM_CADENCE_POS][3] = {
|
|
{FIELD_PAIR_TPBP, FIELD_PAIR_TCBC, FIELD_PAIR_TNBN}, /* prog. */
|
|
|
|
{FIELD_PAIR_TPBP, FIELD_PAIR_TCBP, FIELD_PAIR_TNBC}, /* TFF ABC */
|
|
{FIELD_PAIR_TCBP, FIELD_PAIR_TNBC, FIELD_PAIR_TNBN}, /* TFF BCD */
|
|
{FIELD_PAIR_TCBP, FIELD_PAIR_TCBC, FIELD_PAIR_TNBN}, /* TFF CDE */
|
|
{FIELD_PAIR_TPBP, FIELD_PAIR_TCBC, FIELD_PAIR_TNBC}, /* TFF EAB */
|
|
|
|
{FIELD_PAIR_TPBP, FIELD_PAIR_TPBC, FIELD_PAIR_TCBN}, /* BFF ABC */
|
|
{FIELD_PAIR_TPBC, FIELD_PAIR_TCBN, FIELD_PAIR_TNBN}, /* BFF BCD */
|
|
{FIELD_PAIR_TPBC, FIELD_PAIR_TCBC, FIELD_PAIR_TNBN}, /* BFF CDE */
|
|
{FIELD_PAIR_TPBP, FIELD_PAIR_TCBC, FIELD_PAIR_TCBN}, /* BFF EAB */
|
|
};
|
|
|
|
/**
|
|
* Alternative position detection table for the "scores" cadence detector
|
|
* algorithm.
|
|
*
|
|
* These field pair combinations should give only interlaced frames.
|
|
* There are four for each position.
|
|
*
|
|
* First index: ivtc_cadence_pos
|
|
*
|
|
* Currently unused. During development it was tested that whether we detect
|
|
* best or worst, the resulting detected cadence positions are identical
|
|
* (neither strategy performs any different from the other).
|
|
*/
|
|
static const ivtc_field_pair pi_worst_field_pairs[NUM_CADENCE_POS][4] = {
|
|
{FIELD_PAIR_TPBC, FIELD_PAIR_TCBP,
|
|
FIELD_PAIR_TCBN, FIELD_PAIR_TNBC}, /* prog. */
|
|
|
|
{FIELD_PAIR_TPBC, FIELD_PAIR_TCBC,
|
|
FIELD_PAIR_TCBN, FIELD_PAIR_TNBN}, /* TFF ABC */
|
|
{FIELD_PAIR_TPBP, FIELD_PAIR_TPBC,
|
|
FIELD_PAIR_TCBC, FIELD_PAIR_TCBN}, /* TFF BCD */
|
|
{FIELD_PAIR_TPBP, FIELD_PAIR_TPBC,
|
|
FIELD_PAIR_TCBN, FIELD_PAIR_TNBC}, /* TFF CDE */
|
|
{FIELD_PAIR_TPBC, FIELD_PAIR_TCBP,
|
|
FIELD_PAIR_TCBN, FIELD_PAIR_TNBN}, /* TFF EAB */
|
|
|
|
{FIELD_PAIR_TCBP, FIELD_PAIR_TCBC,
|
|
FIELD_PAIR_TNBC, FIELD_PAIR_TNBN}, /* BFF ABC */
|
|
{FIELD_PAIR_TPBP, FIELD_PAIR_TCBP,
|
|
FIELD_PAIR_TCBC, FIELD_PAIR_TNBC}, /* BFF BCD */
|
|
{FIELD_PAIR_TPBP, FIELD_PAIR_TCBP,
|
|
FIELD_PAIR_TNBC, FIELD_PAIR_TCBN}, /* BFF CDE */
|
|
{FIELD_PAIR_TCBP, FIELD_PAIR_TPBC,
|
|
FIELD_PAIR_TNBC, FIELD_PAIR_TNBN}, /* BFF EAB */
|
|
};
|
|
|
|
/**
|
|
* Table for extracting the i_cadence_pos part of detected cadence position
|
|
* (ivtc_cadence_pos).
|
|
*
|
|
* The counter goes from 0 to 4, where "abc" = 0, "bcd" = 1, ...
|
|
*
|
|
* @see ivtc_cadence_pos
|
|
*/
|
|
static const int pi_detected_pos_to_cadence_pos[NUM_CADENCE_POS] = {
|
|
3, /* prog. */
|
|
0, /* TFF ABC */
|
|
1, /* TFF BCD */
|
|
2, /* TFF CDE */
|
|
4, /* TFF EAB */
|
|
0, /* BFF ABC */
|
|
1, /* BFF BCD */
|
|
2, /* BFF CDE */
|
|
4, /* BFF EAB */
|
|
};
|
|
|
|
/**
|
|
* Table for extracting the telecine field dominance part of detected
|
|
* cadence position (ivtc_cadence_pos).
|
|
*
|
|
* The position "dea" does not provide TFF/BFF information, because it is
|
|
* indistinguishable from progressive.
|
|
*
|
|
* @see ivtc_cadence_pos
|
|
*/
|
|
static const int pi_detected_pos_to_tfd[NUM_CADENCE_POS] = {
|
|
TFD_INVALID, /* prog. */
|
|
TFD_TFF, /* TFF ABC */
|
|
TFD_TFF, /* TFF BCD */
|
|
TFD_TFF, /* TFF CDE */
|
|
TFD_TFF, /* TFF EAB */
|
|
TFD_BFF, /* BFF ABC */
|
|
TFD_BFF, /* BFF BCD */
|
|
TFD_BFF, /* BFF CDE */
|
|
TFD_BFF, /* BFF EAB */
|
|
};
|
|
|
|
/* Valid telecine sequences (TFF and BFF). Indices: [TFD][i_cadence_pos] */
|
|
/* Currently unused and left here for documentation only.
|
|
There is an easier way - just decode the i_cadence_pos part of the
|
|
detected position using the pi_detected_pos_to_cadence_pos table,
|
|
and check that it is successive mod 5. See IVTCCadenceAnalyze(). */
|
|
/*static const int pi_valid_cadences[2][5] = { {CADENCE_POS_TFF_ABC,
|
|
CADENCE_POS_TFF_BCD,
|
|
CADENCE_POS_TFF_CDE,
|
|
CADENCE_POS_PROGRESSIVE,
|
|
CADENCE_POS_TFF_EAB},
|
|
|
|
{CADENCE_POS_BFF_ABC,
|
|
CADENCE_POS_BFF_BCD,
|
|
CADENCE_POS_BFF_CDE,
|
|
CADENCE_POS_PROGRESSIVE,
|
|
CADENCE_POS_BFF_EAB},
|
|
};
|
|
*/
|
|
|
|
/**
|
|
* Operations needed in film frame reconstruction.
|
|
*/
|
|
typedef enum { IVTC_OP_DROP_FRAME,
|
|
IVTC_OP_COPY_N,
|
|
IVTC_OP_COPY_C,
|
|
IVTC_OP_COMPOSE_TNBC,
|
|
IVTC_OP_COMPOSE_TCBN } ivtc_op;
|
|
|
|
/* Note: During hard IVTC, we must avoid COPY_C and do a compose instead.
|
|
If we COPY_C, some subtitles will flicker badly, even if we use the
|
|
cadence-based film frame reconstruction. Try the first scene in
|
|
Kanon (2006) vol. 3 to see the problem.
|
|
|
|
COPY_C can be used without problems when it is used consistently
|
|
(not constantly mixed in with COPY_N and compose operations),
|
|
for example in soft IVTC.
|
|
*/
|
|
/**
|
|
* Operation table for film frame reconstruction depending on cadence position.
|
|
* Indices: [TFD][i_cadence_pos]
|
|
* @see pi_detected_pos_to_tfd
|
|
* @see pi_detected_pos_to_cadence_pos
|
|
*/
|
|
static const ivtc_op pi_reconstruction_ops[2][5] = { /* TFF */
|
|
{IVTC_OP_COMPOSE_TNBC,
|
|
IVTC_OP_COPY_N,
|
|
IVTC_OP_COPY_N,
|
|
IVTC_OP_DROP_FRAME,
|
|
IVTC_OP_COMPOSE_TNBC},
|
|
|
|
/* BFF */
|
|
{IVTC_OP_COMPOSE_TCBN,
|
|
IVTC_OP_COPY_N,
|
|
IVTC_OP_COPY_N,
|
|
IVTC_OP_DROP_FRAME,
|
|
IVTC_OP_COMPOSE_TCBN},
|
|
};
|
|
|
|
/**
|
|
* Timestamp mangling table.
|
|
*
|
|
* This is used in the 29.97 -> 23.976 fps conversion.
|
|
*
|
|
* Index: i_cadence_pos, 0..4.
|
|
*
|
|
* Valid values are nonnegative. The -1 corresponds to the dropped frame
|
|
* and is never used, except for a debug assert.
|
|
*
|
|
* The unit of the values is 1/4 of frame duration.
|
|
* See the function documentation of RenderIVTC() for an explanation.
|
|
* @see ivtc_cadence_pos
|
|
* @see pi_detected_pos_to_cadence_pos
|
|
* @see pi_reconstruction_ops
|
|
* @see RenderIVTC()
|
|
*/
|
|
static const int pi_timestamp_deltas[5] = { 1, 2, 3, -1, 0 };
|
|
|
|
/*****************************************************************************
|
|
* Internal functions
|
|
*****************************************************************************/
|
|
|
|
/**
|
|
* Internal helper function for RenderIVTC(): performs initialization
|
|
* at the start of a new frame.
|
|
*
|
|
* In practice, this slides detector histories.
|
|
*
|
|
* This function should only perform initialization that does NOT require
|
|
* the input frame history buffer. This runs at every frame, including
|
|
* the first two.
|
|
*
|
|
* This is an internal function only used by RenderIVTC().
|
|
* There is no need to call this function manually.
|
|
*
|
|
* @param p_filter The filter instance.
|
|
* @see RenderIVTC()
|
|
*/
|
|
static void IVTCFrameInit( filter_t *p_filter )
|
|
{
|
|
assert( p_filter != NULL );
|
|
|
|
filter_sys_t *p_sys = p_filter->p_sys;
|
|
ivtc_sys_t *p_ivtc = &p_sys->ivtc;
|
|
|
|
/* Slide detector histories */
|
|
for( int i = 1; i < IVTC_DETECTION_HISTORY_SIZE; i++ )
|
|
{
|
|
p_ivtc->pi_top_rep[i-1] = p_ivtc->pi_top_rep[i];
|
|
p_ivtc->pi_bot_rep[i-1] = p_ivtc->pi_bot_rep[i];
|
|
p_ivtc->pi_motion[i-1] = p_ivtc->pi_motion[i];
|
|
|
|
p_ivtc->pi_s_cadence_pos[i-1] = p_ivtc->pi_s_cadence_pos[i];
|
|
p_ivtc->pb_s_reliable[i-1] = p_ivtc->pb_s_reliable[i];
|
|
p_ivtc->pi_v_cadence_pos[i-1] = p_ivtc->pi_v_cadence_pos[i];
|
|
p_ivtc->pi_v_raw[i-1] = p_ivtc->pi_v_raw[i];
|
|
p_ivtc->pb_v_reliable[i-1] = p_ivtc->pb_v_reliable[i];
|
|
|
|
p_ivtc->pi_cadence_pos_history[i-1]
|
|
= p_ivtc->pi_cadence_pos_history[i];
|
|
|
|
p_ivtc->pb_all_progressives[i-1] = p_ivtc->pb_all_progressives[i];
|
|
}
|
|
/* The latest position has not been detected yet. */
|
|
p_ivtc->pi_s_cadence_pos[IVTC_LATEST] = CADENCE_POS_INVALID;
|
|
p_ivtc->pb_s_reliable[IVTC_LATEST] = false;
|
|
p_ivtc->pi_v_cadence_pos[IVTC_LATEST] = CADENCE_POS_INVALID;
|
|
p_ivtc->pi_v_raw[IVTC_LATEST] = VEKTOR_CADENCE_POS_ALL;
|
|
p_ivtc->pb_v_reliable[IVTC_LATEST] = false;
|
|
p_ivtc->pi_cadence_pos_history[IVTC_LATEST] = CADENCE_POS_INVALID;
|
|
p_ivtc->pi_top_rep[IVTC_LATEST] = 0;
|
|
p_ivtc->pi_bot_rep[IVTC_LATEST] = 0;
|
|
p_ivtc->pi_motion[IVTC_LATEST] = -1;
|
|
p_ivtc->pb_all_progressives[IVTC_LATEST] = false;
|
|
|
|
/* Slide history of field pair interlace scores */
|
|
p_ivtc->pi_scores[FIELD_PAIR_TPBP] = p_ivtc->pi_scores[FIELD_PAIR_TCBC];
|
|
p_ivtc->pi_scores[FIELD_PAIR_TPBC] = p_ivtc->pi_scores[FIELD_PAIR_TCBN];
|
|
p_ivtc->pi_scores[FIELD_PAIR_TCBP] = p_ivtc->pi_scores[FIELD_PAIR_TNBC];
|
|
p_ivtc->pi_scores[FIELD_PAIR_TCBC] = p_ivtc->pi_scores[FIELD_PAIR_TNBN];
|
|
/* These have not been detected yet */
|
|
p_ivtc->pi_scores[FIELD_PAIR_TCBN] = 0;
|
|
p_ivtc->pi_scores[FIELD_PAIR_TNBC] = 0;
|
|
p_ivtc->pi_scores[FIELD_PAIR_TNBN] = 0;
|
|
}
|
|
|
|
/**
|
|
* Internal helper function for RenderIVTC(): computes various raw detector
|
|
* data at the start of a new frame.
|
|
*
|
|
* This function requires the input frame history buffer.
|
|
* IVTCFrameInit() must have been called first.
|
|
* Last two frames must be available in the history buffer.
|
|
*
|
|
* This is an internal function only used by RenderIVTC().
|
|
* There is no need to call this function manually.
|
|
*
|
|
* @param p_filter The filter instance.
|
|
* @see RenderIVTC()
|
|
* @see IVTCFrameInit()
|
|
*/
|
|
static void IVTCLowLevelDetect( filter_t *p_filter )
|
|
{
|
|
assert( p_filter != NULL );
|
|
|
|
filter_sys_t *p_sys = p_filter->p_sys;
|
|
ivtc_sys_t *p_ivtc = &p_sys->ivtc;
|
|
picture_t *p_curr = p_sys->pp_history[1];
|
|
picture_t *p_next = p_sys->pp_history[2];
|
|
|
|
assert( p_next != NULL );
|
|
assert( p_curr != NULL );
|
|
|
|
/* Compute interlace scores for TNBN, TNBC and TCBN.
|
|
Note that p_next contains TNBN. */
|
|
p_ivtc->pi_scores[FIELD_PAIR_TNBN] = CalculateInterlaceScore( p_next,
|
|
p_next );
|
|
p_ivtc->pi_scores[FIELD_PAIR_TNBC] = CalculateInterlaceScore( p_next,
|
|
p_curr );
|
|
p_ivtc->pi_scores[FIELD_PAIR_TCBN] = CalculateInterlaceScore( p_curr,
|
|
p_next );
|
|
|
|
int i_top = 0, i_bot = 0;
|
|
int i_motion = EstimateNumBlocksWithMotion(p_curr, p_next, &i_top, &i_bot);
|
|
p_ivtc->pi_motion[IVTC_LATEST] = i_motion;
|
|
|
|
/* If one field changes "clearly more" than the other, we know the
|
|
less changed one is a likely duplicate.
|
|
|
|
Threshold 1/2 is too low for some scenes (e.g. pan of the space junk
|
|
at beginning of The Third ep. 1, right after the OP). Thus, we use 2/3,
|
|
which seems to work.
|
|
*/
|
|
p_ivtc->pi_top_rep[IVTC_LATEST] = (i_top <= 2*i_bot/3);
|
|
p_ivtc->pi_bot_rep[IVTC_LATEST] = (i_bot <= 2*i_top/3);
|
|
}
|
|
|
|
/**
|
|
* Internal helper function for RenderIVTC(): using raw detector data,
|
|
* detect cadence position by an interlace scores based algorithm ("scores").
|
|
*
|
|
* IVTCFrameInit() and IVTCLowLevelDetect() must have been called first.
|
|
* Last frame must be available in the history buffer.
|
|
*
|
|
* This is an internal function only used by RenderIVTC().
|
|
* There is no need to call this function manually.
|
|
*
|
|
* @param p_filter The filter instance.
|
|
* @see RenderIVTC()
|
|
* @see IVTCFrameInit()
|
|
* @see IVTCLowLevelDetect()
|
|
* @see IVTCCadenceDetectFinalize()
|
|
*/
|
|
static void IVTCCadenceDetectAlgoScores( filter_t *p_filter )
|
|
{
|
|
assert( p_filter != NULL );
|
|
|
|
filter_sys_t *p_sys = p_filter->p_sys;
|
|
ivtc_sys_t *p_ivtc = &p_sys->ivtc;
|
|
picture_t *p_next = p_sys->pp_history[2];
|
|
|
|
assert( p_next != NULL );
|
|
|
|
/* Detect likely cadence position according to the tables,
|
|
using the tabulated combinations of all 7 available interlace scores.
|
|
*/
|
|
int pi_ivtc_scores[NUM_CADENCE_POS];
|
|
for( int i = 0; i < NUM_CADENCE_POS; i++ )
|
|
pi_ivtc_scores[i] = p_ivtc->pi_scores[ pi_best_field_pairs[i][0] ]
|
|
+ p_ivtc->pi_scores[ pi_best_field_pairs[i][1] ]
|
|
+ p_ivtc->pi_scores[ pi_best_field_pairs[i][2] ];
|
|
/* Find minimum */
|
|
int j = CADENCE_POS_PROGRESSIVE; /* valid regardless of TFD */
|
|
int minscore = pi_ivtc_scores[j];
|
|
/* A TFF (respectively BFF) stream may only have TFF (respectively BFF)
|
|
telecine. Don't bother looking at the wrong table. */
|
|
int imin = CADENCE_POS_TFF_FIRST; /* first TFF-only entry */
|
|
int iend = CADENCE_POS_TFF_END; /* one past last TFF-only entry */
|
|
if( !p_next->b_top_field_first )
|
|
{
|
|
imin = CADENCE_POS_BFF_FIRST; /* first BFF-only entry */
|
|
iend = CADENCE_POS_BFF_END; /* one past last BFF-only entry */
|
|
}
|
|
for( int i = imin; i < iend; i++ )
|
|
{
|
|
if( pi_ivtc_scores[i] < minscore )
|
|
{
|
|
minscore = pi_ivtc_scores[i];
|
|
j = i;
|
|
}
|
|
}
|
|
|
|
/* Now "j" contains the most likely position according to the tables,
|
|
accounting also for video TFF/BFF. */
|
|
p_ivtc->pi_s_cadence_pos[IVTC_LATEST] = j;
|
|
|
|
/* Estimate reliability of detector result.
|
|
|
|
We do this by checking if the winner is an outlier at least
|
|
to some extent. For anyone better versed in statistics,
|
|
feel free to improve this.
|
|
*/
|
|
|
|
/* Compute sample mean with the winner included and without.
|
|
|
|
Sample mean is defined as mu = sum( x_i, i ) / N ,
|
|
where N is the number of samples.
|
|
*/
|
|
int mean = pi_ivtc_scores[CADENCE_POS_PROGRESSIVE];
|
|
int mean_except_min = 0;
|
|
if( j != CADENCE_POS_PROGRESSIVE )
|
|
mean_except_min = pi_ivtc_scores[CADENCE_POS_PROGRESSIVE];
|
|
for( int i = imin; i < iend; i++ )
|
|
{
|
|
mean += pi_ivtc_scores[i];
|
|
if( i != j )
|
|
mean_except_min += pi_ivtc_scores[i];
|
|
}
|
|
/* iend points one past end, but progressive counts as the +1. */
|
|
mean /= (iend - imin + 1);
|
|
mean_except_min /= (iend - imin);
|
|
|
|
/* Check how much excluding the winner changes the mean. */
|
|
double mean_ratio = (double)mean_except_min / (double)mean;
|
|
|
|
/* Let's pretend that the detected position is a stochastic variable.
|
|
Compute sample variance with the winner included and without.
|
|
|
|
var = sum( (x_i - mu)^2, i ) / N ,
|
|
|
|
where mu is the sample mean.
|
|
|
|
Note that we really need int64_t; the numbers are pretty large.
|
|
*/
|
|
int64_t diff = (int64_t)(pi_ivtc_scores[CADENCE_POS_PROGRESSIVE] - mean);
|
|
int64_t var = diff*diff;
|
|
int64_t var_except_min = 0;
|
|
if( j != CADENCE_POS_PROGRESSIVE )
|
|
{
|
|
int64_t diff_exm = (int64_t)(pi_ivtc_scores[CADENCE_POS_PROGRESSIVE]
|
|
- mean_except_min);
|
|
var_except_min = diff_exm*diff_exm;
|
|
}
|
|
for( int i = imin; i < iend; i++ )
|
|
{
|
|
diff = (int64_t)(pi_ivtc_scores[i] - mean);
|
|
var += (diff*diff);
|
|
if( i != j )
|
|
{
|
|
int64_t diff_exm = (int64_t)(pi_ivtc_scores[i] - mean_except_min);
|
|
var_except_min += (diff_exm*diff_exm);
|
|
}
|
|
}
|
|
/* iend points one past end, but progressive counts as the +1. */
|
|
var /= (uint64_t)(iend - imin + 1);
|
|
var_except_min /= (uint64_t)(iend - imin);
|
|
|
|
/* Extract cadence counter part of detected positions for the
|
|
last two frames.
|
|
|
|
Note that for the previous frame, we use the final detected cadence
|
|
position, which was not necessarily produced by this algorithm.
|
|
It is the result that was judged the most reliable.
|
|
*/
|
|
int j_curr = p_ivtc->pi_cadence_pos_history[IVTC_LATEST-1];
|
|
int pos_next = pi_detected_pos_to_cadence_pos[j];
|
|
|
|
/* Be optimistic when unsure. We bias the detection toward accepting
|
|
the next "correct" position, even if the variance check comes up bad.
|
|
*/
|
|
bool b_expected = false;
|
|
if( j_curr != CADENCE_POS_INVALID )
|
|
{
|
|
int pos_curr = pi_detected_pos_to_cadence_pos[j_curr];
|
|
b_expected = (pos_next == (pos_curr + 1) % 5);
|
|
}
|
|
|
|
/* Use motion detect result as a final sanity check.
|
|
If no motion, the result from this algorithm cannot be reliable.
|
|
*/
|
|
int i_blocks_with_motion = p_ivtc->pi_motion[IVTC_LATEST];
|
|
|
|
/* The numbers given here are empirical constants that have been tuned
|
|
through trial and error. The test material used was NTSC anime DVDs.
|
|
|
|
Easy-to-detect parts seem to give variance boosts of 40-70%, but
|
|
hard-to-detect parts sometimes only 18%. Anything with a smaller boost
|
|
in variance doesn't seem reliable for catching a new lock-on,
|
|
|
|
Additionally, it seems that if the mean changes by less than 0.5%,
|
|
the result is not reliable.
|
|
|
|
Note that the numbers given are only valid for the pi_best_field_pairs
|
|
detector strategy.
|
|
|
|
For motion detection, the detector seems good enough so that
|
|
we can threshold at zero.
|
|
*/
|
|
bool b_result_reliable =
|
|
( i_blocks_with_motion > 0 &&
|
|
mean_ratio > 1.005 &&
|
|
( b_expected || ( (double)var > 1.17*(double)var_except_min ) )
|
|
);
|
|
p_ivtc->pb_s_reliable[IVTC_LATEST] = b_result_reliable;
|
|
}
|
|
|
|
/**
|
|
* Internal helper function for RenderIVTC(): using raw detector data,
|
|
* detect cadence position by a hard field repeat based algorithm ("vektor").
|
|
*
|
|
* This algorithm is inspired by the classic TVTime/Xine IVTC filter
|
|
* by Billy Biggs (Vektor); hence the name. There are however some
|
|
* differences between this and the TVTime/Xine filter.
|
|
*
|
|
* IVTCFrameInit() and IVTCLowLevelDetect() must have been called first.
|
|
* Last frame must be available in the history buffer.
|
|
*
|
|
* This is an internal function only used by RenderIVTC().
|
|
* There is no need to call this function manually.
|
|
*
|
|
* @param p_filter The filter instance.
|
|
* @see RenderIVTC()
|
|
* @see IVTCFrameInit()
|
|
* @see IVTCLowLevelDetect()
|
|
* @see IVTCCadenceDetectFinalize()
|
|
*/
|
|
static void IVTCCadenceDetectAlgoVektor( filter_t *p_filter )
|
|
{
|
|
assert( p_filter != NULL );
|
|
|
|
filter_sys_t *p_sys = p_filter->p_sys;
|
|
ivtc_sys_t *p_ivtc = &p_sys->ivtc;
|
|
|
|
picture_t *p_next = p_sys->pp_history[2];
|
|
|
|
assert( p_next != NULL );
|
|
|
|
/* This algorithm is based on detecting hard-repeated fields (by motion
|
|
detection), and conservatively estimating what the seen repeats could
|
|
mean for the cadence position.
|
|
|
|
"Conservative" means that we do not rule out possibilities if repeats
|
|
are *not* seen, but only *add* possibilities based on what repeats
|
|
*are* seen. This is important. Otherwise full-frame repeats in the
|
|
original film (8fps or 12fps animation is very common in anime),
|
|
causing spurious field repeats, would mess up the detection.
|
|
With this strategy, spurious repeats will only slow down the lock-on,
|
|
and will not break an existing lock-on once acquired.
|
|
|
|
Several possibilities are kept open until the sequence gives enough
|
|
information to make a unique detection. When the sequence becomes
|
|
inconsistent (e.g. bad cut), the detector resets itself.
|
|
|
|
The main ideas taken from the TVTime/Xine algorithm are:
|
|
1) Conservatively using information from detected field repeats,
|
|
2) Cadence counting the earlier detection results and combining with
|
|
the new detection result, and
|
|
3) The observation that video TFF/BFF uniquely determines TFD.
|
|
|
|
The main differences are
|
|
1) Different motion detection (see EstimateNumBlocksWithMotion()).
|
|
Vektor's original estimates the average top/bottom field diff
|
|
over the last 3 frames, while ours uses a block-based approach
|
|
for diffing and just compares the field diffs between "curr" and
|
|
"next" against each other (see IVTCLowLevelDetect()).
|
|
Both approaches are adaptive, but in a different way.
|
|
2) The specific detection logic used is a bit different (see both
|
|
codes for details; the original is in xine-lib, function
|
|
determine_pulldown_offset_short_history_new() in pulldown.c;
|
|
ours is obviously given below). I think the one given here
|
|
is a bit simpler.
|
|
|
|
Note that we don't have to worry about getting a detection in all cases.
|
|
It's enough if we work reliably, say, 99% of the time, and the other 1%
|
|
of the time just admit that we don't know the cadence position.
|
|
(This mostly happens after a bad cut, when the new scene has
|
|
"difficult" motion characteristics, such as repeated film frames.)
|
|
Our frame composer is built to handle also cases where we have no
|
|
reliable detection of the cadence position; see IVTCOutputOrDropFrame().
|
|
More important is to never lock on incorrectly, as this would both
|
|
generate interlacing artifacts where none existed, and cause motion
|
|
to stutter (because duplicate frames would be shown and unique ones
|
|
dropped).
|
|
*/
|
|
|
|
/* Progressive requires no repeats, so it is always a possibility.
|
|
Filtering will drop it out if we know that the current position
|
|
cannot be "dea".
|
|
*/
|
|
int detected = 0;
|
|
detected |= pi_detected_pos_to_bitmask[ CADENCE_POS_PROGRESSIVE ];
|
|
|
|
/* Add in other possibilities depending on field repeats seen during the
|
|
last three input frames (i.e. two transitions between input frames).
|
|
See the "Dups." column in the cadence tables.
|
|
*/
|
|
bool b_top_rep = p_ivtc->pi_top_rep[IVTC_LATEST];
|
|
bool b_bot_rep = p_ivtc->pi_bot_rep[IVTC_LATEST];
|
|
bool b_old_top_rep = p_ivtc->pi_top_rep[IVTC_LATEST-1];
|
|
bool b_old_bot_rep = p_ivtc->pi_bot_rep[IVTC_LATEST-1];
|
|
if( b_top_rep )
|
|
{
|
|
detected |= pi_detected_pos_to_bitmask[ CADENCE_POS_TFF_EAB ];
|
|
detected |= pi_detected_pos_to_bitmask[ CADENCE_POS_BFF_BCD ];
|
|
}
|
|
if( b_old_top_rep )
|
|
{
|
|
detected |= pi_detected_pos_to_bitmask[ CADENCE_POS_TFF_ABC ];
|
|
detected |= pi_detected_pos_to_bitmask[ CADENCE_POS_BFF_CDE ];
|
|
}
|
|
if( b_bot_rep )
|
|
{
|
|
detected |= pi_detected_pos_to_bitmask[ CADENCE_POS_TFF_BCD ];
|
|
detected |= pi_detected_pos_to_bitmask[ CADENCE_POS_BFF_EAB ];
|
|
}
|
|
if( b_old_bot_rep )
|
|
{
|
|
detected |= pi_detected_pos_to_bitmask[ CADENCE_POS_TFF_CDE ];
|
|
detected |= pi_detected_pos_to_bitmask[ CADENCE_POS_BFF_ABC ];
|
|
}
|
|
|
|
/* A TFF stream may only have TFF telecine, and similarly for BFF.
|
|
Discard the possibility we know to be incorrect for this stream.
|
|
(The stream may flipflop between the possibilities if it contains
|
|
soft-telecined sequences or lone field repeats, so we must keep
|
|
detecting this for each incoming frame.)
|
|
*/
|
|
bool b_tff = p_next->b_top_field_first;
|
|
if( b_tff )
|
|
detected &= VEKTOR_CADENCE_POS_TFF;
|
|
else
|
|
detected &= VEKTOR_CADENCE_POS_BFF;
|
|
|
|
/* Predict possible next positions based on our last detection.
|
|
Begin with a shift and carry. */
|
|
int predicted = p_ivtc->pi_v_raw[IVTC_LATEST-1];
|
|
bool b_wrap_tff = false;
|
|
bool b_wrap_bff = false;
|
|
if( predicted & VEKTOR_CADENCE_POS_TFF_HIGH )
|
|
b_wrap_tff = true;
|
|
if( predicted & VEKTOR_CADENCE_POS_BFF_HIGH )
|
|
b_wrap_bff = true;
|
|
/* bump to next position and keep only valid bits */
|
|
predicted = (predicted << 1) & VEKTOR_CADENCE_POS_ALL;
|
|
/* carry */
|
|
if( b_wrap_tff )
|
|
predicted |= VEKTOR_CADENCE_POS_TFF_LOW;
|
|
if( b_wrap_bff )
|
|
predicted |= VEKTOR_CADENCE_POS_BFF_LOW;
|
|
|
|
/* Filter: narrow down possibilities based on previous detection,
|
|
if consistent. If not consistent, reset the detector.
|
|
Reset works better than just using the latest raw detection.
|
|
*/
|
|
if( (detected & predicted) != 0 )
|
|
detected = detected & predicted;
|
|
else
|
|
detected = VEKTOR_CADENCE_POS_ALL;
|
|
|
|
/* We're done. Save result to our internal storage so we can use it
|
|
for prediction at the next frame.
|
|
|
|
Note that the outgoing frame check in IVTCOutputOrDropFrame()
|
|
has a veto right, resetting our state if it determines that
|
|
the cadence has become broken.
|
|
*/
|
|
p_ivtc->pi_v_raw[IVTC_LATEST] = detected;
|
|
|
|
/* See if the position has been detected uniquely.
|
|
If so, we have acquired a lock-on. */
|
|
ivtc_cadence_pos exact = CADENCE_POS_INVALID;
|
|
if( detected != 0 )
|
|
{
|
|
for( int i = 0; i < NUM_CADENCE_POS; i++ )
|
|
{
|
|
/* Note that we must use "&" instead of just equality to catch
|
|
the progressive case, and also not to trigger on an incomplete
|
|
detection. */
|
|
if( detected == (detected & pi_detected_pos_to_bitmask[i]) )
|
|
{
|
|
exact = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If the result was unique, now "exact" contains the detected
|
|
cadence position (and otherwise CADENCE_POS_INVALID).
|
|
|
|
In practice, if the result from this algorithm is unique,
|
|
it is always reliable.
|
|
*/
|
|
p_ivtc->pi_v_cadence_pos[IVTC_LATEST] = exact;
|
|
p_ivtc->pb_v_reliable[IVTC_LATEST] = (exact != CADENCE_POS_INVALID);
|
|
}
|
|
|
|
/**
|
|
* Internal helper function for RenderIVTC(): decide the final detected
|
|
* cadence position for the current position of the PCN stencil,
|
|
* using the results of the different cadence detection algorithms.
|
|
*
|
|
* Must be called after all IVTCCadenceDetectAlgo*() functions.
|
|
*
|
|
* This is an internal function only used by RenderIVTC().
|
|
* There is no need to call this function manually.
|
|
*
|
|
* @param p_filter The filter instance.
|
|
* @see RenderIVTC()
|
|
* @see IVTCCadenceDetectAlgoScores()
|
|
* @see IVTCCadenceDetectAlgoVektor()
|
|
*/
|
|
static void IVTCCadenceDetectFinalize( filter_t *p_filter )
|
|
{
|
|
assert( p_filter != NULL );
|
|
|
|
filter_sys_t *p_sys = p_filter->p_sys;
|
|
ivtc_sys_t *p_ivtc = &p_sys->ivtc;
|
|
|
|
/* In practice "vektor" is more reliable than "scores", but it may
|
|
take longer to lock on. Thus, we prefer "vektor" if its reliable bit
|
|
is set, then "scores", and finally just give up.
|
|
|
|
For progressive sequences, "vektor" outputs "3, -, 3, -, ...",
|
|
because the repeated progressive position is an inconsistent prediction.
|
|
In this case, "scores" fills in the blanks. (This particular task
|
|
could also be done without another cadence detector, by just
|
|
detecting the alternating pattern of "3" and no result.)
|
|
*/
|
|
int pos = CADENCE_POS_INVALID;
|
|
if( p_ivtc->pb_v_reliable[IVTC_LATEST] )
|
|
pos = p_ivtc->pi_v_cadence_pos[IVTC_LATEST];
|
|
else if( p_ivtc->pb_s_reliable[IVTC_LATEST] )
|
|
pos = p_ivtc->pi_s_cadence_pos[IVTC_LATEST];
|
|
p_ivtc->pi_cadence_pos_history[IVTC_LATEST] = pos;
|
|
}
|
|
|
|
/**
|
|
* Internal helper function for RenderIVTC(): using stream flags,
|
|
* detect soft telecine.
|
|
*
|
|
* This function is different from the other detectors; it may enter or exit
|
|
* IVTC_MODE_TELECINED_NTSC_SOFT, if it detects that soft telecine has just
|
|
* been entered or exited.
|
|
*
|
|
* Upon exit from soft telecine, the filter will resume operation in its
|
|
* previous mode (which it had when soft telecine was entered).
|
|
*
|
|
* Last three frames must be available in the history buffer.
|
|
*
|
|
* This is an internal function only used by RenderIVTC().
|
|
* There is no need to call this function manually.
|
|
*
|
|
* @param p_filter The filter instance.
|
|
* @see RenderIVTC()
|
|
*/
|
|
static void IVTCSoftTelecineDetect( filter_t *p_filter )
|
|
{
|
|
assert( p_filter != NULL );
|
|
|
|
filter_sys_t *p_sys = p_filter->p_sys;
|
|
ivtc_sys_t *p_ivtc = &p_sys->ivtc;
|
|
picture_t *p_prev = p_sys->pp_history[0];
|
|
picture_t *p_curr = p_sys->pp_history[1];
|
|
picture_t *p_next = p_sys->pp_history[2];
|
|
|
|
assert( p_next != NULL );
|
|
assert( p_curr != NULL );
|
|
assert( p_prev != NULL );
|
|
|
|
/* Soft telecine can be detected from the flag pattern:
|
|
nb_fields = 3,2,3,2,... and *video* TFF = true, false, false, true
|
|
(TFF telecine) or false, true, true, false (BFF telecine).
|
|
|
|
We don't particularly care which field goes first, because in soft TC
|
|
we're working with progressive frames. And in any case, the video FDs
|
|
of successive frames must match any field repeats in order for field
|
|
renderers (such as traditional DVD player + CRT TV) to work correctly.
|
|
Thus the video TFF/BFF flag provides no additional useful information
|
|
for us on top of checking nb_fields.
|
|
|
|
The only thing to *do* to soft telecine in an IVTC filter is to even
|
|
out the outgoing PTS diffs to 2.5 fields each, so that we get
|
|
a steady 24fps output. Thus, we can do this processing even if it turns
|
|
out that we saw a lone field repeat (which are also sometimes used,
|
|
such as in the Silent Mobius OP and in Sol Bianca). We can be aggressive
|
|
and don't need to care about false positives - as long as we are equally
|
|
aggressive about dropping out of soft telecine mode the moment a "2" is
|
|
followed by another "2" and not a "3" as in soft TC.
|
|
|
|
Finally, we conclude that the one-frame future buffer is enough for us
|
|
to make soft TC decisions just in time for rendering the frame in the
|
|
"current" position. The flag patterns given below constitute proof
|
|
of this property.
|
|
|
|
Soft telecine is relatively rare at least in anime, but it exists;
|
|
e.g. Angel Links OP, Silent Mobius, and Stellvia of the Universe have
|
|
sequences that are soft telecined. Stellvia, especially, alternates
|
|
between soft and hard telecine all the time.
|
|
*/
|
|
|
|
/* Valid stream flag patterns for soft telecine. There are three: */
|
|
|
|
/* Entering soft telecine at frame curr, or running inside it already */
|
|
bool b_soft_telecine_1 = (p_prev->i_nb_fields == 2) &&
|
|
(p_curr->i_nb_fields == 3) &&
|
|
(p_next->i_nb_fields == 2);
|
|
/* Running inside soft telecine */
|
|
bool b_soft_telecine_2 = (p_prev->i_nb_fields == 3) &&
|
|
(p_curr->i_nb_fields == 2) &&
|
|
(p_next->i_nb_fields == 3);
|
|
/* Exiting soft telecine at frame curr (curr is the last frame
|
|
that should be handled as soft TC) */
|
|
bool b_soft_telecine_3 = (p_prev->i_nb_fields == 3) &&
|
|
(p_curr->i_nb_fields == 2) &&
|
|
(p_next->i_nb_fields == 2);
|
|
|
|
/* Soft telecine is very clear-cut - the moment we see or do not see
|
|
a valid flag pattern, we can change the filter mode.
|
|
*/
|
|
if( b_soft_telecine_1 || b_soft_telecine_2 || b_soft_telecine_3 )
|
|
{
|
|
if( p_ivtc->i_mode != IVTC_MODE_TELECINED_NTSC_SOFT )
|
|
{
|
|
msg_Dbg( p_filter, "IVTC: 3:2 pulldown: NTSC soft telecine "\
|
|
"detected." );
|
|
p_ivtc->i_old_mode = p_ivtc->i_mode;
|
|
}
|
|
|
|
/* Valid flag pattern seen, this frame is soft telecined */
|
|
p_ivtc->i_mode = IVTC_MODE_TELECINED_NTSC_SOFT;
|
|
|
|
/* Only used during IVTC'ing hard telecine. */
|
|
p_ivtc->i_cadence_pos = CADENCE_POS_INVALID;
|
|
p_ivtc->i_tfd = TFD_INVALID;
|
|
}
|
|
/* Note: no flag pattern match now */
|
|
else if( p_ivtc->i_mode == IVTC_MODE_TELECINED_NTSC_SOFT )
|
|
{
|
|
msg_Dbg( p_filter, "IVTC: 3:2 pulldown: NTSC soft telecine ended. "\
|
|
"Returning to previous mode." );
|
|
|
|
/* No longer soft telecined, return filter to the mode it had earlier.
|
|
This is needed to fix cases where we came in from hard telecine, and
|
|
should go back, but can't catch a cadence in time before telecined
|
|
frames slip through. Kickstarting back to hard IVTC, using the
|
|
emergency frame composer until the cadence locks on again,
|
|
fixes the problem. This happens a lot in Stellvia.
|
|
*/
|
|
p_ivtc->i_mode = p_ivtc->i_old_mode;
|
|
p_ivtc->i_cadence_pos = 0; /* Wild guess. The film frame reconstruction
|
|
will start in emergency mode, and this
|
|
will be filled in by the detector ASAP.*/
|
|
/* I suppose video field dominance no longer flipflops. */
|
|
p_ivtc->i_tfd = !p_next->b_top_field_first; /* tff <=> TFD == 0 */
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Internal helper function for RenderIVTC(): using the history of detected
|
|
* cadence positions, analyze the cadence and enter or exit
|
|
* IVTC_MODE_TELECINED_NTSC_HARD when appropriate.
|
|
*
|
|
* This also updates b_sequence_valid.
|
|
*
|
|
* Last three frames must be available in the history buffer.
|
|
*
|
|
* This is an internal function only used by RenderIVTC().
|
|
* There is no need to call this function manually.
|
|
*
|
|
* @param p_filter The filter instance.
|
|
* @see RenderIVTC()
|
|
*/
|
|
static void IVTCCadenceAnalyze( filter_t *p_filter )
|
|
{
|
|
assert( p_filter != NULL );
|
|
|
|
filter_sys_t *p_sys = p_filter->p_sys;
|
|
ivtc_sys_t *p_ivtc = &p_sys->ivtc;
|
|
picture_t *p_prev = p_sys->pp_history[0];
|
|
picture_t *p_curr = p_sys->pp_history[1];
|
|
picture_t *p_next = p_sys->pp_history[2];
|
|
|
|
assert( p_next != NULL );
|
|
assert( p_curr != NULL );
|
|
assert( p_prev != NULL );
|
|
|
|
/* Determine which frames in the buffer qualify for analysis.
|
|
|
|
Note that hard telecine always has nb_fields = 2 and
|
|
video TFF = constant (i.e. the stream flags look no different from
|
|
a true interlaced or true progressive stream). Basically, no one ever
|
|
sets the progressive frame flag for the input frames d, e, and a -
|
|
in practice they're all flagged as interlaced.
|
|
|
|
A frame may qualify for hard TC analysis if it has no soft field repeat
|
|
(i.e. it cannot be part of a soft telecine). The condition
|
|
nb_fields == 2 must always match.
|
|
|
|
Additionally, curr and next must have had motion with respect to the
|
|
previous frame, to ensure that the different field combinations have
|
|
produced unique pictures.
|
|
|
|
Alternatively, if there was no motion, but the cadence position was
|
|
reliably detected and it was the expected one, we qualify the frame
|
|
for analysis (mainly, for TFD voting).
|
|
|
|
We only proceed with the cadence analysis if all three frames
|
|
in the buffer qualify.
|
|
*/
|
|
|
|
/* Note that these are the final detected positions
|
|
produced by IVTCCadenceDetectFinalize(). */
|
|
int j_next = p_ivtc->pi_cadence_pos_history[IVTC_LATEST];
|
|
int j_curr = p_ivtc->pi_cadence_pos_history[IVTC_LATEST-1];
|
|
int j_prev = p_ivtc->pi_cadence_pos_history[IVTC_LATEST-2];
|
|
|
|
bool b_expected = false;
|
|
if( j_next != CADENCE_POS_INVALID && j_curr != CADENCE_POS_INVALID )
|
|
{
|
|
int pos_next = pi_detected_pos_to_cadence_pos[j_next];
|
|
int pos_curr = pi_detected_pos_to_cadence_pos[j_curr];
|
|
b_expected = (pos_next == (pos_curr + 1) % 5);
|
|
}
|
|
bool b_old_expected = false;
|
|
if( j_curr != CADENCE_POS_INVALID && j_prev != CADENCE_POS_INVALID )
|
|
{
|
|
int pos_curr = pi_detected_pos_to_cadence_pos[j_curr];
|
|
int pos_prev = pi_detected_pos_to_cadence_pos[j_prev];
|
|
b_old_expected = (pos_curr == (pos_prev + 1) % 5);
|
|
}
|
|
|
|
int i_motion = p_ivtc->pi_motion[IVTC_LATEST];
|
|
int i_old_motion = p_ivtc->pi_motion[IVTC_LATEST-1];
|
|
|
|
bool b_prev_valid = (p_prev->i_nb_fields == 2);
|
|
bool b_curr_valid = (p_curr->i_nb_fields == 2) &&
|
|
(i_old_motion > 0 || b_old_expected);
|
|
bool b_next_valid = (p_next->i_nb_fields == 2) &&
|
|
(i_motion > 0 || b_expected);
|
|
bool b_no_invalids = (b_prev_valid && b_curr_valid && b_next_valid);
|
|
|
|
/* Final sanity check: see that the detection history has been
|
|
completely filled, i.e. the latest three positions of the stencil
|
|
have given a result from the cadence detector.
|
|
*/
|
|
if( b_no_invalids )
|
|
{
|
|
for( int i = 0; i < IVTC_DETECTION_HISTORY_SIZE; ++i )
|
|
{
|
|
const int i_detected_pos = p_ivtc->pi_cadence_pos_history[i];
|
|
if( i_detected_pos == CADENCE_POS_INVALID )
|
|
{
|
|
b_no_invalids = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If still ok, do the analysis. */
|
|
p_ivtc->b_sequence_valid = false; /* needed in frame reconstruction */
|
|
if( b_no_invalids )
|
|
{
|
|
/* Convert the history elements to cadence position and TFD. */
|
|
int pi_tfd[IVTC_DETECTION_HISTORY_SIZE];
|
|
int pi_pos[IVTC_DETECTION_HISTORY_SIZE];
|
|
for( int i = 0; i < IVTC_DETECTION_HISTORY_SIZE; ++i )
|
|
{
|
|
const int i_detected_pos = p_ivtc->pi_cadence_pos_history[i];
|
|
pi_pos[i] = pi_detected_pos_to_cadence_pos[i_detected_pos];
|
|
pi_tfd[i] = pi_detected_pos_to_tfd[i_detected_pos];
|
|
}
|
|
|
|
/* See if the sequence is valid. The cadence positions must be
|
|
successive mod 5. We can't say anything about TFF/BFF yet,
|
|
because the progressive-looking position "dea" may be there.
|
|
If the sequence otherwise looks valid, we handle that last
|
|
by voting.
|
|
|
|
We also test for a progressive signal here, so that we know
|
|
when to exit IVTC_MODE_TELECINED_NTSC_HARD.
|
|
*/
|
|
p_ivtc->b_sequence_valid = true;
|
|
bool b_all_progressive = (pi_pos[0] == 3);
|
|
int j = pi_pos[0];
|
|
for( int i = 1; i < IVTC_DETECTION_HISTORY_SIZE; ++i )
|
|
{
|
|
if( pi_pos[i] != (++j % 5) )
|
|
p_ivtc->b_sequence_valid = false;
|
|
if( pi_pos[i] != 3 )
|
|
b_all_progressive = false;
|
|
}
|
|
p_ivtc->pb_all_progressives[IVTC_LATEST] = b_all_progressive;
|
|
|
|
if( p_ivtc->b_sequence_valid )
|
|
{
|
|
/* Determine TFF/BFF. */
|
|
int i_vote_invalid = 0;
|
|
int i_vote_tff = 0;
|
|
int i_vote_bff = 0;
|
|
for( int i = 0; i < IVTC_DETECTION_HISTORY_SIZE; ++i )
|
|
{
|
|
if( pi_tfd[i] == TFD_INVALID )
|
|
i_vote_invalid++;
|
|
else if( pi_tfd[i] == TFD_TFF )
|
|
i_vote_tff++;
|
|
else if( pi_tfd[i] == TFD_BFF )
|
|
i_vote_bff++;
|
|
}
|
|
|
|
/* With three entries, two votes for any one item are enough
|
|
to decide this conclusively. */
|
|
int i_telecine_field_dominance = TFD_INVALID;
|
|
if( i_vote_tff >= 2)
|
|
i_telecine_field_dominance = TFD_TFF;
|
|
else if( i_vote_bff >= 2)
|
|
i_telecine_field_dominance = TFD_BFF;
|
|
/* In all other cases, "invalid" won or no winner.
|
|
This means no NTSC telecine detected. */
|
|
|
|
/* Lock on to the cadence if it was valid and TFF/BFF was found.
|
|
|
|
Also, aggressively update the cadence counter from the
|
|
lock-on data whenever we can. In practice this has been found
|
|
to be a reliable strategy (if the cadence detectors are
|
|
good enough).
|
|
*/
|
|
if( i_telecine_field_dominance == TFD_TFF )
|
|
{
|
|
if( p_ivtc->i_mode != IVTC_MODE_TELECINED_NTSC_HARD )
|
|
msg_Dbg( p_filter, "IVTC: 3:2 pulldown: NTSC TFF "\
|
|
"hard telecine detected." );
|
|
p_ivtc->i_mode = IVTC_MODE_TELECINED_NTSC_HARD;
|
|
p_ivtc->i_cadence_pos = pi_pos[IVTC_LATEST];
|
|
p_ivtc->i_tfd = TFD_TFF;
|
|
}
|
|
else if( i_telecine_field_dominance == TFD_BFF )
|
|
{
|
|
if( p_ivtc->i_mode != IVTC_MODE_TELECINED_NTSC_HARD )
|
|
msg_Dbg( p_filter, "IVTC: 3:2 pulldown: NTSC BFF "\
|
|
"hard telecine detected." );
|
|
p_ivtc->i_mode = IVTC_MODE_TELECINED_NTSC_HARD;
|
|
p_ivtc->i_cadence_pos = pi_pos[IVTC_LATEST];
|
|
p_ivtc->i_tfd = TFD_BFF;
|
|
}
|
|
}
|
|
/* No telecine... maybe a progressive signal? */
|
|
else if( b_all_progressive )
|
|
{
|
|
/* It seems that in practice, three "3"s in a row can still be
|
|
a fluke rather often. Four or five usually are not.
|
|
This fixes the Stellvia OP. */
|
|
|
|
bool b_really_all_progressive = true;
|
|
for( int i = 0; i < IVTC_DETECTION_HISTORY_SIZE ; i++ )
|
|
{
|
|
if( p_ivtc->pb_all_progressives[i] == false )
|
|
{
|
|
b_really_all_progressive = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If we still think the signal is progressive... */
|
|
if( b_really_all_progressive )
|
|
{
|
|
/* ...exit film mode immediately. This does not break
|
|
soft TC handling, because for soft TC at least one
|
|
of the frames will not qualify (due to i_nb_fields == 3),
|
|
and in that case this analysis will not run.
|
|
*/
|
|
if( p_ivtc->i_mode == IVTC_MODE_TELECINED_NTSC_HARD )
|
|
msg_Dbg( p_filter, "IVTC: 3:2 pulldown: progressive "\
|
|
"signal detected." );
|
|
p_ivtc->i_mode = IVTC_MODE_DETECTING;
|
|
p_ivtc->i_cadence_pos = CADENCE_POS_INVALID;
|
|
p_ivtc->i_tfd = TFD_INVALID;
|
|
}
|
|
}
|
|
/* Final missing "else": no valid NTSC telecine sequence detected.
|
|
|
|
Either there is no telecine, or the detector - although it produced
|
|
results - had trouble finding it. In this case we do nothing,
|
|
as it's not a good idea to act on unreliable data.
|
|
|
|
Note that if we are already in IVTC_MODE_TELECINED_NTSC_HARD, this
|
|
case means that we have lost the lock-on, but are still (probably)
|
|
in a hard-telecined stream. This will start the emergency mode
|
|
for film frame reconstruction. See IVTCOutputOrDropFrame().
|
|
*/
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Internal helper function for RenderIVTC(): render or drop frame,
|
|
* whichever needs to be done. This also sets the output frame PTS.
|
|
*
|
|
* Last two frames must be available in the history buffer.
|
|
*
|
|
* This is an internal function only used by RenderIVTC().
|
|
* There is no need to call this function manually.
|
|
*
|
|
* @param p_filter The filter instance. Must be non-NULL.
|
|
* @param[out] p_dst Frame will be rendered here. Must be non-NULL.
|
|
* @return Whether a frame was constructed.
|
|
* @retval true Yes, output frame is in p_dst.
|
|
* @retval false No, this frame was dropped as part of normal IVTC operation.
|
|
* @see RenderIVTC()
|
|
*/
|
|
static bool IVTCOutputOrDropFrame( filter_t *p_filter, picture_t *p_dst )
|
|
{
|
|
assert( p_filter != NULL );
|
|
assert( p_dst != NULL );
|
|
|
|
filter_sys_t *p_sys = p_filter->p_sys;
|
|
ivtc_sys_t *p_ivtc = &p_sys->ivtc;
|
|
mtime_t t_final = VLC_TS_INVALID; /* for custom timestamp mangling */
|
|
|
|
picture_t *p_curr = p_sys->pp_history[1];
|
|
picture_t *p_next = p_sys->pp_history[2];
|
|
|
|
assert( p_next != NULL );
|
|
assert( p_curr != NULL );
|
|
|
|
/* Perform IVTC if we're in film mode (either hard or soft telecine).
|
|
|
|
Note that we don't necessarily have a lock-on, even if we are in
|
|
IVTC_MODE_TELECINED_NTSC_HARD. We *may* be locked on, or alternatively,
|
|
we have seen a valid cadence some time in the past, but lock-on has
|
|
since been lost, and we have not seen a progressive signal after that.
|
|
The latter case usually results from bad cuts, which interrupt
|
|
the cadence.
|
|
|
|
Lock-on state is given by p_ivtc->b_sequence_valid.
|
|
*/
|
|
int i_result_score = -1;
|
|
int op;
|
|
if( p_ivtc->i_mode == IVTC_MODE_TELECINED_NTSC_HARD )
|
|
{
|
|
/* Decide what to do. The operation table is only enabled
|
|
if the cadence seems reliable. Otherwise we use a backup strategy.
|
|
*/
|
|
if( p_ivtc->b_sequence_valid )
|
|
{
|
|
assert( p_ivtc->i_cadence_pos != CADENCE_POS_INVALID );
|
|
assert( p_ivtc->i_tfd != TFD_INVALID );
|
|
|
|
/* Pick correct operation from the operation table. */
|
|
op = pi_reconstruction_ops[p_ivtc->i_tfd][p_ivtc->i_cadence_pos];
|
|
|
|
if( op == IVTC_OP_DROP_FRAME )
|
|
{
|
|
/* Bump cadence counter into the next expected position */
|
|
p_ivtc->i_cadence_pos = (p_ivtc->i_cadence_pos + 1) % 5;
|
|
|
|
/* Drop frame. We're done. */
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if( op == IVTC_OP_COPY_N )
|
|
i_result_score = p_ivtc->pi_scores[FIELD_PAIR_TNBN];
|
|
else if( op == IVTC_OP_COPY_C )
|
|
i_result_score = p_ivtc->pi_scores[FIELD_PAIR_TCBC];
|
|
else if( op == IVTC_OP_COMPOSE_TNBC )
|
|
i_result_score = p_ivtc->pi_scores[FIELD_PAIR_TNBC];
|
|
else if( op == IVTC_OP_COMPOSE_TCBN )
|
|
i_result_score = p_ivtc->pi_scores[FIELD_PAIR_TCBN];
|
|
|
|
/* Sanity check the result */
|
|
|
|
/* Compute running mean of outgoing interlace score.
|
|
See below for history mechanism. */
|
|
int i_avg = 0;
|
|
for( int i = 0; i < IVTC_DETECTION_HISTORY_SIZE; i++)
|
|
i_avg += p_ivtc->pi_final_scores[i];
|
|
i_avg /= IVTC_DETECTION_HISTORY_SIZE;
|
|
|
|
/* Check if the score suddenly became "clearly larger".
|
|
Also, filter out spurious peaks at the low end. */
|
|
if( i_result_score > 1000 && i_result_score > 2*i_avg )
|
|
{
|
|
/* Sequence wasn't reliable after all; we'll use
|
|
the Transcode strategy for this frame. */
|
|
p_ivtc->b_sequence_valid = false;
|
|
msg_Dbg( p_filter, "Rejected cadence-based frame "\
|
|
"construction: interlace score %d "\
|
|
"(running average %d)",
|
|
i_result_score, i_avg );
|
|
|
|
/* We also reset the detector used in the "vektor"
|
|
algorithm, as it depends on having a reliable previous
|
|
position. In practice, we continue using the Transcode
|
|
strategy until the cadence becomes locked on again.
|
|
(At that point, b_sequence_valid will become true again,
|
|
and we continue with this strategy.)
|
|
*/
|
|
p_ivtc->pi_v_raw[IVTC_LATEST] = VEKTOR_CADENCE_POS_ALL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Frame not dropped, and the cadence counter seems unreliable.
|
|
|
|
Note that this is not an "else" to the previous case. This may
|
|
begin with a valid sequence, and then the above logic decides
|
|
that it wasn't valid after all.
|
|
*/
|
|
if( !p_ivtc->b_sequence_valid )
|
|
{
|
|
/* In this case, we must proceed with no cadence information.
|
|
We use a Transcode-like strategy.
|
|
|
|
We check which field paired with TN or BN (accounting for
|
|
the field dominance) gives the smallest interlace score,
|
|
and declare that combination the resulting progressive frame.
|
|
|
|
This strategy gives good results on average, but often fails
|
|
in talking scenes in anime. Those can be handled more reliably
|
|
with a locked-on cadence produced by the "vektor" algorithm.
|
|
*/
|
|
|
|
int tnbn = p_ivtc->pi_scores[FIELD_PAIR_TNBN]; /* TFF and BFF */
|
|
int tnbc = p_ivtc->pi_scores[FIELD_PAIR_TNBC]; /* TFF only */
|
|
int tcbn = p_ivtc->pi_scores[FIELD_PAIR_TCBN]; /* BFF only */
|
|
|
|
if( p_next->b_top_field_first )
|
|
{
|
|
if( tnbn <= tnbc )
|
|
{
|
|
op = IVTC_OP_COPY_N;
|
|
i_result_score = tnbn;
|
|
}
|
|
else
|
|
{
|
|
op = IVTC_OP_COMPOSE_TNBC;
|
|
i_result_score = tnbc;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( tnbn <= tcbn )
|
|
{
|
|
op = IVTC_OP_COPY_N;
|
|
i_result_score = tnbn;
|
|
}
|
|
else
|
|
{
|
|
op = IVTC_OP_COMPOSE_TCBN;
|
|
i_result_score = tcbn;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Mangle timestamps when locked on.
|
|
|
|
"Current" is the frame that is being extracted now. Use its original
|
|
timestamp as the base.
|
|
|
|
Note that this way there will be no extra delay compared to the
|
|
raw stream, even though we look one frame into the future.
|
|
*/
|
|
if( p_ivtc->b_sequence_valid )
|
|
{
|
|
/* Convert 29.97 -> 23.976 fps. We get to this point only if we
|
|
didn't drop the frame, so we always get a valid delta.
|
|
*/
|
|
int i_timestamp_delta = pi_timestamp_deltas[p_ivtc->i_cadence_pos];
|
|
assert( i_timestamp_delta >= 0 );
|
|
|
|
/* FIXME: use field length as measured by Deinterlace()? */
|
|
t_final = p_curr->date
|
|
+ (p_next->date - p_curr->date)*i_timestamp_delta/4;
|
|
}
|
|
else /* Do not mangle timestamps (or drop frames, either) if cadence
|
|
is not locked on. This causes one of five output frames - if
|
|
all are reconstructed correctly - to be a duplicate, but in
|
|
practice at least with anime (which is the kind of material
|
|
that tends to have this problem) this is less noticeable than
|
|
a sudden jump in the cadence. Especially, a consistently wrong
|
|
lock-on will cause a very visible stutter, which we wish
|
|
to avoid. */
|
|
{
|
|
t_final = p_curr->date;
|
|
}
|
|
|
|
/* Bump cadence counter into the next expected position. */
|
|
p_ivtc->i_cadence_pos = (p_ivtc->i_cadence_pos + 1) % 5;
|
|
}
|
|
else if( p_ivtc->i_mode == IVTC_MODE_TELECINED_NTSC_SOFT )
|
|
{
|
|
/* Soft telecine. We have the progressive frames already;
|
|
even out PTS diffs only. */
|
|
|
|
/* Pass through the "current" frame. We must choose the frame "current"
|
|
in order to be able to detect soft telecine before we have to output
|
|
the frame. See IVTCSoftTelecineDetect(). Also, this allows
|
|
us to peek at the next timestamp to calculate the duration of
|
|
"current".
|
|
*/
|
|
op = IVTC_OP_COPY_C;
|
|
i_result_score = p_ivtc->pi_scores[FIELD_PAIR_TCBC];
|
|
|
|
/* Timestamp mangling for soft telecine: bump "threes" forward by
|
|
0.5 field durations. This is more forgiving for the renderer
|
|
than bumping the "twos" back (which would require to render
|
|
them sooner),
|
|
*/
|
|
if( p_curr->i_nb_fields == 3 )
|
|
{
|
|
/* Approximate field duration from the PTS difference. */
|
|
/* FIXME: use field length as measured by Deinterlace()? */
|
|
mtime_t i_half_field_dur = ( (p_next->date - p_curr->date)/3 ) / 2;
|
|
t_final = p_curr->date + i_half_field_dur;
|
|
}
|
|
else /* Otherwise, use original PTS of the outgoing frame. */
|
|
{
|
|
t_final = p_curr->date;
|
|
}
|
|
}
|
|
else /* Not film mode, timestamp mangling bypassed. */
|
|
{
|
|
op = IVTC_OP_COPY_N;
|
|
i_result_score = p_ivtc->pi_scores[FIELD_PAIR_TNBN];
|
|
|
|
/* Preserve original PTS (note that now, in principle,
|
|
"next" is the outgoing frame) */
|
|
t_final = p_next->date;
|
|
}
|
|
|
|
/* There is only one case where we should drop the frame,
|
|
and it was already handled above. */
|
|
assert( op != IVTC_OP_DROP_FRAME );
|
|
|
|
/* Render into p_dst according to the final operation chosen. */
|
|
if( op == IVTC_OP_COPY_N )
|
|
picture_Copy( p_dst, p_next );
|
|
else if( op == IVTC_OP_COPY_C )
|
|
picture_Copy( p_dst, p_curr );
|
|
else if( op == IVTC_OP_COMPOSE_TNBC )
|
|
ComposeFrame( p_filter, p_dst, p_next, p_curr, CC_ALTLINE );
|
|
else if( op == IVTC_OP_COMPOSE_TCBN )
|
|
ComposeFrame( p_filter, p_dst, p_curr, p_next, CC_ALTLINE );
|
|
|
|
/* Slide history of outgoing interlace scores. This must be done last,
|
|
and only if the frame was not dropped, so we do it here.
|
|
|
|
This is used during the reconstruction to get an idea of what is
|
|
(in the temporally local sense) an acceptable interlace score
|
|
for a correctly reconstructed frame. See above.
|
|
*/
|
|
for( int i = 1; i < IVTC_DETECTION_HISTORY_SIZE; i++ )
|
|
p_ivtc->pi_final_scores[i-1] = p_ivtc->pi_final_scores[i];
|
|
p_ivtc->pi_final_scores[IVTC_LATEST] = i_result_score;
|
|
|
|
/* Note that picture_Copy() copies the PTS, too. Apply timestamp mangling
|
|
now, if any was needed.
|
|
*/
|
|
if( t_final > VLC_TS_INVALID )
|
|
p_dst->date = t_final;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* Public functions
|
|
*****************************************************************************/
|
|
|
|
/* See function doc in header. */
|
|
int RenderIVTC( filter_t *p_filter, picture_t *p_dst )
|
|
{
|
|
assert( p_filter != NULL );
|
|
assert( p_dst != NULL );
|
|
|
|
filter_sys_t *p_sys = p_filter->p_sys;
|
|
ivtc_sys_t *p_ivtc = &p_sys->ivtc;
|
|
|
|
picture_t *p_prev = p_sys->pp_history[0];
|
|
picture_t *p_curr = p_sys->pp_history[1];
|
|
picture_t *p_next = p_sys->pp_history[2];
|
|
|
|
/* If the history mechanism has failed, we have nothing to do. */
|
|
if( !p_next )
|
|
return VLC_EGENERIC;
|
|
|
|
/* Slide algorithm-specific histories */
|
|
IVTCFrameInit( p_filter );
|
|
|
|
/* Filter if we have all the pictures we need.
|
|
Note that we always have p_next at this point. */
|
|
if( p_prev && p_curr )
|
|
{
|
|
/* Update raw data for motion, field repeats, interlace scores... */
|
|
IVTCLowLevelDetect( p_filter );
|
|
|
|
/* Detect soft telecine.
|
|
|
|
Enter/exit IVTC_MODE_TELECINED_NTSC_SOFT when needed.
|
|
*/
|
|
IVTCSoftTelecineDetect( p_filter );
|
|
|
|
/* Detect hard telecine.
|
|
|
|
Enter/exit IVTC_MODE_TELECINED_NTSC_HARD when needed.
|
|
|
|
If we happen to be running in IVTC_MODE_TELECINED_NTSC_SOFT,
|
|
we nevertheless let the algorithms see for themselves that
|
|
the stream is progressive. This doesn't break anything,
|
|
and this way the full filter state gets updated at each frame.
|
|
|
|
See the individual function docs for details.
|
|
*/
|
|
IVTCCadenceDetectAlgoScores( p_filter );
|
|
IVTCCadenceDetectAlgoVektor( p_filter );
|
|
IVTCCadenceDetectFinalize( p_filter ); /* pick winner */
|
|
IVTCCadenceAnalyze( p_filter ); /* update filter state */
|
|
|
|
/* Now we can... */
|
|
bool b_have_output_frame = IVTCOutputOrDropFrame( p_filter, p_dst );
|
|
|
|
/* The next frame will get a custom timestamp, too. */
|
|
p_sys->i_frame_offset = CUSTOM_PTS;
|
|
|
|
if( b_have_output_frame )
|
|
return VLC_SUCCESS;
|
|
else
|
|
return VLC_EGENERIC; /* Signal the caller not to expect a frame */
|
|
}
|
|
else if( !p_prev && !p_curr ) /* first frame */
|
|
{
|
|
/* Render the first frame as-is, so that a picture appears immediately.
|
|
|
|
We will also do some init for the filter. This score will become
|
|
TPBP by the time the actual filter starts. Note that the sliding of
|
|
final scores only starts when the filter has started (third frame).
|
|
*/
|
|
int i_score = CalculateInterlaceScore( p_next, p_next );
|
|
p_ivtc->pi_scores[FIELD_PAIR_TNBN] = i_score;
|
|
p_ivtc->pi_final_scores[0] = i_score;
|
|
|
|
picture_Copy( p_dst, p_next );
|
|
return VLC_SUCCESS;
|
|
}
|
|
else /* second frame */
|
|
{
|
|
/* If the history sliding mechanism works correctly,
|
|
the only remaining possibility is that: */
|
|
assert( p_curr && !p_prev );
|
|
|
|
/* We need three frames for the cadence detector to work, so we just
|
|
do some init for the detector and pass the frame through.
|
|
Passthrough for second frame, too, works better than drop
|
|
for some still-image DVD menus.
|
|
|
|
Now that we have two frames, we can run a full IVTCLowLevelDetect().
|
|
|
|
The interlace scores from here will become TCBC, TCBP and TPBC
|
|
when the filter starts. The score for the current TCBC has already
|
|
been computed at the first frame, and slid into place at the start
|
|
of this frame (by IVTCFrameInit()).
|
|
*/
|
|
IVTCLowLevelDetect( p_filter );
|
|
|
|
/* Note that the sliding mechanism for output scores only starts
|
|
when the actual filter does.
|
|
*/
|
|
p_ivtc->pi_final_scores[1] = p_ivtc->pi_scores[FIELD_PAIR_TNBN];
|
|
|
|
/* At the next frame, the filter starts. The next frame will get
|
|
a custom timestamp. */
|
|
p_sys->i_frame_offset = CUSTOM_PTS;
|
|
|
|
picture_Copy( p_dst, p_next );
|
|
return VLC_SUCCESS;
|
|
}
|
|
}
|
|
|
|
/* See function doc in header. */
|
|
void IVTCClearState( filter_t *p_filter )
|
|
{
|
|
assert( p_filter != NULL );
|
|
|
|
filter_sys_t *p_sys = p_filter->p_sys;
|
|
ivtc_sys_t *p_ivtc = &p_sys->ivtc;
|
|
|
|
p_ivtc->i_cadence_pos = CADENCE_POS_INVALID;
|
|
p_ivtc->i_tfd = TFD_INVALID;
|
|
p_ivtc->b_sequence_valid = false;
|
|
p_ivtc->i_mode = IVTC_MODE_DETECTING;
|
|
p_ivtc->i_old_mode = IVTC_MODE_DETECTING;
|
|
for( int i = 0; i < IVTC_NUM_FIELD_PAIRS; i++ )
|
|
p_ivtc->pi_scores[i] = 0;
|
|
for( int i = 0; i < IVTC_DETECTION_HISTORY_SIZE; i++ )
|
|
{
|
|
p_ivtc->pi_cadence_pos_history[i] = CADENCE_POS_INVALID;
|
|
|
|
p_ivtc->pi_s_cadence_pos[i] = CADENCE_POS_INVALID;
|
|
p_ivtc->pb_s_reliable[i] = false;
|
|
p_ivtc->pi_v_cadence_pos[i] = CADENCE_POS_INVALID;
|
|
p_ivtc->pb_v_reliable[i] = false;
|
|
|
|
p_ivtc->pi_v_raw[i] = VEKTOR_CADENCE_POS_ALL;
|
|
|
|
p_ivtc->pi_top_rep[i] = 0;
|
|
p_ivtc->pi_bot_rep[i] = 0;
|
|
p_ivtc->pi_motion[i] = -1;
|
|
|
|
p_ivtc->pb_all_progressives[i] = false;
|
|
|
|
p_ivtc->pi_final_scores[i] = 0;
|
|
}
|
|
}
|