OSDN Git Service

Various fixes for ffmpeg input files (mp4, avi, mkv, etc.):
authorvan <van@b64f7644-9d1e-0410-96f1-a4d463321fa5>
Thu, 11 Sep 2008 04:05:04 +0000 (04:05 +0000)
committervan <van@b64f7644-9d1e-0410-96f1-a4d463321fa5>
Thu, 11 Sep 2008 04:05:04 +0000 (04:05 +0000)
 - always use source time stamps (HB vfr mpeg4 files now handled correctly)
 - handle Microsoft's braindead "VFW packed b-frames"
 - compute average video frame rate from total duration & number of frames (ignore ffmpeg's frame rate which is closer to a max f.r. for vfr video).
 - workaround an ffmpeg audio decode abort when it used SSE instructions on unaligned buffers.

git-svn-id: svn://localhost/HandBrake/trunk@1687 b64f7644-9d1e-0410-96f1-a4d463321fa5

libhb/decavcodec.c
libhb/reader.c
libhb/stream.c

index 62eca65..f6bffc0 100644 (file)
@@ -81,23 +81,92 @@ hb_work_object_t hb_decavcodec =
     decavcodecBSInfo
 };
 
+#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;
+
 struct hb_work_private_s
 {
     hb_job_t             *job;
     AVCodecContext       *context;
     AVCodecParserContext *parser;
     hb_list_t            *list;
+    double               duration;  // frame duration (for video)
     double               pts_next;  // next pts we expect to generate
     int64_t              pts;       // (video) pts passing from parser to decoder
     int64_t              chap_time; // time of next chap mark (if new_chap != 0)
     int                  new_chap;
-    int                  ignore_pts; // workaround M$ bugs
     uint32_t             nframes;
     uint32_t             ndrops;
     uint32_t             decode_errors;
-    double               duration;  // frame duration (for video)
+    hb_buffer_t*         delayq[HEAP_SIZE];
+    pts_heap_t           pts_heap;
+    void*                buffer;
 };
 
