diff --git a/doc/makepanda/makepanda.py b/doc/makepanda/makepanda.py index 09646c4ca5..a704e1681f 100755 --- a/doc/makepanda/makepanda.py +++ b/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/ # diff --git a/panda/src/grutil/config_grutil.cxx b/panda/src/grutil/config_grutil.cxx index f2810f36be..edd05e8c53 100644 --- a/panda/src/grutil/config_grutil.cxx +++ b/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 { diff --git a/panda/src/grutil/grutil_composite1.cxx b/panda/src/grutil/grutil_composite1.cxx index dbefed87ee..a354d5d355 100644 --- a/panda/src/grutil/grutil_composite1.cxx +++ b/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" diff --git a/panda/src/grutil/movieTexture.I b/panda/src/grutil/movieTexture.I index dc4e6b6e8f..260b1ece5c 100644 --- a/panda/src/grutil/movieTexture.I +++ b/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: +// 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() { +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::Copy Constructor -// Access: Public -// Description: -//////////////////////////////////////////////////////////////////// -INLINE FFMpegTexture::VideoPage:: -VideoPage(const FFMpegTexture::VideoPage ©) : - _color(copy._color) -{ -} - -//////////////////////////////////////////////////////////////////// -// Function: FFMpegTexture::VideoPage::Destructor -// Access: Public -// Description: -//////////////////////////////////////////////////////////////////// -INLINE FFMpegTexture::VideoPage:: -~VideoPage() { -} diff --git a/panda/src/grutil/movieTexture.cxx b/panda/src/grutil/movieTexture.cxx index 00ae4f90cf..61fec435e8 100644 --- a/panda/src/grutil/movieTexture.cxx +++ b/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 ©) : + _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 ©) : - VideoTexture(copy), - _pages(copy._pages) +MovieTexture:: +MovieTexture(const MovieTexture ©) : + Texture(copy) { + // Since 'get_video' can be a slow operation, + // I release the read lock before calling get_video. + + pvector color; + pvector 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]; + return new MovieTexture(*this); } //////////////////////////////////////////////////////////////////// -// 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; -} - -//////////////////////////////////////////////////////////////////// -// 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. +// Function: MovieTexture::VideoPage::Constructor +// Access: Private +// Description: Creates a completely blank video page. //////////////////////////////////////////////////////////////////// -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); +MovieTexture::VideoPage:: +VideoPage() : + _color(0), + _alpha(0) +{ +} - // 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); - - // 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: +// 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. //////////////////////////////////////////////////////////////////// -FFMpegTexture::VideoStream:: -VideoStream() : - _codec_context(NULL), - _format_context(NULL), - _frame(NULL), - _frame_out(NULL), - _next_frame_number(0) -{ - // printf("creating video stream\n"); -} - -//////////////////////////////////////////////////////////////////// -// Function: FFMpegTexture::VideoStream::Copy Constructor -// Access: Public -// Description: -//////////////////////////////////////////////////////////////////// -FFMpegTexture::VideoStream:: -VideoStream(const FFMpegTexture::VideoStream ©) : - _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(); -} - -//////////////////////////////////////////////////////////////////// -// 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; - } - _codec_context->hurry_up = false; - } - - // Now we're ready to read a frame. - int err = read_video_frame(&packet); - if (err < 0) { - return false; - } - - } 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); - } - } - } - - // Free the packet that was allocated by av_read_frame - av_free_packet(&packet); - +bool MovieTexture:: +has_cull_callback() const { 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::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 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; +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); + } + color->fetch_into_texture((MovieTexture*)this, i); + } + 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); } } - - 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; - return true; } - - //////////////////////////////////////////////////////////////////// -// Function: FFMpegTexture::VideoStream::clear -// Access: Public -// Description: Stops the video playback and frees the associated -// resources. +// 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. //////////////////////////////////////////////////////////////////// -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; +bool MovieTexture:: +get_keep_ram_image() const { + // A MovieTexture should never dump its RAM image. + return true; } //////////////////////////////////////////////////////////////////// -// 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. +// 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. //////////////////////////////////////////////////////////////////// -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; +void MovieTexture:: +reload_ram_image() { + // A MovieTexture should never dump its RAM image. + // Therefore, this is not needed. } - -#endif // HAVE_FFMpeg - diff --git a/panda/src/grutil/movieTexture.h b/panda/src/grutil/movieTexture.h index 8e6b443247..153b0d2bf8 100644 --- a/panda/src/grutil/movieTexture.h +++ b/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 ©); + MovieTexture(const MovieTexture ©); 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 ©); - ~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); - - 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; - }; - class VideoPage { public: - INLINE VideoPage(); - INLINE VideoPage(const VideoPage ©); - INLINE ~VideoPage(); + VideoPage(); + PT(MovieVideo) _color; + PT(MovieVideo) _alpha; + double _base_clock; + }; + + typedef pvector Pages; - VideoStream _color, _alpha; + class EXPCL_PANDA_GRUTIL CData : public CycleData { + public: + CData(); + CData(const CData ©); + 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 Pages; - Pages _pages; + PipelineCycler _cycler; + typedef CycleDataReader CDReader; + typedef CycleDataWriter 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 diff --git a/panda/src/movies/movie.I b/panda/src/movies/movie.I index a4eef1541b..86c992d377 100644 --- a/panda/src/movies/movie.I +++ b/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 diff --git a/panda/src/movies/movie.cxx b/panda/src/movies/movie.cxx index 4264bc23e7..9c6c9b761d 100644 --- a/panda/src/movies/movie.cxx +++ b/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); } diff --git a/panda/src/movies/movie.h b/panda/src/movies/movie.h index 6b07cf6b33..1b9e33ab4c 100644 --- a/panda/src/movies/movie.h +++ b/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() { diff --git a/panda/src/movies/movieAudio.I b/panda/src/movies/movieAudio.I index e7bdce12ad..409bbd038f 100644 --- a/panda/src/movies/movieAudio.I +++ b/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 int MovieAudio:: -rate() const { - return _rate; +INLINE CPT(Movie) MovieAudio:: +get_source() const { + return _source; } //////////////////////////////////////////////////////////////////// -// Function: MovieAudio::channels +// Function: MovieAudio::audio_rate // Access: Public -// Description: Returns the number of audio channels. Ie, 1 for -// mono, 2 for stereo. +// Description: See Movie::audio_rate. //////////////////////////////////////////////////////////////////// INLINE int MovieAudio:: -channels() const { - return _channels; +audio_rate() const { + return _source->audio_rate(); +} + +//////////////////////////////////////////////////////////////////// +// Function: MovieAudio::audio_channels +// Access: Public +// Description: See Movie::audio_channels. +//////////////////////////////////////////////////////////////////// +INLINE int MovieAudio:: +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; -} - diff --git a/panda/src/movies/movieAudio.cxx b/panda/src/movies/movieAudio.cxx index 95bb18106d..082804aa40 100644 --- a/panda/src/movies/movieAudio.cxx +++ b/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 -// panda3d-general@lists.sourceforge.net . +// panda3d-general@lists.sourceforge.net // //////////////////////////////////////////////////////////////////// @@ -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; isize_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::at_end +// Function: MovieVideo::get_num_components // 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::get_num_components +//////////////////////////////////////////////////////////////////// +INLINE int MovieVideo:: +get_num_components() const { + return _source->get_num_components(); +} + +//////////////////////////////////////////////////////////////////// +// Function: MovieVideo::length +// Access: Published +// 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::last_start +// Access: Published +// 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:: +last_start() const { + return _last_start; } //////////////////////////////////////////////////////////////////// // Function: MovieVideo::next_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 next frame you can read. // // MovieVideo streams have variable frame rates. Each // frame will specify how long it is to be displayed. @@ -67,16 +108,3 @@ next_start() const { return _next_start; } -//////////////////////////////////////////////////////////////////// -// Function: MovieVideo::approx_len -// 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. -//////////////////////////////////////////////////////////////////// -INLINE double MovieVideo:: -approx_len() const { - return _approx_len; -} - diff --git a/panda/src/movies/movieVideo.cxx b/panda/src/movies/movieVideo.cxx index b1a7c5db34..cb30b6feec 100644 --- a/panda/src/movies/movieVideo.cxx +++ b/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::fetch_into -// Access: Published, Virtual -// Description: Load the next frame into a texture's ram image. -// Advances the frame pointer. +// 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:: -fetch_into(Texture *t) { - - // 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. - - 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); - } else { - img.set_element(0,255); - img.set_element(1,255); - img.set_element(2,255); - img.set_element(3,255); - } - - _next_start = _next_start + 1.0; - if (_next_start > _approx_len) { - _at_end = true; +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; yget_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)); + + allocate_conversion_buffer(); + + fetch_into_buffer(_conversion_buffer, 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 dst_stride = t->get_x_size() * 4; + if (alpha_src == 0) { + unsigned char *p = _conversion_buffer; + for (int y=0; y= 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; +} + diff --git a/panda/src/movies/movieVideo.h b/panda/src/movies/movieVideo.h index 588ec58f1d..2f36f3aa80 100644 --- a/panda/src/movies/movieVideo.h +++ b/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() {