Bläddra i källkod

solve some seeking issues

David Rose 14 år sedan
förälder
incheckning
742d5ffe7e

+ 228 - 134
panda/src/movies/ffmpegVideoCursor.cxx

@@ -76,77 +76,15 @@ init_from(FfmpegVideo *source) {
   _source = source;
   _source = source;
   _filename = _source->get_filename();
   _filename = _source->get_filename();
 
 
-  // Hold the global lock while we open the file and create avcodec
-  // objects.
-  ReMutexHolder av_holder(_av_lock);
-
-  if (!_source->get_subfile_info().is_empty()) {
-    // Read a subfile.
-    if (!_ffvfile.open_subfile(_source->get_subfile_info())) {
-      movies_cat.info() 
-        << "Couldn't open " << _source->get_subfile_info() << "\n";
-      cleanup();
-      return;
-    }
-
-  } else {
-    // Read a filename.
-    if (!_ffvfile.open_vfs(_filename)) {
-      movies_cat.info() 
-        << "Couldn't open " << _filename << "\n";
-      cleanup();
-      return;
-    }
-  }
-
-  _format_ctx = _ffvfile.get_format_context();
-  nassertv(_format_ctx != NULL);
-
-  if (av_find_stream_info(_format_ctx) < 0) {
-    movies_cat.info() 
-      << "Couldn't find stream info\n";
-    cleanup();
-    return;
-  }
-  
-  // Find the video stream
-  for (int i = 0; i < (int)_format_ctx->nb_streams; ++i) {
-    if (_format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
-      _video_index = i;
-      _video_ctx = _format_ctx->streams[i]->codec;
-      _video_timebase = av_q2d(_format_ctx->streams[i]->time_base);
-    }
-  }
-  
-  if (_video_ctx == 0) {
-    movies_cat.info() 
-      << "Couldn't find video_ctx\n";
+  if (!open_stream()) {
     cleanup();
     cleanup();
     return;
     return;
   }
   }
 
 
-  AVCodec *pVideoCodec = avcodec_find_decoder(_video_ctx->codec_id);
-  if (pVideoCodec == NULL) {
-    movies_cat.info() 
-      << "Couldn't find codec\n";
-    cleanup();
-    return;
-  }
-  if (avcodec_open(_video_ctx, pVideoCodec) < 0) {
-    movies_cat.info() 
-      << "Couldn't open codec\n";
-    cleanup();
-    return;
-  }
-  
-  _size_x = _video_ctx->width;
-  _size_y = _video_ctx->height;
-  _num_components = 3; // Don't know how to implement RGBA movies yet.
-  _length = (_format_ctx->duration * 1.0) / AV_TIME_BASE;
-  _can_seek = true;
-  _can_seek_fast = true;
+  ReMutexHolder av_holder(_av_lock);
   
   
 #ifdef HAVE_SWSCALE
 #ifdef HAVE_SWSCALE
+  nassertv(_convert_ctx == NULL);
   _convert_ctx = sws_getContext(_size_x, _size_y,
   _convert_ctx = sws_getContext(_size_x, _size_y,
                                 _video_ctx->pix_fmt, _size_x, _size_y,
                                 _video_ctx->pix_fmt, _size_x, _size_y,
                                 PIX_FMT_BGR24, SWS_BILINEAR | SWS_PRINT_INFO, NULL, NULL, NULL);
                                 PIX_FMT_BGR24, SWS_BILINEAR | SWS_PRINT_INFO, NULL, NULL, NULL);
@@ -469,6 +407,114 @@ release_buffer(Buffer *buffer) {
   do_recycle_frame(buffer);
   do_recycle_frame(buffer);
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: FfmpegVideoCursor::open_stream
+//       Access: Private
+//  Description: Opens the stream for the first time, or when needed
+//               internally.
+////////////////////////////////////////////////////////////////////
+bool FfmpegVideoCursor::
+open_stream() {
+  nassertr(!_ffvfile.is_open(), false);
+
+  // Hold the global lock while we open the file and create avcodec
+  // objects.
+  ReMutexHolder av_holder(_av_lock);
+
+  if (!_source->get_subfile_info().is_empty()) {
+    // Read a subfile.
+    if (!_ffvfile.open_subfile(_source->get_subfile_info())) {
+      movies_cat.info() 
+        << "Couldn't open " << _source->get_subfile_info() << "\n";
+      close_stream();
+      return false;
+    }
+
+  } else {
+    // Read a filename.
+    if (!_ffvfile.open_vfs(_filename)) {
+      movies_cat.info() 
+        << "Couldn't open " << _filename << "\n";
+      close_stream();
+      return false;
+    }
+  }
+
+  nassertr(_format_ctx == NULL, false);
+  _format_ctx = _ffvfile.get_format_context();
+  nassertr(_format_ctx != NULL, false);
+
+  if (av_find_stream_info(_format_ctx) < 0) {
+    movies_cat.info() 
+      << "Couldn't find stream info\n";
+    close_stream();
+    return false;
+  }
+  
+  // Find the video stream
+  nassertr(_video_ctx == NULL, false);
+  for (int i = 0; i < (int)_format_ctx->nb_streams; ++i) {
+    if (_format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
+      _video_index = i;
+      _video_ctx = _format_ctx->streams[i]->codec;
+      _video_timebase = av_q2d(_format_ctx->streams[i]->time_base);
+    }
+  }
+  
+  if (_video_ctx == NULL) {
+    movies_cat.info() 
+      << "Couldn't find video_ctx\n";
+    close_stream();
+    return false;
+  }
+
+  AVCodec *pVideoCodec = avcodec_find_decoder(_video_ctx->codec_id);
+  if (pVideoCodec == NULL) {
+    movies_cat.info() 
+      << "Couldn't find codec\n";
+    close_stream();
+    return false;
+  }
+  if (avcodec_open(_video_ctx, pVideoCodec) < 0) {
+    movies_cat.info() 
+      << "Couldn't open codec\n";
+    close_stream();
+    return false;
+  }
+  
+  _size_x = _video_ctx->width;
+  _size_y = _video_ctx->height;
+  _num_components = 3; // Don't know how to implement RGBA movies yet.
+  _length = (_format_ctx->duration * 1.0) / AV_TIME_BASE;
+  _can_seek = true;
+  _can_seek_fast = true;
+  _eof_reached = false;
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: FfmpegVideoCursor::close_stream
+//       Access: Private
+//  Description: Closes the stream, during cleanup or when needed
+//               internally.
+////////////////////////////////////////////////////////////////////
+void FfmpegVideoCursor::
+close_stream() {
+  // Hold the global lock while we free avcodec objects.
+  ReMutexHolder av_holder(_av_lock);
+  
+  if ((_video_ctx)&&(_video_ctx->codec)) {
+    avcodec_close(_video_ctx);
+  }
+  _video_ctx = NULL;
+  
+  _ffvfile.close();
+  _format_ctx = NULL;
+
+  _video_index = -1;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: FfmpegVideoCursor::cleanup
 //     Function: FfmpegVideoCursor::cleanup
 //       Access: Private
 //       Access: Private
@@ -477,10 +523,17 @@ release_buffer(Buffer *buffer) {
 void FfmpegVideoCursor::
 void FfmpegVideoCursor::
 cleanup() {
 cleanup() {
   stop_thread();
   stop_thread();
-  
-  // Hold the global lock while we free avcodec objects.
+  close_stream();
+
   ReMutexHolder av_holder(_av_lock);
   ReMutexHolder av_holder(_av_lock);
 
 
+#ifdef HAVE_SWSCALE
+  if (_convert_ctx != NULL) {
+    sws_freeContext(_convert_ctx);
+  }
+  _convert_ctx = NULL;
+#endif  // HAVE_SWSCALE
+
   if (_frame) {
   if (_frame) {
     av_free(_frame);
     av_free(_frame);
     _frame = NULL;
     _frame = NULL;
@@ -507,25 +560,6 @@ cleanup() {
     delete _packet1;
     delete _packet1;
     _packet1 = NULL;
     _packet1 = NULL;
   }
   }
-  
-#ifdef HAVE_SWSCALE
-  if (_convert_ctx != NULL) {
-    sws_freeContext(_convert_ctx);
-  }
-  _convert_ctx = NULL;
-#endif  // HAVE_SWSCALE
-  
-  if ((_video_ctx)&&(_video_ctx->codec)) {
-    avcodec_close(_video_ctx);
-  }
-  _video_ctx = NULL;
-  
-  if (_format_ctx) {
-    _ffvfile.close();
-    _format_ctx = NULL;
-  }
-
-  _video_index = -1;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -723,6 +757,11 @@ fetch_packet(double default_time) {
   }
   }
   _packet0->data = 0;
   _packet0->data = 0;
   _packet_time = default_time;
   _packet_time = default_time;
+  _eof_reached = false;
+  if (ffmpeg_cat.is_debug()) {
+    ffmpeg_cat.debug()
+      << "end of video\n";
+  }
   return true;
   return true;
 }
 }
 
 
@@ -744,7 +783,7 @@ flip_packets() {
 //  Description: Called within the sub-thread.  Slides forward until
 //  Description: Called within the sub-thread.  Slides forward until
 //               the indicated time, then fetches a frame from the
 //               the indicated time, then fetches a frame from the
 //               stream and stores it in the frame buffer.  Sets
 //               stream and stores it in the frame buffer.  Sets
-//               last_start and next_start to indicate the extents of
+//               _begin_time and _end_time to indicate the extents of
 //               the frame.  Returns true if the end of the video is
 //               the frame.  Returns true if the end of the video is
 //               reached.
 //               reached.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -754,7 +793,6 @@ fetch_frame(double time) {
   PStatTimer timer(fetch_buffer_pcollector);
   PStatTimer timer(fetch_buffer_pcollector);
 
 
   int finished = 0;
   int finished = 0;
-  _begin_time = _packet_time;
 
 
   if (_packet_time <= time) {
   if (_packet_time <= time) {
     _video_ctx->skip_frame = AVDISCARD_BIDIR;
     _video_ctx->skip_frame = AVDISCARD_BIDIR;
@@ -764,11 +802,8 @@ fetch_frame(double time) {
     
     
     // Get the next packet.  The first packet beyond the time we're
     // Get the next packet.  The first packet beyond the time we're
     // looking for marks the point to stop.
     // looking for marks the point to stop.
+    _begin_time = _packet_time;
     if (fetch_packet(time)) {
     if (fetch_packet(time)) {
-      if (ffmpeg_cat.is_debug()) {
-        ffmpeg_cat.debug()
-          << "end of video\n";
-      }
       _frame_ready = false;
       _frame_ready = false;
       return true;
       return true;
     }
     }
@@ -784,11 +819,8 @@ fetch_frame(double time) {
       avcodec_decode_video2(_video_ctx, _frame, &finished, _packet1);
       avcodec_decode_video2(_video_ctx, _frame, &finished, _packet1);
 #endif
 #endif
       flip_packets();
       flip_packets();
+      _begin_time = _packet_time;
       if (fetch_packet(time)) {
       if (fetch_packet(time)) {
-        if (ffmpeg_cat.is_debug()) {
-          ffmpeg_cat.debug()
-            << "end of video\n";
-        }
         _frame_ready = false;
         _frame_ready = false;
         return true;
         return true;
       }
       }
@@ -807,6 +839,7 @@ fetch_frame(double time) {
     
     
   } else {
   } else {
     // Just get the next frame.
     // Just get the next frame.
+    _begin_time = _packet_time;
     finished = 0;
     finished = 0;
     while (!finished && _packet0->data) {
     while (!finished && _packet0->data) {
 #if LIBAVCODEC_VERSION_INT < 3414272
 #if LIBAVCODEC_VERSION_INT < 3414272
@@ -832,7 +865,7 @@ fetch_frame(double time) {
 //               to be less than or equal to the specified time.
 //               to be less than or equal to the specified time.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void FfmpegVideoCursor::
 void FfmpegVideoCursor::
-seek(double t) {
+seek(double t, bool backward) {
   static PStatCollector seek_pcollector("*:FFMPEG Video Decoding:Seek");
   static PStatCollector seek_pcollector("*:FFMPEG Video Decoding:Seek");
   PStatTimer timer(seek_pcollector);
   PStatTimer timer(seek_pcollector);
 
 
@@ -841,18 +874,30 @@ seek(double t) {
     // Attempts to seek before the first packet will fail.
     // Attempts to seek before the first packet will fail.
     target_ts = _initial_dts;
     target_ts = _initial_dts;
   }
   }
-  if (av_seek_frame(_format_ctx, _video_index, target_ts, AVSEEK_FLAG_BACKWARD) < 0) {
-    if (t >= _packet_time) {
-      return;
+  int flags = 0;
+  if (backward) {
+    flags = AVSEEK_FLAG_BACKWARD;
+    //reset_stream();
+  }
+
+  
+  if (av_seek_frame(_format_ctx, _video_index, target_ts, flags) < 0) {
+    if (ffmpeg_cat.is_debug()) {
+      ffmpeg_cat.debug()
+        << "Seek failure.\n";
     }
     }
-    movies_cat.error() << "Seek failure. Shutting down movie.\n";
-    cleanup();
-    _packet_time = t;
-    return;
+    reset_stream();
+    
+    av_seek_frame(_format_ctx, _video_index, target_ts, 0);
   }
   }
+
   {
   {
-    // Hold the global lock while we close and re-open the video
-    // stream.
+    // Close and re-open the codec (presumably to flush the queue).
+    // Actually, this causes the stream to fail in certain video
+    // files, and doesn't seem to have any useful benefit.  So screw
+    // it, and don't do this.
+
+    /*
     ReMutexHolder av_holder(_av_lock);
     ReMutexHolder av_holder(_av_lock);
 
 
     avcodec_close(_video_ctx);
     avcodec_close(_video_ctx);
@@ -866,15 +911,37 @@ seek(double t) {
       cleanup();
       cleanup();
       return;
       return;
     }
     }
+    */
   }
   }
 
 
-  fetch_packet(t);
+  fetch_packet(0);
   fetch_frame(-1);
   fetch_frame(-1);
-  /*
-  if (_packet_time > t) {
-    _packet_time = t;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: FfmpegVideoCursor::reset_stream
+//       Access: Private
+//  Description: Resets the stream to its initial, first-opened state
+//               by closing and re-opening it.
+////////////////////////////////////////////////////////////////////
+void FfmpegVideoCursor::
+reset_stream() {
+  if (ffmpeg_cat.is_debug()) {
+    ffmpeg_cat.debug()
+      << "Resetting ffmpeg stream.\n";
+  }
+
+  close_stream();
+  if (!open_stream()) {
+    ffmpeg_cat.error()
+      << "Stream error, invalidating movie.\n";
+    cleanup();
+    return;
   }
   }
-  */
+
+  fetch_packet(0.0);
+  _initial_dts = _packet0->dts;
+  fetch_frame(-1);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -890,21 +957,41 @@ fetch_time(double time) {
 
 
   if (time < _begin_time) {
   if (time < _begin_time) {
     // Time is in the past.
     // Time is in the past.
-    if (ffmpeg_cat.is_debug()) {
-      ffmpeg_cat.debug()
-        << "Seeking backward to " << time << " from " << _begin_time << "\n";
-    }
-    seek(time);
-    if (_packet_time > time) {
-      ffmpeg_cat.debug()
-        << "Not far enough back!\n";
-      seek(0);
+    if (_eof_reached) {
+      // Go ahead and reset the video when we back up after having
+      // reached the end.  This avoids potential probelsm with
+      // unseekable streams.
+      if (ffmpeg_cat.is_debug()) {
+        ffmpeg_cat.debug()
+          << "Resetting to " << time << " after eof\n";
+      }
+      reset_stream();
+
+    } else {
+      if (ffmpeg_cat.is_debug()) {
+        ffmpeg_cat.debug()
+          << "Seeking backward to " << time << " from " << _begin_time << "\n";
+      }
+      seek(time, true);
+      if (_begin_time > time) {
+        if (ffmpeg_cat.is_debug()) {
+          ffmpeg_cat.debug()
+            << "Ended up at " << _begin_time << ", not far enough back!\n";
+        }
+        reset_stream();
+        if (ffmpeg_cat.is_debug()) {
+          ffmpeg_cat.debug()
+            << "Reseek to 0, got " << _begin_time << "\n";
+        }
+      }
     }
     }
-    if (ffmpeg_cat.is_debug()) {
-      ffmpeg_cat.debug()
-        << "Correcting, sliding forward to " << time << " from " << _packet_time << "\n";
+    if (time < _begin_time) {
+      if (ffmpeg_cat.is_debug()) {
+        ffmpeg_cat.debug()
+          << "Now sliding forward to " << time << " from " << _begin_time << "\n";
+      }
+      fetch_frame(time);
     }
     }
-    fetch_frame(time);
 
 
   } else if (time < _end_time) {
   } else if (time < _end_time) {
     // Time is in the present: already have the frame.
     // Time is in the present: already have the frame.
@@ -917,7 +1004,7 @@ fetch_time(double time) {
     // Time is in the near future.
     // Time is in the near future.
     if (ffmpeg_cat.is_debug()) {
     if (ffmpeg_cat.is_debug()) {
       ffmpeg_cat.debug()
       ffmpeg_cat.debug()
-        << "Sliding forward to " << time << " from " << _packet_time << "\n";
+        << "Sliding forward to " << time << " from " << _begin_time << "\n";
     }
     }
     fetch_frame(time);
     fetch_frame(time);
 
 
@@ -934,25 +1021,27 @@ fetch_time(double time) {
       ffmpeg_cat.debug()
       ffmpeg_cat.debug()
         << "Jumping forward to " << time << " from " << _begin_time << "\n";
         << "Jumping forward to " << time << " from " << _begin_time << "\n";
     }
     }
-    double base = _packet_time;
-    seek(time);
-    if (_packet_time < base) {
-      _min_fseek += (base - _packet_time);
+    double base = _begin_time;
+    seek(time, false);
+    if (_begin_time < base) {
+      _min_fseek += (base - _begin_time);
       if (ffmpeg_cat.is_debug()) {
       if (ffmpeg_cat.is_debug()) {
         ffmpeg_cat.debug()
         ffmpeg_cat.debug()
           << "Wrong way!  Increasing _min_fseek to " << _min_fseek << "\n";
           << "Wrong way!  Increasing _min_fseek to " << _min_fseek << "\n";
       }
       }
     }
     }
-    if (ffmpeg_cat.is_debug()) {
-      ffmpeg_cat.debug()
-        << "Correcting, sliding forward to " << time << " from " << _packet_time << "\n";
+    if (time < _begin_time) {
+      if (ffmpeg_cat.is_debug()) {
+        ffmpeg_cat.debug()
+          << "Correcting, sliding forward to " << time << " from " << _begin_time << "\n";
+      }
+      fetch_frame(time);
     }
     }
-    fetch_frame(time);
   }
   }
 
 
   if (ffmpeg_cat.is_debug()) {
   if (ffmpeg_cat.is_debug()) {
     ffmpeg_cat.debug()
     ffmpeg_cat.debug()
-      << "Wanted " << time << ", got " << _packet_time << "\n";
+      << "Wanted " << time << ", got " << _begin_time << "\n";
   }
   }
 }
 }
 
 
@@ -969,6 +1058,11 @@ export_frame(MovieVideoCursor::Buffer *buffer) {
 
 
   if (!_frame_ready) {
   if (!_frame_ready) {
     // No frame data ready, just fill with black.
     // No frame data ready, just fill with black.
+    if (ffmpeg_cat.is_debug()) {
+      ffmpeg_cat.debug()
+        << "ffmpeg for " << _filename.get_basename()
+        << ", no frame available.\n";
+    }
     memset(buffer->_block, 0, buffer->_block_size);
     memset(buffer->_block, 0, buffer->_block_size);
     return;
     return;
   }
   }

+ 5 - 1
panda/src/movies/ffmpegVideoCursor.h

@@ -64,6 +64,8 @@ public:
   virtual void release_buffer(Buffer *buffer);
   virtual void release_buffer(Buffer *buffer);
 
 
 private:
 private:
+  bool open_stream();
+  void close_stream();
   void cleanup();
   void cleanup();
 
 
   Filename _filename;
   Filename _filename;
@@ -109,8 +111,9 @@ private:
   bool fetch_packet(double default_time);
   bool fetch_packet(double default_time);
   void flip_packets();
   void flip_packets();
   bool fetch_frame(double time);
   bool fetch_frame(double time);
-  void seek(double t);
+  void seek(double t, bool backward);
   void fetch_time(double time);
   void fetch_time(double time);
+  void reset_stream();
   void export_frame(Buffer *buffer);
   void export_frame(Buffer *buffer);
 
 
   // The following data members will be accessed by the sub-thread.
   // The following data members will be accessed by the sub-thread.
@@ -130,6 +133,7 @@ private:
   double _begin_time;
   double _begin_time;
   double _end_time;
   double _end_time;
   bool _frame_ready;
   bool _frame_ready;
+  bool _eof_reached;
   
   
 public:
 public:
   static void register_with_read_factory();
   static void register_with_read_factory();

+ 11 - 0
panda/src/movies/ffmpegVirtualFile.I

@@ -13,6 +13,17 @@
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 
 
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: FfmpegVirtualFile::is_open
+//       Access: Public
+//  Description: Returns true if the stream is successfully opened,
+//               false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool FfmpegVirtualFile::
+is_open() const {
+  return (_format_context != NULL);
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: FfmpegVirtualFile::get_format_context
 //     Function: FfmpegVirtualFile::get_format_context
 //       Access: Public
 //       Access: Public

+ 1 - 0
panda/src/movies/ffmpegVirtualFile.h

@@ -50,6 +50,7 @@ public:
   bool open_subfile(const SubfileInfo &info);
   bool open_subfile(const SubfileInfo &info);
   void close();
   void close();
 
 
+  INLINE bool is_open() const;
   INLINE AVFormatContext *get_format_context() const;
   INLINE AVFormatContext *get_format_context() const;
 
 
   static void register_protocol();
   static void register_protocol();