OSDN Git Service

Variable frame rate improvements.
authorjbrjake <jbrjake@b64f7644-9d1e-0410-96f1-a4d463321fa5>
Mon, 4 Feb 2008 16:53:27 +0000 (16:53 +0000)
committerjbrjake <jbrjake@b64f7644-9d1e-0410-96f1-a4d463321fa5>
Mon, 4 Feb 2008 16:53:27 +0000 (16:53 +0000)
- Makes it work better with the source's time stamps and avoids discontinuities, by removing its lazy assumption that all frames are 3003 ticks long.
- Handles more than 1 frame being dropped out of 5.
- Preserves chapter markers from being dropped along with frames they're attached to (thanks van!).
- Also, since VFR no longer requires input at 29.97 fps, removes the hard-coding of that frame rate.

git-svn-id: svn://localhost/HandBrake/trunk@1249 b64f7644-9d1e-0410-96f1-a4d463321fa5

libhb/render.c
libhb/work.c

index 3a0a025..24a3d50 100644 (file)
@@ -20,11 +20,15 @@ struct hb_work_private_s
     hb_buffer_t        * buf_scale;
     hb_fifo_t          * subtitle_queue;
     hb_fifo_t          * delay_queue;
-    int                  frames_to_extend;
     int                  dropped_frames;
     int                  extended_frames;
     uint64_t             last_start[4];
     uint64_t             last_stop[4];
+    uint64_t             lost_time[4];
+    uint64_t             total_lost_time;
+    uint64_t             total_gained_time;
+    int64_t              chapter_time;
+    int                  chapter_val;
 };
 
 int  renderInit( hb_work_object_t *, hb_job_t * );
@@ -233,7 +237,15 @@ int renderWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
     {
         hb_fifo_push( pv->subtitle_queue,  hb_buffer_init(0) );
     }
-    
+
+    /* If there's a chapter mark remember it in case we delay or drop its frame */
+    if( in->new_chap )
+    {
+        pv->chapter_time = in->start;
+        pv->chapter_val = in->new_chap;
+        in->new_chap = 0;
+    }
+
     /* Setup render buffer */
     hb_buffer_t * buf_render = hb_buffer_init( 3 * job->width * job->height / 2 );  
     
@@ -280,9 +292,22 @@ int renderWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
             {
                 if( job->vfr )
                 {
-                    pv->frames_to_extend += 4;
+                    /* We need to compensate for the time lost by dropping this frame.
+                       Spread its duration out in quarters, because usually dropped frames
+                       maintain a 1-out-of-5 pattern and this spreads it out amongst the remaining ones.
+                       Store these in the lost_time array, which has 4 slots in it.
+                       Because not every frame duration divides evenly by 4, and we can't lose the
+                       remainder, we have to go through an awkward process to preserve it in the 4th array index. */
+                    uint64_t temp_duration = buf_tmp_out->stop - buf_tmp_out->start;
+                    pv->lost_time[0] += (temp_duration / 4);
+                    pv->lost_time[1] += (temp_duration / 4);
+                    pv->lost_time[2] += (temp_duration / 4);
+                    pv->lost_time[3] += ( temp_duration - (temp_duration / 4) - (temp_duration / 4) - (temp_duration / 4) );
+                    
+                    pv->total_lost_time += temp_duration;
                     pv->dropped_frames++;
-                    hb_fifo_get( pv->subtitle_queue );
+                    
+                    hb_fifo_get( pv->subtitle_queue );                    
                     buf_tmp_in = NULL;
                 }
                 else
@@ -304,8 +329,11 @@ int renderWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
             pv->last_start[i] = pv->last_start[i-1];
             pv->last_stop[i] = pv->last_stop[i-1];
         }
-        pv->last_start[0] = in->start;
-        pv->last_stop[0] = in->stop;
+        
+        /* In order to make sure we have continuous time stamps, store
+           the current frame's duration as starting when the last one stopped. */
+        pv->last_start[0] = pv->last_stop[1];
+        pv->last_stop[0] = pv->last_start[0] + (in->stop - in->start);
     }
     
     /* Apply subtitles */
