diff --git a/panda/src/grutil/movieTexture.I b/panda/src/grutil/movieTexture.I index 3f74b9aa35..023e63d325 100644 --- a/panda/src/grutil/movieTexture.I +++ b/panda/src/grutil/movieTexture.I @@ -52,3 +52,31 @@ get_video_height() const { return cdata->_video_height; } +//////////////////////////////////////////////////////////////////// +// Function: MovieTexture::get_color_cursor +// Access: Published +// Description: Returns the MovieVideoCursor that is feeding the +// color channels for the indicated page, where 0 <= +// page < get_num_pages(). +//////////////////////////////////////////////////////////////////// +INLINE MovieVideoCursor *MovieTexture:: +get_color_cursor(int page) { + CDReader cdata(_cycler); + nassertr(page >= 0 && page < (int)cdata->_pages.size(), NULL); + return cdata->_pages[page]._color; +} + +//////////////////////////////////////////////////////////////////// +// Function: MovieTexture::get_alpha_cursor +// Access: Published +// Description: Returns the MovieVideoCursor that is feeding the +// alpha channel for the indicated page, where 0 <= +// page < get_num_pages(). +//////////////////////////////////////////////////////////////////// +INLINE MovieVideoCursor *MovieTexture:: +get_alpha_cursor(int page) { + CDReader cdata(_cycler); + nassertr(page >= 0 && page < (int)cdata->_pages.size(), NULL); + return cdata->_pages[page]._alpha; +} + diff --git a/panda/src/grutil/movieTexture.h b/panda/src/grutil/movieTexture.h index 1ab8367ef6..82cb5ed5ca 100644 --- a/panda/src/grutil/movieTexture.h +++ b/panda/src/grutil/movieTexture.h @@ -43,6 +43,9 @@ PUBLISHED: INLINE double get_video_length() const; INLINE int get_video_width() const; INLINE int get_video_height() const; + + INLINE MovieVideoCursor *get_color_cursor(int page); + INLINE MovieVideoCursor *get_alpha_cursor(int page); void restart(); void stop(); diff --git a/panda/src/movies/config_movies.cxx b/panda/src/movies/config_movies.cxx index 8de8306ca0..3609a50660 100644 --- a/panda/src/movies/config_movies.cxx +++ b/panda/src/movies/config_movies.cxx @@ -44,6 +44,12 @@ ConfigureFn(config_movies) { init_libmovies(); } +ConfigVariableInt ffmpeg_max_readahead_frames +("ffmpeg-max-readahead-frames", 10, + PRC_DESC("The maximum number of frames ahead which an ffmpeg decoder thread " + "should read in advance of actual playback. Set this to 0 to " + "decode ffmpeg videos in the main thread.")); + //////////////////////////////////////////////////////////////////// // Function: init_libmovies // Description: Initializes the library. This must be called at diff --git a/panda/src/movies/config_movies.h b/panda/src/movies/config_movies.h index 7af6579295..e7617e24d1 100644 --- a/panda/src/movies/config_movies.h +++ b/panda/src/movies/config_movies.h @@ -25,6 +25,8 @@ ConfigureDecl(config_movies, EXPCL_PANDA_MOVIES, EXPTP_PANDA_MOVIES); NotifyCategoryDecl(movies, EXPCL_PANDA_MOVIES, EXPTP_PANDA_MOVIES); NotifyCategoryDecl(ffmpeg, EXPCL_PANDA_MOVIES, EXPTP_PANDA_MOVIES); +extern ConfigVariableInt ffmpeg_max_readahead_frames; + extern EXPCL_PANDA_MOVIES void init_libmovies(); #endif /* __CONFIG_MOVIES_H__ */ diff --git a/panda/src/movies/ffmpegVideoCursor.cxx b/panda/src/movies/ffmpegVideoCursor.cxx index 0b619fbf21..f4b3c9c004 100644 --- a/panda/src/movies/ffmpegVideoCursor.cxx +++ b/panda/src/movies/ffmpegVideoCursor.cxx @@ -26,21 +26,29 @@ extern "C" { #include "pStatCollector.h" #include "pStatTimer.h" +Mutex FfmpegVideoCursor::_av_lock; TypeHandle FfmpegVideoCursor::_type_handle; + #if LIBAVFORMAT_VERSION_MAJOR < 53 #define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO #endif //////////////////////////////////////////////////////////////////// // Function: FfmpegVideoCursor::Default Constructor -// Access: Protected +// Access: Private // Description: This constructor is only used when reading from a bam // file. //////////////////////////////////////////////////////////////////// FfmpegVideoCursor:: FfmpegVideoCursor() : - _packet(NULL), + _max_readahead_frames(0), + _lock("FfmpegVideoCursor::_lock"), + _action_cvar(_lock), + _thread_status(TS_stopped), + _seek_time(0.0), + _packet0(NULL), + _packet1(NULL), _format_ctx(NULL), _video_ctx(NULL), _video_index(-1), @@ -52,13 +60,14 @@ FfmpegVideoCursor() : //////////////////////////////////////////////////////////////////// // Function: FfmpegVideoCursor::init_from -// Access: Protected +// Access: Private // Description: Specifies the source of the video cursor. This is // normally called only by the constructor or when // reading from a bam file. //////////////////////////////////////////////////////////////////// void FfmpegVideoCursor:: init_from(FfmpegVideo *source) { + nassertv(_thread == NULL && _thread_status == TS_stopped); nassertv(source != NULL); _source = source; _filename = _source->get_filename(); @@ -108,18 +117,21 @@ init_from(FfmpegVideo *source) { return; } - AVCodec *pVideoCodec = avcodec_find_decoder(_video_ctx->codec_id); - if (pVideoCodec == NULL) { - movies_cat.info() - << "Couldn't find codec\n"; - cleanup(); - return; - } - if (avcodec_open(_video_ctx, pVideoCodec) < 0) { - movies_cat.info() - << "Couldn't open codec\n"; - cleanup(); - return; + { + MutexHolder av_holder(_av_lock); + AVCodec *pVideoCodec = avcodec_find_decoder(_video_ctx->codec_id); + if (pVideoCodec == NULL) { + movies_cat.info() + << "Couldn't find codec\n"; + cleanup(); + return; + } + if (avcodec_open(_video_ctx, pVideoCodec) < 0) { + movies_cat.info() + << "Couldn't open codec\n"; + cleanup(); + return; + } } _size_x = _video_ctx->width; @@ -129,30 +141,42 @@ init_from(FfmpegVideo *source) { _can_seek = true; _can_seek_fast = true; - _packet = new AVPacket; _frame = avcodec_alloc_frame(); _frame_out = avcodec_alloc_frame(); - if ((_packet == 0)||(_frame == 0)||(_frame_out == 0)) { + if ((_frame == 0)||(_frame_out == 0)) { cleanup(); return; } - memset(_packet, 0, sizeof(AVPacket)); + _packet0 = new AVPacket; + _packet1 = new AVPacket; + memset(_packet0, 0, sizeof(AVPacket)); + memset(_packet1, 0, sizeof(AVPacket)); fetch_packet(0.0); - _initial_dts = _packet->dts; + _initial_dts = _packet0->dts; _packet_time = 0.0; - _last_start = -1.0; - _next_start = 0.0; + _begin_time = -1.0; + _end_time = 0.0; + +#ifdef HAVE_THREADS + set_max_readahead_frames(ffmpeg_max_readahead_frames); +#endif // HAVE_THREADS } //////////////////////////////////////////////////////////////////// // Function: FfmpegVideoCursor::Constructor -// Access: Public +// Access: Published // Description: //////////////////////////////////////////////////////////////////// FfmpegVideoCursor:: -FfmpegVideoCursor(FfmpegVideo *src) : - _packet(NULL), +FfmpegVideoCursor(FfmpegVideo *src) : + _max_readahead_frames(0), + _lock("FfmpegVideoCursor::_lock"), + _action_cvar(_lock), + _thread_status(TS_stopped), + _seek_time(0.0), + _packet0(NULL), + _packet1(NULL), _format_ctx(NULL), _video_ctx(NULL), _video_index(-1), @@ -165,21 +189,246 @@ FfmpegVideoCursor(FfmpegVideo *src) : //////////////////////////////////////////////////////////////////// // Function: FfmpegVideoCursor::Destructor -// Access: Public -// Description: xxx +// Access: Published +// Description: //////////////////////////////////////////////////////////////////// FfmpegVideoCursor:: ~FfmpegVideoCursor() { cleanup(); } +//////////////////////////////////////////////////////////////////// +// Function: FfmpegVideoCursor::set_max_readahead_frames +// Access: Published +// Description: Specifies the maximum number of frames that a +// sub-thread will attempt to read ahead of the current +// frame. Setting this to a nonzero allows the video +// decoding to take place in a sub-thread, which +// smoothes out the video decoding time by spreading it +// evenly over several frames. Set this number larger +// to increase the buffer between the currently visible +// frame and the first undecoded frame; set it smaller +// to reduce memory consumption. +// +// Setting this to zero forces the video to be decoded +// in the main thread. If threading is not available in +// the Panda build, this value is always zero. +//////////////////////////////////////////////////////////////////// +void FfmpegVideoCursor:: +set_max_readahead_frames(int max_readahead_frames) { +#ifndef HAVE_THREADS + if (max_readahead_frames > 0) { + ffmpeg_cat.warning() + << "Couldn't set max_readahead_frames to " << max_readahead_frames + << ": threading not available.\n"; + max_readahead_frames = 0; + } +#endif // HAVE_THREADS + + _max_readahead_frames = max_readahead_frames; + if (_max_readahead_frames > 0) { + if (_thread_status == TS_stopped) { + start_thread(); + } + } else { + if (_thread_status != TS_stopped) { + stop_thread(); + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: FfmpegVideoCursor::get_max_readahead_frames +// Access: Published +// Description: Returns the maximum number of frames that a +// sub-thread will attempt to read ahead of the current +// frame. See set_max_readahead_frames(). +//////////////////////////////////////////////////////////////////// +int FfmpegVideoCursor:: +get_max_readahead_frames() const { + return _max_readahead_frames; +} + +//////////////////////////////////////////////////////////////////// +// Function: FfmpegVideoCursor::start_thread +// Access: Published +// Description: Explicitly starts the ffmpeg decoding thread after it +// has been stopped by a call to stop_thread(). The +// thread is normally started automatically, so there is +// no need to call this method unless you have +// previously called stop_thread() for some reason. +//////////////////////////////////////////////////////////////////// +void FfmpegVideoCursor:: +start_thread() { + MutexHolder holder(_lock); + + if (_thread_status == TS_stopped && _max_readahead_frames > 0) { + // Get a unique name for the thread's sync name. + ostringstream strm; + strm << (void *)this; + _sync_name = strm.str(); + + // Create and start the thread object. + _thread_status = TS_wait; + _thread = new GenericThread(_filename.get_basename(), _sync_name, st_thread_main, this); + if (!_thread->start(TP_normal, true)) { + // Couldn't start the thread. + _thread = NULL; + _thread_status = TS_stopped; + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: FfmpegVideoCursor::stop_thread +// Access: Published +// Description: Explicitly stops the ffmpeg decoding thread. There +// is normally no reason to do this unless you want to +// maintain precise control over what threads are +// consuming CPU resources. Calling this method will +// make the video update in the main thread, regardless +// of the setting of max_readahead_frames, until you +// call start_thread() again. +//////////////////////////////////////////////////////////////////// +void FfmpegVideoCursor:: +stop_thread() { + if (_thread_status != TS_stopped) { + PT(GenericThread) thread = _thread; + { + MutexHolder holder(_lock); + if (_thread_status != TS_stopped) { + _thread_status = TS_shutdown; + } + _action_cvar.notify(); + _thread = NULL; + } + + // Now that we've released the lock, we can join the thread. + thread->join(); + } + + // This is a good time to clean up all of the allocated frame + // objects. It's not really necessary to be holding the lock, since + // the thread is gone, but we'll grab it anyway just in case someone + // else starts the thread up again. + MutexHolder holder(_lock); + + Buffers::iterator bi; + for (bi = _readahead_frames.begin(); bi != _readahead_frames.end(); ++bi) { + internal_free_buffer(*bi); + } + _readahead_frames.clear(); + for (bi = _recycled_frames.begin(); bi != _recycled_frames.end(); ++bi) { + internal_free_buffer(*bi); + } + _recycled_frames.clear(); +} + +//////////////////////////////////////////////////////////////////// +// Function: FfmpegVideoCursor::is_thread_started +// Access: Published +// Description: Returns true if the thread has been started, false if +// not. This will always return false if +// max_readahead_frames is 0. +//////////////////////////////////////////////////////////////////// +bool FfmpegVideoCursor:: +is_thread_started() const { + return (_thread_status != TS_stopped); +} + +//////////////////////////////////////////////////////////////////// +// Function: FfmpegVideoCursor::fetch_buffer +// Access: Public, Virtual +// Description: See MovieVideoCursor::fetch_buffer. +//////////////////////////////////////////////////////////////////// +MovieVideoCursor::Buffer *FfmpegVideoCursor:: +fetch_buffer(double time) { + MutexHolder holder(_lock); + + // If there was an error at any point, just return NULL. + if (_format_ctx == (AVFormatContext *)NULL) { + return NULL; + } + + Buffer *frame = NULL; + if (_thread_status == TS_stopped) { + // Non-threaded case. Just get the next frame directly. + frame = do_alloc_frame(); + + fetch_time(time); + export_frame(frame); + + } else { + // Threaded case. Wait for the thread to serve up the required + // frames. + if (!_readahead_frames.empty()) { + frame = _readahead_frames.front(); + _readahead_frames.pop_front(); + _action_cvar.notify(); + while (frame->_end_time < time && !_readahead_frames.empty()) { + // This frame is too old. Discard it. + if (ffmpeg_cat.is_debug()) { + ffmpeg_cat.debug() + << "ffmpeg for " << _filename.get_basename() + << " at time " << time << ", discarding frame at " + << frame->_begin_time << "\n"; + } + do_recycle_frame(frame); + frame = _readahead_frames.front(); + _readahead_frames.pop_front(); + } + if (frame->_begin_time > time) { + // This frame is too new. Empty all remaining frames and seek + // backwards. + do_recycle_all_frames(); + if (_thread_status == TS_wait || _thread_status == TS_seek || _thread_status == TS_readahead) { + _thread_status = TS_seek; + _seek_time = time; + _action_cvar.notify(); + } + } + } + if (frame == NULL || frame->_end_time < time) { + // No frame available, or the frame is too old. Seek. + if (_thread_status == TS_wait || _thread_status == TS_seek || _thread_status == TS_readahead) { + _thread_status = TS_seek; + _seek_time = time; + _action_cvar.notify(); + } + } + } + + if (frame != NULL && (frame->_end_time < time || frame->_begin_time > time)) { + // The frame is too old or too new. Just recycle it. + do_recycle_frame(frame); + frame = NULL; + } + + return frame; +} + +//////////////////////////////////////////////////////////////////// +// Function: FfmpegVideoCursor::release_buffer +// Access: Public, Virtual +// Description: Should be called after processing the Buffer object +// returned by fetch_buffer(), this releases the Buffer +// for future use again. +//////////////////////////////////////////////////////////////////// +void FfmpegVideoCursor:: +release_buffer(Buffer *buffer) { + MutexHolder holder(_lock); + do_recycle_frame(buffer); +} + //////////////////////////////////////////////////////////////////// // Function: FfmpegVideoCursor::cleanup -// Access: Public +// Access: Private // Description: Reset to a standard inactive state. //////////////////////////////////////////////////////////////////// void FfmpegVideoCursor:: cleanup() { + stop_thread(); + if (_frame) { av_free(_frame); _frame = NULL; @@ -191,15 +440,24 @@ cleanup() { _frame_out = NULL; } - if (_packet) { - if (_packet->data) { - av_free_packet(_packet); + if (_packet0) { + if (_packet0->data) { + av_free_packet(_packet0); } - delete _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. diff --git a/panda/src/movies/ffmpegVideoCursor.h b/panda/src/movies/ffmpegVideoCursor.h index e3b4e50048..3b7dfd09ce 100644 --- a/panda/src/movies/ffmpegVideoCursor.h +++ b/panda/src/movies/ffmpegVideoCursor.h @@ -23,6 +23,9 @@ #include "texture.h" #include "pointerTo.h" #include "ffmpegVirtualFile.h" +#include "genericThread.h" +#include "pmutex.h" +#include "conditionVar.h" struct AVFormatContext; struct AVCodecContext; @@ -35,28 +38,76 @@ struct AVFrame; // Description : //////////////////////////////////////////////////////////////////// class EXPCL_PANDA_MOVIES FfmpegVideoCursor : public MovieVideoCursor { -protected: +private: FfmpegVideoCursor(); void init_from(FfmpegVideo *src); PUBLISHED: FfmpegVideoCursor(FfmpegVideo *src); virtual ~FfmpegVideoCursor(); + + void set_max_readahead_frames(int max_readahead_frames); + int get_max_readahead_frames() const; + + void start_thread(); + BLOCKING void stop_thread(); + bool is_thread_started() const; public: - virtual void fetch_into_texture(double time, Texture *t, int page); - virtual void fetch_into_buffer(double time, unsigned char *block, bool rgba); + virtual Buffer *fetch_buffer(double time); + virtual void release_buffer(Buffer *buffer); + +private: + void cleanup(); + + Filename _filename; + string _sync_name; + int _max_readahead_frames; + PT(GenericThread) _thread; + + // This global Mutex protects calls to avcodec_open/close/etc. + static Mutex _av_lock; + + // Protects _readahead_frames, _recycled_buffers, and all the + // immediately following members. + Mutex _lock; + + // Condition: the thread has something to do. + ConditionVar _action_cvar; + + typedef pdeque 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(); diff --git a/panda/src/movies/inkblotVideoCursor.cxx b/panda/src/movies/inkblotVideoCursor.cxx index 3c11338ad0..f9e5fa6716 100644 --- a/panda/src/movies/inkblotVideoCursor.cxx +++ b/panda/src/movies/inkblotVideoCursor.cxx @@ -57,6 +57,7 @@ InkblotVideoCursor(InkblotVideo *src) : { _size_x = src->_specified_x; _size_y = src->_specified_y; + _num_components = 3; _fps = src->_specified_fps; int padx = _size_x + 2; int pady = _size_y + 2; @@ -81,12 +82,13 @@ InkblotVideoCursor:: } //////////////////////////////////////////////////////////////////// -// Function: InkblotVideoCursor::fetch_into_buffer +// Function: InkblotVideoCursor::fetch_buffer // Access: Published, Virtual -// Description: See MovieVideoCursor::fetch_into_buffer. +// Description: See MovieVideoCursor::fetch_buffer. //////////////////////////////////////////////////////////////////// -void InkblotVideoCursor:: -fetch_into_buffer(double time, unsigned char *data, bool bgra) { +MovieVideoCursor::Buffer *InkblotVideoCursor:: +fetch_buffer(double time) { + Buffer *buffer = get_standard_buffer(); int padx = size_x() + 2; int pady = size_y() + 2; @@ -100,7 +102,7 @@ fetch_into_buffer(double time, unsigned char *data, bool bgra) { _frames_read = 0; } - nassertv(time >= _next_start); + nassertr(time >= _next_start, NULL); while (_next_start <= time) { _last_start = (_frames_read * 1.0) / _fps; @@ -126,6 +128,7 @@ fetch_into_buffer(double time, unsigned char *data, bool bgra) { _cells2 = t; } + unsigned char *data = buffer->_block; for (int y=1; y_begin_time = _last_start; + buffer->_end_time = _next_start; + return buffer; } diff --git a/panda/src/movies/inkblotVideoCursor.h b/panda/src/movies/inkblotVideoCursor.h index 8987ea271c..94584eeaf9 100644 --- a/panda/src/movies/inkblotVideoCursor.h +++ b/panda/src/movies/inkblotVideoCursor.h @@ -32,7 +32,7 @@ class EXPCL_PANDA_MOVIES InkblotVideoCursor : public MovieVideoCursor { virtual ~InkblotVideoCursor(); public: - virtual void fetch_into_buffer(double time, unsigned char *block, bool rgba); + virtual Buffer *fetch_buffer(double time); protected: unsigned char *_cells; diff --git a/panda/src/movies/microphoneAudioDS.cxx b/panda/src/movies/microphoneAudioDS.cxx index 69b8a616e4..be8a55b658 100644 --- a/panda/src/movies/microphoneAudioDS.cxx +++ b/panda/src/movies/microphoneAudioDS.cxx @@ -338,7 +338,7 @@ MicrophoneAudioCursorDS:: } //////////////////////////////////////////////////////////////////// -// Function: MicrophoneAudioCursorDS::fetch_into_buffer +// Function: MicrophoneAudioCursorDS::read_samples // Access: Published // Description: //////////////////////////////////////////////////////////////////// diff --git a/panda/src/movies/movieVideoCursor.cxx b/panda/src/movies/movieVideoCursor.cxx index 12a5f19f00..fb50df6ca1 100644 --- a/panda/src/movies/movieVideoCursor.cxx +++ b/panda/src/movies/movieVideoCursor.cxx @@ -30,7 +30,6 @@ TypeHandle MovieVideoCursor::_type_handle; //////////////////////////////////////////////////////////////////// MovieVideoCursor:: MovieVideoCursor(MovieVideo *src) : - _conversion_buffer(0), _source(src), _size_x(1), _size_y(1), @@ -44,6 +43,7 @@ MovieVideoCursor(MovieVideo *src) : _streaming(false), _ready(false) { + _standard_buffer._block = NULL; } //////////////////////////////////////////////////////////////////// @@ -53,22 +53,8 @@ MovieVideoCursor(MovieVideo *src) : //////////////////////////////////////////////////////////////////// MovieVideoCursor:: ~MovieVideoCursor() { - if (_conversion_buffer != 0) { - delete[] _conversion_buffer; - } -} - -//////////////////////////////////////////////////////////////////// -// Function: MovieVideoCursor::allocate_conversion_buffer -// Access: Private -// Description: The generic implementations of fetch_into_texture -// and fetch_into_alpha require the use of a conversion -// buffer. This allocates the buffer. -//////////////////////////////////////////////////////////////////// -void MovieVideoCursor:: -allocate_conversion_buffer() { - if (_conversion_buffer == 0) { - _conversion_buffer = new unsigned char[size_x() * size_y() * 4]; + if (_standard_buffer._block != NULL) { + PANDA_FREE_ARRAY(_standard_buffer._block); } } @@ -98,19 +84,23 @@ setup_texture(Texture *tex) const { // Description: Discards the next video frame. Still sets // last_start and next_start. // -// See fetch_into_buffer for more details. +// See fetch_buffer for more details. //////////////////////////////////////////////////////////////////// void MovieVideoCursor:: fetch_into_bitbucket(double time) { - // This generic implementation is layered on fetch_into_buffer. + // This generic implementation is layered on fetch_buffer. // It will work for any derived class, so it is never necessary to // redefine this. It is probably possible to make a faster // implementation, but since this function is rarely used, it // probably isn't worth the trouble. - allocate_conversion_buffer(); - fetch_into_buffer(time, _conversion_buffer, false); + Buffer *buffer = fetch_buffer(time); + if (buffer != NULL) { + _last_start = buffer->_begin_time; + _next_start = buffer->_end_time; + release_buffer(buffer); + } } //////////////////////////////////////////////////////////////////// @@ -119,14 +109,14 @@ fetch_into_bitbucket(double time) { // Description: Reads the specified video frame into // the specified texture. // -// See fetch_into_buffer for more details. +// See fetch_buffer for more details. //////////////////////////////////////////////////////////////////// -static PStatCollector fetch_into_texture_collector("*:Decode Video into Texture"); void MovieVideoCursor:: fetch_into_texture(double time, Texture *t, int page) { + static PStatCollector fetch_into_texture_collector("*:Decode Video into Texture"); PStatTimer timer(fetch_into_texture_collector); - // This generic implementation is layered on fetch_into_buffer. + // This generic implementation is layered on fetch_buffer. // It will work for any derived class, so it is never necessary to // redefine this. However, it may be possible to make a faster // implementation that uses fewer intermediate copies, depending @@ -136,26 +126,50 @@ fetch_into_texture(double time, Texture *t, int page) { nassertv(t->get_y_size() >= size_y()); nassertv((t->get_num_components() == 3) || (t->get_num_components() == 4)); nassertv(t->get_component_width() == 1); - nassertv(page < t->get_z_size()); + nassertv(page < t->get_num_pages()); + t->set_keep_ram_image(true); PTA_uchar img = t->modify_ram_image(); unsigned char *data = img.p() + page * t->get_expected_ram_page_size(); - if (t->get_x_size() == size_x()) { - fetch_into_buffer(time, data, t->get_num_components() == 4); + Buffer *buffer = fetch_buffer(time); + if (buffer == NULL) { + // No image available. + return; + } + + _last_start = buffer->_begin_time; + _next_start = buffer->_end_time; + + if (t->get_x_size() == size_x() && t->get_num_components() == get_num_components()) { + memcpy(data, buffer->_block, size_x() * size_y() * get_num_components()); + } else { - allocate_conversion_buffer(); - fetch_into_buffer(time, _conversion_buffer, t->get_num_components() == 4); - int src_stride = size_x() * t->get_num_components(); - int dst_stride = t->get_x_size() * t->get_num_components(); - unsigned char *p = _conversion_buffer; - for (int y=0; y_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; yget_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_block; + int src_width = get_num_components(); for (int y=0; yget_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_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 diff --git a/panda/src/movies/movieVideoCursor.h b/panda/src/movies/movieVideoCursor.h index 5612aa4cfc..2f9c65228d 100644 --- a/panda/src/movies/movieVideoCursor.h +++ b/panda/src/movies/movieVideoCursor.h @@ -18,6 +18,7 @@ #include "pandabase.h" #include "texture.h" #include "pointerTo.h" +#include "memoryBase.h" class MovieVideo; class FactoryParams; @@ -63,11 +64,20 @@ PUBLISHED: virtual void fetch_into_texture_alpha(double time, Texture *t, int page, int alpha_src); public: - virtual void fetch_into_buffer(double time, unsigned char *block, bool bgra); + class Buffer : public MemoryBase { + public: + unsigned char *_block; + size_t _block_size; + double _begin_time; + double _end_time; + }; + virtual Buffer *fetch_buffer(double time); + virtual void release_buffer(Buffer *buffer); -private: - void allocate_conversion_buffer(); - unsigned char *_conversion_buffer; +protected: + Buffer *get_standard_buffer(); + Buffer *internal_alloc_buffer(); + void internal_free_buffer(Buffer *buffer); protected: PT(MovieVideo) _source; @@ -83,6 +93,8 @@ protected: bool _streaming; bool _ready; + Buffer _standard_buffer; + public: virtual void write_datagram(BamWriter *manager, Datagram &dg); virtual int complete_pointers(TypedWritable **plist, BamReader *manager); diff --git a/panda/src/pipeline/Sources.pp b/panda/src/pipeline/Sources.pp index d694236605..7034c00734 100644 --- a/panda/src/pipeline/Sources.pp +++ b/panda/src/pipeline/Sources.pp @@ -37,6 +37,7 @@ cycleDataWriter.h cycleDataWriter.I \ cyclerHolder.h cyclerHolder.I \ externalThread.h \ + genericThread.h genericThread.I \ lightMutex.I lightMutex.h \ lightMutexDirect.h lightMutexDirect.I \ lightMutexHolder.I lightMutexHolder.h \ @@ -98,6 +99,7 @@ cycleDataWriter.cxx \ cyclerHolder.cxx \ externalThread.cxx \ + genericThread.cxx \ lightMutex.cxx \ lightMutexDirect.cxx \ lightMutexHolder.cxx \ @@ -155,6 +157,7 @@ cycleDataWriter.h cycleDataWriter.I \ cyclerHolder.h cyclerHolder.I \ externalThread.h \ + genericThread.h genericThread.I \ lightMutex.I lightMutex.h \ lightMutexDirect.h lightMutexDirect.I \ lightMutexHolder.I lightMutexHolder.h \ diff --git a/panda/src/pipeline/config_pipeline.cxx b/panda/src/pipeline/config_pipeline.cxx index 7b36fa240c..26a35fc544 100644 --- a/panda/src/pipeline/config_pipeline.cxx +++ b/panda/src/pipeline/config_pipeline.cxx @@ -16,6 +16,7 @@ #include "asyncTaskBase.h" #include "mainThread.h" #include "externalThread.h" +#include "genericThread.h" #include "thread.h" #include "pythonThread.h" #include "pandaSystem.h" @@ -73,6 +74,7 @@ init_libpipeline() { AsyncTaskBase::init_type(); MainThread::init_type(); ExternalThread::init_type(); + GenericThread::init_type(); Thread::init_type(); #ifdef HAVE_PYTHON PythonThread::init_type(); diff --git a/panda/src/pipeline/genericThread.I b/panda/src/pipeline/genericThread.I new file mode 100644 index 0000000000..094e0c04fd --- /dev/null +++ b/panda/src/pipeline/genericThread.I @@ -0,0 +1,59 @@ +// Filename: genericThread.I +// Created by: drose (09Nov11) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: GenericThread::set_function +// Access: Published +// Description: Replaces the function that is called when the thread +// runs. +//////////////////////////////////////////////////////////////////// +INLINE void GenericThread:: +set_function(GenericThread::ThreadFunc *function) { + _function = function; +} + +//////////////////////////////////////////////////////////////////// +// Function: GenericThread::get_function +// Access: Published +// Description: Returns the function that is called when the thread +// runs. +//////////////////////////////////////////////////////////////////// +INLINE GenericThread::ThreadFunc *GenericThread:: +get_function() const { + return _function; +} + +//////////////////////////////////////////////////////////////////// +// Function: GenericThread::set_user_data +// Access: Published +// Description: Replaces the void pointer that is passed to the thread +// function. This is any arbitrary pointer; the thread +// object does no processing on it. +//////////////////////////////////////////////////////////////////// +INLINE void GenericThread:: +set_user_data(void *user_data) { + _user_data = user_data; +} + +//////////////////////////////////////////////////////////////////// +// Function: GenericThread::get_user_data +// Access: Published +// Description: Returns the void pointer that is passed to the thread +// function. +//////////////////////////////////////////////////////////////////// +INLINE void *GenericThread:: +get_user_data() const { + return _user_data; +} diff --git a/panda/src/pipeline/genericThread.cxx b/panda/src/pipeline/genericThread.cxx new file mode 100644 index 0000000000..bc4e56ca29 --- /dev/null +++ b/panda/src/pipeline/genericThread.cxx @@ -0,0 +1,55 @@ +// Filename: genericThread.cxx +// Created by: drose (09Nov11) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#include "genericThread.h" +#include "pnotify.h" + +TypeHandle GenericThread::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: GenericThread::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +GenericThread:: +GenericThread(const string &name, const string &sync_name) : + Thread(name, sync_name) +{ + _function = NULL; + _user_data = NULL; +} + +//////////////////////////////////////////////////////////////////// +// Function: GenericThread::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +GenericThread:: +GenericThread(const string &name, const string &sync_name, GenericThread::ThreadFunc *function, void *user_data) : + Thread(name, sync_name), + _function(function), + _user_data(user_data) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: GenericThread::thread_main +// Access: Protected, Virtual +// Description: This is the thread's main execution function. +//////////////////////////////////////////////////////////////////// +void GenericThread:: +thread_main() { + nassertv(_function != NULL); + (*_function)(_user_data); +} diff --git a/panda/src/pipeline/genericThread.h b/panda/src/pipeline/genericThread.h new file mode 100644 index 0000000000..77e6f95a41 --- /dev/null +++ b/panda/src/pipeline/genericThread.h @@ -0,0 +1,67 @@ +// Filename: genericThread.h +// Created by: drose (09Nov11) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#ifndef GENERICTHREAD_H +#define GENERICTHREAD_H + +#include "pandabase.h" +#include "thread.h" + +//////////////////////////////////////////////////////////////////// +// Class : GenericThread +// Description : A generic thread type that allows calling a C-style thread +// function without having to subclass. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA_PIPELINE GenericThread : public Thread { +public: + typedef void ThreadFunc(void *user_data); + + GenericThread(const string &name, const string &sync_name); + GenericThread(const string &name, const string &sync_name, ThreadFunc *function, void *user_data); + + INLINE void set_function(ThreadFunc *function); + INLINE ThreadFunc *get_function() const; + + INLINE void set_user_data(void *user_data); + INLINE void *get_user_data() const; + +protected: + virtual void thread_main(); + +private: + ThreadFunc *_function; + void *_user_data; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + Thread::init_type(); + register_type(_type_handle, "GenericThread", + Thread::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#include "genericThread.I" + +#endif + diff --git a/panda/src/pipeline/pipeline_composite1.cxx b/panda/src/pipeline/pipeline_composite1.cxx index 3039d9980b..40dd5b3647 100644 --- a/panda/src/pipeline/pipeline_composite1.cxx +++ b/panda/src/pipeline/pipeline_composite1.cxx @@ -20,6 +20,7 @@ #include "cycleDataWriter.cxx" #include "cyclerHolder.cxx" #include "externalThread.cxx" +#include "genericThread.cxx" #include "lightMutexDirect.cxx" #include "lightMutexHolder.cxx" #include "lightReMutexDirect.cxx" diff --git a/panda/src/vision/arToolKit.cxx b/panda/src/vision/arToolKit.cxx index 8cb346e65d..a593387d1a 100644 --- a/panda/src/vision/arToolKit.cxx +++ b/panda/src/vision/arToolKit.cxx @@ -283,7 +283,7 @@ analyze(Texture *tex, bool do_flip_texture) { int pady = tex->get_pad_y_size(); int xsize = tex->get_x_size() - padx; int ysize = tex->get_y_size() - pady; - int pagesize = xsize * ysize * 4; + //int pagesize = xsize * ysize * 4; nassertv((xsize > 0) && (ysize > 0)); ARParam cparam; diff --git a/panda/src/vision/webcamVideoCursorOpenCV.cxx b/panda/src/vision/webcamVideoCursorOpenCV.cxx index eca17e604e..035852b456 100644 --- a/panda/src/vision/webcamVideoCursorOpenCV.cxx +++ b/panda/src/vision/webcamVideoCursorOpenCV.cxx @@ -61,18 +61,20 @@ WebcamVideoCursorOpenCV:: } //////////////////////////////////////////////////////////////////// -// Function: WebcamVideoCursorOpenCV::fetch_into_buffer +// Function: WebcamVideoCursorOpenCV::fetch_buffer // Access: Published, Virtual // Description: //////////////////////////////////////////////////////////////////// -void WebcamVideoCursorOpenCV:: -fetch_into_buffer(double time, unsigned char *block, bool bgra) { +MovieVideoCursor::Buffer *WebcamVideoCursorOpenCV:: +fetch_buffer(double time) { if (!_ready) { - return; + return NULL; } - unsigned char *dest = block; - int num_components = bgra ? 4 : 3; + Buffer *buffer = get_standard_buffer(); + unsigned char *dest = buffer->_block; + int num_components = get_num_components(); + nassertr(num_components == 3, NULL); ssize_t dest_x_pitch = num_components; // Assume component_width == 1 ssize_t dest_y_pitch = _size_x * dest_x_pitch; @@ -82,7 +84,7 @@ fetch_into_buffer(double time, unsigned char *block, bool bgra) { if (num_components == 3 && x_pitch == 3) { // The easy case--copy the whole thing in, row by row. ssize_t copy_bytes = _size_x * dest_x_pitch; - nassertv(copy_bytes <= dest_y_pitch && copy_bytes <= abs(y_pitch)); + nassertr(copy_bytes <= dest_y_pitch && copy_bytes <= abs(y_pitch), NULL); for (int y = 0; y < _size_y; ++y) { memcpy(dest, r, copy_bytes); @@ -111,6 +113,8 @@ fetch_into_buffer(double time, unsigned char *block, bool bgra) { } } } + + return buffer; } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/vision/webcamVideoCursorOpenCV.h b/panda/src/vision/webcamVideoCursorOpenCV.h index 538d02deca..48f1ce5549 100644 --- a/panda/src/vision/webcamVideoCursorOpenCV.h +++ b/panda/src/vision/webcamVideoCursorOpenCV.h @@ -31,7 +31,7 @@ class WebcamVideoCursorOpenCV : public MovieVideoCursor { public: WebcamVideoCursorOpenCV(WebcamVideoOpenCV *src); virtual ~WebcamVideoCursorOpenCV(); - virtual void fetch_into_buffer(double time, unsigned char *block, bool rgba); + virtual Buffer *fetch_buffer(double time); private: bool get_frame_data(const unsigned char *&r, diff --git a/panda/src/vision/webcamVideoCursorV4L.cxx b/panda/src/vision/webcamVideoCursorV4L.cxx index ba34c5c5aa..ec819cc399 100644 --- a/panda/src/vision/webcamVideoCursorV4L.cxx +++ b/panda/src/vision/webcamVideoCursorV4L.cxx @@ -308,33 +308,34 @@ WebcamVideoCursorV4L:: } //////////////////////////////////////////////////////////////////// -// Function: WebcamVideoCursorV4L::fetch_into_buffer +// Function: WebcamVideoCursorV4L::fetch_buffer // Access: Published, Virtual // Description: //////////////////////////////////////////////////////////////////// -void WebcamVideoCursorV4L:: -fetch_into_buffer(double time, unsigned char *block, bool bgra) { +MovieVideoCursor::Buffer *WebcamVideoCursorV4L:: +fetch_buffer(double time) { if (!_ready) { - return; + return NULL; } + Buffer *buffer = get_standard_buffer(); + unsigned char *block = buffer->_block; struct v4l2_buffer vbuf; memset(&vbuf, 0, sizeof vbuf); vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; vbuf.memory = V4L2_MEMORY_MMAP; if (-1 == ioctl(_fd, VIDIOC_DQBUF, &vbuf) && errno != EIO) { vision_cat.error() << "Failed to dequeue buffer!\n"; - return; + return NULL; } nassertv(vbuf.index < _bufcount); size_t bufsize = _buflens[vbuf.index]; size_t old_bpl = _format->fmt.pix.bytesperline; - size_t new_bpl = bgra ? _size_x * 4 : _size_x * 3; + size_t new_bpl = _size_x * 3; unsigned char *buf = (unsigned char *) _buffers[vbuf.index]; if (_format->fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG) { #ifdef HAVE_JPEG - nassertv(!bgra); struct my_error_mgr jerr; _cinfo->err = jpeg_std_error(&jerr.pub); jerr.pub.error_exit = my_error_exit; @@ -385,38 +386,20 @@ fetch_into_buffer(double time, unsigned char *block, bool bgra) { // Swap red / blue unsigned char ex; - if (bgra) { - for (size_t i = 0; i < new_bpl * _size_y; i += 4) { - ex = block[i]; - block[i] = block[i + 2]; - block[i + 2] = ex; - } - } else { - for (size_t i = 0; i < new_bpl * _size_y; i += 3) { - ex = block[i]; - block[i] = block[i + 2]; - block[i + 2] = ex; - } + for (size_t i = 0; i < new_bpl * _size_y; i += 3) { + ex = block[i]; + block[i] = block[i + 2]; + block[i + 2] = ex; } #else - nassertv(false); // Not compiled with JPEG support + nassertr(false, NULL); // Not compiled with JPEG support #endif } else { - if (bgra) { - for (size_t row = 0; row < _size_y; ++row) { - size_t c = 0; - for (size_t i = 0; i < old_bpl; i += 4) { - yuyv_to_rgbargba(block + (_size_y - row - 1) * new_bpl + c, buf + row * old_bpl + i); - c += 8; - } - } - } else { - for (size_t row = 0; row < _size_y; ++row) { - size_t c = 0; - for (size_t i = 0; i < old_bpl; i += 4) { - yuyv_to_rgbrgb(block + (_size_y - row - 1) * new_bpl + c, buf + row * old_bpl + i); - c += 6; - } + for (size_t row = 0; row < _size_y; ++row) { + size_t c = 0; + for (size_t i = 0; i < old_bpl; i += 4) { + yuyv_to_rgbrgb(block + (_size_y - row - 1) * new_bpl + c, buf + row * old_bpl + i); + c += 6; } } } @@ -424,6 +407,8 @@ fetch_into_buffer(double time, unsigned char *block, bool bgra) { if (-1 == ioctl(_fd, VIDIOC_QBUF, &vbuf)) { vision_cat.error() << "Failed to exchange buffer with driver!\n"; } + + return buffer; } #endif diff --git a/panda/src/vision/webcamVideoCursorV4L.h b/panda/src/vision/webcamVideoCursorV4L.h index 1bc75cdaa3..cf64012991 100644 --- a/panda/src/vision/webcamVideoCursorV4L.h +++ b/panda/src/vision/webcamVideoCursorV4L.h @@ -37,7 +37,7 @@ class WebcamVideoCursorV4L : public MovieVideoCursor { public: WebcamVideoCursorV4L(WebcamVideoV4L *src); virtual ~WebcamVideoCursorV4L(); - virtual void fetch_into_buffer(double time, unsigned char *block, bool rgba); + virtual Buffer *fetch_buffer(double time); private: int _fd; diff --git a/panda/src/vision/webcamVideoDS.cxx b/panda/src/vision/webcamVideoDS.cxx index aa62b5d3db..99b7160c89 100644 --- a/panda/src/vision/webcamVideoDS.cxx +++ b/panda/src/vision/webcamVideoDS.cxx @@ -136,7 +136,7 @@ class WebcamVideoCursorDS : public MovieVideoCursor public: WebcamVideoCursorDS(WebcamVideoDS *src); virtual ~WebcamVideoCursorDS(); - virtual void fetch_into_buffer(double time, unsigned char *block, bool rgba); + virtual Buffer *fetch_buffer(double time); public: void cleanup(); @@ -654,17 +654,18 @@ WebcamVideoCursorDS:: } //////////////////////////////////////////////////////////////////// -// Function: WebcamVideoCursorDS::fetch_into_buffer +// Function: WebcamVideoCursorDS::fetch_buffer // Access: Published // Description: //////////////////////////////////////////////////////////////////// -void WebcamVideoCursorDS:: -fetch_into_buffer(double time, unsigned char *block, bool bgra) { - +WebcamVideoCursor::Buffer *WebcamVideoCursorDS:: +fetch_buffer(double time) { if (!_ready) { - return; + return NULL; } + Buffer *buffer = get_standard_buffer(); + unsigned char *block = buffer->_block; #ifdef LOCKING_MODE unsigned char *ptr; int pixels = _size_x * _size_y; @@ -672,34 +673,17 @@ fetch_into_buffer(double time, unsigned char *block, bool bgra) { if (res == S_OK) { int size = _saved->GetActualDataLength(); if (size == pixels * 3) { - if (bgra) { - for (int i=0; iRelease(); #else int pixels = _size_x * _size_y; - if (bgra) { - for (int i=0; i