diff -Naur svn-0.8.5b2/libhb/encavcodec.c nyx-bframes/libhb/encavcodec.c --- svn-0.8.5b2/libhb/encavcodec.c 2007-04-14 16:31:22.000000000 -0400 +++ nyx-bframes/libhb/encavcodec.c 2007-04-14 17:22:44.000000000 -0400 @@ -175,6 +175,12 @@ 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 -Naur svn-0.8.5b2/libhb/encx264.c nyx-bframes/libhb/encx264.c --- svn-0.8.5b2/libhb/encx264.c 2007-04-14 16:31:22.000000000 -0400 +++ nyx-bframes/libhb/encx264.c 2007-04-14 17:02:11.000000000 -0400 @@ -23,12 +23,22 @@ 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; a + // 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 @@ 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 @@ 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 @@ buf->stop = in->stop; buf->key = 0; - for( i = 0; i < i_nal; i++ ) + if (i_nal) { - int 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, + 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 -Naur svn-0.8.5b2/libhb/encxvid.c nyx-bframes/libhb/encxvid.c --- svn-0.8.5b2/libhb/encxvid.c 2007-04-14 16:31:22.000000000 -0400 +++ nyx-bframes/libhb/encxvid.c 2007-04-14 17:23:10.000000000 -0400 @@ -149,6 +149,13 @@ 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 -Naur svn-0.8.5b2/libhb/fifo.c nyx-bframes/libhb/fifo.c --- svn-0.8.5b2/libhb/fifo.c 2007-04-14 16:31:22.000000000 -0400 +++ nyx-bframes/libhb/fifo.c 2007-04-14 17:28:05.000000000 -0400 @@ -22,6 +22,8 @@ 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 -Naur svn-0.8.5b2/libhb/internal.h nyx-bframes/libhb/internal.h --- svn-0.8.5b2/libhb/internal.h 2007-04-14 16:31:22.000000000 -0400 +++ nyx-bframes/libhb/internal.h 2007-04-14 17:26:39.000000000 -0400 @@ -40,7 +40,7 @@ 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 -Naur svn-0.8.5b2/libhb/muxmp4.c nyx-bframes/libhb/muxmp4.c --- svn-0.8.5b2/libhb/muxmp4.c 2007-04-14 16:31:22.000000000 -0400 +++ nyx-bframes/libhb/muxmp4.c 2007-04-14 21:30:45.000000000 -0400 @@ -67,26 +67,13 @@ /* 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 @@ 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 @@ 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 @@ 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 -Naur svn-0.8.5b2/libhb/render.c nyx-bframes/libhb/render.c --- svn-0.8.5b2/libhb/render.c 2007-04-14 16:31:22.000000000 -0400 +++ nyx-bframes/libhb/render.c 2007-04-14 17:26:19.000000000 -0400 @@ -103,6 +103,13 @@ 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 -Naur svn-0.8.5b2/libhb/sync.c nyx-bframes/libhb/sync.c --- svn-0.8.5b2/libhb/sync.c 2007-04-14 16:31:22.000000000 -0400 +++ nyx-bframes/libhb/sync.c 2007-04-14 17:25:21.000000000 -0400 @@ -57,8 +57,8 @@ * Local prototypes **********************************************************************/ static void InitAudio( hb_work_object_t * w, int i ); -static int SyncVideo( hb_work_object_t * w ); -static void SyncAudio( hb_work_object_t * w, int i ); +int SyncVideo( hb_work_object_t * w ); +void SyncAudio( hb_work_object_t * w, int i ); static int NeedSilence( hb_work_object_t * w, hb_audio_t * ); static void InsertSilence( hb_work_object_t * w, int i ); static void UpdateState( hb_work_object_t * w ); @@ -238,7 +238,7 @@ *********************************************************************** * **********************************************************************/ -static int SyncVideo( hb_work_object_t * w ) +int SyncVideo( hb_work_object_t * w ) { hb_work_private_t * pv = w->private_data; hb_buffer_t * cur, * next, * sub = NULL; @@ -259,6 +259,14 @@ 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 @@ { 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; } } @@ -411,7 +425,7 @@ *********************************************************************** * **********************************************************************/ -static void SyncAudio( hb_work_object_t * w, int i ) +void SyncAudio( hb_work_object_t * w, int i ) { hb_work_private_t * pv = w->private_data; hb_job_t * job; diff -Naur svn-0.8.5b2/libhb/work.c nyx-bframes/libhb/work.c --- svn-0.8.5b2/libhb/work.c 2007-04-14 16:31:22.000000000 -0400 +++ nyx-bframes/libhb/work.c 2007-04-14 17:27:40.000000000 -0400 @@ -447,7 +447,7 @@ 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; }