Quoted from Mark's original suggestion, posted on the AVScience Home Theater Forum.

Here's a simplified pseudocode implementation of my 3:2 pulldown detection algorithm. You might even easily be able to implement this into DScaler. Almost all of us really don't care about 2:2 pulldown at all as I only see this happening only 0.1 to 0.5% of the time on NTSC television. Without further ado, here you go!


START PSUEDOCODE FOR AUTOMATIC 3:2 PULLDOWN DETECTION


set MAX_MISMATCH = 12
set MAX_VERIFY_CYCLES = 2
set MISMATCH_COUNT = 0
set MOVIE_FIELD_CYCLE = 0
set MOVIE_VERIFY_CYCLE = 0
set MODE = VIDEO
set FRAMES_PER_SECOND = 60
LOOP
  
  IF new field has now arrived on TV card
    //
    // Automatic detection code starts here
    // Compares a pair of even fields or a pair of odd fields.
    //
    GRAB new current field (add to queue of fields)
    COMPARE two fields: current field and two fields ago.
    IF fields DO NOT match THEN
    
        IF MISMATCH_COUNT does not exceed MAX_MISMATCH
            increment MISMATCH_COUNT by 1
            //
            // MISMATCH_COUNT is the number of consecutive pairs
            // of fields that do not match.
        ELSE
            set MODE = VIDEO
            set MOVIE_VERIFY_CYCLE = 0
            set MOVIE_FIELD_CYCLE = 0
            //
            // There has been no duplicate fields lately.
            // It's probably video source.
            //
            // MAX_MISMATCH should be a reasonably high value so
            // that we do not have an oversensitive hair-trigger
            // in switching to video source everytime there is
            // video noise or a single spurious field added/dropped
            // during a movie causing mis-synchronization problems. 
        ENDIF
    
    ELSE IF fields match THEN

        // It's either a stationary image OR a duplicate field in a movie
        IF MISMATCH_COUNT is 4
            //
            // 3:2 pulldown is a cycle of 5 fields where there is only
            // one duplicate field pair, and 4 mismatching pairs.
            // We need to continue detection for at least 2 cycles
            // to be very certain that it is actually 3:2 pulldown
            // This would mean a latency of 10 fields.
            //
            IF MOVIE_VERIFY_CYCLE equals or exceeds MAX_VERIFY_CYCLES
                // 
                // This executes regardless whether we've just entered or 
                // if we're *already* in 3:2 pulldown.  Either way, we are
                // currently now (re)synchronized to 3:2 pulldown and that
                // we've now detected the duplicate field.
                //
                set MODE = MOVIE
                set MOVIE_FIELD_CYCLE = 0
            ELSE
                increment MOVIE_VERIFY_CYCLE by 1
            ENDIF
            
        ELSE MISMATCH_COUNT not equal to 4
            //
            // If execution point hits here, it is probably just 
            // stationary video.  That would produce lots of duplicate
            // field pairs.  We can't determine whether it's video or
            // movie source.  Therefore, we want to keep doing the 
            // current algorithm from a prior detection.  Therefore, 
            // don't modify any variables EXCEPT the MISMATCH_COUNT 
            // variable which will be reset to 0 below.
            // Also, occasionally we'll hit here during synchronization
            // problems during a movie, such as a spurious field added
            // or dropped.  Reset MISMATCH_COUNT to 0 below and let
            // detection cycle happen again in order to re-synchronize.
            // Keep the MODE variable the way it currently is.
        ENDIF
        
        set MISMATCH_COUNT = 0
    ENDIF
    
    // Now, process the images depending on present mode
    IF MODE is MOVIE
        // You could replace below with movie source deinterlacer plugin
        //
        set FRAMES_PER_SECOND = 24
        BEGIN movie-source deinterlacer
            IF MOVIE_FIELD_CYCLE equals 2 or MOVIE_FIELD_CYCLE equals 4
                 Weave current field with previous field
                 Add resulting field to OUTPUT queue 
            ELSE
                 // Do nothing here.
                 // Do not add frame to OUTPUT queue.
                 // During MOVIE_FIELD_CYCLE 0 and 3, the current and
                 // previous fields come from different movie frames.
                 // During MOVIE_FIELD_CYCLE 1, the current and previous
                 // fields identical movie frame as MOVIE_FIELD_CYCLE 2.
                 // Therefore, we skip these cycle values.
            ENDIF
            
            // Looping counter that goes from 0, 1, 2, 3, 4 and back to 0
            increment MOVIE_FIELD_CYCLE by 1
            IF MOVIE_FIELD_CYCLE equals 5 THEN
                set MOVIE_FIELD_CYCLE = 0
            ENDIF
        END 
                
    ELSEIF MODE is VIDEO
        // You could replace below with video source deinterlacer plugin
        //
        set FRAMES_PER_SECOND = 60
        BEGIN video-source deinterlacer
            Apply video source deinterlace algorithm
            Add resulting field to OUTPUT queue 
        END
    ENDIF
  ENDIF
  /
  // You can put video output code here for the OUTPUT queue of frames.
  // You can optionally synchronize to the output refresh this way.
