Panda3D

ffmpegVideoCursor.cxx

00001 // Filename: ffmpegVideoCursor.cxx
00002 // Created by: jyelon (01Aug2007)
00003 //
00004 ////////////////////////////////////////////////////////////////////
00005 //
00006 // PANDA 3D SOFTWARE
00007 // Copyright (c) Carnegie Mellon University.  All rights reserved.
00008 //
00009 // All use of this software is subject to the terms of the revised BSD
00010 // license.  You should have received a copy of this license along
00011 // with this source code in a file named "LICENSE."
00012 //
00013 ////////////////////////////////////////////////////////////////////
00014 
00015 #include "ffmpegVideoCursor.h"
00016 
00017 #ifdef HAVE_FFMPEG
00018 
00019 #include "config_movies.h"
00020 #include "pStatCollector.h"
00021 #include "pStatTimer.h"
00022 #include "mutexHolder.h"
00023 #include "reMutexHolder.h"
00024 #include "ffmpegVideo.h"
00025 #include "bamReader.h"
00026 extern "C" {
00027   #include "libavcodec/avcodec.h"
00028   #include "libavformat/avformat.h"
00029 #ifdef HAVE_SWSCALE
00030   #include "libswscale/swscale.h"
00031 #endif
00032 }
00033 
00034 ReMutex FfmpegVideoCursor::_av_lock;
00035 TypeHandle FfmpegVideoCursor::_type_handle;
00036 TypeHandle FfmpegVideoCursor::FfmpegBuffer::_type_handle;
00037 
00038 PStatCollector FfmpegVideoCursor::_fetch_buffer_pcollector("*:FFMPEG Video Decoding:Fetch");
00039 PStatCollector FfmpegVideoCursor::_seek_pcollector("*:FFMPEG Video Decoding:Seek");
00040 PStatCollector FfmpegVideoCursor::_export_frame_pcollector("*:FFMPEG Convert Video to BGR");
00041 
00042 
00043 #if LIBAVFORMAT_VERSION_MAJOR < 53
00044   #define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO
00045 #endif
00046 
00047 ////////////////////////////////////////////////////////////////////
00048 //     Function: FfmpegVideoCursor::Default Constructor
00049 //       Access: Private
00050 //  Description: This constructor is only used when reading from a bam
00051 //               file.
00052 ////////////////////////////////////////////////////////////////////
00053 FfmpegVideoCursor::
00054 FfmpegVideoCursor() :
00055   _max_readahead_frames(0),
00056   _thread_priority(ffmpeg_thread_priority),
00057   _lock("FfmpegVideoCursor::_lock"),
00058   _action_cvar(_lock),
00059   _thread_status(TS_stopped),
00060   _seek_frame(0),
00061   _packet(NULL),
00062   _format_ctx(NULL),
00063   _video_ctx(NULL),
00064   _convert_ctx(NULL),
00065   _video_index(-1),
00066   _frame(NULL),
00067   _frame_out(NULL),
00068   _eof_known(false)
00069 {
00070 }
00071 
00072 ////////////////////////////////////////////////////////////////////
00073 //     Function: FfmpegVideoCursor::init_from
00074 //       Access: Private
00075 //  Description: Specifies the source of the video cursor.  This is
00076 //               normally called only by the constructor or when
00077 //               reading from a bam file.
00078 ////////////////////////////////////////////////////////////////////
00079 void FfmpegVideoCursor::
00080 init_from(FfmpegVideo *source) {
00081   nassertv(_thread == NULL && _thread_status == TS_stopped);
00082   nassertv(source != NULL);
00083   _source = source;
00084   _filename = _source->get_filename();
00085 
00086   if (!open_stream()) {
00087     cleanup();
00088     return;
00089   }
00090 
00091   ReMutexHolder av_holder(_av_lock);
00092   
00093 #ifdef HAVE_SWSCALE
00094   nassertv(_convert_ctx == NULL);
00095   _convert_ctx = sws_getContext(_size_x, _size_y,
00096                                 _video_ctx->pix_fmt, _size_x, _size_y,
00097                                 PIX_FMT_BGR24, SWS_BILINEAR | SWS_PRINT_INFO, NULL, NULL, NULL);
00098 #endif  // HAVE_SWSCALE
00099 
00100   _frame = avcodec_alloc_frame();
00101   _frame_out = avcodec_alloc_frame();
00102 
00103   if ((_frame == 0)||(_frame_out == 0)) {
00104     cleanup();
00105     return;
00106   }
00107 
00108   _packet = new AVPacket;
00109   memset(_packet, 0, sizeof(AVPacket));
00110   
00111   fetch_packet(0);
00112   fetch_frame(-1);
00113   _initial_dts = _begin_frame;
00114 
00115   _current_frame = -1;
00116   _eof_known = false;
00117   _eof_frame = 0;
00118 
00119 #ifdef HAVE_THREADS
00120   set_max_readahead_frames(ffmpeg_max_readahead_frames);
00121 #endif  // HAVE_THREADS
00122 }
00123 
00124 ////////////////////////////////////////////////////////////////////
00125 //     Function: FfmpegVideoCursor::Constructor
00126 //       Access: Published
00127 //  Description: 
00128 ////////////////////////////////////////////////////////////////////
00129 FfmpegVideoCursor::
00130 FfmpegVideoCursor(FfmpegVideo *src) : 
00131   _max_readahead_frames(0),
00132   _thread_priority(ffmpeg_thread_priority),
00133   _lock("FfmpegVideoCursor::_lock"),
00134   _action_cvar(_lock),
00135   _thread_status(TS_stopped),
00136   _seek_frame(0),
00137   _packet(NULL),
00138   _format_ctx(NULL),
00139   _video_ctx(NULL),
00140   _convert_ctx(NULL),
00141   _video_index(-1),
00142   _frame(NULL),
00143   _frame_out(NULL),
00144   _eof_known(false)
00145 {
00146   init_from(src);
00147 }
00148 
00149 ////////////////////////////////////////////////////////////////////
00150 //     Function: FfmpegVideoCursor::Destructor
00151 //       Access: Published
00152 //  Description: 
00153 ////////////////////////////////////////////////////////////////////
00154 FfmpegVideoCursor::
00155 ~FfmpegVideoCursor() {
00156   cleanup();
00157 }
00158 
00159 ////////////////////////////////////////////////////////////////////
00160 //     Function: FfmpegVideoCursor::set_max_readahead_frames
00161 //       Access: Published
00162 //  Description: Specifies the maximum number of frames that a
00163 //               sub-thread will attempt to read ahead of the current
00164 //               frame.  Setting this to a nonzero allows the video
00165 //               decoding to take place in a sub-thread, which
00166 //               smoothes out the video decoding time by spreading it
00167 //               evenly over several frames.  Set this number larger
00168 //               to increase the buffer between the currently visible
00169 //               frame and the first undecoded frame; set it smaller
00170 //               to reduce memory consumption.
00171 //
00172 //               Setting this to zero forces the video to be decoded
00173 //               in the main thread.  If threading is not available in
00174 //               the Panda build, this value is always zero.
00175 ////////////////////////////////////////////////////////////////////
00176 void FfmpegVideoCursor::
00177 set_max_readahead_frames(int max_readahead_frames) {
00178 #ifndef HAVE_THREADS
00179   if (max_readahead_frames > 0) {
00180     ffmpeg_cat.warning()
00181       << "Couldn't set max_readahead_frames to " << max_readahead_frames
00182       << ": threading not available.\n";
00183     max_readahead_frames = 0;
00184   }
00185 #endif  // HAVE_THREADS
00186 
00187   _max_readahead_frames = max_readahead_frames;
00188   if (_max_readahead_frames > 0) {
00189     if (_thread_status == TS_stopped) {
00190       start_thread();
00191     }
00192   } else {
00193     if (_thread_status != TS_stopped) {
00194       stop_thread();
00195     }
00196   }
00197 }
00198 
00199 ////////////////////////////////////////////////////////////////////
00200 //     Function: FfmpegVideoCursor::get_max_readahead_frames
00201 //       Access: Published
00202 //  Description: Returns the maximum number of frames that a
00203 //               sub-thread will attempt to read ahead of the current
00204 //               frame.  See set_max_readahead_frames().
00205 ////////////////////////////////////////////////////////////////////
00206 int FfmpegVideoCursor::
00207 get_max_readahead_frames() const {
00208   return _max_readahead_frames;
00209 }
00210 
00211 ////////////////////////////////////////////////////////////////////
00212 //     Function: FfmpegVideoCursor::set_thread_priority
00213 //       Access: Published
00214 //  Description: Changes the thread priority of the thread that
00215 //               decodes the ffmpeg video stream (if
00216 //               max_readahead_frames is nonzero).  Normally you
00217 //               shouldn't mess with this, but there may be special
00218 //               cases where a precise balance of CPU utilization
00219 //               between the main thread and the various ffmpeg
00220 //               service threads may be needed.
00221 ////////////////////////////////////////////////////////////////////
00222 void FfmpegVideoCursor::
00223 set_thread_priority(ThreadPriority thread_priority) {
00224   if (_thread_priority != thread_priority) {
00225     _thread_priority = thread_priority;
00226     if (is_thread_started()) {
00227       stop_thread();
00228       start_thread();
00229     }
00230   }
00231 }
00232 
00233 ////////////////////////////////////////////////////////////////////
00234 //     Function: FfmpegVideoCursor::get_thread_priority
00235 //       Access: Published
00236 //  Description: Returns the current thread priority of the thread that
00237 //               decodes the ffmpeg video stream (if
00238 //               max_readahead_frames is nonzero).  See
00239 //               set_thread_priority().
00240 ////////////////////////////////////////////////////////////////////
00241 ThreadPriority FfmpegVideoCursor::
00242 get_thread_priority() const {
00243   return _thread_priority;
00244 }
00245 
00246 ////////////////////////////////////////////////////////////////////
00247 //     Function: FfmpegVideoCursor::start_thread
00248 //       Access: Published
00249 //  Description: Explicitly starts the ffmpeg decoding thread after it
00250 //               has been stopped by a call to stop_thread().  The
00251 //               thread is normally started automatically, so there is
00252 //               no need to call this method unless you have
00253 //               previously called stop_thread() for some reason.
00254 ////////////////////////////////////////////////////////////////////
00255 void FfmpegVideoCursor::
00256 start_thread() {
00257   MutexHolder holder(_lock);
00258 
00259   if (_thread_status == TS_stopped && _max_readahead_frames > 0) {
00260     // Get a unique name for the thread's sync name.
00261     ostringstream strm;
00262     strm << (void *)this;
00263     _sync_name = strm.str();
00264 
00265     // Create and start the thread object.
00266     _thread_status = TS_wait;
00267     _thread = new GenericThread(_filename.get_basename(), _sync_name, st_thread_main, this);
00268     if (!_thread->start(_thread_priority, true)) {
00269       // Couldn't start the thread.
00270       _thread = NULL;
00271       _thread_status = TS_stopped;
00272     }
00273   }
00274 }
00275 
00276 ////////////////////////////////////////////////////////////////////
00277 //     Function: FfmpegVideoCursor::stop_thread
00278 //       Access: Published
00279 //  Description: Explicitly stops the ffmpeg decoding thread.  There
00280 //               is normally no reason to do this unless you want to
00281 //               maintain precise control over what threads are
00282 //               consuming CPU resources.  Calling this method will
00283 //               make the video update in the main thread, regardless
00284 //               of the setting of max_readahead_frames, until you
00285 //               call start_thread() again.
00286 ////////////////////////////////////////////////////////////////////
00287 void FfmpegVideoCursor::
00288 stop_thread() {
00289   if (_thread_status != TS_stopped) {
00290     PT(GenericThread) thread = _thread;
00291     {
00292       MutexHolder holder(_lock);
00293       if (_thread_status != TS_stopped) {
00294         _thread_status = TS_shutdown;
00295       }
00296       _action_cvar.notify();
00297       _thread = NULL;
00298     }
00299 
00300     // Now that we've released the lock, we can join the thread.
00301     thread->join();
00302   }
00303 
00304   // This is a good time to clean up all of the allocated frame
00305   // objects.  It's not really necessary to be holding the lock, since
00306   // the thread is gone, but we'll grab it anyway just in case someone
00307   // else starts the thread up again.
00308   MutexHolder holder(_lock);
00309 
00310   _readahead_frames.clear();
00311 }
00312 
00313 ////////////////////////////////////////////////////////////////////
00314 //     Function: FfmpegVideoCursor::is_thread_started
00315 //       Access: Published
00316 //  Description: Returns true if the thread has been started, false if
00317 //               not.  This will always return false if
00318 //               max_readahead_frames is 0.
00319 ////////////////////////////////////////////////////////////////////
00320 bool FfmpegVideoCursor::
00321 is_thread_started() const {
00322   return (_thread_status != TS_stopped);
00323 }
00324 
00325 ////////////////////////////////////////////////////////////////////
00326 //     Function: FfmpegVideoCursor::set_time
00327 //       Access: Published, Virtual
00328 //  Description: See MovieVideoCursor::set_time().
00329 ////////////////////////////////////////////////////////////////////
00330 bool FfmpegVideoCursor::
00331 set_time(double timestamp, int loop_count) {
00332   int frame = (int)(timestamp / _video_timebase + 0.5);
00333 
00334   if (_eof_known) {
00335     if (loop_count == 0) {
00336       frame = frame % _eof_frame;
00337     } else {
00338       int last_frame = _eof_frame * loop_count;
00339       if (frame < last_frame) {
00340         frame = frame % _eof_frame;
00341       } else {
00342         frame = _eof_frame - 1;
00343       }
00344     }
00345   }
00346 
00347   // No point in trying to position before the first frame.
00348   frame = max(frame, _initial_dts);
00349 
00350   if (ffmpeg_cat.is_spam() && frame != _current_frame) {
00351     ffmpeg_cat.spam()
00352       << "set_time(" << timestamp << "): " << frame
00353       << ", loop_count = " << loop_count << "\n";
00354   }
00355 
00356   _current_frame = frame;
00357   if (_current_frame_buffer != NULL) {
00358     // If we've previously returned a frame, don't bother asking for a
00359     // next one if that frame is still valid.
00360     return (_current_frame >= _current_frame_buffer->_end_frame || 
00361             _current_frame < _current_frame_buffer->_begin_frame);
00362   }
00363 
00364   // If our last request didn't return a frame, try again.
00365   return true;
00366 }
00367 
00368 ////////////////////////////////////////////////////////////////////
00369 //     Function: FfmpegVideoCursor::fetch_buffer
00370 //       Access: Public, Virtual
00371 //  Description: See MovieVideoCursor::fetch_buffer.
00372 ////////////////////////////////////////////////////////////////////
00373 PT(MovieVideoCursor::Buffer) FfmpegVideoCursor::
00374 fetch_buffer() {
00375   MutexHolder holder(_lock);
00376   
00377   // If there was an error at any point, just return NULL.
00378   if (_format_ctx == (AVFormatContext *)NULL) {
00379     return NULL;
00380   }
00381 
00382   PT(FfmpegBuffer) frame;
00383   if (_thread_status == TS_stopped) {
00384     // Non-threaded case.  Just get the next frame directly.
00385     advance_to_frame(_current_frame);
00386     if (_frame_ready) {
00387       frame = do_alloc_frame();
00388       export_frame(frame);
00389     }
00390 
00391   } else {
00392     // Threaded case.  Wait for the thread to serve up the required
00393     // frames.
00394     if (!_readahead_frames.empty()) {
00395       frame = _readahead_frames.front();
00396       _readahead_frames.pop_front();
00397       _action_cvar.notify();
00398       while (frame->_end_frame < _current_frame && !_readahead_frames.empty()) {
00399         // This frame is too old.  Discard it.
00400         if (ffmpeg_cat.is_debug()) {
00401           ffmpeg_cat.debug()
00402             << "ffmpeg for " << _filename.get_basename()
00403             << " at frame " << _current_frame << ", discarding frame at "
00404             << frame->_begin_frame << "\n";
00405         }
00406         frame = _readahead_frames.front();
00407         _readahead_frames.pop_front();
00408       }
00409       if (frame->_begin_frame > _current_frame) {
00410         // This frame is too new.  Empty all remaining frames and seek
00411         // backwards.
00412         if (ffmpeg_cat.is_debug()) {
00413           ffmpeg_cat.debug()
00414             << "ffmpeg for " << _filename.get_basename()
00415             << " at frame " << _current_frame << ", encountered too-new frame at "
00416             << frame->_begin_frame << "\n";
00417         }
00418         do_clear_all_frames();
00419         if (_thread_status == TS_wait || _thread_status == TS_seek || _thread_status == TS_readahead) {
00420           _thread_status = TS_seek;
00421           _seek_frame = _current_frame;
00422           _action_cvar.notify();
00423         }
00424       }
00425     }
00426     if (frame == NULL || frame->_end_frame < _current_frame) {
00427       // No frame available, or the frame is too old.  Seek.
00428       if (_thread_status == TS_wait || _thread_status == TS_seek || _thread_status == TS_readahead) {
00429         _thread_status = TS_seek;
00430         _seek_frame = _current_frame;
00431         _action_cvar.notify();
00432       }
00433     }
00434   }
00435 
00436   if (frame != NULL) {
00437     bool too_old = (frame->_end_frame < _current_frame && !ffmpeg_show_seek_frames);
00438     bool too_new = frame->_begin_frame > _current_frame;
00439     if (too_old || too_new) {
00440       // The frame is too old or too new.  Just recycle it.
00441       frame = NULL;
00442     }
00443   }
00444 
00445   if (frame != NULL) {
00446     _current_frame_buffer = frame;
00447     if (ffmpeg_cat.is_debug()) {
00448       ffmpeg_cat.debug()
00449         << "ffmpeg for " << _filename.get_basename()
00450         << " at frame " << _current_frame << ", returning frame at "
00451         << frame->_begin_frame << "\n";
00452     }
00453   } else {
00454     if (ffmpeg_cat.is_debug()) {
00455       ffmpeg_cat.debug()
00456         << "ffmpeg for " << _filename.get_basename()
00457         << " at frame " << _current_frame << ", returning NULL\n";
00458     }
00459   }
00460   return frame.p();
00461 }
00462 
00463 ////////////////////////////////////////////////////////////////////
00464 //     Function: FfmpegVideoCursor::make_new_buffer
00465 //       Access: Protected, Virtual
00466 //  Description: May be called by a derived class to allocate a new
00467 //               Buffer object.
00468 ////////////////////////////////////////////////////////////////////
00469 PT(MovieVideoCursor::Buffer) FfmpegVideoCursor::
00470 make_new_buffer() {
00471   PT(FfmpegBuffer) frame = new FfmpegBuffer(size_x() * size_y() * get_num_components(), _video_timebase);
00472   return frame.p();
00473 }
00474 
00475 ////////////////////////////////////////////////////////////////////
00476 //     Function: FfmpegVideoCursor::open_stream
00477 //       Access: Private
00478 //  Description: Opens the stream for the first time, or when needed
00479 //               internally.
00480 ////////////////////////////////////////////////////////////////////
00481 bool FfmpegVideoCursor::
00482 open_stream() {
00483   nassertr(!_ffvfile.is_open(), false);
00484 
00485   // Hold the global lock while we open the file and create avcodec
00486   // objects.
00487   ReMutexHolder av_holder(_av_lock);
00488 
00489   if (!_source->get_subfile_info().is_empty()) {
00490     // Read a subfile.
00491     if (!_ffvfile.open_subfile(_source->get_subfile_info())) {
00492       ffmpeg_cat.info() 
00493         << "Couldn't open " << _source->get_subfile_info() << "\n";
00494       close_stream();
00495       return false;
00496     }
00497 
00498   } else {
00499     // Read a filename.
00500     if (!_ffvfile.open_vfs(_filename)) {
00501       ffmpeg_cat.info() 
00502         << "Couldn't open " << _filename << "\n";
00503       close_stream();
00504       return false;
00505     }
00506   }
00507 
00508   nassertr(_format_ctx == NULL, false);
00509   _format_ctx = _ffvfile.get_format_context();
00510   nassertr(_format_ctx != NULL, false);
00511 
00512   if (av_find_stream_info(_format_ctx) < 0) {
00513     ffmpeg_cat.info() 
00514       << "Couldn't find stream info\n";
00515     close_stream();
00516     return false;
00517   }
00518   
00519   // Find the video stream
00520   nassertr(_video_ctx == NULL, false);
00521   for (int i = 0; i < (int)_format_ctx->nb_streams; ++i) {
00522     if (_format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
00523       _video_index = i;
00524       _video_ctx = _format_ctx->streams[i]->codec;
00525       _video_timebase = av_q2d(_format_ctx->streams[i]->time_base);
00526       _min_fseek = (int)(3.0 / _video_timebase);
00527     }
00528   }
00529   
00530   if (_video_ctx == NULL) {
00531     ffmpeg_cat.info() 
00532       << "Couldn't find video_ctx\n";
00533     close_stream();
00534     return false;
00535   }
00536 
00537   AVCodec *pVideoCodec = avcodec_find_decoder(_video_ctx->codec_id);
00538   if (pVideoCodec == NULL) {
00539     ffmpeg_cat.info() 
00540       << "Couldn't find codec\n";
00541     close_stream();
00542     return false;
00543   }
00544   if (avcodec_open(_video_ctx, pVideoCodec) < 0) {
00545     ffmpeg_cat.info() 
00546       << "Couldn't open codec\n";
00547     close_stream();
00548     return false;
00549   }
00550   
00551   _size_x = _video_ctx->width;
00552   _size_y = _video_ctx->height;
00553   _num_components = 3; // Don't know how to implement RGBA movies yet.
00554   _length = (double)_format_ctx->duration / (double)AV_TIME_BASE;
00555   _can_seek = true;
00556   _can_seek_fast = true;
00557 
00558   return true;
00559 }
00560 
00561 ////////////////////////////////////////////////////////////////////
00562 //     Function: FfmpegVideoCursor::close_stream
00563 //       Access: Private
00564 //  Description: Closes the stream, during cleanup or when needed
00565 //               internally.
00566 ////////////////////////////////////////////////////////////////////
00567 void FfmpegVideoCursor::
00568 close_stream() {
00569   // Hold the global lock while we free avcodec objects.
00570   ReMutexHolder av_holder(_av_lock);
00571   
00572   if ((_video_ctx)&&(_video_ctx->codec)) {
00573     avcodec_close(_video_ctx);
00574   }
00575   _video_ctx = NULL;
00576   
00577   _ffvfile.close();
00578   _format_ctx = NULL;
00579 
00580   _video_index = -1;
00581 }
00582 
00583 ////////////////////////////////////////////////////////////////////
00584 //     Function: FfmpegVideoCursor::cleanup
00585 //       Access: Private
00586 //  Description: Reset to a standard inactive state.
00587 ////////////////////////////////////////////////////////////////////
00588 void FfmpegVideoCursor::
00589 cleanup() {
00590   stop_thread();
00591   close_stream();
00592 
00593   ReMutexHolder av_holder(_av_lock);
00594 
00595 #ifdef HAVE_SWSCALE
00596   if (_convert_ctx != NULL) {
00597     sws_freeContext(_convert_ctx);
00598   }
00599   _convert_ctx = NULL;
00600 #endif  // HAVE_SWSCALE
00601 
00602   if (_frame) {
00603     av_free(_frame);
00604     _frame = NULL;
00605   }
00606 
00607   if (_frame_out) {
00608     _frame_out->data[0] = 0;
00609     av_free(_frame_out);
00610     _frame_out = NULL;
00611   }
00612 
00613   if (_packet) {
00614     if (_packet->data) {
00615       av_free_packet(_packet);
00616     }
00617     delete _packet;
00618     _packet = NULL;
00619   }
00620 }
00621 
00622 ////////////////////////////////////////////////////////////////////
00623 //     Function: FfmpegVideoCursor::st_thread_main
00624 //       Access: Private, Static
00625 //  Description: The thread main function, static version (for passing
00626 //               to GenericThread).
00627 ////////////////////////////////////////////////////////////////////
00628 void FfmpegVideoCursor::
00629 st_thread_main(void *self) {
00630   ((FfmpegVideoCursor *)self)->thread_main();
00631 }
00632 
00633 ////////////////////////////////////////////////////////////////////
00634 //     Function: FfmpegVideoCursor::thread_main
00635 //       Access: Private
00636 //  Description: The thread main function.
00637 ////////////////////////////////////////////////////////////////////
00638 void FfmpegVideoCursor::
00639 thread_main() {
00640   if (ffmpeg_cat.is_spam()) {
00641     ffmpeg_cat.spam()
00642       << "ffmpeg thread for " << _filename.get_basename() << " starting.\n";
00643   }
00644 
00645   // First, push the first frame onto the readahead queue.
00646   if (_frame_ready) {
00647     PT(FfmpegBuffer) frame = do_alloc_frame();
00648     export_frame(frame);
00649     MutexHolder holder(_lock);
00650     _readahead_frames.push_back(frame);
00651   }
00652   
00653   // Now repeatedly wait for something interesting to do, until we're told
00654   // to shut down.
00655   MutexHolder holder(_lock);
00656   while (_thread_status != TS_shutdown) {
00657     nassertv(_thread_status != TS_stopped);
00658     _action_cvar.wait();
00659 
00660     while (do_poll()) {
00661       // Keep doing stuff as long as there's something to do.
00662       _lock.release();
00663       PStatClient::thread_tick(_sync_name);
00664       Thread::consider_yield();
00665       _lock.acquire();
00666     }
00667   }
00668 
00669   _thread_status = TS_stopped;
00670   if (ffmpeg_cat.is_spam()) {
00671     ffmpeg_cat.spam()
00672       << "ffmpeg thread for " << _filename.get_basename() << " stopped.\n";
00673   }
00674 }
00675 
00676 ////////////////////////////////////////////////////////////////////
00677 //     Function: FfmpegVideoCursor::do_poll
00678 //       Access: Private
00679 //  Description: Called within the sub-thread.  Assumes the lock is
00680 //               already held.  If there is something for the thread
00681 //               to do, does it and returns true.  If there is nothing
00682 //               for the thread to do, returns false.
00683 ////////////////////////////////////////////////////////////////////
00684 bool FfmpegVideoCursor::
00685 do_poll() {
00686   switch (_thread_status) {
00687   case TS_stopped:
00688   case TS_seeking:
00689     // This shouldn't be possible while the thread is running.
00690     nassertr(false, false);
00691     return false;
00692     
00693   case TS_wait:
00694     // The video hasn't started playing yet.
00695     return false;
00696 
00697   case TS_readahead:
00698     if ((int)_readahead_frames.size() < _max_readahead_frames) {
00699       // Time to read the next frame.
00700       PT(FfmpegBuffer) frame = do_alloc_frame();
00701       nassertr(frame != NULL, false);
00702       _lock.release();
00703       fetch_frame(-1);
00704       if (_frame_ready) {
00705         export_frame(frame);
00706         _lock.acquire();
00707         _readahead_frames.push_back(frame);
00708       } else {
00709         // No frame.
00710         _lock.acquire();
00711       }
00712       return true;
00713     }
00714 
00715     // No room for the next frame yet.  Wait for more.
00716     return false;
00717 
00718   case TS_seek:
00719     // Seek to a specific frame.
00720     {
00721       int seek_frame = _seek_frame;
00722       _thread_status = TS_seeking;
00723       PT(FfmpegBuffer) frame = do_alloc_frame();
00724       nassertr(frame != NULL, false);
00725       _lock.release();
00726       advance_to_frame(seek_frame);
00727       if (_frame_ready) {
00728         export_frame(frame);
00729         _lock.acquire();
00730         do_clear_all_frames();
00731         _readahead_frames.push_back(frame);
00732       } else {
00733         _lock.acquire();
00734         do_clear_all_frames();
00735       }
00736 
00737       if (_thread_status == TS_seeking) {
00738         // After seeking, we automatically transition to readahead.
00739         _thread_status = TS_readahead;
00740       }
00741     }
00742     return true;
00743 
00744   case TS_shutdown:
00745     // Time to stop the thread.
00746     return false;
00747   }
00748 
00749   return false;
00750 }
00751 
00752 ////////////////////////////////////////////////////////////////////
00753 //     Function: FfmpegVideoCursor::do_alloc_frame
00754 //       Access: Private
00755 //  Description: Allocates a new Buffer object.  Assumes the lock is
00756 //               held.
00757 ////////////////////////////////////////////////////////////////////
00758 PT(FfmpegVideoCursor::FfmpegBuffer) FfmpegVideoCursor::
00759 do_alloc_frame() {
00760   PT(Buffer) buffer = make_new_buffer();
00761   return (FfmpegBuffer *)buffer.p();
00762 }
00763  
00764 ////////////////////////////////////////////////////////////////////
00765 //     Function: FfmpegVideoCursor::do_clear_all_frames
00766 //       Access: Private
00767 //  Description: Empties the entire readahead_frames queue.
00768 //               Assumes the lock is held.
00769 ////////////////////////////////////////////////////////////////////
00770 void FfmpegVideoCursor::
00771 do_clear_all_frames() {
00772   _readahead_frames.clear();
00773 }
00774 
00775 ////////////////////////////////////////////////////////////////////
00776 //     Function: FfmpegVideoCursor::fetch_packet
00777 //       Access: Private
00778 //  Description: Called within the sub-thread.  Fetches a video packet
00779 //               and stores it in the packet0 buffer.  Sets packet_frame
00780 //               to the packet's timestamp.  If a packet could not be
00781 //               read, the packet is cleared and the packet_frame is
00782 //               set to the specified default value.  Returns true on
00783 //               failure (such as the end of the video), or false on
00784 //               success.
00785 ////////////////////////////////////////////////////////////////////
00786 bool FfmpegVideoCursor::
00787 fetch_packet(int default_frame) {
00788   if (ffmpeg_global_lock) {
00789     ReMutexHolder av_holder(_av_lock);
00790     return do_fetch_packet(default_frame);
00791   } else {
00792     return do_fetch_packet(default_frame);
00793   }
00794 }
00795 
00796 ////////////////////////////////////////////////////////////////////
00797 //     Function: FfmpegVideoCursor::do_fetch_packet
00798 //       Access: Private
00799 //  Description: As above, with the ffmpeg global lock held (if
00800 //               configured on).
00801 ////////////////////////////////////////////////////////////////////
00802 bool FfmpegVideoCursor::
00803 do_fetch_packet(int default_frame) {
00804   if (_packet->data) {
00805     av_free_packet(_packet);
00806   }
00807   while (av_read_frame(_format_ctx, _packet) >= 0) {
00808     if (_packet->stream_index == _video_index) {
00809       _packet_frame = _packet->dts;
00810       return false;
00811     }
00812     av_free_packet(_packet);
00813   }
00814   _packet->data = 0;
00815 
00816   if (!_eof_known && default_frame != 0) {
00817     _eof_frame = _packet_frame;
00818     _eof_known = true;
00819   }
00820 
00821   if (ffmpeg_cat.is_spam()) {
00822     if (_eof_known) {
00823       ffmpeg_cat.spam()
00824         << "end of video at frame " << _eof_frame << "\n";
00825     } else {
00826       ffmpeg_cat.spam()
00827         << "end of video\n";
00828     }
00829   }
00830   _packet_frame = default_frame;
00831   return true;
00832 }
00833 
00834 ////////////////////////////////////////////////////////////////////
00835 //     Function: FfmpegVideoCursor::fetch_frame
00836 //       Access: Private
00837 //  Description: Called within the sub-thread.  Slides forward until
00838 //               the indicated frame, then fetches a frame from the
00839 //               stream and stores it in the frame buffer.  Sets
00840 //               _begin_frame and _end_frame to indicate the extents of
00841 //               the frame.  Sets _frame_ready true to indicate a
00842 //               frame is now available, or false if it is not (for
00843 //               instance, because the end of the video was reached).
00844 ////////////////////////////////////////////////////////////////////
00845 void FfmpegVideoCursor::
00846 fetch_frame(int frame) {
00847   PStatTimer timer(_fetch_buffer_pcollector);
00848 
00849   int finished = 0;
00850 
00851   if (_packet_frame <= frame) {
00852     finished = 0;
00853 
00854     // Get the next packet.  The first packet beyond the frame we're
00855     // looking for marks the point to stop.
00856     while (_packet_frame <= frame) {
00857       PStatTimer timer(_seek_pcollector);
00858 
00859       // Decode the previous packet, and get the next one.
00860       decode_frame(finished);
00861       _begin_frame = _packet_frame;
00862       if (fetch_packet(frame)) {
00863         _end_frame = _packet_frame;
00864         _frame_ready = false;
00865         return;
00866       }
00867     }
00868 
00869   } else {
00870     // Just get the next frame.
00871     finished = 0;
00872     while (!finished && _packet->data) {
00873       decode_frame(finished);
00874       _begin_frame = _packet_frame;
00875       fetch_packet(_begin_frame + 1);
00876     }
00877   }
00878 
00879   _end_frame = _packet_frame;
00880   _frame_ready = true;
00881 }
00882 
00883 ////////////////////////////////////////////////////////////////////
00884 //     Function: FfmpegVideoCursor::decode_frame
00885 //       Access: Private
00886 //  Description: Called within the sub-thread.  Decodes the data in
00887 //               the specified packet into _frame.
00888 ////////////////////////////////////////////////////////////////////
00889 void FfmpegVideoCursor::
00890 decode_frame(int &finished) {
00891   if (ffmpeg_global_lock) {
00892     ReMutexHolder av_holder(_av_lock);
00893     do_decode_frame(finished);
00894   } else {
00895     do_decode_frame(finished);
00896   }
00897 }
00898 
00899 ////////////////////////////////////////////////////////////////////
00900 //     Function: FfmpegVideoCursor::do_decode_frame
00901 //       Access: Private
00902 //  Description: As above, with the ffmpeg global lock held (if
00903 //               configured on).
00904 ////////////////////////////////////////////////////////////////////
00905 void FfmpegVideoCursor::
00906 do_decode_frame(int &finished) {
00907 #if LIBAVCODEC_VERSION_INT < 3414272
00908   avcodec_decode_video(_video_ctx, _frame,
00909                        &finished, _packet->data, _packet->size);
00910 #else
00911   avcodec_decode_video2(_video_ctx, _frame, &finished, _packet);
00912 #endif
00913 }
00914 
00915 ////////////////////////////////////////////////////////////////////
00916 //     Function: FfmpegVideoCursor::seek
00917 //       Access: Private
00918 //  Description: Called within the sub-thread. Seeks to a target
00919 //               location.  Afterward, the packet_frame is guaranteed
00920 //               to be less than or equal to the specified frame.
00921 ////////////////////////////////////////////////////////////////////
00922 void FfmpegVideoCursor::
00923 seek(int frame, bool backward) {
00924   PStatTimer timer(_seek_pcollector);
00925 
00926   if (ffmpeg_support_seek) {
00927     if (ffmpeg_global_lock) {
00928       ReMutexHolder av_holder(_av_lock);
00929       do_seek(frame, backward);
00930     } else {
00931       do_seek(frame, backward);
00932     }
00933 
00934   } else {
00935     // If seeking isn't supported, close-and-reopen.
00936     if (backward) {
00937       reset_stream();
00938     }
00939   }
00940 }
00941 
00942 ////////////////////////////////////////////////////////////////////
00943 //     Function: FfmpegVideoCursor::do_seek
00944 //       Access: Private
00945 //  Description: As above, with the ffmpeg global lock held (if
00946 //               configured on).  Also only if ffmpeg-support-seek is
00947 //               on.
00948 ////////////////////////////////////////////////////////////////////
00949 void FfmpegVideoCursor::
00950 do_seek(int frame, bool backward) {
00951   PN_int64 target_ts = (PN_int64)frame;
00952   if (target_ts < (PN_int64)(_initial_dts)) {
00953     // Attempts to seek before the first packet will fail.
00954     target_ts = _initial_dts;
00955   }
00956   int flags = 0;
00957   if (backward) {
00958     flags = AVSEEK_FLAG_BACKWARD;
00959   }
00960     
00961   if (av_seek_frame(_format_ctx, _video_index, target_ts, flags) < 0) {
00962     if (ffmpeg_cat.is_spam()) {
00963       ffmpeg_cat.spam()
00964         << "Seek failure.\n";
00965     }
00966       
00967     if (backward) {
00968       // Now try to seek forward.
00969       reset_stream();
00970       seek(frame, false);
00971       return;
00972     }
00973       
00974     // Try a binary search to get a little closer.
00975     if (binary_seek(_initial_dts, frame, frame, 1) < 0) {
00976       if (ffmpeg_cat.is_spam()) {
00977         ffmpeg_cat.spam()
00978           << "Seek double failure.\n";
00979       }
00980       reset_stream();
00981       return;
00982     }
00983   }
00984 
00985   fetch_packet(0);
00986   fetch_frame(-1);
00987 }
00988 
00989 ////////////////////////////////////////////////////////////////////
00990 //     Function: FfmpegVideoCursor::binary_seek
00991 //       Access: Private
00992 //  Description: Casts about within the stream for a reasonably-close
00993 //               frame to seek to.  We're trying to get as close as
00994 //               possible to target_frame.
00995 ////////////////////////////////////////////////////////////////////
00996 int FfmpegVideoCursor::
00997 binary_seek(int min_frame, int max_frame, int target_frame, int num_iterations) {
00998   int try_frame = (min_frame + max_frame) / 2;
00999   if (num_iterations > 5 || try_frame >= max_frame) {
01000     // Success.
01001     return 0; 
01002   }
01003 
01004   if (av_seek_frame(_format_ctx, _video_index, try_frame, AVSEEK_FLAG_BACKWARD) < 0) {
01005     // Failure.  Try lower.
01006     if (binary_seek(min_frame, try_frame - 1, target_frame, num_iterations + 1) < 0) {
01007       return -1;
01008     }
01009   } else {
01010     // Success.  Try higher.
01011     if (binary_seek(try_frame + 1, max_frame, target_frame, num_iterations + 1) < 0) {
01012       return -1;
01013     }
01014   }
01015   return 0;
01016 }
01017 
01018 ////////////////////////////////////////////////////////////////////
01019 //     Function: FfmpegVideoCursor::reset_stream
01020 //       Access: Private
01021 //  Description: Resets the stream to its initial, first-opened state
01022 //               by closing and re-opening it.
01023 ////////////////////////////////////////////////////////////////////
01024 void FfmpegVideoCursor::
01025 reset_stream() {
01026   if (ffmpeg_cat.is_spam()) {
01027     ffmpeg_cat.spam()
01028       << "Resetting ffmpeg stream.\n";
01029   }
01030 
01031   close_stream();
01032   if (!open_stream()) {
01033     ffmpeg_cat.error()
01034       << "Stream error, invalidating movie.\n";
01035     cleanup();
01036     return;
01037   }
01038 
01039   fetch_packet(0);
01040   fetch_frame(-1);
01041 }
01042 
01043 ////////////////////////////////////////////////////////////////////
01044 //     Function: FfmpegVideoCursor::advance_to_frame
01045 //       Access: Private 
01046 //  Description: Called within the sub-thread.  Advance until the
01047 //               specified frame is in the export buffer.
01048 ////////////////////////////////////////////////////////////////////
01049 void FfmpegVideoCursor::
01050 advance_to_frame(int frame) {
01051   PStatTimer timer(_fetch_buffer_pcollector);
01052 
01053   if (frame < _begin_frame) {
01054     // Frame is in the past.
01055     if (ffmpeg_cat.is_spam()) {
01056       ffmpeg_cat.spam()
01057         << "Seeking backward to " << frame << " from " << _begin_frame << "\n";
01058     }
01059     seek(frame, true);
01060     if (_begin_frame > frame) {
01061       if (ffmpeg_cat.is_spam()) {
01062         ffmpeg_cat.spam()
01063           << "Ended up at " << _begin_frame << ", not far enough back!\n";
01064       }
01065       reset_stream();
01066       if (ffmpeg_cat.is_spam()) {
01067         ffmpeg_cat.spam()
01068           << "Reseek to 0, got " << _begin_frame << "\n";
01069       }
01070     }
01071     if (frame > _end_frame) {
01072       if (ffmpeg_cat.is_spam()) {
01073         ffmpeg_cat.spam()
01074           << "Now sliding forward to " << frame << " from " << _begin_frame << "\n";
01075       }
01076       fetch_frame(frame);
01077     }
01078 
01079   } else if (frame < _end_frame) {
01080     // Frame is in the present: already have the frame.
01081     if (ffmpeg_cat.is_spam()) {
01082       ffmpeg_cat.spam()
01083         << "Currently have " << frame << " within " << _begin_frame << " .. " << _end_frame << "\n";
01084     }
01085 
01086   } else if (frame < _end_frame + _min_fseek) {
01087     // Frame is in the near future.
01088     if (ffmpeg_cat.is_spam()) {
01089       ffmpeg_cat.spam()
01090         << "Sliding forward to " << frame << " from " << _begin_frame << "\n";
01091     }
01092     fetch_frame(frame);
01093 
01094   } else {
01095     // Frame is in the far future.  Seek forward, then read.
01096     // There's a danger here: because keyframes are spaced
01097     // unpredictably, trying to seek forward could actually
01098     // move us backward in the stream!  This must be avoided.
01099     // So the rule is, try the seek.  If it hurts us by moving
01100     // us backward, we increase the minimum threshold distance
01101     // for forward-seeking in the future.
01102     
01103     if (ffmpeg_cat.is_spam()) {
01104       ffmpeg_cat.spam()
01105         << "Jumping forward to " << frame << " from " << _begin_frame << "\n";
01106     }
01107     int base = _begin_frame;
01108     seek(frame, false);
01109     if (_begin_frame < base) {
01110       _min_fseek += (base - _begin_frame);
01111       if (ffmpeg_cat.is_spam()) {
01112         ffmpeg_cat.spam()
01113           << "Wrong way!  Increasing _min_fseek to " << _min_fseek << "\n";
01114       }
01115     }
01116     if (frame > _end_frame) {
01117       if (ffmpeg_cat.is_spam()) {
01118         ffmpeg_cat.spam()
01119           << "Correcting, sliding forward to " << frame << " from " << _begin_frame << "\n";
01120       }
01121       fetch_frame(frame);
01122     }
01123   }
01124 
01125   if (ffmpeg_cat.is_spam()) {
01126     ffmpeg_cat.spam()
01127       << "Wanted " << frame << ", got " << _begin_frame << "\n";
01128   }
01129 }
01130 
01131 ////////////////////////////////////////////////////////////////////
01132 //     Function: FfmpegVideoCursor::export_frame
01133 //       Access: Private
01134 //  Description: Called within the sub-thread.  Exports the contents
01135 //               of the frame buffer into the indicated target buffer.
01136 ////////////////////////////////////////////////////////////////////
01137 void FfmpegVideoCursor::
01138 export_frame(FfmpegBuffer *buffer) {
01139   PStatTimer timer(_export_frame_pcollector);
01140 
01141   if (!_frame_ready) {
01142     // No frame data ready, just fill with black.
01143     if (ffmpeg_cat.is_spam()) {
01144       ffmpeg_cat.spam()
01145         << "ffmpeg for " << _filename.get_basename()
01146         << ", no frame available.\n";
01147     }
01148     memset(buffer->_block, 0, buffer->_block_size);
01149     return;
01150   }
01151 
01152   _frame_out->data[0] = buffer->_block + ((_size_y - 1) * _size_x * 3);
01153   _frame_out->linesize[0] = _size_x * -3;
01154   buffer->_begin_frame = _begin_frame;
01155   buffer->_end_frame = _end_frame;
01156 
01157   if (ffmpeg_global_lock) {
01158     ReMutexHolder av_holder(_av_lock);
01159 #ifdef HAVE_SWSCALE
01160     nassertv(_convert_ctx != NULL && _frame != NULL && _frame_out != NULL);
01161     sws_scale(_convert_ctx, _frame->data, _frame->linesize, 0, _size_y, _frame_out->data, _frame_out->linesize);
01162 #else
01163     img_convert((AVPicture *)_frame_out, PIX_FMT_BGR24, 
01164                 (AVPicture *)_frame, _video_ctx->pix_fmt, _size_x, _size_y);
01165 #endif
01166   } else {
01167 #ifdef HAVE_SWSCALE
01168     nassertv(_convert_ctx != NULL && _frame != NULL && _frame_out != NULL);
01169     sws_scale(_convert_ctx, _frame->data, _frame->linesize, 0, _size_y, _frame_out->data, _frame_out->linesize);
01170 #else
01171     img_convert((AVPicture *)_frame_out, PIX_FMT_BGR24, 
01172                 (AVPicture *)_frame, _video_ctx->pix_fmt, _size_x, _size_y);
01173 #endif
01174   }
01175 }
01176 
01177 ////////////////////////////////////////////////////////////////////
01178 //     Function: FfmpegVideoCursor::register_with_read_factory
01179 //       Access: Public, Static
01180 //  Description: Tells the BamReader how to create objects of type
01181 //               FfmpegVideo.
01182 ////////////////////////////////////////////////////////////////////
01183 void FfmpegVideoCursor::
01184 register_with_read_factory() {
01185   BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
01186 }
01187 
01188 ////////////////////////////////////////////////////////////////////
01189 //     Function: FfmpegVideoCursor::write_datagram
01190 //       Access: Public, Virtual
01191 //  Description: Writes the contents of this object to the datagram
01192 //               for shipping out to a Bam file.
01193 ////////////////////////////////////////////////////////////////////
01194 void FfmpegVideoCursor::
01195 write_datagram(BamWriter *manager, Datagram &dg) {
01196   MovieVideoCursor::write_datagram(manager, dg);
01197 
01198   // No need to write any additional data here--all of it comes
01199   // implicitly from the underlying MovieVideo, which we process in
01200   // finalize().
01201 }
01202 
01203 ////////////////////////////////////////////////////////////////////
01204 //     Function: FfmpegVideoCursor::finalize
01205 //       Access: Public, Virtual
01206 //  Description: Called by the BamReader to perform any final actions
01207 //               needed for setting up the object after all objects
01208 //               have been read and all pointers have been completed.
01209 ////////////////////////////////////////////////////////////////////
01210 void FfmpegVideoCursor::
01211 finalize(BamReader *) {
01212   if (_source != (MovieVideo *)NULL) {
01213     FfmpegVideo *video;
01214     DCAST_INTO_V(video, _source);
01215     init_from(video);
01216   }
01217 }
01218 
01219 ////////////////////////////////////////////////////////////////////
01220 //     Function: FfmpegVideoCursor::make_from_bam
01221 //       Access: Private, Static
01222 //  Description: This function is called by the BamReader's factory
01223 //               when a new object of type FfmpegVideo is encountered
01224 //               in the Bam file.  It should create the FfmpegVideo
01225 //               and extract its information from the file.
01226 ////////////////////////////////////////////////////////////////////
01227 TypedWritable *FfmpegVideoCursor::
01228 make_from_bam(const FactoryParams &params) {
01229   FfmpegVideoCursor *video = new FfmpegVideoCursor;
01230   DatagramIterator scan;
01231   BamReader *manager;
01232 
01233   parse_params(params, scan, manager);
01234   video->fillin(scan, manager);
01235 
01236   return video;
01237 }
01238 
01239 ////////////////////////////////////////////////////////////////////
01240 //     Function: FfmpegVideoCursor::fillin
01241 //       Access: Private
01242 //  Description: This internal function is called by make_from_bam to
01243 //               read in all of the relevant data from the BamFile for
01244 //               the new FfmpegVideo.
01245 ////////////////////////////////////////////////////////////////////
01246 void FfmpegVideoCursor::
01247 fillin(DatagramIterator &scan, BamReader *manager) {
01248   MovieVideoCursor::fillin(scan, manager);
01249   
01250   // The MovieVideoCursor gets the underlying MovieVideo pointer.  We
01251   // need a finalize callback so we can initialize ourselves once that
01252   // has been read completely.
01253   manager->register_finalize(this);
01254 }
01255 
01256 ////////////////////////////////////////////////////////////////////
01257 //     Function: FfmpegVideoCursor::FfmpegBuffer::compare_timestamp
01258 //       Access: Published, Virtual
01259 //  Description: Used to sort different buffers to ensure they
01260 //               correspond to the same source frame, particularly
01261 //               important when synchronizing the different pages of a
01262 //               multi-page texture.
01263 //
01264 //               Returns 0 if the two buffers are of the same frame,
01265 //               <0 if this one comes earlier than the other one, and
01266 //               >0 if the other one comes earlier.
01267 ////////////////////////////////////////////////////////////////////
01268 int FfmpegVideoCursor::FfmpegBuffer::
01269 compare_timestamp(const Buffer *other) const {
01270   const FfmpegBuffer *fother;
01271   DCAST_INTO_R(fother, other, 0);
01272   if (_end_frame * _video_timebase <= fother->_begin_frame * fother->_video_timebase) {
01273     return -1;
01274   } else if (_begin_frame * _video_timebase >= fother->_end_frame * fother->_video_timebase) {
01275     return 1;
01276   }
01277   return 0;
01278 }
01279 
01280 ////////////////////////////////////////////////////////////////////
01281 //     Function: FfmpegVideoCursor::FfmpegBuffer::get_timestamp
01282 //       Access: Published, Virtual
01283 //  Description: Returns the nearest timestamp value of this
01284 //               particular buffer.  Ideally,
01285 //               MovieVideoCursor::set_time() for this timestamp would
01286 //               return this buffer again.  This need be defined only
01287 //               if compare_timestamp() is also defined.
01288 ////////////////////////////////////////////////////////////////////
01289 double FfmpegVideoCursor::FfmpegBuffer::
01290 get_timestamp() const {
01291   int mid_frame = (_begin_frame + _end_frame - 1) / 2;
01292   return mid_frame * _video_timebase;
01293 }
01294 
01295 #endif // HAVE_FFMPEG
 All Classes Functions Variables Enumerations