diff options
author | jbrjake <[email protected]> | 2007-04-16 16:58:57 +0000 |
---|---|---|
committer | jbrjake <[email protected]> | 2007-04-16 16:58:57 +0000 |
commit | 1b38e1511ba75646540f6d2b90b21713614de392 (patch) | |
tree | d82a8a528f2c1d6201167d26e5615b5a23b0d145 /libhb | |
parent | a715de4c2ff593e121fbfa394409eff4e493415b (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.c | 6 | ||||
-rw-r--r-- | libhb/encx264.c | 223 | ||||
-rw-r--r-- | libhb/encxvid.c | 7 | ||||
-rw-r--r-- | libhb/fifo.c | 2 | ||||
-rw-r--r-- | libhb/internal.h | 2 | ||||
-rw-r--r-- | libhb/muxmp4.c | 57 | ||||
-rw-r--r-- | libhb/render.c | 7 | ||||
-rw-r--r-- | libhb/sync.c | 14 | ||||
-rw-r--r-- | libhb/work.c | 2 |
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; } |