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"
24 #include "libavcodec/avcodec.h"
25 #include "libavformat/avformat.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");
40 #if LIBAVFORMAT_VERSION_MAJOR < 53
41 #define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO
52 _max_readahead_frames(0),
53 _thread_priority(ffmpeg_thread_priority),
54 _lock(
"FfmpegVideoCursor::_lock"),
56 _thread_status(TS_stopped),
76 void FfmpegVideoCursor::
78 nassertv(_thread == NULL && _thread_status == TS_stopped);
79 nassertv(source != NULL);
81 _filename = _source->get_filename();
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
97 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 59, 100)
98 _frame = av_frame_alloc();
99 _frame_out = av_frame_alloc();
101 _frame = avcodec_alloc_frame();
102 _frame_out = avcodec_alloc_frame();
105 if ((_frame == 0)||(_frame_out == 0)) {
110 _packet =
new AVPacket;
111 memset(_packet, 0,
sizeof(AVPacket));
115 _initial_dts = _begin_frame;
123 #endif // HAVE_THREADS
133 _max_readahead_frames(0),
134 _thread_priority(ffmpeg_thread_priority),
135 _lock(
"FfmpegVideoCursor::_lock"),
137 _thread_status(TS_stopped),
157 ~FfmpegVideoCursor() {
181 if (max_readahead_frames > 0) {
183 <<
"Couldn't set max_readahead_frames to " << max_readahead_frames
184 <<
": threading not available.\n";
185 max_readahead_frames = 0;
187 #endif // HAVE_THREADS
189 _max_readahead_frames = max_readahead_frames;
190 if (_max_readahead_frames > 0) {
191 if (_thread_status == TS_stopped) {
195 if (_thread_status != TS_stopped) {
210 return _max_readahead_frames;
226 if (_thread_priority != thread_priority) {
227 _thread_priority = thread_priority;
245 return _thread_priority;
261 if (_thread_status == TS_stopped && _max_readahead_frames > 0) {
264 strm << (
void *)
this;
265 _sync_name = strm.str();
268 _thread_status = TS_wait;
270 if (!_thread->start(_thread_priority,
true)) {
273 _thread_status = TS_stopped;
291 if (_thread_status != TS_stopped) {
295 if (_thread_status != TS_stopped) {
296 _thread_status = TS_shutdown;
312 _readahead_frames.clear();
324 return (_thread_status != TS_stopped);
334 int frame = (int)(timestamp / _video_timebase + 0.5);
337 if (loop_count == 0) {
338 frame = frame % _eof_frame;
340 int last_frame = _eof_frame * loop_count;
341 if (frame < last_frame) {
342 frame = frame % _eof_frame;
344 frame = _eof_frame - 1;
350 frame = max(frame, _initial_dts);
352 if (ffmpeg_cat.is_spam() && frame != _current_frame) {
354 <<
"set_time(" << timestamp <<
"): " << frame
355 <<
", loop_count = " << loop_count <<
"\n";
358 _current_frame = frame;
359 if (_current_frame_buffer != NULL) {
362 return (_current_frame >= _current_frame_buffer->_end_frame ||
363 _current_frame < _current_frame_buffer->_begin_frame);
380 if (_format_ctx == (AVFormatContext *)NULL) {
384 PT(FfmpegBuffer) frame;
385 if (_thread_status == TS_stopped) {
387 advance_to_frame(_current_frame);
389 frame = do_alloc_frame();
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()) {
402 if (ffmpeg_cat.is_debug()) {
404 <<
"ffmpeg for " << _filename.get_basename()
405 <<
" at frame " << _current_frame <<
", discarding frame at "
406 << frame->_begin_frame <<
"\n";
408 frame = _readahead_frames.front();
409 _readahead_frames.pop_front();
411 if (frame->_begin_frame > _current_frame) {
414 if (ffmpeg_cat.is_debug()) {
416 <<
"ffmpeg for " << _filename.get_basename()
417 <<
" at frame " << _current_frame <<
", encountered too-new frame at "
418 << frame->_begin_frame <<
"\n";
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();
428 if (frame == NULL || frame->_end_frame < _current_frame) {
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();
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) {
448 _current_frame_buffer = frame;
449 if (ffmpeg_cat.is_debug()) {
451 <<
"ffmpeg for " << _filename.get_basename()
452 <<
" at frame " << _current_frame <<
", returning frame at "
453 << frame->_begin_frame <<
"\n";
456 if (ffmpeg_cat.is_debug()) {
458 <<
"ffmpeg for " << _filename.get_basename()
459 <<
" at frame " << _current_frame <<
", returning NULL\n";
485 nassertr(!_ffvfile.is_open(),
false);
491 if (!_source->get_subfile_info().is_empty()) {
493 if (!_ffvfile.open_subfile(_source->get_subfile_info())) {
495 <<
"Couldn't open " << _source->get_subfile_info() <<
"\n";
502 if (!_ffvfile.open_vfs(_filename)) {
504 <<
"Couldn't open " << _filename <<
"\n";
510 nassertr(_format_ctx == NULL,
false);
511 _format_ctx = _ffvfile.get_format_context();
512 nassertr(_format_ctx != NULL,
false);
514 #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53, 6, 0)
515 if (avformat_find_stream_info(_format_ctx, NULL) < 0) {
517 if (av_find_stream_info(_format_ctx) < 0) {
520 <<
"Couldn't find stream info\n";
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) {
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);
536 if (_video_ctx == NULL) {
538 <<
"Couldn't find video_ctx\n";
543 AVCodec *pVideoCodec = avcodec_find_decoder(_video_ctx->codec_id);
544 if (pVideoCodec == NULL) {
546 <<
"Couldn't find codec\n";
550 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53, 8, 0)
551 if (avcodec_open2(_video_ctx, pVideoCodec, NULL) < 0) {
553 if (avcodec_open(_video_ctx, pVideoCodec) < 0) {
556 <<
"Couldn't open codec\n";
561 _size_x = _video_ctx->width;
562 _size_y = _video_ctx->height;
564 _length = (double)_format_ctx->duration / (
double)AV_TIME_BASE;
566 _can_seek_fast =
true;
577 void FfmpegVideoCursor::
582 if ((_video_ctx)&&(_video_ctx->codec)) {
583 avcodec_close(_video_ctx);
598 void FfmpegVideoCursor::
606 if (_convert_ctx != NULL) {
607 sws_freeContext(_convert_ctx);
610 #endif // HAVE_SWSCALE
618 _frame_out->data[0] = 0;
625 av_free_packet(_packet);
638 void FfmpegVideoCursor::
639 st_thread_main(
void *
self) {
648 void FfmpegVideoCursor::
650 if (ffmpeg_cat.is_spam()) {
652 <<
"ffmpeg thread for " << _filename.
get_basename() <<
" starting.\n";
657 PT(FfmpegBuffer) frame = do_alloc_frame();
660 _readahead_frames.push_back(frame);
666 while (_thread_status != TS_shutdown) {
667 nassertv(_thread_status != TS_stopped);
673 PStatClient::thread_tick(_sync_name);
679 _thread_status = TS_stopped;
680 if (ffmpeg_cat.is_spam()) {
682 <<
"ffmpeg thread for " << _filename.
get_basename() <<
" stopped.\n";
694 bool FfmpegVideoCursor::
696 switch (_thread_status) {
700 nassertr(
false,
false);
708 if ((
int)_readahead_frames.size() < _max_readahead_frames) {
710 PT(FfmpegBuffer) frame = do_alloc_frame();
711 nassertr(frame != NULL, false);
717 _readahead_frames.push_back(frame);
731 int seek_frame = _seek_frame;
732 _thread_status = TS_seeking;
733 PT(FfmpegBuffer) frame = do_alloc_frame();
734 nassertr(frame != NULL, false);
736 advance_to_frame(seek_frame);
740 do_clear_all_frames();
741 _readahead_frames.push_back(frame);
744 do_clear_all_frames();
747 if (_thread_status == TS_seeking) {
749 _thread_status = TS_readahead;
770 PT(
Buffer) buffer = make_new_buffer();
771 return (FfmpegBuffer *)buffer.p();
781 do_clear_all_frames() {
782 _readahead_frames.clear();
796 bool FfmpegVideoCursor::
797 fetch_packet(
int default_frame) {
798 if (ffmpeg_global_lock) {
800 return do_fetch_packet(default_frame);
802 return do_fetch_packet(default_frame);
812 bool FfmpegVideoCursor::
813 do_fetch_packet(
int default_frame) {
815 av_free_packet(_packet);
817 while (av_read_frame(_format_ctx, _packet) >= 0) {
818 if (_packet->stream_index == _video_index) {
819 _packet_frame = _packet->dts;
822 av_free_packet(_packet);
826 if (!_eof_known && default_frame != 0) {
827 _eof_frame = _packet_frame;
831 if (ffmpeg_cat.is_spam()) {
834 <<
"end of video at frame " << _eof_frame <<
"\n";
840 _packet_frame = default_frame;
855 void FfmpegVideoCursor::
856 fetch_frame(
int frame) {
861 if (_packet_frame <= frame) {
866 while (_packet_frame <= frame) {
870 decode_frame(finished);
871 _begin_frame = _packet_frame;
872 if (fetch_packet(frame)) {
873 _end_frame = _packet_frame;
874 _frame_ready =
false;
882 while (!finished && _packet->data) {
883 decode_frame(finished);
884 _begin_frame = _packet_frame;
885 fetch_packet(_begin_frame + 1);
889 _end_frame = _packet_frame;
899 void FfmpegVideoCursor::
900 decode_frame(
int &finished) {
901 if (ffmpeg_global_lock) {
903 do_decode_frame(finished);
905 do_decode_frame(finished);
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);
921 avcodec_decode_video2(_video_ctx, _frame, &finished, _packet);
932 void FfmpegVideoCursor::
933 seek(
int frame,
bool backward) {
936 if (ffmpeg_support_seek) {
937 if (ffmpeg_global_lock) {
939 do_seek(frame, backward);
941 do_seek(frame, backward);
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)) {
964 target_ts = _initial_dts;
968 flags = AVSEEK_FLAG_BACKWARD;
971 if (av_seek_frame(_format_ctx, _video_index, target_ts, flags) < 0) {
972 if (ffmpeg_cat.is_spam()) {
974 <<
"Seek failure.\n";
985 if (binary_seek(_initial_dts, frame, frame, 1) < 0) {
986 if (ffmpeg_cat.is_spam()) {
988 <<
"Seek double failure.\n";
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) {
1014 if (av_seek_frame(_format_ctx, _video_index, try_frame, AVSEEK_FLAG_BACKWARD) < 0) {
1016 if (binary_seek(min_frame, try_frame - 1, target_frame, num_iterations + 1) < 0) {
1021 if (binary_seek(try_frame + 1, max_frame, target_frame, num_iterations + 1) < 0) {
1034 void FfmpegVideoCursor::
1036 if (ffmpeg_cat.is_spam()) {
1038 <<
"Resetting ffmpeg stream.\n";
1042 if (!open_stream()) {
1044 <<
"Stream error, invalidating movie.\n";
1059 void FfmpegVideoCursor::
1060 advance_to_frame(
int frame) {
1063 if (frame < _begin_frame) {
1065 if (ffmpeg_cat.is_spam()) {
1067 <<
"Seeking backward to " << frame <<
" from " << _begin_frame <<
"\n";
1070 if (_begin_frame > frame) {
1071 if (ffmpeg_cat.is_spam()) {
1073 <<
"Ended up at " << _begin_frame <<
", not far enough back!\n";
1076 if (ffmpeg_cat.is_spam()) {
1078 <<
"Reseek to 0, got " << _begin_frame <<
"\n";
1081 if (frame > _end_frame) {
1082 if (ffmpeg_cat.is_spam()) {
1084 <<
"Now sliding forward to " << frame <<
" from " << _begin_frame <<
"\n";
1089 }
else if (frame < _end_frame) {
1091 if (ffmpeg_cat.is_spam()) {
1093 <<
"Currently have " << frame <<
" within " << _begin_frame <<
" .. " << _end_frame <<
"\n";
1096 }
else if (frame < _end_frame + _min_fseek) {
1098 if (ffmpeg_cat.is_spam()) {
1100 <<
"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";
1147 void FfmpegVideoCursor::
1148 export_frame(FfmpegBuffer *buffer) {
1151 if (!_frame_ready) {
1153 if (ffmpeg_cat.is_spam()) {
1156 <<
", no frame available.\n";
1158 memset(buffer->_block, 0, buffer->_block_size);
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;
1167 if (ffmpeg_global_lock) {
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);
1173 img_convert((AVPicture *)_frame_out, PIX_FMT_BGR24,
1174 (AVPicture *)_frame, _video_ctx->pix_fmt, _size_x, _size_y);
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);
1181 img_convert((AVPicture *)_frame_out, PIX_FMT_BGR24,
1182 (AVPicture *)_frame, _video_ctx->pix_fmt, _size_x, _size_y);
1224 DCAST_INTO_V(video, _source);
1243 parse_params(params, scan, manager);
1244 video->fillin(scan, manager);
1256 void FfmpegVideoCursor::
1258 MovieVideoCursor::fillin(scan, manager);
1281 DCAST_INTO_R(fother, other, 0);
1282 if (_end_frame * _video_timebase <= fother->_begin_frame * fother->_video_timebase) {
1284 }
else if (_begin_frame * _video_timebase >= fother->_end_frame * fother->_video_timebase) {
1301 int mid_frame = (_begin_frame + _end_frame - 1) / 2;
1302 return mid_frame * _video_timebase;
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...
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.
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...
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.
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...
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.
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...
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.
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.
void register_factory(TypeHandle handle, CreateFunc *func)
Registers a new kind of thing the Factory will be able to create.
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.
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.
A MovieVideo is actually any source that provides a sequence of video frames.
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.
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 ...
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...