Browse Source

support threaded ffmpeg decoding, fix a couple of ffmpeg bugs

David Rose 14 years ago
parent
commit
82e1b6703a

+ 28 - 0
panda/src/grutil/movieTexture.I

@@ -52,3 +52,31 @@ get_video_height() const {
   return cdata->_video_height;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: MovieTexture::get_color_cursor
+//       Access: Published
+//  Description: Returns the MovieVideoCursor that is feeding the
+//               color channels for the indicated page, where 0 <=
+//               page < get_num_pages().
+////////////////////////////////////////////////////////////////////
+INLINE MovieVideoCursor *MovieTexture::
+get_color_cursor(int page) {
+  CDReader cdata(_cycler);
+  nassertr(page >= 0 && page < (int)cdata->_pages.size(), NULL);
+  return cdata->_pages[page]._color;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MovieTexture::get_alpha_cursor
+//       Access: Published
+//  Description: Returns the MovieVideoCursor that is feeding the
+//               alpha channel for the indicated page, where 0 <=
+//               page < get_num_pages().
+////////////////////////////////////////////////////////////////////
+INLINE MovieVideoCursor *MovieTexture::
+get_alpha_cursor(int page) {
+  CDReader cdata(_cycler);
+  nassertr(page >= 0 && page < (int)cdata->_pages.size(), NULL);
+  return cdata->_pages[page]._alpha;
+}
+

+ 3 - 0
panda/src/grutil/movieTexture.h

@@ -43,6 +43,9 @@ PUBLISHED:
   INLINE double get_video_length() const;
   INLINE int get_video_width() const;
   INLINE int get_video_height() const;
+
+  INLINE MovieVideoCursor *get_color_cursor(int page);
+  INLINE MovieVideoCursor *get_alpha_cursor(int page);
   
   void   restart();
   void   stop();

+ 6 - 0
panda/src/movies/config_movies.cxx

@@ -44,6 +44,12 @@ ConfigureFn(config_movies) {
   init_libmovies();
 }
 
+ConfigVariableInt ffmpeg_max_readahead_frames
+("ffmpeg-max-readahead-frames", 10,
+ PRC_DESC("The maximum number of frames ahead which an ffmpeg decoder thread "
+          "should read in advance of actual playback.  Set this to 0 to "
+          "decode ffmpeg videos in the main thread."));
+
 ////////////////////////////////////////////////////////////////////
 //     Function: init_libmovies
 //  Description: Initializes the library.  This must be called at

+ 2 - 0
panda/src/movies/config_movies.h

@@ -25,6 +25,8 @@ ConfigureDecl(config_movies, EXPCL_PANDA_MOVIES, EXPTP_PANDA_MOVIES);
 NotifyCategoryDecl(movies, EXPCL_PANDA_MOVIES, EXPTP_PANDA_MOVIES);
 NotifyCategoryDecl(ffmpeg, EXPCL_PANDA_MOVIES, EXPTP_PANDA_MOVIES);
 
+extern ConfigVariableInt ffmpeg_max_readahead_frames;
+
 extern EXPCL_PANDA_MOVIES void init_libmovies();
 
 #endif /* __CONFIG_MOVIES_H__ */

+ 572 - 174
panda/src/movies/ffmpegVideoCursor.cxx

@@ -26,21 +26,29 @@ extern "C" {
 #include "pStatCollector.h"
 #include "pStatTimer.h"
 
+Mutex FfmpegVideoCursor::_av_lock;
 TypeHandle FfmpegVideoCursor::_type_handle;
 
+
 #if LIBAVFORMAT_VERSION_MAJOR < 53
   #define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO
 #endif
 
 ////////////////////////////////////////////////////////////////////
 //     Function: FfmpegVideoCursor::Default Constructor
-//       Access: Protected
+//       Access: Private
 //  Description: This constructor is only used when reading from a bam
 //               file.
 ////////////////////////////////////////////////////////////////////
 FfmpegVideoCursor::
 FfmpegVideoCursor() :
-  _packet(NULL),
+  _max_readahead_frames(0),
+  _lock("FfmpegVideoCursor::_lock"),
+  _action_cvar(_lock),
+  _thread_status(TS_stopped),
+  _seek_time(0.0),
+  _packet0(NULL),
+  _packet1(NULL),
   _format_ctx(NULL),
   _video_ctx(NULL),
   _video_index(-1),
@@ -52,13 +60,14 @@ FfmpegVideoCursor() :
 
 ////////////////////////////////////////////////////////////////////
 //     Function: FfmpegVideoCursor::init_from
-//       Access: Protected
+//       Access: Private
 //  Description: Specifies the source of the video cursor.  This is
 //               normally called only by the constructor or when
 //               reading from a bam file.
 ////////////////////////////////////////////////////////////////////
 void FfmpegVideoCursor::
 init_from(FfmpegVideo *source) {
+  nassertv(_thread == NULL && _thread_status == TS_stopped);
   nassertv(source != NULL);
   _source = source;
   _filename = _source->get_filename();
@@ -108,18 +117,21 @@ init_from(FfmpegVideo *source) {
     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;
+  {
+    MutexHolder av_holder(_av_lock);
+    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;
@@ -129,30 +141,42 @@ init_from(FfmpegVideo *source) {
   _can_seek = true;
   _can_seek_fast = true;
 
-  _packet = new AVPacket;
   _frame = avcodec_alloc_frame();
   _frame_out = avcodec_alloc_frame();
-  if ((_packet == 0)||(_frame == 0)||(_frame_out == 0)) {
+  if ((_frame == 0)||(_frame_out == 0)) {
     cleanup();
     return;
   }
-  memset(_packet, 0, sizeof(AVPacket));
+  _packet0 = new AVPacket;
+  _packet1 = new AVPacket;
+  memset(_packet0, 0, sizeof(AVPacket));
+  memset(_packet1, 0, sizeof(AVPacket));
   
   fetch_packet(0.0);
-  _initial_dts = _packet->dts;
+  _initial_dts = _packet0->dts;
   _packet_time = 0.0;
-  _last_start = -1.0;
-  _next_start = 0.0;
+  _begin_time = -1.0;
+  _end_time = 0.0;
+
+#ifdef HAVE_THREADS
+  set_max_readahead_frames(ffmpeg_max_readahead_frames);
+#endif  // HAVE_THREADS
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: FfmpegVideoCursor::Constructor
-//       Access: Public
+//       Access: Published
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 FfmpegVideoCursor::
-FfmpegVideoCursor(FfmpegVideo *src) :
-  _packet(NULL),
+FfmpegVideoCursor(FfmpegVideo *src) : 
+  _max_readahead_frames(0),
+  _lock("FfmpegVideoCursor::_lock"),
+  _action_cvar(_lock),
+  _thread_status(TS_stopped),
+  _seek_time(0.0),
+  _packet0(NULL),
+  _packet1(NULL),
   _format_ctx(NULL),
   _video_ctx(NULL),
   _video_index(-1),
@@ -165,21 +189,246 @@ FfmpegVideoCursor(FfmpegVideo *src) :
 
 ////////////////////////////////////////////////////////////////////
 //     Function: FfmpegVideoCursor::Destructor
-//       Access: Public
-//  Description: xxx
+//       Access: Published
+//  Description: 
 ////////////////////////////////////////////////////////////////////
 FfmpegVideoCursor::
 ~FfmpegVideoCursor() {
   cleanup();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: FfmpegVideoCursor::set_max_readahead_frames
+//       Access: Published
+//  Description: Specifies the maximum number of frames that a
+//               sub-thread will attempt to read ahead of the current
+//               frame.  Setting this to a nonzero allows the video
+//               decoding to take place in a sub-thread, which
+//               smoothes out the video decoding time by spreading it
+//               evenly over several frames.  Set this number larger
+//               to increase the buffer between the currently visible
+//               frame and the first undecoded frame; set it smaller
+//               to reduce memory consumption.
+//
+//               Setting this to zero forces the video to be decoded
+//               in the main thread.  If threading is not available in
+//               the Panda build, this value is always zero.
+////////////////////////////////////////////////////////////////////
+void FfmpegVideoCursor::
+set_max_readahead_frames(int max_readahead_frames) {
+#ifndef HAVE_THREADS
+  if (max_readahead_frames > 0) {
+    ffmpeg_cat.warning()
+      << "Couldn't set max_readahead_frames to " << max_readahead_frames
+      << ": threading not available.\n";
+    max_readahead_frames = 0;
+  }
+#endif  // HAVE_THREADS
+
+  _max_readahead_frames = max_readahead_frames;
+  if (_max_readahead_frames > 0) {
+    if (_thread_status == TS_stopped) {
+      start_thread();
+    }
+  } else {
+    if (_thread_status != TS_stopped) {
+      stop_thread();
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: FfmpegVideoCursor::get_max_readahead_frames
+//       Access: Published
+//  Description: Returns the maximum number of frames that a
+//               sub-thread will attempt to read ahead of the current
+//               frame.  See set_max_readahead_frames().
+////////////////////////////////////////////////////////////////////
+int FfmpegVideoCursor::
+get_max_readahead_frames() const {
+  return _max_readahead_frames;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: FfmpegVideoCursor::start_thread
+//       Access: Published
+//  Description: Explicitly starts the ffmpeg decoding thread after it
+//               has been stopped by a call to stop_thread().  The
+//               thread is normally started automatically, so there is
+//               no need to call this method unless you have
+//               previously called stop_thread() for some reason.
+////////////////////////////////////////////////////////////////////
+void FfmpegVideoCursor::
+start_thread() {
+  MutexHolder holder(_lock);
+
+  if (_thread_status == TS_stopped && _max_readahead_frames > 0) {
+    // Get a unique name for the thread's sync name.
+    ostringstream strm;
+    strm << (void *)this;
+    _sync_name = strm.str();
+
+    // Create and start the thread object.
+    _thread_status = TS_wait;
+    _thread = new GenericThread(_filename.get_basename(), _sync_name, st_thread_main, this);
+    if (!_thread->start(TP_normal, true)) {
+      // Couldn't start the thread.
+      _thread = NULL;
+      _thread_status = TS_stopped;
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: FfmpegVideoCursor::stop_thread
+//       Access: Published
+//  Description: Explicitly stops the ffmpeg decoding thread.  There
+//               is normally no reason to do this unless you want to
+//               maintain precise control over what threads are
+//               consuming CPU resources.  Calling this method will
+//               make the video update in the main thread, regardless
+//               of the setting of max_readahead_frames, until you
+//               call start_thread() again.
+////////////////////////////////////////////////////////////////////
+void FfmpegVideoCursor::
+stop_thread() {
+  if (_thread_status != TS_stopped) {
+    PT(GenericThread) thread = _thread;
+    {
+      MutexHolder holder(_lock);
+      if (_thread_status != TS_stopped) {
+        _thread_status = TS_shutdown;
+      }
+      _action_cvar.notify();
+      _thread = NULL;
+    }
+
+    // Now that we've released the lock, we can join the thread.
+    thread->join();
+  }
+
+  // This is a good time to clean up all of the allocated frame
+  // objects.  It's not really necessary to be holding the lock, since
+  // the thread is gone, but we'll grab it anyway just in case someone
+  // else starts the thread up again.
+  MutexHolder holder(_lock);
+
+  Buffers::iterator bi;
+  for (bi = _readahead_frames.begin(); bi != _readahead_frames.end(); ++bi) {
+    internal_free_buffer(*bi);
+  }
+  _readahead_frames.clear();
+  for (bi = _recycled_frames.begin(); bi != _recycled_frames.end(); ++bi) {
+    internal_free_buffer(*bi);
+  }
+  _recycled_frames.clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: FfmpegVideoCursor::is_thread_started
+//       Access: Published
+//  Description: Returns true if the thread has been started, false if
+//               not.  This will always return false if
+//               max_readahead_frames is 0.
+////////////////////////////////////////////////////////////////////
+bool FfmpegVideoCursor::
+is_thread_started() const {
+  return (_thread_status != TS_stopped);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: FfmpegVideoCursor::fetch_buffer
+//       Access: Public, Virtual
+//  Description: See MovieVideoCursor::fetch_buffer.
+////////////////////////////////////////////////////////////////////
+MovieVideoCursor::Buffer *FfmpegVideoCursor::
+fetch_buffer(double time) {
+  MutexHolder holder(_lock);
+  
+  // If there was an error at any point, just return NULL.
+  if (_format_ctx == (AVFormatContext *)NULL) {
+    return NULL;
+  }
+
+  Buffer *frame = NULL;
+  if (_thread_status == TS_stopped) {
+    // Non-threaded case.  Just get the next frame directly.
+    frame = do_alloc_frame();
+    
+    fetch_time(time);
+    export_frame(frame);
+
+  } else {
+    // Threaded case.  Wait for the thread to serve up the required
+    // frames.
+    if (!_readahead_frames.empty()) {
+      frame = _readahead_frames.front();
+      _readahead_frames.pop_front();
+      _action_cvar.notify();
+      while (frame->_end_time < time && !_readahead_frames.empty()) {
+        // This frame is too old.  Discard it.
+        if (ffmpeg_cat.is_debug()) {
+          ffmpeg_cat.debug()
+            << "ffmpeg for " << _filename.get_basename()
+            << " at time " << time << ", discarding frame at "
+            << frame->_begin_time << "\n";
+        }
+        do_recycle_frame(frame);
+        frame = _readahead_frames.front();
+        _readahead_frames.pop_front();
+      }
+      if (frame->_begin_time > time) {
+        // This frame is too new.  Empty all remaining frames and seek
+        // backwards.
+        do_recycle_all_frames();
+        if (_thread_status == TS_wait || _thread_status == TS_seek || _thread_status == TS_readahead) {
+          _thread_status = TS_seek;
+          _seek_time = time;
+          _action_cvar.notify();
+        }
+      }
+    }
+    if (frame == NULL || frame->_end_time < time) {
+      // No frame available, or the frame is too old.  Seek.
+      if (_thread_status == TS_wait || _thread_status == TS_seek || _thread_status == TS_readahead) {
+        _thread_status = TS_seek;
+        _seek_time = time;
+        _action_cvar.notify();
+      }
+    }
+  }
+
+  if (frame != NULL && (frame->_end_time < time || frame->_begin_time > time)) {
+    // The frame is too old or too new.  Just recycle it.
+    do_recycle_frame(frame);
+    frame = NULL;
+  }
+  
+  return frame;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: FfmpegVideoCursor::release_buffer
+//       Access: Public, Virtual
+//  Description: Should be called after processing the Buffer object
+//               returned by fetch_buffer(), this releases the Buffer
+//               for future use again.
+////////////////////////////////////////////////////////////////////
+void FfmpegVideoCursor::
+release_buffer(Buffer *buffer) {
+  MutexHolder holder(_lock);
+  do_recycle_frame(buffer);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: FfmpegVideoCursor::cleanup
-//       Access: Public
+//       Access: Private
 //  Description: Reset to a standard inactive state.
 ////////////////////////////////////////////////////////////////////
 void FfmpegVideoCursor::
 cleanup() {
+  stop_thread();
+
   if (_frame) {
     av_free(_frame);
     _frame = NULL;
@@ -191,15 +440,24 @@ cleanup() {
     _frame_out = NULL;
   }
 
-  if (_packet) {
-    if (_packet->data) {
-      av_free_packet(_packet);
+  if (_packet0) {
+    if (_packet0->data) {
+      av_free_packet(_packet0);
+    }
+    delete _packet0;
+    _packet0 = NULL;
+  }
+
+  if (_packet1) {
+    if (_packet1->data) {
+      av_free_packet(_packet1);
     }
-    delete _packet;
-    _packet = NULL;
+    delete _packet1;
+    _packet1 = NULL;
   }
 
   if ((_video_ctx)&&(_video_ctx->codec)) {
+    MutexHolder av_holder(_av_lock);
     avcodec_close(_video_ctx);
   }
   _video_ctx = NULL;
@@ -213,101 +471,245 @@ cleanup() {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: FfmpegVideoCursor::export_frame
-//       Access: Public, Virtual
-//  Description: Exports the contents of the frame buffer into the
-//               user's target buffer.
+//     Function: FfmpegVideoCursor::st_thread_main
+//       Access: Private, Static
+//  Description: The thread main function, static version (for passing
+//               to GenericThread).
 ////////////////////////////////////////////////////////////////////
-static PStatCollector export_frame_collector("*:FFMPEG Convert Video to BGR");
 void FfmpegVideoCursor::
-export_frame(unsigned char *data, bool bgra, int bufx) {
-  PStatTimer timer(export_frame_collector);
-  if (bgra) {
-    _frame_out->data[0] = data + ((_size_y - 1) * bufx * 4);
-    _frame_out->linesize[0] = bufx * -4;
-#ifdef HAVE_SWSCALE
-    struct SwsContext *convert_ctx = sws_getContext(_size_x, _size_y,
-                               _video_ctx->pix_fmt, _size_x, _size_y,
-                               PIX_FMT_BGRA, SWS_FAST_BILINEAR, NULL, NULL, NULL);
-    nassertv(convert_ctx != NULL);
-    sws_scale(convert_ctx, _frame->data, _frame->linesize,
-              0, _size_y, _frame_out->data, _frame_out->linesize);
-    sws_freeContext(convert_ctx);
-#else
-    img_convert((AVPicture *)_frame_out, PIX_FMT_BGRA, 
-                (AVPicture *)_frame, _video_ctx->pix_fmt, _size_x, _size_y);
-#endif
-  } else {
-    _frame_out->data[0] = data + ((_size_y - 1) * bufx * 3);
-    _frame_out->linesize[0] = bufx * -3;
-#ifdef HAVE_SWSCALE
-    struct SwsContext *convert_ctx = sws_getContext(_size_x, _size_y,
-                               _video_ctx->pix_fmt, _size_x, _size_y,
-                               PIX_FMT_BGR24, SWS_FAST_BILINEAR, NULL, NULL, NULL);
-    nassertv(convert_ctx != NULL);
-    sws_scale(convert_ctx, _frame->data, _frame->linesize,
-              0, _size_y, _frame_out->data, _frame_out->linesize);
-    sws_freeContext(convert_ctx);
-#else
-    img_convert((AVPicture *)_frame_out, PIX_FMT_BGR24, 
-                (AVPicture *)_frame, _video_ctx->pix_fmt, _size_x, _size_y);
-#endif
+st_thread_main(void *self) {
+  ((FfmpegVideoCursor *)self)->thread_main();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: FfmpegVideoCursor::thread_main
+//       Access: Private
+//  Description: The thread main function.
+////////////////////////////////////////////////////////////////////
+void FfmpegVideoCursor::
+thread_main() {
+  MutexHolder holder(_lock);
+  if (ffmpeg_cat.is_debug()) {
+    ffmpeg_cat.debug()
+      << "ffmpeg thread for " << _filename.get_basename() << " starting.\n";
+  }
+  
+  // Repeatedly wait for something interesting to do, until we're told
+  // to shut down.
+  while (_thread_status != TS_shutdown) {
+    nassertv(_thread_status != TS_stopped);
+    _action_cvar.wait();
+
+    while (do_poll()) {
+      // Keep doing stuff as long as there's something to do.
+      PStatClient::thread_tick(_sync_name);
+      Thread::consider_yield();
+    }
+  }
+
+  _thread_status = TS_stopped;
+  if (ffmpeg_cat.is_debug()) {
+    ffmpeg_cat.debug()
+      << "ffmpeg thread for " << _filename.get_basename() << " stopped.\n";
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: FfmpegVideoCursor::do_poll
+//       Access: Private
+//  Description: Called within the sub-thread.  Assumes the lock is
+//               already held.  If there is something for the thread
+//               to do, does it and returns true.  If there is nothing
+//               for the thread to do, returns false.
+////////////////////////////////////////////////////////////////////
+bool FfmpegVideoCursor::
+do_poll() {
+  switch (_thread_status) {
+  case TS_stopped:
+  case TS_seeking:
+    // This shouldn't be possible while the thread is running.
+    nassertr(false, false);
+    return false;
+    
+  case TS_wait:
+    // The video hasn't started playing yet.
+    return false;
+
+  case TS_readahead:
+    if ((int)_readahead_frames.size() < _max_readahead_frames) {
+      // Time to read the next frame.
+      Buffer *frame = do_alloc_frame();
+      nassertr(frame != NULL, false);
+      _lock.release();
+      fetch_frame(-1);
+      export_frame(frame);
+      _lock.acquire();
+      _readahead_frames.push_back(frame);
+      return true;
+    }
+
+    // No room for the next frame yet.  Wait for more.
+    return false;
+
+  case TS_seek:
+    // Seek to a specific time.
+    {
+      double seek_time = _seek_time;
+      _thread_status = TS_seeking;
+      Buffer *frame = do_alloc_frame();
+      nassertr(frame != NULL, false);
+      _lock.release();
+      fetch_time(seek_time);
+      export_frame(frame);
+      _lock.acquire();
+      do_recycle_all_frames();
+      _readahead_frames.push_back(frame);
+      if (_thread_status == TS_seeking) {
+        // After seeking, we automatically transition to readahead.
+        _thread_status = TS_readahead;
+      }
+    }
+    return true;
+
+  case TS_shutdown:
+    // Time to stop the thread.
+    return false;
+  }
+
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: FfmpegVideoCursor::do_alloc_frame
+//       Access: Private
+//  Description: Allocates a new Buffer object, or returns a
+//               previously-recycled object.  Assumes the lock is
+//               held.
+////////////////////////////////////////////////////////////////////
+MovieVideoCursor::Buffer *FfmpegVideoCursor::
+do_alloc_frame() {
+  if (!_recycled_frames.empty()) {
+    Buffer *frame = _recycled_frames.front();
+    _recycled_frames.pop_front();
+    return frame;
+  }
+  return internal_alloc_buffer();
+}
+ 
+////////////////////////////////////////////////////////////////////
+//     Function: FfmpegVideoCursor::do_recycle_frame
+//       Access: Private
+//  Description: Recycles a previously-allocated Buffer object for
+//               future reuse.  Assumes the lock is held.
+////////////////////////////////////////////////////////////////////
+void FfmpegVideoCursor::
+do_recycle_frame(Buffer *frame) {
+  _recycled_frames.push_back(frame);
+}
+ 
+////////////////////////////////////////////////////////////////////
+//     Function: FfmpegVideoCursor::do_recycle_all_frames
+//       Access: Private
+//  Description: Empties the entire readahead_frames queue into the
+//               recycle bin.  Assumes the lock is held.
+////////////////////////////////////////////////////////////////////
+void FfmpegVideoCursor::
+do_recycle_all_frames() {
+  while (!_readahead_frames.empty()) {
+    Buffer *frame = _readahead_frames.front();
+    _readahead_frames.pop_front();
+    if (ffmpeg_cat.is_debug()) {
+      ffmpeg_cat.debug()
+        << "ffmpeg for " << _filename.get_basename()
+        << " recycling frame at " << frame->_begin_time << "\n";
+    }
+    _recycled_frames.push_back(frame);
   }
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: FfmpegVideoCursor::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.  Returns true on
+//       Access: Private
+//  Description: Called within the sub-thread.  Fetches a video packet
+//               and stores it in the packet0 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.  Returns true on
 //               failure (such as the end of the video), or false on
 //               success.
 ////////////////////////////////////////////////////////////////////
 bool FfmpegVideoCursor::
 fetch_packet(double default_time) {
-  if (_packet->data) {
-    av_free_packet(_packet);
+  if (_packet0->data) {
+    av_free_packet(_packet0);
   }
-  while (av_read_frame(_format_ctx, _packet) >= 0) {
-    if (_packet->stream_index == _video_index) {
-      _packet_time = _packet->dts * _video_timebase;
+  while (av_read_frame(_format_ctx, _packet0) >= 0) {
+    if (_packet0->stream_index == _video_index) {
+      _packet_time = _packet0->dts * _video_timebase;
       return false;
     }
-    av_free_packet(_packet);
+    av_free_packet(_packet0);
   }
-  _packet->data = 0;
+  _packet0->data = 0;
   _packet_time = default_time;
   return true;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: FfmpegVideoCursor::flip_packets
+//       Access: Private
+//  Description: Called within the sub-thread.  Reverses _packet0 and _packet1.
+////////////////////////////////////////////////////////////////////
+void FfmpegVideoCursor::
+flip_packets() {
+  AVPacket *t = _packet0;
+  _packet0 = _packet1;
+  _packet1 = t;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: FfmpegVideoCursor::fetch_frame
-//       Access: Protected
-//  Description: Slides forward until the indicated time, then 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.  Returns true if the end of
-//               the video is reached.
+//       Access: Private
+//  Description: Called within the sub-thread.  Slides forward until
+//               the indicated time, then 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.  Returns true if the end of the video is
+//               reached.
 ////////////////////////////////////////////////////////////////////
 bool FfmpegVideoCursor::
 fetch_frame(double time) {
+  static PStatCollector fetch_buffer_pcollector("*:FFMPEG Video Decoding:Fetch");
+  PStatTimer timer(fetch_buffer_pcollector);
+
   int finished = 0;
-  _last_start = _packet_time;
+  _begin_time = _packet_time;
 
   if (_packet_time <= time) {
     static PStatCollector seek_pcollector("*:FFMPEG Video Decoding:Seek");
     PStatTimer timer(seek_pcollector);
+
     _video_ctx->skip_frame = AVDISCARD_BIDIR;
+    // Put the current packet aside in case we discover it's the
+    // packet to keep.
+    flip_packets();
+    
+    // Get the next packet.  The first packet beyond the time we're
+    // looking for marks the point to stop.
+    if (fetch_packet(time)) {
+      ffmpeg_cat.debug()
+        << "end of video\n";
+      return true;
+    }
     while (_packet_time <= time) {
+      // Decode and discard the previous packet.
 #if LIBAVCODEC_VERSION_INT < 3414272
       avcodec_decode_video(_video_ctx, _frame,
-                           &finished, _packet->data, _packet->size);
+                           &finished, _packet1->data, _packet1->size);
 #else
-      avcodec_decode_video2(_video_ctx, _frame, &finished, _packet);
+      avcodec_decode_video2(_video_ctx, _frame, &finished, _packet1);
 #endif
+      flip_packets();
       if (fetch_packet(time)) {
         ffmpeg_cat.debug()
           << "end of video\n";
@@ -315,32 +717,48 @@ fetch_frame(double time) {
       }
     }
     _video_ctx->skip_frame = AVDISCARD_DEFAULT;
-  }
-    
-  finished = 0;
-  while (!finished && _packet->data) {
+
+    // At this point, _packet0 contains the *next* packet to be
+    // decoded next frame, and _packet1 contains the packet to decode
+    // for this frame.
 #if LIBAVCODEC_VERSION_INT < 3414272
     avcodec_decode_video(_video_ctx, _frame,
-                          &finished, _packet->data, _packet->size);
+                         &finished, _packet1->data, _packet1->size);
+#else
+    avcodec_decode_video2(_video_ctx, _frame, &finished, _packet1);
+#endif
+    
+  } else {
+    // Just get the next frame.
+    finished = 0;
+    while (!finished && _packet0->data) {
+#if LIBAVCODEC_VERSION_INT < 3414272
+      avcodec_decode_video(_video_ctx, _frame,
+                           &finished, _packet0->data, _packet0->size);
 #else
-    avcodec_decode_video2(_video_ctx, _frame, &finished, _packet);
+      avcodec_decode_video2(_video_ctx, _frame, &finished, _packet0);
 #endif
-    fetch_packet(_last_start + 1.0);
+      fetch_packet(_begin_time + 1.0);
+    }
   }
-  _next_start = _packet_time;
+
+  _end_time = _packet_time;
 
   return (finished != 0);
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: FfmpegVideoCursor::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.
+//       Access: Private
+//  Description: Called within the sub-thread. Seeks to a target
+//               location.  Afterward, the packet_time is guaranteed
+//               to be less than or equal to the specified time.
 ////////////////////////////////////////////////////////////////////
 void FfmpegVideoCursor::
 seek(double t) {
+  static PStatCollector seek_pcollector("*:FFMPEG Video Decoding:Seek");
+  PStatTimer timer(seek_pcollector);
+
   PN_int64 target_ts = (PN_int64)(t / _video_timebase);
   if (target_ts < (PN_int64)(_initial_dts)) {
     // Attempts to seek before the first packet will fail.
@@ -355,16 +773,22 @@ seek(double t) {
     _packet_time = t;
     return;
   }
-  avcodec_close(_video_ctx);
-  AVCodec *pVideoCodec=avcodec_find_decoder(_video_ctx->codec_id);
-  if(pVideoCodec == 0) {
-    cleanup();
-    return;
-  }
-  if(avcodec_open(_video_ctx, pVideoCodec)<0) {
-    cleanup();
-    return;
+  {
+    MutexHolder av_holder(_av_lock);
+
+    avcodec_close(_video_ctx);
+    AVCodec *pVideoCodec = avcodec_find_decoder(_video_ctx->codec_id);
+    if (pVideoCodec == 0) {
+      cleanup();
+      return;
+    }
+
+    if (avcodec_open(_video_ctx, pVideoCodec)<0) {
+      cleanup();
+      return;
+    }
   }
+
   fetch_packet(t);
   if (_packet_time > t) {
     _packet_time = t;
@@ -373,17 +797,20 @@ seek(double t) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: FfmpegVideoCursor::fetch_time
-//       Access: Public, Virtual
-//  Description: Advance until the specified time is in the 
-//               export buffer.
+//       Access: Private 
+//  Description: Called within the sub-thread.  Advance until the
+//               specified time is in the export buffer.
 ////////////////////////////////////////////////////////////////////
 void FfmpegVideoCursor::
 fetch_time(double time) {
-  if (time < _last_start) {
+  static PStatCollector fetch_buffer_pcollector("*:FFMPEG Video Decoding:Fetch");
+  PStatTimer timer(fetch_buffer_pcollector);
+
+  if (time < _begin_time) {
     // Time is in the past.
     if (ffmpeg_cat.is_debug()) {
       ffmpeg_cat.debug()
-        << "Seeking backward to " << time << " from " << _last_start << "\n";
+        << "Seeking backward to " << time << " from " << _begin_time << "\n";
     }
     seek(time);
     if (_packet_time > time) {
@@ -397,14 +824,14 @@ fetch_time(double time) {
     }
     fetch_frame(time);
 
-  } else if (time < _next_start) {
+  } else if (time < _end_time) {
     // Time is in the present: already have the frame.
     if (ffmpeg_cat.is_debug()) {
       ffmpeg_cat.debug()
         << "Currently have " << time << "\n";
     }
 
-  } else if (time < _next_start + _min_fseek) {
+  } else if (time < _end_time + _min_fseek) {
     // Time is in the near future.
     if (ffmpeg_cat.is_debug()) {
       ffmpeg_cat.debug()
@@ -423,7 +850,7 @@ fetch_time(double time) {
     
     if (ffmpeg_cat.is_debug()) {
       ffmpeg_cat.debug()
-        << "Jumping forward to " << time << " from " << _last_start << "\n";
+        << "Jumping forward to " << time << " from " << _begin_time << "\n";
     }
     double base = _packet_time;
     seek(time);
@@ -448,61 +875,32 @@ fetch_time(double time) {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: FfmpegVideoCursor::fetch_into_texture
-//       Access: Public, Virtual
-//  Description: See MovieVideoCursor::fetch_into_texture.
-////////////////////////////////////////////////////////////////////
-void FfmpegVideoCursor::
-fetch_into_texture(double time, Texture *t, int page) {
-  static PStatCollector fetch_into_texture_pcollector("*:FFMPEG Video Decoding:Fetch");
-  PStatTimer timer(fetch_into_texture_pcollector);
-  
-  nassertv(t->get_x_size() >= size_x());
-  nassertv(t->get_y_size() >= size_y());
-  nassertv((t->get_num_components() == 3) || (t->get_num_components() == 4));
-  nassertv(t->get_component_width() == 1);
-  nassertv(page < t->get_num_pages());
-  
-  PTA_uchar img = t->modify_ram_image();
-  
-  unsigned char *data = img.p() + page * t->get_expected_ram_page_size();
-  
-  // If there was an error at any point, synthesize black.
-  if (_format_ctx==0) {
-    if (data) {
-      memset(data,0,t->get_x_size() * t->get_y_size() * t->get_num_components());
-    }
-    _last_start = time;
-    _next_start = time + 1.0;
-    return;
-  }
-  
-  fetch_time(time);
-  export_frame(data, (t->get_num_components()==4), t->get_x_size());
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: FfmpegVideoCursor::fetch_into_buffer
-//       Access: Public, Virtual
-//  Description: See MovieVideoCursor::fetch_into_buffer.
+//     Function: FfmpegVideoCursor::export_frame
+//       Access: Private
+//  Description: Called within the sub-thread.  Exports the contents
+//               of the frame buffer into the indicated target buffer.
 ////////////////////////////////////////////////////////////////////
 void FfmpegVideoCursor::
-fetch_into_buffer(double time, unsigned char *data, bool bgra) {
-  static PStatCollector fetch_into_buffer_pcollector("*:FFMPEG Video Decoding:Fetch");
-  PStatTimer timer(fetch_into_buffer_pcollector);
-  
-  // If there was an error at any point, synthesize black.
-  if (_format_ctx==0) {
-    if (data) {
-      memset(data,0,size_x()*size_y()*(bgra?4:3));
-    }
-    _last_start = time;
-    _next_start = time + 1.0;
-    return;
-  }
+export_frame(MovieVideoCursor::Buffer *buffer) {
+  static PStatCollector export_frame_collector("*:FFMPEG Convert Video to BGR");
+  PStatTimer timer(export_frame_collector);
 
-  fetch_time(time);
-  export_frame(data, bgra, _size_x);
+  _frame_out->data[0] = buffer->_block + ((_size_y - 1) * _size_x * 3);
+  _frame_out->linesize[0] = _size_x * -3;
+  buffer->_begin_time = _begin_time;
+  buffer->_end_time = _end_time;
+#ifdef HAVE_SWSCALE
+  struct SwsContext *convert_ctx = sws_getContext(_size_x, _size_y,
+                                                  _video_ctx->pix_fmt, _size_x, _size_y,
+                                                  PIX_FMT_BGR24, SWS_FAST_BILINEAR, NULL, NULL, NULL);
+  nassertv(convert_ctx != NULL);
+  sws_scale(convert_ctx, _frame->data, _frame->linesize,
+            0, _size_y, _frame_out->data, _frame_out->linesize);
+  sws_freeContext(convert_ctx);
+#else
+  img_convert((AVPicture *)_frame_out, PIX_FMT_BGR24, 
+              (AVPicture *)_frame, _video_ctx->pix_fmt, _size_x, _size_y);
+#endif
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -549,7 +947,7 @@ finalize(BamReader *) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: FfmpegVideoCursor::make_from_bam
-//       Access: Protected, Static
+//       Access: Private, Static
 //  Description: This function is called by the BamReader's factory
 //               when a new object of type FfmpegVideo is encountered
 //               in the Bam file.  It should create the FfmpegVideo
@@ -569,7 +967,7 @@ make_from_bam(const FactoryParams &params) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: FfmpegVideoCursor::fillin
-//       Access: Protected
+//       Access: Private
 //  Description: This internal function is called by make_from_bam to
 //               read in all of the relevant data from the BamFile for
 //               the new FfmpegVideo.

+ 62 - 9
panda/src/movies/ffmpegVideoCursor.h

@@ -23,6 +23,9 @@
 #include "texture.h"
 #include "pointerTo.h"
 #include "ffmpegVirtualFile.h"
+#include "genericThread.h"
+#include "pmutex.h"
+#include "conditionVar.h"
 
 struct AVFormatContext;
 struct AVCodecContext;
@@ -35,28 +38,76 @@ struct AVFrame;
 // Description : 
 ////////////////////////////////////////////////////////////////////
 class EXPCL_PANDA_MOVIES FfmpegVideoCursor : public MovieVideoCursor {
-protected:
+private:
   FfmpegVideoCursor();
   void init_from(FfmpegVideo *src);
 
 PUBLISHED:
   FfmpegVideoCursor(FfmpegVideo *src);
   virtual ~FfmpegVideoCursor();
+
+  void set_max_readahead_frames(int max_readahead_frames);
+  int get_max_readahead_frames() const;
+
+  void start_thread();
+  BLOCKING void stop_thread();
+  bool is_thread_started() const;
   
 public:
-  virtual void fetch_into_texture(double time, Texture *t, int page);
-  virtual void fetch_into_buffer(double time, unsigned char *block, bool rgba);
+  virtual Buffer *fetch_buffer(double time);
+  virtual void release_buffer(Buffer *buffer);
+
+private:
+  void cleanup();
+
+  Filename _filename;
+  string _sync_name;
+  int _max_readahead_frames;
+  PT(GenericThread) _thread;
+
+  // This global Mutex protects calls to avcodec_open/close/etc.
+  static Mutex _av_lock;
+
+  // Protects _readahead_frames, _recycled_buffers, and all the
+  // immediately following members.
+  Mutex _lock;
+
+  // Condition: the thread has something to do.
+  ConditionVar _action_cvar;
+
+  typedef pdeque<Buffer *> Buffers;
+  Buffers _readahead_frames;
+  Buffers _recycled_frames;
+  enum ThreadStatus {
+    TS_stopped,
+    TS_wait,
+    TS_readahead,
+    TS_seek,
+    TS_seeking,
+    TS_shutdown,
+  };
+  ThreadStatus _thread_status;
+  double _seek_time;
+  
+private:
+  // The following functions will be called in the sub-thread.
+  static void st_thread_main(void *self);
+  void thread_main();
+  bool do_poll();
+
+  Buffer *do_alloc_frame();
+  void do_recycle_frame(Buffer *frame);
+  void do_recycle_all_frames();
 
-protected:
   bool fetch_packet(double default_time);
+  void flip_packets();
   bool fetch_frame(double time);
   void seek(double t);
   void fetch_time(double time);
-  void export_frame(unsigned char *data, bool bgra, int bufx);
-  void cleanup();
-  
-  Filename _filename;
-  AVPacket *_packet;
+  void export_frame(Buffer *buffer);
+
+  // The following data members will be accessed by the sub-thread.
+  AVPacket *_packet0, *_packet1;
   double _packet_time;
   AVFormatContext *_format_ctx;
   AVCodecContext *_video_ctx;
@@ -67,6 +118,8 @@ protected:
   AVFrame *_frame_out;
   int _initial_dts;
   double _min_fseek;
+  double _begin_time;
+  double _end_time;
   
 public:
   static void register_with_read_factory();

+ 13 - 11
panda/src/movies/inkblotVideoCursor.cxx

@@ -57,6 +57,7 @@ InkblotVideoCursor(InkblotVideo *src) :
 {
   _size_x = src->_specified_x;
   _size_y = src->_specified_y;
+  _num_components = 3;
   _fps = src->_specified_fps;
   int padx = _size_x + 2;
   int pady = _size_y + 2;
@@ -81,12 +82,13 @@ InkblotVideoCursor::
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: InkblotVideoCursor::fetch_into_buffer
+//     Function: InkblotVideoCursor::fetch_buffer
 //       Access: Published, Virtual
-//  Description: See MovieVideoCursor::fetch_into_buffer.
+//  Description: See MovieVideoCursor::fetch_buffer.
 ////////////////////////////////////////////////////////////////////
-void InkblotVideoCursor::
-fetch_into_buffer(double time, unsigned char *data, bool bgra) {
+MovieVideoCursor::Buffer *InkblotVideoCursor::
+fetch_buffer(double time) {
+  Buffer *buffer = get_standard_buffer();
 
   int padx = size_x() + 2;
   int pady = size_y() + 2;
@@ -100,7 +102,7 @@ fetch_into_buffer(double time, unsigned char *data, bool bgra) {
     _frames_read = 0;
   }
   
-  nassertv(time >= _next_start);
+  nassertr(time >= _next_start, NULL);
   
   while (_next_start <= time) {
     _last_start = (_frames_read * 1.0) / _fps;
@@ -126,6 +128,7 @@ fetch_into_buffer(double time, unsigned char *data, bool bgra) {
     _cells2 = t;
   }
 
+  unsigned char *data = buffer->_block;
   for (int y=1; y<pady - 1; y++) {
     for (int x=1; x<padx - 1; x++) {
       int val = _cells[x + y*padx];
@@ -135,13 +138,12 @@ fetch_into_buffer(double time, unsigned char *data, bool bgra) {
       data[0] = (c1.b * (16-lerp) + c2.b * lerp) / 16;
       data[1] = (c1.g * (16-lerp) + c2.g * lerp) / 16;
       data[2] = (c1.r * (16-lerp) + c2.r * lerp) / 16;
-      if (bgra) {
-        data[3] = 255;
-        data += 4;
-      } else {
-        data += 3;
-      }
+      data += 3;
     }
   }
+
+  buffer->_begin_time = _last_start;
+  buffer->_end_time = _next_start;
+  return buffer;
 }
 

+ 1 - 1
panda/src/movies/inkblotVideoCursor.h

@@ -32,7 +32,7 @@ class EXPCL_PANDA_MOVIES InkblotVideoCursor : public MovieVideoCursor {
   virtual ~InkblotVideoCursor();
   
  public:
-  virtual void fetch_into_buffer(double time, unsigned char *block, bool rgba);
+  virtual Buffer *fetch_buffer(double time);
   
  protected:
   unsigned char *_cells;

+ 1 - 1
panda/src/movies/microphoneAudioDS.cxx

@@ -338,7 +338,7 @@ MicrophoneAudioCursorDS::
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: MicrophoneAudioCursorDS::fetch_into_buffer
+//     Function: MicrophoneAudioCursorDS::read_samples
 //       Access: Published
 //  Description: 
 ////////////////////////////////////////////////////////////////////

+ 186 - 77
panda/src/movies/movieVideoCursor.cxx

@@ -30,7 +30,6 @@ TypeHandle MovieVideoCursor::_type_handle;
 ////////////////////////////////////////////////////////////////////
 MovieVideoCursor::
 MovieVideoCursor(MovieVideo *src) :
-  _conversion_buffer(0),
   _source(src),
   _size_x(1),
   _size_y(1),
@@ -44,6 +43,7 @@ MovieVideoCursor(MovieVideo *src) :
   _streaming(false),
   _ready(false)
 {
+  _standard_buffer._block = NULL;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -53,22 +53,8 @@ MovieVideoCursor(MovieVideo *src) :
 ////////////////////////////////////////////////////////////////////
 MovieVideoCursor::
 ~MovieVideoCursor() {
-  if (_conversion_buffer != 0) {
-    delete[] _conversion_buffer;
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: MovieVideoCursor::allocate_conversion_buffer
-//       Access: Private
-//  Description: The generic implementations of fetch_into_texture
-//               and fetch_into_alpha require the use of a conversion
-//               buffer.  This allocates the buffer.
-////////////////////////////////////////////////////////////////////
-void MovieVideoCursor::
-allocate_conversion_buffer() {
-  if (_conversion_buffer == 0) {
-    _conversion_buffer = new unsigned char[size_x() * size_y() * 4];
+  if (_standard_buffer._block != NULL) {
+    PANDA_FREE_ARRAY(_standard_buffer._block);
   }
 }
   
@@ -98,19 +84,23 @@ setup_texture(Texture *tex) const {
 //  Description: Discards the next video frame.  Still sets
 //               last_start and next_start.
 //
-//               See fetch_into_buffer for more details.
+//               See fetch_buffer for more details.
 ////////////////////////////////////////////////////////////////////
 void MovieVideoCursor::
 fetch_into_bitbucket(double time) {
 
-  // This generic implementation is layered on fetch_into_buffer.
+  // This generic implementation is layered on fetch_buffer.
   // It will work for any derived class, so it is never necessary to
   // redefine this.  It is probably possible to make a faster
   // implementation, but since this function is rarely used, it
   // probably isn't worth the trouble.
 
-  allocate_conversion_buffer();
-  fetch_into_buffer(time, _conversion_buffer, false);
+  Buffer *buffer = fetch_buffer(time);
+  if (buffer != NULL) {
+    _last_start = buffer->_begin_time;
+    _next_start = buffer->_end_time;
+    release_buffer(buffer);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -119,14 +109,14 @@ fetch_into_bitbucket(double time) {
 //  Description: Reads the specified video frame into 
 //               the specified texture.
 //
-//               See fetch_into_buffer for more details.
+//               See fetch_buffer for more details.
 ////////////////////////////////////////////////////////////////////
-static PStatCollector fetch_into_texture_collector("*:Decode Video into Texture");
 void MovieVideoCursor::
 fetch_into_texture(double time, Texture *t, int page) {
+  static PStatCollector fetch_into_texture_collector("*:Decode Video into Texture");
   PStatTimer timer(fetch_into_texture_collector);
 
-  // This generic implementation is layered on fetch_into_buffer.
+  // This generic implementation is layered on fetch_buffer.
   // It will work for any derived class, so it is never necessary to
   // redefine this.  However, it may be possible to make a faster
   // implementation that uses fewer intermediate copies, depending
@@ -136,26 +126,50 @@ fetch_into_texture(double time, Texture *t, int page) {
   nassertv(t->get_y_size() >= size_y());
   nassertv((t->get_num_components() == 3) || (t->get_num_components() == 4));
   nassertv(t->get_component_width() == 1);
-  nassertv(page < t->get_z_size());
+  nassertv(page < t->get_num_pages());
   
+  t->set_keep_ram_image(true);
   PTA_uchar img = t->modify_ram_image();
   
   unsigned char *data = img.p() + page * t->get_expected_ram_page_size();
 
-  if (t->get_x_size() == size_x()) {
-    fetch_into_buffer(time, data, t->get_num_components() == 4);
+  Buffer *buffer = fetch_buffer(time);
+  if (buffer == NULL) {
+    // No image available.
+    return;
+  }
+
+  _last_start = buffer->_begin_time;
+  _next_start = buffer->_end_time;
+
+  if (t->get_x_size() == size_x() && t->get_num_components() == get_num_components()) {
+    memcpy(data, buffer->_block, size_x() * size_y() * get_num_components());
+
   } else {
-    allocate_conversion_buffer();
-    fetch_into_buffer(time, _conversion_buffer, t->get_num_components() == 4);
-    int src_stride = size_x() * t->get_num_components();
-    int dst_stride = t->get_x_size() * t->get_num_components();
-    unsigned char *p = _conversion_buffer;
-    for (int y=0; y<size_y(); y++) {
-      memcpy(data, p, src_stride);
-      data += dst_stride;
-      p += src_stride;
+    unsigned char *p = buffer->_block;
+    if (t->get_num_components() == get_num_components()) {
+      int src_stride = size_x() * get_num_components();
+      int dst_stride = t->get_x_size() * t->get_num_components();
+      for (int y=0; y<size_y(); y++) {
+        memcpy(data, p, src_stride);
+        data += dst_stride;
+        p += src_stride;
+      }
+    } else {
+      int src_width = get_num_components();
+      int dst_width = t->get_num_components();
+      for (int y = 0; y < size_y(); ++y) {
+        for (int x = 0; x < size_x(); ++x) {
+          data[0] = p[0];
+          data[1] = p[1];
+          data[2] = p[2];
+          data += dst_width;
+          p += src_width;
+        }
+      }
     }
   }
+  release_buffer(buffer);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -165,12 +179,12 @@ fetch_into_texture(double time, Texture *t, int page) {
 //               the alpha channel of the supplied texture.  The
 //               RGB channels of the texture are not touched.
 //
-//               See fetch_into_buffer for more details.
+//               See fetch_buffer for more details.
 ////////////////////////////////////////////////////////////////////
 void MovieVideoCursor::
 fetch_into_texture_alpha(double time, Texture *t, int page, int alpha_src) {
 
-  // This generic implementation is layered on fetch_into_buffer.
+  // This generic implementation is layered on fetch_buffer.
   // It will work for any derived class, so it is never necessary to
   // redefine this.  However, it may be possible to make a faster
   // implementation that uses fewer intermediate copies, depending
@@ -181,20 +195,26 @@ fetch_into_texture_alpha(double time, Texture *t, int page, int alpha_src) {
   nassertv(t->get_num_components() == 4);
   nassertv(t->get_component_width() == 1);
   nassertv(page < t->get_z_size());
-  nassertv((alpha_src >= 0) && (alpha_src <= 4));
+  nassertv((alpha_src >= 0) && (alpha_src <= get_num_components()));
 
-  allocate_conversion_buffer();
-  
-  fetch_into_buffer(time, _conversion_buffer, true);
-  
+  Buffer *buffer = fetch_buffer(time);
+  if (buffer == NULL) {
+    // No image available.
+    return;
+  }
+
+  _last_start = buffer->_begin_time;
+  _next_start = buffer->_end_time;
+
+  t->set_keep_ram_image(true);
   PTA_uchar img = t->modify_ram_image();
   
   unsigned char *data = img.p() + page * t->get_expected_ram_page_size();
   
-  int src_stride = size_x() * 4;
+  int src_stride = size_x() * get_num_components();
   int dst_stride = t->get_x_size() * 4;
   if (alpha_src == 0) {
-    unsigned char *p = _conversion_buffer;
+    unsigned char *p = buffer->_block;
     for (int y=0; y<size_y(); y++) {
       for (int x=0; x<size_x(); x++) {
         data[x*4+3] = (p[x*4+0] + p[x*4+1] + p[x*4+2]) / 3;
@@ -204,15 +224,18 @@ fetch_into_texture_alpha(double time, Texture *t, int page, int alpha_src) {
     }
   } else {
     alpha_src -= 1;
-    unsigned char *p = _conversion_buffer;
+    unsigned char *p = buffer->_block;
+    int src_width = get_num_components();
     for (int y=0; y<size_y(); y++) {
       for (int x=0; x<size_x(); x++) {
-        data[x*4+3] = p[x*4+alpha_src];
+        data[x*4+3] = p[x *src_width + alpha_src];
       }
       data += dst_stride;
       p += src_stride;
     }
   }
+
+  release_buffer(buffer);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -222,12 +245,12 @@ fetch_into_texture_alpha(double time, Texture *t, int page, int alpha_src) {
 //               the RGB channels of the supplied texture.  The alpha
 //               channel of the texture is not touched.
 //
-//               See fetch_into_buffer for more details.
+//               See fetch_buffer for more details.
 ////////////////////////////////////////////////////////////////////
 void MovieVideoCursor::
 fetch_into_texture_rgb(double time, Texture *t, int page) {
 
-  // This generic implementation is layered on fetch_into_buffer.
+  // This generic implementation is layered on fetch_buffer.
   // It will work for any derived class, so it is never necessary to
   // redefine this.  However, it may be possible to make a faster
   // implementation that uses fewer intermediate copies, depending
@@ -239,34 +262,48 @@ fetch_into_texture_rgb(double time, Texture *t, int page) {
   nassertv(t->get_component_width() == 1);
   nassertv(page < t->get_z_size());
 
-  allocate_conversion_buffer();
-  
-  fetch_into_buffer(time, _conversion_buffer, true);
-  
+  Buffer *buffer = fetch_buffer(time);
+  if (buffer == NULL) {
+    // No image available.
+    return;
+  }
+
+  _last_start = buffer->_begin_time;
+  _next_start = buffer->_end_time;
+
+  t->set_keep_ram_image(true);
   PTA_uchar img = t->modify_ram_image();
   
   unsigned char *data = img.p() + page * t->get_expected_ram_page_size();
   
-  int src_stride = size_x() * 4;
+  int src_stride = size_x() * get_num_components();
+  int src_width = get_num_components();
   int dst_stride = t->get_x_size() * 4;
-  unsigned char *p = _conversion_buffer;
+  unsigned char *p = buffer->_block;
   for (int y=0; y<size_y(); y++) {
     for (int x=0; x<size_x(); x++) {
-      data[x*4+0] = p[x*4+0];
-      data[x*4+1] = p[x*4+1];
-      data[x*4+2] = p[x*4+2];
+      data[x * 4 + 0] = p[x * src_width + 0];
+      data[x * 4 + 1] = p[x * src_width + 1];
+      data[x * 4 + 2] = p[x * src_width + 2];
     }
     data += dst_stride;
     p += src_stride;
   }
+
+  release_buffer(buffer);
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: MovieVideoCursor::fetch_into_buffer
+//     Function: MovieVideoCursor::fetch_buffer
 //       Access: Published, Virtual
-//  Description: Reads the specified video frame into the supplied
-//               BGR or BGRA buffer.  The frame's begin and end
-//               times are stored in last_start and next_start.
+//  Description: Reads the specified video frame and returns it in a
+//               pre-allocated buffer.  The frame's begin and end
+//               times are stored in _begin_time and _end_time, within
+//               the buffer.  After you have copied the data from the
+//               buffer, you should call release_buffer() to make the
+//               space available again to populate the next frame.
+//               You may not call fetch_buffer() again until you have
+//               called release_buffer().
 //
 //               If the movie reports that it can_seek, you may
 //               also specify a timestamp less than next_start.
@@ -279,30 +316,102 @@ fetch_into_texture_rgb(double time, Texture *t, int page) {
 //               desired location.  Only if can_seek_fast returns
 //               true can it seek rapidly.
 ////////////////////////////////////////////////////////////////////
-void MovieVideoCursor::
-fetch_into_buffer(double time, unsigned char *data, bool bgra) {
-  
+MovieVideoCursor::Buffer *MovieVideoCursor::
+fetch_buffer(double time) {
+  Buffer *buffer = get_standard_buffer();
+
   // The following is the implementation of the null video stream, ie,
   // a stream of blinking red and blue frames.  This method must be
   // overridden by the subclass.
   
-  _last_start = floor(time);
-  _next_start = _last_start + 1;
+  buffer->_begin_time = floor(time);
+  buffer->_end_time = buffer->_begin_time + 1;
+  int flash = ((int)buffer->_begin_time) & 1;
 
-  if (((int)_last_start) & 1) {
-    data[0] = 255;
-    data[1] = 128;
-    data[2] = 128;
-  } else {
-    data[0] = 128;
-    data[1] = 128;
-    data[2] = 255;
+  unsigned char *p = buffer->_block;
+  int src_width = get_num_components();
+  for (int y = 0; y < size_y(); ++y) {
+    for (int x = 0; x < size_x(); ++x) {
+      if (flash) {
+        p[0] = 255;
+        p[1] = 128;
+        p[2] = 128;
+      } else {
+        p[0] = 128;
+        p[1] = 128;
+        p[2] = 255;
+      }
+      if (src_width == 4) {
+        p[3] = 255;
+      }
+      p += src_width;
+    }
   }
-  if (bgra) {
-    data[3] = 255;
+
+  return buffer;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MovieVideoCursor::release_buffer
+//       Access: Public, Virtual
+//  Description: Should be called after processing the Buffer object
+//               returned by fetch_buffer(), this releases the Buffer
+//               for future use again.
+////////////////////////////////////////////////////////////////////
+void MovieVideoCursor::
+release_buffer(Buffer *buffer) {
+  nassertv(buffer == &_standard_buffer);
+  nassertv(buffer->_begin_time == _last_start && buffer->_end_time == _next_start);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MovieVideoCursor::get_standard_buffer
+//       Access: Protected
+//  Description: May be called by a derived class to return a single
+//               standard Buffer object to easily implement
+//               fetch_buffer().  The default release_buffer()
+//               implementation assumes this method is used.
+////////////////////////////////////////////////////////////////////
+MovieVideoCursor::Buffer *MovieVideoCursor::
+get_standard_buffer() {
+  if (_standard_buffer._block == NULL) {
+    _standard_buffer._block_size = size_x() * size_y() * get_num_components();
+    _standard_buffer._block = (unsigned char *)PANDA_MALLOC_ARRAY(_standard_buffer._block_size);
+    _standard_buffer._begin_time = -1.0;
+    _standard_buffer._end_time = 0.0;
   }
+  return &_standard_buffer;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MovieVideoCursor::internal_alloc_buffer
+//       Access: Protected
+//  Description: May be called by a derived class to allocate a new
+//               Buffer object.  The caller is responsible for
+//               eventually passing this object to
+//               internal_free_buffer().
+////////////////////////////////////////////////////////////////////
+MovieVideoCursor::Buffer *MovieVideoCursor::
+internal_alloc_buffer() {
+  Buffer *buffer = new Buffer;
+  buffer->_block_size = size_x() * size_y() * get_num_components();
+  buffer->_block = (unsigned char *)PANDA_MALLOC_ARRAY(buffer->_block_size);
+  buffer->_begin_time = -1.0;
+  buffer->_end_time = 0.0;
+  return buffer;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: MovieVideoCursor::internal_free_buffer
+//       Access: Protected
+//  Description: Frees a Buffer object allocated via
+//               internal_alloc_buffer().
+////////////////////////////////////////////////////////////////////
+void MovieVideoCursor::
+internal_free_buffer(Buffer *buffer) {
+  PANDA_FREE_ARRAY(buffer->_block);
+  delete buffer;
+}
 
 ////////////////////////////////////////////////////////////////////
 //     Function: MovieVideoCursor::write_datagram

+ 16 - 4
panda/src/movies/movieVideoCursor.h

@@ -18,6 +18,7 @@
 #include "pandabase.h"
 #include "texture.h"
 #include "pointerTo.h"
+#include "memoryBase.h"
 
 class MovieVideo;
 class FactoryParams;
@@ -63,11 +64,20 @@ PUBLISHED:
   virtual void fetch_into_texture_alpha(double time, Texture *t, int page, int alpha_src);
 
 public:
-  virtual void fetch_into_buffer(double time, unsigned char *block, bool bgra);
+  class Buffer : public MemoryBase {
+  public:
+    unsigned char *_block;
+    size_t _block_size;
+    double _begin_time;
+    double _end_time;
+  };
+  virtual Buffer *fetch_buffer(double time);
+  virtual void release_buffer(Buffer *buffer);
   
-private:
-  void allocate_conversion_buffer();
-  unsigned char *_conversion_buffer;
+protected:
+  Buffer *get_standard_buffer();
+  Buffer *internal_alloc_buffer();
+  void internal_free_buffer(Buffer *buffer);
   
 protected:
   PT(MovieVideo) _source;
@@ -83,6 +93,8 @@ protected:
   bool _streaming;
   bool _ready;
 
+  Buffer _standard_buffer;
+
 public:
   virtual void write_datagram(BamWriter *manager, Datagram &dg);
   virtual int complete_pointers(TypedWritable **plist, BamReader *manager);

+ 3 - 0
panda/src/pipeline/Sources.pp

@@ -37,6 +37,7 @@
     cycleDataWriter.h cycleDataWriter.I \
     cyclerHolder.h cyclerHolder.I \
     externalThread.h \
+    genericThread.h genericThread.I \
     lightMutex.I lightMutex.h \
     lightMutexDirect.h lightMutexDirect.I \
     lightMutexHolder.I lightMutexHolder.h \
@@ -98,6 +99,7 @@
     cycleDataWriter.cxx \
     cyclerHolder.cxx \
     externalThread.cxx \
+    genericThread.cxx \
     lightMutex.cxx \
     lightMutexDirect.cxx \
     lightMutexHolder.cxx \
@@ -155,6 +157,7 @@
     cycleDataWriter.h cycleDataWriter.I \
     cyclerHolder.h cyclerHolder.I \
     externalThread.h \
+    genericThread.h genericThread.I \
     lightMutex.I lightMutex.h \
     lightMutexDirect.h lightMutexDirect.I \
     lightMutexHolder.I lightMutexHolder.h \

+ 2 - 0
panda/src/pipeline/config_pipeline.cxx

@@ -16,6 +16,7 @@
 #include "asyncTaskBase.h"
 #include "mainThread.h"
 #include "externalThread.h"
+#include "genericThread.h"
 #include "thread.h"
 #include "pythonThread.h"
 #include "pandaSystem.h"
@@ -73,6 +74,7 @@ init_libpipeline() {
   AsyncTaskBase::init_type();
   MainThread::init_type();
   ExternalThread::init_type();
+  GenericThread::init_type();
   Thread::init_type();
 #ifdef HAVE_PYTHON
   PythonThread::init_type();

+ 59 - 0
panda/src/pipeline/genericThread.I

@@ -0,0 +1,59 @@
+// Filename: genericThread.I
+// Created by:  drose (09Nov11)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: GenericThread::set_function
+//       Access: Published
+//  Description: Replaces the function that is called when the thread
+//               runs.
+////////////////////////////////////////////////////////////////////
+INLINE void GenericThread::
+set_function(GenericThread::ThreadFunc *function) {
+  _function = function;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GenericThread::get_function
+//       Access: Published
+//  Description: Returns the function that is called when the thread
+//               runs.
+////////////////////////////////////////////////////////////////////
+INLINE GenericThread::ThreadFunc *GenericThread::
+get_function() const {
+  return _function;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GenericThread::set_user_data
+//       Access: Published
+//  Description: Replaces the void pointer that is passed to the thread
+//               function.  This is any arbitrary pointer; the thread
+//               object does no processing on it.
+////////////////////////////////////////////////////////////////////
+INLINE void GenericThread::
+set_user_data(void *user_data) {
+  _user_data = user_data;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GenericThread::get_user_data
+//       Access: Published
+//  Description: Returns the void pointer that is passed to the thread
+//               function.
+////////////////////////////////////////////////////////////////////
+INLINE void *GenericThread::
+get_user_data() const {
+  return _user_data;
+}

+ 55 - 0
panda/src/pipeline/genericThread.cxx

@@ -0,0 +1,55 @@
+// Filename: genericThread.cxx
+// Created by:  drose (09Nov11)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#include "genericThread.h"
+#include "pnotify.h"
+
+TypeHandle GenericThread::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: GenericThread::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+GenericThread::
+GenericThread(const string &name, const string &sync_name) :
+  Thread(name, sync_name)
+{
+  _function = NULL;
+  _user_data = NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GenericThread::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+GenericThread::
+GenericThread(const string &name, const string &sync_name, GenericThread::ThreadFunc *function, void *user_data) :
+  Thread(name, sync_name),
+  _function(function),
+  _user_data(user_data)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GenericThread::thread_main
+//       Access: Protected, Virtual
+//  Description: This is the thread's main execution function.
+////////////////////////////////////////////////////////////////////
+void GenericThread::
+thread_main() {
+  nassertv(_function != NULL);
+  (*_function)(_user_data);
+}

+ 67 - 0
panda/src/pipeline/genericThread.h

@@ -0,0 +1,67 @@
+// Filename: genericThread.h
+// Created by:  drose (09Nov11)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef GENERICTHREAD_H
+#define GENERICTHREAD_H
+
+#include "pandabase.h"
+#include "thread.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : GenericThread
+// Description : A generic thread type that allows calling a C-style thread
+//               function without having to subclass.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA_PIPELINE GenericThread : public Thread {
+public:
+  typedef void ThreadFunc(void *user_data);
+
+  GenericThread(const string &name, const string &sync_name);
+  GenericThread(const string &name, const string &sync_name, ThreadFunc *function, void *user_data);
+
+  INLINE void set_function(ThreadFunc *function);
+  INLINE ThreadFunc *get_function() const;
+
+  INLINE void set_user_data(void *user_data);
+  INLINE void *get_user_data() const;
+
+protected:
+  virtual void thread_main();
+
+private:
+  ThreadFunc *_function;
+  void *_user_data;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    Thread::init_type();
+    register_type(_type_handle, "GenericThread",
+                  Thread::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "genericThread.I"
+
+#endif
+

+ 1 - 0
panda/src/pipeline/pipeline_composite1.cxx

@@ -20,6 +20,7 @@
 #include "cycleDataWriter.cxx"
 #include "cyclerHolder.cxx"
 #include "externalThread.cxx"
+#include "genericThread.cxx"
 #include "lightMutexDirect.cxx"
 #include "lightMutexHolder.cxx"
 #include "lightReMutexDirect.cxx"

+ 1 - 1
panda/src/vision/arToolKit.cxx

@@ -283,7 +283,7 @@ analyze(Texture *tex, bool do_flip_texture) {
   int pady = tex->get_pad_y_size();
   int xsize = tex->get_x_size() - padx;
   int ysize = tex->get_y_size() - pady;
-  int pagesize = xsize * ysize * 4;
+  //int pagesize = xsize * ysize * 4;
   nassertv((xsize > 0) && (ysize > 0));
 
   ARParam cparam;

+ 11 - 7
panda/src/vision/webcamVideoCursorOpenCV.cxx

@@ -61,18 +61,20 @@ WebcamVideoCursorOpenCV::
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: WebcamVideoCursorOpenCV::fetch_into_buffer
+//     Function: WebcamVideoCursorOpenCV::fetch_buffer
 //       Access: Published, Virtual
 //  Description:
 ////////////////////////////////////////////////////////////////////
-void WebcamVideoCursorOpenCV::
-fetch_into_buffer(double time, unsigned char *block, bool bgra) {
+MovieVideoCursor::Buffer *WebcamVideoCursorOpenCV::
+fetch_buffer(double time) {
   if (!_ready) {
-    return;
+    return NULL;
   }
 
-  unsigned char *dest = block;
-  int num_components = bgra ? 4 : 3;
+  Buffer *buffer = get_standard_buffer();
+  unsigned char *dest = buffer->_block;
+  int num_components = get_num_components();
+  nassertr(num_components == 3, NULL);
   ssize_t dest_x_pitch = num_components;  // Assume component_width == 1
   ssize_t dest_y_pitch = _size_x * dest_x_pitch;
 
@@ -82,7 +84,7 @@ fetch_into_buffer(double time, unsigned char *block, bool bgra) {
     if (num_components == 3 && x_pitch == 3) {
       // The easy case--copy the whole thing in, row by row.
       ssize_t copy_bytes = _size_x * dest_x_pitch;
-      nassertv(copy_bytes <= dest_y_pitch && copy_bytes <= abs(y_pitch));
+      nassertr(copy_bytes <= dest_y_pitch && copy_bytes <= abs(y_pitch), NULL);
       
       for (int y = 0; y < _size_y; ++y) {
         memcpy(dest, r, copy_bytes);
@@ -111,6 +113,8 @@ fetch_into_buffer(double time, unsigned char *block, bool bgra) {
       }
     }
   }
+
+  return buffer;
 }
 
 ////////////////////////////////////////////////////////////////////

+ 1 - 1
panda/src/vision/webcamVideoCursorOpenCV.h

@@ -31,7 +31,7 @@ class WebcamVideoCursorOpenCV : public MovieVideoCursor {
 public:
   WebcamVideoCursorOpenCV(WebcamVideoOpenCV *src);
   virtual ~WebcamVideoCursorOpenCV();
-  virtual void fetch_into_buffer(double time, unsigned char *block, bool rgba);
+  virtual Buffer *fetch_buffer(double time);
 
 private:
   bool get_frame_data(const unsigned char *&r,

+ 20 - 35
panda/src/vision/webcamVideoCursorV4L.cxx

@@ -308,33 +308,34 @@ WebcamVideoCursorV4L::
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: WebcamVideoCursorV4L::fetch_into_buffer
+//     Function: WebcamVideoCursorV4L::fetch_buffer
 //       Access: Published, Virtual
 //  Description:
 ////////////////////////////////////////////////////////////////////
-void WebcamVideoCursorV4L::
-fetch_into_buffer(double time, unsigned char *block, bool bgra) {
+MovieVideoCursor::Buffer *WebcamVideoCursorV4L::
+fetch_buffer(double time) {
   if (!_ready) {
-    return;
+    return NULL;
   }
 
+  Buffer *buffer = get_standard_buffer();
+  unsigned char *block = buffer->_block;
   struct v4l2_buffer vbuf;
   memset(&vbuf, 0, sizeof vbuf);
   vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
   vbuf.memory = V4L2_MEMORY_MMAP;
   if (-1 == ioctl(_fd, VIDIOC_DQBUF, &vbuf) && errno != EIO) {
     vision_cat.error() << "Failed to dequeue buffer!\n";
-    return;
+    return NULL;
   }
   nassertv(vbuf.index < _bufcount);
   size_t bufsize = _buflens[vbuf.index];
   size_t old_bpl = _format->fmt.pix.bytesperline;
-  size_t new_bpl = bgra ? _size_x * 4 : _size_x * 3;
+  size_t new_bpl = _size_x * 3;
   unsigned char *buf = (unsigned char *) _buffers[vbuf.index];
 
   if (_format->fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG) {
 #ifdef HAVE_JPEG
-    nassertv(!bgra);
     struct my_error_mgr jerr;
     _cinfo->err = jpeg_std_error(&jerr.pub);
     jerr.pub.error_exit = my_error_exit;
@@ -385,38 +386,20 @@ fetch_into_buffer(double time, unsigned char *block, bool bgra) {
 
     // Swap red / blue
     unsigned char ex;
-    if (bgra) {
-      for (size_t i = 0; i < new_bpl * _size_y; i += 4) {
-        ex = block[i];
-        block[i] = block[i + 2];
-        block[i + 2] = ex;
-      }
-    } else {
-      for (size_t i = 0; i < new_bpl * _size_y; i += 3) {
-        ex = block[i];
-        block[i] = block[i + 2];
-        block[i + 2] = ex;
-      }
+    for (size_t i = 0; i < new_bpl * _size_y; i += 3) {
+      ex = block[i];
+      block[i] = block[i + 2];
+      block[i + 2] = ex;
     }
 #else
-    nassertv(false); // Not compiled with JPEG support
+    nassertr(false, NULL); // Not compiled with JPEG support
 #endif
   } else {
-    if (bgra) {
-      for (size_t row = 0; row < _size_y; ++row) {
-        size_t c = 0;
-        for (size_t i = 0; i < old_bpl; i += 4) {
-          yuyv_to_rgbargba(block + (_size_y - row - 1) * new_bpl + c, buf + row * old_bpl + i);
-          c += 8;
-        }
-      }
-    } else {
-      for (size_t row = 0; row < _size_y; ++row) {
-        size_t c = 0;
-        for (size_t i = 0; i < old_bpl; i += 4) {
-          yuyv_to_rgbrgb(block + (_size_y - row - 1) * new_bpl + c, buf + row * old_bpl + i);
-          c += 6;
-        }
+    for (size_t row = 0; row < _size_y; ++row) {
+      size_t c = 0;
+      for (size_t i = 0; i < old_bpl; i += 4) {
+        yuyv_to_rgbrgb(block + (_size_y - row - 1) * new_bpl + c, buf + row * old_bpl + i);
+        c += 6;
       }
     }
   }
@@ -424,6 +407,8 @@ fetch_into_buffer(double time, unsigned char *block, bool bgra) {
   if (-1 == ioctl(_fd, VIDIOC_QBUF, &vbuf)) {
     vision_cat.error() << "Failed to exchange buffer with driver!\n";
   }
+
+  return buffer;
 }
 
 #endif

+ 1 - 1
panda/src/vision/webcamVideoCursorV4L.h

@@ -37,7 +37,7 @@ class WebcamVideoCursorV4L : public MovieVideoCursor {
 public:
   WebcamVideoCursorV4L(WebcamVideoV4L *src);
   virtual ~WebcamVideoCursorV4L();
-  virtual void fetch_into_buffer(double time, unsigned char *block, bool rgba);
+  virtual Buffer *fetch_buffer(double time);
 
 private:
   int _fd;

+ 10 - 26
panda/src/vision/webcamVideoDS.cxx

@@ -136,7 +136,7 @@ class WebcamVideoCursorDS : public MovieVideoCursor
 public:
   WebcamVideoCursorDS(WebcamVideoDS *src);
   virtual ~WebcamVideoCursorDS();
-  virtual void fetch_into_buffer(double time, unsigned char *block, bool rgba);
+  virtual Buffer *fetch_buffer(double time);
 
 public:
   void cleanup();
@@ -654,17 +654,18 @@ WebcamVideoCursorDS::
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: WebcamVideoCursorDS::fetch_into_buffer
+//     Function: WebcamVideoCursorDS::fetch_buffer
 //       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
-void WebcamVideoCursorDS::
-fetch_into_buffer(double time, unsigned char *block, bool bgra) {
-
+WebcamVideoCursor::Buffer *WebcamVideoCursorDS::
+fetch_buffer(double time) {
   if (!_ready) {
-    return;
+    return NULL;
   }
 
+  Buffer *buffer = get_standard_buffer();
+  unsigned char *block = buffer->_block;
 #ifdef LOCKING_MODE
   unsigned char *ptr;
   int pixels = _size_x * _size_y;
@@ -672,34 +673,17 @@ fetch_into_buffer(double time, unsigned char *block, bool bgra) {
   if (res == S_OK) {
     int size = _saved->GetActualDataLength();
     if (size == pixels * 3) {
-      if (bgra) {
-        for (int i=0; i<pixels; i++) {
-          block[i*4+0] = ptr[i*3+0];
-          block[i*4+1] = ptr[i*3+1];
-          block[i*4+2] = ptr[i*3+2];
-          block[i*4+3] = 255;
-        }
-      } else {
-        memcpy(block, ptr, pixels * 3);
-      }
+      memcpy(block, ptr, pixels * 3);
     }
   }
   _saved->Release();
 #else
   int pixels = _size_x * _size_y;
-  if (bgra) {
-    for (int i=0; i<pixels; i++) {
-      block[i*4+0] = _buffer[i*3+0];
-      block[i*4+1] = _buffer[i*3+1];
-      block[i*4+2] = _buffer[i*3+2];
-      block[i*4+3] = 255;
-    }
-  } else {
-    memcpy(block, _buffer, pixels * 3);
-  }
+  memcpy(block, _buffer, pixels * 3);
 #endif
 
   _ready = false;
+  return buffer;
 }