@@ -385,13 +413,17 @@ int renderWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
 
     if( *buf_out )
     {
-        if( pv->frames_to_extend )
+        /* The current frame exists. That means it hasn't been dropped by a filter.
+           Make it accessible as ivtc_buffer so we can edit its duration if needed. */
+        ivtc_buffer = *buf_out;
+
+        if( pv->lost_time[3] > 0 )
         {
             /*
-             * A frame's been dropped by VFR detelecine.
+             * A frame's been dropped earlier by VFR detelecine.
              * Gotta make up the lost time. This will also
-             * slow down the video to 23.976fps.
-             * The dropped frame ran for 3003 ticks, so
+             * slow down the video.
+             * The dropped frame's has to be accounted for, so
              * divvy it up amongst the 4 frames left behind.
              * This is what the delay_queue is for;
              * telecined sequences start 2 frames before
@@ -399,29 +431,62 @@ int renderWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
              * ones you need a 2 frame delay between
              * reading input and writing output.
              */
-            ivtc_buffer = *buf_out;
+                                   
+            /* We want to extend the outputted frame's duration by the value
+              stored in the 4th slot of the lost_time array. Because we need
+              to adjust all the values in the array so they're contiguous,
+              extend the duration inside the array first, before applying
+              it to the current frame buffer. */
+            pv->last_stop[3] += pv->lost_time[3];
             
-            /* The 4th cached frame will be the to use. */
-            ivtc_buffer->start = pv->last_start[3];
-            ivtc_buffer->stop = pv->last_stop[3];
-
-            if (pv->frames_to_extend % 4)
-                ivtc_buffer->stop += 751;
-            else
-                ivtc_buffer->stop += 750;
+            /* Log how much time has been added back in to the video. */
+            pv->total_gained_time += pv->lost_time[3];
             
-            /* Set the 3rd cached frame to start when this one stops,
-               and to stop 3003 ticks later -- a normal 29.97fps
-               length frame. If it needs to be extended as well to
-               make up lost time, it'll be handled on the next
-               loop through the renderer.                            */
-            int temp_duration = pv->last_stop[2] - pv->last_start[2];
-            pv->last_start[2] = ivtc_buffer->stop;
-            pv->last_stop[2] = ivtc_buffer->stop + temp_duration;
+            /* We've pulled the 4th value from the lost_time array
+               and added it to the last_stop array's 4th slot. Now, rotate the
+                lost_time array so the 4th slot now holds the 3rd's value, and
+               so on down the line, and set the 0 index to a value of 0. */
+            int i;
+            for( i=2; i >=  0; i--)
+            {
+                pv->lost_time[i+1] = pv->lost_time[i];
+            }
+            pv->lost_time[0] = 0;
             
-            pv->frames_to_extend--;
+            /* Log how many frames have had their durations extended. */
             pv->extended_frames++;
         }
+        
+        /* We can't use the given time stamps. Previous frames
+           might already have been extended, throwing off the
+           raw values fed to render.c. Instead, their
+           stop and start times are stored in arrays.
+           The 4th cached frame will be the to use.
+           If it needed its duration extended to make up
+           lost time, it will have happened above. */
+        ivtc_buffer->start = pv->last_start[3];
+        ivtc_buffer->stop = pv->last_stop[3];
+        
+        /* Set the 3rd cached frame to start when this one stops,
+           and so on down the line. If any of them need to be
+           extended as well to make up lost time, it'll be handled
+           on the next loop through the renderer.  */
+        int i;   
+        for (i = 2; i >= 0; i--)
+        {
+            int temp_duration = pv->last_stop[i] - pv->last_start[i];
+            pv->last_start[i] = pv->last_stop[i+1];
+            pv->last_stop[i] = pv->last_start[i] + temp_duration;
+        }
+
+        /* If we have a pending chapter mark and this frame is at
+           or after the time of the mark, mark this frame & clear
+           our pending mark. */
+        if( pv->chapter_time && pv->chapter_time <= ivtc_buffer->start )
+        {
+            ivtc_buffer->new_chap = pv->chapter_val;
+            pv->chapter_time = 0;
+        }
 
     }
 