+static int64_t heap_pop( pts_heap_t *heap )
+{
+    int64_t result;
+
+    if ( heap->nheap <= 0 )
+    {
+        return -1;
+    }
+
+    // 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;
+}
 
 
 /***********************************************************************
@@ -135,17 +204,34 @@ static int decavcodecInit( hb_work_object_t * w, hb_job_t * job )
 static void decavcodecClose( hb_work_object_t * w )
 {
     hb_work_private_t * pv = w->private_data;
-    if ( pv->parser )
-       {
-               av_parser_close(pv->parser);
-       }
-    if ( pv->context && pv->context->codec )
-    {
-        avcodec_close( pv->context );
-    }
-    if ( pv->list )
+
+    if ( pv )
     {
-        hb_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 );
+        }
+        if ( pv->parser )
+        {
+            av_parser_close(pv->parser);
+        }
+        if ( pv->context && pv->context->codec )
+        {
+            avcodec_close( pv->context );
+        }
+        if ( pv->list )
+        {
+            hb_list_close( &pv->list );
+        }
+        if ( pv->buffer )
+        {
+            free( pv->buffer );
+            pv->buffer = NULL;
+        }
+        free( pv );
+        w->private_data = NULL;
     }
 }
 
@@ -334,7 +420,6 @@ static int get_frame_buf( AVCodecContext *context, AVFrame *frame )
     hb_work_private_t *pv = context->opaque;
     frame->pts = pv->pts;
     pv->pts = -1;
-
     return avcodec_default_get_buffer( context, frame );
 }
 
@@ -353,6 +438,21 @@ static void log_chapter( hb_work_private_t *pv, int chap_num, int64_t pts )
     }
 }
 
+static void flushDelayQueue( hb_work_private_t *pv )
+{
+    hb_buffer_t *buf;
+    int slot = pv->nframes & (HEAP_SIZE-1);
+
+    // flush all the video packets left on our timestamp-reordering delay q
+    while ( ( buf = pv->delayq[slot] ) != NULL )
+    {
+        buf->start = heap_pop( &pv->pts_heap );
+        hb_list_add( pv->list, buf );
+        pv->delayq[slot] = NULL;
+        slot = ( slot + 1 ) & (HEAP_SIZE-1);
+    }
+}
+
 static int decodeFrame( hb_work_private_t *pv, uint8_t *data, int size )
 {
     int got_picture;
@@ -376,40 +476,86 @@ static int decodeFrame( hb_work_private_t *pv, uint8_t *data, int size )
         // worked at this point frame.pts should hold the frame's pts from the
         // original data stream or -1 if it didn't have one. in the latter case
         // we generate the next pts in sequence for it.
+        double frame_dur = pv->duration;
+        if ( frame_dur <= 0 )
+        {
+            frame_dur = 90000. * (double)pv->context->time_base.num /
+                        (double)pv->context->time_base.den;
+            pv->duration = frame_dur;
+        }
+        if ( frame.repeat_pict )
+        {
+            frame_dur += frame.repeat_pict * frame_dur * 0.5;
+        }
+        // If there was no pts for this frame, assume constant frame rate
+        // video & estimate the next frame time from the last & duration.
         double pts = frame.pts;
         if ( pts < 0 )
         {
             pts = pv->pts_next;
         }
-        if ( pv->duration == 0 )
-        {
-            pv->duration = 90000. * pv->context->time_base.num /
-                           pv->context->time_base.den;
-        }
-        double frame_dur = pv->duration;
-        frame_dur += frame.repeat_pict * frame_dur * 0.5;
         pv->pts_next = pts + frame_dur;
 
-        hb_buffer_t *buf = copy_frame( pv->context, &frame );
-        buf->start = pts;
+        hb_buffer_t *buf;
+
+        // if we're doing a scan we don't worry about timestamp reordering
+        if ( ! pv->job )
+        {
+            buf = copy_frame( pv->context, &frame );
+            buf->start = pts;
+            hb_list_add( pv->list, buf );
+            ++pv->nframes;
+            return got_picture;
+        }
 
-        if ( pv->new_chap && buf->start >= pv->chap_time )
+        // 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 )
         {
-            buf->new_chap = pv->new_chap;
-            pv->new_chap = 0;
-            pv->chap_time = 0;
-            if ( pv->job )
+            buf->start = heap_pop( &pv->pts_heap );
+
+            if ( pv->new_chap && buf->start >= pv->chap_time )
             {
+                buf->new_chap = pv->new_chap;
+                pv->new_chap = 0;
+                pv->chap_time = 0;
                 log_chapter( pv, buf->new_chap, buf->start );
             }
+            else if ( pv->nframes == 0 )
+            {
+                log_chapter( pv, pv->job->chapter_start, buf->start );
+            }
+            hb_list_add( pv->list, buf );
         }
-        else if ( pv->job && pv->nframes == 0 )
-        {
-            log_chapter( pv, pv->job->chapter_start, buf->start );
-        }
-        hb_list_add( pv->list, buf );
+
+        // add the new frame to the delayq & push its timestamp on the heap
+        pv->delayq[slot] = copy_frame( pv->context, &frame );
+        heap_push( &pv->pts_heap, pts );
+
         ++pv->nframes;
     }
+
     return got_picture;
 }
 
@@ -438,8 +584,12 @@ static void decodeVideo( hb_work_private_t *pv, uint8_t *data, int size,
     } while ( pos < size );
 
     /* the stuff above flushed the parser, now flush the decoder */
-    while ( size == 0 && decodeFrame( pv, NULL, 0 ) )
+    if ( size <= 0 )
     {
+        while ( decodeFrame( pv, NULL, 0 ) )
+        {
+        }
+        flushDelayQueue( pv );
     }
 }
 
@@ -499,7 +649,7 @@ 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 = -1;
+    int64_t pts = AV_NOPTS_VALUE;
     int64_t dts = pts;
 
     *buf_in = NULL;
@@ -510,8 +660,6 @@ static int decavcodecvWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
         decodeVideo( pv, in->data, in->size, pts, dts );
         hb_list_add( pv->list, in );
         *buf_out = link_buf_list( pv );
-        hb_log( "%s done: %u frames, %u decoder errors",
-                pv->context->codec->name, pv->nframes, pv->decode_errors );
         return HB_WORK_DONE;
     }
 
