support threaded ffmpeg decoding, fix a couple of ffmpeg bugs
This commit is contained in:
parent
38618c17fb
commit
82e1b6703a
|
|
@ -52,3 +52,31 @@ get_video_height() const {
|
|||
return cdata->_video_height;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MovieTexture::get_color_cursor
|
||||
// Access: Published
|
||||
// Description: Returns the MovieVideoCursor that is feeding the
|
||||
// color channels for the indicated page, where 0 <=
|
||||
// page < get_num_pages().
|
||||
////////////////////////////////////////////////////////////////////
|
||||
INLINE MovieVideoCursor *MovieTexture::
|
||||
get_color_cursor(int page) {
|
||||
CDReader cdata(_cycler);
|
||||
nassertr(page >= 0 && page < (int)cdata->_pages.size(), NULL);
|
||||
return cdata->_pages[page]._color;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MovieTexture::get_alpha_cursor
|
||||
// Access: Published
|
||||
// Description: Returns the MovieVideoCursor that is feeding the
|
||||
// alpha channel for the indicated page, where 0 <=
|
||||
// page < get_num_pages().
|
||||
////////////////////////////////////////////////////////////////////
|
||||
INLINE MovieVideoCursor *MovieTexture::
|
||||
get_alpha_cursor(int page) {
|
||||
CDReader cdata(_cycler);
|
||||
nassertr(page >= 0 && page < (int)cdata->_pages.size(), NULL);
|
||||
return cdata->_pages[page]._alpha;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,9 @@ PUBLISHED:
|
|||
INLINE double get_video_length() const;
|
||||
INLINE int get_video_width() const;
|
||||
INLINE int get_video_height() const;
|
||||
|
||||
INLINE MovieVideoCursor *get_color_cursor(int page);
|
||||
INLINE MovieVideoCursor *get_alpha_cursor(int page);
|
||||
|
||||
void restart();
|
||||
void stop();
|
||||
|
|
|
|||
|
|
@ -44,6 +44,12 @@ ConfigureFn(config_movies) {
|
|||
init_libmovies();
|
||||
}
|
||||
|
||||
ConfigVariableInt ffmpeg_max_readahead_frames
|
||||
("ffmpeg-max-readahead-frames", 10,
|
||||
PRC_DESC("The maximum number of frames ahead which an ffmpeg decoder thread "
|
||||
"should read in advance of actual playback. Set this to 0 to "
|
||||
"decode ffmpeg videos in the main thread."));
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: init_libmovies
|
||||
// Description: Initializes the library. This must be called at
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ ConfigureDecl(config_movies, EXPCL_PANDA_MOVIES, EXPTP_PANDA_MOVIES);
|
|||
NotifyCategoryDecl(movies, EXPCL_PANDA_MOVIES, EXPTP_PANDA_MOVIES);
|
||||
NotifyCategoryDecl(ffmpeg, EXPCL_PANDA_MOVIES, EXPTP_PANDA_MOVIES);
|
||||
|
||||
extern ConfigVariableInt ffmpeg_max_readahead_frames;
|
||||
|
||||
extern EXPCL_PANDA_MOVIES void init_libmovies();
|
||||
|
||||
#endif /* __CONFIG_MOVIES_H__ */
|
||||
|
|
|
|||
|
|
@ -26,21 +26,29 @@ extern "C" {
|
|||
#include "pStatCollector.h"
|
||||
#include "pStatTimer.h"
|
||||
|
||||
Mutex FfmpegVideoCursor::_av_lock;
|
||||
TypeHandle FfmpegVideoCursor::_type_handle;
|
||||
|
||||
|
||||
#if LIBAVFORMAT_VERSION_MAJOR < 53
|
||||
#define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideoCursor::Default Constructor
|
||||
// Access: Protected
|
||||
// Access: Private
|
||||
// Description: This constructor is only used when reading from a bam
|
||||
// file.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
FfmpegVideoCursor::
|
||||
FfmpegVideoCursor() :
|
||||
_packet(NULL),
|
||||
_max_readahead_frames(0),
|
||||
_lock("FfmpegVideoCursor::_lock"),
|
||||
_action_cvar(_lock),
|
||||
_thread_status(TS_stopped),
|
||||
_seek_time(0.0),
|
||||
_packet0(NULL),
|
||||
_packet1(NULL),
|
||||
_format_ctx(NULL),
|
||||
_video_ctx(NULL),
|
||||
_video_index(-1),
|
||||
|
|
@ -52,13 +60,14 @@ FfmpegVideoCursor() :
|
|||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideoCursor::init_from
|
||||
// Access: Protected
|
||||
// Access: Private
|
||||
// Description: Specifies the source of the video cursor. This is
|
||||
// normally called only by the constructor or when
|
||||
// reading from a bam file.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void FfmpegVideoCursor::
|
||||
init_from(FfmpegVideo *source) {
|
||||
nassertv(_thread == NULL && _thread_status == TS_stopped);
|
||||
nassertv(source != NULL);
|
||||
_source = source;
|
||||
_filename = _source->get_filename();
|
||||
|
|
@ -108,18 +117,21 @@ init_from(FfmpegVideo *source) {
|
|||
return;
|
||||
}
|
||||
|
||||
AVCodec *pVideoCodec = avcodec_find_decoder(_video_ctx->codec_id);
|
||||
if (pVideoCodec == NULL) {
|
||||
movies_cat.info()
|
||||
<< "Couldn't find codec\n";
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
if (avcodec_open(_video_ctx, pVideoCodec) < 0) {
|
||||
movies_cat.info()
|
||||
<< "Couldn't open codec\n";
|
||||
cleanup();
|
||||
return;
|
||||
{
|
||||
MutexHolder av_holder(_av_lock);
|
||||
AVCodec *pVideoCodec = avcodec_find_decoder(_video_ctx->codec_id);
|
||||
if (pVideoCodec == NULL) {
|
||||
movies_cat.info()
|
||||
<< "Couldn't find codec\n";
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
if (avcodec_open(_video_ctx, pVideoCodec) < 0) {
|
||||
movies_cat.info()
|
||||
<< "Couldn't open codec\n";
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_size_x = _video_ctx->width;
|
||||
|
|
@ -129,30 +141,42 @@ init_from(FfmpegVideo *source) {
|
|||
_can_seek = true;
|
||||
_can_seek_fast = true;
|
||||
|
||||
_packet = new AVPacket;
|
||||
_frame = avcodec_alloc_frame();
|
||||
_frame_out = avcodec_alloc_frame();
|
||||
if ((_packet == 0)||(_frame == 0)||(_frame_out == 0)) {
|
||||
if ((_frame == 0)||(_frame_out == 0)) {
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
memset(_packet, 0, sizeof(AVPacket));
|
||||
_packet0 = new AVPacket;
|
||||
_packet1 = new AVPacket;
|
||||
memset(_packet0, 0, sizeof(AVPacket));
|
||||
memset(_packet1, 0, sizeof(AVPacket));
|
||||
|
||||
fetch_packet(0.0);
|
||||
_initial_dts = _packet->dts;
|
||||
_initial_dts = _packet0->dts;
|
||||
_packet_time = 0.0;
|
||||
_last_start = -1.0;
|
||||
_next_start = 0.0;
|
||||
_begin_time = -1.0;
|
||||
_end_time = 0.0;
|
||||
|
||||
#ifdef HAVE_THREADS
|
||||
set_max_readahead_frames(ffmpeg_max_readahead_frames);
|
||||
#endif // HAVE_THREADS
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideoCursor::Constructor
|
||||
// Access: Public
|
||||
// Access: Published
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
FfmpegVideoCursor::
|
||||
FfmpegVideoCursor(FfmpegVideo *src) :
|
||||
_packet(NULL),
|
||||
FfmpegVideoCursor(FfmpegVideo *src) :
|
||||
_max_readahead_frames(0),
|
||||
_lock("FfmpegVideoCursor::_lock"),
|
||||
_action_cvar(_lock),
|
||||
_thread_status(TS_stopped),
|
||||
_seek_time(0.0),
|
||||
_packet0(NULL),
|
||||
_packet1(NULL),
|
||||
_format_ctx(NULL),
|
||||
_video_ctx(NULL),
|
||||
_video_index(-1),
|
||||
|
|
@ -165,21 +189,246 @@ FfmpegVideoCursor(FfmpegVideo *src) :
|
|||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideoCursor::Destructor
|
||||
// Access: Public
|
||||
// Description: xxx
|
||||
// Access: Published
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
FfmpegVideoCursor::
|
||||
~FfmpegVideoCursor() {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideoCursor::set_max_readahead_frames
|
||||
// Access: Published
|
||||
// Description: Specifies the maximum number of frames that a
|
||||
// sub-thread will attempt to read ahead of the current
|
||||
// frame. Setting this to a nonzero allows the video
|
||||
// decoding to take place in a sub-thread, which
|
||||
// smoothes out the video decoding time by spreading it
|
||||
// evenly over several frames. Set this number larger
|
||||
// to increase the buffer between the currently visible
|
||||
// frame and the first undecoded frame; set it smaller
|
||||
// to reduce memory consumption.
|
||||
//
|
||||
// Setting this to zero forces the video to be decoded
|
||||
// in the main thread. If threading is not available in
|
||||
// the Panda build, this value is always zero.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void FfmpegVideoCursor::
|
||||
set_max_readahead_frames(int max_readahead_frames) {
|
||||
#ifndef HAVE_THREADS
|
||||
if (max_readahead_frames > 0) {
|
||||
ffmpeg_cat.warning()
|
||||
<< "Couldn't set max_readahead_frames to " << max_readahead_frames
|
||||
<< ": threading not available.\n";
|
||||
max_readahead_frames = 0;
|
||||
}
|
||||
#endif // HAVE_THREADS
|
||||
|
||||
_max_readahead_frames = max_readahead_frames;
|
||||
if (_max_readahead_frames > 0) {
|
||||
if (_thread_status == TS_stopped) {
|
||||
start_thread();
|
||||
}
|
||||
} else {
|
||||
if (_thread_status != TS_stopped) {
|
||||
stop_thread();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideoCursor::get_max_readahead_frames
|
||||
// Access: Published
|
||||
// Description: Returns the maximum number of frames that a
|
||||
// sub-thread will attempt to read ahead of the current
|
||||
// frame. See set_max_readahead_frames().
|
||||
////////////////////////////////////////////////////////////////////
|
||||
int FfmpegVideoCursor::
|
||||
get_max_readahead_frames() const {
|
||||
return _max_readahead_frames;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideoCursor::start_thread
|
||||
// Access: Published
|
||||
// Description: Explicitly starts the ffmpeg decoding thread after it
|
||||
// has been stopped by a call to stop_thread(). The
|
||||
// thread is normally started automatically, so there is
|
||||
// no need to call this method unless you have
|
||||
// previously called stop_thread() for some reason.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void FfmpegVideoCursor::
|
||||
start_thread() {
|
||||
MutexHolder holder(_lock);
|
||||
|
||||
if (_thread_status == TS_stopped && _max_readahead_frames > 0) {
|
||||
// Get a unique name for the thread's sync name.
|
||||
ostringstream strm;
|
||||
strm << (void *)this;
|
||||
_sync_name = strm.str();
|
||||
|
||||
// Create and start the thread object.
|
||||
_thread_status = TS_wait;
|
||||
_thread = new GenericThread(_filename.get_basename(), _sync_name, st_thread_main, this);
|
||||
if (!_thread->start(TP_normal, true)) {
|
||||
// Couldn't start the thread.
|
||||
_thread = NULL;
|
||||
_thread_status = TS_stopped;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideoCursor::stop_thread
|
||||
// Access: Published
|
||||
// Description: Explicitly stops the ffmpeg decoding thread. There
|
||||
// is normally no reason to do this unless you want to
|
||||
// maintain precise control over what threads are
|
||||
// consuming CPU resources. Calling this method will
|
||||
// make the video update in the main thread, regardless
|
||||
// of the setting of max_readahead_frames, until you
|
||||
// call start_thread() again.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void FfmpegVideoCursor::
|
||||
stop_thread() {
|
||||
if (_thread_status != TS_stopped) {
|
||||
PT(GenericThread) thread = _thread;
|
||||
{
|
||||
MutexHolder holder(_lock);
|
||||
if (_thread_status != TS_stopped) {
|
||||
_thread_status = TS_shutdown;
|
||||
}
|
||||
_action_cvar.notify();
|
||||
_thread = NULL;
|
||||
}
|
||||
|
||||
// Now that we've released the lock, we can join the thread.
|
||||
thread->join();
|
||||
}
|
||||
|
||||
// This is a good time to clean up all of the allocated frame
|
||||
// objects. It's not really necessary to be holding the lock, since
|
||||
// the thread is gone, but we'll grab it anyway just in case someone
|
||||
// else starts the thread up again.
|
||||
MutexHolder holder(_lock);
|
||||
|
||||
Buffers::iterator bi;
|
||||
for (bi = _readahead_frames.begin(); bi != _readahead_frames.end(); ++bi) {
|
||||
internal_free_buffer(*bi);
|
||||
}
|
||||
_readahead_frames.clear();
|
||||
for (bi = _recycled_frames.begin(); bi != _recycled_frames.end(); ++bi) {
|
||||
internal_free_buffer(*bi);
|
||||
}
|
||||
_recycled_frames.clear();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideoCursor::is_thread_started
|
||||
// Access: Published
|
||||
// Description: Returns true if the thread has been started, false if
|
||||
// not. This will always return false if
|
||||
// max_readahead_frames is 0.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
bool FfmpegVideoCursor::
|
||||
is_thread_started() const {
|
||||
return (_thread_status != TS_stopped);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideoCursor::fetch_buffer
|
||||
// Access: Public, Virtual
|
||||
// Description: See MovieVideoCursor::fetch_buffer.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
MovieVideoCursor::Buffer *FfmpegVideoCursor::
|
||||
fetch_buffer(double time) {
|
||||
MutexHolder holder(_lock);
|
||||
|
||||
// If there was an error at any point, just return NULL.
|
||||
if (_format_ctx == (AVFormatContext *)NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Buffer *frame = NULL;
|
||||
if (_thread_status == TS_stopped) {
|
||||
// Non-threaded case. Just get the next frame directly.
|
||||
frame = do_alloc_frame();
|
||||
|
||||
fetch_time(time);
|
||||
export_frame(frame);
|
||||
|
||||
} else {
|
||||
// Threaded case. Wait for the thread to serve up the required
|
||||
// frames.
|
||||
if (!_readahead_frames.empty()) {
|
||||
frame = _readahead_frames.front();
|
||||
_readahead_frames.pop_front();
|
||||
_action_cvar.notify();
|
||||
while (frame->_end_time < time && !_readahead_frames.empty()) {
|
||||
// This frame is too old. Discard it.
|
||||
if (ffmpeg_cat.is_debug()) {
|
||||
ffmpeg_cat.debug()
|
||||
<< "ffmpeg for " << _filename.get_basename()
|
||||
<< " at time " << time << ", discarding frame at "
|
||||
<< frame->_begin_time << "\n";
|
||||
}
|
||||
do_recycle_frame(frame);
|
||||
frame = _readahead_frames.front();
|
||||
_readahead_frames.pop_front();
|
||||
}
|
||||
if (frame->_begin_time > time) {
|
||||
// This frame is too new. Empty all remaining frames and seek
|
||||
// backwards.
|
||||
do_recycle_all_frames();
|
||||
if (_thread_status == TS_wait || _thread_status == TS_seek || _thread_status == TS_readahead) {
|
||||
_thread_status = TS_seek;
|
||||
_seek_time = time;
|
||||
_action_cvar.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (frame == NULL || frame->_end_time < time) {
|
||||
// No frame available, or the frame is too old. Seek.
|
||||
if (_thread_status == TS_wait || _thread_status == TS_seek || _thread_status == TS_readahead) {
|
||||
_thread_status = TS_seek;
|
||||
_seek_time = time;
|
||||
_action_cvar.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (frame != NULL && (frame->_end_time < time || frame->_begin_time > time)) {
|
||||
// The frame is too old or too new. Just recycle it.
|
||||
do_recycle_frame(frame);
|
||||
frame = NULL;
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideoCursor::release_buffer
|
||||
// Access: Public, Virtual
|
||||
// Description: Should be called after processing the Buffer object
|
||||
// returned by fetch_buffer(), this releases the Buffer
|
||||
// for future use again.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void FfmpegVideoCursor::
|
||||
release_buffer(Buffer *buffer) {
|
||||
MutexHolder holder(_lock);
|
||||
do_recycle_frame(buffer);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideoCursor::cleanup
|
||||
// Access: Public
|
||||
// Access: Private
|
||||
// Description: Reset to a standard inactive state.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void FfmpegVideoCursor::
|
||||
cleanup() {
|
||||
stop_thread();
|
||||
|
||||
if (_frame) {
|
||||
av_free(_frame);
|
||||
_frame = NULL;
|
||||
|
|
@ -191,15 +440,24 @@ cleanup() {
|
|||
_frame_out = NULL;
|
||||
}
|
||||
|
||||
if (_packet) {
|
||||
if (_packet->data) {
|
||||
av_free_packet(_packet);
|
||||
if (_packet0) {
|
||||
if (_packet0->data) {
|
||||
av_free_packet(_packet0);
|
||||
}
|
||||
delete _packet;
|
||||
_packet = NULL;
|
||||
delete _packet0;
|
||||
_packet0 = NULL;
|
||||
}
|
||||
|
||||
if (_packet1) {
|
||||
if (_packet1->data) {
|
||||
av_free_packet(_packet1);
|
||||
}
|
||||
delete _packet1;
|
||||
_packet1 = NULL;
|
||||
}
|
||||
|
||||
if ((_video_ctx)&&(_video_ctx->codec)) {
|
||||
MutexHolder av_holder(_av_lock);
|
||||
avcodec_close(_video_ctx);
|
||||
}
|
||||
_video_ctx = NULL;
|
||||
|
|
@ -213,101 +471,245 @@ cleanup() {
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideoCursor::export_frame
|
||||
// Access: Public, Virtual
|
||||
// Description: Exports the contents of the frame buffer into the
|
||||
// user's target buffer.
|
||||
// Function: FfmpegVideoCursor::st_thread_main
|
||||
// Access: Private, Static
|
||||
// Description: The thread main function, static version (for passing
|
||||
// to GenericThread).
|
||||
////////////////////////////////////////////////////////////////////
|
||||
static PStatCollector export_frame_collector("*:FFMPEG Convert Video to BGR");
|
||||
void FfmpegVideoCursor::
|
||||
export_frame(unsigned char *data, bool bgra, int bufx) {
|
||||
PStatTimer timer(export_frame_collector);
|
||||
if (bgra) {
|
||||
_frame_out->data[0] = data + ((_size_y - 1) * bufx * 4);
|
||||
_frame_out->linesize[0] = bufx * -4;
|
||||
#ifdef HAVE_SWSCALE
|
||||
struct SwsContext *convert_ctx = sws_getContext(_size_x, _size_y,
|
||||
_video_ctx->pix_fmt, _size_x, _size_y,
|
||||
PIX_FMT_BGRA, SWS_FAST_BILINEAR, NULL, NULL, NULL);
|
||||
nassertv(convert_ctx != NULL);
|
||||
sws_scale(convert_ctx, _frame->data, _frame->linesize,
|
||||
0, _size_y, _frame_out->data, _frame_out->linesize);
|
||||
sws_freeContext(convert_ctx);
|
||||
#else
|
||||
img_convert((AVPicture *)_frame_out, PIX_FMT_BGRA,
|
||||
(AVPicture *)_frame, _video_ctx->pix_fmt, _size_x, _size_y);
|
||||
#endif
|
||||
} else {
|
||||
_frame_out->data[0] = data + ((_size_y - 1) * bufx * 3);
|
||||
_frame_out->linesize[0] = bufx * -3;
|
||||
#ifdef HAVE_SWSCALE
|
||||
struct SwsContext *convert_ctx = sws_getContext(_size_x, _size_y,
|
||||
_video_ctx->pix_fmt, _size_x, _size_y,
|
||||
PIX_FMT_BGR24, SWS_FAST_BILINEAR, NULL, NULL, NULL);
|
||||
nassertv(convert_ctx != NULL);
|
||||
sws_scale(convert_ctx, _frame->data, _frame->linesize,
|
||||
0, _size_y, _frame_out->data, _frame_out->linesize);
|
||||
sws_freeContext(convert_ctx);
|
||||
#else
|
||||
img_convert((AVPicture *)_frame_out, PIX_FMT_BGR24,
|
||||
(AVPicture *)_frame, _video_ctx->pix_fmt, _size_x, _size_y);
|
||||
#endif
|
||||
st_thread_main(void *self) {
|
||||
((FfmpegVideoCursor *)self)->thread_main();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideoCursor::thread_main
|
||||
// Access: Private
|
||||
// Description: The thread main function.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void FfmpegVideoCursor::
|
||||
thread_main() {
|
||||
MutexHolder holder(_lock);
|
||||
if (ffmpeg_cat.is_debug()) {
|
||||
ffmpeg_cat.debug()
|
||||
<< "ffmpeg thread for " << _filename.get_basename() << " starting.\n";
|
||||
}
|
||||
|
||||
// Repeatedly wait for something interesting to do, until we're told
|
||||
// to shut down.
|
||||
while (_thread_status != TS_shutdown) {
|
||||
nassertv(_thread_status != TS_stopped);
|
||||
_action_cvar.wait();
|
||||
|
||||
while (do_poll()) {
|
||||
// Keep doing stuff as long as there's something to do.
|
||||
PStatClient::thread_tick(_sync_name);
|
||||
Thread::consider_yield();
|
||||
}
|
||||
}
|
||||
|
||||
_thread_status = TS_stopped;
|
||||
if (ffmpeg_cat.is_debug()) {
|
||||
ffmpeg_cat.debug()
|
||||
<< "ffmpeg thread for " << _filename.get_basename() << " stopped.\n";
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideoCursor::do_poll
|
||||
// Access: Private
|
||||
// Description: Called within the sub-thread. Assumes the lock is
|
||||
// already held. If there is something for the thread
|
||||
// to do, does it and returns true. If there is nothing
|
||||
// for the thread to do, returns false.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
bool FfmpegVideoCursor::
|
||||
do_poll() {
|
||||
switch (_thread_status) {
|
||||
case TS_stopped:
|
||||
case TS_seeking:
|
||||
// This shouldn't be possible while the thread is running.
|
||||
nassertr(false, false);
|
||||
return false;
|
||||
|
||||
case TS_wait:
|
||||
// The video hasn't started playing yet.
|
||||
return false;
|
||||
|
||||
case TS_readahead:
|
||||
if ((int)_readahead_frames.size() < _max_readahead_frames) {
|
||||
// Time to read the next frame.
|
||||
Buffer *frame = do_alloc_frame();
|
||||
nassertr(frame != NULL, false);
|
||||
_lock.release();
|
||||
fetch_frame(-1);
|
||||
export_frame(frame);
|
||||
_lock.acquire();
|
||||
_readahead_frames.push_back(frame);
|
||||
return true;
|
||||
}
|
||||
|
||||
// No room for the next frame yet. Wait for more.
|
||||
return false;
|
||||
|
||||
case TS_seek:
|
||||
// Seek to a specific time.
|
||||
{
|
||||
double seek_time = _seek_time;
|
||||
_thread_status = TS_seeking;
|
||||
Buffer *frame = do_alloc_frame();
|
||||
nassertr(frame != NULL, false);
|
||||
_lock.release();
|
||||
fetch_time(seek_time);
|
||||
export_frame(frame);
|
||||
_lock.acquire();
|
||||
do_recycle_all_frames();
|
||||
_readahead_frames.push_back(frame);
|
||||
if (_thread_status == TS_seeking) {
|
||||
// After seeking, we automatically transition to readahead.
|
||||
_thread_status = TS_readahead;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
case TS_shutdown:
|
||||
// Time to stop the thread.
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideoCursor::do_alloc_frame
|
||||
// Access: Private
|
||||
// Description: Allocates a new Buffer object, or returns a
|
||||
// previously-recycled object. Assumes the lock is
|
||||
// held.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
MovieVideoCursor::Buffer *FfmpegVideoCursor::
|
||||
do_alloc_frame() {
|
||||
if (!_recycled_frames.empty()) {
|
||||
Buffer *frame = _recycled_frames.front();
|
||||
_recycled_frames.pop_front();
|
||||
return frame;
|
||||
}
|
||||
return internal_alloc_buffer();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideoCursor::do_recycle_frame
|
||||
// Access: Private
|
||||
// Description: Recycles a previously-allocated Buffer object for
|
||||
// future reuse. Assumes the lock is held.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void FfmpegVideoCursor::
|
||||
do_recycle_frame(Buffer *frame) {
|
||||
_recycled_frames.push_back(frame);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideoCursor::do_recycle_all_frames
|
||||
// Access: Private
|
||||
// Description: Empties the entire readahead_frames queue into the
|
||||
// recycle bin. Assumes the lock is held.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void FfmpegVideoCursor::
|
||||
do_recycle_all_frames() {
|
||||
while (!_readahead_frames.empty()) {
|
||||
Buffer *frame = _readahead_frames.front();
|
||||
_readahead_frames.pop_front();
|
||||
if (ffmpeg_cat.is_debug()) {
|
||||
ffmpeg_cat.debug()
|
||||
<< "ffmpeg for " << _filename.get_basename()
|
||||
<< " recycling frame at " << frame->_begin_time << "\n";
|
||||
}
|
||||
_recycled_frames.push_back(frame);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideoCursor::fetch_packet
|
||||
// Access: Protected
|
||||
// Description: Fetches a video packet and stores it in the
|
||||
// packet buffer. Sets packet_time to the packet's
|
||||
// timestamp. If a packet could not be read, the
|
||||
// packet is cleared and the packet_time is set to
|
||||
// the specified default value. Returns true on
|
||||
// Access: Private
|
||||
// Description: Called within the sub-thread. Fetches a video packet
|
||||
// and stores it in the packet0 buffer. Sets packet_time
|
||||
// to the packet's timestamp. If a packet could not be
|
||||
// read, the packet is cleared and the packet_time is
|
||||
// set to the specified default value. Returns true on
|
||||
// failure (such as the end of the video), or false on
|
||||
// success.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
bool FfmpegVideoCursor::
|
||||
fetch_packet(double default_time) {
|
||||
if (_packet->data) {
|
||||
av_free_packet(_packet);
|
||||
if (_packet0->data) {
|
||||
av_free_packet(_packet0);
|
||||
}
|
||||
while (av_read_frame(_format_ctx, _packet) >= 0) {
|
||||
if (_packet->stream_index == _video_index) {
|
||||
_packet_time = _packet->dts * _video_timebase;
|
||||
while (av_read_frame(_format_ctx, _packet0) >= 0) {
|
||||
if (_packet0->stream_index == _video_index) {
|
||||
_packet_time = _packet0->dts * _video_timebase;
|
||||
return false;
|
||||
}
|
||||
av_free_packet(_packet);
|
||||
av_free_packet(_packet0);
|
||||
}
|
||||
_packet->data = 0;
|
||||
_packet0->data = 0;
|
||||
_packet_time = default_time;
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideoCursor::flip_packets
|
||||
// Access: Private
|
||||
// Description: Called within the sub-thread. Reverses _packet0 and _packet1.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void FfmpegVideoCursor::
|
||||
flip_packets() {
|
||||
AVPacket *t = _packet0;
|
||||
_packet0 = _packet1;
|
||||
_packet1 = t;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideoCursor::fetch_frame
|
||||
// Access: Protected
|
||||
// Description: Slides forward until the indicated time, then fetches
|
||||
// a frame from the stream and stores it in the frame
|
||||
// buffer. Sets last_start and next_start to indicate
|
||||
// the extents of the frame. Returns true if the end of
|
||||
// the video is reached.
|
||||
// Access: Private
|
||||
// Description: Called within the sub-thread. Slides forward until
|
||||
// the indicated time, then fetches a frame from the
|
||||
// stream and stores it in the frame buffer. Sets
|
||||
// last_start and next_start to indicate the extents of
|
||||
// the frame. Returns true if the end of the video is
|
||||
// reached.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
bool FfmpegVideoCursor::
|
||||
fetch_frame(double time) {
|
||||
static PStatCollector fetch_buffer_pcollector("*:FFMPEG Video Decoding:Fetch");
|
||||
PStatTimer timer(fetch_buffer_pcollector);
|
||||
|
||||
int finished = 0;
|
||||
_last_start = _packet_time;
|
||||
_begin_time = _packet_time;
|
||||
|
||||
if (_packet_time <= time) {
|
||||
static PStatCollector seek_pcollector("*:FFMPEG Video Decoding:Seek");
|
||||
PStatTimer timer(seek_pcollector);
|
||||
|
||||
_video_ctx->skip_frame = AVDISCARD_BIDIR;
|
||||
// Put the current packet aside in case we discover it's the
|
||||
// packet to keep.
|
||||
flip_packets();
|
||||
|
||||
// Get the next packet. The first packet beyond the time we're
|
||||
// looking for marks the point to stop.
|
||||
if (fetch_packet(time)) {
|
||||
ffmpeg_cat.debug()
|
||||
<< "end of video\n";
|
||||
return true;
|
||||
}
|
||||
while (_packet_time <= time) {
|
||||
// Decode and discard the previous packet.
|
||||
#if LIBAVCODEC_VERSION_INT < 3414272
|
||||
avcodec_decode_video(_video_ctx, _frame,
|
||||
&finished, _packet->data, _packet->size);
|
||||
&finished, _packet1->data, _packet1->size);
|
||||
#else
|
||||
avcodec_decode_video2(_video_ctx, _frame, &finished, _packet);
|
||||
avcodec_decode_video2(_video_ctx, _frame, &finished, _packet1);
|
||||
#endif
|
||||
flip_packets();
|
||||
if (fetch_packet(time)) {
|
||||
ffmpeg_cat.debug()
|
||||
<< "end of video\n";
|
||||
|
|
@ -315,32 +717,48 @@ fetch_frame(double time) {
|
|||
}
|
||||
}
|
||||
_video_ctx->skip_frame = AVDISCARD_DEFAULT;
|
||||
}
|
||||
|
||||
finished = 0;
|
||||
while (!finished && _packet->data) {
|
||||
|
||||
// At this point, _packet0 contains the *next* packet to be
|
||||
// decoded next frame, and _packet1 contains the packet to decode
|
||||
// for this frame.
|
||||
#if LIBAVCODEC_VERSION_INT < 3414272
|
||||
avcodec_decode_video(_video_ctx, _frame,
|
||||
&finished, _packet->data, _packet->size);
|
||||
&finished, _packet1->data, _packet1->size);
|
||||
#else
|
||||
avcodec_decode_video2(_video_ctx, _frame, &finished, _packet);
|
||||
avcodec_decode_video2(_video_ctx, _frame, &finished, _packet1);
|
||||
#endif
|
||||
fetch_packet(_last_start + 1.0);
|
||||
|
||||
} else {
|
||||
// Just get the next frame.
|
||||
finished = 0;
|
||||
while (!finished && _packet0->data) {
|
||||
#if LIBAVCODEC_VERSION_INT < 3414272
|
||||
avcodec_decode_video(_video_ctx, _frame,
|
||||
&finished, _packet0->data, _packet0->size);
|
||||
#else
|
||||
avcodec_decode_video2(_video_ctx, _frame, &finished, _packet0);
|
||||
#endif
|
||||
fetch_packet(_begin_time + 1.0);
|
||||
}
|
||||
}
|
||||
_next_start = _packet_time;
|
||||
|
||||
_end_time = _packet_time;
|
||||
|
||||
return (finished != 0);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideoCursor::seek
|
||||
// Access: Protected
|
||||
// Description: Seeks to a target location. Afterward, the
|
||||
// packet_time is guaranteed to be less than or
|
||||
// equal to the specified time.
|
||||
// Access: Private
|
||||
// Description: Called within the sub-thread. Seeks to a target
|
||||
// location. Afterward, the packet_time is guaranteed
|
||||
// to be less than or equal to the specified time.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void FfmpegVideoCursor::
|
||||
seek(double t) {
|
||||
static PStatCollector seek_pcollector("*:FFMPEG Video Decoding:Seek");
|
||||
PStatTimer timer(seek_pcollector);
|
||||
|
||||
PN_int64 target_ts = (PN_int64)(t / _video_timebase);
|
||||
if (target_ts < (PN_int64)(_initial_dts)) {
|
||||
// Attempts to seek before the first packet will fail.
|
||||
|
|
@ -355,16 +773,22 @@ seek(double t) {
|
|||
_packet_time = t;
|
||||
return;
|
||||
}
|
||||
avcodec_close(_video_ctx);
|
||||
AVCodec *pVideoCodec=avcodec_find_decoder(_video_ctx->codec_id);
|
||||
if(pVideoCodec == 0) {
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
if(avcodec_open(_video_ctx, pVideoCodec)<0) {
|
||||
cleanup();
|
||||
return;
|
||||
{
|
||||
MutexHolder av_holder(_av_lock);
|
||||
|
||||
avcodec_close(_video_ctx);
|
||||
AVCodec *pVideoCodec = avcodec_find_decoder(_video_ctx->codec_id);
|
||||
if (pVideoCodec == 0) {
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
if (avcodec_open(_video_ctx, pVideoCodec)<0) {
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fetch_packet(t);
|
||||
if (_packet_time > t) {
|
||||
_packet_time = t;
|
||||
|
|
@ -373,17 +797,20 @@ seek(double t) {
|
|||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideoCursor::fetch_time
|
||||
// Access: Public, Virtual
|
||||
// Description: Advance until the specified time is in the
|
||||
// export buffer.
|
||||
// Access: Private
|
||||
// Description: Called within the sub-thread. Advance until the
|
||||
// specified time is in the export buffer.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void FfmpegVideoCursor::
|
||||
fetch_time(double time) {
|
||||
if (time < _last_start) {
|
||||
static PStatCollector fetch_buffer_pcollector("*:FFMPEG Video Decoding:Fetch");
|
||||
PStatTimer timer(fetch_buffer_pcollector);
|
||||
|
||||
if (time < _begin_time) {
|
||||
// Time is in the past.
|
||||
if (ffmpeg_cat.is_debug()) {
|
||||
ffmpeg_cat.debug()
|
||||
<< "Seeking backward to " << time << " from " << _last_start << "\n";
|
||||
<< "Seeking backward to " << time << " from " << _begin_time << "\n";
|
||||
}
|
||||
seek(time);
|
||||
if (_packet_time > time) {
|
||||
|
|
@ -397,14 +824,14 @@ fetch_time(double time) {
|
|||
}
|
||||
fetch_frame(time);
|
||||
|
||||
} else if (time < _next_start) {
|
||||
} else if (time < _end_time) {
|
||||
// Time is in the present: already have the frame.
|
||||
if (ffmpeg_cat.is_debug()) {
|
||||
ffmpeg_cat.debug()
|
||||
<< "Currently have " << time << "\n";
|
||||
}
|
||||
|
||||
} else if (time < _next_start + _min_fseek) {
|
||||
} else if (time < _end_time + _min_fseek) {
|
||||
// Time is in the near future.
|
||||
if (ffmpeg_cat.is_debug()) {
|
||||
ffmpeg_cat.debug()
|
||||
|
|
@ -423,7 +850,7 @@ fetch_time(double time) {
|
|||
|
||||
if (ffmpeg_cat.is_debug()) {
|
||||
ffmpeg_cat.debug()
|
||||
<< "Jumping forward to " << time << " from " << _last_start << "\n";
|
||||
<< "Jumping forward to " << time << " from " << _begin_time << "\n";
|
||||
}
|
||||
double base = _packet_time;
|
||||
seek(time);
|
||||
|
|
@ -448,61 +875,32 @@ fetch_time(double time) {
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideoCursor::fetch_into_texture
|
||||
// Access: Public, Virtual
|
||||
// Description: See MovieVideoCursor::fetch_into_texture.
|
||||
// Function: FfmpegVideoCursor::export_frame
|
||||
// Access: Private
|
||||
// Description: Called within the sub-thread. Exports the contents
|
||||
// of the frame buffer into the indicated target buffer.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void FfmpegVideoCursor::
|
||||
fetch_into_texture(double time, Texture *t, int page) {
|
||||
static PStatCollector fetch_into_texture_pcollector("*:FFMPEG Video Decoding:Fetch");
|
||||
PStatTimer timer(fetch_into_texture_pcollector);
|
||||
|
||||
nassertv(t->get_x_size() >= size_x());
|
||||
nassertv(t->get_y_size() >= size_y());
|
||||
nassertv((t->get_num_components() == 3) || (t->get_num_components() == 4));
|
||||
nassertv(t->get_component_width() == 1);
|
||||
nassertv(page < t->get_num_pages());
|
||||
|
||||
PTA_uchar img = t->modify_ram_image();
|
||||
|
||||
unsigned char *data = img.p() + page * t->get_expected_ram_page_size();
|
||||
|
||||
// If there was an error at any point, synthesize black.
|
||||
if (_format_ctx==0) {
|
||||
if (data) {
|
||||
memset(data,0,t->get_x_size() * t->get_y_size() * t->get_num_components());
|
||||
}
|
||||
_last_start = time;
|
||||
_next_start = time + 1.0;
|
||||
return;
|
||||
}
|
||||
|
||||
fetch_time(time);
|
||||
export_frame(data, (t->get_num_components()==4), t->get_x_size());
|
||||
}
|
||||
export_frame(MovieVideoCursor::Buffer *buffer) {
|
||||
static PStatCollector export_frame_collector("*:FFMPEG Convert Video to BGR");
|
||||
PStatTimer timer(export_frame_collector);
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideoCursor::fetch_into_buffer
|
||||
// Access: Public, Virtual
|
||||
// Description: See MovieVideoCursor::fetch_into_buffer.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void FfmpegVideoCursor::
|
||||
fetch_into_buffer(double time, unsigned char *data, bool bgra) {
|
||||
static PStatCollector fetch_into_buffer_pcollector("*:FFMPEG Video Decoding:Fetch");
|
||||
PStatTimer timer(fetch_into_buffer_pcollector);
|
||||
|
||||
// If there was an error at any point, synthesize black.
|
||||
if (_format_ctx==0) {
|
||||
if (data) {
|
||||
memset(data,0,size_x()*size_y()*(bgra?4:3));
|
||||
}
|
||||
_last_start = time;
|
||||
_next_start = time + 1.0;
|
||||
return;
|
||||
}
|
||||
|
||||
fetch_time(time);
|
||||
export_frame(data, bgra, _size_x);
|
||||
_frame_out->data[0] = buffer->_block + ((_size_y - 1) * _size_x * 3);
|
||||
_frame_out->linesize[0] = _size_x * -3;
|
||||
buffer->_begin_time = _begin_time;
|
||||
buffer->_end_time = _end_time;
|
||||
#ifdef HAVE_SWSCALE
|
||||
struct SwsContext *convert_ctx = sws_getContext(_size_x, _size_y,
|
||||
_video_ctx->pix_fmt, _size_x, _size_y,
|
||||
PIX_FMT_BGR24, SWS_FAST_BILINEAR, NULL, NULL, NULL);
|
||||
nassertv(convert_ctx != NULL);
|
||||
sws_scale(convert_ctx, _frame->data, _frame->linesize,
|
||||
0, _size_y, _frame_out->data, _frame_out->linesize);
|
||||
sws_freeContext(convert_ctx);
|
||||
#else
|
||||
img_convert((AVPicture *)_frame_out, PIX_FMT_BGR24,
|
||||
(AVPicture *)_frame, _video_ctx->pix_fmt, _size_x, _size_y);
|
||||
#endif
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -549,7 +947,7 @@ finalize(BamReader *) {
|
|||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideoCursor::make_from_bam
|
||||
// Access: Protected, Static
|
||||
// Access: Private, Static
|
||||
// Description: This function is called by the BamReader's factory
|
||||
// when a new object of type FfmpegVideo is encountered
|
||||
// in the Bam file. It should create the FfmpegVideo
|
||||
|
|
@ -569,7 +967,7 @@ make_from_bam(const FactoryParams ¶ms) {
|
|||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideoCursor::fillin
|
||||
// Access: Protected
|
||||
// Access: Private
|
||||
// Description: This internal function is called by make_from_bam to
|
||||
// read in all of the relevant data from the BamFile for
|
||||
// the new FfmpegVideo.
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@
|
|||
#include "texture.h"
|
||||
#include "pointerTo.h"
|
||||
#include "ffmpegVirtualFile.h"
|
||||
#include "genericThread.h"
|
||||
#include "pmutex.h"
|
||||
#include "conditionVar.h"
|
||||
|
||||
struct AVFormatContext;
|
||||
struct AVCodecContext;
|
||||
|
|
@ -35,28 +38,76 @@ struct AVFrame;
|
|||
// Description :
|
||||
////////////////////////////////////////////////////////////////////
|
||||
class EXPCL_PANDA_MOVIES FfmpegVideoCursor : public MovieVideoCursor {
|
||||
protected:
|
||||
private:
|
||||
FfmpegVideoCursor();
|
||||
void init_from(FfmpegVideo *src);
|
||||
|
||||
PUBLISHED:
|
||||
FfmpegVideoCursor(FfmpegVideo *src);
|
||||
virtual ~FfmpegVideoCursor();
|
||||
|
||||
void set_max_readahead_frames(int max_readahead_frames);
|
||||
int get_max_readahead_frames() const;
|
||||
|
||||
void start_thread();
|
||||
BLOCKING void stop_thread();
|
||||
bool is_thread_started() const;
|
||||
|
||||
public:
|
||||
virtual void fetch_into_texture(double time, Texture *t, int page);
|
||||
virtual void fetch_into_buffer(double time, unsigned char *block, bool rgba);
|
||||
virtual Buffer *fetch_buffer(double time);
|
||||
virtual void release_buffer(Buffer *buffer);
|
||||
|
||||
private:
|
||||
void cleanup();
|
||||
|
||||
Filename _filename;
|
||||
string _sync_name;
|
||||
int _max_readahead_frames;
|
||||
PT(GenericThread) _thread;
|
||||
|
||||
// This global Mutex protects calls to avcodec_open/close/etc.
|
||||
static Mutex _av_lock;
|
||||
|
||||
// Protects _readahead_frames, _recycled_buffers, and all the
|
||||
// immediately following members.
|
||||
Mutex _lock;
|
||||
|
||||
// Condition: the thread has something to do.
|
||||
ConditionVar _action_cvar;
|
||||
|
||||
typedef pdeque<Buffer *> Buffers;
|
||||
Buffers _readahead_frames;
|
||||
Buffers _recycled_frames;
|
||||
enum ThreadStatus {
|
||||
TS_stopped,
|
||||
TS_wait,
|
||||
TS_readahead,
|
||||
TS_seek,
|
||||
TS_seeking,
|
||||
TS_shutdown,
|
||||
};
|
||||
ThreadStatus _thread_status;
|
||||
double _seek_time;
|
||||
|
||||
private:
|
||||
// The following functions will be called in the sub-thread.
|
||||
static void st_thread_main(void *self);
|
||||
void thread_main();
|
||||
bool do_poll();
|
||||
|
||||
Buffer *do_alloc_frame();
|
||||
void do_recycle_frame(Buffer *frame);
|
||||
void do_recycle_all_frames();
|
||||
|
||||
protected:
|
||||
bool fetch_packet(double default_time);
|
||||
void flip_packets();
|
||||
bool fetch_frame(double time);
|
||||
void seek(double t);
|
||||
void fetch_time(double time);
|
||||
void export_frame(unsigned char *data, bool bgra, int bufx);
|
||||
void cleanup();
|
||||
|
||||
Filename _filename;
|
||||
AVPacket *_packet;
|
||||
void export_frame(Buffer *buffer);
|
||||
|
||||
// The following data members will be accessed by the sub-thread.
|
||||
AVPacket *_packet0, *_packet1;
|
||||
double _packet_time;
|
||||
AVFormatContext *_format_ctx;
|
||||
AVCodecContext *_video_ctx;
|
||||
|
|
@ -67,6 +118,8 @@ protected:
|
|||
AVFrame *_frame_out;
|
||||
int _initial_dts;
|
||||
double _min_fseek;
|
||||
double _begin_time;
|
||||
double _end_time;
|
||||
|
||||
public:
|
||||
static void register_with_read_factory();
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ InkblotVideoCursor(InkblotVideo *src) :
|
|||
{
|
||||
_size_x = src->_specified_x;
|
||||
_size_y = src->_specified_y;
|
||||
_num_components = 3;
|
||||
_fps = src->_specified_fps;
|
||||
int padx = _size_x + 2;
|
||||
int pady = _size_y + 2;
|
||||
|
|
@ -81,12 +82,13 @@ InkblotVideoCursor::
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: InkblotVideoCursor::fetch_into_buffer
|
||||
// Function: InkblotVideoCursor::fetch_buffer
|
||||
// Access: Published, Virtual
|
||||
// Description: See MovieVideoCursor::fetch_into_buffer.
|
||||
// Description: See MovieVideoCursor::fetch_buffer.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void InkblotVideoCursor::
|
||||
fetch_into_buffer(double time, unsigned char *data, bool bgra) {
|
||||
MovieVideoCursor::Buffer *InkblotVideoCursor::
|
||||
fetch_buffer(double time) {
|
||||
Buffer *buffer = get_standard_buffer();
|
||||
|
||||
int padx = size_x() + 2;
|
||||
int pady = size_y() + 2;
|
||||
|
|
@ -100,7 +102,7 @@ fetch_into_buffer(double time, unsigned char *data, bool bgra) {
|
|||
_frames_read = 0;
|
||||
}
|
||||
|
||||
nassertv(time >= _next_start);
|
||||
nassertr(time >= _next_start, NULL);
|
||||
|
||||
while (_next_start <= time) {
|
||||
_last_start = (_frames_read * 1.0) / _fps;
|
||||
|
|
@ -126,6 +128,7 @@ fetch_into_buffer(double time, unsigned char *data, bool bgra) {
|
|||
_cells2 = t;
|
||||
}
|
||||
|
||||
unsigned char *data = buffer->_block;
|
||||
for (int y=1; y<pady - 1; y++) {
|
||||
for (int x=1; x<padx - 1; x++) {
|
||||
int val = _cells[x + y*padx];
|
||||
|
|
@ -135,13 +138,12 @@ fetch_into_buffer(double time, unsigned char *data, bool bgra) {
|
|||
data[0] = (c1.b * (16-lerp) + c2.b * lerp) / 16;
|
||||
data[1] = (c1.g * (16-lerp) + c2.g * lerp) / 16;
|
||||
data[2] = (c1.r * (16-lerp) + c2.r * lerp) / 16;
|
||||
if (bgra) {
|
||||
data[3] = 255;
|
||||
data += 4;
|
||||
} else {
|
||||
data += 3;
|
||||
}
|
||||
data += 3;
|
||||
}
|
||||
}
|
||||
|
||||
buffer->_begin_time = _last_start;
|
||||
buffer->_end_time = _next_start;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class EXPCL_PANDA_MOVIES InkblotVideoCursor : public MovieVideoCursor {
|
|||
virtual ~InkblotVideoCursor();
|
||||
|
||||
public:
|
||||
virtual void fetch_into_buffer(double time, unsigned char *block, bool rgba);
|
||||
virtual Buffer *fetch_buffer(double time);
|
||||
|
||||
protected:
|
||||
unsigned char *_cells;
|
||||
|
|
|
|||
|
|
@ -338,7 +338,7 @@ MicrophoneAudioCursorDS::
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MicrophoneAudioCursorDS::fetch_into_buffer
|
||||
// Function: MicrophoneAudioCursorDS::read_samples
|
||||
// Access: Published
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ TypeHandle MovieVideoCursor::_type_handle;
|
|||
////////////////////////////////////////////////////////////////////
|
||||
MovieVideoCursor::
|
||||
MovieVideoCursor(MovieVideo *src) :
|
||||
_conversion_buffer(0),
|
||||
_source(src),
|
||||
_size_x(1),
|
||||
_size_y(1),
|
||||
|
|
@ -44,6 +43,7 @@ MovieVideoCursor(MovieVideo *src) :
|
|||
_streaming(false),
|
||||
_ready(false)
|
||||
{
|
||||
_standard_buffer._block = NULL;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -53,22 +53,8 @@ MovieVideoCursor(MovieVideo *src) :
|
|||
////////////////////////////////////////////////////////////////////
|
||||
MovieVideoCursor::
|
||||
~MovieVideoCursor() {
|
||||
if (_conversion_buffer != 0) {
|
||||
delete[] _conversion_buffer;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MovieVideoCursor::allocate_conversion_buffer
|
||||
// Access: Private
|
||||
// Description: The generic implementations of fetch_into_texture
|
||||
// and fetch_into_alpha require the use of a conversion
|
||||
// buffer. This allocates the buffer.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void MovieVideoCursor::
|
||||
allocate_conversion_buffer() {
|
||||
if (_conversion_buffer == 0) {
|
||||
_conversion_buffer = new unsigned char[size_x() * size_y() * 4];
|
||||
if (_standard_buffer._block != NULL) {
|
||||
PANDA_FREE_ARRAY(_standard_buffer._block);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -98,19 +84,23 @@ setup_texture(Texture *tex) const {
|
|||
// Description: Discards the next video frame. Still sets
|
||||
// last_start and next_start.
|
||||
//
|
||||
// See fetch_into_buffer for more details.
|
||||
// See fetch_buffer for more details.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void MovieVideoCursor::
|
||||
fetch_into_bitbucket(double time) {
|
||||
|
||||
// This generic implementation is layered on fetch_into_buffer.
|
||||
// This generic implementation is layered on fetch_buffer.
|
||||
// It will work for any derived class, so it is never necessary to
|
||||
// redefine this. It is probably possible to make a faster
|
||||
// implementation, but since this function is rarely used, it
|
||||
// probably isn't worth the trouble.
|
||||
|
||||
allocate_conversion_buffer();
|
||||
fetch_into_buffer(time, _conversion_buffer, false);
|
||||
Buffer *buffer = fetch_buffer(time);
|
||||
if (buffer != NULL) {
|
||||
_last_start = buffer->_begin_time;
|
||||
_next_start = buffer->_end_time;
|
||||
release_buffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -119,14 +109,14 @@ fetch_into_bitbucket(double time) {
|
|||
// Description: Reads the specified video frame into
|
||||
// the specified texture.
|
||||
//
|
||||
// See fetch_into_buffer for more details.
|
||||
// See fetch_buffer for more details.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
static PStatCollector fetch_into_texture_collector("*:Decode Video into Texture");
|
||||
void MovieVideoCursor::
|
||||
fetch_into_texture(double time, Texture *t, int page) {
|
||||
static PStatCollector fetch_into_texture_collector("*:Decode Video into Texture");
|
||||
PStatTimer timer(fetch_into_texture_collector);
|
||||
|
||||
// This generic implementation is layered on fetch_into_buffer.
|
||||
// This generic implementation is layered on fetch_buffer.
|
||||
// It will work for any derived class, so it is never necessary to
|
||||
// redefine this. However, it may be possible to make a faster
|
||||
// implementation that uses fewer intermediate copies, depending
|
||||
|
|
@ -136,26 +126,50 @@ fetch_into_texture(double time, Texture *t, int page) {
|
|||
nassertv(t->get_y_size() >= size_y());
|
||||
nassertv((t->get_num_components() == 3) || (t->get_num_components() == 4));
|
||||
nassertv(t->get_component_width() == 1);
|
||||
nassertv(page < t->get_z_size());
|
||||
nassertv(page < t->get_num_pages());
|
||||
|
||||
t->set_keep_ram_image(true);
|
||||
PTA_uchar img = t->modify_ram_image();
|
||||
|
||||
unsigned char *data = img.p() + page * t->get_expected_ram_page_size();
|
||||
|
||||
if (t->get_x_size() == size_x()) {
|
||||
fetch_into_buffer(time, data, t->get_num_components() == 4);
|
||||
Buffer *buffer = fetch_buffer(time);
|
||||
if (buffer == NULL) {
|
||||
// No image available.
|
||||
return;
|
||||
}
|
||||
|
||||
_last_start = buffer->_begin_time;
|
||||
_next_start = buffer->_end_time;
|
||||
|
||||
if (t->get_x_size() == size_x() && t->get_num_components() == get_num_components()) {
|
||||
memcpy(data, buffer->_block, size_x() * size_y() * get_num_components());
|
||||
|
||||
} else {
|
||||
allocate_conversion_buffer();
|
||||
fetch_into_buffer(time, _conversion_buffer, t->get_num_components() == 4);
|
||||
int src_stride = size_x() * t->get_num_components();
|
||||
int dst_stride = t->get_x_size() * t->get_num_components();
|
||||
unsigned char *p = _conversion_buffer;
|
||||
for (int y=0; y<size_y(); y++) {
|
||||
memcpy(data, p, src_stride);
|
||||
data += dst_stride;
|
||||
p += src_stride;
|
||||
unsigned char *p = buffer->_block;
|
||||
if (t->get_num_components() == get_num_components()) {
|
||||
int src_stride = size_x() * get_num_components();
|
||||
int dst_stride = t->get_x_size() * t->get_num_components();
|
||||
for (int y=0; y<size_y(); y++) {
|
||||
memcpy(data, p, src_stride);
|
||||
data += dst_stride;
|
||||
p += src_stride;
|
||||
}
|
||||
} else {
|
||||
int src_width = get_num_components();
|
||||
int dst_width = t->get_num_components();
|
||||
for (int y = 0; y < size_y(); ++y) {
|
||||
for (int x = 0; x < size_x(); ++x) {
|
||||
data[0] = p[0];
|
||||
data[1] = p[1];
|
||||
data[2] = p[2];
|
||||
data += dst_width;
|
||||
p += src_width;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
release_buffer(buffer);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -165,12 +179,12 @@ fetch_into_texture(double time, Texture *t, int page) {
|
|||
// the alpha channel of the supplied texture. The
|
||||
// RGB channels of the texture are not touched.
|
||||
//
|
||||
// See fetch_into_buffer for more details.
|
||||
// See fetch_buffer for more details.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void MovieVideoCursor::
|
||||
fetch_into_texture_alpha(double time, Texture *t, int page, int alpha_src) {
|
||||
|
||||
// This generic implementation is layered on fetch_into_buffer.
|
||||
// This generic implementation is layered on fetch_buffer.
|
||||
// It will work for any derived class, so it is never necessary to
|
||||
// redefine this. However, it may be possible to make a faster
|
||||
// implementation that uses fewer intermediate copies, depending
|
||||
|
|
@ -181,20 +195,26 @@ fetch_into_texture_alpha(double time, Texture *t, int page, int alpha_src) {
|
|||
nassertv(t->get_num_components() == 4);
|
||||
nassertv(t->get_component_width() == 1);
|
||||
nassertv(page < t->get_z_size());
|
||||
nassertv((alpha_src >= 0) && (alpha_src <= 4));
|
||||
nassertv((alpha_src >= 0) && (alpha_src <= get_num_components()));
|
||||
|
||||
allocate_conversion_buffer();
|
||||
|
||||
fetch_into_buffer(time, _conversion_buffer, true);
|
||||
|
||||
Buffer *buffer = fetch_buffer(time);
|
||||
if (buffer == NULL) {
|
||||
// No image available.
|
||||
return;
|
||||
}
|
||||
|
||||
_last_start = buffer->_begin_time;
|
||||
_next_start = buffer->_end_time;
|
||||
|
||||
t->set_keep_ram_image(true);
|
||||
PTA_uchar img = t->modify_ram_image();
|
||||
|
||||
unsigned char *data = img.p() + page * t->get_expected_ram_page_size();
|
||||
|
||||
int src_stride = size_x() * 4;
|
||||
int src_stride = size_x() * get_num_components();
|
||||
int dst_stride = t->get_x_size() * 4;
|
||||
if (alpha_src == 0) {
|
||||
unsigned char *p = _conversion_buffer;
|
||||
unsigned char *p = buffer->_block;
|
||||
for (int y=0; y<size_y(); y++) {
|
||||
for (int x=0; x<size_x(); x++) {
|
||||
data[x*4+3] = (p[x*4+0] + p[x*4+1] + p[x*4+2]) / 3;
|
||||
|
|
@ -204,15 +224,18 @@ fetch_into_texture_alpha(double time, Texture *t, int page, int alpha_src) {
|
|||
}
|
||||
} else {
|
||||
alpha_src -= 1;
|
||||
unsigned char *p = _conversion_buffer;
|
||||
unsigned char *p = buffer->_block;
|
||||
int src_width = get_num_components();
|
||||
for (int y=0; y<size_y(); y++) {
|
||||
for (int x=0; x<size_x(); x++) {
|
||||
data[x*4+3] = p[x*4+alpha_src];
|
||||
data[x*4+3] = p[x *src_width + alpha_src];
|
||||
}
|
||||
data += dst_stride;
|
||||
p += src_stride;
|
||||
}
|
||||
}
|
||||
|
||||
release_buffer(buffer);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -222,12 +245,12 @@ fetch_into_texture_alpha(double time, Texture *t, int page, int alpha_src) {
|
|||
// the RGB channels of the supplied texture. The alpha
|
||||
// channel of the texture is not touched.
|
||||
//
|
||||
// See fetch_into_buffer for more details.
|
||||
// See fetch_buffer for more details.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void MovieVideoCursor::
|
||||
fetch_into_texture_rgb(double time, Texture *t, int page) {
|
||||
|
||||
// This generic implementation is layered on fetch_into_buffer.
|
||||
// This generic implementation is layered on fetch_buffer.
|
||||
// It will work for any derived class, so it is never necessary to
|
||||
// redefine this. However, it may be possible to make a faster
|
||||
// implementation that uses fewer intermediate copies, depending
|
||||
|
|
@ -239,34 +262,48 @@ fetch_into_texture_rgb(double time, Texture *t, int page) {
|
|||
nassertv(t->get_component_width() == 1);
|
||||
nassertv(page < t->get_z_size());
|
||||
|
||||
allocate_conversion_buffer();
|
||||
|
||||
fetch_into_buffer(time, _conversion_buffer, true);
|
||||
|
||||
Buffer *buffer = fetch_buffer(time);
|
||||
if (buffer == NULL) {
|
||||
// No image available.
|
||||
return;
|
||||
}
|
||||
|
||||
_last_start = buffer->_begin_time;
|
||||
_next_start = buffer->_end_time;
|
||||
|
||||
t->set_keep_ram_image(true);
|
||||
PTA_uchar img = t->modify_ram_image();
|
||||
|
||||
unsigned char *data = img.p() + page * t->get_expected_ram_page_size();
|
||||
|
||||
int src_stride = size_x() * 4;
|
||||
int src_stride = size_x() * get_num_components();
|
||||
int src_width = get_num_components();
|
||||
int dst_stride = t->get_x_size() * 4;
|
||||
unsigned char *p = _conversion_buffer;
|
||||
unsigned char *p = buffer->_block;
|
||||
for (int y=0; y<size_y(); y++) {
|
||||
for (int x=0; x<size_x(); x++) {
|
||||
data[x*4+0] = p[x*4+0];
|
||||
data[x*4+1] = p[x*4+1];
|
||||
data[x*4+2] = p[x*4+2];
|
||||
data[x * 4 + 0] = p[x * src_width + 0];
|
||||
data[x * 4 + 1] = p[x * src_width + 1];
|
||||
data[x * 4 + 2] = p[x * src_width + 2];
|
||||
}
|
||||
data += dst_stride;
|
||||
p += src_stride;
|
||||
}
|
||||
|
||||
release_buffer(buffer);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MovieVideoCursor::fetch_into_buffer
|
||||
// Function: MovieVideoCursor::fetch_buffer
|
||||
// Access: Published, Virtual
|
||||
// Description: Reads the specified video frame into the supplied
|
||||
// BGR or BGRA buffer. The frame's begin and end
|
||||
// times are stored in last_start and next_start.
|
||||
// Description: Reads the specified video frame and returns it in a
|
||||
// pre-allocated buffer. The frame's begin and end
|
||||
// times are stored in _begin_time and _end_time, within
|
||||
// the buffer. After you have copied the data from the
|
||||
// buffer, you should call release_buffer() to make the
|
||||
// space available again to populate the next frame.
|
||||
// You may not call fetch_buffer() again until you have
|
||||
// called release_buffer().
|
||||
//
|
||||
// If the movie reports that it can_seek, you may
|
||||
// also specify a timestamp less than next_start.
|
||||
|
|
@ -279,30 +316,102 @@ fetch_into_texture_rgb(double time, Texture *t, int page) {
|
|||
// desired location. Only if can_seek_fast returns
|
||||
// true can it seek rapidly.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void MovieVideoCursor::
|
||||
fetch_into_buffer(double time, unsigned char *data, bool bgra) {
|
||||
|
||||
MovieVideoCursor::Buffer *MovieVideoCursor::
|
||||
fetch_buffer(double time) {
|
||||
Buffer *buffer = get_standard_buffer();
|
||||
|
||||
// The following is the implementation of the null video stream, ie,
|
||||
// a stream of blinking red and blue frames. This method must be
|
||||
// overridden by the subclass.
|
||||
|
||||
_last_start = floor(time);
|
||||
_next_start = _last_start + 1;
|
||||
buffer->_begin_time = floor(time);
|
||||
buffer->_end_time = buffer->_begin_time + 1;
|
||||
int flash = ((int)buffer->_begin_time) & 1;
|
||||
|
||||
if (((int)_last_start) & 1) {
|
||||
data[0] = 255;
|
||||
data[1] = 128;
|
||||
data[2] = 128;
|
||||
} else {
|
||||
data[0] = 128;
|
||||
data[1] = 128;
|
||||
data[2] = 255;
|
||||
}
|
||||
if (bgra) {
|
||||
data[3] = 255;
|
||||
unsigned char *p = buffer->_block;
|
||||
int src_width = get_num_components();
|
||||
for (int y = 0; y < size_y(); ++y) {
|
||||
for (int x = 0; x < size_x(); ++x) {
|
||||
if (flash) {
|
||||
p[0] = 255;
|
||||
p[1] = 128;
|
||||
p[2] = 128;
|
||||
} else {
|
||||
p[0] = 128;
|
||||
p[1] = 128;
|
||||
p[2] = 255;
|
||||
}
|
||||
if (src_width == 4) {
|
||||
p[3] = 255;
|
||||
}
|
||||
p += src_width;
|
||||
}
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MovieVideoCursor::release_buffer
|
||||
// Access: Public, Virtual
|
||||
// Description: Should be called after processing the Buffer object
|
||||
// returned by fetch_buffer(), this releases the Buffer
|
||||
// for future use again.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void MovieVideoCursor::
|
||||
release_buffer(Buffer *buffer) {
|
||||
nassertv(buffer == &_standard_buffer);
|
||||
nassertv(buffer->_begin_time == _last_start && buffer->_end_time == _next_start);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MovieVideoCursor::get_standard_buffer
|
||||
// Access: Protected
|
||||
// Description: May be called by a derived class to return a single
|
||||
// standard Buffer object to easily implement
|
||||
// fetch_buffer(). The default release_buffer()
|
||||
// implementation assumes this method is used.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
MovieVideoCursor::Buffer *MovieVideoCursor::
|
||||
get_standard_buffer() {
|
||||
if (_standard_buffer._block == NULL) {
|
||||
_standard_buffer._block_size = size_x() * size_y() * get_num_components();
|
||||
_standard_buffer._block = (unsigned char *)PANDA_MALLOC_ARRAY(_standard_buffer._block_size);
|
||||
_standard_buffer._begin_time = -1.0;
|
||||
_standard_buffer._end_time = 0.0;
|
||||
}
|
||||
return &_standard_buffer;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MovieVideoCursor::internal_alloc_buffer
|
||||
// Access: Protected
|
||||
// Description: May be called by a derived class to allocate a new
|
||||
// Buffer object. The caller is responsible for
|
||||
// eventually passing this object to
|
||||
// internal_free_buffer().
|
||||
////////////////////////////////////////////////////////////////////
|
||||
MovieVideoCursor::Buffer *MovieVideoCursor::
|
||||
internal_alloc_buffer() {
|
||||
Buffer *buffer = new Buffer;
|
||||
buffer->_block_size = size_x() * size_y() * get_num_components();
|
||||
buffer->_block = (unsigned char *)PANDA_MALLOC_ARRAY(buffer->_block_size);
|
||||
buffer->_begin_time = -1.0;
|
||||
buffer->_end_time = 0.0;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MovieVideoCursor::internal_free_buffer
|
||||
// Access: Protected
|
||||
// Description: Frees a Buffer object allocated via
|
||||
// internal_alloc_buffer().
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void MovieVideoCursor::
|
||||
internal_free_buffer(Buffer *buffer) {
|
||||
PANDA_FREE_ARRAY(buffer->_block);
|
||||
delete buffer;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MovieVideoCursor::write_datagram
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
#include "pandabase.h"
|
||||
#include "texture.h"
|
||||
#include "pointerTo.h"
|
||||
#include "memoryBase.h"
|
||||
|
||||
class MovieVideo;
|
||||
class FactoryParams;
|
||||
|
|
@ -63,11 +64,20 @@ PUBLISHED:
|
|||
virtual void fetch_into_texture_alpha(double time, Texture *t, int page, int alpha_src);
|
||||
|
||||
public:
|
||||
virtual void fetch_into_buffer(double time, unsigned char *block, bool bgra);
|
||||
class Buffer : public MemoryBase {
|
||||
public:
|
||||
unsigned char *_block;
|
||||
size_t _block_size;
|
||||
double _begin_time;
|
||||
double _end_time;
|
||||
};
|
||||
virtual Buffer *fetch_buffer(double time);
|
||||
virtual void release_buffer(Buffer *buffer);
|
||||
|
||||
private:
|
||||
void allocate_conversion_buffer();
|
||||
unsigned char *_conversion_buffer;
|
||||
protected:
|
||||
Buffer *get_standard_buffer();
|
||||
Buffer *internal_alloc_buffer();
|
||||
void internal_free_buffer(Buffer *buffer);
|
||||
|
||||
protected:
|
||||
PT(MovieVideo) _source;
|
||||
|
|
@ -83,6 +93,8 @@ protected:
|
|||
bool _streaming;
|
||||
bool _ready;
|
||||
|
||||
Buffer _standard_buffer;
|
||||
|
||||
public:
|
||||
virtual void write_datagram(BamWriter *manager, Datagram &dg);
|
||||
virtual int complete_pointers(TypedWritable **plist, BamReader *manager);
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@
|
|||
cycleDataWriter.h cycleDataWriter.I \
|
||||
cyclerHolder.h cyclerHolder.I \
|
||||
externalThread.h \
|
||||
genericThread.h genericThread.I \
|
||||
lightMutex.I lightMutex.h \
|
||||
lightMutexDirect.h lightMutexDirect.I \
|
||||
lightMutexHolder.I lightMutexHolder.h \
|
||||
|
|
@ -98,6 +99,7 @@
|
|||
cycleDataWriter.cxx \
|
||||
cyclerHolder.cxx \
|
||||
externalThread.cxx \
|
||||
genericThread.cxx \
|
||||
lightMutex.cxx \
|
||||
lightMutexDirect.cxx \
|
||||
lightMutexHolder.cxx \
|
||||
|
|
@ -155,6 +157,7 @@
|
|||
cycleDataWriter.h cycleDataWriter.I \
|
||||
cyclerHolder.h cyclerHolder.I \
|
||||
externalThread.h \
|
||||
genericThread.h genericThread.I \
|
||||
lightMutex.I lightMutex.h \
|
||||
lightMutexDirect.h lightMutexDirect.I \
|
||||
lightMutexHolder.I lightMutexHolder.h \
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#include "asyncTaskBase.h"
|
||||
#include "mainThread.h"
|
||||
#include "externalThread.h"
|
||||
#include "genericThread.h"
|
||||
#include "thread.h"
|
||||
#include "pythonThread.h"
|
||||
#include "pandaSystem.h"
|
||||
|
|
@ -73,6 +74,7 @@ init_libpipeline() {
|
|||
AsyncTaskBase::init_type();
|
||||
MainThread::init_type();
|
||||
ExternalThread::init_type();
|
||||
GenericThread::init_type();
|
||||
Thread::init_type();
|
||||
#ifdef HAVE_PYTHON
|
||||
PythonThread::init_type();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
// Filename: genericThread.I
|
||||
// Created by: drose (09Nov11)
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// PANDA 3D SOFTWARE
|
||||
// Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
//
|
||||
// All use of this software is subject to the terms of the revised BSD
|
||||
// license. You should have received a copy of this license along
|
||||
// with this source code in a file named "LICENSE."
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: GenericThread::set_function
|
||||
// Access: Published
|
||||
// Description: Replaces the function that is called when the thread
|
||||
// runs.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
INLINE void GenericThread::
|
||||
set_function(GenericThread::ThreadFunc *function) {
|
||||
_function = function;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: GenericThread::get_function
|
||||
// Access: Published
|
||||
// Description: Returns the function that is called when the thread
|
||||
// runs.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
INLINE GenericThread::ThreadFunc *GenericThread::
|
||||
get_function() const {
|
||||
return _function;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: GenericThread::set_user_data
|
||||
// Access: Published
|
||||
// Description: Replaces the void pointer that is passed to the thread
|
||||
// function. This is any arbitrary pointer; the thread
|
||||
// object does no processing on it.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
INLINE void GenericThread::
|
||||
set_user_data(void *user_data) {
|
||||
_user_data = user_data;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: GenericThread::get_user_data
|
||||
// Access: Published
|
||||
// Description: Returns the void pointer that is passed to the thread
|
||||
// function.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
INLINE void *GenericThread::
|
||||
get_user_data() const {
|
||||
return _user_data;
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
// Filename: genericThread.cxx
|
||||
// Created by: drose (09Nov11)
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// PANDA 3D SOFTWARE
|
||||
// Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
//
|
||||
// All use of this software is subject to the terms of the revised BSD
|
||||
// license. You should have received a copy of this license along
|
||||
// with this source code in a file named "LICENSE."
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "genericThread.h"
|
||||
#include "pnotify.h"
|
||||
|
||||
TypeHandle GenericThread::_type_handle;
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: GenericThread::Constructor
|
||||
// Access: Public
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
GenericThread::
|
||||
GenericThread(const string &name, const string &sync_name) :
|
||||
Thread(name, sync_name)
|
||||
{
|
||||
_function = NULL;
|
||||
_user_data = NULL;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: GenericThread::Constructor
|
||||
// Access: Public
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
GenericThread::
|
||||
GenericThread(const string &name, const string &sync_name, GenericThread::ThreadFunc *function, void *user_data) :
|
||||
Thread(name, sync_name),
|
||||
_function(function),
|
||||
_user_data(user_data)
|
||||
{
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: GenericThread::thread_main
|
||||
// Access: Protected, Virtual
|
||||
// Description: This is the thread's main execution function.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void GenericThread::
|
||||
thread_main() {
|
||||
nassertv(_function != NULL);
|
||||
(*_function)(_user_data);
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
// Filename: genericThread.h
|
||||
// Created by: drose (09Nov11)
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// PANDA 3D SOFTWARE
|
||||
// Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
//
|
||||
// All use of this software is subject to the terms of the revised BSD
|
||||
// license. You should have received a copy of this license along
|
||||
// with this source code in a file named "LICENSE."
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef GENERICTHREAD_H
|
||||
#define GENERICTHREAD_H
|
||||
|
||||
#include "pandabase.h"
|
||||
#include "thread.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Class : GenericThread
|
||||
// Description : A generic thread type that allows calling a C-style thread
|
||||
// function without having to subclass.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
class EXPCL_PANDA_PIPELINE GenericThread : public Thread {
|
||||
public:
|
||||
typedef void ThreadFunc(void *user_data);
|
||||
|
||||
GenericThread(const string &name, const string &sync_name);
|
||||
GenericThread(const string &name, const string &sync_name, ThreadFunc *function, void *user_data);
|
||||
|
||||
INLINE void set_function(ThreadFunc *function);
|
||||
INLINE ThreadFunc *get_function() const;
|
||||
|
||||
INLINE void set_user_data(void *user_data);
|
||||
INLINE void *get_user_data() const;
|
||||
|
||||
protected:
|
||||
virtual void thread_main();
|
||||
|
||||
private:
|
||||
ThreadFunc *_function;
|
||||
void *_user_data;
|
||||
|
||||
public:
|
||||
static TypeHandle get_class_type() {
|
||||
return _type_handle;
|
||||
}
|
||||
static void init_type() {
|
||||
Thread::init_type();
|
||||
register_type(_type_handle, "GenericThread",
|
||||
Thread::get_class_type());
|
||||
}
|
||||
virtual TypeHandle get_type() const {
|
||||
return get_class_type();
|
||||
}
|
||||
virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
|
||||
|
||||
private:
|
||||
static TypeHandle _type_handle;
|
||||
};
|
||||
|
||||
#include "genericThread.I"
|
||||
|
||||
#endif
|
||||
|
||||
|
|
@ -20,6 +20,7 @@
|
|||
#include "cycleDataWriter.cxx"
|
||||
#include "cyclerHolder.cxx"
|
||||
#include "externalThread.cxx"
|
||||
#include "genericThread.cxx"
|
||||
#include "lightMutexDirect.cxx"
|
||||
#include "lightMutexHolder.cxx"
|
||||
#include "lightReMutexDirect.cxx"
|
||||
|
|
|
|||
|
|
@ -283,7 +283,7 @@ analyze(Texture *tex, bool do_flip_texture) {
|
|||
int pady = tex->get_pad_y_size();
|
||||
int xsize = tex->get_x_size() - padx;
|
||||
int ysize = tex->get_y_size() - pady;
|
||||
int pagesize = xsize * ysize * 4;
|
||||
//int pagesize = xsize * ysize * 4;
|
||||
nassertv((xsize > 0) && (ysize > 0));
|
||||
|
||||
ARParam cparam;
|
||||
|
|
|
|||
|
|
@ -61,18 +61,20 @@ WebcamVideoCursorOpenCV::
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: WebcamVideoCursorOpenCV::fetch_into_buffer
|
||||
// Function: WebcamVideoCursorOpenCV::fetch_buffer
|
||||
// Access: Published, Virtual
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void WebcamVideoCursorOpenCV::
|
||||
fetch_into_buffer(double time, unsigned char *block, bool bgra) {
|
||||
MovieVideoCursor::Buffer *WebcamVideoCursorOpenCV::
|
||||
fetch_buffer(double time) {
|
||||
if (!_ready) {
|
||||
return;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
unsigned char *dest = block;
|
||||
int num_components = bgra ? 4 : 3;
|
||||
Buffer *buffer = get_standard_buffer();
|
||||
unsigned char *dest = buffer->_block;
|
||||
int num_components = get_num_components();
|
||||
nassertr(num_components == 3, NULL);
|
||||
ssize_t dest_x_pitch = num_components; // Assume component_width == 1
|
||||
ssize_t dest_y_pitch = _size_x * dest_x_pitch;
|
||||
|
||||
|
|
@ -82,7 +84,7 @@ fetch_into_buffer(double time, unsigned char *block, bool bgra) {
|
|||
if (num_components == 3 && x_pitch == 3) {
|
||||
// The easy case--copy the whole thing in, row by row.
|
||||
ssize_t copy_bytes = _size_x * dest_x_pitch;
|
||||
nassertv(copy_bytes <= dest_y_pitch && copy_bytes <= abs(y_pitch));
|
||||
nassertr(copy_bytes <= dest_y_pitch && copy_bytes <= abs(y_pitch), NULL);
|
||||
|
||||
for (int y = 0; y < _size_y; ++y) {
|
||||
memcpy(dest, r, copy_bytes);
|
||||
|
|
@ -111,6 +113,8 @@ fetch_into_buffer(double time, unsigned char *block, bool bgra) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ class WebcamVideoCursorOpenCV : public MovieVideoCursor {
|
|||
public:
|
||||
WebcamVideoCursorOpenCV(WebcamVideoOpenCV *src);
|
||||
virtual ~WebcamVideoCursorOpenCV();
|
||||
virtual void fetch_into_buffer(double time, unsigned char *block, bool rgba);
|
||||
virtual Buffer *fetch_buffer(double time);
|
||||
|
||||
private:
|
||||
bool get_frame_data(const unsigned char *&r,
|
||||
|
|
|
|||
|
|
@ -308,33 +308,34 @@ WebcamVideoCursorV4L::
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: WebcamVideoCursorV4L::fetch_into_buffer
|
||||
// Function: WebcamVideoCursorV4L::fetch_buffer
|
||||
// Access: Published, Virtual
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void WebcamVideoCursorV4L::
|
||||
fetch_into_buffer(double time, unsigned char *block, bool bgra) {
|
||||
MovieVideoCursor::Buffer *WebcamVideoCursorV4L::
|
||||
fetch_buffer(double time) {
|
||||
if (!_ready) {
|
||||
return;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Buffer *buffer = get_standard_buffer();
|
||||
unsigned char *block = buffer->_block;
|
||||
struct v4l2_buffer vbuf;
|
||||
memset(&vbuf, 0, sizeof vbuf);
|
||||
vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
vbuf.memory = V4L2_MEMORY_MMAP;
|
||||
if (-1 == ioctl(_fd, VIDIOC_DQBUF, &vbuf) && errno != EIO) {
|
||||
vision_cat.error() << "Failed to dequeue buffer!\n";
|
||||
return;
|
||||
return NULL;
|
||||
}
|
||||
nassertv(vbuf.index < _bufcount);
|
||||
size_t bufsize = _buflens[vbuf.index];
|
||||
size_t old_bpl = _format->fmt.pix.bytesperline;
|
||||
size_t new_bpl = bgra ? _size_x * 4 : _size_x * 3;
|
||||
size_t new_bpl = _size_x * 3;
|
||||
unsigned char *buf = (unsigned char *) _buffers[vbuf.index];
|
||||
|
||||
if (_format->fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG) {
|
||||
#ifdef HAVE_JPEG
|
||||
nassertv(!bgra);
|
||||
struct my_error_mgr jerr;
|
||||
_cinfo->err = jpeg_std_error(&jerr.pub);
|
||||
jerr.pub.error_exit = my_error_exit;
|
||||
|
|
@ -385,38 +386,20 @@ fetch_into_buffer(double time, unsigned char *block, bool bgra) {
|
|||
|
||||
// Swap red / blue
|
||||
unsigned char ex;
|
||||
if (bgra) {
|
||||
for (size_t i = 0; i < new_bpl * _size_y; i += 4) {
|
||||
ex = block[i];
|
||||
block[i] = block[i + 2];
|
||||
block[i + 2] = ex;
|
||||
}
|
||||
} else {
|
||||
for (size_t i = 0; i < new_bpl * _size_y; i += 3) {
|
||||
ex = block[i];
|
||||
block[i] = block[i + 2];
|
||||
block[i + 2] = ex;
|
||||
}
|
||||
for (size_t i = 0; i < new_bpl * _size_y; i += 3) {
|
||||
ex = block[i];
|
||||
block[i] = block[i + 2];
|
||||
block[i + 2] = ex;
|
||||
}
|
||||
#else
|
||||
nassertv(false); // Not compiled with JPEG support
|
||||
nassertr(false, NULL); // Not compiled with JPEG support
|
||||
#endif
|
||||
} else {
|
||||
if (bgra) {
|
||||
for (size_t row = 0; row < _size_y; ++row) {
|
||||
size_t c = 0;
|
||||
for (size_t i = 0; i < old_bpl; i += 4) {
|
||||
yuyv_to_rgbargba(block + (_size_y - row - 1) * new_bpl + c, buf + row * old_bpl + i);
|
||||
c += 8;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (size_t row = 0; row < _size_y; ++row) {
|
||||
size_t c = 0;
|
||||
for (size_t i = 0; i < old_bpl; i += 4) {
|
||||
yuyv_to_rgbrgb(block + (_size_y - row - 1) * new_bpl + c, buf + row * old_bpl + i);
|
||||
c += 6;
|
||||
}
|
||||
for (size_t row = 0; row < _size_y; ++row) {
|
||||
size_t c = 0;
|
||||
for (size_t i = 0; i < old_bpl; i += 4) {
|
||||
yuyv_to_rgbrgb(block + (_size_y - row - 1) * new_bpl + c, buf + row * old_bpl + i);
|
||||
c += 6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -424,6 +407,8 @@ fetch_into_buffer(double time, unsigned char *block, bool bgra) {
|
|||
if (-1 == ioctl(_fd, VIDIOC_QBUF, &vbuf)) {
|
||||
vision_cat.error() << "Failed to exchange buffer with driver!\n";
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class WebcamVideoCursorV4L : public MovieVideoCursor {
|
|||
public:
|
||||
WebcamVideoCursorV4L(WebcamVideoV4L *src);
|
||||
virtual ~WebcamVideoCursorV4L();
|
||||
virtual void fetch_into_buffer(double time, unsigned char *block, bool rgba);
|
||||
virtual Buffer *fetch_buffer(double time);
|
||||
|
||||
private:
|
||||
int _fd;
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ class WebcamVideoCursorDS : public MovieVideoCursor
|
|||
public:
|
||||
WebcamVideoCursorDS(WebcamVideoDS *src);
|
||||
virtual ~WebcamVideoCursorDS();
|
||||
virtual void fetch_into_buffer(double time, unsigned char *block, bool rgba);
|
||||
virtual Buffer *fetch_buffer(double time);
|
||||
|
||||
public:
|
||||
void cleanup();
|
||||
|
|
@ -654,17 +654,18 @@ WebcamVideoCursorDS::
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: WebcamVideoCursorDS::fetch_into_buffer
|
||||
// Function: WebcamVideoCursorDS::fetch_buffer
|
||||
// Access: Published
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void WebcamVideoCursorDS::
|
||||
fetch_into_buffer(double time, unsigned char *block, bool bgra) {
|
||||
|
||||
WebcamVideoCursor::Buffer *WebcamVideoCursorDS::
|
||||
fetch_buffer(double time) {
|
||||
if (!_ready) {
|
||||
return;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Buffer *buffer = get_standard_buffer();
|
||||
unsigned char *block = buffer->_block;
|
||||
#ifdef LOCKING_MODE
|
||||
unsigned char *ptr;
|
||||
int pixels = _size_x * _size_y;
|
||||
|
|
@ -672,34 +673,17 @@ fetch_into_buffer(double time, unsigned char *block, bool bgra) {
|
|||
if (res == S_OK) {
|
||||
int size = _saved->GetActualDataLength();
|
||||
if (size == pixels * 3) {
|
||||
if (bgra) {
|
||||
for (int i=0; i<pixels; i++) {
|
||||
block[i*4+0] = ptr[i*3+0];
|
||||
block[i*4+1] = ptr[i*3+1];
|
||||
block[i*4+2] = ptr[i*3+2];
|
||||
block[i*4+3] = 255;
|
||||
}
|
||||
} else {
|
||||
memcpy(block, ptr, pixels * 3);
|
||||
}
|
||||
memcpy(block, ptr, pixels * 3);
|
||||
}
|
||||
}
|
||||
_saved->Release();
|
||||
#else
|
||||
int pixels = _size_x * _size_y;
|
||||
if (bgra) {
|
||||
for (int i=0; i<pixels; i++) {
|
||||
block[i*4+0] = _buffer[i*3+0];
|
||||
block[i*4+1] = _buffer[i*3+1];
|
||||
block[i*4+2] = _buffer[i*3+2];
|
||||
block[i*4+3] = 255;
|
||||
}
|
||||
} else {
|
||||
memcpy(block, _buffer, pixels * 3);
|
||||
}
|
||||
memcpy(block, _buffer, pixels * 3);
|
||||
#endif
|
||||
|
||||
_ready = false;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue