]> granicus.if.org Git - handbrake/commitdiff
Much better B-frame muxing frame-reordering. This will preserve the sps/pps info...
authorjbrjake <jb.rubin@gmail.com>
Mon, 16 Apr 2007 16:58:57 +0000 (16:58 +0000)
committerjbrjake <jb.rubin@gmail.com>
Mon, 16 Apr 2007 16:58:57 +0000 (16:58 +0000)
Patch by: Nyx

git-svn-id: svn://svn.handbrake.fr/HandBrake/trunk@513 b64f7644-9d1e-0410-96f1-a4d463321fa5

libhb/encavcodec.c
libhb/encx264.c
libhb/encxvid.c
libhb/fifo.c
libhb/internal.h
libhb/muxmp4.c
libhb/render.c
libhb/sync.c
libhb/work.c

index 6c6b742f6611b2ba2774f43b8719bc443236d1fb..701caf9233bceb0c153374e08346f7853812adea 100644 (file)
@@ -175,6 +175,12 @@ int encavcodecWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
     AVFrame  * frame;
     hb_buffer_t * in = *buf_in, * buf;
 
+    if(!in->data)
+    {
+       *buf_out        = NULL;
+       return HB_WORK_DONE;
+    }
+
     frame              = avcodec_alloc_frame();
     frame->data[0]     = in->data;
     frame->data[1]     = frame->data[0] + job->width * job->height;
index a67225b0150a371991154025591819592e19701c..e1961b5e99f8ed005ce9ef8906ab46a5c77dcb64 100644 (file)
@@ -23,12 +23,22 @@ hb_work_object_t hb_encx264 =
     encx264Close
 };
 
+// 16 is probably overkill but it's also the maximum for h.264 reference frames
+#define MAX_INFLIGHT_FRAMES 16
+
 struct hb_work_private_s
 {
     hb_job_t       * job;
     x264_t         * x264;
     x264_picture_t   pic_in;
 
+    // Internal queue of DTS start/stop values.
+    int64_t        dts_start[MAX_INFLIGHT_FRAMES];
+    int64_t        dts_stop[MAX_INFLIGHT_FRAMES];
+
+    int64_t        dts_write_index;
+    int64_t        dts_read_index;
+
     char             filename[1024];
 };
 
@@ -215,6 +225,9 @@ int encx264Init( hb_work_object_t * w, hb_job_t * job )
     x264_picture_alloc( &pv->pic_in, X264_CSP_I420,
                         job->width, job->height );
 
+    pv->dts_write_index = 0;
+    pv->dts_read_index = 0;
+
     return 0;
 }
 
@@ -240,31 +253,57 @@ int encx264Work( hb_work_object_t * w, hb_buffer_t ** buf_in,
     x264_nal_t  * nal;
     int i;
 
-    /* XXX avoid this memcpy ? */
-    memcpy( pv->pic_in.img.plane[0], in->data, job->width * job->height );
-    if( job->grayscale )
-    {
-        /* XXX x264 has currently no option for grayscale encoding */
-        memset( pv->pic_in.img.plane[1], 0x80, job->width * job->height / 4 );
-        memset( pv->pic_in.img.plane[2], 0x80, job->width * job->height / 4 );
-    }
-    else
+    if (in->data)
     {
-        memcpy( pv->pic_in.img.plane[1], in->data + job->width * job->height,
-                job->width * job->height / 4 );
-        memcpy( pv->pic_in.img.plane[2], in->data + 5 * job->width *
-                job->height / 4, job->width * job->height / 4 );
-    }
+        /* XXX avoid this memcpy ? */
+        memcpy( pv->pic_in.img.plane[0], in->data, job->width * job->height );
+        if( job->grayscale )
+        {
+             /* XXX x264 has currently no option for grayscale encoding */
+             memset( pv->pic_in.img.plane[1], 0x80, job->width * job->height / 4 );
+             memset( pv->pic_in.img.plane[2], 0x80, job->width * job->height / 4 );
+        }
+        else
+        {
+             memcpy( pv->pic_in.img.plane[1], in->data + job->width * job->height,
+                      job->width * job->height / 4 );
+             memcpy( pv->pic_in.img.plane[2], in->data + 5 * job->width *
+                      job->height / 4, job->width * job->height / 4 );
+        }
 
-    pv->pic_in.i_type    = X264_TYPE_AUTO;
-    pv->pic_in.i_qpplus1 = 0;
+        pv->pic_in.i_type    = X264_TYPE_AUTO;
+        pv->pic_in.i_qpplus1 = 0;
 
-    /* Feed the input DTS to x264 so it can figure out proper output PTS */
-    pv->pic_in.i_pts = in->start;
+        // Remember current PTS value, use as DTS later
+        pv->dts_start[pv->dts_write_index & (MAX_INFLIGHT_FRAMES-1)] = in->start;
+        pv->dts_stop[pv->dts_write_index & (MAX_INFLIGHT_FRAMES-1)]  = in->stop;
+        pv->dts_write_index++;
 
-    x264_encoder_encode( pv->x264, &nal, &i_nal,
-                         &pv->pic_in, &pic_out );
+        /* Feed the input DTS to x264 so it can figure out proper output PTS */
+        pv->pic_in.i_pts = in->start;
 
+        x264_encoder_encode( pv->x264, &nal, &i_nal,
+                               &pv->pic_in, &pic_out );        
+    }
+    else
+    {
+        x264_encoder_encode( pv->x264, &nal, &i_nal,
+                             NULL, &pic_out );
+        /* No more delayed B frames */
+        if(i_nal == 0)
+        {
+            *buf_out        = NULL;
+            return HB_WORK_DONE;
+        }
+       else
+       {
+           /* Since we output at least one more frame, drop another empty one onto
+              our input fifo.  We'll keep doing this automatically until we stop
+              getting frames out of the encoder. */
+           hb_fifo_push(w->fifo_in, hb_buffer_init(0));
+       }
+    }
+  
     /* Should be way too large */
     buf        = hb_buffer_init( 3 * job->width * job->height / 2 );
     buf->size  = 0;
@@ -272,76 +311,104 @@ int encx264Work( hb_work_object_t * w, hb_buffer_t ** buf_in,
     buf->stop  = in->stop;
     buf->key   = 0;
 
-    for( i = 0; i < i_nal; i++ )
+    if (i_nal)
     {
-        int size, data;
-
-        data = buf->alloc - buf->size;
-        if( ( size = x264_nal_encode( buf->data + buf->size, &data,
+       /* Should be way too large */
+       buf        = hb_buffer_init( 3 * job->width * job->height / 2 );
+       buf->size  = 0;
+       buf->start = in->start;
+       buf->stop  = in->stop;
+       buf->key   = 0;
+       
+       int64_t dts_start, dts_stop;
+               
+       // Get next DTS value to use
+       dts_start = pv->dts_start[pv->dts_read_index & (MAX_INFLIGHT_FRAMES-1)];
+       dts_stop  = pv->dts_stop[pv->dts_read_index & (MAX_INFLIGHT_FRAMES-1)];
+       pv->dts_read_index++;
+    
+       for( i = 0; i < i_nal; i++ )
+       {
+           int size, data;
+
+           data = buf->alloc - buf->size;
+           if( ( size = x264_nal_encode( buf->data + buf->size, &data,
                                       1, &nal[i] ) ) < 1 )
-        {
-            continue;
-        }
+            {
+                continue;
+            }
 
-        if( job->mux & HB_MUX_AVI )
-        {
-            if( nal[i].i_ref_idc == NAL_PRIORITY_HIGHEST )
+            if( job->mux & HB_MUX_AVI )
             {
-                buf->key = 1;
+                if( nal[i].i_ref_idc == NAL_PRIORITY_HIGHEST )
+                {
+                    buf->key = 1;
+                }
+                buf->size += size;
+                continue;
             }
-            buf->size += size;
-            continue;
-        }
 
-        /* H.264 in .mp4 */
-        switch( buf->data[buf->size+4] & 0x1f )
-        {
-            case 0x7:
-            case 0x8:
-                /* SPS, PPS */
-                break;
-
-            default:
-                /* H.264 in mp4 (stolen from mp4creator) */
-                buf->data[buf->size+0] = ( ( size - 4 ) >> 24 ) & 0xFF;
-                buf->data[buf->size+1] = ( ( size - 4 ) >> 16 ) & 0xFF;
-                buf->data[buf->size+2] = ( ( size - 4 ) >>  8 ) & 0xFF;
-                buf->data[buf->size+3] = ( ( size - 4 ) >>  0 ) & 0xFF;
-
-        /* For IDR (key frames), buf->key = 1,
-           and the same for regular I-frames. */
-        if( (pic_out.i_type == X264_TYPE_IDR) || (pic_out.i_type == X264_TYPE_I) )
-        {
-        buf->key = 1;
-        }
-        /* For B-frames, buf->key = 2 */
-        else if( (pic_out.i_type == X264_TYPE_B) )
-        {
-        buf->key = 2;
-        }                
-        /* This is for b-pyramid, which has reference b-frames
-           However, it doesn't seem to ever be used...
-           They just show up as buf->key == 2 like
-           regular b-frames. */
-        else if( (pic_out.i_type == X264_TYPE_BREF) )
-        {
-            buf->key = 3;
-        }
-        /* For P-frames, buf->key = 0 */
-        else
-        {
-            buf->key = 0;
-        }
+            /* H.264 in .mp4 */
+            switch( buf->data[buf->size+4] & 0x1f )
+            {
+                case 0x7:
+                case 0x8:
+                    /* SPS, PPS */
+                    break;
+
+                default:
+                    /* H.264 in mp4 (stolen from mp4creator) */
+                    buf->data[buf->size+0] = ( ( size - 4 ) >> 24 ) & 0xFF;
+                    buf->data[buf->size+1] = ( ( size - 4 ) >> 16 ) & 0xFF;
+                    buf->data[buf->size+2] = ( ( size - 4 ) >>  8 ) & 0xFF;
+                    buf->data[buf->size+3] = ( ( size - 4 ) >>  0 ) & 0xFF;
+
+                /* For IDR (key frames), buf->key = 1,
+                   and the same for regular I-frames. */
+                if( (pic_out.i_type == X264_TYPE_IDR) || (pic_out.i_type == X264_TYPE_I) )
+                {
+                    buf->key = 1;
+                }
+                /* For B-frames, buf->key = 2 */
+                else if( (pic_out.i_type == X264_TYPE_B) )
+                {
+                    buf->key = 2;
+                }                
+                /* This is for b-pyramid, which has reference b-frames
+                   However, it doesn't seem to ever be used...
+                   They just show up as buf->key == 2 like
+                   regular b-frames. */
+                else if( (pic_out.i_type == X264_TYPE_BREF) )
+                {
+                    buf->key = 3;
+                }
+                /* For P-frames, buf->key = 0 */
+                else
+                {
+                    buf->key = 0;
+                }
 
-        /* Store the output presentation time stamp
-           from x264 for use by muxmp4 in off-setting
-           b-frames with the CTTS atom. */
-        buf->encodedPTS = pic_out.i_pts;
+                /* Store the output presentation time stamp
+                   from x264 for use by muxmp4 in off-setting
+                   b-frames with the CTTS atom. */
+                /* For now, just add 1000000 to the offset so that the
+                   value is pretty much guaranteed to be positive.  The
+                   muxing code will minimize the renderOffsets at the end. */
+        
+                buf->renderOffset = pic_out.i_pts - dts_start + 1000000;
+               
+                /* Send out the next dts values */
+                buf->start = dts_start;
+                buf->stop  = dts_stop;
 
                 buf->size += size;
+            }
         }
     }
 
+    else
+        buf = NULL;
+    
     *buf_out = buf;
 
     return HB_WORK_OK;
index 6d869ae36c23a1d5ca773d5b80f4ddb423446ec0..fbaa5a135755b40537706a0d4861869ce5e09d56 100644 (file)
@@ -149,6 +149,13 @@ int encxvidWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
     xvid_enc_frame_t frame;
     hb_buffer_t * in = *buf_in, * buf;
 
+    /* If this is the last empty frame, we're done */
+    if(!in->data)
+    {
+       *buf_out        = NULL;
+       return HB_WORK_DONE;
+    }
+
     /* Should be way too large */
     buf = hb_buffer_init( 3 * job->width * job->height / 2 );
     buf->start = in->start;
index 93e3e162ea4e880fd3e7947aaed4bd3e2aab8643..323406538d8f99a7632a2d876cd7a28233fccdc7 100644 (file)
@@ -22,6 +22,8 @@ hb_buffer_t * hb_buffer_init( int size )
 
     b->alloc = size;
     b->size  = size;
+    if (!size)
+        return b;
 #if defined( SYS_DARWIN ) || defined( SYS_FREEBSD )
     b->data  = malloc( size );
 #elif defined( SYS_CYGWIN )
index bc6f5063c12797d8592a33025b1efb3c56522e12..efe721e1221cc452b2352ff449e3179a36d26d76 100644 (file)
@@ -40,7 +40,7 @@ struct hb_buffer_s
     int           key;
 
     /* Holds the output PTS from x264, for use by b-frame offsets in muxmp4.c */
-    int64_t     encodedPTS;
+    int64_t     renderOffset;
 
     int           x;
     int           y;
index 8a1ee6b2da95c09bb37ea0d1efbd97302319b1bf..e37da3b24706367cc8c87f3a9fc8a295a1c5714c 100644 (file)
@@ -67,26 +67,13 @@ static int MP4Init( hb_mux_object_t * m )
         /* Stolen from mp4creator */
         MP4SetVideoProfileLevel( m->file, 0x7F );
 
-               if (job->areBframes >= 1)
-               {
-                       hb_log("muxmp4: Adjusting duration for B-frames");
-                   mux_data->track = MP4AddH264VideoTrack( m->file, job->arate,
-                           MP4_INVALID_DURATION+1, job->width, job->height,
-                           job->config.h264.sps[1], /* AVCProfileIndication */
-                           job->config.h264.sps[2], /* profile_compat */
-                           job->config.h264.sps[3], /* AVCLevelIndication */
-                           3 );      /* 4 bytes length before each NAL unit */                 
-               }
-               else
-               {
-                       hb_log("muxmp4: Using default duration as there are no B-frames");
                mux_data->track = MP4AddH264VideoTrack( m->file, job->arate,
                        MP4_INVALID_DURATION, job->width, job->height,
                        job->config.h264.sps[1], /* AVCProfileIndication */
                        job->config.h264.sps[2], /* profile_compat */
                        job->config.h264.sps[3], /* AVCLevelIndication */
                        3 );      /* 4 bytes length before each NAL unit */
-               }
+               
 
         MP4AddH264SequenceParameterSet( m->file, mux_data->track,
                 job->config.h264.sps, job->config.h264.sps_length );
@@ -241,24 +228,13 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data,
         duration = MP4_INVALID_DURATION;
     }
 
-    /* If for some reason the first frame muxmp4 gets isn't a key-frame,
-       drop frames until we get one. (Yes, very bad. Quick and dirty.)
-       This is for QuickTime--when it sees a non-IDR frame first, it
-       displays a white box instead of video until the second GOP.
-       Also, you've got to save the skipped duration to keep from
-       throwing off the offset values. */
-    if((mux_data->track == 1) && (thisSample == 0) && (buf->key != 1) && (job->vcodec == HB_VCODEC_X264))
-    {
-           initDelay +=duration;
-           return 0;
-    }
     /* When we do get the first keyframe, use its duration as the
        initial delay added to the frame order offset for b-frames.
        Because of b-pyramid, double this duration when there are
        b-pyramids, as denoted by job->areBframes equalling 2. */
     if ((mux_data->track == 1) && (thisSample == 0) && (buf->key == 1) && (job->vcodec == HB_VCODEC_X264))
     {
-        initDelay += duration * job->areBframes;
+        initDelay = buf->renderOffset;
         thisSample++;
     }
 
@@ -269,7 +245,7 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data,
        difference between the presentation time stamp x264 gives
        and the decoding time stamp from the buffer data. */
        MP4WriteSample( m->file, mux_data->track, buf->data, buf->size,
-            duration, ((mux_data->track != 1) || (job->areBframes==0) || (job->vcodec != HB_VCODEC_X264)) ? 0 : ( initDelay + ((buf->encodedPTS - buf->start) * job->arate / 90000)),
+            duration, ((mux_data->track != 1) || (job->areBframes==0) || (job->vcodec != HB_VCODEC_X264)) ? 0 : (  buf->renderOffset * job->arate / 90000),
             (buf->key == 1) );
                                 
     return 0;
@@ -282,6 +258,33 @@ static int MP4End( hb_mux_object_t * m )
     char filename[1024]; memset( filename, 0, 1024 );
 #endif
 
+    /* Walk the entire video sample table and find the minumum ctts value. */
+    {
+           MP4SampleId count = MP4GetTrackNumberOfSamples( m->file, 1);
+           MP4SampleId i;
+           MP4Duration renderingOffset = 2000000000, tmp;
+           
+           // Find the smallest rendering offset
+           for(i = 1; i <= count; i++)
+           {
+               tmp = MP4GetSampleRenderingOffset(m->file, 1, i);
+               if(tmp < renderingOffset)
+                   renderingOffset = tmp;
+           }
+           
+           // Adjust all ctts values down by renderingOffset
+           for(i = 1; i <= count; i++)
+           {
+               MP4SetSampleRenderingOffset(m->file,1,i,
+                   MP4GetSampleRenderingOffset(m->file,1,i) - renderingOffset);
+           }
+           
+           // Insert track edit to get A/V back in sync.  The edit amount is
+           // the rendering offset of the first sample.
+           MP4AddTrackEdit(m->file, 1, MP4_INVALID_EDIT_ID, MP4GetSampleRenderingOffset(m->file,1,1),
+               MP4GetTrackDuration(m->file, 1), 0);
+     }
+
     MP4Close( m->file );
 
 #if 0
index 782339116042b50cd498237101eca0ebc763addb..b6f793fea88df275ace1f77cb90504a27217820e 100644 (file)
@@ -103,6 +103,13 @@ int renderWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
     hb_title_t * title = job->title;
     hb_buffer_t * in = *buf_in, * buf;
 
+    if(!in->data)
+    {
+        /* If the input buffer is end of stream, send out an empty one to the next stage as well. */
+        *buf_out = hb_buffer_init(0);
+        return HB_WORK_OK;
+    }
+
     avpicture_fill( &pv->pic_raw, in->data, PIX_FMT_YUV420P,
                     title->width, title->height );
 
index 79423b70c560573325959940e76e3f4ea76100b1..fb5d704acd8f472ff196f3f0419eac1f9fd70297 100644 (file)
@@ -259,6 +259,14 @@ static int SyncVideo( hb_work_object_t * w )
         hb_log( "sync: got %lld frames, %lld expected",
                 pv->count_frames, pv->count_frames_max );
         pv->done = 1;
+        
+        hb_buffer_t * buf_tmp;
+
+       // Drop an empty buffer into our output to ensure that things
+       // get flushed all the way out.
+        buf_tmp = hb_buffer_init(0); // Empty end buffer
+        hb_fifo_push( job->fifo_sync, buf_tmp );
+        
         return HB_WORK_DONE;
     }
 
@@ -399,6 +407,12 @@ static int SyncVideo( hb_work_object_t * w )
         {
             hb_log( "sync: got %lld frames", pv->count_frames );
             pv->done = 1;
+            
+           // Drop an empty buffer into our output to ensure that things
+           // get flushed all the way out.
+           buf_tmp = hb_buffer_init(0); // Empty end buffer
+           hb_fifo_push( job->fifo_sync, buf_tmp );
+            
             break;
         }
     }
index 1ab422a667d4c51eb9c47e21763b3a5bc2d581a7..a4dcb45147a2d668df5196b1b9ef7d809f56a48a 100644 (file)
@@ -447,7 +447,7 @@ static void do_job( hb_job_t * job, int cpu_count )
         if( done &&
             !hb_fifo_size( job->fifo_sync ) &&
             !hb_fifo_size( job->fifo_render ) &&
-            hb_fifo_size( job->fifo_mpeg4 ) < 2 )
+            !hb_fifo_size( job->fifo_mpeg4 ) )
         {
             break;
         }