Browse Source

More progress on Movie/MovieTexture

Josh Yelon 18 years ago
parent
commit
84c44e8872

+ 12 - 11
doc/makepanda/makepanda.py

@@ -1933,6 +1933,7 @@ CopyAllHeaders('panda/src/nativenet')
 CopyAllHeaders('panda/src/net')
 CopyAllHeaders('panda/src/pstatclient')
 CopyAllHeaders('panda/src/gobj')
+CopyAllHeaders('panda/src/movies')
 CopyAllHeaders('panda/src/lerp')
 CopyAllHeaders('panda/src/pgraph')
 CopyAllHeaders('panda/src/cull')
@@ -2535,6 +2536,17 @@ EnqueueIgate(ipath=IPATH, opts=OPTS, outd='libtext.in', obj='libtext_igate.obj',
             src='panda/src/text',  module='panda', library='libtext',
             skip=[], also=["text_composite.cxx"])
 
+#
+# DIRECTORY: panda/src/movies/
+#
+
+IPATH=['panda/src/movies']
+OPTS=['BUILDING_PANDA']
+EnqueueCxx(ipath=IPATH, opts=OPTS, src='movies_composite1.cxx', obj='movies_composite1.obj')
+EnqueueIgate(ipath=IPATH, opts=OPTS, outd='libmovies.in', obj='libmovies_igate.obj',
+            src='panda/src/movies',  module='panda', library='libmovies',
+            skip=[], also=["movies_composite.cxx"])
+
 #
 # DIRECTORY: panda/src/grutil/
 #
@@ -2610,17 +2622,6 @@ EnqueueIgate(ipath=IPATH, opts=OPTS, outd='librecorder.in', obj='librecorder_iga
             src='panda/src/recorder',  module='panda', library='librecorder',
             skip=[], also=["recorder_composite.cxx"])
 
-#
-# DIRECTORY: panda/src/movies/
-#
-
-IPATH=['panda/src/movies']
-OPTS=['BUILDING_PANDA']
-EnqueueCxx(ipath=IPATH, opts=OPTS, src='movies_composite1.cxx', obj='movies_composite1.obj')
-EnqueueIgate(ipath=IPATH, opts=OPTS, outd='libmovies.in', obj='libmovies_igate.obj',
-            src='panda/src/movies',  module='panda', library='libmovies',
-            skip=[], also=["movies_composite.cxx"])
-
 #
 # DIRECTORY: panda/src/vrpn/
 #

+ 6 - 0
panda/src/grutil/config_grutil.cxx

@@ -20,6 +20,7 @@
 #include "frameRateMeter.h"
 #include "openCVTexture.h"
 #include "ffmpegTexture.h"
+#include "movieTexture.h"
 #include "pandaSystem.h"
 #include "texturePool.h"
 #include "nodeVertexTransform.h"
@@ -72,6 +73,11 @@ init_libgrutil() {
   RigidBodyCombiner::init_type();
   PipeOcclusionCullTraverser::init_type();
 
+  MovieTexture::init_type();
+  MovieTexture::register_with_read_factory();
+  TexturePool *ts = TexturePool::get_global_ptr();
+  ts->register_texture_type(MovieTexture::make_texture, "dummyvideo");
+
 #ifdef HAVE_OPENCV
   
   {

+ 1 - 0
panda/src/grutil/grutil_composite1.cxx

@@ -6,6 +6,7 @@
 #include "frameRateMeter.cxx"
 #include "openCVTexture.cxx"
 #include "ffmpegTexture.cxx"
+#include "movieTexture.cxx"
 #include "nodeVertexTransform.cxx"
 #include "pipeOcclusionCullTraverser.cxx"
 #include "rigidBodyCombiner.cxx"

+ 48 - 40
panda/src/grutil/movieTexture.I

@@ -1,5 +1,5 @@
-// Filename: openCVTexture.I
-// Created by:  zacpavlov (19Aug05)
+// Filename: movieTexture.I
+// Created by: jyelon (01Aug2007)
 //
 ////////////////////////////////////////////////////////////////////
 //
@@ -18,52 +18,60 @@
 
 
 ////////////////////////////////////////////////////////////////////
-//     Function: FFMpegTexture::VideoStream::is_valid
-//       Access: Public
-//  Description: Returns true if this stream is open and ready, false
-//               otherwise.
+//     Function: MovieTexture::get_video_width
+//       Access: Published
+//  Description: Returns the width in texels of the source video
+//               stream.  This is not necessarily the width of the
+//               actual texture, since the texture may have been
+//               expanded to raise it to a power of 2.
 ////////////////////////////////////////////////////////////////////
-INLINE bool FFMpegTexture::VideoStream::
-is_valid() const {
-  return (_format_context != NULL && _codec_context != NULL);
+INLINE int MovieTexture::
+get_video_width() const {
+  CDReader cdata(_cycler);
+  return cdata->_video_width;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: FFMpegTexture::VideoStream::is_from_file
-//       Access: Public
-//  Description: Returns true if this stream takes its input from a
-//               video file, false otherwise.
+//     Function: MovieTexture::get_video_height
+//       Access: Published
+//  Description: Returns the height in texels of the source video
+//               stream.  This is not necessarily the height of the
+//               actual texture, since the texture may have been
+//               expanded to raise it to a power of 2.
 ////////////////////////////////////////////////////////////////////
-INLINE bool FFMpegTexture::VideoStream::
-is_from_file() const {
-  return true;
+INLINE int MovieTexture::
+get_video_height() const {
+  CDReader cdata(_cycler);
+  return cdata->_video_height;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: FFMpegTexture::VideoPage::Constructor
-//       Access: Public
-//  Description: 
-////////////////////////////////////////////////////////////////////
-INLINE FFMpegTexture::VideoPage::
-VideoPage() {
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: FFMpegTexture::VideoPage::Copy Constructor
-//       Access: Public
-//  Description: 
+//     Function: MovieTexture::get_tex_scale
+//       Access: Published
+//  Description: Returns a scale pair that is suitable for applying to
+//               geometry via NodePath::set_tex_scale(), which will
+//               convert texture coordinates on the geometry from the
+//               range 0..1 into the appropriate range to render the
+//               video part of the texture.
+//
+//               This is necessary in the event the video source is
+//               not a power of two and set_power_2() is true.  In
+//               this case, the video image will be mapped to the
+//               lower-left corner of the texture, and the rest of the
+//               texture space will be unused; so we will need to
+//               remap any texture coordinates to fill the space
+//               correctly.
 ////////////////////////////////////////////////////////////////////
-INLINE FFMpegTexture::VideoPage::
-VideoPage(const FFMpegTexture::VideoPage &copy) :
-  _color(copy._color)
-{
+INLINE LVecBase2f MovieTexture::
+get_tex_scale() const {
+  CDReader cdata(_cycler);
+  if (cdata->_video_width == 0 ||
+      cdata->_video_height == 0 ||
+      _x_size == 0 ||
+      _y_size == 0) {
+    return LVecBase2f(1.0f, 1.0f);
+  }
+  return LVecBase2f((float)cdata->_video_width / _x_size,
+                    (float)cdata->_video_height / _y_size);
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: FFMpegTexture::VideoPage::Destructor
-//       Access: Public
-//  Description: 
-////////////////////////////////////////////////////////////////////
-INLINE FFMpegTexture::VideoPage::
-~VideoPage() {
-}

+ 240 - 607
panda/src/grutil/movieTexture.cxx

@@ -1,5 +1,5 @@
-// Filename: ffmpegTexture.cxx
-// Created by:  zacpavlov (05May06)
+// Filename: movieTexture.cxx
+// Created by: jyelon (01Aug2007)
 //
 ////////////////////////////////////////////////////////////////////
 //
@@ -18,51 +18,109 @@
 
 #include "pandabase.h"
 
-#ifdef HAVE_FFMPEG
-#include "ffmpegTexture.h"
+#include "movieVideo.h"
+#include "movieTexture.h"
 #include "clockObject.h"
 #include "config_gobj.h"
 #include "config_grutil.h"
-#include "bamCacheRecord.h"
 
-TypeHandle FFMpegTexture::_type_handle;
+TypeHandle MovieTexture::_type_handle;
 
 ////////////////////////////////////////////////////////////////////
-//     Function: FFMpegTexture::Constructor
+//     Function: MovieTexture::Constructor
 //       Access: Published
 //  Description: Sets up the texture to read frames from a camera
 ////////////////////////////////////////////////////////////////////
-FFMpegTexture::
-FFMpegTexture(const string &name) : 
-  VideoTexture(name) 
+MovieTexture::
+MovieTexture(const string &name) : 
+  Texture(name)
 {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: FFMpegTexture::Copy Constructor
+//     Function: MovieTexture::CData::Constructor
+//       Access: public
+//  Description: xxx
+////////////////////////////////////////////////////////////////////
+MovieTexture::CData::
+CData()
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MovieTexture::CData::Copy Constructor
+//       Access: public
+//  Description: xxx
+////////////////////////////////////////////////////////////////////
+MovieTexture::CData::
+CData(const CData &copy) :
+  _pages(copy._pages),
+  _video_width(copy._video_width),
+  _video_height(copy._video_height)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MovieTexture::CData::make_copy
+//       Access: public
+//  Description: xxx
+////////////////////////////////////////////////////////////////////
+CycleData *MovieTexture::CData::
+make_copy() const {
+  return new CData(*this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MovieTexture::Copy Constructor
 //       Access: Protected
-//  Description: Use FFmpegTexture::make_copy() to make a duplicate copy of
-//               an existing FFMpegTexture.
+//  Description: Use MovieTexture::make_copy() to make a duplicate copy of
+//               an existing MovieTexture.
 ////////////////////////////////////////////////////////////////////
-FFMpegTexture::
-FFMpegTexture(const FFMpegTexture &copy) : 
-  VideoTexture(copy),
-  _pages(copy._pages)
+MovieTexture::
+MovieTexture(const MovieTexture &copy) : 
+  Texture(copy)
 {
+  // Since 'get_video' can be a slow operation, 
+  // I release the read lock before calling get_video.
+
+  pvector<MovieVideo *> color;
+  pvector<MovieVideo *> alpha;
+  {
+    CDReader copy_cdata(copy._cycler);
+    color.resize(copy_cdata->_pages.size());
+    alpha.resize(copy_cdata->_pages.size());
+    for (int i=0; i<(int)(color.size()); i++) {
+      color[i] = copy_cdata->_pages[i]._color;
+      alpha[i] = copy_cdata->_pages[i]._alpha;
+    }
+  }
+  
+  {
+    CDWriter cdata(_cycler);
+    cdata->_pages.resize(color.size());
+    for (int i=0; i<(int)(color.size()); i++) {
+      if (color[i]) {
+        cdata->_pages[i]._color = color[i]->get_source()->get_video(0.0);
+      }
+      if (alpha[i]) {
+        cdata->_pages[i]._alpha = color[i]->get_source()->get_video(0.0);
+      }
+    }
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: FFMpegTexture::Destructor
+//     Function: MovieTexture::Destructor
 //       Access: Published, Virtual
-//  Description: I'm betting that texture takes care of the, so we'll just do a clear.
+//  Description: xxx
 ////////////////////////////////////////////////////////////////////
-FFMpegTexture::
-~FFMpegTexture() {
+MovieTexture::
+~MovieTexture() {
   clear();
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: FFMpegTexture::make_copy
+//     Function: MovieTexture::make_copy
 //       Access: Published, Virtual
 //  Description: Returns a new copy of the same Texture.  This copy,
 //               if applied to geometry, will be copied into texture
@@ -70,241 +128,92 @@ FFMpegTexture::
 //               be duplicated in texture memory (and may be
 //               independently modified if desired).
 //               
-//               If the Texture is an FFMpegTexture, the resulting
+//               If the Texture is an MovieTexture, the resulting
 //               duplicate may be animated independently of the
 //               original.
 ////////////////////////////////////////////////////////////////////
-PT(Texture) FFMpegTexture::
+PT(Texture) MovieTexture::
 make_copy() {
-  return new FFMpegTexture(*this);
-}
-
-
-////////////////////////////////////////////////////////////////////
-//     Function: FFMPegTexture::modify_page
-//       Access: Private
-//  Description: Returns a reference to the zth VideoPage (level) of
-//               the texture.  In the case of a 2-d texture, there is
-//               only one page, level 0; but cube maps and 3-d
-//               textures have more.
-////////////////////////////////////////////////////////////////////
-FFMpegTexture::VideoPage &FFMpegTexture::
-modify_page(int z) {
-  nassertr(z < _z_size, _pages[0]);
-  while (z >= (int)_pages.size()) {
-    _pages.push_back(VideoPage());
-  }
-  return _pages[z];
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: FFmpegTexture::reconsider_video_properties
-//       Access: Private
-//  Description: Resets the internal Texture properties when a new
-//               video file is loaded.  Returns true if the new image
-//               is valid, false otherwise.
-////////////////////////////////////////////////////////////////////
-bool FFMpegTexture::
-reconsider_video_properties(const FFMpegTexture::VideoStream &stream, 
-                            int num_components, int z) {
-  double frame_rate = 0.0f;
-  int num_frames = 0;
-  if (!stream._codec_context) {
-    // printf("not valid yet\n");
-    return true;
-  }
-  
-  AVStream *vstream = stream._format_context->streams[stream._stream_number];
-  
-  if (stream.is_from_file()) {
-    // frame rate comes from ffmpeg as an avRational. 
-    frame_rate = vstream->r_frame_rate.num/(float)vstream->r_frame_rate.den;
-    
-    // Number of frames is a little questionable if we've got variable 
-    // frame rate. Duration comes in as a generic timestamp, 
-    // and is therefore multiplied by AV_TIME_BASE.
-    num_frames = (int)((stream._format_context->duration*frame_rate)/AV_TIME_BASE);
-    if (grutil_cat.is_debug()) {
-      grutil_cat.debug()
-        << "Loaded " << stream._filename << ", " << num_frames << " frames at "
-        << frame_rate << " fps\n";
-    }
-  }
-  
-  int width = stream._codec_context->width;
-  int height = stream._codec_context->height;
-
-  int x_size = width;
-  int y_size = height;
-
-  if (textures_power_2 != ATS_none) {
-    x_size = up_to_power_2(width);
-    y_size = up_to_power_2(height);
-  }
-
-  if (grutil_cat.is_debug()) {
-    grutil_cat.debug()
-      << "Video stream is " << width << " by " << height 
-      << " pixels; fitting in texture " << x_size << " by "
-      << y_size << " texels.\n";
-  }
-
-  if (!reconsider_image_properties(x_size, y_size, num_components,
-                                   T_unsigned_byte, z)) {
-    return false;
-  }
-
-  if (_loaded_from_image && 
-      (get_video_width() != width || get_video_height() != height ||
-       get_num_frames() != num_frames || get_frame_rate() != frame_rate)) {
-    grutil_cat.error()
-      << "Video properties have changed for texture " << get_name()
-      << " level " << z << ".\n";
-    return false;
-  }
-
-  set_frame_rate(frame_rate);
-  set_num_frames(num_frames);
-  set_video_size(width, height);
-
-  // By default, the newly-loaded video stream will immediately start
-  // looping.
-  loop(true);
-
-  return true;
+  return new MovieTexture(*this);
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: FFMpegTexture::make_texture
+//     Function: MovieTexture::make_texture
 //       Access: Public, Static
-//  Description: A factory function to make a new FFMpegTexture, used
+//  Description: A factory function to make a new MovieTexture, used
 //               to pass to the TexturePool.
 ////////////////////////////////////////////////////////////////////
-PT(Texture) FFMpegTexture::
+PT(Texture) MovieTexture::
 make_texture() {
-  return new FFMpegTexture;
+  return new MovieTexture("");
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: FFMPegTexture::update_frame
-//       Access: Protected, Virtual
-//  Description: Called once per frame, as needed, to load the new
-//               image contents.
-////////////////////////////////////////////////////////////////////
-void FFMpegTexture::
-update_frame(int frame) {
-  int max_z = max(_z_size, (int)_pages.size());
-  for (int z = 0; z < max_z; ++z) {
-    VideoPage &page = _pages[z];
-    if (page._color.is_valid() || page._alpha.is_valid()) {
-      modify_ram_image();
-    }
-    if (page._color.is_valid()) {
-      nassertv(get_num_components() >= 3 && get_component_width() == 1);
-
-      // A little different from the opencv implementation
-      // The frame is kept on the stream itself. This is partially 
-      // because there is a conversion step that must be done for 
-      // every video (I've gotten very odd results with any video
-      // that I don't convert, even if the IO formats are the same!)  
-      if (page._color.get_frame_data(frame)) {
-        nassertv(get_video_width() <= _x_size && get_video_height() <= _y_size);
-        unsigned char *dest = _ram_images[0]._image.p() + get_expected_ram_page_size() * z;
-        int dest_row_width = (_x_size * _num_components * _component_width);
-        
-        // Simplest case, where we deal with an rgb texture
-        if (get_num_components() == 3) {
-          int source_row_width=3*page._color._codec_context->width;
-          unsigned char * source=(unsigned char *)page._color._frame_out->data[0]
-            +source_row_width*(get_video_height()-1);         
+//     Function: MovieTexture::VideoPage::Constructor
+//       Access: Private
+//  Description: Creates a completely blank video page.
+////////////////////////////////////////////////////////////////////
+MovieTexture::VideoPage::
+VideoPage() :
+  _color(0),
+  _alpha(0)
+{
+}
 
-          // row by row copy.        
-          for (int y = 0; y < get_video_height(); ++y) {
-            memcpy(dest, source, source_row_width);
-            dest += dest_row_width;
-            source -= source_row_width;
-          }
-          // Next best option, we're a 4 component alpha video on one stream 
-        } else if (page._color._codec_context->pix_fmt==PIX_FMT_RGBA32) {
-          int source_row_width= page._color._codec_context->width * 4;
-          unsigned char * source=(unsigned char *)page._color._frame_out->data[0]
-            +source_row_width*(get_video_height()-1);
-          
-          // row by row copy.        
-          for (int y = 0; y < get_video_height(); ++y) {
-            memcpy(dest,source,source_row_width);
-            dest += dest_row_width;
-            source -= source_row_width;
-          } 
-          // Otherwise, we've got to be tricky
-        } else {
-          int source_row_width= page._color._codec_context->width * 3;
-          unsigned char * source=(unsigned char *)page._color._frame_out->data[0]
-            +source_row_width*(get_video_height()-1);
-          
-          // The harder case--interleave the color in with the alpha,
-          // pixel by pixel.
-          nassertv(get_num_components() == 4);
-          for (int y = 0; y < get_video_height(); ++y) {
-            int dx = 0;
-            int sx = 0;
-            for (int x = 0; x < get_video_width(); ++x) {
-              dest[dx] = source[sx];
-              dest[dx + 1] = source[sx + 1];
-              dest[dx + 2] = source[sx + 2];
-              dx += 4;
-              sx += 3;
-            }
-            dest += dest_row_width;
-            source -= source_row_width;
-          }
-        }
-        
-        
-      }
-    }
-    
-    if (page._alpha.is_valid()) {
-      nassertv(get_num_components() == 4 && get_component_width() == 1);
+////////////////////////////////////////////////////////////////////
+//     Function: MovieTexture::recalculate_image_properties
+//       Access: Protected
+//  Description: Resizes the texture, and adjusts the format,
+//               based on the source movies.  The resulting texture
+//               will be large enough to hold all the videos.
+////////////////////////////////////////////////////////////////////
+void MovieTexture::
+recalculate_image_properties(CDWriter &cdata) {
+  int x_max = 0;
+  int y_max = 0;
+  bool alpha = false;
   
-      if (page._alpha.get_frame_data(frame)) {
-        nassertv(get_video_width() <= _x_size && get_video_height() <= _y_size);
-        
-        // Currently, we assume the alpha has been converted to an rgb format
-        // There is no reason it can't be a 256 color grayscale though.
-        unsigned char *dest = _ram_images[0]._image.p() + get_expected_ram_page_size() * z;
-        int dest_row_width = (_x_size * _num_components * _component_width);
-        
-        int source_row_width= page._alpha._codec_context->width * 3;
-        unsigned char * source=(unsigned char *)page._alpha._frame_out->data[0]
-          +source_row_width*(get_video_height()-1);
-        for (int y = 0; y < get_video_height(); ++y) {
-          int dx = 3;
-          int sx = 0;
-          for (int x = 0; x < get_video_width(); ++x) {
-            dest[dx] = source[sx];
-            dx += 4;
-            sx += 3;
-          }
-          dest += dest_row_width;
-          source -= source_row_width;
-        }
-        
-      }
+  for (int i=0; i<_z_size; i++) {
+    MovieVideo *t = cdata->_pages[i]._color;
+    if (t) {
+      if (t->size_x() > x_max) x_max = t->size_x();
+      if (t->size_y() > y_max) y_max = t->size_y();
+      if (t->get_num_components() == 4) alpha=true;
+    }
+    t = cdata->_pages[i]._alpha;
+    if (t) {
+      if (t->size_x() > x_max) x_max = t->size_x();
+      if (t->size_y() > y_max) y_max = t->size_y();
+      alpha = true;
     }
   }
 
+  cdata->_video_width  = x_max;
+  cdata->_video_height = y_max;
+  
+  if (get_texture_type() == TT_cube_map) {
+    // Texture must be square.
+    if (x_max > y_max) y_max = x_max;
+    if (y_max > x_max) x_max = y_max;
+  }
+  
+  if (textures_power_2 != ATS_none) {
+    x_max = up_to_power_2(x_max);
+    y_max = up_to_power_2(y_max);
+  }
+  
+  reconsider_image_properties(x_max, y_max, alpha?4:3, 
+                              T_unsigned_byte, cdata->_pages.size());
 }
 
-
 ////////////////////////////////////////////////////////////////////
-//     Function: FFMpegTexture::do_read_one
+//     Function: MovieTexture::do_read_one
 //       Access: Protected, Virtual
 //  Description: Combines a color and alpha video image from the two
 //               indicated filenames.  Both must be the same kind of
 //               video with similar properties.
 ////////////////////////////////////////////////////////////////////
-bool FFMpegTexture::
+bool MovieTexture::
 do_read_one(const Filename &fullpath, const Filename &alpha_fullpath,
             int z, int n, int primary_file_num_channels, int alpha_file_channel,
             bool header_only, BamCacheRecord *record) {
@@ -315,23 +224,30 @@ do_read_one(const Filename &fullpath, const Filename &alpha_fullpath,
   nassertr(n == 0, false);
   nassertr(z >= 0 && z < get_z_size(), false);
 
-  VideoPage &page = modify_page(z);
-  if (!page._color.read(fullpath)) {
-    grutil_cat.error()
-      << "FFMpeg couldn't read " << fullpath << " as video.\n";
+  PT(Movie) color;
+  PT(MovieVideo) color_video;
+  PT(Movie) alpha;
+  PT(MovieVideo) alpha_video;
+
+  color = Movie::load(fullpath);
+  if (color == 0) {
+    return false;
+  }
+  color_video = color->get_video(0.0);
+  if (color_video == 0) {
     return false;
   }
-
   if (!alpha_fullpath.empty()) {
-    if (!page._alpha.read(alpha_fullpath)) {
-      grutil_cat.error()
-        << "FFMPEG couldn't read " << alpha_fullpath << " as video.\n";
-      page._color.clear();
+    alpha = Movie::load(alpha_fullpath);
+    if (alpha == 0) {
+      return false;
+    }
+    alpha_video = alpha->get_video(0.0);
+    if (alpha_video == 0) {
       return false;
     }
   }
-
-
+  
   if (z == 0) {
     if (!has_name()) {
       set_name(fullpath.get_basename_wo_extension());
@@ -340,82 +256,52 @@ do_read_one(const Filename &fullpath, const Filename &alpha_fullpath,
       set_filename(fullpath);
       set_alpha_filename(alpha_fullpath);
     }
-
+    
     set_fullpath(fullpath);
     set_alpha_fullpath(alpha_fullpath);
   }
-  if (page._color._codec_context->pix_fmt==PIX_FMT_RGBA32) {
-    // There had better not be an alpha interleave here. 
-    nassertr(alpha_fullpath.empty(), false);
-    
-    _primary_file_num_channels = 4;
-    _alpha_file_channel = 0;
-    if (!reconsider_video_properties(page._color, 4, z)) {
-      page._color.clear();
-      return false;
-    }
-     
-  } else {
-    _primary_file_num_channels = 3;
-    _alpha_file_channel = alpha_file_channel;
-
-    if (page._alpha.is_valid()) {
-      if (!reconsider_video_properties(page._color, 4, z)) {
-        page._color.clear();
-        page._alpha.clear();
-        return false;
-      }
-      if (!reconsider_video_properties(page._alpha, 4, z)) {
-        page._color.clear();
-        page._alpha.clear();
-        return false;
-      }
-    } else {
-      if (!reconsider_video_properties(page._color, 3, z)) {
-        page._color.clear();
-        page._alpha.clear();
-        return false;
-      }
-      
-    }
-    
+  
+  _primary_file_num_channels = 4;
+  _alpha_file_channel = alpha_file_channel;
+  
+  {
+    CDWriter cdata(_cycler);
+    cdata->_pages.resize(z+1);
+    cdata->_pages[z]._color = color_video;
+    cdata->_pages[z]._alpha = alpha_video;
+    cdata->_pages[z]._base_clock = ClockObject::get_global_clock()->get_frame_time();
+    recalculate_image_properties(cdata);
   }
+  
+  // Fetch the first frame.  We ignore the timestamp
+  // of the first frame, and assume it starts at zero.
+  
   set_loaded_from_image();
-  clear_current_frame();
-  update_frame(0);
   return true;
 }
 
-
 ////////////////////////////////////////////////////////////////////
-//     Function: FFMpegTexture::do_load_one
+//     Function: MovieTexture::do_load_one
 //       Access: Protected, Virtual
-//  Description: Resets the texture (or the particular level of the
-//               texture) to the indicated static image.
+//  Description: Loading a static image into a MovieTexture is
+//               an error.
 ////////////////////////////////////////////////////////////////////
-bool FFMpegTexture::
+bool MovieTexture::
 do_load_one(const PNMImage &pnmimage, const string &name, int z, int n) {
-  if (z <= (int)_pages.size()) {
-    VideoPage &page = modify_page(z);
-    page._color.clear();
-  }
-
-  return Texture::do_load_one(pnmimage, name, z, n);
+  grutil_cat.error() << "You cannot load a static image into a MovieTexture\n";
+  return false;
 }
 
-
-
-
 ////////////////////////////////////////////////////////////////////
-//     Function: FFMpegTexture::register_with_read_factory
+//     Function: MovieTexture::register_with_read_factory
 //       Access: Public, Static
 //  Description: Factory method to generate a Texture object
 ////////////////////////////////////////////////////////////////////
-void FFMpegTexture::
+void MovieTexture::
 register_with_read_factory() {
   // Since Texture is such a funny object that is reloaded from the
   // TexturePool each time, instead of actually being read fully from
-  // the bam file, and since the VideoTexture and FFMpegTexture
+  // the bam file, and since the VideoTexture and MovieTexture
   // classes don't really add any useful data to the bam record, we
   // don't need to define make_from_bam(), fillin(), or
   // write_datagram() in this class--we just inherit the same
@@ -428,334 +314,81 @@ register_with_read_factory() {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: FFMpegTexture::VideoStream::Constructor
-//       Access: Public
-//  Description: 
-////////////////////////////////////////////////////////////////////
-FFMpegTexture::VideoStream::
-VideoStream() :
-  _codec_context(NULL),
-  _format_context(NULL),
-  _frame(NULL),
-  _frame_out(NULL),
-  _next_frame_number(0)
-{
-  // printf("creating video stream\n");
-}
-
+//     Function: MovieTexture::has_cull_callback
+//       Access: Public, Virtual
+//  Description: Should be overridden by derived classes to return
+//               true if cull_callback() has been defined.  Otherwise,
+//               returns false to indicate cull_callback() does not
+//               need to be called for this node during the cull
+//               traversal.
 ////////////////////////////////////////////////////////////////////
-//     Function: FFMpegTexture::VideoStream::Copy Constructor
-//       Access: Public
-//  Description: 
-////////////////////////////////////////////////////////////////////
-FFMpegTexture::VideoStream::
-VideoStream(const FFMpegTexture::VideoStream &copy) :
-  _codec_context(NULL),
-  _format_context(NULL),
-  _frame(NULL),
-  _frame_out(NULL),
-  _next_frame_number(0)
-{
-  // Rather than copying the _capture pointer, we must open a new
-  // stream that references the same file.
-  if (copy.is_valid()) {
-    if (copy.is_from_file()) {
-      read(copy._filename);
-    }
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: FFMpegTexture::VideoStream::Copy Constructor
-//       Access: Public
-//  Description: 
-////////////////////////////////////////////////////////////////////
-FFMpegTexture::VideoStream::
-~VideoStream() {
-  clear();
+bool MovieTexture::
+has_cull_callback() const {
+  return true;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: FFMpegTexture::VideoStream::get_frame_data
-//       Access: Public
-//  Description: Returns the pointer to the beginning of the
-//               decompressed buffer for the indicated frame number.
-//               It is most efficient to call this in increasing order
-//               of frame number.
-////////////////////////////////////////////////////////////////////
-bool FFMpegTexture::VideoStream::
-get_frame_data(int frame_number) {
-  nassertr(is_valid(), false);
-  int coming_from = _next_frame_number;
-
-  _next_frame_number = frame_number + 1;
-  AVPacket packet;
-  AVStream *vstream = _format_context->streams[_stream_number];
-  
-  int got_frame;
-                  
-  // Can we get to our target frame just by skipping forward a few
-  // frames?  We arbitrarily draw the line at 50 frames for now.
-  if (frame_number >= coming_from && frame_number - coming_from < 50) { 
-
-    if (frame_number > coming_from) {
-      // Ok, we do have to skip a few frames.
-      _codec_context->hurry_up = true;
-      while (frame_number > coming_from) {
-        int err = read_video_frame(&packet);
-        if (err < 0) {
-          return false;
-        }
-        avcodec_decode_video(_codec_context, _frame, &got_frame, packet.data,
-                             packet.size);
-        av_free_packet(&packet);
-        ++coming_from;
+//     Function: MovieTexture::cull_callback
+//       Access: Public, Virtual
+//  Description: This function will be called during the cull 
+//               traversal to update the MovieTexture.  This update
+//               consists of fetching the next video frame from the
+//               underlying MovieVideo sources.  The MovieVideo
+//               object belongs to the cull thread.
+////////////////////////////////////////////////////////////////////
+bool MovieTexture::
+cull_callback(CullTraverser *, const CullTraverserData &) const {
+  CDReader cdata(_cycler);
+  double now = ClockObject::get_global_clock()->get_frame_time();
+  for (int i=0; i<((int)(cdata->_pages.size())); i++) {
+    double delta = now - cdata->_pages[i]._base_clock;
+    MovieVideo *color = cdata->_pages[i]._color;
+    MovieVideo *alpha = cdata->_pages[i]._alpha;
+    if (color) {
+      double offset = delta;
+      if (offset > color->next_start()) {
+        color->seek_ahead(offset);
+      } else if (offset < color->last_start()) {
+        color = color->get_source()->get_video(offset);
       }
-      _codec_context->hurry_up = false;
-    }
-
-    // Now we're ready to read a frame.
-    int err = read_video_frame(&packet);
-    if (err < 0) {
-      return false;
+      color->fetch_into_texture((MovieTexture*)this, i);
     }
-
-  } else {
-    // We have to skip backward, or maybe forward a whole bunch of
-    // frames.  Better off seeking through the stream.
-
-    double time_stamp = ((double)AV_TIME_BASE * frame_number * vstream->r_frame_rate.den) / vstream->r_frame_rate.num;
-    double curr_time_stamp;
-    
-    // find point in time
-    av_seek_frame(_format_context, -1, (long long)time_stamp,
-                  AVSEEK_FLAG_BACKWARD);
-    
-    // Okay, now we're at the nearest keyframe behind our timestamp.
-    // Hurry up and move through frames until we find a frame just after it.
-    _codec_context->hurry_up = true;
-    do {
-      int err = read_video_frame(&packet);
-      if (err < 0) {
-        return false;
-      }
-
-      curr_time_stamp = (((double)AV_TIME_BASE * packet.pts) / 
-                         ((double)packet.duration * av_q2d(vstream->r_frame_rate)));
-      if (curr_time_stamp > time_stamp) {
-        break;
-      }
-
-      avcodec_decode_video(_codec_context, _frame, &got_frame, packet.data,
-                           packet.size);
-
-      av_free_packet(&packet);
-    } while (true);
-    
-    _codec_context->hurry_up = false;
-    // Now near frame with Packet ready for decode (and free)
-  }
-  
-  // Now we have a packet from someone. Lets get this in a frame
-  
-  int frame_finished;
-
-  // Is this a packet from the video stream?
-  if (packet.stream_index == _stream_number) {
-    // Decode video frame
-    avcodec_decode_video(_codec_context, _frame, &frame_finished, 
-                         packet.data, packet.size);
-
-    // Did we get a video frame?
-    if (frame_finished) {
-      // Convert the image from its native format to RGB
-      if (_codec_context->pix_fmt != PIX_FMT_RGBA32) {
-        img_convert((AVPicture *)_frame_out, PIX_FMT_BGR24, 
-                    (AVPicture *)_frame, _codec_context->pix_fmt, 
-                    _codec_context->width, _codec_context->height);
-
-      } else { // _codec_context->pix_fmt == PIX_FMT_RGBA32
-        img_convert((AVPicture *)_frame_out, PIX_FMT_RGBA32, 
-                    (AVPicture *)_frame, _codec_context->pix_fmt, 
-                    _codec_context->width, _codec_context->height);
+    if (alpha) {
+      double offset = delta;
+      if (offset > alpha->next_start()) {
+        alpha->seek_ahead(offset);
+      } else if (offset < alpha->last_start()) {
+        alpha = alpha->get_source()->get_video(offset);
       }
+      alpha->fetch_into_texture_alpha((MovieTexture*)this, i, _alpha_file_channel);
     }
   }
-
-  // Free the packet that was allocated by av_read_frame
-  av_free_packet(&packet);
-
   return true;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: FFMpegTexture::VideoStream::read
-//       Access: Public
-//  Description: Sets up the stream to read the indicated file.
-//               Returns true on success, false on failure.
+//     Function: MovieTexture::get_keep_ram_image
+//       Access: Published, Virtual
+//  Description: A MovieTexture must always keep its ram image, 
+//               since there is no way to reload it from the 
+//               source MovieVideo.
 ////////////////////////////////////////////////////////////////////
-bool FFMpegTexture::VideoStream::
-read(const Filename &filename) {
-  // Clear out the last stream
-  clear();
-  
-  string os_specific = filename.to_os_specific();
-  // Open video file
-  if (av_open_input_file(&_format_context, os_specific.c_str(), NULL, 
-                         0, NULL) != 0) {
-    // Don't do anything, because nothing happened yet
-    return false;
-  }
-
-  // Retrieve stream information
-  if (av_find_stream_info(_format_context) < 0) {
-    clear();
-    return false;
-  }
-  dump_format(_format_context, 0, os_specific.c_str(), false);
-  
-  _stream_number = -1;
-  for(int i = 0; i < _format_context->nb_streams; i++) {
-    if ((*_format_context->streams[i]->codec).codec_type == CODEC_TYPE_VIDEO) {
-      _stream_number = i;
-      break;
-    }
-  }
-
-  if (_stream_number == -1) {
-    clear();
-    return false;
-  }
-  
-  // Get a pointer to the codec context for the video stream
-  AVCodecContext *codec_context = _format_context->streams[_stream_number]->codec;
-  
-  // Find the decoder for the video stream
-  // printf("codec id is %d\n",codec_context->codec_id);
-  _codec = avcodec_find_decoder(codec_context->codec_id);
-  if (_codec == NULL) {
-    clear();
-    return false;
-  }
-  
-  if (_codec->capabilities & CODEC_CAP_TRUNCATED) {
-    codec_context->flags |= CODEC_FLAG_TRUNCATED;
-  }
-
-  // Open codec
-  _codec_context = codec_context;
-  if (avcodec_open(_codec_context, _codec) < 0) {
-    _codec_context = NULL;
-    clear();
-    return false;
-  }
-  
-  _frame = avcodec_alloc_frame();
-  
-  if (_codec_context->pix_fmt != PIX_FMT_RGBA32) {
-    _frame_out = avcodec_alloc_frame();
-    if (_frame_out == NULL) {
-      clear();
-      return false;
-    }
-    
-    // Determine required buffer size and allocate buffer
-    _image_size_bytes = avpicture_get_size(PIX_FMT_BGR24, _codec_context->width,
-                                           _codec_context->height);
-            
-    _raw_data = new uint8_t[_image_size_bytes];
-
-    // Assign appropriate parts of buffer to image planes in _frameRGB
-    avpicture_fill((AVPicture *)_frame_out, _raw_data, PIX_FMT_BGR24,
-                   _codec_context->width, _codec_context->height);
-
-  } else {
-    _frame_out = avcodec_alloc_frame();
-    if (_frame_out == NULL) {
-      clear();
-      return false;
-    }
-    
-    // Determine required buffer size and allocate buffer
-    _image_size_bytes = avpicture_get_size(PIX_FMT_RGBA32, _codec_context->width,
-                                           _codec_context->height);
-            
-    _raw_data = new uint8_t[_image_size_bytes];
-    // Assign appropriate parts of buffer to image planes in _frameRGB
-    avpicture_fill((AVPicture *)_frame_out, _raw_data, PIX_FMT_RGBA32,
-                   _codec_context->width, _codec_context->height);
-  } 
-  // We could put an option here for single channel frames.
-  
-  _next_frame_number = 0;
-  _filename = filename;
-
+bool MovieTexture::
+get_keep_ram_image() const {
+  // A MovieTexture should never dump its RAM image.
   return true;
 }
 
-
-
-////////////////////////////////////////////////////////////////////
-//     Function: FFMpegTexture::VideoStream::clear
-//       Access: Public
-//  Description: Stops the video playback and frees the associated
-//               resources.
-////////////////////////////////////////////////////////////////////
-void FFMpegTexture::VideoStream::
-clear() {
-  if (_codec_context) {
-    avcodec_close(_codec_context);
-    _codec_context = NULL;
-  }
-  if (_format_context) {
-    av_close_input_file(_format_context);
-    _format_context = NULL;
-  }
-  if (_frame) {
-    av_free(_frame);
-    _frame = NULL;
-  }
-  if (_frame_out) {
-    av_free(_frame_out);
-    _frame_out = NULL;
-  }
-  
-  _next_frame_number = 0;
-}
-
 ////////////////////////////////////////////////////////////////////
-//     Function: FFMpegTexture::VideoStream::read_video_frame
-//       Access: Private
-//  Description: Fills packet with the next sequential video frame in
-//               the stream, skipping over all non-video frames.
-//               packet must later be deallocated with
-//               av_free_packet().
-//
-//               Returns nonnegative on success, or negative on error.
-////////////////////////////////////////////////////////////////////
-int FFMpegTexture::VideoStream::
-read_video_frame(AVPacket *packet) {
-  int err = av_read_frame(_format_context, packet);
-  if (err < 0) {
-    return err;
-  }
-
-  while (packet->stream_index != _stream_number) {
-    // It's not a video packet; free it and get another.
-    av_free_packet(packet);
-
-    err = av_read_frame(_format_context, packet);
-    if (err < 0) {
-      grutil_cat.debug()
-        << "Got error " << err << " reading frame.\n";
-      return err;
-    }
-  }
-
-  // This is a video packet, return it.
-  return err;
+//     Function: MovieTexture::reload_ram_image
+//       Access: Protected, Virtual
+//  Description: A MovieTexture must always keep its ram image, 
+//               since there is no way to reload it from the 
+//               source MovieVideo.
+////////////////////////////////////////////////////////////////////
+void MovieTexture::
+reload_ram_image() {
+  // A MovieTexture should never dump its RAM image.
+  // Therefore, this is not needed.
 }
 
-
-#endif  // HAVE_FFMpeg
-

+ 49 - 73
panda/src/grutil/movieTexture.h

@@ -1,5 +1,5 @@
-// Filename: openCVTexture.h
-// Created by:  zacpavlov (19Aug05)
+// Filename: movieTexture.h
+// Created by: jyelon (01Aug2007)
 //
 ////////////////////////////////////////////////////////////////////
 //
@@ -16,97 +16,76 @@
 //
 ////////////////////////////////////////////////////////////////////
 
-#ifndef FFMPEGTEXTURE_H
-#define FFMPEGTEXTURE_H
+#ifndef MOVIETEXTURE_H
+#define MOVIETEXTURE_H
 
 #include "pandabase.h"
-#ifdef HAVE_FFMPEG
-
-#include "videoTexture.h"
-
-#include "avcodec.h"
-#include "avformat.h"
+#include "movie.h"
+#include "movieVideo.h"
+#include "movieAudio.h"
 
 ////////////////////////////////////////////////////////////////////
-//       Class : OpenCVTexture
-// Description : A specialization on VideoTexture that takes its input
-//               using the CV library, to produce an animated texture,
-//               with its source taken from an .avi file or from a
-//               camera input.
+//       Class : MovieTexture
+// Description : A texture that fetches video frames from an
+//               underlying object of class Movie.
 ////////////////////////////////////////////////////////////////////
-class EXPCL_PANDA_GRUTIL FFMpegTexture : public VideoTexture {
+class EXPCL_PANDA_GRUTIL MovieTexture : public Texture {
 PUBLISHED:
-  FFMpegTexture(const string &name = string());
+  MovieTexture(const string &name);
 protected:
-  FFMpegTexture(const FFMpegTexture &copy);
+  MovieTexture(const MovieTexture &copy);
 PUBLISHED:
-  virtual ~FFMpegTexture();
+  virtual ~MovieTexture();
 
   virtual PT(Texture) make_copy();
 
+  INLINE int get_video_width() const;
+  INLINE int get_video_height() const;
+  INLINE LVecBase2f get_tex_scale() const;
+
 public:
   static PT(Texture) make_texture();
-
+  virtual bool has_cull_callback() const;
+  virtual bool cull_callback(CullTraverser *trav, const CullTraverserData &data) const;
+ 
 protected:
-  virtual void update_frame(int frame);
+  virtual void reload_ram_image();
+  virtual bool get_keep_ram_image() const;
   virtual bool do_read_one(const Filename &fullpath, const Filename &alpha_fullpath,
                            int z, int n, int primary_file_num_channels, int alpha_file_channel,
                            bool header_only, BamCacheRecord *record);
   virtual bool do_load_one(const PNMImage &pnmimage, const string &name,
                            int z, int n);
 
-private:    
-  class VideoPage;
-  class VideoStream;
-
-  VideoPage &modify_page(int z);
-  bool reconsider_video_properties(const VideoStream &stream, 
-                                   int num_components, int z);
-  void do_update();
-    
-  class VideoStream {
-  public:
-    VideoStream();
-    VideoStream(const VideoStream &copy);
-    ~VideoStream();
-
-    bool read(const Filename &filename);
-    void clear();
-    INLINE bool is_valid() const;
-    INLINE bool is_from_file() const;
-    bool get_frame_data(int frame);
-
-  private:
-    int read_video_frame(AVPacket *packet);
-
+  class VideoPage {
   public:
-    AVCodecContext *_codec_context; 
-    AVFormatContext *_format_context; 
-    
-    int _stream_number;
-    AVFrame *_frame;
-    AVFrame *_frame_out;
-
-    Filename _filename;
-    int _next_frame_number;
-    int _image_size_bytes;
-
-  private:
-    unsigned char * _raw_data;
-    AVCodec *_codec;
+    VideoPage();
+    PT(MovieVideo) _color;
+    PT(MovieVideo) _alpha;
+    double _base_clock;
   };
+  
+  typedef pvector<VideoPage> Pages;
 
-  class VideoPage {
+  class EXPCL_PANDA_GRUTIL CData : public CycleData {
   public:
-    INLINE VideoPage();
-    INLINE VideoPage(const VideoPage &copy);
-    INLINE ~VideoPage();
-
-    VideoStream _color, _alpha;
+    CData();
+    CData(const CData &copy);
+    virtual CycleData *make_copy() const;
+    virtual TypeHandle get_parent_type() const {
+      return MovieTexture::get_class_type();
+    }
+
+    Pages _pages;
+    int _video_width;
+    int _video_height;
   };
 
-  typedef pvector<VideoPage> Pages;
-  Pages _pages;
+  PipelineCycler<CData> _cycler;
+  typedef CycleDataReader<CData> CDReader;
+  typedef CycleDataWriter<CData> CDWriter;
+  
+  void recalculate_image_properties(CDWriter &cdata);
 
 public:
   static void register_with_read_factory();
@@ -117,8 +96,8 @@ public:
   }
   static void init_type() {
     VideoTexture::init_type();
-    register_type(_type_handle, "FFMpegTexture",
-                  VideoTexture::get_class_type());
+    register_type(_type_handle, "MovieTexture",
+                  Texture::get_class_type());
   }
   virtual TypeHandle get_type() const {
     return get_class_type();
@@ -129,10 +108,7 @@ private:
   static TypeHandle _type_handle;
 };
 
-#include "ffmpegTexture.I"
-
-
+#include "movieTexture.I"
 
-#endif  // HAVE_OPENCV
 
 #endif

+ 92 - 0
panda/src/movies/movie.I

@@ -16,6 +16,98 @@
 //
 ////////////////////////////////////////////////////////////////////
 
+////////////////////////////////////////////////////////////////////
+//     Function: Movie::size_x
+//       Access: Published
+//  Description: Get the horizontal size of the movie.
+////////////////////////////////////////////////////////////////////
+INLINE int Movie::
+size_x() const {
+  return _size_x;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Movie::size_y
+//       Access: Published
+//  Description: Get the vertical size of the movie.
+////////////////////////////////////////////////////////////////////
+INLINE int Movie::
+size_y() const {
+  return _size_y;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Movie::get_num_components
+//       Access: Published
+//  Description: Indicates whether the movie is monochrome, color,
+//               or color-plus-alpha.
+//
+//               You can fetch the movie's contents into any texture,
+//               regardless of the texture's format.  However, it may
+//               be faster if the texture matches the movie.
+////////////////////////////////////////////////////////////////////
+INLINE int Movie::
+get_num_components() const {
+  return _num_components;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Movie::length
+//       Access: Published
+//  Description: Returns the length of the movie.
+//
+//               Some kinds of Movie, such as internet TV station, 
+//               might not have a predictable length.  In that case,
+//               the length will be set to a very large number: 1.0E10.
+//               If the internet TV station goes offline, the video
+//               or audio stream will set its abort flag.  Reaching the
+//               end of the movie (ie, the specified length) normally
+//               does not cause the abort flag to be set.
+//
+//               The video and audio streams produced by get_video and
+//               get_audio are always of unlimited duration - you can
+//               always read another video frame or another audio
+//               sample.  This is true even if the specified length
+//               is reached, or an abort is flagged. If either stream
+//               runs out of data, it will synthesize blank video
+//               frames and silent audio samples as necessary to
+//               satisfy read requests.
+//
+//               Some AVI files have incorrect length values encoded
+//               into them - usually, they're a second or two long or
+//               short.  When playing such an AVI using the Movie class,
+//               you may see a slightly truncated video, or a slightly
+//               elongated video (padded with black frames).  There are
+//               utilities out there to fix the length values in AVI
+//               files.
+//
+////////////////////////////////////////////////////////////////////
+INLINE double Movie::
+length() const {
+  return _length;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Movie::audio_rate
+//       Access: Public
+//  Description: Returns the audio sample rate, in samples per sec.
+////////////////////////////////////////////////////////////////////
+INLINE int Movie::
+audio_rate() const {
+  return _audio_rate;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Movie::audio_channels
+//       Access: Public
+//  Description: Returns the number of audio channels.  Ie, 1 for
+//               mono, 2 for stereo.
+////////////////////////////////////////////////////////////////////
+INLINE int Movie::
+audio_channels() const {
+  return _audio_channels;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: Movie::ignores_offset
 //       Access: Published

+ 25 - 9
panda/src/movies/movie.cxx

@@ -27,17 +27,22 @@ TypeHandle Movie::_type_handle;
 //     Function: Movie::Constructor
 //       Access: Published
 //  Description: This constructor returns a null/dummy movie --- 
-//               the video is plain blue, and the audio is silent.
-//               To get more interesting movie, you need to construct
-//               a subclass of this class.
+//               the video is flashing blue/white, and the audio is
+//               silent.  To get more interesting movie, you need to
+//               construct a subclass of this class.
 ////////////////////////////////////////////////////////////////////
 Movie::
 Movie(const string &name, double len) :
   Namable(name),
+  _size_x(1),
+  _size_y(1),
+  _num_components(3),
+  _length(len),
+  _audio_rate(8000),
+  _audio_channels(1),
   _ignores_offset(true),
   _dummy_video(true),
-  _dummy_audio(true),
-  _dummy_len(len)
+  _dummy_audio(true)
 {
 }
 
@@ -57,8 +62,8 @@ Movie::
 //               MovieVideo or subclass of MovieVideo.
 ////////////////////////////////////////////////////////////////////
 PT(MovieVideo) Movie::
-get_video(double offset) {
-  return new MovieVideo(get_name(), _dummy_len);
+get_video(double offset) const {
+  return new MovieVideo(get_name(), this);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -68,6 +73,17 @@ get_video(double offset) {
 //               MovieAudio or subclass of MovieAudio.
 ////////////////////////////////////////////////////////////////////
 PT(MovieAudio) Movie::
-get_audio(double offset) {
-  return new MovieAudio(get_name(), _dummy_len);
+get_audio(double offset) const {
+  return new MovieAudio(get_name(), this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Movie::load
+//       Access: Published, Static
+//  Description: Loads a movie from a file.
+////////////////////////////////////////////////////////////////////
+PT(Movie) Movie::
+load(const Filename &path) {
+  // For now, just return a dummy movie.
+  return new Movie("dummy",30.0);
 }

+ 28 - 6
panda/src/movies/movie.h

@@ -22,8 +22,9 @@
 #include "pandabase.h"
 #include "texture.h"
 #include "pointerTo.h"
-#include "movieVideo.h"
-#include "movieAudio.h"
+
+class MovieVideo;
+class MovieAudio;
 
 ////////////////////////////////////////////////////////////////////
 //       Class : Movie
@@ -31,25 +32,46 @@
 //               an audio and a video stream.  So that could include
 //               an AVI file, or an internet TV station.  It could
 //               also be an MP3 file paired with a dummy video stream.
+//
+//               Class Movie and anything derived from Movie must be
+//               immutable, for thread-safety reasons.  (However, the
+//               MovieVideo and MovieAudio objects constructed by
+//               get_video and get_audio do not need to be immutable
+//               or thread-safe).
 ////////////////////////////////////////////////////////////////////
 class EXPCL_PANDA_MOVIES Movie : public TypedWritableReferenceCount, public Namable{
 
 PUBLISHED:
   Movie(const string &name, double len);
+
+  INLINE int size_x() const;
+  INLINE int size_y() const;
+  INLINE int get_num_components() const;
+  INLINE double length() const;
+  INLINE int audio_rate() const;
+  INLINE int audio_channels() const;
+
   INLINE bool ignores_offset() const;
   INLINE bool dummy_video() const;
   INLINE bool dummy_audio() const;
-  virtual PT(MovieVideo) get_video(double offset=0.0);
-  virtual PT(MovieAudio) get_audio(double offset=0.0);
+
+  virtual PT(MovieVideo) get_video(double offset=0.0) const;
+  virtual PT(MovieAudio) get_audio(double offset=0.0) const;
+  static PT(Movie) load(const Filename &path);
   
 public:
   virtual ~Movie();
-  
+
 private:
+  int _size_x;
+  int _size_y;
+  int _num_components;
+  double _length;
+  int _audio_rate;
+  int _audio_channels;
   bool _ignores_offset;
   bool _dummy_video;
   bool _dummy_audio;
-  double _dummy_len;
   
 public:
   static TypeHandle get_class_type() {

+ 30 - 31
panda/src/movies/movieAudio.I

@@ -17,24 +17,43 @@
 ////////////////////////////////////////////////////////////////////
 
 ////////////////////////////////////////////////////////////////////
-//     Function: MovieAudio::rate
+//     Function: MovieAudio::get_source
 //       Access: Public
-//  Description: Returns the audio sample rate, in samples per sec.
+//  Description: Returns the source Movie.
+////////////////////////////////////////////////////////////////////
+INLINE CPT(Movie) MovieAudio::
+get_source() const {
+  return _source;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MovieAudio::audio_rate
+//       Access: Public
+//  Description: See Movie::audio_rate.
 ////////////////////////////////////////////////////////////////////
 INLINE int MovieAudio::
-rate() const {
-  return _rate;
+audio_rate() const {
+  return _source->audio_rate();
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: MovieAudio::channels
+//     Function: MovieAudio::audio_channels
 //       Access: Public
-//  Description: Returns the number of audio channels.  Ie, 1 for
-//               mono, 2 for stereo.
+//  Description: See Movie::audio_channels.
 ////////////////////////////////////////////////////////////////////
 INLINE int MovieAudio::
-channels() const {
-  return _channels;
+audio_channels() const {
+  return _source->audio_channels();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MovieAudio::length
+//       Access: Public
+//  Description: See Movie::length.
+////////////////////////////////////////////////////////////////////
+INLINE double MovieAudio::
+length() const {
+  return _source->length();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -54,28 +73,8 @@ samples_read() const {
 //  Description: Skip audio samples from the stream.  This is mostly
 //               for debugging purposes.
 ////////////////////////////////////////////////////////////////////
-INLINE int MovieAudio::
+INLINE void MovieAudio::
 skip_samples(int n) {
-  return read_samples(n, 0);
+  read_samples(n, 0);
 }
  
-////////////////////////////////////////////////////////////////////
-//     Function: MovieAudio::approx_len
-//       Access: Public
-//  Description: Returns a "best guess" amount of the stream length.
-//               For ffmpeg streams, this is usually a pretty close
-//               approximation --- ie, off by a few hundred samples.
-//               For infinite streams, always returns the constant
-//               value 1.0E10.  Caution: it is legal for a stream to
-//               periodically update its estimate of the length!
-//
-//               Stream lengths cannot be determined with accuracy,
-//               because the lengths encoded in AVI files are
-//               generally inaccurate, and some streams (ie, streaming
-//               internet audio) have no knowable length.
-////////////////////////////////////////////////////////////////////
-INLINE double MovieAudio::
-approx_len() const {
-  return _approx_len;
-}
-

+ 11 - 35
panda/src/movies/movieAudio.cxx

@@ -12,7 +12,7 @@
 // the license at http://etc.cmu.edu/panda3d/docs/license/ .
 //
 // To contact the maintainers of this program write to
-// [email protected] .
+// [email protected] 
 //
 ////////////////////////////////////////////////////////////////////
 
@@ -29,12 +29,10 @@ TypeHandle MovieAudio::_type_handle;
 //               a subclass of this class.
 ////////////////////////////////////////////////////////////////////
 MovieAudio::
-MovieAudio(const string &name, double len) :
+MovieAudio(const string &name, CPT(Movie) source) :
   Namable(name),
-  _rate(8000),
-  _channels(1),
-  _samples_read(0),
-  _approx_len(len)
+  _source(source),
+  _samples_read(0)
 {
 }
 
@@ -52,44 +50,22 @@ MovieAudio::
 //       Access: Public, Virtual
 //  Description: Read audio samples from the stream.  N is the
 //               number of samples you wish to read.  Your buffer
-//               must be at least as large as N * channels.  
-//               Multiple-channel audio will be interleaved. Returns
-//               the actual number of samples read.  This will always
-//               be equal to N unless end-of-stream has been reached.
-//               It is legal to pass a null pointer, in this case,
-//               the samples are discarded.
+//               must be equal in size to N * channels.  
+//               Multiple-channel audio will be interleaved. 
 ////////////////////////////////////////////////////////////////////
-int MovieAudio::
+void MovieAudio::
 read_samples(int n, PN_int16 *data) {
 
   // This is the null implementation, which generates pure silence.
   // Normally, this method will be overridden by a subclass.
 
-  if (n < 0) {
-    return 0;
+  if (n <= 0) {
+    return;
   }
 
-  // Convert length to an integer sample count.
-  // This could generate an integer-overflow, in this case,
-  // just set the integer length to maxint (74 hrs).
-  int ilen;
-  if (_approx_len < 268000.0) {
-    ilen = _approx_len * 8000.0;
-  } else {
-    ilen = 0x7FFFFFFF;
-  }
-
-  // Generate and return samples.
-  int remain = ilen - _samples_read;
-  if (n > remain) {
-    n = remain;
-  }
-  if (data) {
-    for (int i=0; i<n; i++) {
-      data[i] = 0;
-    }
+  for (int i=0; i<n; i++) {
+    data[i] = 0;
   }
   _samples_read += n;
-  return n;
 }
 

+ 11 - 11
panda/src/movies/movieAudio.h

@@ -23,6 +23,7 @@
 #include "namable.h"
 #include "texture.h"
 #include "pointerTo.h"
+#include "movie.h"
 
 ////////////////////////////////////////////////////////////////////
 //       Class : MovieAudio
@@ -31,22 +32,21 @@
 class EXPCL_PANDA_MOVIES MovieAudio : public TypedWritableReferenceCount, public Namable {
 
 PUBLISHED:
-  MovieAudio(const string &name, double len);
-  INLINE int rate() const;
-  INLINE int channels() const;
+  MovieAudio(const string &name, CPT(Movie) source);
+  INLINE CPT(Movie) get_source() const;
+  INLINE int audio_rate() const;
+  INLINE int audio_channels() const;
+  INLINE double length() const;
   INLINE int samples_read() const;
-  INLINE double approx_len() const;
-  INLINE int skip_samples(int n);
+  INLINE void skip_samples(int n);
   
 public:
-  virtual int read_samples(int n, PN_int16 *data);
+  virtual void read_samples(int n, PN_int16 *data);
   virtual ~MovieAudio();
-
-private:
-  int _rate;
-  int _channels;
+  
+protected:
+  CPT(Movie) _source;
   int _samples_read;
-  double _approx_len;
   
 public:
   static TypeHandle get_class_type() {

+ 54 - 26
panda/src/movies/movieVideo.I

@@ -16,67 +16,95 @@
 //
 ////////////////////////////////////////////////////////////////////
 
+////////////////////////////////////////////////////////////////////
+//     Function: MovieVideo::get_source
+//       Access: Published
+//  Description: Returns a pointer to the source Movie.
+////////////////////////////////////////////////////////////////////
+INLINE CPT(Movie) MovieVideo::
+get_source() const {
+  return _source;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: MovieVideo::size_x
 //       Access: Published
-//  Description: Get the horizontal size of the movie.
+//  Description: See Movie::size_x
 ////////////////////////////////////////////////////////////////////
 INLINE int MovieVideo::
 size_x() const {
-  return _size_x;
+  return _source->size_x();
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: MovieVideo::size_y
 //       Access: Published
-//  Description: Get the vertical size of the movie.
+//  Description: See Movie::size_y
 ////////////////////////////////////////////////////////////////////
 INLINE int MovieVideo::
 size_y() const {
-  return _size_y;
+  return _source->size_y();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MovieVideo::get_num_components
+//       Access: Published
+//  Description: See Movie::get_num_components
+////////////////////////////////////////////////////////////////////
+INLINE int MovieVideo::
+get_num_components() const {
+  return _source->get_num_components();
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: MovieVideo::at_end
+//     Function: MovieVideo::length
 //       Access: Published
-//  Description: Returns true if there is no video left to fetch.
-//               
-//               It is not an error to call 'fetch' when at_end, 
-//               doing so will yield a black frame of one second
-//               duration.
+//  Description: See Movie::length
+////////////////////////////////////////////////////////////////////
+INLINE int MovieVideo::
+length() const {
+  return _source->length();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MovieVideo::aborted
+//       Access: Published
+//  Description: Returns true if the video has aborted prematurely.
+//               For example, this could occur if the Movie was actually
+//               an internet TV station, and the connection was lost.
+//               Reaching the normal end of the video does not
+//               constitute an 'abort' condition.
 ////////////////////////////////////////////////////////////////////
 INLINE bool MovieVideo::
-at_end() const {
-  return _at_end;
+aborted() const {
+  return _aborted;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: MovieVideo::next_start
+//     Function: MovieVideo::last_start
 //       Access: Published
-//  Description: Indicates the time at which the current frame should
-//               stop being displayed and when the next frame should
-//               start being displayed.  This time is relative to the
-//               start of the stream.
+//  Description: Returns the start time of the last frame you read.
 //
 //               MovieVideo streams have variable frame rates.  Each
 //               frame will specify how long it is to be displayed.
 //               These lengths may not be equal from frame to frame.
 ////////////////////////////////////////////////////////////////////
 INLINE double MovieVideo::
-next_start() const {
-  return _next_start;
+last_start() const {
+  return _last_start;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: MovieVideo::approx_len
+//     Function: MovieVideo::next_start
 //       Access: Published
-//  Description: Get the approximate length of the movie.
-//               This is not guaranteed to be accurate. The only
-//               accurate way to determine when the movie is over
-//               is to check the at_end flag.
+//  Description: Returns the start time of the next frame you can read.
+//
+//               MovieVideo streams have variable frame rates.  Each
+//               frame will specify how long it is to be displayed.
+//               These lengths may not be equal from frame to frame.
 ////////////////////////////////////////////////////////////////////
 INLINE double MovieVideo::
-approx_len() const {
-  return _approx_len;
+next_start() const {
+  return _next_start;
 }
 

+ 165 - 40
panda/src/movies/movieVideo.cxx

@@ -30,20 +30,13 @@ TypeHandle MovieVideo::_type_handle;
 //               to construct a subclass of this class.
 ////////////////////////////////////////////////////////////////////
 MovieVideo::
-MovieVideo(const string &name, double len) :
+MovieVideo(const string &name, CPT(Movie) source) :
   Namable(name),
-  _size_x(1),
-  _size_y(1),
-  _at_end(false),
-  _next_start(0.0),
-  _approx_len(len)
+  _source(source),
+  _aborted(false),
+  _curr_start(-1.0),
+  _next_start(0.0)
 {
-  if (len < 0.0) {
-    _approx_len = 0.0;
-  }
-  if (_approx_len == 0.0) {
-    _at_end = true;
-  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -53,45 +46,177 @@ MovieVideo(const string &name, double len) :
 ////////////////////////////////////////////////////////////////////
 MovieVideo::
 ~MovieVideo() {
+  if (_conversion_buffer != 0) {
+    delete[] _conversion_buffer;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MovieVideo::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 MovieVideo::
+allocate_conversion_buffer() {
+  if (_conversion_buffer == 0) {
+    _conversion_buffer = new unsigned char[size_x() * size_y() * 4];
+  }
+}
+  
+////////////////////////////////////////////////////////////////////
+//     Function: MovieVideo::seek_ahead
+//       Access: Published, Virtual
+//  Description: Seeks forward until the next frame contains time t.
+//               Also updates last_start and next_start.
+//               It is an error to pass in a value less than
+//               next_start (that would be a backward seek).
+//
+//               Arbitrary seeking (both forward and backward) can
+//               be accomplished by fetching the original Movie
+//               object, and then calling get_video with an offset.
+//               However, unlike seek_ahead, the result is not
+//               guaranteed to be quite precise, because AVI files
+//               often have inaccurate indices.
+////////////////////////////////////////////////////////////////////
+void MovieVideo::
+seek_ahead(double t) {
+
+  // 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.
+
+  nassertv(t >= _next_start);
+  _next_start = floor(t);
+  _last_start = _next_start - 1.0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MovieVideo::fetch_into_texture
+//       Access: Published, Virtual
+//  Description: Fetch the next frame into a page of a texture.
+////////////////////////////////////////////////////////////////////
+void MovieVideo::
+fetch_into_texture(Texture *t, int page) {
+
+  // This generic implementation is layered on fetch_into_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
+  // on the capabilities of the underlying codec software.
+
+  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_z_size());
+  
+  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(data, t->get_num_components() == 4);
+  } else {
+    allocate_conversion_buffer();
+    fetch_into_buffer(_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;
+    }
+  }
 }
- 
+
 ////////////////////////////////////////////////////////////////////
-//     Function: MovieVideo::fetch_into
+//     Function: MovieVideo::fetch_into_texture_alpha
 //       Access: Published, Virtual
-//  Description: Load the next frame into a texture's ram image.
-//               Advances the frame pointer.
+//  Description: Fetch the next frame into the alpha channel
+//               of a texture.
 ////////////////////////////////////////////////////////////////////
 void MovieVideo::
-fetch_into(Texture *t) {
+fetch_into_texture_alpha(Texture *t, int page, int alpha_src) {
+
+  // This generic implementation is layered on fetch_into_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
+  // on the capabilities of the underlying codec software.
+
+  nassertv(t->get_x_size() >= size_x());
+  nassertv(t->get_y_size() >= size_y());
+  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));
 
-  // The following is the implementation of the null video
-  // stream --- a stream of solid blue frames.  Normally,
-  // this method will be overridden by the subclass.
+  allocate_conversion_buffer();
+  
+  fetch_into_buffer(_conversion_buffer, true);
   
-  t->setup_texture(Texture::TT_2d_texture, 1, 1, 1,
-                   Texture::T_unsigned_byte, Texture::F_rgba);
   PTA_uchar img = t->modify_ram_image();
   
-  int frame_index = (int)_next_start;
-  if (_at_end) {
-    img.set_element(0,0);
-    img.set_element(1,0);
-    img.set_element(2,0);
-    img.set_element(3,255);
-  } else if (frame_index & 1) {
-    img.set_element(0,128);
-    img.set_element(1,128);
-    img.set_element(2,255);
-    img.set_element(3,255);
+  unsigned char *data = img.p() + page * t->get_expected_ram_page_size();
+  
+  int src_stride = size_x() * 4;
+  int dst_stride = t->get_x_size() * 4;
+  if (alpha_src == 0) {
+    unsigned char *p = _conversion_buffer;
+    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;
+      }
+      data += dst_stride;
+      p += src_stride;
+    }
   } else {
-    img.set_element(0,255);
-    img.set_element(1,255);
-    img.set_element(2,255);
-    img.set_element(3,255);
+    alpha_src -= 1;
+    unsigned char *p = _conversion_buffer;
+    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 += dst_stride;
+      p += src_stride;
+    }
   }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MovieVideo::fetch_into_buffer
+//       Access: Published, Virtual
+//  Description: Fetch the next frame into a supplied RGB8 or RGBA8
+//               buffer.
+////////////////////////////////////////////////////////////////////
+void MovieVideo::
+fetch_into_buffer(unsigned char *data, bool rgba) {
   
-  _next_start = _next_start + 1.0;
-  if (_next_start > _approx_len) {
-    _at_end = true;
+  // 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.
+
+  if (_next_start >= length()) {
+    data[0] = 0;
+    data[1] = 0;
+    data[2] = 0;
+  } else if (((int)_next_start) & 1) {
+    data[0] = 255;
+    data[1] = 128;
+    data[2] = 128;
+  } else {
+    data[0] = 128;
+    data[1] = 128;
+    data[2] = 255;
+  }
+  if (rgba) {
+    data[3] = 255;
   }
+  
+  _curr_start = _next_start;
+  _next_start = _next_start + 1.0;
 }
+

+ 21 - 12
panda/src/movies/movieVideo.h

@@ -22,6 +22,7 @@
 #include "pandabase.h"
 #include "texture.h"
 #include "pointerTo.h"
+#include "movie.h"
 
 ////////////////////////////////////////////////////////////////////
 //       Class : MovieVideo
@@ -30,23 +31,31 @@
 class EXPCL_PANDA_MOVIES MovieVideo : public TypedWritableReferenceCount, public Namable {
 
  PUBLISHED:
-  MovieVideo(const string &name, double len);
+  MovieVideo(const string &name, CPT(Movie) source);
+  virtual ~MovieVideo();
+  INLINE CPT(Movie) get_source() const;
   INLINE int size_x() const;
   INLINE int size_y() const;
-  INLINE bool at_end() const;
-  INLINE double approx_len() const;
+  INLINE int get_num_components() const;
+  INLINE int length() const;
+  INLINE bool aborted() const;
+  INLINE double last_start() const;
   INLINE double next_start() const;
-  virtual void fetch_into(Texture *t);
-  
- public:
-  virtual ~MovieVideo();
-  
+  virtual void seek_ahead(double t);
+  virtual void fetch_into_texture(Texture *t, int page);
+  virtual void fetch_into_texture_alpha(Texture *t, int page, int alpha_src);
+  virtual void fetch_into_buffer(unsigned char *block, bool rgba);
+
  private:
-  int _size_x;
-  int _size_y;
-  bool _at_end;
+  void allocate_conversion_buffer();
+  unsigned char *_conversion_buffer;
+  
+ protected:
+  CPT(Movie) _source;
+  bool _aborted;
+  double _last_start;
+  double _curr_start;
   double _next_start;
-  double _approx_len;
   
 public:
   static TypeHandle get_class_type() {