Browse Source

Added support for seeking

Josh Yelon 18 years ago
parent
commit
1593012d5a

+ 35 - 28
doc/makepanda/makepanda.py

@@ -1571,7 +1571,7 @@ MakeDirectory("built/pandac/input")
 #
 #
 ##########################################################################################
 ##########################################################################################
 
 
-DEFAULT_SETTINGS=[
+DTOOL_CONFIG=[
     #_Variable_________________________Windows___________________Unix__________
     #_Variable_________________________Windows___________________Unix__________
     ("HAVE_PYTHON",                    '1',                      '1'),
     ("HAVE_PYTHON",                    '1',                      '1'),
     ("PYTHON_FRAMEWORK",               'UNDEF',                  'UNDEF'),
     ("PYTHON_FRAMEWORK",               'UNDEF',                  'UNDEF'),
@@ -1669,6 +1669,10 @@ DEFAULT_SETTINGS=[
     ("HAVE_CGGL",                      'UNDEF',                  'UNDEF'),
     ("HAVE_CGGL",                      'UNDEF',                  'UNDEF'),
     ("HAVE_CGDX9",                     'UNDEF',                  'UNDEF'),
     ("HAVE_CGDX9",                     'UNDEF',                  'UNDEF'),
     ("HAVE_FFMPEG",                    'UNDEF',                  'UNDEF'),
     ("HAVE_FFMPEG",                    'UNDEF',                  'UNDEF'),
+    ("PRC_SAVE_DESCRIPTIONS",          '1',                      '1'),
+]
+
+PRC_PARAMETERS=[
     ("DEFAULT_PRC_DIR",                '"<auto>etc"',            '"<auto>etc"'),
     ("DEFAULT_PRC_DIR",                '"<auto>etc"',            '"<auto>etc"'),
     ("PRC_DIR_ENVVARS",                '"PANDA_PRC_DIR"',        '"PANDA_PRC_DIR"'),
     ("PRC_DIR_ENVVARS",                '"PANDA_PRC_DIR"',        '"PANDA_PRC_DIR"'),
     ("PRC_PATH_ENVVARS",               '"PANDA_PRC_PATH"',       '"PANDA_PRC_PATH"'),
     ("PRC_PATH_ENVVARS",               '"PANDA_PRC_PATH"',       '"PANDA_PRC_PATH"'),
@@ -1681,71 +1685,74 @@ DEFAULT_SETTINGS=[
     ("PRC_RESPECT_TRUST_LEVEL",        'UNDEF',                  'UNDEF'),
     ("PRC_RESPECT_TRUST_LEVEL",        'UNDEF',                  'UNDEF'),
     ("PRC_DCONFIG_TRUST_LEVEL",        '0',                      '0'),
     ("PRC_DCONFIG_TRUST_LEVEL",        '0',                      '0'),
     ("PRC_INC_TRUST_LEVEL",            '0',                      '0'),
     ("PRC_INC_TRUST_LEVEL",            '0',                      '0'),
-    ("PRC_SAVE_DESCRIPTIONS",          '1',                      '1'),
 ]
 ]
 
 
 def WriteConfigSettings():
 def WriteConfigSettings():
-    settings={}
+    dtool_config={}
+    prc_parameters={}
     if (sys.platform == "win32"):
     if (sys.platform == "win32"):
-        for key,win,unix in DEFAULT_SETTINGS:
-            settings[key] = win
+        for key,win,unix in DTOOL_CONFIG:
+            dtool_config[key] = win
+        for key,win,unix in PRC_PARAMETERS:
+            prc_parameters[key] = win
     else:
     else:
         for key,win,unix in DEFAULT_SETTINGS:
         for key,win,unix in DEFAULT_SETTINGS:
-            settings[key] = unix
-    
+            dtool_config[key] = unix
+        for key,win,unix in PRC_PARAMETERS:
+            prc_parameters[key] = unix
+
     for x in PACKAGES:
     for x in PACKAGES:
         if (OMIT.count(x)==0):
         if (OMIT.count(x)==0):
-            if (settings.has_key("HAVE_"+x)):
-                settings["HAVE_"+x] = '1'
+            if (dtool_config.has_key("HAVE_"+x)):
+                dtool_config["HAVE_"+x] = '1'
     
     
-    settings["HAVE_NET"] = '1'
+    dtool_config["HAVE_NET"] = '1'
     
     
     if (OMIT.count("NVIDIACG")==0):
     if (OMIT.count("NVIDIACG")==0):
-        settings["HAVE_CG"] = '1'
-        settings["HAVE_CGGL"] = '1'
-        settings["HAVE_CGDX9"] = '1'
+        dtool_config["HAVE_CG"] = '1'
+        dtool_config["HAVE_CGGL"] = '1'
+        dtool_config["HAVE_CGDX9"] = '1'
     
     
     if (OPTIMIZE <= 3):
     if (OPTIMIZE <= 3):
-        if (settings["HAVE_NET"] != 'UNDEF'):
-            settings["DO_PSTATS"] = '1'
+        if (dtool_config["HAVE_NET"] != 'UNDEF'):
+            dtool_config["DO_PSTATS"] = '1'
     
     
     if (OPTIMIZE <= 3):
     if (OPTIMIZE <= 3):
-        settings["DO_COLLISION_RECORDING"] = '1'
+        dtool_config["DO_COLLISION_RECORDING"] = '1'
     
     
     #if (OPTIMIZE <= 2):
     #if (OPTIMIZE <= 2):
-    #    settings["TRACK_IN_INTERPRETER"] = '1'
+    #    dtool_config["TRACK_IN_INTERPRETER"] = '1'
     
     
     if (OPTIMIZE <= 3):
     if (OPTIMIZE <= 3):
-        settings["DO_MEMORY_USAGE"] = '1'
+        dtool_config["DO_MEMORY_USAGE"] = '1'
     
     
     #if (OPTIMIZE <= 1):
     #if (OPTIMIZE <= 1):
-    #    settings["DO_PIPELINING"] = '1'
+    #    dtool_config["DO_PIPELINING"] = '1'
     
     
     if (OPTIMIZE <= 3):
     if (OPTIMIZE <= 3):
-        settings["NOTIFY_DEBUG"] = '1'
+        dtool_config["NOTIFY_DEBUG"] = '1'
 
 
     conf = "/* prc_parameters.h.  Generated automatically by makepanda.py */\n"
     conf = "/* prc_parameters.h.  Generated automatically by makepanda.py */\n"
-    for key in settings.keys():
+    for key in prc_parameters.keys():
         if ((key == "DEFAULT_PRC_DIR") or (key[:4]=="PRC_")):
         if ((key == "DEFAULT_PRC_DIR") or (key[:4]=="PRC_")):
-            val = settings[key]
+            val = prc_parameters[key]
             if (val == 'UNDEF'): conf = conf + "#undef " + key + "\n"
             if (val == 'UNDEF'): conf = conf + "#undef " + key + "\n"
             else:                conf = conf + "#define " + key + " " + val + "\n"
             else:                conf = conf + "#define " + key + " " + val + "\n"
-            del settings[key]
+            del prc_parameters[key]
     ConditionalWriteFile('built/include/prc_parameters.h', conf)
     ConditionalWriteFile('built/include/prc_parameters.h', conf)
 
 
     conf = "/* dtool_config.h.  Generated automatically by makepanda.py */\n"
     conf = "/* dtool_config.h.  Generated automatically by makepanda.py */\n"
-    for key in settings.keys():
-        val = settings[key]
+    for key in dtool_config.keys():
+        val = dtool_config[key]
         if (val == 'UNDEF'): conf = conf + "#undef " + key + "\n"
         if (val == 'UNDEF'): conf = conf + "#undef " + key + "\n"
         else:                conf = conf + "#define " + key + " " + val + "\n"
         else:                conf = conf + "#define " + key + " " + val + "\n"
-        del settings[key]
+        del dtool_config[key]
     ConditionalWriteFile('built/include/dtool_config.h', conf)
     ConditionalWriteFile('built/include/dtool_config.h', conf)
 
 
     for x in PACKAGES:
     for x in PACKAGES:
         if (OMIT.count(x)): ConditionalWriteFile('built/tmp/dtool_have_'+x.lower()+'.dat',"0\n")
         if (OMIT.count(x)): ConditionalWriteFile('built/tmp/dtool_have_'+x.lower()+'.dat',"0\n")
         else:               ConditionalWriteFile('built/tmp/dtool_have_'+x.lower()+'.dat',"1\n")
         else:               ConditionalWriteFile('built/tmp/dtool_have_'+x.lower()+'.dat',"1\n")
 
 
-
 WriteConfigSettings()
 WriteConfigSettings()
 
 
 
 
@@ -2541,7 +2548,7 @@ EnqueueIgate(ipath=IPATH, opts=OPTS, outd='libtext.in', obj='libtext_igate.obj',
 #
 #
 
 
 IPATH=['panda/src/movies']
 IPATH=['panda/src/movies']
-OPTS=['BUILDING_PANDA']
+OPTS=['BUILDING_PANDA', 'FFMPEG']
 EnqueueCxx(ipath=IPATH, opts=OPTS, src='movies_composite1.cxx', obj='movies_composite1.obj')
 EnqueueCxx(ipath=IPATH, opts=OPTS, src='movies_composite1.cxx', obj='movies_composite1.obj')
 EnqueueIgate(ipath=IPATH, opts=OPTS, outd='libmovies.in', obj='libmovies_igate.obj',
 EnqueueIgate(ipath=IPATH, opts=OPTS, outd='libmovies.in', obj='libmovies_igate.obj',
             src='panda/src/movies',  module='panda', library='libmovies',
             src='panda/src/movies',  module='panda', library='libmovies',

+ 0 - 39
panda/src/movies/ffmpegVideo.I

@@ -16,42 +16,3 @@
 //
 //
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 
 
-////////////////////////////////////////////////////////////////////
-//     Function: FfmpegVideo::get_time_correction
-//       Access: Private
-//  Description: The timestamps of packets in AVI files are often
-//               really inaccurate.  This returns a small heuristic
-//               correction based on analysis of the audio packets.
-////////////////////////////////////////////////////////////////////
-
-INLINE double FfmpegVideo::
-get_time_correction() {
-  // The idea here is simple.  For audio packets it
-  // is possible to calculate an accurate time by counting
-  // the audio samples.  It is then possible to measure
-  // the difference between the reported time and the
-  // actual time of the audio packet.  We average this 
-  // difference over the last few audio packets.  We then
-  // assume that the video packets are off by the same amount.
-
-  double correction = 0.0;
-  for (int i=0; i<_time_correction_window; i++) {
-    correction += _time_corrections[i];
-  }
-  correction /= _time_correction_window;
-  return correction;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: FfmpegVideo::update_time_correction
-//       Access: Private
-//  Description: The timestamps of packets in AVI files are often
-//               really inaccurate.  This records some data which
-//               is needed to calculate a heuristic correction factor.
-////////////////////////////////////////////////////////////////////
-INLINE void FfmpegVideo::
-update_time_correction(double diff) {
-  _time_corrections[_time_correction_next % _time_correction_window] = diff;
-  _time_correction_next += 1;
-}
-

+ 137 - 113
panda/src/movies/ffmpegVideo.cxx

@@ -34,15 +34,12 @@ FfmpegVideo::
 FfmpegVideo(const Filename &name) :
 FfmpegVideo(const Filename &name) :
   MovieVideo(name),
   MovieVideo(name),
   _filename(name),
   _filename(name),
-  _samples_read(0),
   _format_ctx(0),
   _format_ctx(0),
   _video_index(-1),
   _video_index(-1),
-  _audio_index(-1),
   _video_ctx(0),
   _video_ctx(0),
-  _audio_ctx(0),
   _frame(0),
   _frame(0),
   _frame_out(0),
   _frame_out(0),
-  _time_correction_next(0)
+  _min_fseek(3.0)
 {
 {
   string osname = _filename.to_os_specific();
   string osname = _filename.to_os_specific();
   if (av_open_input_file(&_format_ctx, osname.c_str(), NULL, 0, NULL)!=0) {
   if (av_open_input_file(&_format_ctx, osname.c_str(), NULL, 0, NULL)!=0) {
@@ -55,18 +52,13 @@ FfmpegVideo(const Filename &name) :
     return;
     return;
   }
   }
   
   
-  // Find the video and audio streams
+  // Find the video stream
   for(int i=0; i<_format_ctx->nb_streams; i++) {
   for(int i=0; i<_format_ctx->nb_streams; i++) {
     if(_format_ctx->streams[i]->codec->codec_type==CODEC_TYPE_VIDEO) {
     if(_format_ctx->streams[i]->codec->codec_type==CODEC_TYPE_VIDEO) {
       _video_index = i;
       _video_index = i;
       _video_ctx = _format_ctx->streams[i]->codec;
       _video_ctx = _format_ctx->streams[i]->codec;
       _video_timebase = av_q2d(_format_ctx->streams[i]->time_base);
       _video_timebase = av_q2d(_format_ctx->streams[i]->time_base);
     }
     }
-    if(_format_ctx->streams[i]->codec->codec_type==CODEC_TYPE_AUDIO) {
-      _audio_index = i;
-      _audio_ctx = _format_ctx->streams[i]->codec;
-      _audio_timebase = av_q2d(_format_ctx->streams[i]->time_base);
-    }
   }
   }
   
   
   if (_video_ctx == 0) {
   if (_video_ctx == 0) {
@@ -87,34 +79,23 @@ FfmpegVideo(const Filename &name) :
   _size_x = _video_ctx->width;
   _size_x = _video_ctx->width;
   _size_y = _video_ctx->height;
   _size_y = _video_ctx->height;
   _num_components = 3; // Don't know how to implement RGBA movies yet.
   _num_components = 3; // Don't know how to implement RGBA movies yet.
-  
-  if (_audio_ctx) {
-    AVCodec *pAudioCodec=avcodec_find_decoder(_audio_ctx->codec_id);
-    if (pAudioCodec == 0) {
-      _audio_ctx = 0;
-      _audio_index = -1;
-    } else {
-      if (avcodec_open(_audio_ctx, pAudioCodec)<0) {
-        _audio_ctx = 0;
-        _audio_index = -1;
-      }
-    }
-  }
-
   _length = (_format_ctx->duration * 1.0) / AV_TIME_BASE;
   _length = (_format_ctx->duration * 1.0) / AV_TIME_BASE;
   _can_seek = true;
   _can_seek = true;
   _can_seek_fast = true;
   _can_seek_fast = true;
 
 
-  memset(_time_corrections, 0, sizeof(_time_corrections));
-  
+  _packet = new AVPacket;
   _frame = avcodec_alloc_frame();
   _frame = avcodec_alloc_frame();
   _frame_out = avcodec_alloc_frame();
   _frame_out = avcodec_alloc_frame();
-  if ((_frame == 0)||(_frame_out == 0)) {
+  if ((_packet == 0)||(_frame == 0)||(_frame_out == 0)) {
     cleanup();
     cleanup();
     return;
     return;
   }
   }
+  memset(_packet, 0, sizeof(AVPacket));
   
   
-  read_ahead();
+  fetch_packet(0.0);
+  _initial_dts = _packet->dts;
+  _packet_time = 0.0;
+  _last_start = -1.0;
   _next_start = 0.0;
   _next_start = 0.0;
 }
 }
 
 
@@ -136,6 +117,13 @@ FfmpegVideo::
 void FfmpegVideo::
 void FfmpegVideo::
 cleanup() {
 cleanup() {
   _frame_out->data[0] = 0;
   _frame_out->data[0] = 0;
+  if (_packet) {
+    if (_packet->data) {
+      av_free_packet(_packet);
+    }
+    delete _packet;
+    _packet = 0;
+  }
   if (_format_ctx) {
   if (_format_ctx) {
     av_close_input_file(_format_ctx);
     av_close_input_file(_format_ctx);
     _format_ctx = 0;
     _format_ctx = 0;
@@ -149,83 +137,103 @@ cleanup() {
     _frame_out = 0;
     _frame_out = 0;
   }
   }
   _video_ctx = 0;
   _video_ctx = 0;
-  _audio_ctx = 0;
   _video_index = -1;
   _video_index = -1;
-  _audio_index = -1;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: FfmpegVideo::read_ahead
-//       Access: Protected
-//  Description: The video prefetches the next video frame in order
-//               to be able to implement the next_start query.
-//               This function skips over audio packets, but in the
-//               process, it updates the time_correction value.
-//               If the stream is out of video packets, the packet
-//               is empty.
+//     Function: FfmpegVideo::export_frame
+//       Access: Public, Virtual
+//  Description: Exports the contents of the frame buffer into the
+//               user's target buffer.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void FfmpegVideo::
 void FfmpegVideo::
-read_ahead() {
-  if (_format_ctx == 0) {
-    return;
+export_frame(unsigned char *data, bool bgra) {
+  if (bgra) {
+    _frame_out->data[0] = data + ((_size_y - 1) * _size_x * 4);
+    _frame_out->linesize[0] = _size_x * -4;
+    img_convert((AVPicture *)_frame_out, PIX_FMT_BGRA, 
+                (AVPicture *)_frame, _video_ctx->pix_fmt, _size_x, _size_y);
+  } else {
+    _frame_out->data[0] = data + ((_size_y - 1) * _size_x * 3);
+    _frame_out->linesize[0] = _size_x * -3;
+    img_convert((AVPicture *)_frame_out, PIX_FMT_BGR24, 
+                (AVPicture *)_frame, _video_ctx->pix_fmt, _size_x, _size_y);
   }
   }
-  
-  AVPacket pkt;
+}
 
 
-  while(av_read_frame(_format_ctx, &pkt)>=0) {
-    
-    // Is this a packet from the video stream?
-    // If so, store corrected timestamp and return.
-    
-    if (pkt.stream_index== _video_index) {
-      double dts = pkt.dts * _video_timebase;
-      double correction = get_time_correction();
-      _next_start = dts + correction;
-      cerr << "Video: " << dts << " " << dts+correction << "\n";
-      if (_next_start < _last_start) {
-        _next_start = _last_start;
-      }
-      int finished = 0;
-      avcodec_decode_video(_video_ctx, _frame,
-                           &finished, pkt.data, pkt.size);
-      if (finished) {
-        return;
-      }
+////////////////////////////////////////////////////////////////////
+//     Function: FfmpegVideo::fetch_packet
+//       Access: Protected
+//  Description: Fetches a video packet and stores it in the 
+//               packet buffer.  Sets packet_time to the packet's
+//               timestamp.  If a packet could not be read, the
+//               packet is cleared and the packet_time is set to
+//               the specified default value.
+////////////////////////////////////////////////////////////////////
+void FfmpegVideo::
+fetch_packet(double default_time) {
+  if (_packet->data) {
+    av_free_packet(_packet);
+  }
+  while (av_read_frame(_format_ctx, _packet) >= 0) {
+    if (_packet->stream_index == _video_index) {
+      _packet_time = _packet->dts * _video_timebase;
+      return;
     }
     }
-    
-    // Is this a packet from the audio stream?
-    // If so, use it to calibrate the time correction value.
-    
-    if (pkt.stream_index== _audio_index) {
-      double dts = pkt.dts * _audio_timebase;
-      double real = (_samples_read * 1.0) / _audio_ctx->sample_rate;
-      update_time_correction(real - dts);
+    av_free_packet(_packet);
+  }
+  _packet->data = 0;
+  _packet_time = default_time;
+}
 
 
-      int16_t buffer[4096];
-      uint8_t *audio_pkt_data = pkt.data;
-      int audio_pkt_size = pkt.size;
-      while(audio_pkt_size > 0) {
-        int data_size = sizeof(buffer);
-        int decoded = avcodec_decode_audio(_audio_ctx, buffer, &data_size,
-                                           audio_pkt_data, audio_pkt_size);
-        if(decoded <= 0) {
-          break;
-        }
-        audio_pkt_data += decoded;
-        audio_pkt_size -= decoded;
-        if (data_size > 0) {
-          _samples_read += data_size / (2 * _audio_ctx->channels);
-        }
-      }
+////////////////////////////////////////////////////////////////////
+//     Function: FfmpegVideo::fetch_frame
+//       Access: Protected
+//  Description: Fetches a frame from the stream and stores it in
+//               the frame buffer.  Sets last_start and next_start
+//               to indicate the extents of the frame.
+////////////////////////////////////////////////////////////////////
+void FfmpegVideo::
+fetch_frame() {
+  int finished = 0;
+  _last_start = _packet_time;
+  while (!finished && _packet->data) {
+    avcodec_decode_video(_video_ctx, _frame,
+                         &finished, _packet->data, _packet->size);
+    fetch_packet(_last_start + 1.0);
+  }
+  _next_start = _packet_time;
+  cerr << "Fetch yields " << _last_start << " - " << _next_start << "\n";
+}
 
 
-      cerr << "Audio: " << pkt.dts << "(" << dts << ") " << real << "\n";
+////////////////////////////////////////////////////////////////////
+//     Function: FfmpegVideo::seek
+//       Access: Protected
+//  Description: Seeks to a target location.  Afterward, the
+//               packet_time is guaranteed to be less than or 
+//               equal to the specified time.
+////////////////////////////////////////////////////////////////////
+void FfmpegVideo::
+seek(double t) {
+  long long target_ts = (long long)(t / _video_timebase);
+  if (target_ts < (long long)(_initial_dts)) {
+    // Attempts to seek before the first packet will fail.
+    target_ts = _initial_dts;
+  }
+  if (av_seek_frame(_format_ctx, _video_index, target_ts, AVSEEK_FLAG_BACKWARD) < 0) {
+    if (t >= _packet_time) {
+      return;
     }
     }
-    
-    av_free_packet(&pkt);
+    movies_cat.error() << "Seek failure. Shutting down movie.\n";
+    cleanup();
+    _packet_time = t;
+    return;
+  }
+  fetch_packet(t);
+  cerr << "Seeking to " << t << " yields " << _packet_time << "\n";
+  if (_packet_time > t) {
+    _packet_time = t;
   }
   }
-  
-  cerr << "Synthesized dummy frame.\n";
-  _next_start = _next_start + 1.0;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -235,33 +243,49 @@ read_ahead() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void FfmpegVideo::
 void FfmpegVideo::
 fetch_into_buffer(double time, unsigned char *data, bool bgra) {
 fetch_into_buffer(double time, unsigned char *data, bool bgra) {
-
-  // If there was an error at any point, fetch black.
+  
+  // If there was an error at any point, synthesize black.
   if (_format_ctx==0) {
   if (_format_ctx==0) {
-    memset(data,0,size_x()*size_y()*(bgra?4:3));
-    _last_start = _next_start;
-    _next_start += 1.0;
+    if (data) {
+      memset(data,0,size_x()*size_y()*(bgra?4:3));
+    }
+    _last_start = time;
+    _next_start = time + 1.0;
     return;
     return;
   }
   }
   
   
-  AVCodecContext *ctx = _format_ctx->streams[_video_index]->codec;
-  nassertv(ctx->width  == size_x());
-  nassertv(ctx->height == size_y());
-  
-  if (bgra) {
-    _frame_out->data[0] = data + ((ctx->height-1) * ctx->width * 4);
-    _frame_out->linesize[0] = ctx->width * -4;
-    img_convert((AVPicture *)_frame_out, PIX_FMT_BGRA, 
-                (AVPicture *)_frame, ctx->pix_fmt, ctx->width, ctx->height);
+  if (time < _last_start) {
+    // Time is in the past.
+    seek(time);
+    while (_packet_time <= time) {
+      fetch_frame();
+    }
+  } else if (time < _next_start) {
+    // Time is in the present: already have the frame.
+  } else if (time < _next_start + _min_fseek) {
+    // Time is in the near future.
+    while (_packet_time <= time) {
+      fetch_frame();
+    }
   } else {
   } else {
-    _frame_out->data[0] = data + ((ctx->height-1) * ctx->width * 3);
-    _frame_out->linesize[0] = ctx->width * -3;
-    img_convert((AVPicture *)_frame_out, PIX_FMT_BGR24, 
-                (AVPicture *)_frame, ctx->pix_fmt, ctx->width, ctx->height);
+    // Time is in the far future.  Seek forward, then read.
+    // There's a danger here: because keyframes are spaced
+    // unpredictably, trying to seek forward could actually
+    // move us backward in the stream!  This must be avoided.
+    // So the rule is, try the seek.  If it hurts us by moving
+    // us backward, we increase the minimum threshold distance
+    // for forward-seeking in the future.
+    
+    double base = _packet_time;
+    seek(time);
+    if (_packet_time < base) {
+      _min_fseek += (base - _packet_time);
+    }
+    while (_packet_time <= time) {
+      fetch_frame();
+    }
   }
   }
-
-  _last_start = _next_start;
-  read_ahead();
+  export_frame(data, bgra);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////

+ 9 - 9
panda/src/movies/ffmpegVideo.h

@@ -47,24 +47,24 @@ class EXPCL_PANDA_MOVIES FfmpegVideo : public MovieVideo {
  protected:
  protected:
   INLINE double get_time_correction();
   INLINE double get_time_correction();
   INLINE void update_time_correction(double diff);
   INLINE void update_time_correction(double diff);
-  void read_ahead();
+  void fetch_packet(double default_time);
+  void fetch_frame();
+  void seek(double t);
+  void export_frame(unsigned char *data, bool bgra);
   void cleanup();
   void cleanup();
   
   
-  static const int _time_correction_window = 4;
-  double _time_corrections[_time_correction_window];
-  int _time_correction_next;
-
   Filename _filename;
   Filename _filename;
+  AVPacket *_packet;
+  double _packet_time;
   AVFormatContext *_format_ctx;
   AVFormatContext *_format_ctx;
   AVCodecContext *_video_ctx;
   AVCodecContext *_video_ctx;
   AVCodecContext *_audio_ctx;
   AVCodecContext *_audio_ctx;
-  int _video_index;
-  int _audio_index;
+  int    _video_index;
   double _video_timebase;
   double _video_timebase;
-  double _audio_timebase;
   AVFrame *_frame;
   AVFrame *_frame;
   AVFrame *_frame_out;
   AVFrame *_frame_out;
-  int _samples_read;
+  int _initial_dts;
+  double _min_fseek;
   
   
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {