X-Git-Url: http://git.osdn.jp/view?a=blobdiff_plain;f=libhb%2Fsync.c;h=94b8411b491ca8309d9d1011c4399cbee003e7cc;hb=033e32de9c380f54c7d1362a3979da205ebc3a29;hp=3521ad036e4edd5cbbeb9429cd9263f71d4cfc70;hpb=60def64e919bc7db74142f636cad58d9c932d28f;p=handbrake-jp%2Fhandbrake-jp-git.git diff --git a/libhb/sync.c b/libhb/sync.c index 3521ad03..94b8411b 100644 --- a/libhb/sync.c +++ b/libhb/sync.c @@ -1,13 +1,13 @@ /* $Id: sync.c,v 1.38 2005/04/14 21:57:58 titer Exp $ This file is part of the HandBrake source code. - Homepage: . + Homepage: . It may be used under the terms of the GNU General Public License. */ #include "hb.h" - +#include "hbffmpeg.h" +#include #include "samplerate.h" -#include "ffmpeg/avcodec.h" #ifdef INT64_MIN #undef INT64_MIN /* Because it isn't defined correctly in Zeta */ @@ -19,8 +19,12 @@ typedef struct { hb_audio_t * audio; - int64_t count_frames; - + + int64_t next_start; /* start time of next output frame */ + int64_t next_pts; /* start time of next input frame */ + int64_t first_drop; /* PTS of first 'went backwards' frame dropped */ + int drop_count; /* count of 'time went backwards' drops */ + /* Raw */ SRC_STATE * state; SRC_DATA data; @@ -34,42 +38,43 @@ typedef struct struct hb_work_private_s { hb_job_t * job; - int done; - + int busy; // bitmask with one bit for each active input + // (bit 0 = video; 1 = audio 0, 2 = audio 1, ... + // appropriate bit is cleared when input gets + // an eof buf. syncWork returns done when all + // bits are clear. /* Video */ - hb_subtitle_t * subtitle; int64_t pts_offset; - int64_t pts_offset_old; - int64_t count_frames; - int64_t count_frames_max; - int64_t video_sequence; + 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]; - - /* Flags */ - int discontinuity; + int64_t audio_passthru_slip; + int64_t video_pts_slip; /* Statistics */ uint64_t st_counts[4]; uint64_t st_dates[4]; uint64_t st_first; - - /* Throttle message flags */ - int trashing_audio; - int inserting_silence; - int way_out_of_sync; }; /*********************************************************************** * Local prototypes **********************************************************************/ static void InitAudio( hb_work_object_t * w, int i ); -static int SyncVideo( hb_work_object_t * w ); +static void SyncVideo( hb_work_object_t * w ); static void SyncAudio( hb_work_object_t * w, int i ); -static int NeedSilence( hb_work_object_t * w, hb_audio_t * ); -static void InsertSilence( hb_work_object_t * w, int i ); +static void InsertSilence( hb_work_object_t * w, int i, int64_t d ); static void UpdateState( hb_work_object_t * w ); /*********************************************************************** @@ -90,39 +95,52 @@ int syncInit( hb_work_object_t * w, hb_job_t * job ) pv->job = job; pv->pts_offset = INT64_MIN; - pv->pts_offset_old = INT64_MIN; - pv->count_frames = 0; - - pv->discontinuity = 0; - - pv->trashing_audio = 0; - pv->inserting_silence = 0; - pv->way_out_of_sync = 0; - /* Calculate how many video frames we are expecting */ - duration = 0; - for( i = job->chapter_start; i <= job->chapter_end; i++ ) + if( job->pass == 2 ) + { + /* We already have an accurate frame count from pass 1 */ + hb_interjob_t * interjob = hb_interjob_get( job->h ); + pv->count_frames_max = interjob->frame_count; + } + else { - chapter = hb_list_item( title->list_chapter, i - 1 ); - duration += chapter->duration; - } - duration += 90000; - /* 1 second safety so we're sure we won't miss anything */ - 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 */ + } + pv->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", pv->count_frames_max ); + pv->busy |= 1; /* 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++ ) + { + pv->busy |= ( 1 << (i + 1) ); + InitAudio( w, i ); + } } - /* Get subtitle info, if any */ - pv->subtitle = hb_list_item( title->list_subtitle, 0 ); - - pv->video_sequence = 0; - return 0; } @@ -136,14 +154,36 @@ void syncClose( hb_work_object_t * w ) hb_work_private_t * pv = w->private_data; hb_job_t * job = pv->job; hb_title_t * title = job->title; - + hb_audio_t * audio = NULL; int i; - if( pv->cur ) hb_buffer_close( &pv->cur ); + if( pv->cur ) + { + hb_buffer_close( &pv->cur ); + } + + hb_log( "sync: got %d frames, %d expected", + pv->count_frames, pv->count_frames_max ); + + /* save data for second pass */ + if( job->pass == 1 ) + { + /* Preserve frame count for better accuracy in pass 2 */ + hb_interjob_t * interjob = hb_interjob_get( job->h ); + interjob->frame_count = pv->count_frames; + interjob->last_job = job->sequence_id; + interjob->total_time = pv->next_start; + } + + if (pv->drops || pv->dups ) + { + hb_log( "sync: %d frames dropped, %d duplicated", pv->drops, pv->dups ); + } for( i = 0; i < hb_list_count( title->list_audio ); i++ ) { - if( job->acodec & HB_ACODEC_AC3 ) + audio = hb_list_item( title->list_audio, i ); + if( audio->config.out.codec == HB_ACODEC_AC3 ) { free( pv->sync_audio[i].ac3_buf ); } @@ -152,7 +192,7 @@ void syncClose( hb_work_object_t * w ) src_delete( pv->sync_audio[i].state ); } } - + free( pv ); w->private_data = NULL; } @@ -173,17 +213,16 @@ int syncWork( hb_work_object_t * w, hb_buffer_t ** unused1, hb_work_private_t * pv = w->private_data; int i; - /* If we ever got a video frame, handle audio now */ - if( pv->pts_offset != INT64_MIN ) + if ( pv->busy & 1 ) + SyncVideo( w ); + + for( i = 0; i < hb_list_count( pv->job->title->list_audio ); i++ ) { - for( i = 0; i < hb_list_count( pv->job->title->list_audio ); i++ ) - { + if ( pv->busy & ( 1 << (i + 1) ) ) SyncAudio( w, i ); - } } - /* Handle video */ - return SyncVideo( w ); + return ( pv->busy? HB_WORK_OK : HB_WORK_DONE ); } hb_work_object_t hb_sync = @@ -205,7 +244,7 @@ static void InitAudio( hb_work_object_t * w, int i ) sync = &pv->sync_audio[i]; sync->audio = hb_list_item( title->list_audio, i ); - if( job->acodec & HB_ACODEC_AC3 ) + if( sync->audio->config.out.codec == HB_ACODEC_AC3 ) { /* Have a silent AC-3 frame ready in case we have to fill a gap */ @@ -216,11 +255,11 @@ static void InitAudio( hb_work_object_t * w, int i ) codec = avcodec_find_encoder( CODEC_ID_AC3 ); c = avcodec_alloc_context(); - c->bit_rate = sync->audio->bitrate; - c->sample_rate = sync->audio->rate; - c->channels = 2; + c->bit_rate = sync->audio->config.in.bitrate; + c->sample_rate = sync->audio->config.in.samplerate; + c->channels = HB_INPUT_CH_LAYOUT_GET_DISCRETE_COUNT( sync->audio->config.in.channel_layout ); - if( avcodec_open( c, codec ) < 0 ) + if( hb_avcodec_open( c, codec ) < 0 ) { hb_log( "sync: avcodec_open failed" ); return; @@ -228,8 +267,8 @@ static void InitAudio( hb_work_object_t * w, int i ) zeros = calloc( AC3_SAMPLES_PER_FRAME * sizeof( short ) * c->channels, 1 ); - sync->ac3_size = sync->audio->bitrate * AC3_SAMPLES_PER_FRAME / - sync->audio->rate / 8; + sync->ac3_size = sync->audio->config.in.bitrate * AC3_SAMPLES_PER_FRAME / + sync->audio->config.in.samplerate / 8; sync->ac3_buf = malloc( sync->ac3_size ); if( avcodec_encode_audio( c, sync->ac3_buf, sync->ac3_size, @@ -237,327 +276,448 @@ static void InitAudio( hb_work_object_t * w, int i ) { hb_log( "sync: avcodec_encode_audio failed" ); } - + free( zeros ); - avcodec_close( c ); + hb_avcodec_close( c ); av_free( c ); } else { /* Initialize libsamplerate */ int error; - sync->state = src_new( SRC_LINEAR, HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT(sync->audio->amixdown), &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; } } - - -#define PTS_DISCONTINUITY_TOLERANCE 90000 - /*********************************************************************** * SyncVideo *********************************************************************** - * + * **********************************************************************/ -static int SyncVideo( hb_work_object_t * w ) +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; - int64_t pts_expected; - int chap_break; + hb_subtitle_t *subtitle; + int i; + int64_t pts_skip; - if( pv->done ) + if( !pv->cur && !( pv->cur = hb_fifo_get( job->fifo_raw ) ) ) { - return HB_WORK_DONE; + /* We haven't even got a frame yet */ + return; } - - if( hb_thread_has_exited( job->reader ) && - !hb_fifo_size( job->fifo_mpeg2 ) && - !hb_fifo_size( job->fifo_raw ) ) + cur = pv->cur; + pts_skip = 0; + if( cur->size == 0 ) { - /* 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; - - hb_buffer_t * buf_tmp; + /* we got an end-of-stream. Feed it downstream & signal that we're done. */ + hb_fifo_push( job->fifo_sync, hb_buffer_init( 0 ) ); - // Drop an empty buffer into our output to ensure that things - // get flushed all the way out. - buf_tmp = hb_buffer_init(0); // Empty end buffer - hb_fifo_push( job->fifo_sync, buf_tmp ); - - return HB_WORK_DONE; - } + /* + * 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 ) + { + hb_fifo_push( subtitle->fifo_out, hb_buffer_init( 0 ) ); + } + } - if( !pv->cur && !( pv->cur = hb_fifo_get( job->fifo_raw ) ) ) - { - /* We haven't even got a frame yet */ - return HB_WORK_OK; + pv->busy &=~ 1; + return; } - 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 */ + compute the duration of the current frame*/ while( !hb_fifo_is_full( job->fifo_sync ) && ( next = hb_fifo_see( job->fifo_raw ) ) ) { hb_buffer_t * buf_tmp; + if( next->size == 0 ) + { + /* 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 ) + { + 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 */ - hb_log( "sync: first pts is %lld", cur->start ); - pv->pts_offset = cur->start; + 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 %"PRId64, cur->start ); + cur->start = 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. + * 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. */ - pv->video_sequence = cur->sequence; - - /* Check for PTS jumps over 0.5 second */ - if( next->start < cur->start - PTS_DISCONTINUITY_TOLERANCE || - next->start > cur->start + PTS_DISCONTINUITY_TOLERANCE ) + if ( (int64_t)( next->start - pv->video_pts_slip - cur->start ) <= 0 ) { - hb_log( "Sync: Video PTS discontinuity %s (current buffer start=%lld, next buffer start=%lld)", - pv->discontinuity ? "second" : "first", cur->start, next->start ); - - /* - * Do we need to trash the subtitle, is it from the next->start period - * or is it from our old position. If the latter then trash it. - */ - if( pv->subtitle ) + if ( pv->first_drop == 0 ) { - while( ( sub = hb_fifo_see( pv->subtitle->fifo_raw ) ) ) - { - if( ( sub->start > ( cur->start - PTS_DISCONTINUITY_TOLERANCE ) ) && - ( sub->start < ( cur->start + PTS_DISCONTINUITY_TOLERANCE ) ) ) - { - /* - * The subtitle is from our current time region which we are - * jumping from. So trash it as we are about to jump backwards - * or forwards and don't want it blocking the subtitle fifo. - */ - hb_log("Trashing subtitle 0x%x due to PTS discontinuity", sub); - sub = hb_fifo_get( pv->subtitle->fifo_raw ); - hb_buffer_close( &sub ); - } else { - break; - } - } + pv->first_drop = next->start; } - - /* Trash current picture */ - /* Also, make sure we don't trash a chapter break */ - chap_break = cur->new_chap; - hb_buffer_close( &cur ); - pv->cur = cur = hb_fifo_get( job->fifo_raw ); - cur->new_chap |= chap_break; // Don't stomp existing chapter breaks - - /* Calculate new offset */ - pv->pts_offset_old = pv->pts_offset; - pv->pts_offset = cur->start - - pv->count_frames * pv->job->vrate_base / 300; - - if( !pv->discontinuity ) + ++pv->drop_count; + if (next->start - cur->start > 0) { - pv->discontinuity = 1; + pts_skip += next->start - cur->start; + pv->video_pts_slip -= next->start - cur->start; } - - pv->video_sequence = cur->sequence; + buf_tmp = hb_fifo_get( job->fifo_raw ); + if ( buf_tmp->new_chap ) + { + // don't drop a chapter mark when we drop the buffer + pv->chap_mark = buf_tmp->new_chap; + } + hb_buffer_close( &buf_tmp ); continue; } - - /* Look for a subtitle for this frame */ - if( pv->subtitle ) + if ( pv->first_drop ) { - hb_buffer_t * sub2; - while( ( sub = hb_fifo_see( pv->subtitle->fifo_raw ) ) ) - { - /* If two subtitles overlap, make the first one stop - when the second one starts */ - sub2 = hb_fifo_see2( pv->subtitle->fifo_raw ); - if( sub2 && sub->stop > sub2->start ) - sub->stop = sub2->start; + hb_log( "sync: video time didn't advance - dropped %d frames " + "(delta %d ms, current %"PRId64", next %"PRId64", dur %d)", + pv->drop_count, (int)( cur->start - pv->first_drop ) / 90, + cur->start, next->start, (int)( next->start - cur->start ) ); + pv->first_drop = 0; + pv->drop_count = 0; + } - // hb_log("0x%x: video seq: %lld subtitle sequence: %lld", - // sub, cur->sequence, sub->sequence); + /* + * 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; - 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; - } + /* + * 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 ); - 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. - */ - break; - } - else + /* + * Rewrite timestamps on subtitles that need it (on raw queue). + */ + if( subtitle->source == CC608SUB || + subtitle->source == CC708SUB || + subtitle->source == SRTSUB ) + { + /* + * Rewrite timestamps on subtitles that came from Closed Captions + * since they are using the MPEG2 timestamps. + */ + while( ( sub = hb_fifo_see( subtitle->fifo_raw ) ) ) { /* - * The stop time is in the past. But is it due to - * it having been played already, or has the PTS - * been reset to 0? + * 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( ( cur->start - sub->stop ) > PTS_DISCONTINUITY_TOLERANCE ) { + if( sub->size <= 0 ) + { + sub = hb_fifo_get( subtitle->fifo_raw ); + hb_fifo_push( subtitle->fifo_out, sub ); + sub = NULL; + break; + } else { /* - * There is a lot of time between our current - * video and where this subtitle is ending, - * assume that we are about to reset the PTS - * and do not throw away this subtitle. + * 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. + * */ - break; + if( sub->start < cur->start ) + { + sub = hb_fifo_get( subtitle->fifo_raw ); + hb_fifo_push( subtitle->fifo_out, sub ); + } else { + sub = NULL; + break; + } } } - - /* - * The subtitle is older than this picture, trash it - */ - sub = hb_fifo_get( pv->subtitle->fifo_raw ); - hb_buffer_close( &sub ); } - /* - * There is a valid subtitle, is it time to display it? - */ - if( sub ) + if( subtitle->source == VOBSUB ) { - if( sub->stop > sub->start) + hb_buffer_t * sub2; + while( ( sub = hb_fifo_see( subtitle->fifo_raw ) ) ) { - /* - * 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 ) + if( sub->size == 0 ) { /* - * We should be playing this, so leave the - * subtitle in place. - * - * fall through to display + * EOF, pass it through immediately. */ - } - else + 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 ) { /* - * Defer until the play point is within the subtitle + * 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; } - } - else - { - /* - * The end of the subtitle is less than the start, this is a - * sign of a PTS discontinuity. - */ - if( sub->start > cur->start ) - { + + if( sub->stop > cur->start ) { /* - * we haven't reached the start time yet, or - * we have jumped backwards after having - * already started this subtitle. + * The stop time is in the future, so fall through + * and we'll deal with it in the next block of + * code. */ - if( cur->start < sub->stop ) + + /* + * There is a valid subtitle, is it time to display it? + */ + if( sub->stop > sub->start) { /* - * We have jumped backwards and so should - * continue displaying this subtitle. - * - * fall through to display. + * Normal subtitle which ends after it starts, + * check to see that the current video is between + * the start and end. */ - } - else + 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 { /* - * Defer until the play point is within the subtitle + * The end of the subtitle is less than the start, + * this is a sign of a PTS discontinuity. */ - sub = NULL; + 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/ + */ + } } - } else { + break; + } + else + { + /* - * Play this subtitle as the start is greater than our - * video point. - * - * fall through to display/ + * The subtitle is older than this picture, trash it */ + sub = hb_fifo_get( subtitle->fifo_raw ); + hb_buffer_close( &sub ); + } + } + + /* If we have a subtitle for this picture, copy it */ + /* FIXME: we should avoid this memcpy */ + if( sub ) + { + if( sub->size > 0 ) + { + if( subtitle->config.dest == RENDERSUB ) + { + if ( cur->sub == NULL ) + { + /* + * Tack onto the video buffer for rendering + */ + cur->sub = hb_buffer_init( sub->size ); + cur->sub->x = sub->x; + cur->sub->y = sub->y; + cur->sub->width = sub->width; + cur->sub->height = sub->height; + memcpy( cur->sub->data, sub->data, sub->size ); + } + } else { + /* + * Pass-Through, pop it off of the raw queue, + */ + 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_out, sub ); + } } } } - } - - /* The PTS of the frame we are expecting now */ - pts_expected = pv->pts_offset + - pv->count_frames * pv->job->vrate_base / 300; - - //hb_log("Video expecting PTS %lld, current frame: %lld, next frame: %lld, cf: %lld", - // pts_expected, cur->start, next->start, pv->count_frames * pv->job->vrate_base / 300 ); + } // end subtitles - if( cur->start < pts_expected - pv->job->vrate_base / 300 / 2 && - next->start < pts_expected + pv->job->vrate_base / 300 / 2 ) + /* + * Adjust the pts of the current frame so that it's contiguous + * with the previous frame. The start time of the current frame + * has to be the end time of the previous frame and the stop + * time has to be the start of the next frame. We don't + * make any adjustments to the source timestamps other than removing + * the clock offsets (which also removes pts discontinuities). + * This means we automatically encode at the source's frame rate. + * MP2 uses an implicit duration (frames end when the next frame + * starts) but more advanced containers like MP4 use an explicit + * duration. Since we're looking ahead one frame we set the + * explicit stop time from the start time of the next frame. + */ + buf_tmp = cur; + pv->cur = cur = hb_fifo_get( job->fifo_raw ); + cur->sub = NULL; + pv->next_pts = cur->start; + int64_t duration = cur->start - pts_skip - buf_tmp->start; + pts_skip = 0; + if ( duration <= 0 ) { - /* The current frame is too old but the next one matches, - let's trash */ - /* Also, make sure we don't trash a chapter break */ - chap_break = cur->new_chap; - hb_buffer_close( &cur ); - pv->cur = cur = hb_fifo_get( job->fifo_raw ); - cur->new_chap |= chap_break; // Make sure we don't stomp the existing one. - - continue; + hb_log( "sync: invalid video duration %"PRId64", start %"PRId64", next %"PRId64"", + duration, buf_tmp->start, next->start ); } - 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 ); - buf_tmp->sequence = cur->sequence; - } - 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 ); - } - - /* 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; - - /* If we have a subtitle for this picture, copy it */ - /* FIXME: we should avoid this memcpy */ - if( sub ) + buf_tmp->start = pv->next_start; + pv->next_start += duration; + buf_tmp->stop = pv->next_start; + + if ( pv->chap_mark ) { - buf_tmp->sub = hb_buffer_init( sub->size ); - buf_tmp->sub->x = sub->x; - buf_tmp->sub->y = sub->y; - buf_tmp->sub->width = sub->width; - buf_tmp->sub->height = sub->height; - memcpy( buf_tmp->sub->data, sub->data, sub->size ); + // we have a pending chapter mark from a recent drop - put it on this + // buffer (this may make it one frame late but we can't do any better). + buf_tmp->new_chap = pv->chap_mark; + pv->chap_mark = 0; } /* Push the frame to the renderer */ @@ -565,352 +725,243 @@ static int SyncVideo( hb_work_object_t * w ) /* Update UI */ UpdateState( w ); - - /* Make sure we won't get more frames then expected */ - if( pv->count_frames >= pv->count_frames_max * 2) + + if( job->frame_to_stop && pv->count_frames > job->frame_to_stop ) { - hb_log( "sync: got too many frames (%lld), exiting early", pv->count_frames ); - pv->done = 1; - - // Drop an empty buffer into our output to ensure that things - // get flushed all the way out. - buf_tmp = hb_buffer_init(0); // Empty end buffer - hb_fifo_push( job->fifo_sync, buf_tmp ); - - break; + // Drop an empty buffer into our output to ensure that things + // get flushed all the way out. + hb_fifo_push( job->fifo_sync, hb_buffer_init( 0 ) ); + pv->busy &=~ 1; + hb_log( "sync: reached %d frames, exiting early (%i busy)", + pv->count_frames, pv->busy ); + return; } } +} + +static void OutputAudioFrame( hb_job_t *job, hb_audio_t *audio, hb_buffer_t *buf, + hb_sync_audio_t *sync, hb_fifo_t *fifo, int i ) +{ + int64_t start = sync->next_start; + int64_t duration = buf->stop - buf->start; + + sync->next_pts += duration; - return HB_WORK_OK; + if( audio->config.in.samplerate == audio->config.out.samplerate || + audio->config.out.codec == HB_ACODEC_AC3 || + audio->config.out.codec == HB_ACODEC_DCA ) + { + /* + * If we don't have to do sample rate conversion or this audio is + * pass-thru just send the input buffer downstream after adjusting + * its timestamps to make the output stream continuous. + */ + } + else + { + /* Not pass-thru - do sample rate conversion */ + int count_in, count_out; + hb_buffer_t * buf_raw = buf; + int channel_count = HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT(audio->config.out.mixdown) * + sizeof( float ); + + count_in = buf_raw->size / channel_count; + /* + * When using stupid rates like 44.1 there will always be some + * truncation error. E.g., a 1536 sample AC3 frame will turn into a + * 1536*44.1/48.0 = 1411.2 sample frame. If we just truncate the .2 + * the error will build up over time and eventually the audio will + * substantially lag the video. libsamplerate will keep track of the + * fractional sample & give it to us when appropriate if we give it + * an extra sample of space in the output buffer. + */ + count_out = ( duration * audio->config.out.samplerate ) / 90000 + 1; + + sync->data.input_frames = count_in; + sync->data.output_frames = count_out; + sync->data.src_ratio = (double)audio->config.out.samplerate / + (double)audio->config.in.samplerate; + + buf = hb_buffer_init( count_out * channel_count ); + sync->data.data_in = (float *) buf_raw->data; + sync->data.data_out = (float *) buf->data; + if( src_process( sync->state, &sync->data ) ) + { + /* XXX If this happens, we're screwed */ + hb_log( "sync: audio %d src_process failed", i ); + } + hb_buffer_close( &buf_raw ); + + buf->size = sync->data.output_frames_gen * channel_count; + duration = ( sync->data.output_frames_gen * 90000 ) / + audio->config.out.samplerate; + } + buf->frametype = HB_FRAME_AUDIO; + buf->start = start; + buf->stop = start + duration; + sync->next_start = start + duration; + hb_fifo_push( fifo, buf ); } /*********************************************************************** * SyncAudio *********************************************************************** - * + * **********************************************************************/ static void SyncAudio( hb_work_object_t * w, int i ) { hb_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->sync_audio[i]; + hb_audio_t * audio = sync->audio; hb_buffer_t * buf; - hb_sync_audio_t * sync; - hb_fifo_t * fifo; - int rate; + int64_t start; - int64_t pts_expected; - int64_t start; - - job = pv->job; - sync = &pv->sync_audio[i]; - audio = sync->audio; - - if( job->acodec & HB_ACODEC_AC3 ) + if( audio->config.out.codec == HB_ACODEC_AC3 || + audio->config.out.codec == HB_ACODEC_DCA ) { - fifo = audio->fifo_out; - rate = audio->rate; + fifo = audio->priv.fifo_out; } else { - fifo = audio->fifo_sync; - rate = job->arate; + fifo = audio->priv.fifo_sync; } - while( !hb_fifo_is_full( fifo ) && - ( buf = hb_fifo_see( audio->fifo_raw ) ) ) + while( !hb_fifo_is_full( fifo ) && ( buf = hb_fifo_see( audio->priv.fifo_raw ) ) ) { - /* The PTS of the samples we are expecting now */ - pts_expected = pv->pts_offset + sync->count_frames * 90000 / rate; - - // hb_log("Video Sequence: %lld, Audio Sequence: %lld", pv->video_sequence, buf->sequence); - - /* - * Using the same logic as the Video have we crossed a VOB - * boundary as detected by the expected PTS and the PTS of our - * audio being out by more than the tolerance value. - */ - if( buf->start > pts_expected + PTS_DISCONTINUITY_TOLERANCE || - buf->start < pts_expected - PTS_DISCONTINUITY_TOLERANCE ) + start = buf->start - pv->audio_passthru_slip; + /* if the next buffer is an eof send it downstream */ + if ( buf->size <= 0 ) { - /* There has been a PTS discontinuity, and this frame might - be from before the discontinuity*/ - - if( pv->discontinuity ) - { - /* - * There is an outstanding discontinuity, so use the offset from - * that discontinuity. - */ - pts_expected = pv->pts_offset_old + sync->count_frames * - 90000 / rate; - } - else - { - /* - * No outstanding discontinuity, so the audio must be leading the - * video (or the PTS values are really stuffed). So lets mark this - * as a discontinuity ourselves for the audio to use until - * the video also crosses the discontinuity. - * - * pts_offset is used when we are in the same time space as the video - * pts_offset_old when in a discontinuity. - * - * Therefore set the pts_offset_old given the new pts_offset for this - * current buffer. - */ - pv->discontinuity = 1; - pv->pts_offset_old = buf->start - sync->count_frames * - 90000 / rate; - pts_expected = pv->pts_offset_old + sync->count_frames * - 90000 / rate; - - hb_log("Sync: Audio discontinuity (sequence: vid %lld aud %lld) (pts %lld < %lld < %lld)", - pv->video_sequence, buf->sequence, - pts_expected - PTS_DISCONTINUITY_TOLERANCE, buf->start, - pts_expected + PTS_DISCONTINUITY_TOLERANCE ); - } - - /* - * Is the audio from a valid period given the previous - * Video PTS. I.e. has there just been a video PTS - * discontinuity and this audio belongs to the vdeo from - * before? - */ - if( buf->start > pts_expected + PTS_DISCONTINUITY_TOLERANCE || - buf->start < pts_expected - PTS_DISCONTINUITY_TOLERANCE ) - { - /* - * It's outside of our tolerance for where the video - * is now, and it's outside of the tolerance for - * where we have been in the case of a VOB change. - * Try and reconverge regardless. so continue on to - * our convergence code below which will kick in as - * it will be more than 100ms out. - * - * Note that trashing the Audio could make things - * worse if the Audio is in front because we will end - * up diverging even more. We need to hold on to the - * audio until the video catches up. - */ - if( !pv->way_out_of_sync ) - { - hb_log("Sync: Audio is way out of sync, attempt to reconverge from current video PTS"); - pv->way_out_of_sync = 1; - } - - /* - * It wasn't from the old place, so we must be from - * the new, but just too far out. So attempt to - * reconverge by resetting the point we want to be to - * where we are currently wanting to be. - */ - pts_expected = pv->pts_offset + sync->count_frames * 90000 / rate; - start = pts_expected - pv->pts_offset; - } else { - /* Use the older offset */ - start = pts_expected - pv->pts_offset_old; - } + buf = hb_fifo_get( audio->priv.fifo_raw ); + hb_fifo_push( fifo, buf ); + pv->busy &=~ (1 << (i + 1) ); + return; } - else + if( job->frame_to_stop && pv->count_frames >= job->frame_to_stop ) { - start = pts_expected - pv->pts_offset; - - if( pv->discontinuity ) - { - /* - * The Audio is tracking the Video again using the normal pts_offset, so the - * discontinuity is over. - */ - hb_log( "Sync: Audio joined Video after discontinuity at PTS %lld", buf->start ); - pv->discontinuity = 0; - } + hb_fifo_push( fifo, hb_buffer_init(0) ); + pv->busy &=~ (1 << (i + 1) ); + return; } - - /* Tolerance: 100 ms */ - if( buf->start < pts_expected - 9000 ) + if ( (int64_t)( start - sync->next_pts ) < 0 ) { - if( !pv->trashing_audio ) + // 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 ) { - /* Audio is behind the Video, trash it, can't use it now. */ - hb_log( "Sync: Audio PTS (%lld) < Video PTS (%lld) by greater than 100ms, trashing audio to reconverge", - buf->start, pts_expected); - pv->trashing_audio = 1; + // 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; } - buf = hb_fifo_get( audio->fifo_raw ); - hb_buffer_close( &buf ); - continue; + sync->next_pts = start; } - else if( buf->start > pts_expected + 9000 ) + if ( sync->first_drop ) { - /* Audio is ahead of the Video, insert silence until we catch up*/ - if( !pv->inserting_silence ) - { - hb_log("Sync: Audio PTS (%lld) > Video PTS (%lld) by greater than 100ms insert silence until reconverged", buf->start, pts_expected); - pv->inserting_silence = 1; - } - InsertSilence( w, i ); - continue; - } - else + // we were dropping old data but input buf time is now current + hb_log( "sync: audio %d time went backwards %d ms, dropped %d frames " + "(next %"PRId64", current %"PRId64")", i, + (int)( sync->next_pts - sync->first_drop ) / 90, + sync->drop_count, sync->first_drop, sync->next_pts ); + sync->first_drop = 0; + sync->drop_count = 0; + sync->next_pts = start; + } + if ( start - sync->next_pts >= (90 * 70) ) { - if( pv->trashing_audio || pv->inserting_silence ) + if ( start - sync->next_pts > (90000LL * 60) ) { - hb_log( "Sync: Audio back in Sync at PTS %lld", buf->start ); - pv->trashing_audio = 0; - pv->inserting_silence = 0; + // there's a gap of more than a minute between the last + // frame and this. assume we got a corrupted timestamp + // and just drop the next buf. + hb_log( "sync: %d minute time gap in audio %d - dropping buf" + " start %"PRId64", next %"PRId64, + (int)((start - sync->next_pts) / (90000*60)), + i, start, sync->next_pts ); + buf = hb_fifo_get( audio->priv.fifo_raw ); + hb_buffer_close( &buf ); + continue; } - if( pv->way_out_of_sync ) - { - hb_log( "Sync: Audio no longer way out of sync at PTS %lld", - buf->start ); - pv->way_out_of_sync = 0; - } - } - - 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; - - sync->count_frames += AC3_SAMPLES_PER_FRAME; - } - else - { - hb_buffer_t * buf_raw = hb_fifo_get( audio->fifo_raw ); - - int count_in, count_out; - - count_in = buf_raw->size / HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT(audio->amixdown) / sizeof( float ); - count_out = ( buf_raw->stop - buf_raw->start ) * job->arate / 90000; - if( buf->start < pts_expected - 1500 ) - count_out--; - else if( buf->start > pts_expected + 1500 ) - count_out++; - - sync->data.data_in = (float *) buf_raw->data; - sync->data.input_frames = count_in; - sync->data.output_frames = count_out; - - sync->data.src_ratio = (double) sync->data.output_frames / - (double) sync->data.input_frames; - - buf = hb_buffer_init( sync->data.output_frames * HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT(audio->amixdown) * - sizeof( float ) ); - sync->data.data_out = (float *) buf->data; - if( src_process( sync->state, &sync->data ) ) + /* + * 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 ) { - /* XXX If this happens, we're screwed */ - hb_log( "sync: src_process failed" ); + hb_log( "sync: audio gap %d ms. Skipping frames. Audio %d" + " start %"PRId64", next %"PRId64, + (int)((start - sync->next_pts) / 90), + i, start, sync->next_pts ); + pv->audio_passthru_slip += (start - sync->next_pts); + pv->video_pts_slip += (start - sync->next_pts); + return; } - hb_buffer_close( &buf_raw ); - - buf->size = sync->data.output_frames_gen * HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT(audio->amixdown) * sizeof( float ); - - /* Set dates for resampled data */ - buf->start = start; - buf->stop = start + sync->data.output_frames_gen * - 90000 / job->arate; - - sync->count_frames += sync->data.output_frames_gen; + hb_log( "sync: adding %d ms of silence to audio %d" + " start %"PRId64", next %"PRId64, + (int)((start - sync->next_pts) / 90), + i, start, sync->next_pts ); + InsertSilence( w, i, start - sync->next_pts ); + return; } - buf->frametype = HB_FRAME_AUDIO; - hb_fifo_push( fifo, buf ); - } - - if( hb_fifo_is_full( fifo ) && - pv->way_out_of_sync ) - { /* - * Trash the top audio packet to avoid dead lock as we reconverge. + * 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->fifo_raw ); - hb_buffer_close( &buf ); - } - - if( NeedSilence( w, audio ) ) - { - InsertSilence( w, i ); + buf = hb_fifo_get( audio->priv.fifo_raw ); + OutputAudioFrame( job, audio, buf, sync, fifo, i ); } } -static int NeedSilence( hb_work_object_t * w, hb_audio_t * audio ) +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; - - if( hb_fifo_size( audio->fifo_in ) || - hb_fifo_size( audio->fifo_raw ) || - hb_fifo_size( audio->fifo_sync ) || - hb_fifo_size( audio->fifo_out ) ) - { - /* We have some audio, we are fine */ - return 0; - } - - /* No audio left in fifos */ - - if( hb_thread_has_exited( job->reader ) ) + hb_job_t *job = pv->job; + hb_sync_audio_t *sync = &pv->sync_audio[i]; + hb_buffer_t *buf; + hb_fifo_t *fifo; + + // to keep pass-thru and regular audio in sync we generate silence in + // AC3 frame-sized units. If the silence duration isn't an integer multiple + // of the AC3 frame duration we will truncate or round up depending on + // which minimizes the timing error. + const int frame_dur = ( 90000 * AC3_SAMPLES_PER_FRAME ) / + sync->audio->config.in.samplerate; + int frame_count = ( duration + (frame_dur >> 1) ) / frame_dur; + + while ( --frame_count >= 0 ) { - /* We might miss some audio to complete encoding and muxing - the video track */ - hb_log("Reader has exited early, inserting silence."); - return 1; - } - - if( hb_fifo_is_full( job->fifo_mpeg2 ) && - hb_fifo_is_full( job->fifo_raw ) && - hb_fifo_is_full( job->fifo_sync ) && - hb_fifo_is_full( job->fifo_render ) && - hb_fifo_is_full( job->fifo_mpeg4 ) ) - { - /* Too much video and no audio, oh-oh */ - hb_log("Still got some video - and nothing in the audio fifo, insert silence"); - return 1; - } - - return 0; -} - -static void InsertSilence( hb_work_object_t * w, int i ) -{ - 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 - { - buf = hb_buffer_init( HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT(sync->audio->amixdown) * 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_fifo_push( sync->audio->fifo_sync, buf ); - - sync->count_frames += job->arate / 20; + if( sync->audio->config.out.codec == HB_ACODEC_AC3 ) + { + buf = hb_buffer_init( sync->ac3_size ); + buf->start = sync->next_pts; + buf->stop = buf->start + frame_dur; + memcpy( buf->data, sync->ac3_buf, buf->size ); + fifo = sync->audio->priv.fifo_out; + } + else + { + buf = hb_buffer_init( AC3_SAMPLES_PER_FRAME * sizeof( float ) * + HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT( + sync->audio->config.out.mixdown) ); + buf->start = sync->next_pts; + buf->stop = buf->start + frame_dur; + memset( buf->data, 0, buf->size ); + fifo = sync->audio->priv.fifo_sync; + } + OutputAudioFrame( job, sync->audio, buf, sync, fifo, i ); } } @@ -922,6 +973,8 @@ static void UpdateState( hb_work_object_t * w ) if( !pv->count_frames ) { pv->st_first = hb_get_date(); + pv->job->st_pause_date = -1; + pv->job->st_paused = 0; } pv->count_frames++; @@ -933,14 +986,14 @@ static void UpdateState( hb_work_object_t * w ) 3 * sizeof( uint64_t ) ); pv->st_dates[3] = hb_get_date(); pv->st_counts[3] = pv->count_frames; - } + } #define p state.param.working state.state = HB_STATE_WORKING; p.progress = (float) pv->count_frames / (float) pv->count_frames_max; if( p.progress > 1.0 ) { - p.progress = 1.0; + p.progress = 1.0; } p.rate_cur = 1000.0 * (float) ( pv->st_counts[3] - pv->st_counts[0] ) / @@ -949,7 +1002,7 @@ static void UpdateState( hb_work_object_t * w ) { int eta; p.rate_avg = 1000.0 * (float) pv->st_counts[3] / - (float) ( pv->st_dates[3] - pv->st_first ); + (float) ( pv->st_dates[3] - pv->st_first - pv->job->st_paused); eta = (float) ( pv->count_frames_max - pv->st_counts[3] ) / p.rate_avg; p.hours = eta / 3600;