X-Git-Url: http://git.osdn.jp/view?a=blobdiff_plain;f=libhb%2Fsync.c;h=1ceeee9c39e72a2d97721d6391882fc3a3c677b0;hb=48ae0d0afa95433d7d90a1b55402623ec78ae07e;hp=61e567bbc80546283937591ed1a1de0f0d5a13e7;hpb=84a869069dd966bbbc62fa3763bf5aeb9076d812;p=handbrake-jp%2Fhandbrake-jp-git.git diff --git a/libhb/sync.c b/libhb/sync.c index 61e567bb..1ceeee9c 100644 --- a/libhb/sync.c +++ b/libhb/sync.c @@ -1,13 +1,13 @@ /* $Id: sync.c,v 1.38 2005/04/14 21:57:58 titer Exp $ This file is part of the HandBrake source code. - Homepage: . + Homepage: . It may be used under the terms of the GNU General Public License. */ #include "hb.h" - +#include "hbffmpeg.h" +#include #include "samplerate.h" -#include "ffmpeg/avcodec.h" #ifdef INT64_MIN #undef INT64_MIN /* Because it isn't defined correctly in Zeta */ @@ -19,8 +19,12 @@ typedef struct { hb_audio_t * audio; - int64_t count_frames; - + + int64_t next_start; /* start time of next output frame */ + int64_t next_pts; /* start time of next input frame */ + int64_t first_drop; /* PTS of first 'went backwards' frame dropped */ + int drop_count; /* count of 'time went backwards' drops */ + /* Raw */ SRC_STATE * state; SRC_DATA data; @@ -31,23 +35,32 @@ typedef struct } hb_sync_audio_t; -struct hb_work_object_s +struct hb_work_private_s { - HB_WORK_COMMON; - hb_job_t * job; - int done; - + int busy; // bitmask with one bit for each active input + // (bit 0 = video; 1 = audio 0, 2 = audio 1, ... + // appropriate bit is cleared when input gets + // an eof buf. syncWork returns done when all + // bits are clear. /* Video */ - hb_subtitle_t * subtitle; int64_t pts_offset; - int64_t pts_offset_old; - int64_t count_frames; - int64_t count_frames_max; + int64_t next_start; /* start time of next output frame */ + int64_t next_pts; /* start time of next input frame */ + int64_t first_drop; /* PTS of first 'went backwards' frame dropped */ + int drop_count; /* count of 'time went backwards' drops */ + int drops; /* frames dropped to make a cbr video stream */ + int dups; /* frames duplicated to make a cbr video stream */ + int video_sequence; + int count_frames; + int count_frames_max; + int chap_mark; /* to propagate chapter mark across a drop */ hb_buffer_t * cur; /* The next picture to process */ /* Audio */ hb_sync_audio_t sync_audio[8]; + int64_t audio_passthru_slip; + int64_t video_pts_slip; /* Statistics */ uint64_t st_counts[4]; @@ -59,13 +72,9 @@ struct hb_work_object_s * Local prototypes **********************************************************************/ static void InitAudio( hb_work_object_t * w, int i ); -static void Close( hb_work_object_t ** _w ); -static int Work( hb_work_object_t * w, hb_buffer_t ** unused1, - hb_buffer_t ** unused2 ); -static int SyncVideo( hb_work_object_t * w ); +static void SyncVideo( hb_work_object_t * w ); static 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 InsertSilence( hb_work_object_t * w, int i, int64_t d ); static void UpdateState( hb_work_object_t * w ); /*********************************************************************** @@ -73,59 +82,169 @@ static void UpdateState( hb_work_object_t * w ); *********************************************************************** * Initialize the work object **********************************************************************/ -hb_work_object_t * hb_work_sync_init( hb_job_t * job ) +int syncInit( hb_work_object_t * w, hb_job_t * job ) { - hb_work_object_t * w; hb_title_t * title = job->title; hb_chapter_t * chapter; int i; uint64_t duration; + hb_work_private_t * pv; - w = calloc( sizeof( hb_work_object_t ), 1 ); - w->name = strdup( "Synchronization" ); - w->work = Work; - w->close = Close; + pv = calloc( 1, sizeof( hb_work_private_t ) ); + w->private_data = pv; - w->job = job; - w->pts_offset = INT64_MIN; - w->pts_offset_old = INT64_MIN; - w->count_frames = 0; + pv->job = job; + pv->pts_offset = INT64_MIN; - /* Calculate how many video frames we are expecting */ - duration = 0; - for( i = job->chapter_start; i <= job->chapter_end; i++ ) + if( job->pass == 2 ) + { + /* We already have an accurate frame count from pass 1 */ + hb_interjob_t * interjob = hb_interjob_get( job->h ); + pv->count_frames_max = interjob->frame_count; + } + else { - chapter = hb_list_item( title->list_chapter, i - 1 ); - duration += chapter->duration; - } - duration += 90000; - /* 1 second safety so we're sure we won't miss anything */ - w->count_frames_max = duration * job->vrate / job->vrate_base / 90000; + /* Calculate how many video frames we are expecting */ + if ( job->pts_to_stop ) + { + duration = job->pts_to_stop + 90000; + } + else if( job->frame_to_stop ) + { + /* Set the duration to a rough estimate */ + duration = ( job->frame_to_stop / ( title->rate / title->rate_base ) ) * 90000; + } + else + { + duration = 0; + for( i = job->chapter_start; i <= job->chapter_end; i++ ) + { + chapter = hb_list_item( title->list_chapter, i - 1 ); + duration += chapter->duration; + } + duration += 90000; + /* 1 second safety so we're sure we won't miss anything */ + } + pv->count_frames_max = duration * title->rate / title->rate_base / 90000; + } - hb_log( "sync: expecting %lld video frames", w->count_frames_max ); + hb_log( "sync: expecting %d video frames", pv->count_frames_max ); + pv->busy |= 1; /* Initialize libsamplerate for every audio track we have */ + if ( ! job->indepth_scan ) + { + for( i = 0; i < hb_list_count( title->list_audio ) && i < 8; i++ ) + { + pv->busy |= ( 1 << (i + 1) ); + InitAudio( w, i ); + } + } + + return 0; +} + +/*********************************************************************** + * Close + *********************************************************************** + * + **********************************************************************/ +void syncClose( hb_work_object_t * w ) +{ + hb_work_private_t * pv = w->private_data; + hb_job_t * job = pv->job; + hb_title_t * title = job->title; + hb_audio_t * audio = NULL; + int i; + + if( pv->cur ) + { + hb_buffer_close( &pv->cur ); + } + + hb_log( "sync: got %d frames, %d expected", + pv->count_frames, pv->count_frames_max ); + + /* save data for second pass */ + if( job->pass == 1 ) + { + /* Preserve frame count for better accuracy in pass 2 */ + hb_interjob_t * interjob = hb_interjob_get( job->h ); + interjob->frame_count = pv->count_frames; + interjob->last_job = job->sequence_id; + interjob->total_time = pv->next_start; + } + + if (pv->drops || pv->dups ) + { + hb_log( "sync: %d frames dropped, %d duplicated", pv->drops, pv->dups ); + } + for( i = 0; i < hb_list_count( title->list_audio ); i++ ) { - InitAudio( w, i ); + audio = hb_list_item( title->list_audio, i ); + if( audio->config.out.codec == HB_ACODEC_AC3 ) + { + free( pv->sync_audio[i].ac3_buf ); + } + else + { + src_delete( pv->sync_audio[i].state ); + } } - /* Get subtitle info, if any */ - w->subtitle = hb_list_item( title->list_subtitle, 0 ); + free( pv ); + w->private_data = NULL; +} + +/*********************************************************************** + * Work + *********************************************************************** + * The root routine of this work abject + * + * The way this works is that we are syncing the audio to the PTS of + * the last video that we processed. That's why we skip the audio sync + * if we haven't got a valid PTS from the video yet. + * + **********************************************************************/ +int syncWork( hb_work_object_t * w, hb_buffer_t ** unused1, + hb_buffer_t ** unused2 ) +{ + hb_work_private_t * pv = w->private_data; + int i; + + if ( pv->busy & 1 ) + SyncVideo( w ); - return w; + for( i = 0; i < hb_list_count( pv->job->title->list_audio ); i++ ) + { + if ( pv->busy & ( 1 << (i + 1) ) ) + SyncAudio( w, i ); + } + + return ( pv->busy? HB_WORK_OK : HB_WORK_DONE ); } +hb_work_object_t hb_sync = +{ + WORK_SYNC, + "Synchronization", + syncInit, + syncWork, + syncClose +}; + static void InitAudio( hb_work_object_t * w, int i ) { - hb_job_t * job = w->job; + hb_work_private_t * pv = w->private_data; + hb_job_t * job = pv->job; hb_title_t * title = job->title; hb_sync_audio_t * sync; - sync = &w->sync_audio[i]; + sync = &pv->sync_audio[i]; sync->audio = hb_list_item( title->list_audio, i ); - if( job->acodec & HB_ACODEC_AC3 ) + if( sync->audio->config.out.codec == HB_ACODEC_AC3 ) { /* Have a silent AC-3 frame ready in case we have to fill a gap */ @@ -136,11 +255,11 @@ static void InitAudio( hb_work_object_t * w, int i ) codec = avcodec_find_encoder( CODEC_ID_AC3 ); c = avcodec_alloc_context(); - c->bit_rate = sync->audio->bitrate; - c->sample_rate = sync->audio->rate; - c->channels = sync->audio->channels; + c->bit_rate = sync->audio->config.in.bitrate; + c->sample_rate = sync->audio->config.in.samplerate; + c->channels = HB_INPUT_CH_LAYOUT_GET_DISCRETE_COUNT( sync->audio->config.in.channel_layout ); - if( avcodec_open( c, codec ) < 0 ) + if( hb_avcodec_open( c, codec ) < 0 ) { hb_log( "sync: avcodec_open failed" ); return; @@ -148,8 +267,8 @@ static void InitAudio( hb_work_object_t * w, int i ) zeros = calloc( AC3_SAMPLES_PER_FRAME * sizeof( short ) * c->channels, 1 ); - sync->ac3_size = sync->audio->bitrate * AC3_SAMPLES_PER_FRAME / - sync->audio->rate / 8; + sync->ac3_size = sync->audio->config.in.bitrate * AC3_SAMPLES_PER_FRAME / + sync->audio->config.in.samplerate / 8; sync->ac3_buf = malloc( sync->ac3_size ); if( avcodec_encode_audio( c, sync->ac3_buf, sync->ac3_size, @@ -157,229 +276,458 @@ static void InitAudio( hb_work_object_t * w, int i ) { hb_log( "sync: avcodec_encode_audio failed" ); } - + free( zeros ); - avcodec_close( c ); + hb_avcodec_close( c ); av_free( c ); } else { /* Initialize libsamplerate */ int error; - sync->state = src_new( SRC_LINEAR, 2, &error ); + sync->state = src_new( SRC_SINC_MEDIUM_QUALITY, HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT(sync->audio->config.out.mixdown), &error ); sync->data.end_of_input = 0; } } /*********************************************************************** - * Close + * SyncVideo *********************************************************************** * **********************************************************************/ -static void Close( hb_work_object_t ** _w ) +static void SyncVideo( hb_work_object_t * w ) { - hb_work_object_t * w = *_w; - hb_job_t * job = w->job; - hb_title_t * title = job->title; - + hb_work_private_t * pv = w->private_data; + hb_buffer_t * cur, * next, * sub = NULL; + hb_job_t * job = pv->job; + hb_subtitle_t *subtitle; int i; + int64_t pts_skip; - if( w->cur ) hb_buffer_close( &w->cur ); - - for( i = 0; i < hb_list_count( title->list_audio ); i++ ) + if( !pv->cur && !( pv->cur = hb_fifo_get( job->fifo_raw ) ) ) { - if( job->acodec & HB_ACODEC_AC3 ) - { - free( w->sync_audio[i].ac3_buf ); - } - else - { - src_delete( w->sync_audio[i].state ); - } + /* We haven't even got a frame yet */ + return; } - - free( w->name ); - free( w ); - *_w = NULL; -} - -/*********************************************************************** - * Work - *********************************************************************** - * The root routine of this work abject - **********************************************************************/ -static int Work( hb_work_object_t * w, hb_buffer_t ** unused1, - hb_buffer_t ** unused2 ) -{ - int i; - - /* If we ever got a video frame, handle audio now */ - if( w->pts_offset != INT64_MIN ) + cur = pv->cur; + pts_skip = 0; + if( cur->size == 0 ) { - for( i = 0; i < hb_list_count( w->job->title->list_audio ); i++ ) + /* we got an end-of-stream. Feed it downstream & signal that we're done. */ + hb_fifo_push( job->fifo_sync, hb_buffer_init( 0 ) ); + + /* + * Push through any subtitle EOFs in case they were not synced through. + */ + for( i = 0; i < hb_list_count( job->list_subtitle ); i++) { - SyncAudio( w, i ); + subtitle = hb_list_item( job->list_subtitle, i ); + if( subtitle->config.dest == PASSTHRUSUB ) + { + hb_fifo_push( subtitle->fifo_out, hb_buffer_init( 0 ) ); + } } - } - - /* Handle video */ - return SyncVideo( w ); -} - -#define PTS_DISCONTINUITY_TOLERANCE 90000 - -/*********************************************************************** - * SyncVideo - *********************************************************************** - * - **********************************************************************/ -static int SyncVideo( hb_work_object_t * w ) -{ - hb_buffer_t * cur, * next, * sub = NULL; - hb_job_t * job = w->job; - int64_t pts_expected; - - if( w->done ) - { - return HB_WORK_DONE; - } - if( hb_thread_has_exited( job->reader ) && - !hb_fifo_size( job->fifo_mpeg2 ) && - !hb_fifo_size( job->fifo_raw ) ) - { - /* All video data has been processed already, we won't get - more */ - hb_log( "sync: got %lld frames, %lld expected", - w->count_frames, w->count_frames_max ); - w->done = 1; - return HB_WORK_DONE; + pv->busy &=~ 1; + return; } - if( !w->cur && !( w->cur = hb_fifo_get( job->fifo_raw ) ) ) - { - /* We haven't even got a frame yet */ - return HB_WORK_OK; - } - cur = w->cur; - /* At this point we have a frame to process. Let's check 1) if we will be able to push into the fifo ahead 2) if the next frame is there already, since we need it to - know whether we'll have to repeat the current frame or not */ + compute the duration of the current frame*/ while( !hb_fifo_is_full( job->fifo_sync ) && ( next = hb_fifo_see( job->fifo_raw ) ) ) { hb_buffer_t * buf_tmp; - if( w->pts_offset == INT64_MIN ) + if( next->size == 0 ) { - /* This is our first frame */ - hb_log( "sync: first pts is %lld", cur->start ); - w->pts_offset = cur->start; - } - - /* Check for PTS jumps over 0.5 second */ - if( next->start < cur->start - PTS_DISCONTINUITY_TOLERANCE || - next->start > cur->start + PTS_DISCONTINUITY_TOLERANCE ) - { - hb_log( "PTS discontinuity (%lld, %lld)", - cur->start, next->start ); - - /* Trash all subtitles */ - if( w->subtitle ) + /* we got an end-of-stream. Feed it downstream & signal that + * we're done. Note that this means we drop the final frame of + * video (we don't know its duration). On DVDs the final frame + * is often strange and dropping it seems to be a good idea. */ + hb_fifo_push( job->fifo_sync, hb_buffer_init( 0 ) ); + + /* + * Push through any subtitle EOFs in case they were not synced through. + */ + for( i = 0; i < hb_list_count( job->list_subtitle ); i++) { - while( ( sub = hb_fifo_get( w->subtitle->fifo_raw ) ) ) + subtitle = hb_list_item( job->list_subtitle, i ); + if( subtitle->config.dest == PASSTHRUSUB ) { - hb_buffer_close( &sub ); + hb_fifo_push( subtitle->fifo_out, hb_buffer_init( 0 ) ); } } - - /* Trash current picture */ - hb_buffer_close( &cur ); - w->cur = cur = hb_fifo_get( job->fifo_raw ); - - /* Calculate new offset */ - w->pts_offset_old = w->pts_offset; - w->pts_offset = cur->start - - w->count_frames * w->job->vrate_base / 300; - continue; + pv->busy &=~ 1; + return; } - - /* Look for a subtitle for this frame */ - if( w->subtitle ) + if( pv->pts_offset == INT64_MIN ) { - hb_buffer_t * sub2; - while( ( sub = hb_fifo_see( w->subtitle->fifo_raw ) ) ) + /* This is our first frame */ + pv->pts_offset = 0; + if ( cur->start != 0 ) { - /* If two subtitles overlap, make the first one stop - when the second one starts */ - sub2 = hb_fifo_see2( w->subtitle->fifo_raw ); - if( sub2 && sub->stop > sub2->start ) - sub->stop = sub2->start; - - if( sub->stop > cur->start ) - break; - - /* The subtitle is older than this picture, trash it */ - sub = hb_fifo_get( w->subtitle->fifo_raw ); - hb_buffer_close( &sub ); + /* + * The first pts from a dvd should always be zero but + * can be non-zero with a transport or program stream since + * we're not guaranteed to start on an IDR frame. If we get + * a non-zero initial PTS extend its duration so it behaves + * as if it started at zero so that our audio timing will + * be in sync. + */ + hb_log( "sync: first pts is %"PRId64, cur->start ); + cur->start = 0; } + } - /* If we have subtitles left in the fifo, check if we should - apply the first one to the current frame or if we should - keep it for later */ - if( sub && sub->start > cur->start ) + /* + * since the first frame is always 0 and the upstream reader code + * is taking care of adjusting for pts discontinuities, we just have + * to deal with the next frame's start being in the past. This can + * happen when the PTS is adjusted after data loss but video frame + * reordering causes some frames with the old clock to appear after + * the clock change. This creates frames that overlap in time which + * looks to us like time going backward. The downstream muxing code + * can deal with overlaps of up to a frame time but anything larger + * we handle by dropping frames here. + */ + if ( (int64_t)( next->start - pv->video_pts_slip - cur->start ) <= 0 ) + { + if ( pv->first_drop == 0 ) + { + pv->first_drop = next->start; + } + ++pv->drop_count; + if (next->start - cur->start > 0) + { + pts_skip += next->start - cur->start; + pv->video_pts_slip -= next->start - cur->start; + } + buf_tmp = hb_fifo_get( job->fifo_raw ); + if ( buf_tmp->new_chap ) { - sub = NULL; + // don't drop a chapter mark when we drop the buffer + pv->chap_mark = buf_tmp->new_chap; } + hb_buffer_close( &buf_tmp ); + continue; } - - /* The PTS of the frame we are expecting now */ - pts_expected = w->pts_offset + - w->count_frames * w->job->vrate_base / 300; - - if( cur->start < pts_expected - w->job->vrate_base / 300 / 2 && - next->start < pts_expected + w->job->vrate_base / 300 / 2 ) + if ( pv->first_drop ) { - /* The current frame is too old but the next one matches, - let's trash */ - hb_buffer_close( &cur ); - w->cur = cur = hb_fifo_get( job->fifo_raw ); - continue; + hb_log( "sync: video time didn't advance - dropped %d frames " + "(delta %d ms, current %"PRId64", next %"PRId64", dur %d)", + pv->drop_count, (int)( cur->start - pv->first_drop ) / 90, + cur->start, next->start, (int)( next->start - cur->start ) ); + pv->first_drop = 0; + pv->drop_count = 0; } - if( next->start > pts_expected + 3 * w->job->vrate_base / 300 / 2 ) + /* + * Track the video sequence number localy so that we can sync the audio + * to it using the sequence number as well as the PTS. + */ + pv->video_sequence = cur->sequence; + + /* + * Look for a subtitle for this frame. + * + * If found then it will be tagged onto a video buffer of the correct time and + * sent in to the render pipeline. This only needs to be done for VOBSUBs which + * get rendered, other types of subtitles can just sit in their raw_queue until + * delt with at muxing. + */ + for( i = 0; i < hb_list_count( job->list_subtitle ); i++) { - /* We'll need the current frame more than one time. Make a - copy of it and keep it */ - buf_tmp = hb_buffer_init( cur->size ); - memcpy( buf_tmp->data, cur->data, cur->size ); - } - else + subtitle = hb_list_item( job->list_subtitle, i ); + + /* + * Rewrite timestamps on subtitles that need it (on raw queue). + */ + if( subtitle->source == CC608SUB || + subtitle->source == CC708SUB || + subtitle->source == SRTSUB ) + { + /* + * Rewrite timestamps on subtitles that came from Closed Captions + * since they are using the MPEG2 timestamps. + */ + while( ( sub = hb_fifo_see( subtitle->fifo_raw ) ) ) + { + /* + * Rewrite the timestamps as and when the video + * (cur->start) reaches the same timestamp as a + * closed caption (sub->start). + * + * What about discontinuity boundaries - not delt + * with here - Van? + * + * Bypass the sync fifo altogether. + */ + if( sub->size <= 0 ) + { + sub = hb_fifo_get( subtitle->fifo_raw ); + hb_fifo_push( subtitle->fifo_out, sub ); + sub = NULL; + break; + } else { + /* + * Sync the subtitles to the incoming video, and use + * the matching converted video timestamp. + * + * Note that it doesn't appear that we need to convert + * timestamps, I guess that they were already correct, + * so just push them through for rendering. + * + */ + if( sub->start < cur->start ) + { + uint64_t duration; + duration = sub->stop - sub->start; + sub = hb_fifo_get( subtitle->fifo_raw ); + hb_fifo_push( subtitle->fifo_out, sub ); + } else { + sub = NULL; + break; + } + } + } + } + + if( subtitle->source == VOBSUB ) + { + hb_buffer_t * sub2; + while( ( sub = hb_fifo_see( subtitle->fifo_raw ) ) ) + { + if( sub->size == 0 ) + { + /* + * EOF, pass it through immediately. + */ + break; + } + + /* If two subtitles overlap, make the first one stop + when the second one starts */ + sub2 = hb_fifo_see2( subtitle->fifo_raw ); + if( sub2 && sub->stop > sub2->start ) + { + sub->stop = sub2->start; + } + + // hb_log("0x%x: video seq: %lld subtitle sequence: %lld", + // sub, cur->sequence, sub->sequence); + + if( sub->sequence > cur->sequence ) + { + /* + * The video is behind where we are, so wait until + * it catches up to the same reader point on the + * DVD. Then our PTS should be in the same region + * as the video. + */ + sub = NULL; + break; + } + + if( sub->stop > cur->start ) { + /* + * The stop time is in the future, so fall through + * and we'll deal with it in the next block of + * code. + */ + + /* + * There is a valid subtitle, is it time to display it? + */ + if( sub->stop > sub->start) + { + /* + * Normal subtitle which ends after it starts, + * check to see that the current video is between + * the start and end. + */ + if( cur->start > sub->start && + cur->start < sub->stop ) + { + /* + * We should be playing this, so leave the + * subtitle in place. + * + * fall through to display + */ + if( ( sub->stop - sub->start ) < ( 2 * 90000 ) ) + { + /* + * Subtitle is on for less than three + * seconds, extend the time that it is + * displayed to make it easier to read. + * Make it 3 seconds or until the next + * subtitle is displayed. + * + * This is in response to Indochine which + * only displays subs for 1 second - + * too fast to read. + */ + sub->stop = sub->start + ( 2 * 90000 ); + + sub2 = hb_fifo_see2( subtitle->fifo_raw ); + + if( sub2 && sub->stop > sub2->start ) + { + sub->stop = sub2->start; + } + } + } + else + { + /* + * Defer until the play point is within + * the subtitle + */ + sub = NULL; + } + } + else + { + /* + * The end of the subtitle is less than the start, + * this is a sign of a PTS discontinuity. + */ + if( sub->start > cur->start ) + { + /* + * we haven't reached the start time yet, or + * we have jumped backwards after having + * already started this subtitle. + */ + if( cur->start < sub->stop ) + { + /* + * We have jumped backwards and so should + * continue displaying this subtitle. + * + * fall through to display. + */ + } + else + { + /* + * Defer until the play point is + * within the subtitle + */ + sub = NULL; + } + } else { + /* + * Play this subtitle as the start is + * greater than our video point. + * + * fall through to display/ + */ + } + } + break; + } + else + { + + /* + * The subtitle is older than this picture, trash it + */ + sub = hb_fifo_get( subtitle->fifo_raw ); + hb_buffer_close( &sub ); + } + } + + /* If we have a subtitle for this picture, copy it */ + /* FIXME: we should avoid this memcpy */ + if( sub ) + { + if( sub->size > 0 ) + { + if( subtitle->config.dest == RENDERSUB ) + { + if ( cur->sub == NULL ) + { + /* + * Tack onto the video buffer for rendering + */ + cur->sub = hb_buffer_init( sub->size ); + cur->sub->x = sub->x; + cur->sub->y = sub->y; + cur->sub->width = sub->width; + cur->sub->height = sub->height; + memcpy( cur->sub->data, sub->data, sub->size ); + } + } else { + /* + * Pass-Through, pop it off of the raw queue, + * rewrite times and make it available to be + * reencoded. + */ + uint64_t sub_duration; + sub = hb_fifo_get( subtitle->fifo_raw ); + sub_duration = sub->stop - sub->start; + sub->start = cur->start; + buf_tmp = hb_fifo_see( job->fifo_raw ); + int64_t duration = buf_tmp->start - cur->start; + sub->stop = sub->start + duration; + hb_fifo_push( subtitle->fifo_sync, sub ); + } + } else { + /* + * EOF - consume for rendered, else pass through + */ + if( subtitle->config.dest == RENDERSUB ) + { + sub = hb_fifo_get( subtitle->fifo_raw ); + hb_buffer_close( &sub ); + } else { + sub = hb_fifo_get( subtitle->fifo_raw ); + hb_fifo_push( subtitle->fifo_out, sub ); + } + } + } + } + } // end subtitles + + /* + * Adjust the pts of the current frame so that it's contiguous + * with the previous frame. The start time of the current frame + * has to be the end time of the previous frame and the stop + * time has to be the start of the next frame. We don't + * make any adjustments to the source timestamps other than removing + * the clock offsets (which also removes pts discontinuities). + * This means we automatically encode at the source's frame rate. + * MP2 uses an implicit duration (frames end when the next frame + * starts) but more advanced containers like MP4 use an explicit + * duration. Since we're looking ahead one frame we set the + * explicit stop time from the start time of the next frame. + */ + buf_tmp = cur; + pv->cur = cur = hb_fifo_get( job->fifo_raw ); + cur->sub = NULL; + pv->next_pts = cur->start; + int64_t duration = cur->start - pts_skip - buf_tmp->start; + pts_skip = 0; + if ( duration <= 0 ) { - /* The frame has the expected date and won't have to be - duplicated, just put it through */ - buf_tmp = cur; - w->cur = cur = hb_fifo_get( job->fifo_raw ); + hb_log( "sync: invalid video duration %"PRId64", start %"PRId64", next %"PRId64"", + duration, buf_tmp->start, next->start ); } - /* Replace those MPEG-2 dates with our dates */ - buf_tmp->start = (uint64_t) w->count_frames * - w->job->vrate_base / 300; - buf_tmp->stop = (uint64_t) ( w->count_frames + 1 ) * - w->job->vrate_base / 300; + buf_tmp->start = pv->next_start; + pv->next_start += duration; + buf_tmp->stop = pv->next_start; - /* If we have a subtitle for this picture, copy it */ - /* FIXME: we should avoid this memcpy */ - if( sub ) + if ( pv->chap_mark ) { - buf_tmp->sub = hb_buffer_init( sub->size ); - buf_tmp->sub->x = sub->x; - buf_tmp->sub->y = sub->y; - buf_tmp->sub->width = sub->width; - buf_tmp->sub->height = sub->height; - memcpy( buf_tmp->sub->data, sub->data, sub->size ); + // we have a pending chapter mark from a recent drop - put it on this + // buffer (this may make it one frame late but we can't do any better). + buf_tmp->new_chap = pv->chap_mark; + pv->chap_mark = 0; } /* Push the frame to the renderer */ @@ -387,269 +735,283 @@ static int SyncVideo( hb_work_object_t * w ) /* Update UI */ UpdateState( w ); - - /* Make sure we won't get more frames then expected */ - if( w->count_frames >= w->count_frames_max ) + + if( job->frame_to_stop && pv->count_frames > job->frame_to_stop ) { - hb_log( "sync: got %lld frames", w->count_frames ); - w->done = 1; - break; + // Drop an empty buffer into our output to ensure that things + // get flushed all the way out. + hb_fifo_push( job->fifo_sync, hb_buffer_init( 0 ) ); + pv->busy &=~ 1; + hb_log( "sync: reached %d frames, exiting early (%i busy)", + pv->count_frames, pv->busy ); + return; } } +} + +static void OutputAudioFrame( hb_job_t *job, hb_audio_t *audio, hb_buffer_t *buf, + hb_sync_audio_t *sync, hb_fifo_t *fifo, int i ) +{ + int64_t start = sync->next_start; + int64_t duration = buf->stop - buf->start; + + sync->next_pts += duration; - return HB_WORK_OK; + if( audio->config.in.samplerate == audio->config.out.samplerate || + audio->config.out.codec == HB_ACODEC_AC3 || + audio->config.out.codec == HB_ACODEC_DCA ) + { + /* + * If we don't have to do sample rate conversion or this audio is + * pass-thru just send the input buffer downstream after adjusting + * its timestamps to make the output stream continuous. + */ + } + else + { + /* Not pass-thru - do sample rate conversion */ + int count_in, count_out; + hb_buffer_t * buf_raw = buf; + int channel_count = HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT(audio->config.out.mixdown) * + sizeof( float ); + + count_in = buf_raw->size / channel_count; + /* + * When using stupid rates like 44.1 there will always be some + * truncation error. E.g., a 1536 sample AC3 frame will turn into a + * 1536*44.1/48.0 = 1411.2 sample frame. If we just truncate the .2 + * the error will build up over time and eventually the audio will + * substantially lag the video. libsamplerate will keep track of the + * fractional sample & give it to us when appropriate if we give it + * an extra sample of space in the output buffer. + */ + count_out = ( duration * audio->config.out.samplerate ) / 90000 + 1; + + sync->data.input_frames = count_in; + sync->data.output_frames = count_out; + sync->data.src_ratio = (double)audio->config.out.samplerate / + (double)audio->config.in.samplerate; + + buf = hb_buffer_init( count_out * channel_count ); + sync->data.data_in = (float *) buf_raw->data; + sync->data.data_out = (float *) buf->data; + if( src_process( sync->state, &sync->data ) ) + { + /* XXX If this happens, we're screwed */ + hb_log( "sync: audio %d src_process failed", i ); + } + hb_buffer_close( &buf_raw ); + + buf->size = sync->data.output_frames_gen * channel_count; + duration = ( sync->data.output_frames_gen * 90000 ) / + audio->config.out.samplerate; + } + buf->frametype = HB_FRAME_AUDIO; + buf->start = start; + buf->stop = start + duration; + sync->next_start = start + duration; + hb_fifo_push( fifo, buf ); } /*********************************************************************** * SyncAudio *********************************************************************** - * + * **********************************************************************/ static void SyncAudio( hb_work_object_t * w, int i ) { - hb_job_t * job; - hb_audio_t * audio; + hb_work_private_t * pv = w->private_data; + hb_job_t * job = pv->job; + hb_sync_audio_t * sync = &pv->sync_audio[i]; + hb_audio_t * audio = sync->audio; hb_buffer_t * buf; - hb_sync_audio_t * sync; - hb_fifo_t * fifo; - int rate; + int64_t start; - int64_t pts_expected; - int64_t start; - - job = w->job; - sync = &w->sync_audio[i]; - audio = sync->audio; - - if( job->acodec & HB_ACODEC_AC3 ) + if( audio->config.out.codec == HB_ACODEC_AC3 || + audio->config.out.codec == HB_ACODEC_DCA ) { - fifo = audio->fifo_out; - rate = audio->rate; + fifo = audio->priv.fifo_out; } else { - fifo = audio->fifo_sync; - rate = job->arate; + fifo = audio->priv.fifo_sync; } - while( !hb_fifo_is_full( fifo ) && - ( buf = hb_fifo_see( audio->fifo_raw ) ) ) + while( !hb_fifo_is_full( fifo ) && ( buf = hb_fifo_see( audio->priv.fifo_raw ) ) ) { - /* The PTS of the samples we are expecting now */ - pts_expected = w->pts_offset + sync->count_frames * 90000 / rate; - - if( ( buf->start > pts_expected + PTS_DISCONTINUITY_TOLERANCE || - buf->start < pts_expected - PTS_DISCONTINUITY_TOLERANCE ) && - w->pts_offset_old > INT64_MIN ) + start = buf->start - pv->audio_passthru_slip; + /* if the next buffer is an eof send it downstream */ + if ( buf->size <= 0 ) { - /* There has been a PTS discontinuity, and this frame might - be from before the discontinuity */ - pts_expected = w->pts_offset_old + sync->count_frames * - 90000 / rate; - - if( buf->start > pts_expected + PTS_DISCONTINUITY_TOLERANCE || - buf->start < pts_expected - PTS_DISCONTINUITY_TOLERANCE ) - { - /* There is really nothing we can do with it */ - buf = hb_fifo_get( audio->fifo_raw ); - hb_buffer_close( &buf ); - continue; - } - - /* Use the older offset */ - start = pts_expected - w->pts_offset_old; - } - else - { - start = pts_expected - w->pts_offset; + buf = hb_fifo_get( audio->priv.fifo_raw ); + hb_fifo_push( fifo, buf ); + pv->busy &=~ (1 << (i + 1) ); + return; } - - /* Tolerance: 100 ms */ - if( buf->start < pts_expected - 9000 ) + if( job->frame_to_stop && pv->count_frames >= job->frame_to_stop ) { - /* Late audio, trash it */ - hb_log( "sync: trashing late audio" ); - buf = hb_fifo_get( audio->fifo_raw ); - hb_buffer_close( &buf ); - continue; + hb_fifo_push( fifo, hb_buffer_init(0) ); + pv->busy &=~ (1 << (i + 1) ); + return; } - else if( buf->start > pts_expected + 9000 ) + if ( (int64_t)( start - sync->next_pts ) < 0 ) { - /* Missing audio, send a frame of silence */ - InsertSilence( w, i ); - continue; + // audio time went backwards. + // If our output clock is more than a half frame ahead of the + // input clock drop this frame to move closer to sync. + // Otherwise drop frames until the input clock matches the output clock. + if ( sync->first_drop || sync->next_start - start > 90*15 ) + { + // Discard data that's in the past. + if ( sync->first_drop == 0 ) + { + sync->first_drop = sync->next_pts; + } + ++sync->drop_count; + buf = hb_fifo_get( audio->priv.fifo_raw ); + hb_buffer_close( &buf ); + continue; + } + sync->next_pts = start; } - - if( job->acodec & HB_ACODEC_AC3 ) + if ( sync->first_drop ) { - buf = hb_fifo_get( audio->fifo_raw ); - buf->start = start; - buf->stop = start + 90000 * AC3_SAMPLES_PER_FRAME / rate; - - sync->count_frames += AC3_SAMPLES_PER_FRAME; + // we were dropping old data but input buf time is now current + hb_log( "sync: audio %d time went backwards %d ms, dropped %d frames " + "(next %"PRId64", current %"PRId64")", i, + (int)( sync->next_pts - sync->first_drop ) / 90, + sync->drop_count, sync->first_drop, sync->next_pts ); + sync->first_drop = 0; + sync->drop_count = 0; + sync->next_pts = start; } - else + if ( start - sync->next_pts >= (90 * 70) ) { - hb_buffer_t * buf_raw = hb_fifo_get( audio->fifo_raw ); - - int count_in, count_out; - - count_in = buf_raw->size / 2 / sizeof( float ); - count_out = ( buf_raw->stop - buf_raw->start ) * job->arate / 90000; - if( buf->start < pts_expected - 1500 ) - count_out--; - else if( buf->start > pts_expected + 1500 ) - count_out++; - - sync->data.data_in = (float *) buf_raw->data; - sync->data.input_frames = count_in; - sync->data.output_frames = count_out; - - sync->data.src_ratio = (double) sync->data.output_frames / - (double) sync->data.input_frames; - - buf = hb_buffer_init( sync->data.output_frames * 2 * - sizeof( float ) ); - sync->data.data_out = (float *) buf->data; - if( src_process( sync->state, &sync->data ) ) + if ( start - sync->next_pts > (90000LL * 60) ) { - /* XXX If this happens, we're screwed */ - hb_log( "sync: src_process failed" ); + // there's a gap of more than a minute between the last + // frame and this. assume we got a corrupted timestamp + // and just drop the next buf. + hb_log( "sync: %d minute time gap in audio %d - dropping buf" + " start %"PRId64", next %"PRId64, + (int)((start - sync->next_pts) / (90000*60)), + i, start, sync->next_pts ); + buf = hb_fifo_get( audio->priv.fifo_raw ); + hb_buffer_close( &buf ); + continue; } - hb_buffer_close( &buf_raw ); - - buf->size = sync->data.output_frames_gen * 2 * sizeof( float ); - - /* Set dates for resampled data */ - buf->start = start; - buf->stop = start + sync->data.output_frames_gen * - 90000 / job->arate; - - sync->count_frames += sync->data.output_frames_gen; + /* + * there's a gap of at least 70ms between the last + * frame we processed & the next. Fill it with silence. + * Or in the case of DCA, skip some frames from the + * other streams. + */ + if( sync->audio->config.out.codec == HB_ACODEC_DCA ) + { + hb_log( "sync: audio gap %d ms. Skipping frames. Audio %d" + " start %"PRId64", next %"PRId64, + (int)((start - sync->next_pts) / 90), + i, start, sync->next_pts ); + pv->audio_passthru_slip += (start - sync->next_pts); + pv->video_pts_slip += (start - sync->next_pts); + return; + } + hb_log( "sync: adding %d ms of silence to audio %d" + " start %"PRId64", next %"PRId64, + (int)((start - sync->next_pts) / 90), + i, start, sync->next_pts ); + InsertSilence( w, i, start - sync->next_pts ); + return; } - buf->key = 1; - hb_fifo_push( fifo, buf ); - } - - if( NeedSilence( w, audio ) ) - { - InsertSilence( w, i ); - } -} - -static int NeedSilence( hb_work_object_t * w, hb_audio_t * audio ) -{ - hb_job_t * job = w->job; - - if( hb_fifo_size( audio->fifo_in ) || - hb_fifo_size( audio->fifo_raw ) || - hb_fifo_size( audio->fifo_sync ) || - hb_fifo_size( audio->fifo_out ) ) - { - /* We have some audio, we are fine */ - return 0; - } - - /* No audio left in fifos */ - - if( hb_thread_has_exited( job->reader ) ) - { - /* We might miss some audio to complete encoding and muxing - the video track */ - return 1; + /* + * When we get here we've taken care of all the dups and gaps in the + * audio stream and are ready to inject the next input frame into + * the output stream. + */ + buf = hb_fifo_get( audio->priv.fifo_raw ); + OutputAudioFrame( job, audio, buf, sync, fifo, i ); } - - if( hb_fifo_is_full( job->fifo_mpeg2 ) && - hb_fifo_is_full( job->fifo_raw ) && - hb_fifo_is_full( job->fifo_sync ) && - hb_fifo_is_full( job->fifo_render ) && - hb_fifo_is_full( job->fifo_mpeg4 ) ) - { - /* Too much video and no audio, oh-oh */ - return 1; - } - - return 0; } -static void InsertSilence( hb_work_object_t * w, int i ) +static void InsertSilence( hb_work_object_t * w, int i, int64_t duration ) { - hb_job_t * job; - hb_sync_audio_t * sync; - hb_buffer_t * buf; - - job = w->job; - sync = &w->sync_audio[i]; - - if( job->acodec & HB_ACODEC_AC3 ) - { - buf = hb_buffer_init( sync->ac3_size ); - buf->start = sync->count_frames * 90000 / sync->audio->rate; - buf->stop = buf->start + 90000 * AC3_SAMPLES_PER_FRAME / - sync->audio->rate; - memcpy( buf->data, sync->ac3_buf, buf->size ); - - hb_log( "sync: adding a silent AC-3 frame for track %x", - sync->audio->id ); - hb_fifo_push( sync->audio->fifo_out, buf ); - - sync->count_frames += AC3_SAMPLES_PER_FRAME; - - } - else + hb_work_private_t * pv = w->private_data; + hb_job_t *job = pv->job; + hb_sync_audio_t *sync = &pv->sync_audio[i]; + hb_buffer_t *buf; + hb_fifo_t *fifo; + + // to keep pass-thru and regular audio in sync we generate silence in + // AC3 frame-sized units. If the silence duration isn't an integer multiple + // of the AC3 frame duration we will truncate or round up depending on + // which minimizes the timing error. + const int frame_dur = ( 90000 * AC3_SAMPLES_PER_FRAME ) / + sync->audio->config.in.samplerate; + int frame_count = ( duration + (frame_dur >> 1) ) / frame_dur; + + while ( --frame_count >= 0 ) { - buf = hb_buffer_init( 2 * job->arate / 20 * - sizeof( float ) ); - buf->start = sync->count_frames * 90000 / job->arate; - buf->stop = buf->start + 90000 / 20; - memset( buf->data, 0, buf->size ); - - hb_log( "sync: adding 50 ms of silence for track %x", - sync->audio->id ); - hb_fifo_push( sync->audio->fifo_sync, buf ); - - sync->count_frames += job->arate / 20; + if( sync->audio->config.out.codec == HB_ACODEC_AC3 ) + { + buf = hb_buffer_init( sync->ac3_size ); + buf->start = sync->next_pts; + buf->stop = buf->start + frame_dur; + memcpy( buf->data, sync->ac3_buf, buf->size ); + fifo = sync->audio->priv.fifo_out; + } + else + { + buf = hb_buffer_init( AC3_SAMPLES_PER_FRAME * sizeof( float ) * + HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT( + sync->audio->config.out.mixdown) ); + buf->start = sync->next_pts; + buf->stop = buf->start + frame_dur; + memset( buf->data, 0, buf->size ); + fifo = sync->audio->priv.fifo_sync; + } + OutputAudioFrame( job, sync->audio, buf, sync, fifo, i ); } } static void UpdateState( hb_work_object_t * w ) { + hb_work_private_t * pv = w->private_data; hb_state_t state; - if( !w->count_frames ) + if( !pv->count_frames ) { - w->st_first = hb_get_date(); + pv->st_first = hb_get_date(); } - w->count_frames++; + pv->count_frames++; - if( hb_get_date() > w->st_dates[3] + 1000 ) + if( hb_get_date() > pv->st_dates[3] + 1000 ) { - memmove( &w->st_dates[0], &w->st_dates[1], + memmove( &pv->st_dates[0], &pv->st_dates[1], 3 * sizeof( uint64_t ) ); - memmove( &w->st_counts[0], &w->st_counts[1], + memmove( &pv->st_counts[0], &pv->st_counts[1], 3 * sizeof( uint64_t ) ); - w->st_dates[3] = hb_get_date(); - w->st_counts[3] = w->count_frames; - } + pv->st_dates[3] = hb_get_date(); + pv->st_counts[3] = pv->count_frames; + } #define p state.param.working state.state = HB_STATE_WORKING; - p.progress = (float) w->count_frames / (float) w->count_frames_max; + p.progress = (float) pv->count_frames / (float) pv->count_frames_max; if( p.progress > 1.0 ) { - p.progress = 1.0; + p.progress = 1.0; } p.rate_cur = 1000.0 * - (float) ( w->st_counts[3] - w->st_counts[0] ) / - (float) ( w->st_dates[3] - w->st_dates[0] ); - if( hb_get_date() > w->st_first + 4000 ) + (float) ( pv->st_counts[3] - pv->st_counts[0] ) / + (float) ( pv->st_dates[3] - pv->st_dates[0] ); + if( hb_get_date() > pv->st_first + 4000 ) { int eta; - p.rate_avg = 1000.0 * (float) w->st_counts[3] / - (float) ( w->st_dates[3] - w->st_first ); - eta = (float) ( w->count_frames_max - w->st_counts[3] ) / + p.rate_avg = 1000.0 * (float) pv->st_counts[3] / + (float) ( pv->st_dates[3] - pv->st_first ); + eta = (float) ( pv->count_frames_max - pv->st_counts[3] ) / p.rate_avg; p.hours = eta / 3600; p.minutes = ( eta % 3600 ) / 60; @@ -664,5 +1026,5 @@ static void UpdateState( hb_work_object_t * w ) } #undef p - hb_set_state( w->job->h, &state ); + hb_set_state( pv->job->h, &state ); }