X-Git-Url: http://git.osdn.jp/view?a=blobdiff_plain;f=libhb%2Fsync.c;h=94b8411b491ca8309d9d1011c4399cbee003e7cc;hb=033e32de9c380f54c7d1362a3979da205ebc3a29;hp=5b4891bb60e1eb3bd914cf0d46be72558d7035bb;hpb=bcd20ea430ced01a2c10605c8f6a77de9c6af410;p=handbrake-jp%2Fhandbrake-jp-git.git diff --git a/libhb/sync.c b/libhb/sync.c index 5b4891bb..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 */ @@ -22,10 +22,8 @@ typedef struct int64_t next_start; /* start time of next output frame */ int64_t next_pts; /* start time of next input frame */ - int64_t start_silence; /* if we're inserting silence, the time we started */ int64_t first_drop; /* PTS of first 'went backwards' frame dropped */ int drop_count; /* count of 'time went backwards' drops */ - int inserting_silence; /* Raw */ SRC_STATE * state; @@ -40,22 +38,29 @@ 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 next_start; /* start time of next output frame */ int64_t next_pts; /* start time of next input frame */ int64_t first_drop; /* PTS of first 'went backwards' frame dropped */ int drop_count; /* count of 'time went backwards' drops */ + int drops; /* frames dropped to make a cbr video stream */ + int dups; /* frames duplicated to make a cbr video stream */ int video_sequence; int count_frames; int count_frames_max; + int chap_mark; /* to propagate chapter mark across a drop */ hb_buffer_t * cur; /* The next picture to process */ /* Audio */ hb_sync_audio_t sync_audio[8]; + int64_t audio_passthru_slip; + int64_t video_pts_slip; /* Statistics */ uint64_t st_counts[4]; @@ -67,9 +72,8 @@ struct hb_work_private_s * 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 *, int i ); static void InsertSilence( hb_work_object_t * w, int i, int64_t d ); static void UpdateState( hb_work_object_t * w ); @@ -91,32 +95,52 @@ int syncInit( hb_work_object_t * w, hb_job_t * job ) pv->job = job; pv->pts_offset = INT64_MIN; - pv->count_frames = 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 ) { - chapter = hb_list_item( title->list_chapter, i - 1 ); - duration += chapter->duration; + /* 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 + { + /* 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; } - 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; 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; } @@ -131,20 +155,33 @@ void syncClose( hb_work_object_t * w ) 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 ); + } - for( i = 0; i < hb_list_count( title->list_audio ); i++ ) + hb_log( "sync: got %d frames, %d expected", + pv->count_frames, pv->count_frames_max ); + + /* save data for second pass */ + if( job->pass == 1 ) { - if ( pv->sync_audio[i].start_silence ) - { - hb_log( "sync: added %d ms of silence to audio %d", - (int)((pv->sync_audio[i].next_pts - - pv->sync_audio[i].start_silence) / 90), i ); - } + /* 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++ ) + { audio = hb_list_item( title->list_audio, i ); if( audio->config.out.codec == HB_ACODEC_AC3 ) { @@ -176,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 = @@ -223,7 +259,7 @@ static void InitAudio( hb_work_object_t * w, int i ) 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; @@ -242,14 +278,14 @@ static void InitAudio( hb_work_object_t * w, int i ) } 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->config.out.mixdown), &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; } } @@ -259,43 +295,42 @@ 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 ) { hb_work_private_t * pv = w->private_data; hb_buffer_t * cur, * next, * sub = NULL; hb_job_t * job = pv->job; + hb_subtitle_t *subtitle; + int i; + int64_t pts_skip; - if( 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 %d frames, %d expected", - pv->count_frames, pv->count_frames_max ); - pv->done = 1; + /* we got an end-of-stream. Feed it downstream & signal that we're done. */ + hb_fifo_push( job->fifo_sync, hb_buffer_init( 0 ) ); - hb_buffer_t * buf_tmp; - - // Drop an empty buffer into our output to ensure that things - // get flushed all the way out. - buf_tmp = hb_buffer_init(0); // Empty end buffer - hb_fifo_push( job->fifo_sync, buf_tmp ); - - return HB_WORK_DONE; - } + /* + * 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 @@ -306,6 +341,28 @@ static int SyncVideo( hb_work_object_t * w ) { 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 */ @@ -320,7 +377,7 @@ static int SyncVideo( hb_work_object_t * w ) * as if it started at zero so that our audio timing will * be in sync. */ - hb_log( "sync: first pts is %lld", cur->start ); + hb_log( "sync: first pts is %"PRId64, cur->start ); cur->start = 0; } } @@ -336,23 +393,33 @@ static int SyncVideo( hb_work_object_t * w ) * can deal with overlaps of up to a frame time but anything larger * we handle by dropping frames here. */ - if ( pv->next_pts - next->start > 1000 ) + if ( (int64_t)( next->start - pv->video_pts_slip - cur->start ) <= 0 ) { if ( pv->first_drop == 0 ) { pv->first_drop = next->start; } ++pv->drop_count; + if (next->start - cur->start > 0) + { + pts_skip += next->start - cur->start; + pv->video_pts_slip -= next->start - cur->start; + } buf_tmp = hb_fifo_get( job->fifo_raw ); + if ( buf_tmp->new_chap ) + { + // don't drop a chapter mark when we drop the buffer + pv->chap_mark = buf_tmp->new_chap; + } hb_buffer_close( &buf_tmp ); continue; } if ( pv->first_drop ) { - hb_log( "sync: video time went backwards %d ms, dropped %d frames " - "(frame %lld, expected %lld)", - (int)( pv->next_pts - pv->first_drop ) / 90, pv->drop_count, - pv->first_drop, pv->next_pts ); + 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; } @@ -363,138 +430,258 @@ static int SyncVideo( hb_work_object_t * w ) */ pv->video_sequence = cur->sequence; - /* Look for a subtitle for this frame */ - if( pv->subtitle ) + /* + * 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++) { - 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("0x%x: video seq: %lld subtitle sequence: %lld", - // sub, cur->sequence, sub->sequence); - - if( sub->sequence > cur->sequence ) - { - /* - * The video is behind where we are, so wait until - * it catches up to the same reader point on the - * DVD. Then our PTS should be in the same region - * as the video. - */ - sub = NULL; - break; - } - - if( sub->stop > cur->start ) { - /* - * The stop time is in the future, so fall through - * and we'll deal with it in the next block of - * code. - */ - break; - } - - /* - * The subtitle is older than this picture, trash it - */ - sub = hb_fifo_get( pv->subtitle->fifo_raw ); - hb_buffer_close( &sub ); - } + subtitle = hb_list_item( job->list_subtitle, i ); /* - * There is a valid subtitle, is it time to display it? + * Rewrite timestamps on subtitles that need it (on raw queue). */ - if( sub ) + if( subtitle->source == CC608SUB || + subtitle->source == CC708SUB || + subtitle->source == SRTSUB ) { - if( sub->stop > sub->start) + /* + * Rewrite timestamps on subtitles that came from Closed Captions + * since they are using the MPEG2 timestamps. + */ + 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. + * 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->start && - cur->start < sub->stop ) + if( sub->size <= 0 ) { + sub = hb_fifo_get( subtitle->fifo_raw ); + hb_fifo_push( subtitle->fifo_out, sub ); + sub = NULL; + break; + } else { /* - * We should be playing this, so leave the - * subtitle in place. + * 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. * - * fall through to display */ - if( ( sub->stop - sub->start ) < ( 3 * 90000 ) ) + if( sub->start < cur->start ) { - /* - * Subtitle is on for less than three seconds, extend - * the time that it is displayed to make it easier - * to read. Make it 3 seconds or until the next - * subtitle is displayed. - * - * This is in response to Indochine which only - * displays subs for 1 second - too fast to read. - */ - sub->stop = sub->start + ( 3 * 90000 ); + sub = hb_fifo_get( subtitle->fifo_raw ); + hb_fifo_push( subtitle->fifo_out, sub ); + } else { + sub = NULL; + break; + } + } + } + } - sub2 = hb_fifo_see2( pv->subtitle->fifo_raw ); + if( subtitle->source == VOBSUB ) + { + hb_buffer_t * sub2; + while( ( sub = hb_fifo_see( subtitle->fifo_raw ) ) ) + { + if( sub->size == 0 ) + { + /* + * EOF, pass it through immediately. + */ + break; + } - if( sub2 && sub->stop > sub2->start ) - { - sub->stop = sub2->start; - } - } + /* 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; } - else + + // 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 ) { + /* + * The stop time is in the future, so fall through + * and we'll deal with it in the next block of + * code. + */ + /* - * we haven't reached the start time yet, or - * we have jumped backwards after having - * already started this subtitle. + * There is a valid subtitle, is it time to display it? */ - if( cur->start < sub->stop ) + 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. */ + 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 ); + } } } } - } + } // end subtitles /* * Adjust the pts of the current frame so that it's contiguous @@ -511,22 +698,26 @@ static int SyncVideo( hb_work_object_t * w ) */ buf_tmp = cur; pv->cur = cur = hb_fifo_get( job->fifo_raw ); - pv->next_pts = next->start; - int64_t duration = next->start - buf_tmp->start; + cur->sub = NULL; + pv->next_pts = cur->start; + int64_t duration = cur->start - pts_skip - buf_tmp->start; + pts_skip = 0; + if ( duration <= 0 ) + { + hb_log( "sync: invalid video duration %"PRId64", start %"PRId64", next %"PRId64"", + duration, buf_tmp->start, next->start ); + } + buf_tmp->start = pv->next_start; pv->next_start += duration; buf_tmp->stop = pv->next_start; - /* If we have a subtitle for this picture, copy it */ - /* FIXME: we should avoid this memcpy */ - if( sub ) + if ( pv->chap_mark ) { - buf_tmp->sub = hb_buffer_init( sub->size ); - buf_tmp->sub->x = sub->x; - buf_tmp->sub->y = sub->y; - buf_tmp->sub->width = sub->width; - buf_tmp->sub->height = sub->height; - memcpy( buf_tmp->sub->data, sub->data, sub->size ); + // we have a pending chapter mark from a recent drop - put it on this + // buffer (this may make it one frame late but we can't do any better). + buf_tmp->new_chap = pv->chap_mark; + pv->chap_mark = 0; } /* Push the frame to the renderer */ @@ -534,23 +725,18 @@ 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 (%d), 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; } } - - return HB_WORK_OK; } static void OutputAudioFrame( hb_job_t *job, hb_audio_t *audio, hb_buffer_t *buf, @@ -558,25 +744,15 @@ static void OutputAudioFrame( hb_job_t *job, hb_audio_t *audio, hb_buffer_t *buf { int64_t start = sync->next_start; int64_t duration = buf->stop - buf->start; - if (duration <= 0 || - duration > ( 90000 * AC3_SAMPLES_PER_FRAME ) / audio->config.out.samplerate ) - { - hb_log("sync: audio %d weird duration %lld, start %lld, stop %lld, next %lld", - i, duration, buf->start, buf->stop, sync->next_pts); - if ( duration <= 0 ) - { - duration = ( 90000 * AC3_SAMPLES_PER_FRAME ) / audio->config.out.samplerate; - buf->stop = buf->start + duration; - } - } + sync->next_pts += duration; - if( /* audio->rate == job->arate || This should work but doesn't */ + 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 AC3 + * 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. */ @@ -590,11 +766,21 @@ static void OutputAudioFrame( hb_job_t *job, hb_audio_t *audio, hb_buffer_t *buf sizeof( float ); count_in = buf_raw->size / channel_count; - count_out = ( buf_raw->stop - buf_raw->start ) * audio->config.out.samplerate / 90000; + /* + * 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)count_out / (double)count_in; + 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; @@ -607,10 +793,12 @@ static void OutputAudioFrame( hb_job_t *job, hb_audio_t *audio, hb_buffer_t *buf 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; - buf->frametype = HB_FRAME_AUDIO; sync->next_start = start + duration; hb_fifo_push( fifo, buf ); } @@ -628,81 +816,103 @@ static void SyncAudio( hb_work_object_t * w, int i ) hb_audio_t * audio = sync->audio; hb_buffer_t * buf; hb_fifo_t * fifo; - int rate; + int64_t start; - if( audio->config.out.codec == HB_ACODEC_AC3 ) + if( audio->config.out.codec == HB_ACODEC_AC3 || + audio->config.out.codec == HB_ACODEC_DCA ) { fifo = audio->priv.fifo_out; - rate = audio->config.in.samplerate; } else { fifo = audio->priv.fifo_sync; - rate = audio->config.out.samplerate; } while( !hb_fifo_is_full( fifo ) && ( buf = hb_fifo_see( audio->priv.fifo_raw ) ) ) { - if ( sync->next_pts - buf->start > 500 ) + start = buf->start - pv->audio_passthru_slip; + /* if the next buffer is an eof send it downstream */ + if ( buf->size <= 0 ) { - /* - * audio time went backwards by more than a frame time (this can - * happen when we reset the PTS because of lost data). - * Discard data that's in the past. - */ - if ( sync->first_drop == 0 ) + buf = hb_fifo_get( audio->priv.fifo_raw ); + hb_fifo_push( fifo, buf ); + pv->busy &=~ (1 << (i + 1) ); + return; + } + if( job->frame_to_stop && pv->count_frames >= job->frame_to_stop ) + { + hb_fifo_push( fifo, hb_buffer_init(0) ); + pv->busy &=~ (1 << (i + 1) ); + return; + } + if ( (int64_t)( start - sync->next_pts ) < 0 ) + { + // audio time went backwards. + // If our output clock is more than a half frame ahead of the + // input clock drop this frame to move closer to sync. + // Otherwise drop frames until the input clock matches the output clock. + if ( sync->first_drop || sync->next_start - start > 90*15 ) { - sync->first_drop = buf->start; + // Discard data that's in the past. + if ( sync->first_drop == 0 ) + { + sync->first_drop = sync->next_pts; + } + ++sync->drop_count; + buf = hb_fifo_get( audio->priv.fifo_raw ); + hb_buffer_close( &buf ); + continue; } - ++sync->drop_count; - buf = hb_fifo_get( audio->priv.fifo_raw ); - hb_buffer_close( &buf ); - continue; + sync->next_pts = start; } if ( sync->first_drop ) { + // we were dropping old data but input buf time is now current hb_log( "sync: audio %d time went backwards %d ms, dropped %d frames " - "(frame %lld, expected %lld)", i, + "(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 ( sync->inserting_silence && buf->start - sync->next_pts > 0 ) + if ( start - sync->next_pts >= (90 * 70) ) { - /* - * if we're within one frame time of the amount of silence - * we need, insert just what we need otherwise insert a frame time. - */ - int64_t framedur = buf->stop - buf->start; - if ( buf->start - sync->next_pts <= framedur ) + if ( start - sync->next_pts > (90000LL * 60) ) { - InsertSilence( w, i, buf->start - sync->next_pts ); - sync->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; } - else - { - InsertSilence( w, i, framedur ); - } - continue; - } - if ( buf->start - sync->next_pts >= (90 * 100) ) - { /* - * there's a gap of at least 100ms between the last + * 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->inserting_silence ) + if( sync->audio->config.out.codec == HB_ACODEC_DCA ) { - hb_log( "sync: adding %d ms of silence to audio %d" - " start %lld, next %lld", - (int)((buf->start - sync->next_pts) / 90), - i, buf->start, sync->next_pts ); - sync->inserting_silence = 1; + 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; } - InsertSilence( w, i, buf->stop - buf->start ); - continue; + 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; } /* @@ -713,42 +923,6 @@ static void SyncAudio( hb_work_object_t * w, int i ) buf = hb_fifo_get( audio->priv.fifo_raw ); OutputAudioFrame( job, audio, buf, sync, fifo, i ); } - - if( NeedSilence( w, audio, i ) ) - { - InsertSilence( w, i, (90000 * AC3_SAMPLES_PER_FRAME) / sync->audio->config.out.samplerate ); - } -} - -static int NeedSilence( hb_work_object_t * w, hb_audio_t * audio, int i ) -{ - hb_work_private_t * pv = w->private_data; - hb_job_t * job = pv->job; - hb_sync_audio_t * sync = &pv->sync_audio[i]; - - if( hb_fifo_size( audio->priv.fifo_in ) || - hb_fifo_size( audio->priv.fifo_raw ) || - hb_fifo_size( audio->priv.fifo_sync ) || - hb_fifo_size( audio->priv.fifo_out ) ) - { - /* We have some audio, we are fine */ - return 0; - } - - /* No audio left in fifos */ - - if( hb_thread_has_exited( job->reader ) ) - { - /* We might miss some audio to complete encoding and muxing - the video track */ - if ( sync->start_silence == 0 ) - { - hb_log("sync: reader has exited, adding silence to audio %d", i); - sync->start_silence = sync->next_pts; - } - return 1; - } - return 0; } static void InsertSilence( hb_work_object_t * w, int i, int64_t duration ) @@ -757,23 +931,37 @@ static void InsertSilence( hb_work_object_t * w, int i, int64_t duration ) hb_job_t *job = pv->job; hb_sync_audio_t *sync = &pv->sync_audio[i]; hb_buffer_t *buf; + hb_fifo_t *fifo; - 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 + duration; - memcpy( buf->data, sync->ac3_buf, buf->size ); - OutputAudioFrame( job, sync->audio, buf, sync, sync->audio->priv.fifo_out, i ); - } - else + // to keep pass-thru and regular audio in sync we generate silence in + // AC3 frame-sized units. If the silence duration isn't an integer multiple + // of the AC3 frame duration we will truncate or round up depending on + // which minimizes the timing error. + const int frame_dur = ( 90000 * AC3_SAMPLES_PER_FRAME ) / + sync->audio->config.in.samplerate; + int frame_count = ( duration + (frame_dur >> 1) ) / frame_dur; + + while ( --frame_count >= 0 ) { - buf = hb_buffer_init( duration * sizeof( float ) * - HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT(sync->audio->config.out.mixdown) ); - buf->start = sync->next_pts; - buf->stop = buf->start + duration; - memset( buf->data, 0, buf->size ); - OutputAudioFrame( job, sync->audio, buf, sync, sync->audio->priv.fifo_sync, i ); + 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 ); } } @@ -785,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++; @@ -812,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;