@@ -620,31 +768,40 @@ static void init_ffmpeg_context( hb_work_object_t *w )
     // the frame rate in the codec is usually bogus but it's sometimes
     // ok in the stream.
     AVStream *st = hb_ffmpeg_avstream( w->codec_param );
-    AVRational tb;
-    // XXX because the time bases are so screwed up, we only take values
-    // in the range 8fps - 64fps.
-    if ( st->time_base.num * 64 > st->time_base.den &&
-         st->time_base.den > st->time_base.num * 8 )
-    {
-        tb = st->time_base;
-    }
-    else if ( st->codec->time_base.num * 64 > st->codec->time_base.den &&
-              st->codec->time_base.den > st->codec->time_base.num * 8 )
-    {
-        tb = st->codec->time_base;
-    }
-    else if ( st->r_frame_rate.den * 64 > st->r_frame_rate.num &&
-              st->r_frame_rate.num > st->r_frame_rate.den * 8 )
+
+    if ( st->nb_frames && st->duration )
     {
-        tb.num = st->r_frame_rate.den;
-        tb.den = st->r_frame_rate.num;
+        // compute the average frame duration from the total number
+        // of frames & the total duration.
+        pv->duration = ( (double)st->duration * (double)st->time_base.num ) /
+                       ( (double)st->nb_frames * (double)st->time_base.den );
     }
     else
     {
-        tb.num = 1001;  /*XXX*/
-        tb.den = 30000; /*XXX*/
+        // XXX We don't have a frame count or duration so try to use the
+        // far less reliable time base info in the stream.
+        // Because the time bases are so screwed up, we only take values
+        // in the range 8fps - 64fps.
+        AVRational tb;
+        if ( st->time_base.num * 64 > st->time_base.den &&
+             st->time_base.den > st->time_base.num * 8 )
+        {
+            tb = st->time_base;
+        }
+        else if ( st->r_frame_rate.den * 64 > st->r_frame_rate.num &&
+                  st->r_frame_rate.num > st->r_frame_rate.den * 8 )
+        {
+            tb.num = st->r_frame_rate.den;
+            tb.den = st->r_frame_rate.num;
+        }
+        else
+        {
+            tb.num = 1001;  /*XXX*/
+            tb.den = 24000; /*XXX*/
+        }
+        pv->duration =  (double)tb.num / (double)tb.den;
     }
-    pv->duration = 90000. * tb.num / tb.den;
+    pv->duration *= 90000.;
 
     // we have to wrap ffmpeg's get_buffer to be able to set the pts (?!)
     pv->context->opaque = pv;
@@ -675,7 +832,8 @@ static int decavcodecviInit( hb_work_object_t * w, hb_job_t * job )
     w->private_data = pv;
     pv->job   = job;
     pv->list = hb_list_init();
-
+    pv->pts_next = -1;
+    pv->pts = -1;
     return 0;
 }
 
@@ -686,32 +844,8 @@ static int decavcodecviWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
     if ( ! pv->context )
     {
         init_ffmpeg_context( w );
-
-        switch ( pv->context->codec_id )
-        {
-            // These are the only formats whose timestamps we'll believe.
-            // All others are treated as CFR (i.e., we take the first timestamp
-            // then generate all the others from the frame rate). The reason for
-            // this is that the M$ encoders are so frigging buggy with garbage
-            // like packed b-frames (vfw divx mpeg4) that believing their timestamps
-            // results in discarding more than half the video frames because they'll
-            // be out of sequence (and attempting to reseqence them doesn't work
-            // because it's the timestamps that are wrong, not the decoded frame
-            // order). All hail Redmond, ancestral home of the rich & stupid.
-            case CODEC_ID_MPEG2VIDEO:
-            case CODEC_ID_RAWVIDEO:
-            case CODEC_ID_H264:
-            case CODEC_ID_VC1:
-                break;
-
-            default:
-                pv->ignore_pts = 1;
-                break;
-        }
     }
     hb_buffer_t *in = *buf_in;
-    int64_t pts = -1;
-
     *buf_in = NULL;
 
     /* if we got an empty buffer signaling end-of-stream send it downstream */
@@ -721,45 +855,21 @@ static int decavcodecviWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
         while ( decodeFrame( pv, NULL, 0 ) )
         {
         }
