X-Git-Url: http://git.osdn.jp/view?a=blobdiff_plain;f=libhb%2Fsync.c;h=9c36ee30a72eaf4df45d411e392825fd84909b5f;hb=d7389faafa327bb1be3340c34c30f353a8f52960;hp=2043b9dd18e26d0f3f4e1bb5a9f32be4c45a705e;hpb=0eebca08f46ebc25edd7fea90fab8ad6cf545cd0;p=handbrake-jp%2Fhandbrake-jp-git.git diff --git a/libhb/sync.c b/libhb/sync.c index 2043b9dd..9c36ee30 100644 --- a/libhb/sync.c +++ b/libhb/sync.c @@ -18,12 +18,28 @@ typedef struct { - hb_audio_t * audio; + hb_lock_t * mutex; + int ref; /* Reference count to tell us when it's unused */ + int count_frames; + int64_t audio_passthru_slip; + int64_t video_pts_slip; + int64_t pts_offset; + + /* Frame based point-to-point support */ + int64_t audio_pts_thresh; + int start_found; + hb_cond_t * next_frame; + int pts_count; + int64_t * first_pts; +} hb_sync_common_t; +typedef struct +{ + int index; 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 */ + 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; @@ -32,75 +48,100 @@ typedef struct /* AC-3 */ int ac3_size; uint8_t * ac3_buf; - } hb_sync_audio_t; -struct hb_work_private_s +typedef struct { - hb_job_t * job; - 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 */ - int64_t pts_offset; - 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; + int first_frame; + int64_t pts_skip; + 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_max; + int chap_mark; /* to propagate chapter mark across a drop */ + hb_buffer_t * cur; /* The next picture to process */ /* Statistics */ - uint64_t st_counts[4]; - uint64_t st_dates[4]; - uint64_t st_first; + uint64_t st_counts[4]; + uint64_t st_dates[4]; + uint64_t st_first; +} hb_sync_video_t; + +struct hb_work_private_s +{ + hb_job_t * job; + hb_sync_common_t * common; + union + { + hb_sync_video_t video; + hb_sync_audio_t audio; + } type; }; /*********************************************************************** * Local prototypes **********************************************************************/ -static void InitAudio( hb_work_object_t * w, int i ); -static void SyncVideo( hb_work_object_t * w ); -static void SyncAudio( hb_work_object_t * w, int i ); -static void InsertSilence( hb_work_object_t * w, int i, int64_t d ); +static void getPtsOffset( hb_work_object_t * w ); +static int checkPtsOffset( hb_work_object_t * w ); +static void InitAudio( hb_job_t * job, hb_sync_common_t * common, int i ); +static void InsertSilence( hb_work_object_t * w, int64_t d ); static void UpdateState( hb_work_object_t * w ); +static void UpdateSearchState( hb_work_object_t * w, int64_t start ); +static hb_buffer_t * OutputAudioFrame( hb_audio_t *audio, hb_buffer_t *buf, + hb_sync_audio_t *sync ); /*********************************************************************** * hb_work_sync_init *********************************************************************** * Initialize the work object **********************************************************************/ -int syncInit( hb_work_object_t * w, hb_job_t * job ) +hb_work_object_t * hb_sync_init( hb_job_t * job ) { - hb_title_t * title = job->title; - hb_chapter_t * chapter; - int i; - uint64_t duration; + hb_title_t * title = job->title; + hb_chapter_t * chapter; + int i; + uint64_t duration; hb_work_private_t * pv; + hb_sync_video_t * sync; + hb_work_object_t * w; + hb_work_object_t * ret = NULL; pv = calloc( 1, sizeof( hb_work_private_t ) ); + sync = &pv->type.video; + pv->common = calloc( 1, sizeof( hb_sync_common_t ) ); + pv->common->ref++; + pv->common->mutex = hb_lock_init(); + pv->common->audio_pts_thresh = -1; + pv->common->next_frame = hb_cond_init(); + pv->common->pts_count = 1; + if ( job->frame_to_start || job->pts_to_start ) + { + pv->common->start_found = 0; + } + else + { + pv->common->start_found = 1; + } + + ret = w = hb_get_work( WORK_SYNC_VIDEO ); w->private_data = pv; + w->fifo_in = job->fifo_raw; + w->fifo_out = job->fifo_sync; pv->job = job; - pv->pts_offset = INT64_MIN; + pv->common->pts_offset = INT64_MIN; + sync->first_frame = 1; 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; + sync->count_frames_max = interjob->frame_count; } else { @@ -125,72 +166,76 @@ int syncInit( hb_work_object_t * w, hb_job_t * job ) 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; + sync->count_frames_max = duration * title->rate / title->rate_base / 90000; } - hb_log( "sync: expecting %d video frames", pv->count_frames_max ); - pv->busy |= 1; + hb_log( "sync: expecting %d video frames", sync->count_frames_max ); /* 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 ); + InitAudio( job, pv->common, i ); } } + pv->common->first_pts = malloc( sizeof(int64_t) * pv->common->pts_count ); + for ( i = 0; i < pv->common->pts_count; i++ ) + pv->common->first_pts[i] = INT64_MAX; - return 0; + return ret; } /*********************************************************************** - * Close + * Close Video *********************************************************************** * **********************************************************************/ -void syncClose( hb_work_object_t * w ) +void syncVideoClose( 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; + hb_sync_video_t * sync = &pv->type.video; + + // Wake up audio sync if it's still waiting on condition. + pv->common->pts_offset = 0; + pv->common->start_found = 1; + hb_cond_broadcast( pv->common->next_frame ); - if( pv->cur ) + if( sync->cur ) { - hb_buffer_close( &pv->cur ); + hb_buffer_close( &sync->cur ); } hb_log( "sync: got %d frames, %d expected", - pv->count_frames, pv->count_frames_max ); + pv->common->count_frames, sync->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->frame_count = pv->common->count_frames; interjob->last_job = job->sequence_id; - interjob->total_time = pv->next_start; + interjob->total_time = sync->next_start; } - if (pv->drops || pv->dups ) + if (sync->drops || sync->dups ) { - hb_log( "sync: %d frames dropped, %d duplicated", pv->drops, pv->dups ); + hb_log( "sync: %d frames dropped, %d duplicated", + sync->drops, sync->dups ); } - for( i = 0; i < hb_list_count( title->list_audio ); i++ ) + hb_lock( pv->common->mutex ); + if ( --pv->common->ref == 0 ) { - 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 ); - } + hb_unlock( pv->common->mutex ); + hb_lock_close( &pv->common->mutex ); + free( pv->common ); + } + else + { + hb_unlock( pv->common->mutex ); } free( pv ); @@ -198,123 +243,165 @@ void syncClose( hb_work_object_t * w ) } /*********************************************************************** - * Work + * syncVideoWork *********************************************************************** - * 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 ) +int syncVideoWork( hb_work_object_t * w, hb_buffer_t ** buf_in, + hb_buffer_t ** buf_out ) { + hb_buffer_t * cur, * next, * sub = NULL; hb_work_private_t * pv = w->private_data; + hb_job_t * job = pv->job; + hb_subtitle_t * subtitle; + hb_sync_video_t * sync = &pv->type.video; int i; - if ( pv->busy & 1 ) - SyncVideo( w ); + *buf_out = NULL; + next = *buf_in; + *buf_in = NULL; - for( i = 0; i < hb_list_count( pv->job->title->list_audio ); i++ ) + /* Wait for start of point-to-point encoding */ + if( !pv->common->start_found ) { - if ( pv->busy & ( 1 << (i + 1) ) ) - SyncAudio( w, i ); - } + hb_sync_video_t * sync = &pv->type.video; - return ( pv->busy? HB_WORK_OK : HB_WORK_DONE ); -} + if( next->size == 0 ) + { + *buf_out = next; + pv->common->start_found = 1; + hb_cond_broadcast( pv->common->next_frame ); -hb_work_object_t hb_sync = -{ - WORK_SYNC, - "Synchronization", - syncInit, - syncWork, - syncClose -}; + /* + * Push through any subtitle EOFs in case they + * were not synced through. + */ + for( i = 0; i < hb_list_count( job->list_subtitle ); i++) + { + subtitle = hb_list_item( job->list_subtitle, i ); + if( subtitle->config.dest == PASSTHRUSUB ) + { + if( subtitle->source == VOBSUB ) + hb_fifo_push( subtitle->fifo_sync, hb_buffer_init( 0 ) ); + else + hb_fifo_push( subtitle->fifo_out, hb_buffer_init( 0 ) ); + } + } + return HB_WORK_DONE; + } + if ( pv->common->count_frames < job->frame_to_start || + next->start < job->pts_to_start ) + { + // Flush any subtitles that have pts prior to the + // current frame + for( i = 0; i < hb_list_count( job->list_subtitle ); i++) + { + subtitle = hb_list_item( job->list_subtitle, i ); + while( ( sub = hb_fifo_see( subtitle->fifo_raw ) ) ) + { + if ( sub->start > next->start ) + break; + sub = hb_fifo_get( subtitle->fifo_raw ); + hb_buffer_close( &sub ); + } + } + hb_lock( pv->common->mutex ); + // Tell the audio threads what must be dropped + pv->common->audio_pts_thresh = next->start; + hb_cond_broadcast( pv->common->next_frame ); + hb_unlock( pv->common->mutex ); -static void InitAudio( hb_work_object_t * w, int i ) -{ - 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; + UpdateSearchState( w, next->start ); + hb_buffer_close( &next ); - sync = &pv->sync_audio[i]; - sync->audio = hb_list_item( title->list_audio, i ); + return HB_WORK_OK; + } + hb_lock( pv->common->mutex ); + pv->common->start_found = 1; + pv->common->count_frames = 0; + hb_cond_broadcast( pv->common->next_frame ); + hb_unlock( pv->common->mutex ); + sync->st_first = 0; + } - if( sync->audio->config.out.codec == HB_ACODEC_AC3 ) + /* Wait till we can determine the initial pts of all streams */ + if( pv->common->pts_offset == INT64_MIN ) { - /* Have a silent AC-3 frame ready in case we have to fill a - gap */ - AVCodec * codec; - AVCodecContext * c; - short * zeros; - - codec = avcodec_find_encoder( CODEC_ID_AC3 ); - c = avcodec_alloc_context(); - - 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( hb_avcodec_open( c, codec ) < 0 ) + pv->common->first_pts[0] = next->start; + hb_lock( pv->common->mutex ); + while( pv->common->pts_offset == INT64_MIN ) { - hb_log( "sync: avcodec_open failed" ); - return; + // Full fifos will make us wait forever, so get the + // pts offset from the available streams if full + if ( hb_fifo_is_full( job->fifo_raw ) ) + { + getPtsOffset( w ); + hb_cond_broadcast( pv->common->next_frame ); + } + else if ( checkPtsOffset( w ) ) + hb_cond_broadcast( pv->common->next_frame ); + else + hb_cond_timedwait( pv->common->next_frame, pv->common->mutex, 200 ); } + hb_unlock( pv->common->mutex ); + } - zeros = calloc( AC3_SAMPLES_PER_FRAME * - sizeof( short ) * c->channels, 1 ); - 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, - zeros ) != sync->ac3_size ) + if( !sync->cur ) + { + sync->cur = next; + if( sync->cur->size == 0 ) { - hb_log( "sync: avcodec_encode_audio failed" ); - } + /* we got an end-of-stream as our first video packet? + * Feed it downstream & signal that we're done. + */ + *buf_out = hb_buffer_init( 0 ); - free( zeros ); - hb_avcodec_close( c ); - av_free( c ); + pv->common->start_found = 1; + hb_cond_broadcast( pv->common->next_frame ); + + /* + * Push through any subtitle EOFs in case they + * were not synced through. + */ + for( i = 0; i < hb_list_count( job->list_subtitle ); i++) + { + subtitle = hb_list_item( job->list_subtitle, i ); + if( subtitle->config.dest == PASSTHRUSUB ) + { + if( subtitle->source == VOBSUB ) + hb_fifo_push( subtitle->fifo_sync, hb_buffer_init( 0 ) ); + else + hb_fifo_push( subtitle->fifo_out, hb_buffer_init( 0 ) ); + } + } + return HB_WORK_DONE; + } + return HB_WORK_OK; } - else + cur = sync->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 + compute the duration of the current frame*/ + if( next->size == 0 ) { - /* Initialize libsamplerate */ - int 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; - } -} + hb_buffer_close( &next ); -/*********************************************************************** - * SyncVideo - *********************************************************************** - * - **********************************************************************/ -static void SyncVideo( hb_work_object_t * w ) -{ - 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; + cur->start = sync->next_start; + cur->stop = cur->start + 90000. / ((double)job->vrate / (double)job->vrate_base); - if( !pv->cur && !( pv->cur = hb_fifo_get( job->fifo_raw ) ) ) - { - /* We haven't even got a frame yet */ - return; - } - cur = pv->cur; - pts_skip = 0; - if( cur->size == 0 ) - { - /* we got an end-of-stream. Feed it downstream & signal that we're done. */ - hb_fifo_push( job->fifo_sync, hb_buffer_init( 0 ) ); + /* Make sure last frame is reflected in frame count */ + pv->common->count_frames++; + + /* Push the frame to the renderer */ + hb_fifo_push( job->fifo_sync, cur ); + sync->cur = NULL; + + /* 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. */ + *buf_out = hb_buffer_init( 0 ); /* * Push through any subtitle EOFs in case they were not synced through. @@ -324,432 +411,761 @@ static void SyncVideo( hb_work_object_t * w ) subtitle = hb_list_item( job->list_subtitle, i ); if( subtitle->config.dest == PASSTHRUSUB ) { - hb_fifo_push( subtitle->fifo_out, hb_buffer_init( 0 ) ); + if( subtitle->source == VOBSUB ) + hb_fifo_push( subtitle->fifo_sync, hb_buffer_init( 0 ) ); + else + hb_fifo_push( subtitle->fifo_out, hb_buffer_init( 0 ) ); } } - - pv->busy &=~ 1; - return; + pv->common->start_found = 1; + hb_cond_broadcast( pv->common->next_frame ); + return HB_WORK_DONE; } - /* 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 - compute the duration of the current frame*/ - while( !hb_fifo_is_full( job->fifo_sync ) && - ( next = hb_fifo_see( job->fifo_raw ) ) ) + /* Check for end of point-to-point frame encoding */ + if( job->frame_to_stop && pv->common->count_frames > job->frame_to_stop ) { - hb_buffer_t * buf_tmp; + // Drop an empty buffer into our output to ensure that things + // get flushed all the way out. + hb_buffer_close( &sync->cur ); + hb_buffer_close( &next ); + *buf_out = hb_buffer_init( 0 ); + hb_log( "sync: reached %d frames, exiting early", + pv->common->count_frames ); - if( next->size == 0 ) + /* + * Push through any subtitle EOFs in case they were not synced through. + */ + for( i = 0; i < hb_list_count( job->list_subtitle ); i++) { - /* 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++) + subtitle = hb_list_item( job->list_subtitle, i ); + if( subtitle->config.dest == PASSTHRUSUB ) { - subtitle = hb_list_item( job->list_subtitle, i ); - if( subtitle->config.dest == PASSTHRUSUB ) - { + if( subtitle->source == VOBSUB ) + hb_fifo_push( subtitle->fifo_sync, hb_buffer_init( 0 ) ); + else hb_fifo_push( subtitle->fifo_out, hb_buffer_init( 0 ) ); - } - } - pv->busy &=~ 1; - return; - } - if( pv->pts_offset == INT64_MIN ) - { - /* This is our first frame */ - pv->pts_offset = 0; - if ( cur->start != 0 ) - { - /* - * 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 %lld", cur->start ); - cur->start = 0; } } + return HB_WORK_DONE; + } + + /* Check for end of point-to-point pts encoding */ + if( job->pts_to_stop && sync->next_start >= job->pts_to_stop ) + { + // Drop an empty buffer into our output to ensure that things + // get flushed all the way out. + hb_log( "sync: reached pts %"PRId64", exiting early", + sync->cur->start ); + hb_buffer_close( &sync->cur ); + hb_buffer_close( &next ); + *buf_out = hb_buffer_init( 0 ); /* - * 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. + * Push through any subtitle EOFs in case they were not synced through. */ - if ( (int64_t)( next->start - pv->video_pts_slip - cur->start ) <= 0 ) + for( i = 0; i < hb_list_count( job->list_subtitle ); i++) { - 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 ) + subtitle = hb_list_item( job->list_subtitle, i ); + if( subtitle->config.dest == PASSTHRUSUB ) { - // don't drop a chapter mark when we drop the buffer - pv->chap_mark = buf_tmp->new_chap; + if( subtitle->source == VOBSUB ) + hb_fifo_push( subtitle->fifo_sync, hb_buffer_init( 0 ) ); + else + hb_fifo_push( subtitle->fifo_out, hb_buffer_init( 0 ) ); } - hb_buffer_close( &buf_tmp ); - continue; } - if ( pv->first_drop ) + return HB_WORK_DONE; + } + + if( sync->first_frame ) + { + /* This is our first frame */ + if ( cur->start > pv->common->pts_offset ) { - hb_log( "sync: video time didn't advance - dropped %d frames " - "(delta %d ms, current %lld, next %lld, 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; + /* + * 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 = pv->common->pts_offset; } + sync->first_frame = 0; + } - /* - * 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; + /* + * 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. + */ + hb_lock( pv->common->mutex ); + if ( (int64_t)( next->start - pv->common->video_pts_slip - cur->start ) <= 0 ) + { + if ( sync->first_drop == 0 ) + { + sync->first_drop = next->start; + } + ++sync->drop_count; + if (next->start - cur->start > 0) + { + sync->pts_skip += next->start - cur->start; + pv->common->video_pts_slip -= next->start - cur->start; + } + hb_unlock( pv->common->mutex ); + if ( next->new_chap ) + { + // don't drop a chapter mark when we drop the buffer + sync->chap_mark = next->new_chap; + } + hb_buffer_close( &next ); + return HB_WORK_OK; + } + hb_unlock( pv->common->mutex ); + if ( sync->first_drop ) + { + hb_log( "sync: video time didn't advance - dropped %d frames " + "(delta %d ms, current %"PRId64", next %"PRId64", dur %d)", + sync->drop_count, (int)( cur->start - sync->first_drop ) / 90, + cur->start, next->start, (int)( next->start - cur->start ) ); + sync->first_drop = 0; + sync->drop_count = 0; + } + + /* + * Track the video sequence number localy so that we can sync the audio + * to it using the sequence number as well as the PTS. + */ + sync->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++) + { + subtitle = hb_list_item( job->list_subtitle, i ); /* - * 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. + * Rewrite timestamps on subtitles that need it (on raw queue). */ - for( i = 0; i < hb_list_count( job->list_subtitle ); i++) + if( subtitle->source == CC608SUB || + subtitle->source == CC708SUB || + subtitle->source == SRTSUB || + subtitle->source == UTF8SUB || + subtitle->source == TX3GSUB || + subtitle->source == SSASUB) { - subtitle = hb_list_item( job->list_subtitle, i ); - /* - * Rewrite timestamps on subtitles that need it (on raw queue). + * Rewrite timestamps on subtitles that came from Closed Captions + * since they are using the MPEG2 timestamps. */ - if( subtitle->source == CC608SUB || - subtitle->source == CC708SUB ) + while( ( sub = hb_fifo_see( subtitle->fifo_raw ) ) ) { /* - * Rewrite timestamps on subtitles that came from Closed Captions - * since they are using the MPEG2 timestamps. + * 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. */ - while( ( sub = hb_fifo_see( subtitle->fifo_raw ) ) ) + if( sub->size <= 0 ) { + sub = hb_fifo_get( subtitle->fifo_raw ); + hb_fifo_push( subtitle->fifo_out, sub ); + sub = NULL; + break; + } else { /* - * Rewrite the timestamps as and when the video - * (cur->start) reaches the same timestamp as a - * closed caption (sub->start). + * Sync the subtitles to the incoming video, and use + * the matching converted video timestamp. * - * What about discontinuity boundaries - not delt - * with here - Van? + * 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. * - * Bypass the sync fifo altogether. */ - if( sub->size <= 0 ) + if( sub->start < cur->start ) { sub = hb_fifo_get( subtitle->fifo_raw ); hb_fifo_push( subtitle->fifo_out, sub ); + } else { 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 ) + if( subtitle->source == VOBSUB ) + { + hb_buffer_t * sub2; + while( ( sub = hb_fifo_see( subtitle->fifo_raw ) ) ) { - hb_buffer_t * sub2; - while( ( sub = hb_fifo_see( subtitle->fifo_raw ) ) ) + if( sub->size == 0 ) { - if( sub->size == 0 ) - { - /* - * EOF, pass it through immediately. - */ - break; - } + /* + * 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. - */ + /* 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: %"PRId64" subtitle sequence: %"PRId64, + // 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) + { /* - * There is a valid subtitle, is it time to display it? + * Normal subtitle which ends after it starts, + * check to see that the current video is between + * the start and end. */ - if( sub->stop > sub->start) + if( cur->start > sub->start && + cur->start < sub->stop ) { /* - * 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 ) ) { /* - * 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 ) { - /* - * 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; - } + 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. + * Defer until the play point is within + * the subtitle */ - 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/ - */ - } + sub = NULL; } - break; } else { - /* - * The subtitle is older than this picture, trash it + * The end of the subtitle is less than the start, + * this is a sign of a PTS discontinuity. */ - 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( sub->start > cur->start ) { - if ( cur->sub == NULL ) + /* + * we haven't reached the start time yet, or + * we have jumped backwards after having + * already started this subtitle. + */ + if( cur->start < sub->stop ) { /* - * Tack onto the video buffer for rendering + * We have jumped backwards and so should + * continue displaying this subtitle. + * + * fall through to display. */ - 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 + { + /* + * Defer until the play point is + * within the subtitle + */ + sub = NULL; } } else { /* - * Pass-Through, pop it off of the raw queue, - * rewrite times and make it available to be - * reencoded. + * 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 */ - 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 ); + 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 { /* - * 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 ); - } + * Pass-Through, pop it off of the raw queue, + */ + sub = hb_fifo_get( subtitle->fifo_raw ); + 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_sync, sub ); } } } - } // end subtitles + } + } // 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_out = cur; + sync->cur = cur = next; + cur->sub = NULL; + sync->next_pts = cur->start; + int64_t duration = cur->start - sync->pts_skip - (*buf_out)->start; + sync->pts_skip = 0; + if ( duration <= 0 ) + { + hb_log( "sync: invalid video duration %"PRId64", start %"PRId64", next %"PRId64"", + duration, (*buf_out)->start, next->start ); + } + + (*buf_out)->start = sync->next_start; + sync->next_start += duration; + (*buf_out)->stop = sync->next_start; + + if ( sync->chap_mark ) + { + // 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_out)->new_chap = sync->chap_mark; + sync->chap_mark = 0; + } + + /* Update UI */ + UpdateState( w ); + + return HB_WORK_OK; +} + +// sync*Init does nothing because sync has a special initializer +// that takes care of initializing video and all audio tracks +int syncVideoInit( hb_work_object_t * w, hb_job_t * job) +{ + return 0; +} + +hb_work_object_t hb_sync_video = +{ + WORK_SYNC_VIDEO, + "Video Synchronization", + syncVideoInit, + syncVideoWork, + syncVideoClose +}; + +/*********************************************************************** + * Close Audio + *********************************************************************** + * + **********************************************************************/ +void syncAudioClose( hb_work_object_t * w ) +{ + hb_work_private_t * pv = w->private_data; + hb_sync_audio_t * sync = &pv->type.audio; + + if( w->audio->config.out.codec == HB_ACODEC_AC3 ) + { + free( sync->ac3_buf ); + } + else + { + src_delete( sync->state ); + } + + hb_lock( pv->common->mutex ); + if ( --pv->common->ref == 0 ) + { + hb_unlock( pv->common->mutex ); + hb_lock_close( &pv->common->mutex ); + free( pv->common ); + } + else + { + hb_unlock( pv->common->mutex ); + } + + free( pv ); + w->private_data = NULL; +} + +int syncAudioInit( hb_work_object_t * w, hb_job_t * job) +{ + return 0; +} + +/*********************************************************************** + * SyncAudio + *********************************************************************** + * + **********************************************************************/ +static int syncAudioWork( hb_work_object_t * w, hb_buffer_t ** buf_in, + hb_buffer_t ** buf_out ) +{ + hb_work_private_t * pv = w->private_data; + hb_job_t * job = pv->job; + hb_sync_audio_t * sync = &pv->type.audio; + hb_buffer_t * buf; + int64_t start; + + *buf_out = NULL; + buf = *buf_in; + *buf_in = NULL; + /* if the next buffer is an eof send it downstream */ + if ( buf->size <= 0 ) + { + hb_buffer_close( &buf ); + *buf_out = hb_buffer_init( 0 ); + return HB_WORK_DONE; + } + + /* Wait for start frame if doing point-to-point */ + hb_lock( pv->common->mutex ); + while ( !pv->common->start_found ) + { + if ( pv->common->audio_pts_thresh < 0 ) + { + // I would initialize this in hb_sync_init, but + // job->pts_to_start can be modified by reader + // after hb_sync_init is called. + pv->common->audio_pts_thresh = job->pts_to_start; + } + if ( buf->start < pv->common->audio_pts_thresh ) + { + hb_buffer_close( &buf ); + hb_unlock( pv->common->mutex ); + return HB_WORK_OK; + } + while ( !pv->common->start_found && + buf->start >= pv->common->audio_pts_thresh ) + { + hb_cond_timedwait( pv->common->next_frame, pv->common->mutex, 200 ); + } + } + if ( buf->start < pv->common->audio_pts_thresh ) + { + hb_buffer_close( &buf ); + hb_unlock( pv->common->mutex ); + return HB_WORK_OK; + } + hb_unlock( pv->common->mutex ); + + /* Wait till we can determine the initial pts of all streams */ + if( pv->common->pts_offset == INT64_MIN ) + { + pv->common->first_pts[sync->index+1] = buf->start; + hb_lock( pv->common->mutex ); + while( pv->common->pts_offset == INT64_MIN ) + { + // Full fifos will make us wait forever, so get the + // pts offset from the available streams if full + if (hb_fifo_is_full(w->fifo_in)) + { + getPtsOffset( w ); + hb_cond_broadcast( pv->common->next_frame ); + } + else if ( checkPtsOffset( w ) ) + hb_cond_broadcast( pv->common->next_frame ); + else + hb_cond_timedwait( pv->common->next_frame, pv->common->mutex, 200 ); + } + hb_unlock( pv->common->mutex ); + } + if( job->frame_to_stop && pv->common->count_frames >= job->frame_to_stop ) + { + hb_buffer_close( &buf ); + *buf_out = hb_buffer_init( 0 ); + return HB_WORK_DONE; + } + + if( job->pts_to_stop && sync->next_start >= job->pts_to_stop ) + { + hb_buffer_close( &buf ); + *buf_out = hb_buffer_init( 0 ); + return HB_WORK_DONE; + } + + hb_lock( pv->common->mutex ); + start = buf->start - pv->common->audio_passthru_slip; + hb_unlock( pv->common->mutex ); + if ( (int64_t)( start - sync->next_pts ) < 0 ) + { + // 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; + hb_buffer_close( &buf ); + return HB_WORK_OK; + } + sync->next_pts = start; + } + if ( sync->first_drop ) + { + // 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")", w->audio->id, + (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; + } + if ( start - sync->next_pts >= (90 * 70) ) + { + if ( start - sync->next_pts > (90000LL * 60) ) + { + // 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)), + w->audio->id, start, sync->next_pts ); + hb_buffer_close( &buf ); + return HB_WORK_OK; + } /* - * 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. + * 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. */ - 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 ) + if( w->audio->config.out.codec == HB_ACODEC_DCA ) { - hb_log( "sync: invalid video duration %lld, start %lld, next %lld", - duration, buf_tmp->start, next->start ); + hb_log( "sync: audio gap %d ms. Skipping frames. Audio %d" + " start %"PRId64", next %"PRId64, + (int)((start - sync->next_pts) / 90), + w->audio->id, start, sync->next_pts ); + hb_lock( pv->common->mutex ); + pv->common->audio_passthru_slip += (start - sync->next_pts); + pv->common->video_pts_slip += (start - sync->next_pts); + hb_unlock( pv->common->mutex ); + *buf_out = buf; + return HB_WORK_OK; } + hb_log( "sync: adding %d ms of silence to audio %d" + " start %"PRId64", next %"PRId64, + (int)((start - sync->next_pts) / 90), + w->audio->id, start, sync->next_pts ); + InsertSilence( w, start - sync->next_pts ); + } + + /* + * 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_out = OutputAudioFrame( w->audio, buf, sync ); + return HB_WORK_OK; +} - buf_tmp->start = pv->next_start; - pv->next_start += duration; - buf_tmp->stop = pv->next_start; +hb_work_object_t hb_sync_audio = +{ + WORK_SYNC_AUDIO, + "AudioSynchronization", + syncAudioInit, + syncAudioWork, + syncAudioClose +}; - if ( pv->chap_mark ) +static void InitAudio( hb_job_t * job, hb_sync_common_t * common, int i ) +{ + hb_work_object_t * w; + hb_work_private_t * pv; + hb_title_t * title = job->title; + hb_sync_audio_t * sync; + + pv = calloc( 1, sizeof( hb_work_private_t ) ); + sync = &pv->type.audio; + sync->index = i; + pv->job = job; + pv->common = common; + pv->common->ref++; + pv->common->pts_count++; + + w = hb_get_work( WORK_SYNC_AUDIO ); + w->private_data = pv; + w->audio = hb_list_item( title->list_audio, i ); + w->fifo_in = w->audio->priv.fifo_raw; + + if( w->audio->config.out.codec == HB_ACODEC_AC3 || + w->audio->config.out.codec == HB_ACODEC_DCA ) + { + w->fifo_out = w->audio->priv.fifo_out; + } + else + { + w->fifo_out = w->audio->priv.fifo_sync; + } + + if( w->audio->config.out.codec == HB_ACODEC_AC3 ) + { + /* Have a silent AC-3 frame ready in case we have to fill a + gap */ + AVCodec * codec; + AVCodecContext * c; + short * zeros; + + codec = avcodec_find_encoder( CODEC_ID_AC3 ); + c = avcodec_alloc_context(); + + c->bit_rate = w->audio->config.in.bitrate; + c->sample_rate = w->audio->config.in.samplerate; + c->channels = HB_INPUT_CH_LAYOUT_GET_DISCRETE_COUNT( w->audio->config.in.channel_layout ); + + if( hb_avcodec_open( c, codec ) < 0 ) { - // 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; + hb_log( "sync: avcodec_open failed" ); + return; } - /* Push the frame to the renderer */ - hb_fifo_push( job->fifo_sync, buf_tmp ); + zeros = calloc( AC3_SAMPLES_PER_FRAME * + sizeof( short ) * c->channels, 1 ); + sync->ac3_size = w->audio->config.in.bitrate * AC3_SAMPLES_PER_FRAME / + w->audio->config.in.samplerate / 8; + sync->ac3_buf = malloc( sync->ac3_size ); - /* Update UI */ - UpdateState( w ); - - if( job->frame_to_stop && pv->count_frames > job->frame_to_stop ) + if( avcodec_encode_audio( c, sync->ac3_buf, sync->ac3_size, + zeros ) != sync->ac3_size ) { - // 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; + hb_log( "sync: avcodec_encode_audio failed" ); } + + free( zeros ); + hb_avcodec_close( c ); + av_free( c ); } + else + { + /* Initialize libsamplerate */ + int error; + sync->state = src_new( SRC_SINC_MEDIUM_QUALITY, + HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT( + w->audio->config.out.mixdown), &error ); + sync->data.end_of_input = 0; + } + hb_list_add( job->list_work, w ); } -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 ) +static hb_buffer_t * OutputAudioFrame( hb_audio_t *audio, hb_buffer_t *buf, + hb_sync_audio_t *sync ) { int64_t start = sync->next_start; int64_t duration = buf->stop - buf->start; @@ -797,7 +1213,7 @@ static void OutputAudioFrame( hb_job_t *job, hb_audio_t *audio, hb_buffer_t *buf if( src_process( sync->state, &sync->data ) ) { /* XXX If this happens, we're screwed */ - hb_log( "sync: audio %d src_process failed", i ); + hb_log( "sync: audio %d src_process failed", audio->id ); } hb_buffer_close( &buf_raw ); @@ -809,136 +1225,13 @@ static void OutputAudioFrame( hb_job_t *job, hb_audio_t *audio, hb_buffer_t *buf buf->start = start; buf->stop = start + duration; sync->next_start = start + duration; - hb_fifo_push( fifo, buf ); + return buf; } -/*********************************************************************** - * SyncAudio - *********************************************************************** - * - **********************************************************************/ -static void SyncAudio( hb_work_object_t * w, int i ) +static void InsertSilence( hb_work_object_t * w, int64_t duration ) { 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_fifo_t * fifo; - int64_t start; - - if( audio->config.out.codec == HB_ACODEC_AC3 || - audio->config.out.codec == HB_ACODEC_DCA ) - { - fifo = audio->priv.fifo_out; - } - else - { - fifo = audio->priv.fifo_sync; - } - - while( !hb_fifo_is_full( fifo ) && ( buf = hb_fifo_see( audio->priv.fifo_raw ) ) ) - { - start = buf->start - pv->audio_passthru_slip; - /* if the next buffer is an eof send it downstream */ - if ( buf->size <= 0 ) - { - buf = hb_fifo_get( audio->priv.fifo_raw ); - hb_fifo_push( fifo, buf ); - pv->busy &=~ (1 << (i + 1) ); - return; - } - if( job->frame_to_stop && pv->count_frames >= job->frame_to_stop ) - { - hb_fifo_push( fifo, hb_buffer_init(0) ); - pv->busy &=~ (1 << (i + 1) ); - return; - } - if ( (int64_t)( start - sync->next_pts ) < 0 ) - { - // 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 ( sync->first_drop ) - { - // 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 %lld, current %lld)", 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; - } - if ( start - sync->next_pts >= (90 * 70) ) - { - if ( start - sync->next_pts > (90000LL * 60) ) - { - // 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 %lld, next %lld", - (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; - } - /* - * 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 %lld, next %lld", - (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 %lld, next %lld", - (int)((start - sync->next_pts) / 90), - i, start, sync->next_pts ); - InsertSilence( w, i, start - sync->next_pts ); - return; - } - - /* - * 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 ); - } -} - -static void InsertSilence( hb_work_object_t * w, int i, int64_t duration ) -{ - hb_work_private_t * pv = w->private_data; - hb_job_t *job = pv->job; - hb_sync_audio_t *sync = &pv->sync_audio[i]; + hb_sync_audio_t *sync = &pv->type.audio; hb_buffer_t *buf; hb_fifo_t *fifo; @@ -947,70 +1240,74 @@ static void InsertSilence( hb_work_object_t * w, int i, int64_t duration ) // 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; + w->audio->config.in.samplerate; int frame_count = ( duration + (frame_dur >> 1) ) / frame_dur; while ( --frame_count >= 0 ) { - if( sync->audio->config.out.codec == HB_ACODEC_AC3 ) + if( w->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; + fifo = w->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) ); + w->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; + fifo = w->audio->priv.fifo_sync; } - OutputAudioFrame( job, sync->audio, buf, sync, fifo, i ); + buf = OutputAudioFrame( w->audio, buf, sync ); + hb_fifo_push( fifo, buf ); } } static void UpdateState( hb_work_object_t * w ) { hb_work_private_t * pv = w->private_data; + hb_sync_video_t * sync = &pv->type.video; hb_state_t state; - if( !pv->count_frames ) + if( !pv->common->count_frames ) { - pv->st_first = hb_get_date(); + sync->st_first = hb_get_date(); + pv->job->st_pause_date = -1; + pv->job->st_paused = 0; } - pv->count_frames++; + pv->common->count_frames++; - if( hb_get_date() > pv->st_dates[3] + 1000 ) + if( hb_get_date() > sync->st_dates[3] + 1000 ) { - memmove( &pv->st_dates[0], &pv->st_dates[1], + memmove( &sync->st_dates[0], &sync->st_dates[1], 3 * sizeof( uint64_t ) ); - memmove( &pv->st_counts[0], &pv->st_counts[1], + memmove( &sync->st_counts[0], &sync->st_counts[1], 3 * sizeof( uint64_t ) ); - pv->st_dates[3] = hb_get_date(); - pv->st_counts[3] = pv->count_frames; + sync->st_dates[3] = hb_get_date(); + sync->st_counts[3] = pv->common->count_frames; } #define p state.param.working state.state = HB_STATE_WORKING; - p.progress = (float) pv->count_frames / (float) pv->count_frames_max; + p.progress = (float) pv->common->count_frames / (float) sync->count_frames_max; if( p.progress > 1.0 ) { p.progress = 1.0; } p.rate_cur = 1000.0 * - (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 ) + (float) ( sync->st_counts[3] - sync->st_counts[0] ) / + (float) ( sync->st_dates[3] - sync->st_dates[0] ); + if( hb_get_date() > sync->st_first + 4000 ) { int eta; - 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 = 1000.0 * (float) sync->st_counts[3] / + (float) ( sync->st_dates[3] - sync->st_first - pv->job->st_paused); + eta = (float) ( sync->count_frames_max - sync->st_counts[3] ) / p.rate_avg; p.hours = eta / 3600; p.minutes = ( eta % 3600 ) / 60; @@ -1027,3 +1324,92 @@ static void UpdateState( hb_work_object_t * w ) hb_set_state( pv->job->h, &state ); } + +static void UpdateSearchState( hb_work_object_t * w, int64_t start ) +{ + hb_work_private_t * pv = w->private_data; + hb_sync_video_t * sync = &pv->type.video; + hb_state_t state; + uint64_t now; + double avg; + + now = hb_get_date(); + if( !pv->common->count_frames ) + { + sync->st_first = now; + pv->job->st_pause_date = -1; + pv->job->st_paused = 0; + } + pv->common->count_frames++; + +#define p state.param.working + state.state = HB_STATE_SEARCHING; + if ( pv->job->frame_to_start ) + p.progress = (float) pv->common->count_frames / + (float) pv->job->frame_to_start; + else if ( pv->job->pts_to_start ) + p.progress = (float) start / (float) pv->job->pts_to_start; + else + p.progress = 0; + if( p.progress > 1.0 ) + { + p.progress = 1.0; + } + if (now > sync->st_first) + { + int eta; + + if ( pv->job->frame_to_start ) + { + avg = 1000.0 * (double)pv->common->count_frames / (now - sync->st_first); + eta = ( pv->job->frame_to_start - pv->common->count_frames ) / avg; + } + else if ( pv->job->pts_to_start ) + { + avg = 1000.0 * (double)start / (now - sync->st_first); + eta = ( pv->job->pts_to_start - start ) / avg; + } + p.hours = eta / 3600; + p.minutes = ( eta % 3600 ) / 60; + p.seconds = eta % 60; + } + else + { + p.rate_avg = 0.0; + p.hours = -1; + p.minutes = -1; + p.seconds = -1; + } +#undef p + + hb_set_state( pv->job->h, &state ); +} + +static void getPtsOffset( hb_work_object_t * w ) +{ + hb_work_private_t * pv = w->private_data; + int i ; + int64_t first_pts = INT64_MAX; + + for( i = 0; i < pv->common->pts_count; i++ ) + { + if ( pv->common->first_pts[i] < first_pts ) + first_pts = pv->common->first_pts[i]; + } + pv->common->audio_passthru_slip = pv->common->pts_offset = first_pts; + return; +} + +static int checkPtsOffset( hb_work_object_t * w ) +{ + hb_work_private_t * pv = w->private_data; + int i ; + + for( i = 0; i < pv->common->pts_count; i++ ) + { + if ( pv->common->first_pts[i] == INT64_MAX ) + return 0; + } + getPtsOffset( w ); + return 1; +}