X-Git-Url: http://git.osdn.jp/view?a=blobdiff_plain;f=libhb%2Fsync.c;h=9c36ee30a72eaf4df45d411e392825fd84909b5f;hb=d7389faafa327bb1be3340c34c30f353a8f52960;hp=cd526c11acd679101248c8de452fa342ac03d475;hpb=68e3a3c68e1af09f9547b2deca6bc4716e7ee10d;p=handbrake-jp%2Fhandbrake-jp-git.git diff --git a/libhb/sync.c b/libhb/sync.c index cd526c11..9c36ee30 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 */ @@ -18,9 +18,29 @@ typedef struct { - hb_audio_t * audio; - int64_t count_frames; - + 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 */ + /* Raw */ SRC_STATE * state; SRC_DATA data; @@ -28,635 +48,1266 @@ 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 done; - /* Video */ - hb_subtitle_t * subtitle; - int64_t pts_offset; - int64_t pts_offset_old; - int64_t count_frames; - int64_t count_frames_max; - hb_buffer_t * cur; /* The next picture to process */ - - /* Audio */ - hb_sync_audio_t sync_audio[8]; + 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 int 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 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->pts_offset_old = INT64_MIN; - pv->count_frames = 0; + pv->common->pts_offset = INT64_MIN; + sync->first_frame = 1; - /* 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 ); + sync->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 */ - pv->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 */ + } + sync->count_frames_max = duration * title->rate / title->rate_base / 90000; + } - hb_log( "sync: expecting %lld video frames", pv->count_frames_max ); + hb_log( "sync: expecting %d video frames", sync->count_frames_max ); /* Initialize libsamplerate for every audio track we have */ - for( i = 0; i < hb_list_count( title->list_audio ); i++ ) + if ( ! job->indepth_scan ) { - InitAudio( w, i ); + for( i = 0; i < hb_list_count( title->list_audio ) && i < 8; 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; - /* Get subtitle info, if any */ - pv->subtitle = hb_list_item( title->list_subtitle, 0 ); - - 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; - - int i; + hb_sync_video_t * sync = &pv->type.video; - if( pv->cur ) hb_buffer_close( &pv->cur ); + // 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 ); - for( i = 0; i < hb_list_count( title->list_audio ); i++ ) + if( sync->cur ) { - if( job->acodec & HB_ACODEC_AC3 ) - { - free( pv->sync_audio[i].ac3_buf ); - } - else - { - src_delete( pv->sync_audio[i].state ); - } + hb_buffer_close( &sync->cur ); } + + hb_log( "sync: got %d frames, %d expected", + 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->common->count_frames; + interjob->last_job = job->sequence_id; + interjob->total_time = sync->next_start; + } + + if (sync->drops || sync->dups ) + { + hb_log( "sync: %d frames dropped, %d duplicated", + sync->drops, sync->dups ); + } + + 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; } /*********************************************************************** - * Work + * syncVideoWork *********************************************************************** - * The root routine of this work abject + * **********************************************************************/ -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 we ever got a video frame, handle audio now */ - if( pv->pts_offset != INT64_MIN ) + *buf_out = NULL; + next = *buf_in; + *buf_in = NULL; + + /* Wait for start of point-to-point encoding */ + if( !pv->common->start_found ) { - for( i = 0; i < hb_list_count( pv->job->title->list_audio ); i++ ) + hb_sync_video_t * sync = &pv->type.video; + + if( next->size == 0 ) { - SyncAudio( w, i ); + *buf_out = next; + 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; } - } - - /* Handle video */ - return SyncVideo( w ); -} - -hb_work_object_t hb_sync = -{ - WORK_SYNC, - "Synchronization", - syncInit, - syncWork, - syncClose -}; + 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( job->acodec & 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->bitrate; - c->sample_rate = sync->audio->rate; - c->channels = sync->audio->channels; - - if( 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->bitrate * AC3_SAMPLES_PER_FRAME / - sync->audio->rate / 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 ); + + 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; } - - free( zeros ); - avcodec_close( c ); - av_free( c ); + 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_LINEAR, 2, &error ); - sync->data.end_of_input = 0; - } -} + hb_buffer_close( &next ); + cur->start = sync->next_start; + cur->stop = cur->start + 90000. / ((double)job->vrate / (double)job->vrate_base); + /* Make sure last frame is reflected in frame count */ + pv->common->count_frames++; -#define PTS_DISCONTINUITY_TOLERANCE 90000 - -/*********************************************************************** - * SyncVideo - *********************************************************************** - * - **********************************************************************/ -static int 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; - int64_t pts_expected; + /* 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. + */ + 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 ) ); + } + } + pv->common->start_found = 1; + hb_cond_broadcast( pv->common->next_frame ); + return HB_WORK_DONE; + } - if( pv->done ) + /* Check for end of point-to-point frame encoding */ + if( job->frame_to_stop && pv->common->count_frames > job->frame_to_stop ) { + // 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 ); + + /* + * 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( hb_thread_has_exited( job->reader ) && - !hb_fifo_size( job->fifo_mpeg2 ) && - !hb_fifo_size( job->fifo_raw ) ) + /* Check for end of point-to-point pts encoding */ + if( job->pts_to_stop && sync->next_start >= job->pts_to_stop ) { - /* All video data has been processed already, we won't get - more */ - hb_log( "sync: got %lld frames, %lld expected", - pv->count_frames, pv->count_frames_max ); - pv->done = 1; + // 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 ); + + /* + * 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->cur && !( pv->cur = hb_fifo_get( job->fifo_raw ) ) ) + if( sync->first_frame ) { - /* We haven't even got a frame yet */ - return HB_WORK_OK; + /* This is our first frame */ + if ( cur->start > pv->common->pts_offset ) + { + /* + * 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; } - cur = pv->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 */ - while( !hb_fifo_is_full( job->fifo_sync ) && - ( next = hb_fifo_see( job->fifo_raw ) ) ) + /* + * 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 ) { - hb_buffer_t * buf_tmp; - - if( pv->pts_offset == INT64_MIN ) + if ( sync->first_drop == 0 ) + { + sync->first_drop = next->start; + } + ++sync->drop_count; + if (next->start - cur->start > 0) { - /* This is our first frame */ - hb_log( "sync: first pts is %lld", cur->start ); - pv->pts_offset = cur->start; + 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; + } - /* Check for PTS jumps over 0.5 second */ - if( next->start < cur->start - PTS_DISCONTINUITY_TOLERANCE || - next->start > cur->start + PTS_DISCONTINUITY_TOLERANCE ) + /* + * 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 ); + + /* + * Rewrite timestamps on subtitles that need it (on raw queue). + */ + if( subtitle->source == CC608SUB || + subtitle->source == CC708SUB || + subtitle->source == SRTSUB || + subtitle->source == UTF8SUB || + subtitle->source == TX3GSUB || + subtitle->source == SSASUB) { - hb_log( "PTS discontinuity (%lld, %lld)", - cur->start, next->start ); - - /* Trash all subtitles */ - if( pv->subtitle ) + /* + * Rewrite timestamps on subtitles that came from Closed Captions + * since they are using the MPEG2 timestamps. + */ + while( ( sub = hb_fifo_see( subtitle->fifo_raw ) ) ) { - while( ( sub = hb_fifo_get( pv->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 ) { - hb_buffer_close( &sub ); + 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 ) + { + sub = hb_fifo_get( subtitle->fifo_raw ); + hb_fifo_push( subtitle->fifo_out, sub ); + } else { + sub = NULL; + break; + } } } - - /* Trash current picture */ - hb_buffer_close( &cur ); - pv->cur = cur = hb_fifo_get( job->fifo_raw ); - - /* Calculate new offset */ - pv->pts_offset_old = pv->pts_offset; - pv->pts_offset = cur->start - - pv->count_frames * pv->job->vrate_base / 300; - continue; } - /* Look for a subtitle for this frame */ - if( pv->subtitle ) + if( subtitle->source == VOBSUB ) { hb_buffer_t * sub2; - while( ( sub = hb_fifo_see( pv->subtitle->fifo_raw ) ) ) + 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( pv->subtitle->fifo_raw ); + sub2 = hb_fifo_see2( subtitle->fifo_raw ); if( sub2 && sub->stop > sub2->start ) + { sub->stop = sub2->start; - - if( sub->stop > cur->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; - - /* The subtitle is older than this picture, trash it */ - sub = hb_fifo_get( pv->subtitle->fifo_raw ); - hb_buffer_close( &sub ); + } + + 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 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 ) + + /* If we have a subtitle for this picture, copy it */ + /* FIXME: we should avoid this memcpy */ + if( sub ) { - sub = NULL; + 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, + */ + 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 + + /* + * 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 ); + } - /* The PTS of the frame we are expecting now */ - pts_expected = pv->pts_offset + - pv->count_frames * pv->job->vrate_base / 300; + (*buf_out)->start = sync->next_start; + sync->next_start += duration; + (*buf_out)->stop = sync->next_start; - if( cur->start < pts_expected - pv->job->vrate_base / 300 / 2 && - next->start < pts_expected + pv->job->vrate_base / 300 / 2 ) - { - /* The current frame is too old but the next one matches, - let's trash */ - hb_buffer_close( &cur ); - pv->cur = cur = hb_fifo_get( job->fifo_raw ); - continue; - } + 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; + } - if( next->start > pts_expected + 3 * pv->job->vrate_base / 300 / 2 ) - { - /* 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 - { - /* The frame has the expected date and won't have to be - duplicated, just put it through */ - buf_tmp = cur; - pv->cur = cur = hb_fifo_get( job->fifo_raw ); - } + /* Update UI */ + UpdateState( w ); - /* Replace those MPEG-2 dates with our dates */ - buf_tmp->start = (uint64_t) pv->count_frames * - pv->job->vrate_base / 300; - buf_tmp->stop = (uint64_t) ( pv->count_frames + 1 ) * - pv->job->vrate_base / 300; + return HB_WORK_OK; +} - /* If we have a subtitle for this picture, copy it */ - /* FIXME: we should avoid this memcpy */ - if( sub ) - { - 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 ); - } +// 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; +} - /* Push the frame to the renderer */ - hb_fifo_push( job->fifo_sync, buf_tmp ); +hb_work_object_t hb_sync_video = +{ + WORK_SYNC_VIDEO, + "Video Synchronization", + syncVideoInit, + syncVideoWork, + syncVideoClose +}; - /* Update UI */ - UpdateState( w ); +/*********************************************************************** + * 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; - /* Make sure we won't get more frames then expected */ - if( pv->count_frames >= pv->count_frames_max ) - { - hb_log( "sync: got %lld frames", pv->count_frames ); - pv->done = 1; - break; - } + if( w->audio->config.out.codec == HB_ACODEC_AC3 ) + { + free( sync->ac3_buf ); + } + else + { + src_delete( sync->state ); } - return HB_WORK_OK; + 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 void SyncAudio( hb_work_object_t * w, int i ) +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; - hb_audio_t * audio; + hb_job_t * job = pv->job; + hb_sync_audio_t * sync = &pv->type.audio; hb_buffer_t * buf; - hb_sync_audio_t * sync; - - hb_fifo_t * fifo; - int rate; - - int64_t pts_expected; - int64_t start; + int64_t start; - job = pv->job; - sync = &pv->sync_audio[i]; - audio = sync->audio; + *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; + } - if( job->acodec & HB_ACODEC_AC3 ) + /* Wait for start frame if doing point-to-point */ + hb_lock( pv->common->mutex ); + while ( !pv->common->start_found ) { - fifo = audio->fifo_out; - rate = audio->rate; + 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 ); + } } - else + if ( buf->start < pv->common->audio_pts_thresh ) { - fifo = audio->fifo_sync; - rate = job->arate; + hb_buffer_close( &buf ); + hb_unlock( pv->common->mutex ); + return HB_WORK_OK; } + hb_unlock( pv->common->mutex ); - while( !hb_fifo_is_full( fifo ) && - ( buf = hb_fifo_see( audio->fifo_raw ) ) ) + /* Wait till we can determine the initial pts of all streams */ + if( pv->common->pts_offset == INT64_MIN ) { - /* The PTS of the samples we are expecting now */ - pts_expected = pv->pts_offset + sync->count_frames * 90000 / rate; - - if( ( buf->start > pts_expected + PTS_DISCONTINUITY_TOLERANCE || - buf->start < pts_expected - PTS_DISCONTINUITY_TOLERANCE ) && - pv->pts_offset_old > INT64_MIN ) + pv->common->first_pts[sync->index+1] = buf->start; + hb_lock( pv->common->mutex ); + while( pv->common->pts_offset == INT64_MIN ) { - /* There has been a PTS discontinuity, and this frame might - be from before the discontinuity */ - pts_expected = pv->pts_offset_old + sync->count_frames * - 90000 / rate; - - if( buf->start > pts_expected + PTS_DISCONTINUITY_TOLERANCE || - buf->start < pts_expected - PTS_DISCONTINUITY_TOLERANCE ) + // 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)) { - /* There is really nothing we can do with it */ - buf = hb_fifo_get( audio->fifo_raw ); - hb_buffer_close( &buf ); - continue; + getPtsOffset( w ); + hb_cond_broadcast( pv->common->next_frame ); } - - /* Use the older offset */ - start = pts_expected - pv->pts_offset_old; + else if ( checkPtsOffset( w ) ) + hb_cond_broadcast( pv->common->next_frame ); + else + hb_cond_timedwait( pv->common->next_frame, pv->common->mutex, 200 ); } - else + 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 ) { - start = pts_expected - pv->pts_offset; + // 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; } - - /* Tolerance: 100 ms */ - if( buf->start < pts_expected - 9000 ) + 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) ) { - /* Late audio, trash it */ - hb_log( "sync: trashing late audio" ); - buf = hb_fifo_get( audio->fifo_raw ); + // 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 ); - continue; + return HB_WORK_OK; } - else if( buf->start > pts_expected + 9000 ) + /* + * 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( w->audio->config.out.codec == HB_ACODEC_DCA ) { - /* Missing audio, send a frame of silence */ - InsertSilence( w, i ); - continue; + 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 ); + } - if( job->acodec & HB_ACODEC_AC3 ) - { - buf = hb_fifo_get( audio->fifo_raw ); - buf->start = start; - buf->stop = start + 90000 * AC3_SAMPLES_PER_FRAME / rate; + /* + * 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; +} - sync->count_frames += AC3_SAMPLES_PER_FRAME; - } - else - { - hb_buffer_t * buf_raw = hb_fifo_get( audio->fifo_raw ); +hb_work_object_t hb_sync_audio = +{ + WORK_SYNC_AUDIO, + "AudioSynchronization", + syncAudioInit, + syncAudioWork, + syncAudioClose +}; - int count_in, count_out; +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; - 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++; + 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; - sync->data.data_in = (float *) buf_raw->data; - sync->data.input_frames = count_in; - sync->data.output_frames = count_out; + 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; + } - sync->data.src_ratio = (double) sync->data.output_frames / - (double) sync->data.input_frames; + 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; - 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 ) ) - { - /* XXX If this happens, we're screwed */ - hb_log( "sync: src_process failed" ); - } - hb_buffer_close( &buf_raw ); + codec = avcodec_find_encoder( CODEC_ID_AC3 ); + c = avcodec_alloc_context(); - buf->size = sync->data.output_frames_gen * 2 * sizeof( float ); + 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 ); - /* Set dates for resampled data */ - buf->start = start; - buf->stop = start + sync->data.output_frames_gen * - 90000 / job->arate; + if( hb_avcodec_open( c, codec ) < 0 ) + { + hb_log( "sync: avcodec_open failed" ); + return; + } - sync->count_frames += sync->data.output_frames_gen; + 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 ); + + if( avcodec_encode_audio( c, sync->ac3_buf, sync->ac3_size, + zeros ) != sync->ac3_size ) + { + hb_log( "sync: avcodec_encode_audio failed" ); } - buf->key = 1; - hb_fifo_push( fifo, buf ); + free( zeros ); + hb_avcodec_close( c ); + av_free( c ); } - - if( NeedSilence( w, audio ) ) + else { - InsertSilence( w, i ); + /* 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 int NeedSilence( hb_work_object_t * w, hb_audio_t * audio ) +static hb_buffer_t * OutputAudioFrame( hb_audio_t *audio, hb_buffer_t *buf, + hb_sync_audio_t *sync ) { - hb_work_private_t * pv = w->private_data; - hb_job_t * job = pv->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; - } + int64_t start = sync->next_start; + int64_t duration = buf->stop - buf->start; - /* No audio left in fifos */ + sync->next_pts += duration; - if( hb_thread_has_exited( job->reader ) ) + if( audio->config.in.samplerate == audio->config.out.samplerate || + audio->config.out.codec == HB_ACODEC_AC3 || + audio->config.out.codec == HB_ACODEC_DCA ) { - /* We might miss some audio to complete encoding and muxing - the video track */ - return 1; + /* + * 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. + */ } - - 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 ) ) + else { - /* Too much video and no audio, oh-oh */ - return 1; - } + /* 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", audio->id ); + } + hb_buffer_close( &buf_raw ); - return 0; + 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; + return buf; } -static void InsertSilence( 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; - hb_sync_audio_t * sync; - hb_buffer_t * buf; - - job = pv->job; - sync = &pv->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_sync_audio_t *sync = &pv->type.audio; + 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 ) / + w->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( 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 = w->audio->priv.fifo_out; + } + else + { + buf = hb_buffer_init( AC3_SAMPLES_PER_FRAME * sizeof( float ) * + HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT( + w->audio->config.out.mixdown) ); + buf->start = sync->next_pts; + buf->stop = buf->start + frame_dur; + memset( buf->data, 0, buf->size ); + fifo = w->audio->priv.fifo_sync; + } + 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.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; @@ -673,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; +}