From 742d5ffe7e8a55a177d8cd996ed8b9898bc35642 Mon Sep 17 00:00:00 2001 From: David Rose Date: Tue, 15 Nov 2011 02:17:27 +0000 Subject: [PATCH] solve some seeking issues --- panda/src/movies/ffmpegVideoCursor.cxx | 370 ++++++++++++++++--------- panda/src/movies/ffmpegVideoCursor.h | 6 +- panda/src/movies/ffmpegVirtualFile.I | 11 + panda/src/movies/ffmpegVirtualFile.h | 1 + 4 files changed, 249 insertions(+), 139 deletions(-) diff --git a/panda/src/movies/ffmpegVideoCursor.cxx b/panda/src/movies/ffmpegVideoCursor.cxx index 56245875e6..25a9a83198 100644 --- a/panda/src/movies/ffmpegVideoCursor.cxx +++ b/panda/src/movies/ffmpegVideoCursor.cxx @@ -76,77 +76,15 @@ init_from(FfmpegVideo *source) { _source = source; _filename = _source->get_filename(); - // Hold the global lock while we open the file and create avcodec - // objects. + if (!open_stream()) { + cleanup(); + return; + } + ReMutexHolder av_holder(_av_lock); - - if (!_source->get_subfile_info().is_empty()) { - // Read a subfile. - if (!_ffvfile.open_subfile(_source->get_subfile_info())) { - movies_cat.info() - << "Couldn't open " << _source->get_subfile_info() << "\n"; - cleanup(); - return; - } - - } else { - // Read a filename. - if (!_ffvfile.open_vfs(_filename)) { - movies_cat.info() - << "Couldn't open " << _filename << "\n"; - cleanup(); - return; - } - } - - _format_ctx = _ffvfile.get_format_context(); - nassertv(_format_ctx != NULL); - - if (av_find_stream_info(_format_ctx) < 0) { - movies_cat.info() - << "Couldn't find stream info\n"; - cleanup(); - return; - } - - // Find the video stream - for (int i = 0; i < (int)_format_ctx->nb_streams; ++i) { - if (_format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { - _video_index = i; - _video_ctx = _format_ctx->streams[i]->codec; - _video_timebase = av_q2d(_format_ctx->streams[i]->time_base); - } - } - - if (_video_ctx == 0) { - movies_cat.info() - << "Couldn't find video_ctx\n"; - cleanup(); - return; - } - - AVCodec *pVideoCodec = avcodec_find_decoder(_video_ctx->codec_id); - if (pVideoCodec == NULL) { - movies_cat.info() - << "Couldn't find codec\n"; - cleanup(); - return; - } - if (avcodec_open(_video_ctx, pVideoCodec) < 0) { - movies_cat.info() - << "Couldn't open codec\n"; - cleanup(); - return; - } - - _size_x = _video_ctx->width; - _size_y = _video_ctx->height; - _num_components = 3; // Don't know how to implement RGBA movies yet. - _length = (_format_ctx->duration * 1.0) / AV_TIME_BASE; - _can_seek = true; - _can_seek_fast = true; #ifdef HAVE_SWSCALE + nassertv(_convert_ctx == NULL); _convert_ctx = sws_getContext(_size_x, _size_y, _video_ctx->pix_fmt, _size_x, _size_y, PIX_FMT_BGR24, SWS_BILINEAR | SWS_PRINT_INFO, NULL, NULL, NULL); @@ -469,6 +407,114 @@ release_buffer(Buffer *buffer) { do_recycle_frame(buffer); } +//////////////////////////////////////////////////////////////////// +// Function: FfmpegVideoCursor::open_stream +// Access: Private +// Description: Opens the stream for the first time, or when needed +// internally. +//////////////////////////////////////////////////////////////////// +bool FfmpegVideoCursor:: +open_stream() { + nassertr(!_ffvfile.is_open(), false); + + // Hold the global lock while we open the file and create avcodec + // objects. + ReMutexHolder av_holder(_av_lock); + + if (!_source->get_subfile_info().is_empty()) { + // Read a subfile. + if (!_ffvfile.open_subfile(_source->get_subfile_info())) { + movies_cat.info() + << "Couldn't open " << _source->get_subfile_info() << "\n"; + close_stream(); + return false; + } + + } else { + // Read a filename. + if (!_ffvfile.open_vfs(_filename)) { + movies_cat.info() + << "Couldn't open " << _filename << "\n"; + close_stream(); + return false; + } + } + + nassertr(_format_ctx == NULL, false); + _format_ctx = _ffvfile.get_format_context(); + nassertr(_format_ctx != NULL, false); + + if (av_find_stream_info(_format_ctx) < 0) { + movies_cat.info() + << "Couldn't find stream info\n"; + close_stream(); + return false; + } + + // Find the video stream + nassertr(_video_ctx == NULL, false); + for (int i = 0; i < (int)_format_ctx->nb_streams; ++i) { + if (_format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + _video_index = i; + _video_ctx = _format_ctx->streams[i]->codec; + _video_timebase = av_q2d(_format_ctx->streams[i]->time_base); + } + } + + if (_video_ctx == NULL) { + movies_cat.info() + << "Couldn't find video_ctx\n"; + close_stream(); + return false; + } + + AVCodec *pVideoCodec = avcodec_find_decoder(_video_ctx->codec_id); + if (pVideoCodec == NULL) { + movies_cat.info() + << "Couldn't find codec\n"; + close_stream(); + return false; + } + if (avcodec_open(_video_ctx, pVideoCodec) < 0) { + movies_cat.info() + << "Couldn't open codec\n"; + close_stream(); + return false; + } + + _size_x = _video_ctx->width; + _size_y = _video_ctx->height; + _num_components = 3; // Don't know how to implement RGBA movies yet. + _length = (_format_ctx->duration * 1.0) / AV_TIME_BASE; + _can_seek = true; + _can_seek_fast = true; + _eof_reached = false; + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: FfmpegVideoCursor::close_stream +// Access: Private +// Description: Closes the stream, during cleanup or when needed +// internally. +//////////////////////////////////////////////////////////////////// +void FfmpegVideoCursor:: +close_stream() { + // Hold the global lock while we free avcodec objects. + ReMutexHolder av_holder(_av_lock); + + if ((_video_ctx)&&(_video_ctx->codec)) { + avcodec_close(_video_ctx); + } + _video_ctx = NULL; + + _ffvfile.close(); + _format_ctx = NULL; + + _video_index = -1; +} + //////////////////////////////////////////////////////////////////// // Function: FfmpegVideoCursor::cleanup // Access: Private @@ -477,10 +523,17 @@ release_buffer(Buffer *buffer) { void FfmpegVideoCursor:: cleanup() { stop_thread(); - - // Hold the global lock while we free avcodec objects. + close_stream(); + ReMutexHolder av_holder(_av_lock); +#ifdef HAVE_SWSCALE + if (_convert_ctx != NULL) { + sws_freeContext(_convert_ctx); + } + _convert_ctx = NULL; +#endif // HAVE_SWSCALE + if (_frame) { av_free(_frame); _frame = NULL; @@ -507,25 +560,6 @@ cleanup() { delete _packet1; _packet1 = NULL; } - -#ifdef HAVE_SWSCALE - if (_convert_ctx != NULL) { - sws_freeContext(_convert_ctx); - } - _convert_ctx = NULL; -#endif // HAVE_SWSCALE - - if ((_video_ctx)&&(_video_ctx->codec)) { - avcodec_close(_video_ctx); - } - _video_ctx = NULL; - - if (_format_ctx) { - _ffvfile.close(); - _format_ctx = NULL; - } - - _video_index = -1; } //////////////////////////////////////////////////////////////////// @@ -723,6 +757,11 @@ fetch_packet(double default_time) { } _packet0->data = 0; _packet_time = default_time; + _eof_reached = false; + if (ffmpeg_cat.is_debug()) { + ffmpeg_cat.debug() + << "end of video\n"; + } return true; } @@ -744,7 +783,7 @@ flip_packets() { // Description: Called within the sub-thread. Slides forward until // the indicated time, then fetches a frame from the // stream and stores it in the frame buffer. Sets -// last_start and next_start to indicate the extents of +// _begin_time and _end_time to indicate the extents of // the frame. Returns true if the end of the video is // reached. //////////////////////////////////////////////////////////////////// @@ -754,7 +793,6 @@ fetch_frame(double time) { PStatTimer timer(fetch_buffer_pcollector); int finished = 0; - _begin_time = _packet_time; if (_packet_time <= time) { _video_ctx->skip_frame = AVDISCARD_BIDIR; @@ -764,11 +802,8 @@ fetch_frame(double time) { // Get the next packet. The first packet beyond the time we're // looking for marks the point to stop. + _begin_time = _packet_time; if (fetch_packet(time)) { - if (ffmpeg_cat.is_debug()) { - ffmpeg_cat.debug() - << "end of video\n"; - } _frame_ready = false; return true; } @@ -784,11 +819,8 @@ fetch_frame(double time) { avcodec_decode_video2(_video_ctx, _frame, &finished, _packet1); #endif flip_packets(); + _begin_time = _packet_time; if (fetch_packet(time)) { - if (ffmpeg_cat.is_debug()) { - ffmpeg_cat.debug() - << "end of video\n"; - } _frame_ready = false; return true; } @@ -807,6 +839,7 @@ fetch_frame(double time) { } else { // Just get the next frame. + _begin_time = _packet_time; finished = 0; while (!finished && _packet0->data) { #if LIBAVCODEC_VERSION_INT < 3414272 @@ -832,7 +865,7 @@ fetch_frame(double time) { // to be less than or equal to the specified time. //////////////////////////////////////////////////////////////////// void FfmpegVideoCursor:: -seek(double t) { +seek(double t, bool backward) { static PStatCollector seek_pcollector("*:FFMPEG Video Decoding:Seek"); PStatTimer timer(seek_pcollector); @@ -841,18 +874,30 @@ seek(double t) { // Attempts to seek before the first packet will fail. target_ts = _initial_dts; } - if (av_seek_frame(_format_ctx, _video_index, target_ts, AVSEEK_FLAG_BACKWARD) < 0) { - if (t >= _packet_time) { - return; - } - movies_cat.error() << "Seek failure. Shutting down movie.\n"; - cleanup(); - _packet_time = t; - return; + int flags = 0; + if (backward) { + flags = AVSEEK_FLAG_BACKWARD; + //reset_stream(); } + + + if (av_seek_frame(_format_ctx, _video_index, target_ts, flags) < 0) { + if (ffmpeg_cat.is_debug()) { + ffmpeg_cat.debug() + << "Seek failure.\n"; + } + reset_stream(); + + av_seek_frame(_format_ctx, _video_index, target_ts, 0); + } + { - // Hold the global lock while we close and re-open the video - // stream. + // Close and re-open the codec (presumably to flush the queue). + // Actually, this causes the stream to fail in certain video + // files, and doesn't seem to have any useful benefit. So screw + // it, and don't do this. + + /* ReMutexHolder av_holder(_av_lock); avcodec_close(_video_ctx); @@ -866,15 +911,37 @@ seek(double t) { cleanup(); return; } + */ } - fetch_packet(t); + fetch_packet(0); fetch_frame(-1); - /* - if (_packet_time > t) { - _packet_time = t; +} + +//////////////////////////////////////////////////////////////////// +// Function: FfmpegVideoCursor::reset_stream +// Access: Private +// Description: Resets the stream to its initial, first-opened state +// by closing and re-opening it. +//////////////////////////////////////////////////////////////////// +void FfmpegVideoCursor:: +reset_stream() { + if (ffmpeg_cat.is_debug()) { + ffmpeg_cat.debug() + << "Resetting ffmpeg stream.\n"; } - */ + + close_stream(); + if (!open_stream()) { + ffmpeg_cat.error() + << "Stream error, invalidating movie.\n"; + cleanup(); + return; + } + + fetch_packet(0.0); + _initial_dts = _packet0->dts; + fetch_frame(-1); } //////////////////////////////////////////////////////////////////// @@ -890,21 +957,41 @@ fetch_time(double time) { if (time < _begin_time) { // Time is in the past. - if (ffmpeg_cat.is_debug()) { - ffmpeg_cat.debug() - << "Seeking backward to " << time << " from " << _begin_time << "\n"; + if (_eof_reached) { + // Go ahead and reset the video when we back up after having + // reached the end. This avoids potential probelsm with + // unseekable streams. + if (ffmpeg_cat.is_debug()) { + ffmpeg_cat.debug() + << "Resetting to " << time << " after eof\n"; + } + reset_stream(); + + } else { + if (ffmpeg_cat.is_debug()) { + ffmpeg_cat.debug() + << "Seeking backward to " << time << " from " << _begin_time << "\n"; + } + seek(time, true); + if (_begin_time > time) { + if (ffmpeg_cat.is_debug()) { + ffmpeg_cat.debug() + << "Ended up at " << _begin_time << ", not far enough back!\n"; + } + reset_stream(); + if (ffmpeg_cat.is_debug()) { + ffmpeg_cat.debug() + << "Reseek to 0, got " << _begin_time << "\n"; + } + } } - seek(time); - if (_packet_time > time) { - ffmpeg_cat.debug() - << "Not far enough back!\n"; - seek(0); + if (time < _begin_time) { + if (ffmpeg_cat.is_debug()) { + ffmpeg_cat.debug() + << "Now sliding forward to " << time << " from " << _begin_time << "\n"; + } + fetch_frame(time); } - if (ffmpeg_cat.is_debug()) { - ffmpeg_cat.debug() - << "Correcting, sliding forward to " << time << " from " << _packet_time << "\n"; - } - fetch_frame(time); } else if (time < _end_time) { // Time is in the present: already have the frame. @@ -917,7 +1004,7 @@ fetch_time(double time) { // Time is in the near future. if (ffmpeg_cat.is_debug()) { ffmpeg_cat.debug() - << "Sliding forward to " << time << " from " << _packet_time << "\n"; + << "Sliding forward to " << time << " from " << _begin_time << "\n"; } fetch_frame(time); @@ -934,25 +1021,27 @@ fetch_time(double time) { ffmpeg_cat.debug() << "Jumping forward to " << time << " from " << _begin_time << "\n"; } - double base = _packet_time; - seek(time); - if (_packet_time < base) { - _min_fseek += (base - _packet_time); + double base = _begin_time; + seek(time, false); + if (_begin_time < base) { + _min_fseek += (base - _begin_time); if (ffmpeg_cat.is_debug()) { ffmpeg_cat.debug() << "Wrong way! Increasing _min_fseek to " << _min_fseek << "\n"; } } - if (ffmpeg_cat.is_debug()) { - ffmpeg_cat.debug() - << "Correcting, sliding forward to " << time << " from " << _packet_time << "\n"; + if (time < _begin_time) { + if (ffmpeg_cat.is_debug()) { + ffmpeg_cat.debug() + << "Correcting, sliding forward to " << time << " from " << _begin_time << "\n"; + } + fetch_frame(time); } - fetch_frame(time); } if (ffmpeg_cat.is_debug()) { ffmpeg_cat.debug() - << "Wanted " << time << ", got " << _packet_time << "\n"; + << "Wanted " << time << ", got " << _begin_time << "\n"; } } @@ -969,6 +1058,11 @@ export_frame(MovieVideoCursor::Buffer *buffer) { if (!_frame_ready) { // No frame data ready, just fill with black. + if (ffmpeg_cat.is_debug()) { + ffmpeg_cat.debug() + << "ffmpeg for " << _filename.get_basename() + << ", no frame available.\n"; + } memset(buffer->_block, 0, buffer->_block_size); return; } diff --git a/panda/src/movies/ffmpegVideoCursor.h b/panda/src/movies/ffmpegVideoCursor.h index 3c8d1183e8..c39fe0143d 100644 --- a/panda/src/movies/ffmpegVideoCursor.h +++ b/panda/src/movies/ffmpegVideoCursor.h @@ -64,6 +64,8 @@ public: virtual void release_buffer(Buffer *buffer); private: + bool open_stream(); + void close_stream(); void cleanup(); Filename _filename; @@ -109,8 +111,9 @@ private: bool fetch_packet(double default_time); void flip_packets(); bool fetch_frame(double time); - void seek(double t); + void seek(double t, bool backward); void fetch_time(double time); + void reset_stream(); void export_frame(Buffer *buffer); // The following data members will be accessed by the sub-thread. @@ -130,6 +133,7 @@ private: double _begin_time; double _end_time; bool _frame_ready; + bool _eof_reached; public: static void register_with_read_factory(); diff --git a/panda/src/movies/ffmpegVirtualFile.I b/panda/src/movies/ffmpegVirtualFile.I index 3cf1542012..a66bb1a2a1 100644 --- a/panda/src/movies/ffmpegVirtualFile.I +++ b/panda/src/movies/ffmpegVirtualFile.I @@ -13,6 +13,17 @@ //////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////// +// Function: FfmpegVirtualFile::is_open +// Access: Public +// Description: Returns true if the stream is successfully opened, +// false otherwise. +//////////////////////////////////////////////////////////////////// +INLINE bool FfmpegVirtualFile:: +is_open() const { + return (_format_context != NULL); +} + //////////////////////////////////////////////////////////////////// // Function: FfmpegVirtualFile::get_format_context // Access: Public diff --git a/panda/src/movies/ffmpegVirtualFile.h b/panda/src/movies/ffmpegVirtualFile.h index ce944c6cc6..319f7d9d58 100644 --- a/panda/src/movies/ffmpegVirtualFile.h +++ b/panda/src/movies/ffmpegVirtualFile.h @@ -50,6 +50,7 @@ public: bool open_subfile(const SubfileInfo &info); void close(); + INLINE bool is_open() const; INLINE AVFormatContext *get_format_context() const; static void register_protocol();