23 #include <libavcodec/avcodec.h> 24 #include <libavformat/avformat.h> 25 #include <libavutil/pixdesc.h> 27 #include <libswscale/swscale.h> 31 ReMutex FfmpegVideoCursor::_av_lock;
33 TypeHandle FfmpegVideoCursor::FfmpegBuffer::_type_handle;
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");
39 #if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(52, 32, 100) 40 #define AV_PIX_FMT_FLAG_ALPHA PIX_FMT_ALPHA 48 _max_readahead_frames(0),
49 _thread_priority(ffmpeg_thread_priority),
50 _lock(
"FfmpegVideoCursor::_lock"),
52 _thread_status(TS_stopped),
57 _convert_ctx(nullptr),
58 _pixel_format((int)AV_PIX_FMT_NONE),
70 void FfmpegVideoCursor::
72 nassertv(_thread ==
nullptr && _thread_status == TS_stopped);
73 nassertv(source !=
nullptr);
75 _filename = _source->get_filename();
82 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 45, 101) 83 _frame = av_frame_alloc();
84 _frame_out = av_frame_alloc();
86 _frame = avcodec_alloc_frame();
87 _frame_out = avcodec_alloc_frame();
90 if ((_frame ==
nullptr)||(_frame_out ==
nullptr)) {
95 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 12, 100) 96 _packet = av_packet_alloc();
98 _packet =
new AVPacket;
99 av_init_packet(_packet);
104 _initial_dts = _begin_frame;
115 switch (_video_ctx->pix_fmt) {
116 case AV_PIX_FMT_GRAY8:
118 _pixel_format = (int)AV_PIX_FMT_GRAY8;
120 case AV_PIX_FMT_Y400A:
122 _pixel_format = (int)AV_PIX_FMT_Y400A;
125 const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(_video_ctx->pix_fmt);
126 if (desc && (desc->flags & AV_PIX_FMT_FLAG_ALPHA) != 0) {
128 _pixel_format = (int)AV_PIX_FMT_BGRA;
131 _pixel_format = (int)AV_PIX_FMT_BGR24;
137 nassertv(_convert_ctx ==
nullptr);
138 _convert_ctx = sws_getContext(_size_x, _size_y, _video_ctx->pix_fmt,
139 _size_x, _size_y, (AVPixelFormat)_pixel_format,
140 SWS_BILINEAR | SWS_PRINT_INFO,
nullptr,
nullptr,
nullptr);
141 #endif // HAVE_SWSCALE 145 #endif // HAVE_THREADS 153 _max_readahead_frames(0),
154 _thread_priority(ffmpeg_thread_priority),
155 _lock(
"FfmpegVideoCursor::_lock"),
157 _thread_status(TS_stopped),
160 _format_ctx(nullptr),
162 _convert_ctx(nullptr),
175 ~FfmpegVideoCursor() {
193 if (max_readahead_frames > 0) {
195 <<
"Couldn't set max_readahead_frames to " << max_readahead_frames
196 <<
": threading not available.\n";
197 max_readahead_frames = 0;
199 #endif // HAVE_THREADS 201 _max_readahead_frames = max_readahead_frames;
202 if (_max_readahead_frames > 0) {
203 if (_thread_status == TS_stopped) {
207 if (_thread_status != TS_stopped) {
219 return _max_readahead_frames;
231 if (_thread_priority != thread_priority) {
232 _thread_priority = thread_priority;
247 return _thread_priority;
260 if (_thread_status == TS_stopped && _max_readahead_frames > 0) {
262 std::ostringstream strm;
263 strm << (
void *)
this;
264 _sync_name = strm.str();
267 _thread_status = TS_wait;
269 if (!_thread->start(_thread_priority,
true)) {
272 _thread_status = TS_stopped;
286 if (_thread_status != TS_stopped) {
290 if (_thread_status != TS_stopped) {
291 _thread_status = TS_shutdown;
307 _readahead_frames.clear();
316 return (_thread_status != TS_stopped);
324 int frame = (int)(timestamp / _video_timebase + 0.5);
327 if (loop_count == 0) {
328 frame = frame % (_eof_frame + 1);
330 int last_frame = (_eof_frame + 1) * loop_count;
331 if (frame < last_frame) {
332 frame = frame % (_eof_frame + 1);
340 frame = std::max(frame, _initial_dts);
342 if (ffmpeg_cat.is_spam() && frame != _current_frame) {
344 <<
"set_time(" << timestamp <<
"): " << frame
345 <<
", loop_count = " << loop_count <<
"\n";
348 _current_frame = frame;
349 if (_current_frame_buffer !=
nullptr) {
352 return (_current_frame >= _current_frame_buffer->_end_frame ||
353 _current_frame < _current_frame_buffer->_begin_frame);
368 if (_format_ctx ==
nullptr) {
372 PT(FfmpegBuffer) frame;
373 if (_thread_status == TS_stopped) {
375 advance_to_frame(_current_frame);
377 frame = do_alloc_frame();
383 if (!_readahead_frames.empty()) {
384 frame = _readahead_frames.front();
385 _readahead_frames.pop_front();
387 while (frame->_end_frame < _current_frame && !_readahead_frames.empty()) {
389 if (ffmpeg_cat.is_debug()) {
392 <<
" at frame " << _current_frame <<
", discarding frame at " 393 << frame->_begin_frame <<
"\n";
395 frame = _readahead_frames.front();
396 _readahead_frames.pop_front();
398 if (frame->_begin_frame > _current_frame) {
401 if (ffmpeg_cat.is_debug()) {
404 <<
" at frame " << _current_frame <<
", encountered too-new frame at " 405 << frame->_begin_frame <<
"\n";
407 do_clear_all_frames();
408 if (_thread_status == TS_wait || _thread_status == TS_seek || _thread_status == TS_readahead) {
409 _thread_status = TS_seek;
410 _seek_frame = _current_frame;
415 if (frame ==
nullptr || frame->_end_frame < _current_frame) {
417 if (_thread_status == TS_wait || _thread_status == TS_seek || _thread_status == TS_readahead) {
418 _thread_status = TS_seek;
419 _seek_frame = _current_frame;
425 if (frame !=
nullptr) {
426 bool too_old = (frame->_end_frame < _current_frame && !ffmpeg_show_seek_frames);
427 bool too_new = frame->_begin_frame > _current_frame;
428 if (too_old || too_new) {
434 if (frame !=
nullptr) {
435 _current_frame_buffer = frame;
436 if (ffmpeg_cat.is_debug()) {
439 <<
" at frame " << _current_frame <<
", returning frame at " 440 << frame->_begin_frame <<
"\n";
443 if (ffmpeg_cat.is_debug()) {
446 <<
" at frame " << _current_frame <<
", returning NULL\n";
457 PT(FfmpegBuffer) frame =
new FfmpegBuffer(size_x() * size_y() * get_num_components(), _video_timebase);
464 bool FfmpegVideoCursor::
466 nassertr(!_ffvfile.
is_open(),
false);
471 if (!_source->get_subfile_info().is_empty()) {
473 if (!_ffvfile.
open_subfile(_source->get_subfile_info())) {
475 <<
"Couldn't open " << _source->get_subfile_info() <<
"\n";
482 if (!_ffvfile.
open_vfs(_filename)) {
484 <<
"Couldn't open " << _filename <<
"\n";
490 nassertr(_format_ctx ==
nullptr,
false);
492 nassertr(_format_ctx !=
nullptr,
false);
494 if (avformat_find_stream_info(_format_ctx,
nullptr) < 0) {
496 <<
"Couldn't find stream info\n";
501 nassertr(_video_ctx ==
nullptr,
false);
506 #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 41, 100) 507 AVCodecParameters *codecpar;
509 AVCodecContext *codecpar;
513 AVStream *stream =
nullptr;
514 for (
int i = 0; i < (int)_format_ctx->nb_streams; ++i) {
515 #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 41, 100) 516 codecpar = _format_ctx->streams[i]->codecpar;
518 codecpar = _format_ctx->streams[i]->codec;
520 if (codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
522 stream = _format_ctx->streams[i];
527 if (stream ==
nullptr) {
529 <<
"Couldn't find stream\n";
534 _video_timebase = av_q2d(stream->time_base);
535 _min_fseek = (int)(3.0 / _video_timebase);
537 AVCodec *pVideoCodec =
nullptr;
538 if (ffmpeg_prefer_libvpx) {
539 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 0, 0) 540 if (codecpar->codec_id == AV_CODEC_ID_VP9) {
541 pVideoCodec = avcodec_find_decoder_by_name(
"libvpx-vp9");
544 if (codecpar->codec_id == AV_CODEC_ID_VP8) {
545 pVideoCodec = avcodec_find_decoder_by_name(
"libvpx");
548 if (pVideoCodec ==
nullptr) {
549 pVideoCodec = avcodec_find_decoder(codecpar->codec_id);
551 if (pVideoCodec ==
nullptr) {
553 <<
"Couldn't find codec\n";
558 _video_ctx = avcodec_alloc_context3(pVideoCodec);
560 if (_video_ctx ==
nullptr) {
562 <<
"Couldn't allocate _video_ctx\n";
567 #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 41, 100) 568 avcodec_parameters_to_context(_video_ctx, codecpar);
570 avcodec_copy_context(_video_ctx, codecpar);
573 if (avcodec_open2(_video_ctx, pVideoCodec,
nullptr) < 0) {
575 <<
"Couldn't open codec\n";
580 _size_x = _video_ctx->width;
581 _size_y = _video_ctx->height;
583 _length = (double)_format_ctx->duration / (
double)AV_TIME_BASE;
585 _can_seek_fast =
true;
593 void FfmpegVideoCursor::
598 if (_video_ctx && _video_ctx->codec) {
599 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 37, 100) 601 avcodec_send_packet(_video_ctx,
nullptr);
602 while (avcodec_receive_frame(_video_ctx, _frame) == 0) {}
603 avcodec_flush_buffers(_video_ctx);
606 avcodec_close(_video_ctx);
607 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 52, 0) 608 avcodec_free_context(&_video_ctx);
613 _video_ctx =
nullptr;
616 _format_ctx =
nullptr;
624 void FfmpegVideoCursor::
632 if (_convert_ctx !=
nullptr) {
633 sws_freeContext(_convert_ctx);
635 _convert_ctx =
nullptr;
636 #endif // HAVE_SWSCALE 644 _frame_out->data[0] =
nullptr;
646 _frame_out =
nullptr;
650 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 12, 100) 651 av_packet_free(&_packet);
654 av_free_packet(_packet);
665 void FfmpegVideoCursor::
666 st_thread_main(
void *
self) {
673 void FfmpegVideoCursor::
675 if (ffmpeg_cat.is_spam()) {
677 <<
"ffmpeg thread for " << _filename.
get_basename() <<
" starting.\n";
682 PT(FfmpegBuffer) frame = do_alloc_frame();
685 _readahead_frames.push_back(frame);
691 while (_thread_status != TS_shutdown) {
692 nassertv(_thread_status != TS_stopped);
698 PStatClient::thread_tick(_sync_name);
704 _thread_status = TS_stopped;
705 if (ffmpeg_cat.is_spam()) {
707 <<
"ffmpeg thread for " << _filename.
get_basename() <<
" stopped.\n";
716 bool FfmpegVideoCursor::
718 switch (_thread_status) {
722 nassertr(
false,
false);
730 if ((
int)_readahead_frames.size() < _max_readahead_frames) {
732 PT(FfmpegBuffer) frame = do_alloc_frame();
733 nassertr(frame !=
nullptr,
false);
739 _readahead_frames.push_back(frame);
753 int seek_frame = _seek_frame;
754 _thread_status = TS_seeking;
755 PT(FfmpegBuffer) frame = do_alloc_frame();
756 nassertr(frame !=
nullptr,
false);
758 advance_to_frame(seek_frame);
762 do_clear_all_frames();
763 _readahead_frames.push_back(frame);
766 do_clear_all_frames();
769 if (_thread_status == TS_seeking) {
771 _thread_status = TS_readahead;
789 PT(
Buffer) buffer = make_new_buffer();
790 return (FfmpegBuffer *)buffer.p();
796 void FfmpegVideoCursor::
797 do_clear_all_frames() {
798 _readahead_frames.clear();
808 bool FfmpegVideoCursor::
809 fetch_packet(
int default_frame) {
810 if (ffmpeg_global_lock) {
812 return do_fetch_packet(default_frame);
814 return do_fetch_packet(default_frame);
821 bool FfmpegVideoCursor::
822 do_fetch_packet(
int default_frame) {
824 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 12, 100) 825 av_packet_unref(_packet);
827 av_free_packet(_packet);
830 while (av_read_frame(_format_ctx, _packet) >= 0) {
831 if (_packet->stream_index == _video_index) {
832 _packet_frame = _packet->dts;
835 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 12, 100) 836 av_packet_unref(_packet);
838 av_free_packet(_packet);
841 _packet->data =
nullptr;
843 if (!_eof_known && default_frame != 0) {
844 _eof_frame = _packet_frame;
848 if (ffmpeg_cat.is_spam()) {
851 <<
"end of video at frame " << _eof_frame <<
"\n";
857 _packet_frame = default_frame;
868 void FfmpegVideoCursor::
869 fetch_frame(
int frame) {
874 if (_packet_frame <= frame) {
879 while (_packet_frame <= frame) {
883 decode_frame(finished);
884 _begin_frame = _packet_frame;
885 if (fetch_packet(frame)) {
886 _end_frame = _packet_frame;
887 _frame_ready =
false;
895 while (!finished && _packet->data) {
896 decode_frame(finished);
897 _begin_frame = _packet_frame;
898 fetch_packet(_begin_frame + 1);
902 _end_frame = _packet_frame;
910 void FfmpegVideoCursor::
911 decode_frame(
int &finished) {
912 if (ffmpeg_global_lock) {
914 do_decode_frame(finished);
916 do_decode_frame(finished);
923 void FfmpegVideoCursor::
924 do_decode_frame(
int &finished) {
925 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 37, 100) 930 avcodec_send_packet(_video_ctx, _packet);
932 int ret = avcodec_receive_frame(_video_ctx, _frame);
933 finished = (ret == 0);
935 avcodec_decode_video2(_video_ctx, _frame, &finished, _packet);
943 void FfmpegVideoCursor::
944 seek(
int frame,
bool backward) {
947 if (ffmpeg_support_seek) {
948 if (ffmpeg_global_lock) {
950 do_seek(frame, backward);
952 do_seek(frame, backward);
967 void FfmpegVideoCursor::
968 do_seek(
int frame,
bool backward) {
969 int64_t target_ts = (int64_t)frame;
970 if (target_ts < (int64_t)(_initial_dts)) {
972 target_ts = _initial_dts;
976 flags = AVSEEK_FLAG_BACKWARD;
979 if (av_seek_frame(_format_ctx, _video_index, target_ts, flags) < 0) {
980 if (ffmpeg_cat.is_spam()) {
982 <<
"Seek failure.\n";
993 if (binary_seek(_initial_dts, frame, frame, 1) < 0) {
994 if (ffmpeg_cat.is_spam()) {
996 <<
"Seek double failure.\n";
1011 int FfmpegVideoCursor::
1012 binary_seek(
int min_frame,
int max_frame,
int target_frame,
int num_iterations) {
1013 int try_frame = (min_frame + max_frame) / 2;
1014 if (num_iterations > 5 || try_frame >= max_frame) {
1019 if (av_seek_frame(_format_ctx, _video_index, try_frame, AVSEEK_FLAG_BACKWARD) < 0) {
1021 if (binary_seek(min_frame, try_frame - 1, target_frame, num_iterations + 1) < 0) {
1026 if (binary_seek(try_frame + 1, max_frame, target_frame, num_iterations + 1) < 0) {
1037 void FfmpegVideoCursor::
1039 if (ffmpeg_cat.is_spam()) {
1041 <<
"Resetting ffmpeg stream.\n";
1045 if (!open_stream()) {
1047 <<
"Stream error, invalidating movie.\n";
1060 void FfmpegVideoCursor::
1061 advance_to_frame(
int frame) {
1064 if (frame < _begin_frame) {
1066 if (ffmpeg_cat.is_spam()) {
1068 <<
"Seeking backward to " << frame <<
" from " << _begin_frame <<
"\n";
1071 if (_begin_frame > frame) {
1072 if (ffmpeg_cat.is_spam()) {
1074 <<
"Ended up at " << _begin_frame <<
", not far enough back!\n";
1077 if (ffmpeg_cat.is_spam()) {
1079 <<
"Reseek to 0, got " << _begin_frame <<
"\n";
1082 if (frame > _end_frame) {
1083 if (ffmpeg_cat.is_spam()) {
1085 <<
"Now sliding forward to " << frame <<
" from " << _begin_frame <<
"\n";
1090 }
else if (frame < _end_frame) {
1092 if (ffmpeg_cat.is_spam()) {
1094 <<
"Currently have " << frame <<
" within " << _begin_frame <<
" .. " << _end_frame <<
"\n";
1097 }
else if (frame < _end_frame + _min_fseek) {
1099 if (ffmpeg_cat.is_spam()) {
1101 <<
"Sliding forward to " << frame <<
" from " << _begin_frame <<
"\n";
1113 if (ffmpeg_cat.is_spam()) {
1115 <<
"Jumping forward to " << frame <<
" from " << _begin_frame <<
"\n";
1117 int base = _begin_frame;
1119 if (_begin_frame < base) {
1120 _min_fseek += (base - _begin_frame);
1121 if (ffmpeg_cat.is_spam()) {
1123 <<
"Wrong way! Increasing _min_fseek to " << _min_fseek <<
"\n";
1126 if (frame > _end_frame) {
1127 if (ffmpeg_cat.is_spam()) {
1129 <<
"Correcting, sliding forward to " << frame <<
" from " << _begin_frame <<
"\n";
1135 if (ffmpeg_cat.is_spam()) {
1137 <<
"Wanted " << frame <<
", got " << _begin_frame <<
"\n";
1145 void FfmpegVideoCursor::
1146 export_frame(FfmpegBuffer *buffer) {
1149 if (!_frame_ready) {
1151 if (ffmpeg_cat.is_spam()) {
1154 <<
", no frame available.\n";
1156 memset(buffer->_block, 0, buffer->_block_size);
1160 _frame_out->data[0] = buffer->_block + ((_size_y - 1) * _size_x * _num_components);
1161 _frame_out->linesize[0] = _size_x * -_num_components;
1162 buffer->_begin_frame = _begin_frame;
1163 buffer->_end_frame = _end_frame;
1165 if (ffmpeg_global_lock) {
1168 nassertv(_convert_ctx !=
nullptr && _frame !=
nullptr && _frame_out !=
nullptr);
1169 sws_scale(_convert_ctx, _frame->data, _frame->linesize, 0, _size_y, _frame_out->data, _frame_out->linesize);
1171 img_convert((AVPicture *)_frame_out, (AVPixelFormat)_pixel_format,
1172 (AVPicture *)_frame, _video_ctx->pix_fmt, _size_x, _size_y);
1176 nassertv(_convert_ctx !=
nullptr && _frame !=
nullptr && _frame_out !=
nullptr);
1177 sws_scale(_convert_ctx, _frame->data, _frame->linesize, 0, _size_y, _frame_out->data, _frame_out->linesize);
1179 img_convert((AVPicture *)_frame_out, (AVPixelFormat)_pixel_format,
1180 (AVPicture *)_frame, _video_ctx->pix_fmt, _size_x, _size_y);
1212 if (_source !=
nullptr) {
1214 DCAST_INTO_V(video, _source);
1231 video->fillin(scan, manager);
1240 void FfmpegVideoCursor::
1242 MovieVideoCursor::fillin(scan, manager);
1261 DCAST_INTO_R(fother, other, 0);
1262 if (_end_frame * _video_timebase <= fother->_begin_frame * fother->_video_timebase) {
1264 }
else if (_begin_frame * _video_timebase >= fother->_end_frame * fother->_video_timebase) {
1277 int mid_frame = (_begin_frame + _end_frame - 1) / 2;
1278 return mid_frame * _video_timebase;
bool is_thread_started() const
Returns true if the thread has been started, false if not.
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().
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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...
bool open_vfs(const Filename &filename)
Opens the movie file via Panda's VFS.
Base class for objects that can be written to and read from Bam files.
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...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is the fundamental interface for writing binary objects to a Bam file, to be extracted later by ...
A lightweight C++ object whose constructor calls acquire() and whose destructor calls release() on a ...
void close()
Explicitly closes the opened file.
static void consider_yield()
Possibly suspends the current thread for the rest of the current epoch, if it has run for enough this...
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...
A lightweight class that represents a single element that may be timed and/or counted via stats.
void parse_params(const FactoryParams ¶ms, DatagramIterator &scan, BamReader *&manager)
Takes in a FactoryParams, passed from a WritableFactory into any TypedWritable's make function,...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void acquire() const
Grabs the mutex if it is available.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
An instance of this class is passed to the Factory when requesting it to do its business and construc...
virtual int compare_timestamp(const Buffer *other) const
Used to sort different buffers to ensure they correspond to the same source frame,...
void register_factory(TypeHandle handle, CreateFunc *func, void *user_data=nullptr)
Registers a new kind of thing the Factory will be able to create.
void register_finalize(TypedWritable *whom)
Should be called by an object reading itself from the Bam file to indicate that this particular objec...
Similar to MutexHolder, but for a reentrant mutex.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A generic thread type that allows calling a C-style thread function without having to subclass.
virtual void write_datagram(BamWriter *manager, Datagram &dg)
Writes the contents of this object to the datagram for shipping out to a Bam file.
std::string get_basename() const
Returns the basename part of the filename.
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...
virtual double get_timestamp() const
Returns the nearest timestamp value of this particular buffer.
static WritableFactory * get_factory()
Returns the global WritableFactory for generating TypedWritable objects.
virtual void write_datagram(BamWriter *manager, Datagram &dg)
Writes the contents of this object to the datagram for shipping out to a Bam file.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A class to retrieve the individual data elements previously stored in a Datagram.
ThreadPriority get_thread_priority() const
Returns the current thread priority of the thread that decodes the ffmpeg video stream (if max_readah...
AVFormatContext * get_format_context() const
Returns a pointer to the opened ffmpeg context, or NULL if the file was not successfully opened.
void release() const
Releases the mutex.
void stop_thread()
Explicitly stops the ffmpeg decoding thread.
TypeHandle is the identifier used to differentiate C++ class types.
bool open_subfile(const SubfileInfo &info)
Opens the movie file directly from a file on disk (does not go through the VFS).
PT(MovieVideoCursor::Buffer) FfmpegVideoCursor
See MovieVideoCursor::fetch_buffer.
An ordered list of data elements, formatted in memory for transmission over a socket or writing to a ...
bool is_open() const
Returns true if the stream is successfully opened, false otherwise.
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...