+        flushDelayQueue( pv );
         hb_list_add( pv->list, in );
         *buf_out = link_buf_list( pv );
-        hb_log( "%s done: %u frames, %u decoder errors, %u drops",
-                pv->context->codec->name, pv->nframes, pv->decode_errors,
-                pv->ndrops );
         return HB_WORK_DONE;
     }
 
-    if( in->start >= 0 )
+    int64_t pts = in->start;
+    if( pts >= 0 )
     {
         // use the first timestamp as our 'next expected' pts
-        if ( pv->pts_next <= 0 )
-        {
-            pv->pts_next = in->start;
-        }
-
-        if ( ! pv->ignore_pts )
+        if ( pv->pts_next < 0 )
         {
-            pts = in->start;
-            if ( pv->pts > 0 )
-            {
-                hb_log( "overwriting pts %lld with %lld (diff %d)",
-                        pv->pts, pts, pts - pv->pts );
-            }
-            if ( pv->pts_next - pts >= pv->duration )
-            {
-                // this frame starts more than a frame time before where
-                // the nominal frame rate says it should - drop it.
-                // log the first 10 drops so we'll know what's going on.
-                if ( pv->ndrops++ < 10 )
-                {
-                    hb_log( "time reversal next %.0f pts %lld (diff %g)",
-                            pv->pts_next, pts, pv->pts_next - pts );
-                }
-                hb_buffer_close( &in );
-                return HB_WORK_OK;
-            }
-            pv->pts = pts;
+            pv->pts_next = pts;
         }
+        pv->pts = pts;
     }
 
     if ( in->new_chap )