ENDLOOP

 

EXAMPLE SEQUENCE OF FRAMES FROM 3:2 PULLDOWN

Field 00: Movie frame 1
Field 01: Movie frame 1
Field 02: Movie frame 1
Field 03: Movie frame 2
Field 04: Movie frame 2
Field 05: Movie frame 3
Field 06: Movie frame 3
Field 07: Movie frame 3
Field 08: Movie frame 4
Field 09: Movie frame 4
Field 10: Movie frame 5
Field 11: Movie frame 5
Field 12: Movie frame 5
Field 13: Movie frame 6
Field 14: Movie frame 6

Field 02 and 00 is MATCH --- MOVIE_FIELD_CYCLE = 0
Field 03 and 01 is mismatch - MOVIE_FIELD_CYCLE = 1
Field 04 and 02 is mismatch - MOVIE_FIELD_CYCLE = 2
Field 05 and 03 is mismatch - MOVIE_FIELD_CYCLE = 3
Field 06 and 04 is mismatch - MOVIE_FIELD_CYCLE = 4
Field 07 and 05 is MATCH --- MOVIE_FIELD_CYCLE = 0
Field 08 and 06 is mismatch - MOVIE_FIELD_CYCLE = 1
Field 09 and 07 is mismatch - MOVIE_FIELD_CYCLE = 2
Field 10 and 08 is mismatch - MOVIE_FIELD_CYCLE = 3
Field 11 and 09 is mismatch - MOVIE_FIELD_CYCLE = 4
Field 12 and 10 is MATCH --- MOVIE_FIELD_CYCLE = 0
Field 13 and 11 is mismatch - MOVIE_FIELD_CYCLE = 1
Field 14 and 12 is mismatch - MOVIE_FIELD_CYCLE = 2

 

OPTIONAL: BETTER SYNCHRONIZATION TO MONITOR REFRESH

It is desirable to make the output refresh independent of the input refresh for better synchronization. During 3/2 pulldown material, it is very desirable to synchronize to the monitor refresh instead of the input TV signal refresh. During watching in normal TV viewers, 3/2 pulldown is doubled to 6/4 pulldown when the refresh rate is doubled to 120 Hz. It would be very nice to have something closer to 5/5 pulldown when watching movie material instead of 6/4 pulldown.

In the pseudocode above in the last section,
There are 24 frames per second in OUTPUT queue in MOVIE mode
There are 60 frames per second in OUTPUT queue in VIDEO mode

You can synchronize the OUTPUT queue of fields to the monitor refresh, based on the value in the FRAMES_PER_SECOND variable. By keeping a small output buffer of frames (of about 2 or so frames) you can keep the pipeline of output frames flowing smoothly and synchronized much better to the computer monitor.

Also, since the computer clock is accurate, this can help "stabilize" unstable NTSC refresh such as those from a VCR which can sometimes have slightly-varying refresh rates (fluctuating randomly as low as 59Hz all the way up to 61Hz although usually by smaller increments). This can interact badly with a consistent computer refresh. Regular TV sets have a very slight amount of "give" that allows it to compensate for very minor fluctuations in refresh rate, but computer monitors won't compensate.

