More progress on Movie/MovieTexture
This commit is contained in:
parent
506cf473cf
commit
84c44e8872
|
|
@ -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/
|
||||
#
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<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];
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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<VideoPage> 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<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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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; i<n; i++) {
|
||||
data[i] = 0;
|
||||
}
|
||||
for (int i=0; i<n; i++) {
|
||||
data[i] = 0;
|
||||
}
|
||||
_samples_read += n;
|
||||
return n;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -16,47 +16,88 @@
|
|||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// 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::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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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; y<size_y(); y++) {
|
||||
memcpy(data, p, src_stride);
|
||||
data += dst_stride;
|
||||
p += src_stride;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MovieVideo::fetch_into_texture_alpha
|
||||
// Access: Published, Virtual
|
||||
// Description: Fetch the next frame into the alpha channel
|
||||
// of a texture.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void MovieVideo::
|
||||
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));
|
||||
|
||||
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<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 {
|
||||
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) {
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue