summaryrefslogtreecommitdiffstats
path: root/libhb
diff options
context:
space:
mode:
authorjbrjake <[email protected]>2007-04-16 16:58:57 +0000
committerjbrjake <[email protected]>2007-04-16 16:58:57 +0000
commit1b38e1511ba75646540f6d2b90b21713614de392 (patch)
treed82a8a528f2c1d6201167d26e5615b5a23b0d145 /libhb
parenta715de4c2ff593e121fbfa394409eff4e493415b (diff)
Much better B-frame muxing frame-reordering. This will preserve the sps/pps info, properly offset the first frame, and flush any remaining frames at the end of the encode.
Patch by: Nyx git-svn-id: svn://svn.handbrake.fr/HandBrake/trunk@513 b64f7644-9d1e-0410-96f1-a4d463321fa5
Diffstat (limited to 'libhb')
-rw-r--r--libhb/encavcodec.c6
-rw-r--r--libhb/encx264.c223
-rw-r--r--libhb/encxvid.c7
-rw-r--r--libhb/fifo.c2
-rw-r--r--libhb/internal.h2
-rw-r--r--libhb/muxmp4.c57
-rw-r--r--libhb/render.c7
-rw-r--r--libhb/sync.c14
-rw-r--r--libhb/work.c2
9 files changed, 213 insertions, 107 deletions
diff --git a/libhb/encavcodec.c b/libhb/encavcodec.c
index 6c6b742f6..701caf923 100644
--- a/libhb/encavcodec.c
+++ b/libhb/encavcodec.c
@@ -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;
diff --git a/libhb/encx264.c b/libhb/encx264.c
index a67225b01..e1961b5e9 100644
--- a/libhb/encx264.c
+++ b/libhb/encx264.c
@@ -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;
diff --git a/libhb/encxvid.c b/libhb/encxvid.c
index 6d869ae36..fbaa5a135 100644
--- a/libhb/encxvid.c
+++ b/libhb/encxvid.c
@@ -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;
diff --git a/libhb/fifo.c b/libhb/fifo.c
index 93e3e162e..323406538 100644
--- a/libhb/fifo.c
+++ b/libhb/fifo.c
@@ -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 )
diff --git a/libhb/internal.h b/libhb/internal.h
index bc6f5063c..efe721e12 100644
--- a/libhb/internal.h
+++ b/libhb/internal.h
@@ -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;
diff --git a/libhb/muxmp4.c b/libhb/muxmp4.c
index 8a1ee6b2d..e37da3b24 100644
--- a/libhb/muxmp4.c
+++ b/libhb/muxmp4.c
@@ -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
diff --git a/libhb/render.c b/libhb/render.c
index 782339116..b6f793fea 100644
--- a/libhb/render.c
+++ b/libhb/render.c
@@ -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 );
diff --git a/libhb/sync.c b/libhb/sync.c
index 79423b70c..fb5d704ac 100644
--- a/libhb/sync.c
+++ b/libhb/sync.c
@@ -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;
}
}
diff --git a/libhb/work.c b/libhb/work.c
index 1ab422a66..a4dcb4514 100644
--- a/libhb/work.c
+++ b/libhb/work.c
@@ -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;
}