It can also smooth out jerkiness problems caused by fluctuating CPU usage on a frame-by-frame basis, since deinterlacing one pair of frames might take longer than deinterlacing a different pair of frames (using certain video source deinterlacing algorithms).

There is also the problem of compensating for drift when the output framerate is decoupled from the refresh of the input signal. There are two good possible ways of getting around this drift problem:

(1) AVOID displaying the output frame if there is less than 2 frames in the OUTPUT queue, and ACCELERATE displaying of the next frame if the number of frames in the OUTPUT queue exceeds a predetermined value such as 4. That way, you always keep 2, 3 or 4 frames buffered at all times for maximum fluidity. (You could experiment, and set the minimum to 1 and maximum to 3, to find out which produces the best fluidity at the minimum possible buffering. Too much buffering will cause audio to be noticeably lagged behind the video)

(2) Synchronize to a clock that's generated from the TV tuner card, instead of using the computer's clock. For example, increment an integer clock counter by 1 everytime a field arrives from the TV card. This clock would be incremented about 59.94 times per second for most NTSC signals. Keep an eye on the computer's clock everytime you increment the integer clock by 60 - to calculate the exact amount of time it takes for 60 fields to hit the TV tuner card. Do this continously every second. Divide this value by 24 to compute the amount of time there should be between frames, during MOVIE mode, during the next second. (If you prefer, you could use 5 second cycles instead of 1 second cycles, or keep a "rolling average" every field based on the previous X number of fields).

 

APPENDIX: PSEUDOCODE WITHOUT COMMENTS

For determining the amount of "code bulk", this is how small the pseudocode is, when I remove my comments from the above pseudocode. Look at how small the pseudocode is - since you already have the routines written in DScaler, look at how simple it is to implement automatic 3:2 pulldown deinterlacing!


set MAX_MISMATCH = 12
set MAX_VERIFY_CYCLES = 2
set MISMATCH_COUNT = 0
set MOVIE_FIELD_CYCLE = 0
set MOVIE_VERIFY_CYCLE = 0
set MODE = VIDEO
set FRAMES_PER_SECOND = 60
LOOP
  IF new field has now arrived on TV card
    GRAB new current field (add to queue of fields)
    COMPARE two fields: current field and two fields ago.
    IF fields DO NOT match THEN
        IF MISMATCH_COUNT does not exceed MAX_MISMATCH
            increment MISMATCH_COUNT by 1
        ELSE
            set MODE = VIDEO
            set MOVIE_VERIFY_CYCLE = 0
            set MOVIE_FIELD_CYCLE = 0
        ENDIF
    ELSE IF fields match THEN
        IF MISMATCH_COUNT is 4
            IF MOVIE_VERIFY_CYCLE equals or exceeds MAX_VERIFY_CYCLES
                set MODE = MOVIE
                set MOVIE_FIELD_CYCLE = 0
            ELSE
                increment MOVIE_VERIFY_CYCLE by 1
            ENDIF
        ELSE MISMATCH_COUNT not equal to 4
        ENDIF
        set MISMATCH_COUNT = 0
    ENDIF
    
    IF MODE is MOVIE
        set FRAMES_PER_SECOND = 24
        BEGIN movie-source deinterlacer
            IF MOVIE_FIELD_CYCLE equals 2 or MOVIE_FIELD_CYCLE equals 4
                 Weave current field with previous field
                 Add resulting field to OUTPUT queue 
            ENDIF
            increment MOVIE_FIELD_CYCLE by 1
            IF MOVIE_FIELD_CYCLE equals 5 THEN
                set MOVIE_FIELD_CYCLE = 0
            ENDIF
        END 
    ELSEIF MODE is VIDEO
        set FRAMES_PER_SECOND = 60
        BEGIN video-source deinterlacer
            Apply video source deinterlace algorithm
            Add resulting field to OUTPUT queue 
        END
    ENDIF
  ENDIF
ENDLOOP

(c) Mark Rejhon 2000