Panda3D
 All Classes Functions Variables Enumerations
ffmpegVideoCursor.cxx
1 // Filename: ffmpegVideoCursor.cxx
2 // Created by: jyelon (01Aug2007)
3 //
4 ////////////////////////////////////////////////////////////////////
5 //
6 // PANDA 3D SOFTWARE
7 // Copyright (c) Carnegie Mellon University. All rights reserved.
8 //
9 // All use of this software is subject to the terms of the revised BSD
10 // license. You should have received a copy of this license along
11 // with this source code in a file named "LICENSE."
12 //
13 ////////////////////////////////////////////////////////////////////
14 
15 #include "ffmpegVideoCursor.h"
16 #include "config_ffmpeg.h"
17 #include "pStatCollector.h"
18 #include "pStatTimer.h"
19 #include "mutexHolder.h"
20 #include "reMutexHolder.h"
21 #include "ffmpegVideo.h"
22 #include "bamReader.h"
23 extern "C" {
24  #include "libavcodec/avcodec.h"
25  #include "libavformat/avformat.h"
26 #ifdef HAVE_SWSCALE
27  #include "libswscale/swscale.h"
28 #endif
29 }
30 
31 ReMutex FfmpegVideoCursor::_av_lock;
32 TypeHandle FfmpegVideoCursor::_type_handle;
33 TypeHandle FfmpegVideoCursor::FfmpegBuffer::_type_handle;
34 
35 PStatCollector FfmpegVideoCursor::_fetch_buffer_pcollector("*:FFMPEG Video Decoding:Fetch");
36 PStatCollector FfmpegVideoCursor::_seek_pcollector("*:FFMPEG Video Decoding:Seek");
37 PStatCollector FfmpegVideoCursor::_export_frame_pcollector("*:FFMPEG Convert Video to BGR");
38 
39 
40 #if LIBAVFORMAT_VERSION_MAJOR < 53
41  #define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO
42 #endif
43 
44 ////////////////////////////////////////////////////////////////////
45 // Function: FfmpegVideoCursor::Default Constructor
46 // Access: Private
47 // Description: This constructor is only used when reading from a bam
48 // file.
49 ////////////////////////////////////////////////////////////////////
50 FfmpegVideoCursor::
51 FfmpegVideoCursor() :
52  _max_readahead_frames(0),
53  _thread_priority(ffmpeg_thread_priority),
54  _lock("FfmpegVideoCursor::_lock"),
55  _action_cvar(_lock),
56  _thread_status(TS_stopped),
57  _seek_frame(0),
58  _packet(NULL),
59  _format_ctx(NULL),
60  _video_ctx(NULL),
61  _convert_ctx(NULL),
62  _video_index(-1),
63  _frame(NULL),
64  _frame_out(NULL),
65  _eof_known(false)
66 {
67 }
68 
69 ////////////////////////////////////////////////////////////////////
70 // Function: FfmpegVideoCursor::init_from
71 // Access: Private
72 // Description: Specifies the source of the video cursor. This is
73 // normally called only by the constructor or when
74 // reading from a bam file.
75 ////////////////////////////////////////////////////////////////////
76 void FfmpegVideoCursor::
77 init_from(FfmpegVideo *source) {
78  nassertv(_thread == NULL && _thread_status == TS_stopped);
79  nassertv(source != NULL);
80  _source = source;
81  _filename = _source->get_filename();
82 
83  if (!open_stream()) {
84  cleanup();
85  return;
86  }
87 
88  ReMutexHolder av_holder(_av_lock);
89 
90 #ifdef HAVE_SWSCALE
91  nassertv(_convert_ctx == NULL);
92  _convert_ctx = sws_getContext(_size_x, _size_y,
93  _video_ctx->pix_fmt, _size_x, _size_y,
94  PIX_FMT_BGR24, SWS_BILINEAR | SWS_PRINT_INFO, NULL, NULL, NULL);
95 #endif // HAVE_SWSCALE
96 
97 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 59, 100)
98  _frame = av_frame_alloc();
99  _frame_out = av_frame_alloc();
100 #else
101  _frame = avcodec_alloc_frame();
102  _frame_out = avcodec_alloc_frame();
103 #endif
104 
105  if ((_frame == 0)||(_frame_out == 0)) {
106  cleanup();
107  return;
108  }
109 
110  _packet = new AVPacket;
111  memset(_packet, 0, sizeof(AVPacket));
112 
113  fetch_packet(0);
114  fetch_frame(-1);
115  _initial_dts = _begin_frame;
116 
117  _current_frame = -1;
118  _eof_known = false;
119  _eof_frame = 0;
120 
121 #ifdef HAVE_THREADS
122  set_max_readahead_frames(ffmpeg_max_readahead_frames);
123 #endif // HAVE_THREADS
124 }
125 
126 ////////////////////////////////////////////////////////////////////
127 // Function: FfmpegVideoCursor::Constructor
128 // Access: Published
129 // Description:
130 ////////////////////////////////////////////////////////////////////
131 FfmpegVideoCursor::
132 FfmpegVideoCursor(FfmpegVideo *src) :
133  _max_readahead_frames(0),
134  _thread_priority(ffmpeg_thread_priority),
135  _lock("FfmpegVideoCursor::_lock"),
136  _action_cvar(_lock),
137  _thread_status(TS_stopped),
138  _seek_frame(0),
139  _packet(NULL),
140  _format_ctx(NULL),
141  _video_ctx(NULL),
142  _convert_ctx(NULL),
143  _video_index(-1),
144  _frame(NULL),
145  _frame_out(NULL),
146  _eof_known(false)
147 {
148  init_from(src);
149 }
150 
151 ////////////////////////////////////////////////////////////////////
152 // Function: FfmpegVideoCursor::Destructor
153 // Access: Published
154 // Description:
155 ////////////////////////////////////////////////////////////////////
156 FfmpegVideoCursor::
157 ~FfmpegVideoCursor() {
158  cleanup();
159 }
160 
161 ////////////////////////////////////////////////////////////////////
162 // Function: FfmpegVideoCursor::set_max_readahead_frames
163 // Access: Published
164 // Description: Specifies the maximum number of frames that a
165 // sub-thread will attempt to read ahead of the current
166 // frame. Setting this to a nonzero allows the video
167 // decoding to take place in a sub-thread, which
168 // smoothes out the video decoding time by spreading it
169 // evenly over several frames. Set this number larger
170 // to increase the buffer between the currently visible
171 // frame and the first undecoded frame; set it smaller
172 // to reduce memory consumption.
173 //
174 // Setting this to zero forces the video to be decoded
175 // in the main thread. If threading is not available in
176 // the Panda build, this value is always zero.
177 ////////////////////////////////////////////////////////////////////
179 set_max_readahead_frames(int max_readahead_frames) {
180 #ifndef HAVE_THREADS
181  if (max_readahead_frames > 0) {
182  ffmpeg_cat.warning()
183  << "Couldn't set max_readahead_frames to " << max_readahead_frames
184  << ": threading not available.\n";
185  max_readahead_frames = 0;
186  }
187 #endif // HAVE_THREADS
188 
189  _max_readahead_frames = max_readahead_frames;
190  if (_max_readahead_frames > 0) {
191  if (_thread_status == TS_stopped) {
192  start_thread();
193  }
194  } else {
195  if (_thread_status != TS_stopped) {
196  stop_thread();
197  }
198  }
199 }
200 
201 ////////////////////////////////////////////////////////////////////
202 // Function: FfmpegVideoCursor::get_max_readahead_frames
203 // Access: Published
204 // Description: Returns the maximum number of frames that a
205 // sub-thread will attempt to read ahead of the current
206 // frame. See set_max_readahead_frames().
207 ////////////////////////////////////////////////////////////////////
210  return _max_readahead_frames;
211 }
212 
213 ////////////////////////////////////////////////////////////////////
214 // Function: FfmpegVideoCursor::set_thread_priority
215 // Access: Published
216 // Description: Changes the thread priority of the thread that
217 // decodes the ffmpeg video stream (if
218 // max_readahead_frames is nonzero). Normally you
219 // shouldn't mess with this, but there may be special
220 // cases where a precise balance of CPU utilization
221 // between the main thread and the various ffmpeg
222 // service threads may be needed.
223 ////////////////////////////////////////////////////////////////////
225 set_thread_priority(ThreadPriority thread_priority) {
226  if (_thread_priority != thread_priority) {
227  _thread_priority = thread_priority;
228  if (is_thread_started()) {
229  stop_thread();
230  start_thread();
231  }
232  }
233 }
234 
235 ////////////////////////////////////////////////////////////////////
236 // Function: FfmpegVideoCursor::get_thread_priority
237 // Access: Published
238 // Description: Returns the current thread priority of the thread that
239 // decodes the ffmpeg video stream (if
240 // max_readahead_frames is nonzero). See
241 // set_thread_priority().
242 ////////////////////////////////////////////////////////////////////
243 ThreadPriority FfmpegVideoCursor::
245  return _thread_priority;
246 }
247 
248 ////////////////////////////////////////////////////////////////////
249 // Function: FfmpegVideoCursor::start_thread
250 // Access: Published
251 // Description: Explicitly starts the ffmpeg decoding thread after it
252 // has been stopped by a call to stop_thread(). The
253 // thread is normally started automatically, so there is
254 // no need to call this method unless you have
255 // previously called stop_thread() for some reason.
256 ////////////////////////////////////////////////////////////////////
259  MutexHolder holder(_lock);
260 
261  if (_thread_status == TS_stopped && _max_readahead_frames > 0) {
262  // Get a unique name for the thread's sync name.
263  ostringstream strm;
264  strm << (void *)this;
265  _sync_name = strm.str();
266 
267  // Create and start the thread object.
268  _thread_status = TS_wait;
269  _thread = new GenericThread(_filename.get_basename(), _sync_name, st_thread_main, this);
270  if (!_thread->start(_thread_priority, true)) {
271  // Couldn't start the thread.
272  _thread = NULL;
273  _thread_status = TS_stopped;
274  }
275  }
276 }
277 
278 ////////////////////////////////////////////////////////////////////
279 // Function: FfmpegVideoCursor::stop_thread
280 // Access: Published
281 // Description: Explicitly stops the ffmpeg decoding thread. There
282 // is normally no reason to do this unless you want to
283 // maintain precise control over what threads are
284 // consuming CPU resources. Calling this method will
285 // make the video update in the main thread, regardless
286 // of the setting of max_readahead_frames, until you
287 // call start_thread() again.
288 ////////////////////////////////////////////////////////////////////
291  if (_thread_status != TS_stopped) {
292  PT(GenericThread) thread = _thread;
293  {
294  MutexHolder holder(_lock);
295  if (_thread_status != TS_stopped) {
296  _thread_status = TS_shutdown;
297  }
298  _action_cvar.notify();
299  _thread = NULL;
300  }
301 
302  // Now that we've released the lock, we can join the thread.
303  thread->join();
304  }
305 
306  // This is a good time to clean up all of the allocated frame
307  // objects. It's not really necessary to be holding the lock, since
308  // the thread is gone, but we'll grab it anyway just in case someone
309  // else starts the thread up again.
310  MutexHolder holder(_lock);
311 
312  _readahead_frames.clear();
313 }
314 
315 ////////////////////////////////////////////////////////////////////
316 // Function: FfmpegVideoCursor::is_thread_started
317 // Access: Published
318 // Description: Returns true if the thread has been started, false if
319 // not. This will always return false if
320 // max_readahead_frames is 0.
321 ////////////////////////////////////////////////////////////////////
324  return (_thread_status != TS_stopped);
325 }
326 
327 ////////////////////////////////////////////////////////////////////
328 // Function: FfmpegVideoCursor::set_time
329 // Access: Published, Virtual
330 // Description: See MovieVideoCursor::set_time().
331 ////////////////////////////////////////////////////////////////////
333 set_time(double timestamp, int loop_count) {
334  int frame = (int)(timestamp / _video_timebase + 0.5);
335 
336  if (_eof_known) {
337  if (loop_count == 0) {
338  frame = frame % _eof_frame;
339  } else {
340  int last_frame = _eof_frame * loop_count;
341  if (frame < last_frame) {
342  frame = frame % _eof_frame;
343  } else {
344  frame = _eof_frame - 1;
345  }
346  }
347  }
348 
349  // No point in trying to position before the first frame.
350  frame = max(frame, _initial_dts);
351 
352  if (ffmpeg_cat.is_spam() && frame != _current_frame) {
353  ffmpeg_cat.spam()
354  << "set_time(" << timestamp << "): " << frame
355  << ", loop_count = " << loop_count << "\n";
356  }
357 
358  _current_frame = frame;
359  if (_current_frame_buffer != NULL) {
360  // If we've previously returned a frame, don't bother asking for a
361  // next one if that frame is still valid.
362  return (_current_frame >= _current_frame_buffer->_end_frame ||
363  _current_frame < _current_frame_buffer->_begin_frame);
364  }
365 
366  // If our last request didn't return a frame, try again.
367  return true;
368 }
369 
370 ////////////////////////////////////////////////////////////////////
371 // Function: FfmpegVideoCursor::fetch_buffer
372 // Access: Public, Virtual
373 // Description: See MovieVideoCursor::fetch_buffer.
374 ////////////////////////////////////////////////////////////////////
376 fetch_buffer() {
377  MutexHolder holder(_lock);
378 
379  // If there was an error at any point, just return NULL.
380  if (_format_ctx == (AVFormatContext *)NULL) {
381  return NULL;
382  }
383 
384  PT(FfmpegBuffer) frame;
385  if (_thread_status == TS_stopped) {
386  // Non-threaded case. Just get the next frame directly.
387  advance_to_frame(_current_frame);
388  if (_frame_ready) {
389  frame = do_alloc_frame();
390  export_frame(frame);
391  }
392 
393  } else {
394  // Threaded case. Wait for the thread to serve up the required
395  // frames.
396  if (!_readahead_frames.empty()) {
397  frame = _readahead_frames.front();
398  _readahead_frames.pop_front();
399  _action_cvar.notify();
400  while (frame->_end_frame < _current_frame && !_readahead_frames.empty()) {
401  // This frame is too old. Discard it.
402  if (ffmpeg_cat.is_debug()) {
403  ffmpeg_cat.debug()
404  << "ffmpeg for " << _filename.get_basename()
405  << " at frame " << _current_frame << ", discarding frame at "
406  << frame->_begin_frame << "\n";
407  }
408  frame = _readahead_frames.front();
409  _readahead_frames.pop_front();
410  }
411  if (frame->_begin_frame > _current_frame) {
412  // This frame is too new. Empty all remaining frames and seek
413  // backwards.
414  if (ffmpeg_cat.is_debug()) {
415  ffmpeg_cat.debug()
416  << "ffmpeg for " << _filename.get_basename()
417  << " at frame " << _current_frame << ", encountered too-new frame at "
418  << frame->_begin_frame << "\n";
419  }
420  do_clear_all_frames();
421  if (_thread_status == TS_wait || _thread_status == TS_seek || _thread_status == TS_readahead) {
422  _thread_status = TS_seek;
423  _seek_frame = _current_frame;
424  _action_cvar.notify();
425  }
426  }
427  }
428  if (frame == NULL || frame->_end_frame < _current_frame) {
429  // No frame available, or the frame is too old. Seek.
430  if (_thread_status == TS_wait || _thread_status == TS_seek || _thread_status == TS_readahead) {
431  _thread_status = TS_seek;
432  _seek_frame = _current_frame;
433  _action_cvar.notify();
434  }
435  }
436  }
437 
438  if (frame != NULL) {
439  bool too_old = (frame->_end_frame < _current_frame && !ffmpeg_show_seek_frames);
440  bool too_new = frame->_begin_frame > _current_frame;
441  if (too_old || too_new) {
442  // The frame is too old or too new. Just recycle it.
443  frame = NULL;
444  }
445  }
446 
447  if (frame != NULL) {
448  _current_frame_buffer = frame;
449  if (ffmpeg_cat.is_debug()) {
450  ffmpeg_cat.debug()
451  << "ffmpeg for " << _filename.get_basename()
452  << " at frame " << _current_frame << ", returning frame at "
453  << frame->_begin_frame << "\n";
454  }
455  } else {
456  if (ffmpeg_cat.is_debug()) {
457  ffmpeg_cat.debug()
458  << "ffmpeg for " << _filename.get_basename()
459  << " at frame " << _current_frame << ", returning NULL\n";
460  }
461  }
462  return frame.p();
463 }
464 
465 ////////////////////////////////////////////////////////////////////
466 // Function: FfmpegVideoCursor::make_new_buffer
467 // Access: Protected, Virtual
468 // Description: May be called by a derived class to allocate a new
469 // Buffer object.
470 ////////////////////////////////////////////////////////////////////
472 make_new_buffer() {
473  PT(FfmpegBuffer) frame = new FfmpegBuffer(size_x() * size_y() * get_num_components(), _video_timebase);
474  return frame.p();
475 }
476 
477 ////////////////////////////////////////////////////////////////////
478 // Function: FfmpegVideoCursor::open_stream
479 // Access: Private
480 // Description: Opens the stream for the first time, or when needed
481 // internally.
482 ////////////////////////////////////////////////////////////////////
483 bool FfmpegVideoCursor::
484 open_stream() {
485  nassertr(!_ffvfile.is_open(), false);
486 
487  // Hold the global lock while we open the file and create avcodec
488  // objects.
489  ReMutexHolder av_holder(_av_lock);
490 
491  if (!_source->get_subfile_info().is_empty()) {
492  // Read a subfile.
493  if (!_ffvfile.open_subfile(_source->get_subfile_info())) {
494  ffmpeg_cat.info()
495  << "Couldn't open " << _source->get_subfile_info() << "\n";
496  close_stream();
497  return false;
498  }
499 
500  } else {
501  // Read a filename.
502  if (!_ffvfile.open_vfs(_filename)) {
503  ffmpeg_cat.info()
504  << "Couldn't open " << _filename << "\n";
505  close_stream();
506  return false;
507  }
508  }
509 
510  nassertr(_format_ctx == NULL, false);
511  _format_ctx = _ffvfile.get_format_context();
512  nassertr(_format_ctx != NULL, false);
513 
514 #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53, 6, 0)
515  if (avformat_find_stream_info(_format_ctx, NULL) < 0) {
516 #else
517  if (av_find_stream_info(_format_ctx) < 0) {
518 #endif
519  ffmpeg_cat.info()
520  << "Couldn't find stream info\n";
521  close_stream();
522  return false;
523  }
524 
525  // Find the video stream
526  nassertr(_video_ctx == NULL, false);
527  for (int i = 0; i < (int)_format_ctx->nb_streams; ++i) {
528  if (_format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
529  _video_index = i;
530  _video_ctx = _format_ctx->streams[i]->codec;
531  _video_timebase = av_q2d(_format_ctx->streams[i]->time_base);
532  _min_fseek = (int)(3.0 / _video_timebase);
533  }
534  }
535 
536  if (_video_ctx == NULL) {
537  ffmpeg_cat.info()
538  << "Couldn't find video_ctx\n";
539  close_stream();
540  return false;
541  }
542 
543  AVCodec *pVideoCodec = avcodec_find_decoder(_video_ctx->codec_id);
544  if (pVideoCodec == NULL) {
545  ffmpeg_cat.info()
546  << "Couldn't find codec\n";
547  close_stream();
548  return false;
549  }
550 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53, 8, 0)
551  if (avcodec_open2(_video_ctx, pVideoCodec, NULL) < 0) {
552 #else
553  if (avcodec_open(_video_ctx, pVideoCodec) < 0) {
554 #endif
555  ffmpeg_cat.info()
556  << "Couldn't open codec\n";
557  close_stream();
558  return false;
559  }
560 
561  _size_x = _video_ctx->width;
562  _size_y = _video_ctx->height;
563  _num_components = 3; // Don't know how to implement RGBA movies yet.
564  _length = (double)_format_ctx->duration / (double)AV_TIME_BASE;
565  _can_seek = true;
566  _can_seek_fast = true;
567 
568  return true;
569 }
570 
571 ////////////////////////////////////////////////////////////////////
572 // Function: FfmpegVideoCursor::close_stream
573 // Access: Private
574 // Description: Closes the stream, during cleanup or when needed
575 // internally.
576 ////////////////////////////////////////////////////////////////////
577 void FfmpegVideoCursor::
578 close_stream() {
579  // Hold the global lock while we free avcodec objects.
580  ReMutexHolder av_holder(_av_lock);
581 
582  if ((_video_ctx)&&(_video_ctx->codec)) {
583  avcodec_close(_video_ctx);
584  }
585  _video_ctx = NULL;
586 
587  _ffvfile.close();
588  _format_ctx = NULL;
589 
590  _video_index = -1;
591 }
592 
593 ////////////////////////////////////////////////////////////////////
594 // Function: FfmpegVideoCursor::cleanup
595 // Access: Private
596 // Description: Reset to a standard inactive state.
597 ////////////////////////////////////////////////////////////////////
598 void FfmpegVideoCursor::
599 cleanup() {
600  stop_thread();
601  close_stream();
602 
603  ReMutexHolder av_holder(_av_lock);
604 
605 #ifdef HAVE_SWSCALE
606  if (_convert_ctx != NULL) {
607  sws_freeContext(_convert_ctx);
608  }
609  _convert_ctx = NULL;
610 #endif // HAVE_SWSCALE
611 
612  if (_frame) {
613  av_free(_frame);
614  _frame = NULL;
615  }
616 
617  if (_frame_out) {
618  _frame_out->data[0] = 0;
619  av_free(_frame_out);
620  _frame_out = NULL;
621  }
622 
623  if (_packet) {
624  if (_packet->data) {
625  av_free_packet(_packet);
626  }
627  delete _packet;
628  _packet = NULL;
629  }
630 }
631 
632 ////////////////////////////////////////////////////////////////////
633 // Function: FfmpegVideoCursor::st_thread_main
634 // Access: Private, Static
635 // Description: The thread main function, static version (for passing
636 // to GenericThread).
637 ////////////////////////////////////////////////////////////////////
638 void FfmpegVideoCursor::
639 st_thread_main(void *self) {
640  ((FfmpegVideoCursor *)self)->thread_main();
641 }
642 
643 ////////////////////////////////////////////////////////////////////
644 // Function: FfmpegVideoCursor::thread_main
645 // Access: Private
646 // Description: The thread main function.
647 ////////////////////////////////////////////////////////////////////
648 void FfmpegVideoCursor::
649 thread_main() {
650  if (ffmpeg_cat.is_spam()) {
651  ffmpeg_cat.spam()
652  << "ffmpeg thread for " << _filename.get_basename() << " starting.\n";
653  }
654 
655  // First, push the first frame onto the readahead queue.
656  if (_frame_ready) {
657  PT(FfmpegBuffer) frame = do_alloc_frame();
658  export_frame(frame);
659  MutexHolder holder(_lock);
660  _readahead_frames.push_back(frame);
661  }
662 
663  // Now repeatedly wait for something interesting to do, until we're told
664  // to shut down.
665  MutexHolder holder(_lock);
666  while (_thread_status != TS_shutdown) {
667  nassertv(_thread_status != TS_stopped);
668  _action_cvar.wait();
669 
670  while (do_poll()) {
671  // Keep doing stuff as long as there's something to do.
672  _lock.release();
673  PStatClient::thread_tick(_sync_name);
675  _lock.acquire();
676  }
677  }
678 
679  _thread_status = TS_stopped;
680  if (ffmpeg_cat.is_spam()) {
681  ffmpeg_cat.spam()
682  << "ffmpeg thread for " << _filename.get_basename() << " stopped.\n";
683  }
684 }
685 
686 ////////////////////////////////////////////////////////////////////
687 // Function: FfmpegVideoCursor::do_poll
688 // Access: Private
689 // Description: Called within the sub-thread. Assumes the lock is
690 // already held. If there is something for the thread
691 // to do, does it and returns true. If there is nothing
692 // for the thread to do, returns false.
693 ////////////////////////////////////////////////////////////////////
694 bool FfmpegVideoCursor::
695 do_poll() {
696  switch (_thread_status) {
697  case TS_stopped:
698  case TS_seeking:
699  // This shouldn't be possible while the thread is running.
700  nassertr(false, false);
701  return false;
702 
703  case TS_wait:
704  // The video hasn't started playing yet.
705  return false;
706 
707  case TS_readahead:
708  if ((int)_readahead_frames.size() < _max_readahead_frames) {
709  // Time to read the next frame.
710  PT(FfmpegBuffer) frame = do_alloc_frame();
711  nassertr(frame != NULL, false);
712  _lock.release();
713  fetch_frame(-1);
714  if (_frame_ready) {
715  export_frame(frame);
716  _lock.acquire();
717  _readahead_frames.push_back(frame);
718  } else {
719  // No frame.
720  _lock.acquire();
721  }
722  return true;
723  }
724 
725  // No room for the next frame yet. Wait for more.
726  return false;
727 
728  case TS_seek:
729  // Seek to a specific frame.
730  {
731  int seek_frame = _seek_frame;
732  _thread_status = TS_seeking;
733  PT(FfmpegBuffer) frame = do_alloc_frame();
734  nassertr(frame != NULL, false);
735  _lock.release();
736  advance_to_frame(seek_frame);
737  if (_frame_ready) {
738  export_frame(frame);
739  _lock.acquire();
740  do_clear_all_frames();
741  _readahead_frames.push_back(frame);
742  } else {
743  _lock.acquire();
744  do_clear_all_frames();
745  }
746 
747  if (_thread_status == TS_seeking) {
748  // After seeking, we automatically transition to readahead.
749  _thread_status = TS_readahead;
750  }
751  }
752  return true;
753 
754  case TS_shutdown:
755  // Time to stop the thread.
756  return false;
757  }
758 
759  return false;
760 }
761 
762 ////////////////////////////////////////////////////////////////////
763 // Function: FfmpegVideoCursor::do_alloc_frame
764 // Access: Private
765 // Description: Allocates a new Buffer object. Assumes the lock is
766 // held.
767 ////////////////////////////////////////////////////////////////////
769 do_alloc_frame() {
770  PT(Buffer) buffer = make_new_buffer();
771  return (FfmpegBuffer *)buffer.p();
772 }
773 
774 ////////////////////////////////////////////////////////////////////
775 // Function: FfmpegVideoCursor::do_clear_all_frames
776 // Access: Private
777 // Description: Empties the entire readahead_frames queue.
778 // Assumes the lock is held.
779 ////////////////////////////////////////////////////////////////////
780 void FfmpegVideoCursor::
781 do_clear_all_frames() {
782  _readahead_frames.clear();
783 }
784 
785 ////////////////////////////////////////////////////////////////////
786 // Function: FfmpegVideoCursor::fetch_packet
787 // Access: Private
788 // Description: Called within the sub-thread. Fetches a video packet
789 // and stores it in the packet0 buffer. Sets packet_frame
790 // to the packet's timestamp. If a packet could not be
791 // read, the packet is cleared and the packet_frame is
792 // set to the specified default value. Returns true on
793 // failure (such as the end of the video), or false on
794 // success.
795 ////////////////////////////////////////////////////////////////////
796 bool FfmpegVideoCursor::
797 fetch_packet(int default_frame) {
798  if (ffmpeg_global_lock) {
799  ReMutexHolder av_holder(_av_lock);
800  return do_fetch_packet(default_frame);
801  } else {
802  return do_fetch_packet(default_frame);
803  }
804 }
805 
806 ////////////////////////////////////////////////////////////////////
807 // Function: FfmpegVideoCursor::do_fetch_packet
808 // Access: Private
809 // Description: As above, with the ffmpeg global lock held (if
810 // configured on).
811 ////////////////////////////////////////////////////////////////////
812 bool FfmpegVideoCursor::
813 do_fetch_packet(int default_frame) {
814  if (_packet->data) {
815  av_free_packet(_packet);
816  }
817  while (av_read_frame(_format_ctx, _packet) >= 0) {
818  if (_packet->stream_index == _video_index) {
819  _packet_frame = _packet->dts;
820  return false;
821  }
822  av_free_packet(_packet);
823  }
824  _packet->data = 0;
825 
826  if (!_eof_known && default_frame != 0) {
827  _eof_frame = _packet_frame;
828  _eof_known = true;
829  }
830 
831  if (ffmpeg_cat.is_spam()) {
832  if (_eof_known) {
833  ffmpeg_cat.spam()
834  << "end of video at frame " << _eof_frame << "\n";
835  } else {
836  ffmpeg_cat.spam()
837  << "end of video\n";
838  }
839  }
840  _packet_frame = default_frame;
841  return true;
842 }
843 
844 ////////////////////////////////////////////////////////////////////
845 // Function: FfmpegVideoCursor::fetch_frame
846 // Access: Private
847 // Description: Called within the sub-thread. Slides forward until
848 // the indicated frame, then fetches a frame from the
849 // stream and stores it in the frame buffer. Sets
850 // _begin_frame and _end_frame to indicate the extents of
851 // the frame. Sets _frame_ready true to indicate a
852 // frame is now available, or false if it is not (for
853 // instance, because the end of the video was reached).
854 ////////////////////////////////////////////////////////////////////
855 void FfmpegVideoCursor::
856 fetch_frame(int frame) {
857  PStatTimer timer(_fetch_buffer_pcollector);
858 
859  int finished = 0;
860 
861  if (_packet_frame <= frame) {
862  finished = 0;
863 
864  // Get the next packet. The first packet beyond the frame we're
865  // looking for marks the point to stop.
866  while (_packet_frame <= frame) {
867  PStatTimer timer(_seek_pcollector);
868 
869  // Decode the previous packet, and get the next one.
870  decode_frame(finished);
871  _begin_frame = _packet_frame;
872  if (fetch_packet(frame)) {
873  _end_frame = _packet_frame;
874  _frame_ready = false;
875  return;
876  }
877  }
878 
879  } else {
880  // Just get the next frame.
881  finished = 0;
882  while (!finished && _packet->data) {
883  decode_frame(finished);
884  _begin_frame = _packet_frame;
885  fetch_packet(_begin_frame + 1);
886  }
887  }
888 
889  _end_frame = _packet_frame;
890  _frame_ready = true;
891 }
892 
893 ////////////////////////////////////////////////////////////////////
894 // Function: FfmpegVideoCursor::decode_frame
895 // Access: Private
896 // Description: Called within the sub-thread. Decodes the data in
897 // the specified packet into _frame.
898 ////////////////////////////////////////////////////////////////////
899 void FfmpegVideoCursor::
900 decode_frame(int &finished) {
901  if (ffmpeg_global_lock) {
902  ReMutexHolder av_holder(_av_lock);
903  do_decode_frame(finished);
904  } else {
905  do_decode_frame(finished);
906  }
907 }
908 
909 ////////////////////////////////////////////////////////////////////
910 // Function: FfmpegVideoCursor::do_decode_frame
911 // Access: Private
912 // Description: As above, with the ffmpeg global lock held (if
913 // configured on).
914 ////////////////////////////////////////////////////////////////////
915 void FfmpegVideoCursor::
916 do_decode_frame(int &finished) {
917 #if LIBAVCODEC_VERSION_INT < 3414272
918  avcodec_decode_video(_video_ctx, _frame,
919  &finished, _packet->data, _packet->size);
920 #else
921  avcodec_decode_video2(_video_ctx, _frame, &finished, _packet);
922 #endif
923 }
924 
925 ////////////////////////////////////////////////////////////////////
926 // Function: FfmpegVideoCursor::seek
927 // Access: Private
928 // Description: Called within the sub-thread. Seeks to a target
929 // location. Afterward, the packet_frame is guaranteed
930 // to be less than or equal to the specified frame.
931 ////////////////////////////////////////////////////////////////////
932 void FfmpegVideoCursor::
933 seek(int frame, bool backward) {
934  PStatTimer timer(_seek_pcollector);
935 
936  if (ffmpeg_support_seek) {
937  if (ffmpeg_global_lock) {
938  ReMutexHolder av_holder(_av_lock);
939  do_seek(frame, backward);
940  } else {
941  do_seek(frame, backward);
942  }
943 
944  } else {
945  // If seeking isn't supported, close-and-reopen.
946  if (backward) {
947  reset_stream();
948  }
949  }
950 }
951 
952 ////////////////////////////////////////////////////////////////////
953 // Function: FfmpegVideoCursor::do_seek
954 // Access: Private
955 // Description: As above, with the ffmpeg global lock held (if
956 // configured on). Also only if ffmpeg-support-seek is
957 // on.
958 ////////////////////////////////////////////////////////////////////
959 void FfmpegVideoCursor::
960 do_seek(int frame, bool backward) {
961  PN_int64 target_ts = (PN_int64)frame;
962  if (target_ts < (PN_int64)(_initial_dts)) {
963  // Attempts to seek before the first packet will fail.
964  target_ts = _initial_dts;
965  }
966  int flags = 0;
967  if (backward) {
968  flags = AVSEEK_FLAG_BACKWARD;
969  }
970 
971  if (av_seek_frame(_format_ctx, _video_index, target_ts, flags) < 0) {
972  if (ffmpeg_cat.is_spam()) {
973  ffmpeg_cat.spam()
974  << "Seek failure.\n";
975  }
976 
977  if (backward) {
978  // Now try to seek forward.
979  reset_stream();
980  seek(frame, false);
981  return;
982  }
983 
984  // Try a binary search to get a little closer.
985  if (binary_seek(_initial_dts, frame, frame, 1) < 0) {
986  if (ffmpeg_cat.is_spam()) {
987  ffmpeg_cat.spam()
988  << "Seek double failure.\n";
989  }
990  reset_stream();
991  return;
992  }
993  }
994 
995  fetch_packet(0);
996  fetch_frame(-1);
997 }
998 
999 ////////////////////////////////////////////////////////////////////
1000 // Function: FfmpegVideoCursor::binary_seek
1001 // Access: Private
1002 // Description: Casts about within the stream for a reasonably-close
1003 // frame to seek to. We're trying to get as close as
1004 // possible to target_frame.
1005 ////////////////////////////////////////////////////////////////////
1006 int FfmpegVideoCursor::
1007 binary_seek(int min_frame, int max_frame, int target_frame, int num_iterations) {
1008  int try_frame = (min_frame + max_frame) / 2;
1009  if (num_iterations > 5 || try_frame >= max_frame) {
1010  // Success.
1011  return 0;
1012  }
1013 
1014  if (av_seek_frame(_format_ctx, _video_index, try_frame, AVSEEK_FLAG_BACKWARD) < 0) {
1015  // Failure. Try lower.
1016  if (binary_seek(min_frame, try_frame - 1, target_frame, num_iterations + 1) < 0) {
1017  return -1;
1018  }
1019  } else {
1020  // Success. Try higher.
1021  if (binary_seek(try_frame + 1, max_frame, target_frame, num_iterations + 1) < 0) {
1022  return -1;
1023  }
1024  }
1025  return 0;
1026 }
1027 
1028 ////////////////////////////////////////////////////////////////////
1029 // Function: FfmpegVideoCursor::reset_stream
1030 // Access: Private
1031 // Description: Resets the stream to its initial, first-opened state
1032 // by closing and re-opening it.
1033 ////////////////////////////////////////////////////////////////////
1034 void FfmpegVideoCursor::
1035 reset_stream() {
1036  if (ffmpeg_cat.is_spam()) {
1037  ffmpeg_cat.spam()
1038  << "Resetting ffmpeg stream.\n";
1039  }
1040 
1041  close_stream();
1042  if (!open_stream()) {
1043  ffmpeg_cat.error()
1044  << "Stream error, invalidating movie.\n";
1045  cleanup();
1046  return;
1047  }
1048 
1049  fetch_packet(0);
1050  fetch_frame(-1);
1051 }
1052 
1053 ////////////////////////////////////////////////////////////////////
1054 // Function: FfmpegVideoCursor::advance_to_frame
1055 // Access: Private
1056 // Description: Called within the sub-thread. Advance until the
1057 // specified frame is in the export buffer.
1058 ////////////////////////////////////////////////////////////////////
1059 void FfmpegVideoCursor::
1060 advance_to_frame(int frame) {
1061  PStatTimer timer(_fetch_buffer_pcollector);
1062 
1063  if (frame < _begin_frame) {
1064  // Frame is in the past.
1065  if (ffmpeg_cat.is_spam()) {
1066  ffmpeg_cat.spam()
1067  << "Seeking backward to " << frame << " from " << _begin_frame << "\n";
1068  }
1069  seek(frame, true);
1070  if (_begin_frame > frame) {
1071  if (ffmpeg_cat.is_spam()) {
1072  ffmpeg_cat.spam()
1073  << "Ended up at " << _begin_frame << ", not far enough back!\n";
1074  }
1075  reset_stream();
1076  if (ffmpeg_cat.is_spam()) {
1077  ffmpeg_cat.spam()
1078  << "Reseek to 0, got " << _begin_frame << "\n";
1079  }
1080  }
1081  if (frame > _end_frame) {
1082  if (ffmpeg_cat.is_spam()) {
1083  ffmpeg_cat.spam()
1084  << "Now sliding forward to " << frame << " from " << _begin_frame << "\n";
1085  }
1086  fetch_frame(frame);
1087  }
1088 
1089  } else if (frame < _end_frame) {
1090  // Frame is in the present: already have the frame.
1091  if (ffmpeg_cat.is_spam()) {
1092  ffmpeg_cat.spam()
1093  << "Currently have " << frame << " within " << _begin_frame << " .. " << _end_frame << "\n";
1094  }
1095 
1096  } else if (frame < _end_frame + _min_fseek) {
1097  // Frame is in the near future.
1098  if (ffmpeg_cat.is_spam()) {
1099  ffmpeg_cat.spam()
1100  << "Sliding forward to " << frame << " from " << _begin_frame << "\n";
1101  }
1102  fetch_frame(frame);
1103 
1104  } else {
1105  // Frame is in the far future. Seek forward, then read.
1106  // There's a danger here: because keyframes are spaced
1107  // unpredictably, trying to seek forward could actually
1108  // move us backward in the stream! This must be avoided.
1109  // So the rule is, try the seek. If it hurts us by moving
1110  // us backward, we increase the minimum threshold distance
1111  // for forward-seeking in the future.
1112 
1113  if (ffmpeg_cat.is_spam()) {
1114  ffmpeg_cat.spam()
1115  << "Jumping forward to " << frame << " from " << _begin_frame << "\n";
1116  }
1117  int base = _begin_frame;
1118  seek(frame, false);
1119  if (_begin_frame < base) {
1120  _min_fseek += (base - _begin_frame);
1121  if (ffmpeg_cat.is_spam()) {
1122  ffmpeg_cat.spam()
1123  << "Wrong way! Increasing _min_fseek to " << _min_fseek << "\n";
1124  }
1125  }
1126  if (frame > _end_frame) {
1127  if (ffmpeg_cat.is_spam()) {
1128  ffmpeg_cat.spam()
1129  << "Correcting, sliding forward to " << frame << " from " << _begin_frame << "\n";
1130  }
1131  fetch_frame(frame);
1132  }
1133  }
1134 
1135  if (ffmpeg_cat.is_spam()) {
1136  ffmpeg_cat.spam()
1137  << "Wanted " << frame << ", got " << _begin_frame << "\n";
1138  }
1139 }
1140 
1141 ////////////////////////////////////////////////////////////////////
1142 // Function: FfmpegVideoCursor::export_frame
1143 // Access: Private
1144 // Description: Called within the sub-thread. Exports the contents
1145 // of the frame buffer into the indicated target buffer.
1146 ////////////////////////////////////////////////////////////////////
1147 void FfmpegVideoCursor::
1148 export_frame(FfmpegBuffer *buffer) {
1149  PStatTimer timer(_export_frame_pcollector);
1150 
1151  if (!_frame_ready) {
1152  // No frame data ready, just fill with black.
1153  if (ffmpeg_cat.is_spam()) {
1154  ffmpeg_cat.spam()
1155  << "ffmpeg for " << _filename.get_basename()
1156  << ", no frame available.\n";
1157  }
1158  memset(buffer->_block, 0, buffer->_block_size);
1159  return;
1160  }
1161 
1162  _frame_out->data[0] = buffer->_block + ((_size_y - 1) * _size_x * 3);
1163  _frame_out->linesize[0] = _size_x * -3;
1164  buffer->_begin_frame = _begin_frame;
1165  buffer->_end_frame = _end_frame;
1166 
1167  if (ffmpeg_global_lock) {
1168  ReMutexHolder av_holder(_av_lock);
1169 #ifdef HAVE_SWSCALE
1170  nassertv(_convert_ctx != NULL && _frame != NULL && _frame_out != NULL);
1171  sws_scale(_convert_ctx, _frame->data, _frame->linesize, 0, _size_y, _frame_out->data, _frame_out->linesize);
1172 #else
1173  img_convert((AVPicture *)_frame_out, PIX_FMT_BGR24,
1174  (AVPicture *)_frame, _video_ctx->pix_fmt, _size_x, _size_y);
1175 #endif
1176  } else {
1177 #ifdef HAVE_SWSCALE
1178  nassertv(_convert_ctx != NULL && _frame != NULL && _frame_out != NULL);
1179  sws_scale(_convert_ctx, _frame->data, _frame->linesize, 0, _size_y, _frame_out->data, _frame_out->linesize);
1180 #else
1181  img_convert((AVPicture *)_frame_out, PIX_FMT_BGR24,
1182  (AVPicture *)_frame, _video_ctx->pix_fmt, _size_x, _size_y);
1183 #endif
1184  }
1185 }
1186 
1187 ////////////////////////////////////////////////////////////////////
1188 // Function: FfmpegVideoCursor::register_with_read_factory
1189 // Access: Public, Static
1190 // Description: Tells the BamReader how to create objects of type
1191 // FfmpegVideo.
1192 ////////////////////////////////////////////////////////////////////
1195  BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
1196 }
1197 
1198 ////////////////////////////////////////////////////////////////////
1199 // Function: FfmpegVideoCursor::write_datagram
1200 // Access: Public, Virtual
1201 // Description: Writes the contents of this object to the datagram
1202 // for shipping out to a Bam file.
1203 ////////////////////////////////////////////////////////////////////
1206  MovieVideoCursor::write_datagram(manager, dg);
1207 
1208  // No need to write any additional data here--all of it comes
1209  // implicitly from the underlying MovieVideo, which we process in
1210  // finalize().
1211 }
1212 
1213 ////////////////////////////////////////////////////////////////////
1214 // Function: FfmpegVideoCursor::finalize
1215 // Access: Public, Virtual
1216 // Description: Called by the BamReader to perform any final actions
1217 // needed for setting up the object after all objects
1218 // have been read and all pointers have been completed.
1219 ////////////////////////////////////////////////////////////////////
1222  if (_source != (MovieVideo *)NULL) {
1223  FfmpegVideo *video;
1224  DCAST_INTO_V(video, _source);
1225  init_from(video);
1226  }
1227 }
1228 
1229 ////////////////////////////////////////////////////////////////////
1230 // Function: FfmpegVideoCursor::make_from_bam
1231 // Access: Private, Static
1232 // Description: This function is called by the BamReader's factory
1233 // when a new object of type FfmpegVideo is encountered
1234 // in the Bam file. It should create the FfmpegVideo
1235 // and extract its information from the file.
1236 ////////////////////////////////////////////////////////////////////
1237 TypedWritable *FfmpegVideoCursor::
1238 make_from_bam(const FactoryParams &params) {
1239  FfmpegVideoCursor *video = new FfmpegVideoCursor;
1240  DatagramIterator scan;
1241  BamReader *manager;
1242 
1243  parse_params(params, scan, manager);
1244  video->fillin(scan, manager);
1245 
1246  return video;
1247 }
1248 
1249 ////////////////////////////////////////////////////////////////////
1250 // Function: FfmpegVideoCursor::fillin
1251 // Access: Private
1252 // Description: This internal function is called by make_from_bam to
1253 // read in all of the relevant data from the BamFile for
1254 // the new FfmpegVideo.
1255 ////////////////////////////////////////////////////////////////////
1256 void FfmpegVideoCursor::
1257 fillin(DatagramIterator &scan, BamReader *manager) {
1258  MovieVideoCursor::fillin(scan, manager);
1259 
1260  // The MovieVideoCursor gets the underlying MovieVideo pointer. We
1261  // need a finalize callback so we can initialize ourselves once that
1262  // has been read completely.
1263  manager->register_finalize(this);
1264 }
1265 
1266 ////////////////////////////////////////////////////////////////////
1267 // Function: FfmpegVideoCursor::FfmpegBuffer::compare_timestamp
1268 // Access: Published, Virtual
1269 // Description: Used to sort different buffers to ensure they
1270 // correspond to the same source frame, particularly
1271 // important when synchronizing the different pages of a
1272 // multi-page texture.
1273 //
1274 // Returns 0 if the two buffers are of the same frame,
1275 // <0 if this one comes earlier than the other one, and
1276 // >0 if the other one comes earlier.
1277 ////////////////////////////////////////////////////////////////////
1279 compare_timestamp(const Buffer *other) const {
1280  const FfmpegBuffer *fother;
1281  DCAST_INTO_R(fother, other, 0);
1282  if (_end_frame * _video_timebase <= fother->_begin_frame * fother->_video_timebase) {
1283  return -1;
1284  } else if (_begin_frame * _video_timebase >= fother->_end_frame * fother->_video_timebase) {
1285  return 1;
1286  }
1287  return 0;
1288 }
1289 
1290 ////////////////////////////////////////////////////////////////////
1291 // Function: FfmpegVideoCursor::FfmpegBuffer::get_timestamp
1292 // Access: Published, Virtual
1293 // Description: Returns the nearest timestamp value of this
1294 // particular buffer. Ideally,
1295 // MovieVideoCursor::set_time() for this timestamp would
1296 // return this buffer again. This need be defined only
1297 // if compare_timestamp() is also defined.
1298 ////////////////////////////////////////////////////////////////////
1300 get_timestamp() const {
1301  int mid_frame = (_begin_frame + _end_frame - 1) / 2;
1302  return mid_frame * _video_timebase;
1303 }
virtual bool set_time(double timestamp, int loop_count)
See MovieVideoCursor::set_time().
void start_thread()
Explicitly starts the ffmpeg decoding thread after it has been stopped by a call to stop_thread()...
virtual void finalize(BamReader *manager)
Called by the BamReader to perform any final actions needed for setting up the object after all objec...
This is the fundamental interface for extracting binary objects from a Bam file, as generated by a Ba...
Definition: bamReader.h:122
bool is_thread_started() const
Returns true if the thread has been started, false if not.
virtual double get_timestamp() const
Returns the nearest timestamp value of this particular buffer.
Base class for objects that can be written to and read from Bam files.
Definition: typedWritable.h:37
static void register_with_read_factory()
Tells the BamReader how to create objects of type FfmpegVideo.
A lightweight class that can be used to automatically start and stop a PStatCollector around a sectio...
Definition: pStatTimer.h:34
This is the fundamental interface for writing binary objects to a Bam file, to be extracted later by ...
Definition: bamWriter.h:73
A lightweight C++ object whose constructor calls acquire() and whose destructor calls release() on a ...
Definition: mutexHolder.h:29
void close()
Explicitly closes the opened file.
int get_num_components() const
Returns 4 if the movie has an alpha channel, 3 otherwise.
static void consider_yield()
Possibly suspends the current thread for the rest of the current epoch, if it has run for enough this...
Definition: thread.I:263
virtual int compare_timestamp(const Buffer *other) const
Used to sort different buffers to ensure they correspond to the same source frame, particularly important when synchronizing the different pages of a multi-page texture.
Definition: buffer.h:26
A lightweight class that represents a single element that may be timed and/or counted via stats...
ThreadPriority get_thread_priority() const
Returns the current thread priority of the thread that decodes the ffmpeg video stream (if max_readah...
An instance of this class is passed to the Factory when requesting it to do its business and construc...
Definition: factoryParams.h:40
void register_finalize(TypedWritable *whom)
Should be called by an object reading itself from the Bam file to indicate that this particular objec...
Definition: bamReader.cxx:880
Similar to MutexHolder, but for a reentrant mutex.
Definition: reMutexHolder.h:27
A generic thread type that allows calling a C-style thread function without having to subclass...
Definition: genericThread.h:26
virtual void write_datagram(BamWriter *manager, Datagram &dg)
Writes the contents of this object to the datagram for shipping out to a Bam file.
void register_factory(TypeHandle handle, CreateFunc *func)
Registers a new kind of thing the Factory will be able to create.
Definition: factory.I:90
void set_max_readahead_frames(int max_readahead_frames)
Specifies the maximum number of frames that a sub-thread will attempt to read ahead of the current fr...
int size_y() const
Get the vertical size of the movie.
string get_basename() const
Returns the basename part of the filename.
Definition: filename.I:436
static WritableFactory * get_factory()
Returns the global WritableFactory for generating TypedWritable objects.
Definition: bamReader.I:213
virtual void write_datagram(BamWriter *manager, Datagram &dg)
Writes the contents of this object to the datagram for shipping out to a Bam file.
A MovieVideo is actually any source that provides a sequence of video frames.
Definition: movieVideo.h:42
A class to retrieve the individual data elements previously stored in a Datagram. ...
void stop_thread()
Explicitly stops the ffmpeg decoding thread.
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:85
int get_max_readahead_frames() const
Returns the maximum number of frames that a sub-thread will attempt to read ahead of the current fram...
An ordered list of data elements, formatted in memory for transmission over a socket or writing to a ...
Definition: datagram.h:43
A reentrant mutex.
Definition: reMutex.h:36
void wait()
Waits on the condition.
void notify()
Informs one of the other threads who are currently blocked on wait() that the relevant condition has ...
void set_thread_priority(ThreadPriority thread_priority)
Changes the thread priority of the thread that decodes the ffmpeg video stream (if max_readahead_fram...