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, _video_ctx->pix_fmt,
93 #
if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51, 74, 100)
94 _size_x, _size_y, AV_PIX_FMT_BGR24,
96 _size_x, _size_y, PIX_FMT_BGR24,
98 SWS_BILINEAR | SWS_PRINT_INFO, NULL, NULL, NULL);
99 #endif // HAVE_SWSCALE 101 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 59, 100) 102 _frame = av_frame_alloc();
103 _frame_out = av_frame_alloc();
105 _frame = avcodec_alloc_frame();
106 _frame_out = avcodec_alloc_frame();
109 if ((_frame == 0)||(_frame_out == 0)) {
114 _packet =
new AVPacket;
115 memset(_packet, 0,
sizeof(AVPacket));
119 _initial_dts = _begin_frame;
127 #endif // HAVE_THREADS 137 _max_readahead_frames(0),
138 _thread_priority(ffmpeg_thread_priority),
139 _lock(
"FfmpegVideoCursor::_lock"),
141 _thread_status(TS_stopped),
161 ~FfmpegVideoCursor() {
185 if (max_readahead_frames > 0) {
187 <<
"Couldn't set max_readahead_frames to " << max_readahead_frames
188 <<
": threading not available.\n";
189 max_readahead_frames = 0;
191 #endif // HAVE_THREADS 193 _max_readahead_frames = max_readahead_frames;
194 if (_max_readahead_frames > 0) {
195 if (_thread_status == TS_stopped) {
199 if (_thread_status != TS_stopped) {
214 return _max_readahead_frames;
230 if (_thread_priority != thread_priority) {
231 _thread_priority = thread_priority;
249 return _thread_priority;
265 if (_thread_status == TS_stopped && _max_readahead_frames > 0) {
268 strm << (
void *)
this;
269 _sync_name = strm.str();
272 _thread_status = TS_wait;
273 _thread =
new GenericThread(_filename.get_basename(), _sync_name, st_thread_main,
this);
274 if (!_thread->start(_thread_priority,
true)) {
277 _thread_status = TS_stopped;
295 if (_thread_status != TS_stopped) {
299 if (_thread_status != TS_stopped) {
300 _thread_status = TS_shutdown;
302 _action_cvar.notify();
316 _readahead_frames.clear();
328 return (_thread_status != TS_stopped);
338 int frame = (int)(timestamp / _video_timebase + 0.5);
341 if (loop_count == 0) {
342 frame = frame % _eof_frame;
344 int last_frame = _eof_frame * loop_count;
345 if (frame < last_frame) {
346 frame = frame % _eof_frame;
348 frame = _eof_frame - 1;
354 frame = max(frame, _initial_dts);
356 if (ffmpeg_cat.is_spam() && frame != _current_frame) {
358 <<
"set_time(" << timestamp <<
"): " << frame
359 <<
", loop_count = " << loop_count <<
"\n";
362 _current_frame = frame;
363 if (_current_frame_buffer != NULL) {
366 return (_current_frame >= _current_frame_buffer->_end_frame ||
367 _current_frame < _current_frame_buffer->_begin_frame);
384 if (_format_ctx == (AVFormatContext *)NULL) {
389 if (_thread_status == TS_stopped) {
391 advance_to_frame(_current_frame);
393 frame = do_alloc_frame();
400 if (!_readahead_frames.empty()) {
401 frame = _readahead_frames.front();
402 _readahead_frames.pop_front();
403 _action_cvar.notify();
404 while (frame->_end_frame < _current_frame && !_readahead_frames.empty()) {
406 if (ffmpeg_cat.is_debug()) {
408 <<
"ffmpeg for " << _filename.get_basename()
409 <<
" at frame " << _current_frame <<
", discarding frame at " 410 << frame->_begin_frame <<
"\n";
412 frame = _readahead_frames.front();
413 _readahead_frames.pop_front();
415 if (frame->_begin_frame > _current_frame) {
418 if (ffmpeg_cat.is_debug()) {
420 <<
"ffmpeg for " << _filename.get_basename()
421 <<
" at frame " << _current_frame <<
", encountered too-new frame at " 422 << frame->_begin_frame <<
"\n";
424 do_clear_all_frames();
425 if (_thread_status == TS_wait || _thread_status == TS_seek || _thread_status == TS_readahead) {
426 _thread_status = TS_seek;
427 _seek_frame = _current_frame;
428 _action_cvar.notify();
432 if (frame == NULL || frame->_end_frame < _current_frame) {
434 if (_thread_status == TS_wait || _thread_status == TS_seek || _thread_status == TS_readahead) {
435 _thread_status = TS_seek;
436 _seek_frame = _current_frame;
437 _action_cvar.notify();
443 bool too_old = (frame->_end_frame < _current_frame && !ffmpeg_show_seek_frames);
444 bool too_new = frame->_begin_frame > _current_frame;
445 if (too_old || too_new) {
452 _current_frame_buffer = frame;
453 if (ffmpeg_cat.is_debug()) {
455 <<
"ffmpeg for " << _filename.get_basename()
456 <<
" at frame " << _current_frame <<
", returning frame at " 457 << frame->_begin_frame <<
"\n";
460 if (ffmpeg_cat.is_debug()) {
462 <<
"ffmpeg for " << _filename.get_basename()
463 <<
" at frame " << _current_frame <<
", returning NULL\n";
487 bool FfmpegVideoCursor::
489 nassertr(!_ffvfile.is_open(),
false);
495 if (!_source->get_subfile_info().is_empty()) {
497 if (!_ffvfile.open_subfile(_source->get_subfile_info())) {
499 <<
"Couldn't open " << _source->get_subfile_info() <<
"\n";
506 if (!_ffvfile.open_vfs(_filename)) {
508 <<
"Couldn't open " << _filename <<
"\n";
514 nassertr(_format_ctx == NULL,
false);
515 _format_ctx = _ffvfile.get_format_context();
516 nassertr(_format_ctx != NULL,
false);
518 #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53, 6, 0) 519 if (avformat_find_stream_info(_format_ctx, NULL) < 0) {
521 if (av_find_stream_info(_format_ctx) < 0) {
524 <<
"Couldn't find stream info\n";
530 nassertr(_video_ctx == NULL,
false);
531 for (
int i = 0; i < (int)_format_ctx->nb_streams; ++i) {
532 if (_format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
534 _video_ctx = _format_ctx->streams[i]->codec;
535 _video_timebase = av_q2d(_format_ctx->streams[i]->time_base);
536 _min_fseek = (int)(3.0 / _video_timebase);
540 if (_video_ctx == NULL) {
542 <<
"Couldn't find video_ctx\n";
547 AVCodec *pVideoCodec = avcodec_find_decoder(_video_ctx->codec_id);
548 if (pVideoCodec == NULL) {
550 <<
"Couldn't find codec\n";
554 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53, 8, 0) 555 if (avcodec_open2(_video_ctx, pVideoCodec, NULL) < 0) {
557 if (avcodec_open(_video_ctx, pVideoCodec) < 0) {
560 <<
"Couldn't open codec\n";
565 _size_x = _video_ctx->width;
566 _size_y = _video_ctx->height;
568 _length = (double)_format_ctx->duration / (
double)AV_TIME_BASE;
570 _can_seek_fast =
true;
581 void FfmpegVideoCursor::
586 if ((_video_ctx)&&(_video_ctx->codec)) {
587 avcodec_close(_video_ctx);
602 void FfmpegVideoCursor::
610 if (_convert_ctx != NULL) {
611 sws_freeContext(_convert_ctx);
614 #endif // HAVE_SWSCALE 622 _frame_out->data[0] = 0;
629 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 12, 100) 630 av_packet_unref(_packet);
632 av_free_packet(_packet);
646 void FfmpegVideoCursor::
647 st_thread_main(
void *
self) {
656 void FfmpegVideoCursor::
658 if (ffmpeg_cat.is_spam()) {
660 <<
"ffmpeg thread for " << _filename.get_basename() <<
" starting.\n";
668 _readahead_frames.push_back(frame);
674 while (_thread_status != TS_shutdown) {
675 nassertv(_thread_status != TS_stopped);
681 PStatClient::thread_tick(_sync_name);
687 _thread_status = TS_stopped;
688 if (ffmpeg_cat.is_spam()) {
690 <<
"ffmpeg thread for " << _filename.get_basename() <<
" stopped.\n";
702 bool FfmpegVideoCursor::
704 switch (_thread_status) {
708 nassertr(
false,
false);
716 if ((
int)_readahead_frames.size() < _max_readahead_frames) {
719 nassertr(frame != NULL,
false);
725 _readahead_frames.push_back(frame);
739 int seek_frame = _seek_frame;
740 _thread_status = TS_seeking;
742 nassertr(frame != NULL,
false);
744 advance_to_frame(seek_frame);
748 do_clear_all_frames();
749 _readahead_frames.push_back(frame);
752 do_clear_all_frames();
755 if (_thread_status == TS_seeking) {
757 _thread_status = TS_readahead;
778 PT(
Buffer) buffer = make_new_buffer();
788 void FfmpegVideoCursor::
789 do_clear_all_frames() {
790 _readahead_frames.clear();
804 bool FfmpegVideoCursor::
805 fetch_packet(
int default_frame) {
806 if (ffmpeg_global_lock) {
808 return do_fetch_packet(default_frame);
810 return do_fetch_packet(default_frame);
820 bool FfmpegVideoCursor::
821 do_fetch_packet(
int default_frame) {
823 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 12, 100) 824 av_packet_unref(_packet);
826 av_free_packet(_packet);
829 while (av_read_frame(_format_ctx, _packet) >= 0) {
830 if (_packet->stream_index == _video_index) {
831 _packet_frame = _packet->dts;
834 #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 12, 100) 835 av_packet_unref(_packet);
837 av_free_packet(_packet);
842 if (!_eof_known && default_frame != 0) {
843 _eof_frame = _packet_frame;
847 if (ffmpeg_cat.is_spam()) {
850 <<
"end of video at frame " << _eof_frame <<
"\n";
856 _packet_frame = default_frame;
871 void FfmpegVideoCursor::
872 fetch_frame(
int frame) {
877 if (_packet_frame <= frame) {
882 while (_packet_frame <= frame) {
886 decode_frame(finished);
887 _begin_frame = _packet_frame;
888 if (fetch_packet(frame)) {
889 _end_frame = _packet_frame;
890 _frame_ready =
false;
898 while (!finished && _packet->data) {
899 decode_frame(finished);
900 _begin_frame = _packet_frame;
901 fetch_packet(_begin_frame + 1);
905 _end_frame = _packet_frame;
915 void FfmpegVideoCursor::
916 decode_frame(
int &finished) {
917 if (ffmpeg_global_lock) {
919 do_decode_frame(finished);
921 do_decode_frame(finished);
931 void FfmpegVideoCursor::
932 do_decode_frame(
int &finished) {
933 #if LIBAVCODEC_VERSION_INT < 3414272 934 avcodec_decode_video(_video_ctx, _frame,
935 &finished, _packet->data, _packet->size);
937 avcodec_decode_video2(_video_ctx, _frame, &finished, _packet);
948 void FfmpegVideoCursor::
949 seek(
int frame,
bool backward) {
952 if (ffmpeg_support_seek) {
953 if (ffmpeg_global_lock) {
955 do_seek(frame, backward);
957 do_seek(frame, backward);
975 void FfmpegVideoCursor::
976 do_seek(
int frame,
bool backward) {
977 PN_int64 target_ts = (PN_int64)frame;
978 if (target_ts < (PN_int64)(_initial_dts)) {
980 target_ts = _initial_dts;
984 flags = AVSEEK_FLAG_BACKWARD;
987 if (av_seek_frame(_format_ctx, _video_index, target_ts, flags) < 0) {
988 if (ffmpeg_cat.is_spam()) {
990 <<
"Seek failure.\n";
1001 if (binary_seek(_initial_dts, frame, frame, 1) < 0) {
1002 if (ffmpeg_cat.is_spam()) {
1004 <<
"Seek double failure.\n";
1022 int FfmpegVideoCursor::
1023 binary_seek(
int min_frame,
int max_frame,
int target_frame,
int num_iterations) {
1024 int try_frame = (min_frame + max_frame) / 2;
1025 if (num_iterations > 5 || try_frame >= max_frame) {
1030 if (av_seek_frame(_format_ctx, _video_index, try_frame, AVSEEK_FLAG_BACKWARD) < 0) {
1032 if (binary_seek(min_frame, try_frame - 1, target_frame, num_iterations + 1) < 0) {
1037 if (binary_seek(try_frame + 1, max_frame, target_frame, num_iterations + 1) < 0) {
1050 void FfmpegVideoCursor::
1052 if (ffmpeg_cat.is_spam()) {
1054 <<
"Resetting ffmpeg stream.\n";
1058 if (!open_stream()) {
1060 <<
"Stream error, invalidating movie.\n";
1075 void FfmpegVideoCursor::
1076 advance_to_frame(
int frame) {
1079 if (frame < _begin_frame) {
1081 if (ffmpeg_cat.is_spam()) {
1083 <<
"Seeking backward to " << frame <<
" from " << _begin_frame <<
"\n";
1086 if (_begin_frame > frame) {
1087 if (ffmpeg_cat.is_spam()) {
1089 <<
"Ended up at " << _begin_frame <<
", not far enough back!\n";
1092 if (ffmpeg_cat.is_spam()) {
1094 <<
"Reseek to 0, got " << _begin_frame <<
"\n";
1097 if (frame > _end_frame) {
1098 if (ffmpeg_cat.is_spam()) {
1100 <<
"Now sliding forward to " << frame <<
" from " << _begin_frame <<
"\n";
1105 }
else if (frame < _end_frame) {
1107 if (ffmpeg_cat.is_spam()) {
1109 <<
"Currently have " << frame <<
" within " << _begin_frame <<
" .. " << _end_frame <<
"\n";
1112 }
else if (frame < _end_frame + _min_fseek) {
1114 if (ffmpeg_cat.is_spam()) {
1116 <<
"Sliding forward to " << frame <<
" from " << _begin_frame <<
"\n";
1129 if (ffmpeg_cat.is_spam()) {
1131 <<
"Jumping forward to " << frame <<
" from " << _begin_frame <<
"\n";
1133 int base = _begin_frame;
1135 if (_begin_frame < base) {
1136 _min_fseek += (base - _begin_frame);
1137 if (ffmpeg_cat.is_spam()) {
1139 <<
"Wrong way! Increasing _min_fseek to " << _min_fseek <<
"\n";
1142 if (frame > _end_frame) {
1143 if (ffmpeg_cat.is_spam()) {
1145 <<
"Correcting, sliding forward to " << frame <<
" from " << _begin_frame <<
"\n";
1151 if (ffmpeg_cat.is_spam()) {
1153 <<
"Wanted " << frame <<
", got " << _begin_frame <<
"\n";
1163 void FfmpegVideoCursor::
1167 if (!_frame_ready) {
1169 if (ffmpeg_cat.is_spam()) {
1171 <<
"ffmpeg for " << _filename.get_basename()
1172 <<
", no frame available.\n";
1174 memset(buffer->_block, 0, buffer->_block_size);
1178 _frame_out->data[0] = buffer->_block + ((_size_y - 1) * _size_x * 3);
1179 _frame_out->linesize[0] = _size_x * -3;
1180 buffer->_begin_frame = _begin_frame;
1181 buffer->_end_frame = _end_frame;
1183 if (ffmpeg_global_lock) {
1186 nassertv(_convert_ctx != NULL && _frame != NULL && _frame_out != NULL);
1187 sws_scale(_convert_ctx, _frame->data, _frame->linesize, 0, _size_y, _frame_out->data, _frame_out->linesize);
1189 img_convert((AVPicture *)_frame_out, PIX_FMT_BGR24,
1190 (AVPicture *)_frame, _video_ctx->pix_fmt, _size_x, _size_y);
1194 nassertv(_convert_ctx != NULL && _frame != NULL && _frame_out != NULL);
1195 sws_scale(_convert_ctx, _frame->data, _frame->linesize, 0, _size_y, _frame_out->data, _frame_out->linesize);
1197 img_convert((AVPicture *)_frame_out, PIX_FMT_BGR24,
1198 (AVPicture *)_frame, _video_ctx->pix_fmt, _size_x, _size_y);
1240 DCAST_INTO_V(video, _source);
1259 parse_params(params, scan, manager);
1260 video->fillin(scan, manager);
1272 void FfmpegVideoCursor::
1274 MovieVideoCursor::fillin(scan, manager);
1297 DCAST_INTO_R(fother, other, 0);
1298 if (_end_frame * _video_timebase <= fother->_begin_frame * fother->_video_timebase) {
1300 }
else if (_begin_frame * _video_timebase >= fother->_end_frame * fother->_video_timebase) {
1317 int mid_frame = (_begin_frame + _end_frame - 1) / 2;
1318 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()...
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...
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 ...
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...
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, particularly important when synchronizing the different pages of a multi-page texture.
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...
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.
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. ...
ThreadPriority get_thread_priority() const
Returns the current thread priority of the thread that decodes the ffmpeg video stream (if max_readah...
void stop_thread()
Explicitly stops the ffmpeg decoding thread.
TypeHandle is the identifier used to differentiate C++ class types.
An ordered list of data elements, formatted in memory for transmission over a socket or writing to a ...
void set_thread_priority(ThreadPriority thread_priority)
Changes the thread priority of the thread that decodes the ffmpeg video stream (if max_readahead_fram...