diff options
Diffstat (limited to 'libhb/sync.c')
-rw-r--r-- | libhb/sync.c | 501 |
1 files changed, 459 insertions, 42 deletions
diff --git a/libhb/sync.c b/libhb/sync.c index 48464d834..6fe00c206 100644 --- a/libhb/sync.c +++ b/libhb/sync.c @@ -17,7 +17,7 @@ // Audio is small, buffer a lot. It helps to ensure that we see // the initial PTS from all input streams before setting the 'zero' point. -#define SYNC_MAX_AUDIO_QUEUE_LEN 100 +#define SYNC_MAX_AUDIO_QUEUE_LEN 200 #define SYNC_MIN_AUDIO_QUEUE_LEN 30 // We do not place a limit on the number of subtitle frames @@ -57,12 +57,22 @@ typedef struct typedef struct sync_common_s sync_common_t; +#define SCR_HASH_SZ (2 << 3) +#define SCR_HASH_MASK (SCR_HASH_SZ - 1) + +typedef struct +{ + int scr_sequence; + int64_t scr_offset; +} scr_t; + typedef struct { sync_common_t * common; // Stream I/O control hb_list_t * in_queue; + hb_list_t * scr_delay_queue; int max_len; int min_len; hb_cond_t * cond_full; @@ -74,6 +84,11 @@ typedef struct double next_pts; double last_pts; + // SCR recovery + int last_scr_sequence; + double last_scr_pts; + double last_duration; + // frame statistics int64_t first_pts; int64_t min_frame_duration; @@ -127,7 +142,7 @@ typedef struct struct sync_common_s { - /* Audio/Video sync thread synchronization */ + // Audio/Video sync thread synchronization hb_job_t * job; hb_lock_t * mutex; int stream_count; @@ -135,6 +150,10 @@ struct sync_common_s int found_first_pts; int done; + // SCR adjustments + scr_t scr[SCR_HASH_SZ]; + int first_scr; + // point-to-point support int start_found; int64_t start_pts; @@ -163,8 +182,10 @@ struct hb_work_private_s static void UpdateState( sync_common_t * common, int frame_count ); static void UpdateSearchState( sync_common_t * common, int64_t start, int frame_count ); +static int UpdateSCR( sync_stream_t * stream, hb_buffer_t * buf ); static hb_buffer_t * FilterAudioFrame( sync_stream_t * stream, hb_buffer_t *buf ); +static void SortedQueueBuffer( sync_stream_t * stream, hb_buffer_t * buf ); static hb_buffer_t * sanitizeSubtitle(sync_stream_t * stream, hb_buffer_t * sub); @@ -223,16 +244,12 @@ static void signalBuffer( sync_stream_t * stream ) } } -static void allSlip( sync_common_t * common, int64_t delta ) +static void scrSlip( sync_common_t * common, int64_t delta ) { int ii; - for (ii = 0; ii < common->stream_count; ii++) + for (ii = 0; ii < SCR_HASH_SZ; ii++) { - common->streams[ii].pts_slip += delta; - if (common->streams[ii].next_pts != (int64_t)AV_NOPTS_VALUE) - { - common->streams[ii].next_pts -= delta; - } + common->scr[ii].scr_offset += delta; } } @@ -240,27 +257,105 @@ static void shiftTS( sync_common_t * common, int64_t delta ) { int ii, jj; - allSlip(common, delta); + scrSlip(common, delta); for (ii = 0; ii < common->stream_count; ii++) { + hb_buffer_t * buf = NULL; sync_stream_t * stream = &common->streams[ii]; - int count = hb_list_count(stream->in_queue); + int count = hb_list_count(stream->in_queue); + for (jj = 0; jj < count; jj++) { - hb_buffer_t * buf = hb_list_item(stream->in_queue, jj); - buf->s.start -= delta; + buf = hb_list_item(stream->in_queue, jj); + if (buf->s.start != AV_NOPTS_VALUE) + { + buf->s.start -= delta; + } if (buf->s.stop != AV_NOPTS_VALUE) { buf->s.stop -= delta; } } + if (buf != NULL && buf->s.start != AV_NOPTS_VALUE) + { + stream->last_scr_pts = buf->s.start + buf->s.duration; + } + else + { + stream->last_scr_pts = (int64_t)AV_NOPTS_VALUE; + } + } +} + +static void computeInitialTS( sync_common_t * common, + sync_stream_t * first_stream ) +{ + int ii, count; + hb_buffer_t * prev; + + // Process first_stream first since it has the initial PTS + prev = NULL; + count = hb_list_count(first_stream->in_queue); + for (ii = 0; ii < count; ii++) + { + hb_buffer_t * buf = hb_list_item(first_stream->in_queue, ii); + if (UpdateSCR(first_stream, buf)) + { + if (first_stream->type == SYNC_TYPE_VIDEO && prev != NULL) + { + double duration = buf->s.start - prev->s.start; + if (duration > 0) + { + prev->s.duration = duration; + prev->s.stop = buf->s.start; + } + } + } + prev = buf; + } + for (ii = 0; ii < common->stream_count; ii++) + { + sync_stream_t * stream = &common->streams[ii]; + + if (stream == first_stream) + { + // skip first_stream, already done + continue; + } + + int jj; + prev = NULL; + for (jj = 0; jj < hb_list_count(stream->in_queue);) + { + hb_buffer_t * buf = hb_list_item(stream->in_queue, jj); + if (!UpdateSCR(stream, buf)) + { + // Subtitle put into delay queue, remove it from in_queue + hb_list_rem(stream->in_queue, buf); + } + else + { + jj++; + if (stream->type == SYNC_TYPE_VIDEO && prev != NULL) + { + double duration = buf->s.start - prev->s.start; + if (duration > 0) + { + prev->s.duration = duration; + prev->s.stop = buf->s.start; + } + } + } + prev = buf; + } } } static void checkFirstPts( sync_common_t * common ) { - int ii; - int64_t first_pts = INT64_MAX; + int ii; + int64_t first_pts = INT64_MAX; + sync_stream_t * first_stream = NULL; for (ii = 0; ii < common->stream_count; ii++) { @@ -275,22 +370,30 @@ static void checkFirstPts( sync_common_t * common ) if (hb_list_count(stream->in_queue) > 0) { hb_buffer_t * buf = hb_list_item(stream->in_queue, 0); - if (first_pts > buf->s.start) + if (buf->s.start != AV_NOPTS_VALUE && buf->s.start < first_pts) { first_pts = buf->s.start; + first_stream = stream; } } } + // We should *always* find a first pts because we let the queues + // fill before performing this test. if (first_pts != INT64_MAX) { - // Add a fudge factor to first pts to prevent negative - // timestamps from leaking through. The pipeline can - // handle a positive offset, but some things choke on - // negative offsets - //first_pts -= 500000; - shiftTS(common, first_pts); + common->found_first_pts = 1; + // We may have buffers that have no timestamps (i.e. AV_NOPTS_VALUE). + // Compute these timestamps based on previous buffer's timestamp + // and duration. + computeInitialTS(common, first_stream); + // After this initialization, all AV_NOPTS_VALUE timestamps + // will be filled in by UpdateSCR() + } + else + { + // This should never happen + hb_error("checkFirstPts: No initial PTS found!\n"); } - common->found_first_pts = 1; } static void addDelta( sync_common_t * common, int64_t start, int64_t delta) @@ -586,6 +689,56 @@ static void dejitterAudio( sync_stream_t * stream ) } } +static hb_buffer_t * CreateSilenceBuf( sync_stream_t * stream, int64_t dur ) +{ + double frame_dur, next_pts; + int size; + hb_buffer_list_t list; + hb_buffer_t * buf; + + if (stream->audio.audio->config.out.codec & HB_ACODEC_PASS_FLAG) + { + return NULL; + } + frame_dur = (90000. * stream->audio.audio->config.out.samples_per_frame) / + stream->audio.audio->config.in.samplerate; + size = sizeof(float) * stream->audio.audio->config.out.samples_per_frame * + hb_mixdown_get_discrete_channel_count( + stream->audio.audio->config.out.mixdown ); + + hb_buffer_list_clear(&list); + next_pts = stream->next_pts; + while (dur >= frame_dur) + { + buf = hb_buffer_init(size); + memset(buf->data, 0, buf->size); + buf->s.start = next_pts; + buf->s.duration = frame_dur; + next_pts += frame_dur; + buf->s.stop = next_pts; + dur -= frame_dur; + hb_buffer_list_append(&list, buf); + } + if (dur > 0) + { + size = sizeof(float) * + (dur * stream->audio.audio->config.in.samplerate / 90000) * + hb_mixdown_get_discrete_channel_count( + stream->audio.audio->config.out.mixdown ); + if (size > 0) + { + buf = hb_buffer_init(size); + memset(buf->data, 0, buf->size); + buf->s.start = next_pts; + buf->s.duration = frame_dur; + next_pts += frame_dur; + buf->s.stop = next_pts; + hb_buffer_list_append(&list, buf); + } + } + return hb_buffer_list_clear(&list); +} + // Fix audio gaps that could not be corrected with dejitter static void fixAudioGap( sync_stream_t * stream ) { @@ -609,8 +762,23 @@ static void fixAudioGap( sync_stream_t * stream ) { stream->gap_pts = buf->s.start; } - addDelta(stream->common, stream->next_pts, gap); - applyDeltas(stream->common); + buf = CreateSilenceBuf(stream, gap); + if (buf != NULL) + { + hb_buffer_t * next; + int pos; + for (pos = 0; buf != NULL; buf = next, pos++) + { + next = buf->next; + buf->next = NULL; + hb_list_insert(stream->in_queue, pos, buf); + } + } + else + { + addDelta(stream->common, stream->next_pts, gap); + applyDeltas(stream->common); + } stream->gap_duration += gap; } else @@ -1305,26 +1473,70 @@ static void Synchronize( sync_stream_t * stream ) hb_unlock(common->mutex); } -static void updateDuration( sync_stream_t * stream, int64_t start ) +static void updateDuration( sync_stream_t * stream ) { - // The video decoder does not set an initial duration for frames. - // So set it here. + // The video decoder sets a nominal duration for frames. But the + // actual duration needs to be computed from timestamps. if (stream->type == SYNC_TYPE_VIDEO) { int count = hb_list_count(stream->in_queue); - if (count > 0) + if (count >= 2) { - hb_buffer_t * buf = hb_list_item(stream->in_queue, count - 1); - double duration = start - buf->s.start; + hb_buffer_t * buf1 = hb_list_item(stream->in_queue, count - 1); + hb_buffer_t * buf2 = hb_list_item(stream->in_queue, count - 2); + double duration = buf1->s.start - buf2->s.start; if (duration > 0) { - buf->s.duration = duration; - buf->s.stop = start; + buf2->s.duration = duration; + buf2->s.stop = buf1->s.start; + } + else + { + buf2->s.duration = 0.; + buf2->s.start = buf2->s.stop = buf1->s.start; + } + } + } +} + +static void ProcessSCRDelayQueue( sync_common_t * common ) +{ + int ii, jj; + + for (ii = 0; ii < common->stream_count; ii++) + { + sync_stream_t * stream = &common->streams[ii]; + for (jj = 0; jj < hb_list_count(stream->scr_delay_queue);) + { + hb_buffer_t * buf = hb_list_item(stream->scr_delay_queue, jj); + int hash = buf->s.scr_sequence & SCR_HASH_MASK; + if (buf->s.scr_sequence < 0) + { + // Unset scr_sequence inidicates an external stream + // (e.g. SRT subtitle) that is not on the same timebase + // as the source tracks. Do not adjust timestamps for + // scr_offset in this case. + hb_list_rem(stream->scr_delay_queue, buf); + SortedQueueBuffer(stream, buf); + } + else if (buf->s.scr_sequence == common->scr[hash].scr_sequence) + { + if (buf->s.start != AV_NOPTS_VALUE) + { + buf->s.start -= common->scr[hash].scr_offset; + buf->s.start -= stream->pts_slip; + } + if (buf->s.stop != AV_NOPTS_VALUE) + { + buf->s.stop -= common->scr[hash].scr_offset; + buf->s.stop -= stream->pts_slip; + } + hb_list_rem(stream->scr_delay_queue, buf); + SortedQueueBuffer(stream, buf); } else { - buf->s.duration = 0.; - buf->s.stop = buf->s.start; + jj++; } } } @@ -1360,6 +1572,174 @@ static int getStreamId( sync_stream_t * stream ) } } +static int UpdateSCR( sync_stream_t * stream, hb_buffer_t * buf ) +{ + int hash = buf->s.scr_sequence & SCR_HASH_MASK; + sync_common_t * common = stream->common; + double last_scr_pts, last_duration; + int64_t scr_offset = 0; + + if (buf->s.scr_sequence < stream->last_scr_sequence) + { + // In decoder error conditions, the decoder can send us out of + // order frames. Often the stream error will also trigger a + // discontinuity detection. An out of order frame will + // cause an incorrect SCR offset, so drop such frames. + hb_deep_log(3, "SCR sequence went backwards %d -> %d", + stream->last_scr_sequence, buf->s.scr_sequence); + hb_buffer_close(&buf); + return 0; + } + if (buf->s.scr_sequence >= 0) + { + if (buf->s.scr_sequence != common->scr[hash].scr_sequence) + { + if (stream->type == SYNC_TYPE_SUBTITLE || + (stream->last_scr_pts == (int64_t)AV_NOPTS_VALUE && + common->first_scr)) + { + // We got a new scr, but we have no last_scr_pts to base it + // off of. Delay till we can compute the scr offset from a + // different stream. + hb_list_add(stream->scr_delay_queue, buf); + return 0; + } + if (buf->s.start != AV_NOPTS_VALUE) + { + last_scr_pts = stream->last_scr_pts; + last_duration = stream->last_duration; + if (last_scr_pts == (int64_t)AV_NOPTS_VALUE) + { + last_scr_pts = 0.; + last_duration = 0.; + common->first_scr = 1; + } + // New SCR. Compute SCR offset + common->scr[hash].scr_sequence = buf->s.scr_sequence; + common->scr[hash].scr_offset = buf->s.start - + (last_scr_pts + last_duration); + hb_deep_log(4, + "New SCR: type %8s id %x scr seq %d scr offset %ld " + "start %"PRId64" last %f dur %f", + getStreamType(stream), getStreamId(stream), + buf->s.scr_sequence, common->scr[hash].scr_offset, + buf->s.start, last_scr_pts, last_duration); + ProcessSCRDelayQueue(common); + } + } + scr_offset = common->scr[hash].scr_offset; + } + + // Adjust buffer timestamps for SCR offset + if (buf->s.start != AV_NOPTS_VALUE) + { + buf->s.start -= scr_offset; + last_scr_pts = buf->s.start; + } + else if (stream->last_scr_pts != (int64_t)AV_NOPTS_VALUE) + { + last_scr_pts = stream->last_scr_pts + stream->last_duration; + buf->s.start = last_scr_pts; + } + else + { + // This should happen extremely rarely if ever. + // But if we get here, this is the first buffer received for + // this stream. Normally, some buffers will be queued for + // every stream before we ever call UpdateSCR. + // We don't really know what it's timestamp should be, + // but 0 is a good guess. + buf->s.start = 0; + last_scr_pts = buf->s.start; + } + if (buf->s.stop != AV_NOPTS_VALUE) + { + buf->s.stop -= scr_offset; + } + if (last_scr_pts > stream->last_scr_pts) + { + stream->last_scr_pts = last_scr_pts; + } + if (buf->s.scr_sequence > stream->last_scr_sequence) + { + stream->last_scr_sequence = buf->s.scr_sequence; + } + stream->last_duration = buf->s.duration; + + return 1; +} + +// Handle broken timestamps that are out of order +// These are usually due to a broken decoder (e.g. QSV and libav AVI packed +// b-frame support). But sometimes can come from a severely broken or +// corrupted source file. +// +// We can pretty reliably fix out of order timestamps *if* every frame +// has a timestamp. But some container formats allow frames with no +// timestamp (e.g. TS and PS). When there is no timestamp, we will +// compute one based on the last frames timestamp. If the missing +// timestamp is out of order and really belonged on an earlier frame (A), +// this will result in the frame before (A) being long and the frame +// after the current will overlap current. +// +// The condition above of one long frame and one overlap will most likely +// get fixed by dejitterVideo. dejitterVideo finds sequences where the +// sum of the durations of frames 1..N == (1/fps) * N. When it finds such +// a sequence, it adjusts the frame durations to all be 1/fps. Since the +// vast majority of video is constant framerate, this will fix the above +// problem most of the time. +static void SortedQueueBuffer( sync_stream_t * stream, hb_buffer_t * buf ) +{ + int64_t start; + int ii, count; + + start = buf->s.start; + hb_list_add(stream->in_queue, buf); + + // Search for the first earlier timestamp that is < this one. + // Under normal circumstances where the timestamps are not broken, + // this will only check the next to last buffer in the queue + // before aborting. + count = hb_list_count(stream->in_queue); + for (ii = count - 2; ii >= 0; ii--) + { + buf = hb_list_item(stream->in_queue, ii); + if (buf->s.start < start || start == AV_NOPTS_VALUE) + { + break; + } + } + if (ii < count - 2) + { + hb_buffer_t * prev = NULL; + int jj; + + // The timestamp was out of order. + // The timestamp belongs at position ii + 1 + // Every timestamp from ii + 2 to count - 1 needs to be shifted up. + if (ii >= 0) + { + prev = hb_list_item(stream->in_queue, ii); + } + for (jj = ii + 1; jj < count; jj++) + { + int64_t tmp_start; + + buf = hb_list_item(stream->in_queue, jj); + tmp_start = buf->s.start; + buf->s.start = start; + start = tmp_start; + if (stream->type == SYNC_TYPE_VIDEO && prev != NULL) + { + // recompute video buffer duration + prev->s.duration = buf->s.start - prev->s.start; + prev->s.stop = buf->s.start; + } + prev = buf; + } + } +} + static void QueueBuffer( sync_stream_t * stream, hb_buffer_t * buf ) { hb_lock(stream->common->mutex); @@ -1374,17 +1754,42 @@ static void QueueBuffer( sync_stream_t * stream, hb_buffer_t * buf ) buf->s.renderOffset = AV_NOPTS_VALUE; hb_deep_log(11, - "type %8s id %x start %"PRId64" stop %"PRId64" dur %f", - getStreamType(stream), getStreamId(stream), + "type %8s id %x scr seq %d start %"PRId64" stop %"PRId64" dur %f", + getStreamType(stream), getStreamId(stream), buf->s.scr_sequence, buf->s.start, buf->s.stop, buf->s.duration); - buf->s.start -= stream->pts_slip; - if (buf->s.stop != AV_NOPTS_VALUE) + if (stream->common->found_first_pts) + { + if (UpdateSCR(stream, buf)) + { + // Apply any stream slips. + // Stream slips will only temporarily differ between + // the streams. The slips get updated in applyDeltas. When + // all the deltas are absorbed, the stream slips will all + // be equal. + buf->s.start -= stream->pts_slip; + if (buf->s.stop != AV_NOPTS_VALUE) + { + buf->s.stop -= stream->pts_slip; + } + + SortedQueueBuffer(stream, buf); + updateDuration(stream); + } + } + else { - buf->s.stop -= stream->pts_slip; + if (buf->s.start == AV_NOPTS_VALUE && + hb_list_count(stream->in_queue) == 0) + { + // We require an initial pts to start synchronization + saveChap(stream, buf); + hb_buffer_close(&buf); + hb_unlock(stream->common->mutex); + return; + } + SortedQueueBuffer(stream, buf); } - updateDuration(stream, buf->s.start); - hb_list_add(stream->in_queue, buf); // Make adjustments for gaps found in other streams applyDeltas(stream->common); @@ -1426,6 +1831,7 @@ static int InitAudio( sync_common_t * common, int index ) pv->stream->cond_full = hb_cond_init(); if (pv->stream->cond_full == NULL) goto fail; pv->stream->in_queue = hb_list_init(); + pv->stream->scr_delay_queue = hb_list_init(); pv->stream->max_len = SYNC_MAX_AUDIO_QUEUE_LEN; pv->stream->min_len = SYNC_MIN_AUDIO_QUEUE_LEN; if (pv->stream->in_queue == NULL) goto fail; @@ -1435,6 +1841,9 @@ static int InitAudio( sync_common_t * common, int index ) pv->stream->first_pts = AV_NOPTS_VALUE; pv->stream->next_pts = (int64_t)AV_NOPTS_VALUE; pv->stream->last_pts = (int64_t)AV_NOPTS_VALUE; + pv->stream->last_scr_pts = (int64_t)AV_NOPTS_VALUE; + pv->stream->last_scr_sequence = -1; + pv->stream->last_duration = (int64_t)AV_NOPTS_VALUE; pv->stream->audio.audio = audio; pv->stream->fifo_out = w->fifo_out; @@ -1498,6 +1907,7 @@ static int InitSubtitle( sync_common_t * common, int index ) pv->stream->cond_full = hb_cond_init(); if (pv->stream->cond_full == NULL) goto fail; pv->stream->in_queue = hb_list_init(); + pv->stream->scr_delay_queue = hb_list_init(); pv->stream->max_len = SYNC_MAX_SUBTITLE_QUEUE_LEN; pv->stream->min_len = SYNC_MIN_SUBTITLE_QUEUE_LEN; if (pv->stream->in_queue == NULL) goto fail; @@ -1507,6 +1917,9 @@ static int InitSubtitle( sync_common_t * common, int index ) pv->stream->first_pts = AV_NOPTS_VALUE; pv->stream->next_pts = (int64_t)AV_NOPTS_VALUE; pv->stream->last_pts = (int64_t)AV_NOPTS_VALUE; + pv->stream->last_scr_pts = (int64_t)AV_NOPTS_VALUE; + pv->stream->last_scr_sequence = -1; + pv->stream->last_duration = (int64_t)AV_NOPTS_VALUE; pv->stream->subtitle.subtitle = subtitle; pv->stream->fifo_out = subtitle->fifo_out; @@ -1594,6 +2007,7 @@ static int syncVideoInit( hb_work_object_t * w, hb_job_t * job) pv->stream->cond_full = hb_cond_init(); if (pv->stream->cond_full == NULL) goto fail; pv->stream->in_queue = hb_list_init(); + pv->stream->scr_delay_queue = hb_list_init(); pv->stream->max_len = SYNC_MAX_VIDEO_QUEUE_LEN; pv->stream->min_len = SYNC_MIN_VIDEO_QUEUE_LEN; if (pv->stream->in_queue == NULL) goto fail; @@ -1603,6 +2017,9 @@ static int syncVideoInit( hb_work_object_t * w, hb_job_t * job) pv->stream->first_pts = AV_NOPTS_VALUE; pv->stream->next_pts = (int64_t)AV_NOPTS_VALUE; pv->stream->last_pts = (int64_t)AV_NOPTS_VALUE; + pv->stream->last_scr_pts = (int64_t)AV_NOPTS_VALUE; + pv->stream->last_scr_sequence = -1; + pv->stream->last_duration = (int64_t)AV_NOPTS_VALUE; pv->stream->fifo_out = job->fifo_sync; pv->stream->video.id = job->title->video_id; |