@@ -778,45 +888,15 @@ static int decavcodecviInfo( hb_work_object_t *w, hb_work_info_t *info )
 {
     if ( decavcodecvInfo( w, info ) )
     {
-        // There are at least three different video frame rates in ffmpeg:
-        //  - time_base in the AVStream
-        //  - time_base in the AVCodecContext
-        //  - r_frame_rate in the AVStream
-        // There's no guidence on which if any of these to believe but the
-        // routine compute_frame_duration tries the stream first then the codec.
-        // In general the codec time base seems bogus & the stream time base is
-        // ok except for wmv's where the stream time base is also bogus but
-        // r_frame_rate is sometimes ok & sometimes a random number.
-        AVStream *st = hb_ffmpeg_avstream( w->codec_param );
-        AVRational tb;
-        // XXX because the time bases are so screwed up, we only take values
-        // in the range 8fps - 64fps.
-        if ( st->time_base.num * 64 > st->time_base.den &&
-             st->time_base.den > st->time_base.num * 8 )
-        {
-            tb = st->time_base;
-        }
-        else if ( st->codec->time_base.num * 64 > st->codec->time_base.den &&
-                  st->codec->time_base.den > st->codec->time_base.num * 8 )
+        hb_work_private_t *pv = w->private_data;
+        if ( ! pv->context )
         {
-            tb = st->codec->time_base;
+            init_ffmpeg_context( w );
         }
-        else if ( st->r_frame_rate.den * 64 > st->r_frame_rate.num &&
-                  st->r_frame_rate.num > st->r_frame_rate.den * 8 )
-        {
-            tb.num = st->r_frame_rate.den;
-            tb.den = st->r_frame_rate.num;
-        }
-        else
-        {
-            tb.num = 1001;  /*XXX*/
-            tb.den = 30000; /*XXX*/
-        }
-
-        // ffmpeg gives the frame rate in frames per second while HB wants
-        // it in units of the 27MHz MPEG clock. */
+        // we have the frame duration in units of the 90KHz pts clock but
+        // need it in units of the 27MHz MPEG clock. */
         info->rate = 27000000;
-        info->rate_base = (int64_t)tb.num * 27000000LL / tb.den;
+        info->rate_base = pv->duration * 300.;
         return 1;
     }
     return 0;
@@ -829,8 +909,20 @@ static void decodeAudio( hb_work_private_t *pv, uint8_t *data, int size )
 
     while ( pos < size )
     {
-        int16_t buffer[AVCODEC_MAX_AUDIO_FRAME_SIZE];
-        int out_size = sizeof(buffer);
+        int16_t *buffer = pv->buffer;
+        if ( buffer == NULL )
+        {
+            // XXX ffmpeg bug workaround
+            // malloc a buffer for the audio decode. On an x86, ffmpeg
+            // uses mmx/sse instructions on this buffer without checking
+            // that it's 16 byte aligned and this will cause an abort if
+            // the buffer is allocated on our stack. Rather than doing
+            // complicated, machine dependent alignment here we use the
+            // fact that malloc returns an aligned pointer on most architectures.
+            pv->buffer = malloc( AVCODEC_MAX_AUDIO_FRAME_SIZE );
+            buffer = pv->buffer;
+        }
+        int out_size = AVCODEC_MAX_AUDIO_FRAME_SIZE;
         int len = avcodec_decode_audio2( context, buffer, &out_size,
                                          data + pos, size - pos );
         if ( len <= 0 )
@@ -842,9 +934,11 @@ static void decodeAudio( hb_work_private_t *pv, uint8_t *data, int size )
         {
             hb_buffer_t *buf = hb_buffer_init( 2 * out_size );
 
+            // convert from bytes to total samples
+            out_size >>= 1;
+
             double pts = pv->pts_next;
             buf->start = pts;
-            out_size >>= 1;
             pts += out_size * pv->duration;
             buf->stop  = pts;
             pv->pts_next = pts;
@@ -875,11 +969,18 @@ static int decavcodecaiWork( hb_work_object_t *w, hb_buffer_t **buf_in,
     if ( ! pv->context )
     {
         init_ffmpeg_context( w );
+        // duration is a scaling factor to go from #bytes in the decoded
+        // frame to frame time (in 90KHz mpeg ticks). 'channels' converts
+        // total samples to per-channel samples. 'sample_rate' converts
+        // per-channel samples to seconds per sample and the 90000
+        // is mpeg ticks per second.
         pv->duration = 90000. /
                     (double)( pv->context->sample_rate * pv->context->channels );
     }
     hb_buffer_t *in = *buf_in;
 
+    // if the packet has a timestamp use it if we don't have a timestamp yet
+    // or if there's been a timing discontinuity of more than 100ms.
     if ( in->start >= 0 &&
          ( pv->pts_next < 0 || ( in->start - pv->pts_next ) > 90*100 ) )
     {
index 0516932..8e9da7f 100644 (file)
@@ -58,6 +58,7 @@ hb_thread_t * hb_reader_init( hb_job_t * job )
     r->stream_timing[0].id = r->title->video_id;
     r->stream_timing[0].average = 90000. * (double)job->vrate_base /
                                            (double)job->vrate;
+    r->stream_timing[0].last = -r->stream_timing[0].average;
     r->stream_timing[1].id = -1;
 
     return hb_thread_init( "reader", ReaderFunc, r,
@@ -77,7 +78,7 @@ static void push_buf( const hb_reader_t *r, hb_fifo_t *fifo, hb_buffer_t *buf )
     hb_fifo_push( fifo, buf );
 }
 
-// The MPEG STD (Standard Timing Decoder) essentially requires that we keep
+// 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
@@ -109,8 +110,8 @@ static stream_timing_t *id_to_st( hb_reader_t *r, const hb_buffer_t *buf )
             st = r->stream_timing + slot;
         }
         st->id = buf->id;
-        st->last = buf->renderOffset;
         st->average = 30.*90.;
+        st->last = buf->renderOffset - st->average;
 
         st[1].id = -1;
     }
@@ -268,13 +269,14 @@ static void ReaderFunc( void * _r )
             if ( ! r->saw_video )
             {
                 /* The first video packet defines 'time zero' so discard
-                   data until we get a video packet with a PTS */
-                if ( buf->id == r->title->video_id && buf->start != -1 )
+                   data until we get a video packet with a PTS & DTS */
+                if ( buf->id == r->title->video_id && buf->start != -1 &&
+                     buf->renderOffset != -1 )
                 {
                     r->saw_video = 1;
                     r->scr_changes = r->demux.scr_changes;
                     new_scr_offset( r, buf );
-                    hb_log( "reader: first SCR %llu scr_offset %llu",
+                    hb_log( "reader: first SCR %lld scr_offset %lld",
                             r->demux.last_scr, r->scr_offset );
                 }
                 else
@@ -284,7 +286,7 @@ static void ReaderFunc( void * _r )
             }
             if( fifos )
             {
-                if ( buf->start != -1 )
+                if ( buf->renderOffset != -1 )
                 {
                     if ( r->scr_changes == r->demux.scr_changes )
                     {
@@ -301,9 +303,10 @@ static void ReaderFunc( void * _r )
                         new_scr_offset( r, buf );
                     }
                     // adjust timestamps to remove System Clock Reference offsets.
-                    buf->start -= r->scr_offset;
                     buf->renderOffset -= r->scr_offset;
                 }
+                if ( buf->start != -1 )
+                    buf->start -= r->scr_offset;
 
                 buf->sequence = r->sequence++;
                 /* if there are mutiple output fifos, send a copy of the
@@ -326,13 +329,11 @@ static void ReaderFunc( void * _r )
         }
     }
 
-    /* send empty buffers upstream to video & audio decoders to signal we're done */
+    // send empty buffers downstream to video & audio decoders to signal we're done.
     push_buf( r, r->job->fifo_mpeg2, hb_buffer_init(0) );
 
     hb_audio_t *audio;
-    for( n = 0;
-         ( audio = hb_list_item( r->job->title->list_audio, n ) ) != NULL;
-         ++n )
+    for( n = 0; ( audio = hb_list_item( r->job->title->list_audio, n ) ); ++n )
     {
         if ( audio->priv.fifo_in )
             push_buf( r, audio->priv.fifo_in, hb_buffer_init(0) );
@@ -342,12 +343,12 @@ static void ReaderFunc( void * _r )
     hb_buffer_close( &ps );
     if (r->dvd)
     {
-      hb_dvd_stop( r->dvd );
-      hb_dvd_close( &r->dvd );
+        hb_dvd_stop( r->dvd );
+        hb_dvd_close( &r->dvd );
     }
     else if (r->stream)
     {
-      hb_stream_close(&r->stream);
+        hb_stream_close(&r->stream);
     }
 
     if ( r->stream_timing )
index 930af30..9768d95 100755 (executable)
@@ -4,15 +4,16 @@
    Homepage: <http://handbrake.fr/>.
    It may be used under the terms of the GNU General Public License. */
 
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+
 #include "hb.h"
 #include "lang.h"
 #include "a52dec/a52.h"
 #include "libavcodec/avcodec.h"
 #include "libavformat/avformat.h"
 
-#include <string.h>
-#include <ctype.h>
-
 #define min(a, b) a < b ? a : b
 
 /*
@@ -136,6 +137,8 @@ struct hb_stream_s
     hb_title_t *title;
 
     AVFormatContext *ffmpeg_ic;
+    AVPacket *ffmpeg_pkt;
+    double ffmpeg_tsconv[MAX_STREAMS];
 
     struct {
         int lang_code;
@@ -2337,6 +2340,9 @@ static int ffmpeg_codec_param( hb_stream_t *stream, int stream_index )
 // (the original scan stream was closed and no longer exists).
 static void ffmpeg_remap_stream( hb_stream_t *stream, hb_title_t *title )
 {
+    // tell ffmpeg we want a pts on every frame it returns
+    stream->ffmpeg_ic->flags |= AVFMT_FLAG_GENPTS;
+
     // all the video & audio came from the same stream so remapping
     // the video's stream slot takes care of everything.
     int slot = title->video_codec_param & (ffmpeg_sl_size - 1);
@@ -2392,6 +2398,8 @@ static int ffmpeg_open( hb_stream_t *stream, hb_title_t *title )
 
     stream->ffmpeg_ic = ic;
     stream->hb_stream_type = ffmpeg;
+    stream->ffmpeg_pkt = malloc(sizeof(*stream->ffmpeg_pkt));
+    av_init_packet( stream->ffmpeg_pkt );
 
     if ( title )
     {
@@ -2440,6 +2448,11 @@ static void ffmpeg_close( hb_stream_t *d )
         av_close_input_file( ffmpeg_deferred_close );
     }
     ffmpeg_deferred_close = d->ffmpeg_ic;
+    if ( d->ffmpeg_pkt != NULL )
+    {
+        free( d->ffmpeg_pkt );
+        d->ffmpeg_pkt = NULL;
+    }
 }
 
 static void add_ffmpeg_audio( hb_title_t *title, hb_stream_t *stream, int id )
@@ -2561,27 +2574,58 @@ static int64_t av_to_hb_pts( int64_t pts, double conv_factor )
 
 static int ffmpeg_read( hb_stream_t *stream, hb_buffer_t *buf )
 {
-    AVPacket pkt;
+    int err;
+    if ( ( err = av_read_frame( stream->ffmpeg_ic, stream->ffmpeg_pkt )) < 0 )
+    {
+        // XXX the following conditional is to handle avi files that
+        // use M$ 'packed b-frames' and occasionally have negative
+        // sizes for the null frames these require.
+        if ( err != AVERROR_NOMEM || stream->ffmpeg_pkt->size >= 0 )
+            // eof
+            return 0;
+    }
+    if ( stream->ffmpeg_pkt->size <= 0 )
+    {
+        // M$ "invalid and inefficient" packed b-frames require 'null frames'
+        // following them to preserve the timing (since the packing puts two
+        // or more frames in what looks like one avi frame). The contents and
+        // size of these null frames are ignored by the ff_h263_decode_frame
+        // as long as they're < 20 bytes. We need a positive size so we use
+        // one byte if we're given a zero or negative size. We don't know
+        // if the pkt data points anywhere reasonable so we just stick a
+        // byte of zero in our outbound buf.
+        buf->size = 1;
+        *buf->data = 0;
+    }
+    else
+    {
+        if ( stream->ffmpeg_pkt->size > buf->alloc )
+        {
+            // need to expand buffer
+            hb_buffer_realloc( buf, stream->ffmpeg_pkt->size );
+        }
+        memcpy( buf->data, stream->ffmpeg_pkt->data, stream->ffmpeg_pkt->size );
+        buf->size = stream->ffmpeg_pkt->size;
+    }
+    buf->id = stream->ffmpeg_pkt->stream_index;
 
-    if ( av_read_frame( stream->ffmpeg_ic, &pkt ) < 0 )
+    // if we haven't done it already, compute a conversion factor to go
+    // from the ffmpeg timebase for the stream to HB's 90KHz timebase.
+    double tsconv = stream->ffmpeg_tsconv[stream->ffmpeg_pkt->stream_index];
+    if ( ! tsconv )
     {
-        return 0;
+        AVStream *s = stream->ffmpeg_ic->streams[stream->ffmpeg_pkt->stream_index];
+        tsconv = 90000. * (double)s->time_base.num / (double)s->time_base.den;
+        stream->ffmpeg_tsconv[stream->ffmpeg_pkt->stream_index] = tsconv;
+    }
+
+    buf->start = av_to_hb_pts( stream->ffmpeg_pkt->pts, tsconv );
+    buf->renderOffset = av_to_hb_pts( stream->ffmpeg_pkt->dts, tsconv );
+    if ( buf->renderOffset >= 0 && buf->start == -1 )
+    {
+        buf->start = buf->renderOffset;
     }
-    if ( pkt.size > buf->alloc )
-    {
-        // need to expand buffer
-        hb_buffer_realloc( buf, pkt.size );
-    }
-    memcpy( buf->data, pkt.data, pkt.size );
-    buf->id = pkt.stream_index;
-    buf->size = pkt.size;
-    int64_t pts = pkt.pts != AV_NOPTS_VALUE? pkt.pts : 
-                         pkt.dts != AV_NOPTS_VALUE? pkt.dts : -1;
-    buf->start = av_to_hb_pts( pts,
-                  av_q2d(stream->ffmpeg_ic->streams[pkt.stream_index]->time_base)*90000. );
-    buf->renderOffset = av_to_hb_pts( pkt.pts,
-                  av_q2d(stream->ffmpeg_ic->streams[pkt.stream_index]->time_base)*90000. );
-    av_free_packet( &pkt );
+    av_free_packet( stream->ffmpeg_pkt );
     return 1;
 }