solve some seeking issues

This commit is contained in:
David Rose 2011-11-15 02:17:27 +00:00
parent c0aaffd8e7
commit 742d5ffe7e
4 changed files with 249 additions and 139 deletions

View File

@ -76,77 +76,15 @@ init_from(FfmpegVideo *source) {
_source = source; _source = source;
_filename = _source->get_filename(); _filename = _source->get_filename();
// Hold the global lock while we open the file and create avcodec if (!open_stream()) {
// objects. cleanup();
return;
}
ReMutexHolder av_holder(_av_lock); 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 #ifdef HAVE_SWSCALE
nassertv(_convert_ctx == NULL);
_convert_ctx = sws_getContext(_size_x, _size_y, _convert_ctx = sws_getContext(_size_x, _size_y,
_video_ctx->pix_fmt, _size_x, _size_y, _video_ctx->pix_fmt, _size_x, _size_y,
PIX_FMT_BGR24, SWS_BILINEAR | SWS_PRINT_INFO, NULL, NULL, NULL); PIX_FMT_BGR24, SWS_BILINEAR | SWS_PRINT_INFO, NULL, NULL, NULL);
@ -469,6 +407,114 @@ release_buffer(Buffer *buffer) {
do_recycle_frame(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 // Function: FfmpegVideoCursor::cleanup
// Access: Private // Access: Private
@ -477,10 +523,17 @@ release_buffer(Buffer *buffer) {
void FfmpegVideoCursor:: void FfmpegVideoCursor::
cleanup() { cleanup() {
stop_thread(); stop_thread();
close_stream();
// Hold the global lock while we free avcodec objects.
ReMutexHolder av_holder(_av_lock); ReMutexHolder av_holder(_av_lock);
#ifdef HAVE_SWSCALE
if (_convert_ctx != NULL) {
sws_freeContext(_convert_ctx);
}
_convert_ctx = NULL;
#endif // HAVE_SWSCALE
if (_frame) { if (_frame) {
av_free(_frame); av_free(_frame);
_frame = NULL; _frame = NULL;
@ -507,25 +560,6 @@ cleanup() {
delete _packet1; delete _packet1;
_packet1 = NULL; _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; _packet0->data = 0;
_packet_time = default_time; _packet_time = default_time;
_eof_reached = false;
if (ffmpeg_cat.is_debug()) {
ffmpeg_cat.debug()
<< "end of video\n";
}
return true; return true;
} }
@ -744,7 +783,7 @@ flip_packets() {
// Description: Called within the sub-thread. Slides forward until // Description: Called within the sub-thread. Slides forward until
// the indicated time, then fetches a frame from the // the indicated time, then fetches a frame from the
// stream and stores it in the frame buffer. Sets // 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 // the frame. Returns true if the end of the video is
// reached. // reached.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
@ -754,7 +793,6 @@ fetch_frame(double time) {
PStatTimer timer(fetch_buffer_pcollector); PStatTimer timer(fetch_buffer_pcollector);
int finished = 0; int finished = 0;
_begin_time = _packet_time;
if (_packet_time <= time) { if (_packet_time <= time) {
_video_ctx->skip_frame = AVDISCARD_BIDIR; _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 // Get the next packet. The first packet beyond the time we're
// looking for marks the point to stop. // looking for marks the point to stop.
_begin_time = _packet_time;
if (fetch_packet(time)) { if (fetch_packet(time)) {
if (ffmpeg_cat.is_debug()) {
ffmpeg_cat.debug()
<< "end of video\n";
}
_frame_ready = false; _frame_ready = false;
return true; return true;
} }
@ -784,11 +819,8 @@ fetch_frame(double time) {
avcodec_decode_video2(_video_ctx, _frame, &finished, _packet1); avcodec_decode_video2(_video_ctx, _frame, &finished, _packet1);
#endif #endif
flip_packets(); flip_packets();
_begin_time = _packet_time;
if (fetch_packet(time)) { if (fetch_packet(time)) {
if (ffmpeg_cat.is_debug()) {
ffmpeg_cat.debug()
<< "end of video\n";
}
_frame_ready = false; _frame_ready = false;
return true; return true;
} }
@ -807,6 +839,7 @@ fetch_frame(double time) {
} else { } else {
// Just get the next frame. // Just get the next frame.
_begin_time = _packet_time;
finished = 0; finished = 0;
while (!finished && _packet0->data) { while (!finished && _packet0->data) {
#if LIBAVCODEC_VERSION_INT < 3414272 #if LIBAVCODEC_VERSION_INT < 3414272
@ -832,7 +865,7 @@ fetch_frame(double time) {
// to be less than or equal to the specified time. // to be less than or equal to the specified time.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
void FfmpegVideoCursor:: void FfmpegVideoCursor::
seek(double t) { seek(double t, bool backward) {
static PStatCollector seek_pcollector("*:FFMPEG Video Decoding:Seek"); static PStatCollector seek_pcollector("*:FFMPEG Video Decoding:Seek");
PStatTimer timer(seek_pcollector); PStatTimer timer(seek_pcollector);
@ -841,18 +874,30 @@ seek(double t) {
// Attempts to seek before the first packet will fail. // Attempts to seek before the first packet will fail.
target_ts = _initial_dts; target_ts = _initial_dts;
} }
if (av_seek_frame(_format_ctx, _video_index, target_ts, AVSEEK_FLAG_BACKWARD) < 0) { int flags = 0;
if (t >= _packet_time) { if (backward) {
return; flags = AVSEEK_FLAG_BACKWARD;
} //reset_stream();
movies_cat.error() << "Seek failure. Shutting down movie.\n";
cleanup();
_packet_time = t;
return;
} }
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 // Close and re-open the codec (presumably to flush the queue).
// stream. // 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); ReMutexHolder av_holder(_av_lock);
avcodec_close(_video_ctx); avcodec_close(_video_ctx);
@ -866,15 +911,37 @@ seek(double t) {
cleanup(); cleanup();
return; return;
} }
*/
} }
fetch_packet(t); fetch_packet(0);
fetch_frame(-1); 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) { if (time < _begin_time) {
// Time is in the past. // Time is in the past.
if (ffmpeg_cat.is_debug()) { if (_eof_reached) {
ffmpeg_cat.debug() // Go ahead and reset the video when we back up after having
<< "Seeking backward to " << time << " from " << _begin_time << "\n"; // 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 (time < _begin_time) {
if (_packet_time > time) { if (ffmpeg_cat.is_debug()) {
ffmpeg_cat.debug() ffmpeg_cat.debug()
<< "Not far enough back!\n"; << "Now sliding forward to " << time << " from " << _begin_time << "\n";
seek(0); }
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) { } else if (time < _end_time) {
// Time is in the present: already have the frame. // Time is in the present: already have the frame.
@ -917,7 +1004,7 @@ fetch_time(double time) {
// Time is in the near future. // Time is in the near future.
if (ffmpeg_cat.is_debug()) { if (ffmpeg_cat.is_debug()) {
ffmpeg_cat.debug() ffmpeg_cat.debug()
<< "Sliding forward to " << time << " from " << _packet_time << "\n"; << "Sliding forward to " << time << " from " << _begin_time << "\n";
} }
fetch_frame(time); fetch_frame(time);
@ -934,25 +1021,27 @@ fetch_time(double time) {
ffmpeg_cat.debug() ffmpeg_cat.debug()
<< "Jumping forward to " << time << " from " << _begin_time << "\n"; << "Jumping forward to " << time << " from " << _begin_time << "\n";
} }
double base = _packet_time; double base = _begin_time;
seek(time); seek(time, false);
if (_packet_time < base) { if (_begin_time < base) {
_min_fseek += (base - _packet_time); _min_fseek += (base - _begin_time);
if (ffmpeg_cat.is_debug()) { if (ffmpeg_cat.is_debug()) {
ffmpeg_cat.debug() ffmpeg_cat.debug()
<< "Wrong way! Increasing _min_fseek to " << _min_fseek << "\n"; << "Wrong way! Increasing _min_fseek to " << _min_fseek << "\n";
} }
} }
if (ffmpeg_cat.is_debug()) { if (time < _begin_time) {
ffmpeg_cat.debug() if (ffmpeg_cat.is_debug()) {
<< "Correcting, sliding forward to " << time << " from " << _packet_time << "\n"; ffmpeg_cat.debug()
<< "Correcting, sliding forward to " << time << " from " << _begin_time << "\n";
}
fetch_frame(time);
} }
fetch_frame(time);
} }
if (ffmpeg_cat.is_debug()) { if (ffmpeg_cat.is_debug()) {
ffmpeg_cat.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) { if (!_frame_ready) {
// No frame data ready, just fill with black. // 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); memset(buffer->_block, 0, buffer->_block_size);
return; return;
} }

View File

@ -64,6 +64,8 @@ public:
virtual void release_buffer(Buffer *buffer); virtual void release_buffer(Buffer *buffer);
private: private:
bool open_stream();
void close_stream();
void cleanup(); void cleanup();
Filename _filename; Filename _filename;
@ -109,8 +111,9 @@ private:
bool fetch_packet(double default_time); bool fetch_packet(double default_time);
void flip_packets(); void flip_packets();
bool fetch_frame(double time); bool fetch_frame(double time);
void seek(double t); void seek(double t, bool backward);
void fetch_time(double time); void fetch_time(double time);
void reset_stream();
void export_frame(Buffer *buffer); void export_frame(Buffer *buffer);
// The following data members will be accessed by the sub-thread. // The following data members will be accessed by the sub-thread.
@ -130,6 +133,7 @@ private:
double _begin_time; double _begin_time;
double _end_time; double _end_time;
bool _frame_ready; bool _frame_ready;
bool _eof_reached;
public: public:
static void register_with_read_factory(); static void register_with_read_factory();

View File

@ -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 // Function: FfmpegVirtualFile::get_format_context
// Access: Public // Access: Public

View File

@ -50,6 +50,7 @@ public:
bool open_subfile(const SubfileInfo &info); bool open_subfile(const SubfileInfo &info);
void close(); void close();
INLINE bool is_open() const;
INLINE AVFormatContext *get_format_context() const; INLINE AVFormatContext *get_format_context() const;
static void register_protocol(); static void register_protocol();