summaryrefslogtreecommitdiffstats
path: root/libhb
diff options
context:
space:
mode:
authorJohn Stebbins <[email protected]>2016-05-24 14:12:07 -0700
committerJohn Stebbins <[email protected]>2016-05-24 14:12:07 -0700
commite8c37c88b864f515aacda7a1a8fbcda9ed6b8bad (patch)
tree96b423f89f5a78af0a64db32d64b9dae50c63f67 /libhb
parent76595a4b2131536eb3e498b161944057e882d038 (diff)
sync: correct timestamp discontinuities in sync instead of reader (#192)
* sync: correct timestamp discontinuities in sync instead of reader This patch passes discontinuity information through the pipeline till it reaches sync.c. The timestamps are passed through the pipeline as read and unmodified to sync.c (instead of attempting to correct discontinuities in reader). In sync, when we see a discontinuity, we know where the next timestamp should be based on the timestamp and duration of the previous buffer (before the discontinuity). So we calculate an "SCR" offset based on the timestamp after the discontinuity and what we calculate it should be. The old discontinuity handling code was broken due to the following. The MPEG STD timing model relies heavily on the decoder having an STC that is phase lock looped to the PCRs in the stream. When decoding a broadcast stream, the decoder can count on the time measure between PCRs using the STC to match to a high degree of accuracy. I.e. STC - lastSTC == PCR - lastPCR. When a discontinuity occurs, the decoder calculates a new PCR offset = PCR - STC. I.e. the offset is the new PCR value minus what it would have been if there had been no discontinuity. The above does not work without a reliable STC, which we do not have. We have been attempting to approximate one by avereraging the duration of received packets and extrapolating an "STC" based on the last PTS and the average packet duration. But this is highly variable and unreliable. * decavcodec: fix data type of next_pts It needs to be double so that partial ticks are not lost * deccc608sub: clarify comment * sync: allow queueing more audio Audio is small, and there is often a significant amount of audio in the stream before the first video frame. * sync: improve handling of damaged streams When data is missing, the audio decoder was extrapolating timestamps from the last pts before the error caused by the missing data which caused sync issues. Also, missing data can cause the video decoder to output a frame out of order with the wrong scr sequence. Drop such frames.
Diffstat (limited to 'libhb')
-rw-r--r--libhb/decavcodec.c743
-rw-r--r--libhb/deccc608sub.c14
-rw-r--r--libhb/deccc608sub.h2
-rw-r--r--libhb/declpcm.c20
-rw-r--r--libhb/decpgssub.c2
-rw-r--r--libhb/decssasub.c47
-rw-r--r--libhb/dectx3gsub.c7
-rw-r--r--libhb/decvobsub.c46
-rw-r--r--libhb/fifo.c16
-rw-r--r--libhb/internal.h2
-rw-r--r--libhb/reader.c514
-rw-r--r--libhb/sync.c501
12 files changed, 1022 insertions, 892 deletions
diff --git a/libhb/decavcodec.c b/libhb/decavcodec.c
index c35ec6343..eef1059df 100644
--- a/libhb/decavcodec.c
+++ b/libhb/decavcodec.c
@@ -47,7 +47,6 @@
#endif
static void compute_frame_duration( hb_work_private_t *pv );
-static void flushDelayQueue( hb_work_private_t *pv );
static int decavcodecaInit( hb_work_object_t *, hb_job_t * );
static int decavcodecaWork( hb_work_object_t *, hb_buffer_t **, hb_buffer_t ** );
static void decavcodecClose( hb_work_object_t * );
@@ -65,14 +64,29 @@ hb_work_object_t hb_decavcodeca =
.bsinfo = decavcodecaBSInfo
};
-#define HEAP_SIZE 8
-typedef struct {
- // there are nheap items on the heap indexed 1..nheap (i.e., top of
- // heap is 1). The 0th slot is unused - a marker is put there to check
- // for overwrite errs.
- int64_t h[HEAP_SIZE+1];
- int nheap;
-} pts_heap_t;
+typedef struct
+{
+ uint8_t * data;
+ int size;
+ int64_t pts;
+ int64_t dts;
+ int frametype;
+ int scr_sequence;
+ int new_chap;
+} packet_info_t;
+
+typedef struct reordered_data_s reordered_data_t;
+
+struct reordered_data_s
+{
+ int64_t sequence;
+ int64_t pts;
+ int scr_sequence;
+ int new_chap;
+};
+
+#define REORDERED_HASH_SZ (2 << 7)
+#define REORDERED_HASH_MASK (REORDERED_HASH_SZ - 1)
struct hb_work_private_s
{
@@ -87,24 +101,26 @@ struct hb_work_private_s
hb_buffer_list_t list;
double duration; // frame duration (for video)
double field_duration; // field duration (for video)
- double pts_next; // next pts we expect to generate
- int64_t chap_time; // time of next chap mark (if new_chap != 0)
+ int64_t chap_time; // time of next chap mark
+ int chap_scr;
int new_chap; // output chapter mark pending
+ int64_t last_pts;
+ double next_pts;
uint32_t nframes;
- uint32_t ndrops;
uint32_t decode_errors;
- int64_t prev_pts;
- int brokenTS; // video stream may contain packed b-frames
- hb_buffer_t* delayq[HEAP_SIZE];
- int queue_primed;
- pts_heap_t pts_heap;
- void* buffer;
+ packet_info_t packet_info;
+ uint8_t unfinished;
+ reordered_data_t * reordered_hash[REORDERED_HASH_SZ];
+ int64_t sequence;
+ int last_scr_sequence;
+ int last_chapter;
struct SwsContext * sws_context; // if we have to rescale or convert color space
+
int sws_width;
int sws_height;
int sws_pix_fmt;
- int cadence[12];
- int wait_for_keyframe;
+
+ hb_audio_t * audio;
hb_audio_resample_t * resample;
#ifdef USE_QSV
@@ -114,129 +130,13 @@ struct hb_work_private_s
int decode;
av_qsv_config config;
const char * codec_name;
-#define USE_QSV_PTS_WORKAROUND // work around out-of-order output timestamps
-#ifdef USE_QSV_PTS_WORKAROUND
- hb_list_t * pts_list;
-#endif
} qsv;
#endif
hb_list_t * list_subtitle;
};
-#ifdef USE_QSV_PTS_WORKAROUND
-// save/restore PTS if the decoder may not attach the right PTS to the frame
-static void hb_av_add_new_pts(hb_list_t *list, int64_t new_pts)
-{
- int index = 0;
- int64_t *cur_item, *new_item;
- if (list != NULL && new_pts != AV_NOPTS_VALUE)
- {
- new_item = malloc(sizeof(int64_t));
- if (new_item != NULL)
- {
- *new_item = new_pts;
- // sort chronologically
- for (index = 0; index < hb_list_count(list); index++)
- {
- cur_item = hb_list_item(list, index);
- if (cur_item != NULL)
- {
- if (*cur_item == *new_item)
- {
- // no duplicates
- free(new_item);
- return;
- }
- if (*cur_item > *new_item)
- {
- // insert here
- break;
- }
- }
- }
- hb_list_insert(list, index, new_item);
- }
- }
-}
-static int64_t hb_av_pop_next_pts(hb_list_t *list)
-{
- int64_t *item, next_pts = AV_NOPTS_VALUE;
- if (list != NULL && hb_list_count(list) > 0)
- {
- item = hb_list_item(list, 0);
- if (item != NULL)
- {
- next_pts = *item;
- hb_list_rem(list, item);
- free(item);
- }
- }
- return next_pts;
-}
-#endif
-
-static void decodeAudio( hb_audio_t * audio, hb_work_private_t *pv, uint8_t *data, int size, int64_t pts );
-
-
-static int64_t heap_pop( pts_heap_t *heap )
-{
- int64_t result;
-
- if ( heap->nheap <= 0 )
- {
- return AV_NOPTS_VALUE;
- }
-
- // return the top of the heap then put the bottom element on top,
- // decrease the heap size by one & rebalence the heap.
- result = heap->h[1];
-
- int64_t v = heap->h[heap->nheap--];
- int parent = 1;
- int child = parent << 1;
- while ( child <= heap->nheap )
- {
- // find the smallest of the two children of parent
- if (child < heap->nheap && heap->h[child] > heap->h[child+1] )
- ++child;
-
- if (v <= heap->h[child])
- // new item is smaller than either child so it's the new parent.
- break;
-
- // smallest child is smaller than new item so move it up then
- // check its children.
- int64_t hp = heap->h[child];
- heap->h[parent] = hp;
- parent = child;
- child = parent << 1;
- }
- heap->h[parent] = v;
- return result;
-}
-
-static void heap_push( pts_heap_t *heap, int64_t v )
-{
- if ( heap->nheap < HEAP_SIZE )
- {
- ++heap->nheap;
- }
-
- // stick the new value on the bottom of the heap then bubble it
- // up to its correct spot.
- int child = heap->nheap;
- while (child > 1) {
- int parent = child >> 1;
- if (heap->h[parent] <= v)
- break;
- // move parent down
- int64_t hp = heap->h[parent];
- heap->h[child] = hp;
- child = parent;
- }
- heap->h[child] = v;
-}
+static void decodeAudio( hb_work_private_t *pv, packet_info_t * packet_info );
/***********************************************************************
* hb_work_decavcodec_init
@@ -250,7 +150,9 @@ static int decavcodecaInit( hb_work_object_t * w, hb_job_t * job )
hb_work_private_t * pv = calloc( 1, sizeof( hb_work_private_t ) );
w->private_data = pv;
- pv->job = job;
+ pv->job = job;
+ pv->audio = w->audio;
+ pv->next_pts = (int64_t)AV_NOPTS_VALUE;
if (job)
pv->title = job->title;
else
@@ -418,14 +320,12 @@ static void closePrivData( hb_work_private_t ** ppv )
if ( pv )
{
- flushDelayQueue( pv );
hb_buffer_list_close(&pv->list);
if ( pv->job && pv->context && pv->context->codec )
{
- hb_log( "%s-decoder done: %u frames, %u decoder errors, %u drops",
- pv->context->codec->name, pv->nframes, pv->decode_errors,
- pv->ndrops );
+ hb_log( "%s-decoder done: %u frames, %u decoder errors",
+ pv->context->codec->name, pv->nframes, pv->decode_errors);
}
av_frame_free(&pv->frame);
if ( pv->sws_context )
@@ -460,20 +360,11 @@ static void closePrivData( hb_work_private_t ** ppv )
}
hb_audio_resample_free(pv->resample);
-#ifdef USE_QSV_PTS_WORKAROUND
- if (pv->qsv.decode && pv->qsv.pts_list != NULL)
-
+ int ii;
+ for (ii = 0; ii < REORDERED_HASH_SZ; ii++)
{
- while (hb_list_count(pv->qsv.pts_list) > 0)
- {
- int64_t *item = hb_list_item(pv->qsv.pts_list, 0);
- hb_list_rem(pv->qsv.pts_list, item);
- free(item);
- }
- hb_list_close(&pv->qsv.pts_list);
+ free(pv->reordered_hash[ii]);
}
-#endif
-
free(pv);
}
*ppv = NULL;
@@ -496,7 +387,7 @@ static void decavcodecClose( hb_work_object_t * w )
*
**********************************************************************/
static int decavcodecaWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
- hb_buffer_t ** buf_out )
+ hb_buffer_t ** buf_out )
{
hb_work_private_t * pv = w->private_data;
hb_buffer_t * in = *buf_in;
@@ -519,35 +410,65 @@ static int decavcodecaWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
*buf_out = NULL;
- if ( in->s.start < 0 && pv->pts_next <= 0 )
- {
- // discard buffers that start before video time 0
- return HB_WORK_OK;
- }
-
- int pos, len;
- for ( pos = 0; pos < in->size; pos += len )
- {
- uint8_t *pout;
- int pout_len;
- int64_t cur;
-
- cur = in->s.start;
+ int pos, len;
+ int64_t pts = in->s.start;
+
+ // There are a 3 scenarios that can happen here.
+ // 1. The buffer contains exactly one frame of data
+ // 2. The buffer contains multiple frames of data
+ // 3. The buffer contains a partial frame of data
+ //
+ // In scenario 2, we want to be sure that the timestamps are only
+ // applied to the first frame in the buffer. Additional frames
+ // in the buffer will have their timestamps computed in sync.
+ //
+ // In scenario 3, we need to save the ancillary buffer info of an
+ // unfinished frame so it can be applied when we receive the last
+ // buffer of that frame.
+ if (!pv->unfinished)
+ {
+ // New packet, and no previous data pending
+ pv->packet_info.scr_sequence = in->s.scr_sequence;
+ pv->packet_info.new_chap = in->s.new_chap;
+ pv->packet_info.frametype = in->s.frametype;
+ }
+ for (pos = 0; pos < in->size; pos += len)
+ {
+ uint8_t * pout;
+ int pout_len;
+ int64_t parser_pts;
if ( pv->parser != NULL )
{
- len = av_parser_parse2( pv->parser, pv->context, &pout, &pout_len,
- in->data + pos, in->size - pos, cur, cur, 0 );
- cur = pv->parser->pts;
+ len = av_parser_parse2(pv->parser, pv->context, &pout, &pout_len,
+ in->data + pos, in->size - pos,
+ pts, pts, 0 );
+ parser_pts = pv->parser->pts;
+ pts = AV_NOPTS_VALUE;
}
else
{
pout = in->data;
len = pout_len = in->size;
+ parser_pts = in->s.start;
}
if (pout != NULL && pout_len > 0)
{
- decodeAudio( w->audio, pv, pout, pout_len, cur );
+ pv->packet_info.data = pout;
+ pv->packet_info.size = pout_len;
+ pv->packet_info.pts = parser_pts;
+
+ decodeAudio(pv, &pv->packet_info);
+
+ // There could have been an unfinished packet when we entered
+ // decodeVideo that is now finished. The next packet is associated
+ // with the input buffer, so set it's chapter and scr info.
+ pv->packet_info.scr_sequence = in->s.scr_sequence;
+ pv->unfinished = 0;
+ }
+ if (len > 0 && pout_len <= 0)
+ {
+ pv->unfinished = 1;
}
}
*buf_out = hb_buffer_list_clear(&pv->list);
@@ -804,6 +725,37 @@ static int decavcodecaBSInfo( hb_work_object_t *w, const hb_buffer_t *buf,
return ret;
}
+reordered_data_t *
+reordered_hash_rem(hb_work_private_t * pv, int64_t sequence)
+{
+ reordered_data_t * reordered;
+ int slot = sequence & REORDERED_HASH_MASK;
+
+ reordered = pv->reordered_hash[slot];
+ if (reordered == NULL)
+ {
+ // This shouldn't happen...
+ // But, this happens sometimes when libav outputs exactly the same
+ // frame twice for some reason.
+ hb_deep_log(3, "decavcodec: missing sequence %"PRId64"", sequence);
+ }
+ pv->reordered_hash[slot] = NULL;
+ return reordered;
+}
+
+void
+reordered_hash_add(hb_work_private_t * pv, reordered_data_t * reordered)
+{
+ int slot = reordered->sequence & REORDERED_HASH_MASK;
+
+ // Free any unused previous entries.
+ // This can happen due to libav parser feeding partial
+ // frames data to the decoder.
+ // It can also happen due to decoding errors.
+ free(pv->reordered_hash[slot]);
+ pv->reordered_hash[slot] = reordered;
+}
+
/* -------------------------------------------------------------
* General purpose video decoder using libavcodec
*/
@@ -847,7 +799,41 @@ static hb_buffer_t *copy_frame( hb_work_private_t *pv )
h = pv->job->title->geometry.height;
}
- hb_buffer_t *out = hb_video_buffer_init( w, h );
+ reordered_data_t * reordered = NULL;
+ hb_buffer_t * out = hb_video_buffer_init( w, h );
+
+ if (pv->frame->pkt_pts != AV_NOPTS_VALUE)
+ {
+ reordered = reordered_hash_rem(pv, pv->frame->pkt_pts);
+ }
+ if (reordered != NULL)
+ {
+ out->s.scr_sequence = reordered->scr_sequence;
+ out->s.start = reordered->pts;
+ out->s.new_chap = reordered->new_chap;
+ pv->last_scr_sequence = reordered->scr_sequence;
+ pv->last_chapter = reordered->new_chap;
+ free(reordered);
+ }
+ else
+ {
+ out->s.scr_sequence = pv->last_scr_sequence;
+ out->s.start = AV_NOPTS_VALUE;
+ }
+ if (out->s.new_chap > 0 && out->s.new_chap == pv->new_chap)
+ {
+ pv->new_chap = 0;
+ }
+ // It is possible that the buffer with new_chap gets dropped
+ // by the decoder. So also check if the output buffer is after
+ // the new_chap in the timeline.
+ if (pv->new_chap > 0 &&
+ (out->s.scr_sequence > pv->chap_scr ||
+ (out->s.scr_sequence == pv->chap_scr && out->s.start > pv->chap_time)))
+ {
+ out->s.new_chap = pv->new_chap;
+ pv->new_chap = 0;
+ }
#ifdef USE_QSV
// no need to copy the frame data when decoding with QSV to opaque memory
@@ -909,21 +895,6 @@ static hb_buffer_t *copy_frame( hb_work_private_t *pv )
return out;
}
-static void flushDelayQueue( hb_work_private_t *pv )
-{
- hb_buffer_t *buf;
- int slot = pv->queue_primed ? pv->nframes & (HEAP_SIZE-1) : 0;
-
- // flush all the video packets left on our timestamp-reordering delay q
- while ((buf = pv->delayq[slot]) != NULL)
- {
- buf->s.start = heap_pop(&pv->pts_heap);
- hb_buffer_list_append(&pv->list, buf);
- pv->delayq[slot] = NULL;
- slot = ( slot + 1 ) & (HEAP_SIZE-1);
- }
-}
-
// send cc_buf to the CC decoder(s)
static void cc_send_to_decoder(hb_work_private_t *pv, hb_buffer_t *buf)
{
@@ -946,7 +917,7 @@ static void cc_send_to_decoder(hb_work_private_t *pv, hb_buffer_t *buf)
hb_fifo_push( subtitle->fifo_in, buf );
}
-static hb_buffer_t * cc_fill_buffer(hb_work_private_t *pv, uint8_t *cc, int size, int64_t pts)
+static hb_buffer_t * cc_fill_buffer(hb_work_private_t *pv, uint8_t *cc, int size)
{
int cc_count[4] = {0,};
int ii;
@@ -966,7 +937,6 @@ static hb_buffer_t * cc_fill_buffer(hb_work_private_t *pv, uint8_t *cc, int size
if (cc_count[0] > 0)
{
buf = hb_buffer_init(cc_count[0] * 2);
- buf->s.start = pts;
int jj = 0;
for (ii = 0; ii < size; ii += 3)
{
@@ -1004,20 +974,13 @@ static int get_frame_type(int type)
* ('data', 'size').
* The output of this function is stored in 'pv->list', which contains a list
* of zero or more decoded packets.
- *
- * The returned packets are guaranteed to have their timestamps in the correct
- * order, even if the original packets decoded by libavcodec have misordered
- * timestamps, due to the use of 'packed B-frames'.
- *
- * Internally the set of decoded packets may be buffered in 'pv->delayq'
- * until enough packets have been decoded so that the timestamps can be
- * correctly rewritten, if this is necessary.
*/
-static int decodeFrame( hb_work_object_t *w, uint8_t *data, int size, int64_t pts, int64_t dts, uint8_t frametype )
+static int decodeFrame( hb_work_object_t *w, packet_info_t * packet_info )
{
hb_work_private_t *pv = w->private_data;
int got_picture, oldlevel = 0;
AVPacket avp;
+ reordered_data_t * reordered;
if ( global_verbosity_level <= 1 )
{
@@ -1026,10 +989,35 @@ static int decodeFrame( hb_work_object_t *w, uint8_t *data, int size, int64_t pt
}
av_init_packet(&avp);
- avp.data = data;
- avp.size = size;
- avp.pts = pts;
- avp.dts = dts;
+ if (packet_info != NULL)
+ {
+ avp.data = packet_info->data;
+ avp.size = packet_info->size;
+ avp.pts = pv->sequence;
+ avp.dts = pv->sequence;
+ reordered = malloc(sizeof(*reordered));
+ if (reordered != NULL)
+ {
+ reordered->sequence = pv->sequence++;
+ reordered->pts = packet_info->pts;
+ reordered->scr_sequence = packet_info->scr_sequence;
+ reordered->new_chap = packet_info->new_chap;
+ }
+ reordered_hash_add(pv, reordered);
+
+ // libav avcodec_decode_video2() needs AVPacket flagged with
+ // AV_PKT_FLAG_KEY for some codecs. For example, sequence of
+ // PNG in a mov container.
+ if (packet_info->frametype & HB_FRAME_KEY)
+ {
+ avp.flags |= AV_PKT_FLAG_KEY;
+ }
+ }
+ else
+ {
+ avp.data = NULL;
+ avp.size = 0;
+ }
if (pv->palette != NULL)
{
@@ -1042,30 +1030,7 @@ static int decodeFrame( hb_work_object_t *w, uint8_t *data, int size, int64_t pt
hb_buffer_close(&pv->palette);
}
- /*
- * libav avcodec_decode_video2() needs AVPacket flagged with AV_PKT_FLAG_KEY
- * for some codecs. For example, sequence of PNG in a mov container.
- */
- if ( frametype & HB_FRAME_KEY )
- {
- avp.flags |= AV_PKT_FLAG_KEY;
- }
-
-#ifdef USE_QSV_PTS_WORKAROUND
- /*
- * The MediaSDK decoder will return decoded frames in the correct order,
- * but *sometimes* with the incorrect timestamp assigned to them.
- *
- * We work around it by saving the input timestamps (in chronological order)
- * and restoring them after decoding.
- */
- if (pv->qsv.decode && avp.data != NULL)
- {
- hb_av_add_new_pts(pv->qsv.pts_list, avp.pts);
- }
-#endif
-
- if ( avcodec_decode_video2( pv->context, pv->frame, &got_picture, &avp ) < 0 )
+ if (avcodec_decode_video2(pv->context, pv->frame, &got_picture, &avp) < 0)
{
++pv->decode_errors;
}
@@ -1079,19 +1044,11 @@ static int decodeFrame( hb_work_object_t *w, uint8_t *data, int size, int64_t pt
}
#endif
-#ifdef USE_QSV_PTS_WORKAROUND
- if (pv->qsv.decode && got_picture)
- {
- // we got a decoded frame, restore the lowest available PTS
- pv->frame->pkt_pts = hb_av_pop_next_pts(pv->qsv.pts_list);
- }
-#endif
-
if ( global_verbosity_level <= 1 )
{
av_log_set_level( oldlevel );
}
- if( got_picture )
+ if (got_picture)
{
uint16_t flags = 0;
@@ -1112,32 +1069,25 @@ static int decodeFrame( hb_work_object_t *w, uint8_t *data, int size, int64_t pt
// recompute the frame/field duration, because sometimes it changes
compute_frame_duration( pv );
- double pts;
- double frame_dur = pv->duration;
+ double frame_dur = pv->duration;
if ( pv->frame->repeat_pict )
{
frame_dur += pv->frame->repeat_pict * pv->field_duration;
}
-
- // If there was no pts for this frame, assume constant frame rate
- // video & estimate the next frame time from the last & duration.
- if (pv->frame->pkt_pts == AV_NOPTS_VALUE)
+ hb_buffer_t * out = copy_frame( pv );
+ if (out->s.start == AV_NOPTS_VALUE)
{
- pts = pv->pts_next;
+ out->s.start = pv->next_pts;
}
else
{
- pts = pv->frame->pkt_pts;
- // Detect streams with broken out of order timestamps
- if (!pv->brokenTS && pv->frame->pkt_pts < pv->prev_pts)
- {
- hb_log("Broken timestamps detected. Reordering.");
- pv->brokenTS = 1;
- }
- pv->prev_pts = pv->frame->pkt_pts;
+ pv->next_pts = out->s.start;
+ }
+ if (pv->next_pts != (int64_t)AV_NOPTS_VALUE)
+ {
+ pv->next_pts += frame_dur;
+ out->s.stop = pv->next_pts;
}
-
- pv->pts_next = pts + frame_dur;
if ( pv->frame->top_field_first )
{
@@ -1209,86 +1159,30 @@ static int decodeFrame( hb_work_object_t *w, uint8_t *data, int size, int64_t pt
if (pv->list_subtitle != NULL && sd->size > 0)
{
hb_buffer_t *cc_buf;
- cc_buf = cc_fill_buffer(pv, sd->data, sd->size, pts);
+ cc_buf = cc_fill_buffer(pv, sd->data, sd->size);
+ if (cc_buf != NULL)
+ {
+ cc_buf->s.start = out->s.start;
+ cc_buf->s.scr_sequence = out->s.scr_sequence;
+ }
cc_send_to_decoder(pv, cc_buf);
}
}
- hb_buffer_t *buf;
-
- // if we're doing a scan or this content couldn't have been broken
- // by Microsoft we don't worry about timestamp reordering
- if ( ! pv->job || ! pv->brokenTS )
- {
- buf = copy_frame( pv );
- av_frame_unref(pv->frame);
- buf->s.start = pts;
-
- buf->s.flags = flags;
- buf->s.frametype = frametype;
-
- if ( pv->new_chap && buf->s.start >= pv->chap_time )
- {
- buf->s.new_chap = pv->new_chap;
- pv->new_chap = 0;
- pv->chap_time = 0;
- }
- hb_buffer_list_append(&pv->list, buf);
- ++pv->nframes;
- return got_picture;
- }
-
- // XXX This following probably addresses a libavcodec bug but I don't
- // see an easy fix so we workaround it here.
- //
- // The M$ 'packed B-frames' atrocity results in decoded frames with
- // the wrong timestamp. E.g., if there are 2 b-frames the timestamps
- // we see here will be "2 3 1 5 6 4 ..." instead of "1 2 3 4 5 6".
- // The frames are actually delivered in the right order but with
- // the wrong timestamp. To get the correct timestamp attached to
- // each frame we have a delay queue (longer than the max number of
- // b-frames) & a sorting heap for the timestamps. As each frame
- // comes out of the decoder the oldest frame in the queue is removed
- // and associated with the smallest timestamp. Then the new frame is
- // added to the queue & its timestamp is pushed on the heap.
- // This does nothing if the timestamps are correct (i.e., the video
- // uses a codec that Micro$oft hasn't broken yet) but the frames
- // get timestamped correctly even when M$ has munged them.
-
- // remove the oldest picture from the frame queue (if any) &
- // give it the smallest timestamp from our heap. The queue size
- // is a power of two so we get the slot of the oldest by masking
- // the frame count & this will become the slot of the newest
- // once we've removed & processed the oldest.
- int slot = pv->nframes & (HEAP_SIZE-1);
- if ( ( buf = pv->delayq[slot] ) != NULL )
- {
- pv->queue_primed = 1;
- buf->s.start = heap_pop( &pv->pts_heap );
- if ( pv->new_chap && buf->s.start >= pv->chap_time )
- {
- buf->s.new_chap = pv->new_chap;
- pv->new_chap = 0;
- pv->chap_time = 0;
- }
- hb_buffer_list_append(&pv->list, buf);
- }
-
- // add the new frame to the delayq & push its timestamp on the heap
- buf = copy_frame( pv );
av_frame_unref(pv->frame);
- /* Store picture flags for later use by filters */
- buf->s.flags = flags;
- buf->s.frametype = frametype;
- pv->delayq[slot] = buf;
- heap_push( &pv->pts_heap, pts );
+ out->s.duration = frame_dur;
+ out->s.flags = flags;
+ out->s.frametype = frametype;
+
+ hb_buffer_list_append(&pv->list, out);
++pv->nframes;
}
return got_picture;
}
-static void decodeVideo( hb_work_object_t *w, uint8_t *data, int size, int64_t pts, int64_t dts, uint8_t frametype )
+
+static void decodeVideo( hb_work_object_t *w, hb_buffer_t * in)
{
hb_work_private_t *pv = w->private_data;
@@ -1298,37 +1192,98 @@ static void decodeVideo( hb_work_object_t *w, uint8_t *data, int size, int64_t p
* generally a frame in the parser & one or more frames in the decoder
* (depending on the bframes setting).
*/
- int pos = 0;
- do {
- uint8_t *pout;
- int pout_len, len;
- int64_t parser_pts, parser_dts;
- if ( pv->parser )
+ int pos, len;
+ int64_t pts = in->s.start;
+ int64_t dts = in->s.renderOffset;
+
+ if (in->s.new_chap > 0)
+ {
+ pv->new_chap = in->s.new_chap;
+ pv->chap_scr = in->s.scr_sequence;
+ if (in->s.start != AV_NOPTS_VALUE)
+ {
+ pv->chap_time = in->s.start;
+ }
+ else
+ {
+ pv->chap_time = pv->last_pts + 1;
+ }
+ }
+ if (in->s.start != AV_NOPTS_VALUE)
+ {
+ pv->last_pts = in->s.start;
+ }
+
+ // There are a 3 scenarios that can happen here.
+ // 1. The buffer contains exactly one frame of data
+ // 2. The buffer contains multiple frames of data
+ // 3. The buffer contains a partial frame of data
+ //
+ // In scenario 2, we want to be sure that the timestamps are only
+ // applied to the first frame in the buffer. Additional frames
+ // in the buffer will have their timestamps computed in sync.
+ //
+ // In scenario 3, we need to save the ancillary buffer info of an
+ // unfinished frame so it can be applied when we receive the last
+ // buffer of that frame.
+ if (!pv->unfinished)
+ {
+ // New packet, and no previous data pending
+ pv->packet_info.scr_sequence = in->s.scr_sequence;
+ pv->packet_info.new_chap = in->s.new_chap;
+ pv->packet_info.frametype = in->s.frametype;
+ }
+ for (pos = 0; pos < in->size; pos += len)
+ {
+ uint8_t * pout;
+ int pout_len;
+ int64_t parser_pts, parser_dts;
+
+ if (pv->parser)
{
- len = av_parser_parse2( pv->parser, pv->context, &pout, &pout_len,
- data + pos, size - pos, pts, dts, 0 );
+ len = av_parser_parse2(pv->parser, pv->context, &pout, &pout_len,
+ in->data + pos, in->size - pos,
+ pts, dts, 0 );
parser_pts = pv->parser->pts;
parser_dts = pv->parser->dts;
+ pts = AV_NOPTS_VALUE;
+ dts = AV_NOPTS_VALUE;
}
else
{
- pout = data;
- len = pout_len = size;
+ pout = in->data;
+ len = pout_len = in->size;
parser_pts = pts;
parser_dts = dts;
}
- pos += len;
- if ( pout_len > 0 )
+ if (pout != NULL && pout_len > 0)
{
- decodeFrame( w, pout, pout_len, parser_pts, parser_dts, frametype );
+ pv->packet_info.data = pout;
+ pv->packet_info.size = pout_len;
+ pv->packet_info.pts = parser_pts;
+ pv->packet_info.dts = parser_dts;
+
+ decodeFrame(w, &pv->packet_info);
+
+ // There could have been an unfinished packet when we entered
+ // decodeVideo that is now finished. The next packet is associated
+ // with the input buffer, so set it's chapter and scr info.
+ pv->packet_info.scr_sequence = in->s.scr_sequence;
+ pv->packet_info.new_chap = in->s.new_chap;
+ pv->packet_info.frametype = in->s.frametype;
+ pv->unfinished = 0;
}
- } while ( pos < size );
+ if (len > 0 && pout_len <= 0)
+ {
+ pv->unfinished = 1;
+ }
+ }
/* the stuff above flushed the parser, now flush the decoder */
- if (size <= 0)
+ if (in->s.flags & HB_BUF_FLAG_EOF)
{
- while (decodeFrame(w, NULL, 0, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0))
+ while (decodeFrame(w, NULL))
{
continue;
}
@@ -1336,13 +1291,12 @@ static void decodeVideo( hb_work_object_t *w, uint8_t *data, int size, int64_t p
if (pv->qsv.decode)
{
// flush a second time
- while (decodeFrame(w, NULL, 0, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0))
+ while (decodeFrame(w, NULL))
{
continue;
}
}
#endif
- flushDelayQueue(pv);
if (pv->list_subtitle != NULL)
cc_send_to_decoder(pv, hb_buffer_eof_init());
}
@@ -1354,8 +1308,8 @@ static int decavcodecvInit( hb_work_object_t * w, hb_job_t * job )
hb_work_private_t *pv = calloc( 1, sizeof( hb_work_private_t ) );
w->private_data = pv;
- pv->wait_for_keyframe = 60;
- pv->job = job;
+ pv->job = job;
+ pv->next_pts = (int64_t)AV_NOPTS_VALUE;
if ( job )
pv->title = job->title;
else
@@ -1427,9 +1381,6 @@ static int decavcodecvInit( hb_work_object_t * w, hb_job_t * job )
#ifdef USE_QSV
if (pv->qsv.decode)
{
-#ifdef USE_QSV_PTS_WORKAROUND
- pv->qsv.pts_list = hb_list_init();
-#endif
// set the QSV configuration before opening the decoder
pv->context->hwaccel_context = &pv->qsv.config;
}
@@ -1452,13 +1403,6 @@ static int decavcodecvInit( hb_work_object_t * w, hb_job_t * job )
av_dict_free( &av_opts );
pv->video_codec_opened = 1;
- // avi, mkv and possibly mp4 containers can contain the M$ VFW packed
- // b-frames abortion that messes up frame ordering and timestamps.
- // XXX ffmpeg knows which streams are broken but doesn't expose the
- // info externally. We should patch ffmpeg to add a flag to the
- // codec context for this but until then we mark all ffmpeg streams
- // as suspicious.
- pv->brokenTS = 1;
}
else
{
@@ -1538,8 +1482,6 @@ static int decavcodecvWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
{
hb_work_private_t *pv = w->private_data;
hb_buffer_t *in = *buf_in;
- int64_t pts = AV_NOPTS_VALUE;
- int64_t dts = pts;
*buf_in = NULL;
*buf_out = NULL;
@@ -1557,7 +1499,7 @@ static int decavcodecvWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
{
if (pv->context != NULL && pv->context->codec != NULL)
{
- decodeVideo(w, in->data, 0, pts, dts, 0);
+ decodeVideo(w, in);
}
hb_buffer_list_append(&pv->list, in);
*buf_out = hb_buffer_list_clear(&pv->list);
@@ -1605,9 +1547,6 @@ static int decavcodecvWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
#ifdef USE_QSV
if (pv->qsv.decode)
{
-#ifdef USE_QSV_PTS_WORKAROUND
- pv->qsv.pts_list = hb_list_init();
-#endif
// set the QSV configuration before opening the decoder
pv->context->hwaccel_context = &pv->qsv.config;
}
@@ -1632,22 +1571,12 @@ static int decavcodecvWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
pv->video_codec_opened = 1;
}
- if( in->s.start >= 0 )
- {
- pts = in->s.start;
- dts = in->s.renderOffset;
- }
- if ( in->s.new_chap )
- {
- pv->new_chap = in->s.new_chap;
- pv->chap_time = pts >= 0? pts : pv->pts_next;
- }
if (in->palette != NULL)
{
pv->palette = in->palette;
in->palette = NULL;
}
- decodeVideo( w, in->data, in->size, pts, dts, in->s.frametype );
+ decodeVideo(w, in);
hb_buffer_close( &in );
*buf_out = hb_buffer_list_clear(&pv->list);
return HB_WORK_OK;
@@ -1871,7 +1800,6 @@ static void decavcodecvFlush( hb_work_object_t *w )
if (pv->context != NULL && pv->context->codec != NULL)
{
- flushDelayQueue( pv );
hb_buffer_list_close(&pv->list);
if ( pv->title->opaque_priv == NULL )
{
@@ -1890,7 +1818,6 @@ static void decavcodecvFlush( hb_work_object_t *w )
avcodec_flush_buffers( pv->context );
}
}
- pv->wait_for_keyframe = 60;
}
hb_work_object_t hb_decavcodecv =
@@ -1904,30 +1831,34 @@ hb_work_object_t hb_decavcodecv =
.info = decavcodecvInfo,
.bsinfo = decavcodecvBSInfo
};
-static void decodeAudio(hb_audio_t *audio, hb_work_private_t *pv, uint8_t *data,
- int size, int64_t pts)
+
+static void decodeAudio(hb_work_private_t *pv, packet_info_t * packet_info)
{
- AVCodecContext *context = pv->context;
- int loop_limit = 256;
- int pos = 0;
+ AVCodecContext * context = pv->context;
+ int loop_limit = 256;
+ int pos = 0;
+ int64_t pts = packet_info->pts;
- // If we are given a pts, use it; but don't lose partial ticks.
- if (pts != AV_NOPTS_VALUE && (int64_t)pv->pts_next != pts)
- pv->pts_next = pts;
- while (pos < size)
+ while (pos < packet_info->size)
{
int got_frame;
AVPacket avp;
av_init_packet(&avp);
- avp.data = data + pos;
- avp.size = size - pos;
- avp.pts = pv->pts_next;
+ avp.data = packet_info->data + pos;
+ avp.size = packet_info->size - pos;
+ avp.pts = pts;
avp.dts = AV_NOPTS_VALUE;
int len = avcodec_decode_audio4(context, pv->frame, &got_frame, &avp);
if (len < 0)
{
+ if (pts != AV_NOPTS_VALUE)
+ {
+ // Update next_pts since subsequent packets may have no
+ // pts and depend on next_pts being up to date
+ pv->next_pts = pts + pv->duration;
+ }
++pv->decode_errors;
}
if ((len < 0) || (!got_frame && !(loop_limit--)))
@@ -1943,8 +1874,9 @@ static void decodeAudio(hb_audio_t *audio, hb_work_private_t *pv, uint8_t *data,
if (got_frame)
{
- hb_buffer_t *out;
- int samplerate;
+ hb_buffer_t * out;
+ int samplerate;
+
// libavcoded doesn't yet consistently set frame->sample_rate
if (pv->frame->sample_rate != 0)
{
@@ -1954,10 +1886,9 @@ static void decodeAudio(hb_audio_t *audio, hb_work_private_t *pv, uint8_t *data,
{
samplerate = context->sample_rate;
}
- double duration = (90000. * pv->frame->nb_samples /
- (double)samplerate);
+ pv->duration = (90000. * pv->frame->nb_samples / samplerate);
- if (audio->config.out.codec & HB_ACODEC_PASS_FLAG)
+ if (pv->audio->config.out.codec & HB_ACODEC_PASS_FLAG)
{
// Note that even though we are doing passthru, we had to decode
// so that we know the stop time and the pts of the next audio
@@ -1972,10 +1903,12 @@ static void decodeAudio(hb_audio_t *audio, hb_work_private_t *pv, uint8_t *data,
av_frame_get_side_data(pv->frame,
AV_FRAME_DATA_DOWNMIX_INFO)) != NULL)
{
- double surround_mix_level, center_mix_level;
- AVDownmixInfo *downmix_info = (AVDownmixInfo*)side_data->data;
- if (audio->config.out.mixdown == HB_AMIXDOWN_DOLBY ||
- audio->config.out.mixdown == HB_AMIXDOWN_DOLBYPLII)
+ double surround_mix_level, center_mix_level;
+ AVDownmixInfo * downmix_info;
+
+ downmix_info = (AVDownmixInfo*)side_data->data;
+ if (pv->audio->config.out.mixdown == HB_AMIXDOWN_DOLBY ||
+ pv->audio->config.out.mixdown == HB_AMIXDOWN_DOLBYPLII)
{
surround_mix_level = downmix_info->surround_mix_level_ltrt;
center_mix_level = downmix_info->center_mix_level_ltrt;
@@ -2003,16 +1936,30 @@ static void decodeAudio(hb_audio_t *audio, hb_work_private_t *pv, uint8_t *data,
out = hb_audio_resample(pv->resample, pv->frame->extended_data,
pv->frame->nb_samples);
}
- av_frame_unref(pv->frame);
if (out != NULL)
{
- out->s.start = pv->pts_next;
- out->s.duration = duration;
- out->s.stop = duration + pv->pts_next;
- pv->pts_next = duration + pv->pts_next;
+ out->s.scr_sequence = packet_info->scr_sequence;
+ out->s.start = pv->frame->pkt_pts;
+ out->s.duration = pv->duration;
+ if (out->s.start == AV_NOPTS_VALUE)
+ {
+ out->s.start = pv->next_pts;
+ }
+ else
+ {
+ pv->next_pts = out->s.start;
+ }
+ if (pv->next_pts != (int64_t)AV_NOPTS_VALUE)
+ {
+ pv->next_pts += pv->duration;
+ out->s.stop = pv->next_pts;
+ }
hb_buffer_list_append(&pv->list, out);
+
+ pts = AV_NOPTS_VALUE;
}
+ av_frame_unref(pv->frame);
++pv->nframes;
}
}
diff --git a/libhb/deccc608sub.c b/libhb/deccc608sub.c
index 8ac6642da..03c544f20 100644
--- a/libhb/deccc608sub.c
+++ b/libhb/deccc608sub.c
@@ -135,6 +135,7 @@ static int general_608_init (struct s_write *wb)
hb_buffer_list_clear(&wb->list);
wb->last_pts = 0;
+ wb->last_scr_sequence = 0;
return 0;
}
@@ -944,6 +945,7 @@ static int write_cc_buffer_as_ssa(struct eia608_screen *data,
buffer->s.frametype = HB_FRAME_SUBTITLE;
buffer->s.start = ms_start;
buffer->s.stop = AV_NOPTS_VALUE;
+ buffer->s.scr_sequence = wb->data608->current_visible_scr_sequence;
sprintf((char*)buffer->data, "%d,,Default,,0,0,0,,", ++wb->line);
len = strlen((char*)buffer->data);
memcpy(buffer->data + len, wb->enc_buffer, wb->enc_buffer_used);
@@ -958,6 +960,7 @@ static int write_cc_buffer_as_ssa(struct eia608_screen *data,
buffer->s.flags = HB_BUF_FLAG_EOS;
buffer->s.start = ms_start;
buffer->s.stop = ms_start;
+ buffer->s.scr_sequence = wb->data608->current_visible_scr_sequence;
hb_buffer_list_append(&wb->list, buffer);
wb->clear_sub_needed = 0;
}
@@ -1232,10 +1235,12 @@ static void handle_command(unsigned char c1, const unsigned char c2,
case COM_RESUMECAPTIONLOADING:
wb->data608->mode=MODE_POPUP;
wb->data608->current_visible_start_ms = wb->last_pts;
+ wb->data608->current_visible_scr_sequence = wb->last_scr_sequence;
break;
case COM_RESUMETEXTDISPLAY:
wb->data608->mode=MODE_TEXT;
wb->data608->current_visible_start_ms = wb->last_pts;
+ wb->data608->current_visible_scr_sequence = wb->last_scr_sequence;
break;
case COM_ROLLUP2:
if (wb->data608->rollup_base_row + 1 < 2)
@@ -1260,6 +1265,7 @@ static void handle_command(unsigned char c1, const unsigned char c2,
wb->rollup_cr = 1;
}
wb->data608->current_visible_start_ms = wb->last_pts;
+ wb->data608->current_visible_scr_sequence = wb->last_scr_sequence;
wb->data608->mode=MODE_ROLLUP_2;
erase_memory (wb, 0);
wb->data608->cursor_column = 0;
@@ -1287,6 +1293,7 @@ static void handle_command(unsigned char c1, const unsigned char c2,
wb->rollup_cr = 1;
}
wb->data608->current_visible_start_ms = wb->last_pts;
+ wb->data608->current_visible_scr_sequence = wb->last_scr_sequence;
wb->data608->mode=MODE_ROLLUP_3;
erase_memory (wb, 0);
wb->data608->cursor_column = 0;
@@ -1314,6 +1321,7 @@ static void handle_command(unsigned char c1, const unsigned char c2,
wb->rollup_cr = 1;
}
wb->data608->current_visible_start_ms = wb->last_pts;
+ wb->data608->current_visible_scr_sequence = wb->last_scr_sequence;
wb->data608->mode = MODE_ROLLUP_4;
wb->data608->cursor_column = 0;
wb->data608->cursor_row = wb->data608->rollup_base_row;
@@ -1328,6 +1336,7 @@ static void handle_command(unsigned char c1, const unsigned char c2,
{
wb->rollup_cr = 0;
wb->data608->current_visible_start_ms = wb->last_pts;
+ wb->data608->current_visible_scr_sequence = wb->last_scr_sequence;
break;
}
if (write_cc_buffer(wb))
@@ -1335,6 +1344,7 @@ static void handle_command(unsigned char c1, const unsigned char c2,
roll_up(wb);
wb->data608->cursor_column = 0;
wb->data608->current_visible_start_ms = wb->last_pts;
+ wb->data608->current_visible_scr_sequence = wb->last_scr_sequence;
break;
case COM_ERASENONDISPLAYEDMEMORY:
erase_memory (wb,0);
@@ -1353,6 +1363,7 @@ static void handle_command(unsigned char c1, const unsigned char c2,
// the last pts is the time to remove the previously
// displayed CC from the display
wb->data608->current_visible_start_ms = wb->last_pts;
+ wb->data608->current_visible_scr_sequence = wb->last_scr_sequence;
// Write "clear" subtitle if necessary
struct eia608_screen *data;
@@ -1367,6 +1378,7 @@ static void handle_command(unsigned char c1, const unsigned char c2,
{
swap_visible_buffer(wb);
wb->data608->current_visible_start_ms = wb->last_pts;
+ wb->data608->current_visible_scr_sequence = wb->last_scr_sequence;
}
if (write_cc_buffer(wb))
wb->data608->screenfuls_counter++;
@@ -1732,6 +1744,7 @@ static void process608(const unsigned char *data, int length,
// write a buffer now
write_cc_buffer(wb);
wb->data608->current_visible_start_ms = wb->last_pts;
+ wb->data608->current_visible_scr_sequence = wb->last_scr_sequence;
}
}
}
@@ -1811,6 +1824,7 @@ static int decccWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
}
pv->cc608->last_pts = in->s.start;
+ pv->cc608->last_scr_sequence = in->s.scr_sequence;
process608(in->data, in->size, pv->cc608);
/*
diff --git a/libhb/deccc608sub.h b/libhb/deccc608sub.h
index 6dcdf4690..202890dc0 100644
--- a/libhb/deccc608sub.h
+++ b/libhb/deccc608sub.h
@@ -70,6 +70,7 @@ struct eia608
int ssa_counter; // Number of subs currently written
int screenfuls_counter; // Number of meaningful screenfuls written
int64_t current_visible_start_ms; // At what time did the current visible buffer became so?
+ int64_t current_visible_scr_sequence; // At what SCR did the current visible buffer became so?
enum cc_modes mode;
unsigned char last_c1, last_c2;
int channel; // Currently selected channel
@@ -89,6 +90,7 @@ struct s_write {
hb_buffer_t *hb_buffer;
hb_buffer_t *hb_last_buffer;
int64_t last_pts;
+ int last_scr_sequence;
unsigned char *enc_buffer; // Generic general purpose buffer
unsigned enc_buffer_used;
unsigned enc_buffer_capacity;
diff --git a/libhb/declpcm.c b/libhb/declpcm.c
index a75c506d9..e7d009375 100644
--- a/libhb/declpcm.c
+++ b/libhb/declpcm.c
@@ -20,6 +20,7 @@ struct hb_work_private_s
uint32_t pos; /* buffer offset for next input data */
int64_t next_pts; /* pts for next output frame */
+ int scr_sequence;
/* the following is frame info for the frame we're currently accumulating */
uint64_t duration; /* frame duratin (in 90KHz ticks) */
@@ -151,7 +152,11 @@ static void lpcmInfo( hb_work_object_t *w, hb_buffer_t *in )
pv->nsamples = ( pv->duration * pv->samplerate ) / 90000;
pv->size = pv->nchunks * chunk_size;
- pv->next_pts = in->s.start;
+ if (in->s.start != AV_NOPTS_VALUE)
+ {
+ pv->next_pts = in->s.start;
+ }
+ pv->scr_sequence = in->s.scr_sequence;
}
static int declpcmInit( hb_work_object_t * w, hb_job_t * job )
@@ -160,6 +165,7 @@ static int declpcmInit( hb_work_object_t * w, hb_job_t * job )
w->private_data = pv;
pv->job = job;
+ pv->next_pts = (int64_t)AV_NOPTS_VALUE;
pv->resample =
hb_audio_resample_init(AV_SAMPLE_FMT_FLT,
w->audio->config.out.mixdown,
@@ -336,10 +342,14 @@ static hb_buffer_t *Decode( hb_work_object_t *w )
if (out != NULL)
{
- out->s.start = pv->next_pts;
- out->s.duration = pv->duration;
- pv->next_pts += pv->duration;
- out->s.stop = pv->next_pts;
+ out->s.start = pv->next_pts;
+ out->s.duration = pv->duration;
+ if (pv->next_pts != (int64_t)AV_NOPTS_VALUE)
+ {
+ pv->next_pts += pv->duration;
+ out->s.stop = pv->next_pts;
+ }
+ out->s.scr_sequence = pv->scr_sequence;
}
return out;
}
diff --git a/libhb/decpgssub.c b/libhb/decpgssub.c
index 2ddd76f45..9efb83009 100644
--- a/libhb/decpgssub.c
+++ b/libhb/decpgssub.c
@@ -410,6 +410,7 @@ static int decsubWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
out->s.start = pts;
out->s.stop = AV_NOPTS_VALUE;
out->s.renderOffset = AV_NOPTS_VALUE;
+ out->s.scr_sequence = in->s.scr_sequence;
out->f.x = x0;
out->f.y = y0;
out->f.window_width = pv->context->width;
@@ -474,6 +475,7 @@ static int decsubWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
out->s.start = pts;
out->s.stop = pts;
out->s.renderOffset = AV_NOPTS_VALUE;
+ out->s.scr_sequence = in->s.scr_sequence;
out->f.x = 0;
out->f.y = 0;
out->f.width = 0;
diff --git a/libhb/decssasub.c b/libhb/decssasub.c
index 7027926e8..98f62d4fe 100644
--- a/libhb/decssasub.c
+++ b/libhb/decssasub.c
@@ -226,7 +226,9 @@ void hb_ssa_style_init(hb_subtitle_style_t *style)
style->bg_alpha = 0xFF;
}
-static hb_buffer_t *ssa_decode_line_to_mkv_ssa( hb_work_object_t * w, uint8_t *in_data, int in_size );
+static hb_buffer_t *
+ssa_decode_line_to_mkv_ssa( hb_work_object_t * w, int scr_sequence,
+ uint8_t *in_data, int in_size );
/*
* Decodes a single SSA packet to one or more TEXTSUB or PICTURESUB subtitle packets.
@@ -256,37 +258,11 @@ static hb_buffer_t *ssa_decode_packet( hb_work_object_t * w, hb_buffer_t *in )
continue;
// Decode an individual SSA line
- buf = ssa_decode_line_to_mkv_ssa(w, (uint8_t *)curLine,
- strlen(curLine));
+ buf = ssa_decode_line_to_mkv_ssa(w, in->s.scr_sequence,
+ (uint8_t *)curLine, strlen(curLine));
hb_buffer_list_append(&list, buf);
}
- // For point-to-point encoding, when the start time of the stream
- // may be offset, the timestamps of the subtitles must be offset as well.
- //
- // HACK: Here we are making the assumption that, under normal circumstances,
- // the output display time of the first output packet is equal to the
- // display time of the input packet.
- //
- // During point-to-point encoding, the display time of the input
- // packet will be offset to compensate.
- //
- // Therefore we offset all of the output packets by a slip amount
- // such that first output packet's display time aligns with the
- // input packet's display time. This should give the correct time
- // when point-to-point encoding is in effect.
- buf = hb_buffer_list_head(&list);
- if (buf && buf->s.start > in->s.start)
- {
- int64_t slip = buf->s.start - in->s.start;
- while (buf != NULL)
- {
- buf->s.start -= slip;
- buf->s.stop -= slip;
- buf = buf->next;
- }
- }
-
return hb_buffer_list_clear(&list);
}
@@ -346,7 +322,9 @@ static uint8_t *find_field( uint8_t *pos, uint8_t *end, int fieldNum )
* ReadOrder,Marked, Style,Name,MarginL,MarginR,MarginV,Effect,Text '\0'
* 1 2 3 4 5 6 7 8 9
*/
-static hb_buffer_t *ssa_decode_line_to_mkv_ssa( hb_work_object_t * w, uint8_t *in_data, int in_size )
+static hb_buffer_t *
+ssa_decode_line_to_mkv_ssa( hb_work_object_t * w, int scr_sequence,
+ uint8_t *in_data, int in_size )
{
hb_work_private_t * pv = w->private_data;
hb_buffer_t * out;
@@ -393,10 +371,11 @@ static hb_buffer_t *ssa_decode_line_to_mkv_ssa( hb_work_object_t * w, uint8_t *i
strcat( mkvIn, "," );
strcat( mkvIn, (char *)styleToTextFields );
- out->size = strlen(mkvIn) + 1;
- out->s.frametype = HB_FRAME_SUBTITLE;
- out->s.start = in_start;
- out->s.stop = in_stop;
+ out->size = strlen(mkvIn) + 1;
+ out->s.frametype = HB_FRAME_SUBTITLE;
+ out->s.start = in_start;
+ out->s.stop = in_stop;
+ out->s.scr_sequence = scr_sequence;
if( out->size == 0 )
{
diff --git a/libhb/dectx3gsub.c b/libhb/dectx3gsub.c
index 3baec1d41..591fd48f3 100644
--- a/libhb/dectx3gsub.c
+++ b/libhb/dectx3gsub.c
@@ -216,9 +216,10 @@ static hb_buffer_t *tx3g_decode_to_ssa(hb_work_private_t *pv, hb_buffer_t *in)
out->size = dst - out->data;
// Copy metadata from the input packet to the output packet
- out->s.frametype = HB_FRAME_SUBTITLE;
- out->s.start = in->s.start;
- out->s.stop = in->s.stop;
+ out->s.frametype = HB_FRAME_SUBTITLE;
+ out->s.start = in->s.start;
+ out->s.stop = in->s.stop;
+ out->s.scr_sequence = in->s.scr_sequence;
fail:
free(styleRecords);
diff --git a/libhb/decvobsub.c b/libhb/decvobsub.c
index 85644c47d..4fb98685b 100644
--- a/libhb/decvobsub.c
+++ b/libhb/decvobsub.c
@@ -38,8 +38,10 @@ struct hb_work_private_s
int size_got;
int size_rle;
int64_t pts;
+ int current_scr_sequence;
int64_t pts_start;
int64_t pts_stop;
+ int scr_sequence;
int pts_forced;
int x;
int y;
@@ -127,7 +129,8 @@ int decsubWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
pv->size_got = in->size;
if( in->s.start >= 0 )
{
- pv->pts = in->s.start;
+ pv->pts = in->s.start;
+ pv->current_scr_sequence = in->s.scr_sequence;
}
}
}
@@ -141,7 +144,8 @@ int decsubWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
pv->size_got += in->size;
if( in->s.start >= 0 )
{
- pv->pts = in->s.start;
+ pv->pts = in->s.start;
+ pv->current_scr_sequence = in->s.scr_sequence;
}
}
else
@@ -180,7 +184,8 @@ int decsubWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
// of the current sub as the start of the next.
// This can happen if reader invalidates timestamps while
// waiting for an audio to update the SCR.
- pv->pts = pv->pts_stop;
+ pv->pts = pv->pts_stop;
+ pv->current_scr_sequence = pv->scr_sequence;
}
}
@@ -255,21 +260,26 @@ static void ParseControls( hb_work_object_t * w )
switch( command )
{
case 0x00: // 0x00 - FSTA_DSP - Forced Start Display, no arguments
- pv->pts_start = pv->pts + date * 1024;
- pv->pts_forced = 1;
+ pv->pts_start = pv->pts + date * 1024;
+ pv->scr_sequence = pv->current_scr_sequence;
+ pv->pts_forced = 1;
w->subtitle->hits++;
w->subtitle->forced_hits++;
break;
case 0x01: // 0x01 - STA_DSP - Start Display, no arguments
- pv->pts_start = pv->pts + date * 1024;
- pv->pts_forced = 0;
+ pv->pts_start = pv->pts + date * 1024;
+ pv->scr_sequence = pv->current_scr_sequence;
+ pv->pts_forced = 0;
w->subtitle->hits++;
break;
case 0x02: // 0x02 - STP_DSP - Stop Display, no arguments
- if(pv->pts_stop == AV_NOPTS_VALUE)
- pv->pts_stop = pv->pts + date * 1024;
+ if (pv->pts_stop == AV_NOPTS_VALUE)
+ {
+ pv->pts_stop = pv->pts + date * 1024;
+ pv->scr_sequence = pv->current_scr_sequence;
+ }
break;
case 0x03: // 0x03 - SET_COLOR - Set Colour indices
@@ -346,7 +356,8 @@ static void ParseControls( hb_work_object_t * w )
// fading-out
if (currAlpha < lastAlpha && pv->pts_stop == AV_NOPTS_VALUE)
{
- pv->pts_stop = pv->pts + date * 1024;
+ pv->pts_stop = pv->pts + date * 1024;
+ pv->scr_sequence = pv->current_scr_sequence;
}
i += 2;
@@ -382,7 +393,8 @@ static void ParseControls( hb_work_object_t * w )
if( pv->pts_start == AV_NOPTS_VALUE )
{
// Set pts to end of last sub if the start time is unknown.
- pv->pts_start = pv->pts;
+ pv->pts_start = pv->pts;
+ pv->scr_sequence = pv->current_scr_sequence;
}
}
@@ -535,9 +547,10 @@ static hb_buffer_t * CropSubtitle( hb_work_object_t * w, uint8_t * raw )
realheight = crop[1] - crop[0] + 1;
buf = hb_frame_buffer_init( AV_PIX_FMT_YUVA420P, realwidth, realheight );
- buf->s.frametype = HB_FRAME_SUBTITLE;
- buf->s.start = pv->pts_start;
- buf->s.stop = pv->pts_stop;
+ buf->s.frametype = HB_FRAME_SUBTITLE;
+ buf->s.start = pv->pts_start;
+ buf->s.stop = pv->pts_stop;
+ buf->s.scr_sequence = pv->scr_sequence;
buf->f.x = pv->x + crop[2];
buf->f.y = pv->y + crop[0];
@@ -606,8 +619,9 @@ static hb_buffer_t * Decode( hb_work_object_t * w )
if (w->subtitle->config.dest == PASSTHRUSUB)
{
- pv->buf->s.start = pv->pts_start;
- pv->buf->s.stop = pv->pts_stop;
+ pv->buf->s.start = pv->pts_start;
+ pv->buf->s.stop = pv->pts_stop;
+ pv->buf->s.scr_sequence = pv->scr_sequence;
buf = pv->buf;
pv->buf = NULL;
return buf;
diff --git a/libhb/fifo.c b/libhb/fifo.c
index 0795619ce..42894c7a0 100644
--- a/libhb/fifo.c
+++ b/libhb/fifo.c
@@ -393,12 +393,13 @@ hb_buffer_t * hb_buffer_init_internal( int size , int needsMapped )
int loc = b->cl.buffer_location;
memset( b, 0, sizeof(hb_buffer_t) );
- b->alloc = buffer_pool->buffer_size;
- b->size = size;
- b->data = data;
- b->s.start = AV_NOPTS_VALUE;
- b->s.stop = AV_NOPTS_VALUE;
+ b->alloc = buffer_pool->buffer_size;
+ b->size = size;
+ b->data = data;
+ b->s.start = AV_NOPTS_VALUE;
+ b->s.stop = AV_NOPTS_VALUE;
b->s.renderOffset = AV_NOPTS_VALUE;
+ b->s.scr_sequence = -1;
/* OpenCL */
b->cl.buffer = buffer;
@@ -470,9 +471,10 @@ hb_buffer_t * hb_buffer_init_internal( int size , int needsMapped )
buffers.allocated += b->alloc;
hb_unlock(buffers.lock);
}
- b->s.start = AV_NOPTS_VALUE;
- b->s.stop = AV_NOPTS_VALUE;
+ b->s.start = AV_NOPTS_VALUE;
+ b->s.stop = AV_NOPTS_VALUE;
b->s.renderOffset = AV_NOPTS_VALUE;
+ b->s.scr_sequence = -1;
#if defined(HB_BUFFER_DEBUG)
hb_lock(buffers.lock);
hb_list_add(buffers.alloc_list, b);
diff --git a/libhb/internal.h b/libhb/internal.h
index f4ad2b3c9..d1f92ca8d 100644
--- a/libhb/internal.h
+++ b/libhb/internal.h
@@ -66,6 +66,8 @@ struct hb_buffer_settings_s
int64_t stop; // stop time of frame
int64_t renderOffset; // DTS used by b-frame offsets in muxmp4
int64_t pcr;
+ int scr_sequence; // The SCR sequence that this buffer's
+ // timestamps are referenced to
int split;
uint8_t discontinuity;
int new_chap; // Video packets: if non-zero, is the index of the chapter whose boundary was crossed
diff --git a/libhb/reader.c b/libhb/reader.c
index 84de0856a..45da42ca9 100644
--- a/libhb/reader.c
+++ b/libhb/reader.c
@@ -27,17 +27,6 @@ hb_work_object_t hb_reader =
typedef struct
{
- int startup;
- double average; // average time between packets
- double filtered_average; // average time between packets
- int64_t last; // last timestamp seen on this stream
- int id; // stream id
- int is_audio; // != 0 if this is an audio stream
- int valid; // Stream timing is not valid until next scr.
-} stream_timing_t;
-
-typedef struct
-{
int id;
hb_buffer_list_t list;
} buffer_splice_list_t;
@@ -53,20 +42,18 @@ struct hb_work_private_s
hb_dvd_t * dvd;
hb_stream_t * stream;
- stream_timing_t *stream_timing;
- int64_t scr_offset;
- int sub_scr_set;
hb_psdemux_t demux;
int scr_changes;
- uint8_t st_slots; // size (in slots) of stream_timing array
- uint8_t saw_video; // != 0 if we've seen video
- uint8_t saw_audio; // != 0 if we've seen audio
+ int64_t scr_offset;
+ int64_t last_pts;
int start_found; // found pts_to_start point
int64_t pts_to_start;
int chapter_end;
+
uint64_t st_first;
- uint64_t duration;
+ int64_t duration;
+
hb_fifo_t ** fifos;
buffer_splice_list_t * splice_list;
@@ -77,14 +64,29 @@ struct hb_work_private_s
* Local prototypes
**********************************************************************/
static hb_fifo_t ** GetFifoForId( hb_work_private_t * r, int id );
-static void UpdateState( hb_work_private_t * r, int64_t start);
static hb_buffer_list_t * get_splice_list(hb_work_private_t * r, int id);
+static void UpdateState( hb_work_private_t * r );
/***********************************************************************
* reader_init
***********************************************************************
*
**********************************************************************/
+static int64_t chapter_end_pts(hb_title_t * title, int chapter_end )
+{
+ hb_chapter_t * chapter;
+ int64_t duration;
+ int ii;
+
+ duration = 0;
+ for (ii = 0; ii < chapter_end; ii++)
+ {
+ chapter = hb_list_item(title->list_chapter, ii);
+ duration += chapter->duration;
+ }
+ return duration;
+}
+
static int hb_reader_open( hb_work_private_t * r )
{
if ( r->title->type == HB_BD_TYPE )
@@ -119,6 +121,8 @@ static int hb_reader_open( hb_work_private_t * r )
else
{
hb_bd_seek_chapter(r->bd, r->job->chapter_start);
+ r->duration -= chapter_end_pts(r->job->title,
+ r->job->chapter_start - 1);
}
}
else if (r->title->type == HB_DVD_TYPE)
@@ -130,6 +134,8 @@ static int hb_reader_open( hb_work_private_t * r )
hb_dvd_close(&r->dvd);
return 1;
}
+ r->duration -= chapter_end_pts(r->job->title,
+ r->job->chapter_start - 1);
if (r->job->angle)
{
hb_dvd_set_angle(r->dvd, r->job->angle);
@@ -141,6 +147,10 @@ static int hb_reader_open( hb_work_private_t * r )
(r->job->seek_points ? (r->job->seek_points + 1.0)
: 11.0));
}
+ // libdvdnav doesn't have a seek to timestamp function.
+ // So we will have to decode frames until we find the correct time
+ // in sync.c
+ r->start_found = 1;
}
else if (r->title->type == HB_STREAM_TYPE ||
r->title->type == HB_FF_STREAM_TYPE)
@@ -161,11 +171,17 @@ static int hb_reader_open( hb_work_private_t * r )
// that we want. So we will retrieve the start time of the
// first packet we get, subtract that from pts_to_start, and
// inspect the reset of the frames in sync.
- r->start_found = 2;
r->duration -= r->job->pts_to_start;
}
- // hb_stream_seek_ts does nothing for TS streams and will return
- // an error.
+ else
+ {
+ // hb_stream_seek_ts does nothing for TS streams and will
+ // return an error.
+ //
+ // So we will decode frames until we find the correct time
+ // in sync.c
+ r->start_found = 1;
+ }
}
else
{
@@ -187,6 +203,8 @@ static int hb_reader_open( hb_work_private_t * r )
* Seek to the start chapter.
*/
hb_stream_seek_chapter(r->stream, start);
+ r->duration -= chapter_end_pts(r->job->title,
+ r->job->chapter_start - 1);
}
}
else
@@ -210,22 +228,14 @@ static int reader_init( hb_work_object_t * w, hb_job_t * job )
r->title = job->title;
r->die = job->die;
- r->st_slots = 4;
- r->stream_timing = calloc( sizeof(stream_timing_t), r->st_slots );
- r->stream_timing[0].id = r->title->video_id;
- r->stream_timing[0].average = 90000. * (double)job->vrate.den /
- job->vrate.num;
- r->stream_timing[0].filtered_average = r->stream_timing[0].average;
- r->stream_timing[0].last = -r->stream_timing[0].average;
- r->stream_timing[0].valid = 1;
- r->stream_timing[0].startup = 10;
- r->stream_timing[1].id = -1;
-
r->demux.last_scr = AV_NOPTS_VALUE;
+ r->last_pts = AV_NOPTS_VALUE;
r->chapter_end = job->chapter_end;
- if ( !job->pts_to_start )
+ if (!job->pts_to_start)
+ {
r->start_found = 1;
+ }
else
{
// The frame at the actual start time may not be an i-frame
@@ -235,7 +245,7 @@ static int reader_init( hb_work_object_t * w, hb_job_t * job )
r->pts_to_start = MAX(0, job->pts_to_start - 1000000);
}
- if (job->pts_to_stop)
+ if (job->pts_to_stop > 0)
{
r->duration = job->pts_to_start + job->pts_to_stop;
}
@@ -243,18 +253,18 @@ static int reader_init( hb_work_object_t * w, hb_job_t * job )
{
int frames = job->frame_to_start + job->frame_to_stop;
r->duration = (int64_t)frames * job->title->vrate.den * 90000 /
- job->title->vrate.num;
+ job->title->vrate.num;
}
else
{
- hb_chapter_t *chapter;
- int ii;
-
- r->duration = 0;
- for (ii = job->chapter_start; ii < job->chapter_end; ii++)
+ int count = hb_list_count(job->title->list_chapter);
+ if (count == 0 || count <= job->chapter_end)
{
- chapter = hb_list_item( job->title->list_chapter, ii - 1);
- r->duration += chapter->duration;
+ r->duration = job->title->duration;
+ }
+ else
+ {
+ r->duration = chapter_end_pts(job->title, job->chapter_end);
}
}
@@ -289,7 +299,6 @@ static int reader_init( hb_work_object_t * w, hb_job_t * job )
// with the reader. Specifically avcodec needs this.
if ( hb_reader_open( r ) )
{
- free( r->stream_timing );
free( r );
return 1;
}
@@ -320,11 +329,6 @@ static void reader_close( hb_work_object_t * w )
hb_stream_close(&r->stream);
}
- if ( r->stream_timing )
- {
- free( r->stream_timing );
- }
-
int ii;
for (ii = 0; ii < r->splice_list_size; ii++)
{
@@ -391,150 +395,6 @@ static void push_buf( hb_work_private_t *r, hb_fifo_t *fifo, hb_buffer_t *buf )
}
}
-static int is_audio( hb_work_private_t *r, int id )
-{
- int i;
- hb_audio_t *audio;
-
- for( i = 0; ( audio = hb_list_item( r->title->list_audio, i ) ); ++i )
- {
- if ( audio->id == id )
- {
- return 1;
- }
- }
- return 0;
-}
-
-static int is_subtitle( hb_work_private_t *r, int id )
-{
- int i;
- hb_subtitle_t *sub;
-
- for( i = 0; ( sub = hb_list_item( r->title->list_subtitle, i ) ); ++i )
- {
- if ( sub->id == id )
- {
- return 1;
- }
- }
- return 0;
-}
-
-// The MPEG STD (Standard Target Decoder) essentially requires that we keep
-// per-stream timing so that when there's a timing discontinuity we can
-// seemlessly join packets on either side of the discontinuity. This join
-// requires that we know the timestamp of the previous packet and the
-// average inter-packet time (since we position the new packet at the end
-// of the previous packet). The next four routines keep track of this
-// per-stream timing.
-
-// find or create the per-stream timing state for 'buf'
-
-static stream_timing_t *id_to_st( hb_work_private_t *r, const hb_buffer_t *buf, int valid )
-{
- stream_timing_t *st = r->stream_timing;
- while ( st->id != buf->s.id && st->id != -1)
- {
- ++st;
- }
- // if we haven't seen this stream add it.
- if ( st->id == -1 )
- {
- // we keep the steam timing info in an array with some power-of-two
- // number of slots. If we don't have two slots left (one for our new
- // entry plus one for the "-1" eol) we need to expand the array.
- int slot = st - r->stream_timing;
- if ( slot + 1 >= r->st_slots )
- {
- r->st_slots *= 2;
- r->stream_timing = realloc( r->stream_timing, r->st_slots *
- sizeof(*r->stream_timing) );
- st = r->stream_timing + slot;
- }
- st->id = buf->s.id;
- st->average = 30.*90.;
- st->filtered_average = st->average;
- st->startup = 10;
- st->last = -st->average;
- if ( ( st->is_audio = is_audio( r, buf->s.id ) ) != 0 )
- {
- r->saw_audio = 1;
- }
- st[1].id = -1;
- st->valid = valid;
- }
- return st;
-}
-
-// update the average inter-packet time of the stream associated with 'buf'
-// using a recursive low-pass filter with a 16 packet time constant.
-
-static void update_ipt( hb_work_private_t *r, const hb_buffer_t *buf )
-{
- stream_timing_t *st = id_to_st( r, buf, 1 );
-
- if (buf->s.renderOffset == AV_NOPTS_VALUE)
- {
- st->last += st->filtered_average;
- return;
- }
-
- double dt = buf->s.renderOffset - st->last;
-
- // Protect against spurious bad timestamps
- // timestamps should only move forward and by reasonable increments
- if ( dt > 0 && dt < 5 * 90000LL )
- {
- if( st->startup )
- {
- st->average += ( dt - st->average ) * (1./4.);
- st->startup--;
- }
- else
- {
- st->average += ( dt - st->average ) * (1./32.);
- }
- // Ignore outliers
- if (dt < 1.5 * st->average)
- {
- st->filtered_average += ( dt - st->filtered_average ) * (1./32.);
- }
- }
- st->last = buf->s.renderOffset;
- st->valid = 1;
-}
-
-// use the per-stream state associated with 'buf' to compute a new scr_offset
-// such that 'buf' will follow the previous packet of this stream separated
-// by the average packet time of the stream.
-
-static void new_scr_offset( hb_work_private_t *r, hb_buffer_t *buf )
-{
- stream_timing_t *st = id_to_st( r, buf, 1 );
- int64_t last;
- if ( !st->valid )
- {
- // !valid means we've not received any previous data
- // for this stream. There is no 'last' packet time.
- // So approximate it with video's last time.
- last = r->stream_timing[0].last;
- st->valid = 1;
- }
- else
- {
- last = st->last;
- }
- int64_t nxt = last + st->filtered_average;
- r->scr_offset = buf->s.renderOffset - nxt;
- // This log is handy when you need to debug timing problems...
- //hb_log("id %x last %"PRId64" avg %g nxt %"PRId64" renderOffset %"PRId64
- // " scr_offset %"PRId64"",
- // buf->s.id, last, st->filtered_average, nxt,
- // buf->s.renderOffset, r->scr_offset);
- r->scr_changes = r->demux.scr_changes;
-}
-
static void reader_send_eof( hb_work_private_t * r )
{
int ii;
@@ -619,10 +479,57 @@ static int reader_work( hb_work_object_t * w, hb_buffer_t ** buf_in,
while ((buf = hb_buffer_list_rem_head(&list)) != NULL)
{
- fifos = GetFifoForId( r, buf->s.id );
+ if (buf->s.start != AV_NOPTS_VALUE &&
+ r->scr_changes != r->demux.scr_changes)
+ {
+ // First valid timestamp after an SCR change. Update
+ // the per-stream scr sequence number
+ r->scr_changes = r->demux.scr_changes;
+
+ // libav tries to be too smart with timestamps and
+ // enforces unnecessary conditions. One such condition
+ // is that subtitle timestamps must be monotonically
+ // increasing. To encure this is the case, we calculate
+ // an offset upon each SCR change that will guarantee this.
+ // This is just a very rough SCR offset. A fine grained
+ // offset that maintains proper sync is calculated in sync.c
+ if (r->last_pts != AV_NOPTS_VALUE)
+ {
+ r->scr_offset = r->last_pts + 90000 - buf->s.start;
+ }
+ else
+ {
+ r->scr_offset = -buf->s.start;
+ }
+ }
+ // Set the scr sequence that this buffer's timestamps are
+ // referenced to.
+ buf->s.scr_sequence = r->scr_changes;
+ if (buf->s.start != AV_NOPTS_VALUE)
+ {
+ buf->s.start += r->scr_offset;
+ }
+ if (buf->s.renderOffset != AV_NOPTS_VALUE)
+ {
+ buf->s.renderOffset += r->scr_offset;
+ }
+ if (buf->s.start > r->last_pts)
+ {
+ r->last_pts = buf->s.start;
+ UpdateState(r);
+ }
- if (fifos && r->stream && r->start_found == 2 )
+ fifos = GetFifoForId( r, buf->s.id );
+ if (fifos && r->stream && !r->start_found)
{
+ // libav is allowing SSA subtitles to leak through that are
+ // prior to the seek point. So only make the adjustment to
+ // pts_to_start after we see the next video buffer.
+ if (buf->s.id != r->job->title->video_id)
+ {
+ hb_buffer_close(&buf);
+ continue;
+ }
// We will inspect the timestamps of each frame in sync
// to skip from this seek point to the timestamp we
// want to start at.
@@ -638,176 +545,9 @@ static int reader_work( hb_work_object_t * w, hb_buffer_t ** buf_in,
r->start_found = 1;
}
- if ( fifos && ! r->saw_video && !r->job->indepth_scan )
- {
- // The first data packet with a PTS from an audio or video stream
- // that we're decoding defines 'time zero'. Discard packets until
- // we get one.
- if (buf->s.start != AV_NOPTS_VALUE &&
- buf->s.renderOffset != AV_NOPTS_VALUE &&
- (buf->s.id == r->title->video_id ||
- is_audio( r, buf->s.id)))
- {
- // force a new scr offset computation
- r->scr_changes = r->demux.scr_changes - 1;
- // create a stream state if we don't have one so the
- // offset will get computed correctly.
- id_to_st( r, buf, 1 );
- r->saw_video = 1;
- hb_log( "reader: first SCR %"PRId64" id 0x%x DTS %"PRId64,
- r->demux.last_scr, buf->s.id, buf->s.renderOffset );
- }
- else
- {
- fifos = NULL;
- }
- }
-
- if ( r->job->indepth_scan || fifos )
- {
- if ( buf->s.renderOffset != AV_NOPTS_VALUE )
- {
- if ( r->scr_changes != r->demux.scr_changes )
- {
- // This is the first audio or video packet after an SCR
- // change. Compute a new scr offset that would make this
- // packet follow the last of this stream with the
- // correct average spacing.
- stream_timing_t *st = id_to_st( r, buf, 0 );
-
- // if this is the video stream and we don't have
- // audio yet or this is an audio stream
- // generate a new scr
- if ( st->is_audio ||
- ( st == r->stream_timing && !r->saw_audio ) )
- {
- new_scr_offset( r, buf );
- r->sub_scr_set = 0;
- }
- else
- {
- // defer the scr change until we get some
- // audio since audio has a timestamp per
- // frame but video & subtitles don't. Clear
- // the timestamps so the decoder will generate
- // them from the frame durations.
- if (is_subtitle(r, buf->s.id) &&
- buf->s.start != AV_NOPTS_VALUE)
- {
- if (!r->sub_scr_set)
- {
- // We can't generate timestamps in the
- // subtitle decoder as we can for
- // audio & video. So we need to make
- // the closest guess that we can
- // for the subtitles start time here.
- int64_t last = r->stream_timing[0].last;
- r->scr_offset = buf->s.start - last;
- r->sub_scr_set = 1;
- }
- }
- else
- {
- buf->s.start = AV_NOPTS_VALUE;
- buf->s.renderOffset = AV_NOPTS_VALUE;
- }
- }
- }
- }
- if ( buf->s.start != AV_NOPTS_VALUE )
- {
- int64_t start = buf->s.start - r->scr_offset;
-
- if (!r->start_found || r->job->indepth_scan)
- {
- UpdateState( r, start );
- }
-
- if (r->job->indepth_scan && r->job->pts_to_stop &&
- start >= r->pts_to_start + r->job->pts_to_stop)
- {
- // sync normally would terminate p-to-p
- // but sync doesn't run during indepth scan
- hb_log("reader: reached pts %"PRId64", exiting early", start);
- reader_send_eof(r);
- hb_buffer_list_close(&list);
- return HB_WORK_DONE;
- }
-
- if (!r->start_found && start >= r->pts_to_start)
- {
- // pts_to_start point found
- // Note that this code path only gets executed for
- // medai where we have not performed an initial seek
- // to get close to the start time. So the 'start' time
- // is the time since the first frame.
-
- if (r->stream)
- {
- // libav multi-threaded decoders can get into
- // a bad state if the initial data is not
- // decodable. So try to improve the chances of
- // a good start by waiting for an initial iframe
- hb_stream_set_need_keyframe(r->stream, 1);
- hb_buffer_close( &buf );
- continue;
- }
- r->start_found = 1;
- // sync.c also pays attention to job->pts_to_start
- // It eats up the 10 second slack that we build in
- // to the start time here in reader (so that video
- // decode is clean at the start time).
- // sync.c expects pts_to_start to be relative to the
- // first timestamp it sees.
- if (r->job->pts_to_start > start)
- {
- r->job->pts_to_start -= start;
- }
- else
- {
- r->job->pts_to_start = 0;
- }
- }
- // This log is handy when you need to debug timing problems
- //hb_log("id %x scr_offset %"PRId64
- // " start %"PRId64" --> %"PRId64"",
- // buf->s.id, r->scr_offset, buf->s.start,
- // buf->s.start - r->scr_offset);
- buf->s.start -= r->scr_offset;
- if ( buf->s.stop != AV_NOPTS_VALUE )
- {
- buf->s.stop -= r->scr_offset;
- }
- }
- if ( buf->s.renderOffset != AV_NOPTS_VALUE )
- {
- // This packet is referenced to the same SCR as the last.
- // Adjust timestamp to remove the System Clock Reference
- // offset then update the average inter-packet time
- // for this stream.
- buf->s.renderOffset -= r->scr_offset;
- update_ipt( r, buf );
- }
-#if 0
- // JAS: This was added to fix a rare "audio time went backward"
- // sync error I found in one sample. But it has a bad side
- // effect on DVDs, causing frequent "adding silence" sync
- // errors. So I am disabling it.
- else
- {
- update_ipt( r, buf );
- }
-#endif
- }
buf = splice_discontinuity(r, buf);
- if( fifos && buf != NULL )
+ if (fifos && buf != NULL)
{
- if ( !r->start_found )
- {
- hb_buffer_close( &buf );
- continue;
- }
-
/* if there are mutiple output fifos, send a copy of the
* buffer down all but the first (we have to not ship the
* original buffer or we'll race with the thread that's
@@ -832,12 +572,18 @@ static int reader_work( hb_work_object_t * w, hb_buffer_t ** buf_in,
return HB_WORK_OK;
}
-static void UpdateState( hb_work_private_t * r, int64_t start)
+static void UpdateState( hb_work_private_t * r )
{
hb_state_t state;
uint64_t now;
double avg;
+ if (!r->job->indepth_scan || !r->start_found)
+ {
+ // Only update state when sync.c is not handling state updates
+ return;
+ }
+
now = hb_get_date();
if( !r->st_first )
{
@@ -846,16 +592,8 @@ static void UpdateState( hb_work_private_t * r, int64_t start)
hb_get_state2(r->job->h, &state);
#define p state.param.working
- if ( !r->job->indepth_scan )
- {
- state.state = HB_STATE_SEARCHING;
- p.progress = (float) start / (float) r->job->pts_to_start;
- }
- else
- {
- state.state = HB_STATE_WORKING;
- p.progress = (float) start / (float) r->duration;
- }
+ state.state = HB_STATE_WORKING;
+ p.progress = (float) r->last_pts / (float) r->duration;
if( p.progress > 1.0 )
{
p.progress = 1.0;
@@ -866,11 +604,12 @@ static void UpdateState( hb_work_private_t * r, int64_t start)
{
int eta;
- avg = 1000.0 * (double)start / (now - r->st_first);
- if ( !r->job->indepth_scan )
- eta = ( r->job->pts_to_start - start ) / avg;
- else
- eta = ( r->duration - start ) / avg;
+ avg = 1000.0 * (double)r->last_pts / (now - r->st_first);
+ eta = (r->duration - r->last_pts) / avg;
+ if (eta < 0)
+ {
+ eta = 0;
+ }
p.hours = eta / 3600;
p.minutes = ( eta % 3600 ) / 60;
p.seconds = eta % 60;
@@ -885,6 +624,7 @@ static void UpdateState( hb_work_private_t * r, int64_t start)
hb_set_state( r->job->h, &state );
}
+
/***********************************************************************
* GetFifoForId
***********************************************************************
@@ -898,9 +638,9 @@ static hb_fifo_t ** GetFifoForId( hb_work_private_t * r, int id )
hb_subtitle_t * subtitle;
int i, n;
- if( id == title->video_id )
+ if (id == title->video_id)
{
- if (job->indepth_scan && !job->frame_to_stop)
+ if (job->indepth_scan && r->start_found)
{
/*
* Ditch the video here during the indepth scan until
@@ -919,7 +659,7 @@ static hb_fifo_t ** GetFifoForId( hb_work_private_t * r, int id )
}
}
- for( i = n = 0; i < hb_list_count( job->list_subtitle ); i++ )
+ for (i = n = 0; i < hb_list_count( job->list_subtitle ); i++)
{
subtitle = hb_list_item( job->list_subtitle, i );
if (id == subtitle->id)
@@ -928,24 +668,24 @@ static hb_fifo_t ** GetFifoForId( hb_work_private_t * r, int id )
r->fifos[n++] = subtitle->fifo_in;
}
}
- if ( n != 0 )
+ if (n != 0)
{
r->fifos[n] = NULL;
return r->fifos;
}
- if( !job->indepth_scan )
+ if (!job->indepth_scan)
{
- for( i = n = 0; i < hb_list_count( job->list_audio ); i++ )
+ for (i = n = 0; i < hb_list_count( job->list_audio ); i++)
{
audio = hb_list_item( job->list_audio, i );
- if( id == audio->id )
+ if (id == audio->id)
{
r->fifos[n++] = audio->priv.fifo_in;
}
}
- if( n != 0 )
+ if (n != 0)
{
r->fifos[n] = NULL;
return r->fifos;
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;