+static void push_buf( const hb_reader_t *r, hb_fifo_t *fifo, hb_buffer_t *buf )
+{
+ while( !*r->die && !r->job->done && hb_fifo_is_full( fifo ) )
+ {
+ /*
+ * Loop until the incoming fifo is ready to receive
+ * this buffer.
+ */
+ hb_snooze( 50 );
+ }
+ hb_fifo_push( fifo, buf );
+}
+
+static int is_audio( hb_reader_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;
+}
+
+// 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 the per-stream timing state for 'buf'
+
+static stream_timing_t *find_st( hb_reader_t *r, const hb_buffer_t *buf )
+{
+ stream_timing_t *st = r->stream_timing;
+ for ( ; st->id != -1; ++st )
+ {
+ if ( st->id == buf->id )
+ return st;
+ }
+ return NULL;
+}
+
+// find or create the per-stream timing state for 'buf'
+
+static stream_timing_t *id_to_st( hb_reader_t *r, const hb_buffer_t *buf )
+{
+ stream_timing_t *st = r->stream_timing;
+ while ( st->id != buf->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->id;
+ st->average = 30.*90.;
+ st->last = buf->renderOffset - st->average;
+ if ( ( st->is_audio = is_audio( r, buf->id ) ) != 0 )
+ {
+ r->saw_audio = 1;
+ }
+ st[1].id = -1;
+ }
+ 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_reader_t *r, const hb_buffer_t *buf )
+{
+ stream_timing_t *st = id_to_st( r, buf );
+ double dt = buf->renderOffset - st->last;
+ st->average += ( dt - st->average ) * (1./32.);
+ st->last = buf->renderOffset;
+}
+
+// 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_reader_t *r, hb_buffer_t *buf )
+{
+ stream_timing_t *st = id_to_st( r, buf );
+ int64_t nxt = st->last + st->average;
+ r->scr_offset = buf->renderOffset - nxt;
+ buf->renderOffset = nxt;
+ r->scr_changes = r->demux.scr_changes;
+ st->last = buf->renderOffset;
+}
+