@@ -431,11 +496,12 @@ int renderWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
 void renderClose( hb_work_object_t * w )
 {
     hb_work_private_t * pv = w->private_data;   
-        
-    hb_log("render: dropped frames: %i (%i ticks)", pv->dropped_frames, (pv->dropped_frames * 3003) );
-    hb_log("render: extended frames: %i (%i ticks)", pv->extended_frames, ( ( pv->extended_frames / 4 ) * 3003 ) );
-    hb_log("render: Lost time: %i frames (%i ticks)", (pv->dropped_frames * 4) - (pv->extended_frames), (pv->dropped_frames * 3003) - ( ( pv->extended_frames / 4 ) * 3003 ) );
 
+    hb_log("render: lost time: %lld (%i frames)", pv->total_lost_time, pv->dropped_frames);
+    hb_log("render: gained time: %lld (%i frames) (%lld not accounted for)", pv->total_gained_time, pv->extended_frames, pv->total_lost_time - pv->total_gained_time);
+    if (pv->dropped_frames)
+        hb_log("render: average dropped frame duration: %lld", (pv->total_lost_time / pv->dropped_frames) );
+    
     /* Cleanup subtitle queue */
     if( pv->subtitle_queue )
     {
@@ -476,12 +542,23 @@ int renderInit( hb_work_object_t * w, hb_job_t * job )
     /* Setup FIFO queue for subtitle cache */
     pv->subtitle_queue = hb_fifo_init( 8 );    
     pv->delay_queue = hb_fifo_init( 8 );
-    pv->frames_to_extend = 0;
+
+    /* VFR IVTC needs a bunch of time-keeping variables to track
+      how many frames are dropped, how many are extended, what the
+      last 4 start and stop times were (so they can be modified),
+      how much time has been lost and gained overall, how much time
+      the latest 4 frames should be extended by, and where chapter
+      markers are (so they can be saved if their frames are dropped.) */
     pv->dropped_frames = 0;
     pv->extended_frames = 0;
     pv->last_start[0] = 0;
     pv->last_stop[0] = 0;
-    
+    pv->total_lost_time = 0;
+    pv->total_gained_time = 0;
+    pv->lost_time[0] = 0; pv->lost_time[1] = 0; pv->lost_time[2] = 0; pv->lost_time[3] = 0;
+    pv->chapter_time = 0;
+    pv->chapter_val  = 0;
+
     /* Setup filters */
     /* TODO: Move to work.c? */
     if( job->filters )
index 2f7bed9..3aa534f 100644 (file)
@@ -164,8 +164,6 @@ static void do_job( hb_job_t * job, int cpu_count )
     
     if ( job->vfr )
     {
-        job->vrate_base = 900900;
-
         int detelecine_present = 0;        
         if ( job->filters )
         {
@@ -190,9 +188,9 @@ static void do_job( hb_job_t * job, int cpu_count )
             
             /* Add it to the list. */
             hb_list_add( job->filters, filter );
+            
+            hb_log("work: VFR mode -- adding detelecine filter");
         }
-        
-        hb_log("work: VFR mode -- Switching FPS to 29.97 and detelecining.");
     }
     
     if( job->filters )
@@ -208,17 +206,25 @@ static void do_job( hb_job_t * job, int cpu_count )
         }
     }
     
+    if( job->vfr)
+    {
+        hb_log( " + video frame rate: variable (detected %.3f fps)", (float) job->vrate /
+            (float) job->vrate_base );
+    }
+    else
+    {
+        hb_log( " + video frame rate: %.3f fps", (float) job->vrate / (float) job->vrate_base);
+    }
+    
     if( job->vquality >= 0.0 && job->vquality <= 1.0 )
     {
-        hb_log( " + %.3f fps, video quality %.2f", (float) job->vrate /
-                (float) job->vrate_base, job->vquality );
+        hb_log( " + video quality %.2f", job->vquality );
     }
     else
     {
-        hb_log( " + %.3f fps, video bitrate %d kbps, pass %d",
-                (float) job->vrate / (float) job->vrate_base,
-                job->vbitrate, job->pass );
+        hb_log( " + video bitrate %d kbps, pass %d", job->vbitrate, job->pass );
     }
+
        hb_log (" + PixelRatio: %d, width:%d, height: %d",job->pixel_ratio,job->width, job->height);
     job->fifo_mpeg2  = hb_fifo_init( 2048 );
     job->fifo_raw    = hb_fifo_init( FIFO_CPU_MULT * cpu_count );