|
|
@@ -49,7 +49,7 @@ FfmpegVideoCursor() :
|
|
|
_lock("FfmpegVideoCursor::_lock"),
|
|
|
_action_cvar(_lock),
|
|
|
_thread_status(TS_stopped),
|
|
|
- _seek_time(0.0),
|
|
|
+ _seek_frame(0),
|
|
|
_packet0(NULL),
|
|
|
_packet1(NULL),
|
|
|
_format_ctx(NULL),
|
|
|
@@ -58,7 +58,7 @@ FfmpegVideoCursor() :
|
|
|
_video_index(-1),
|
|
|
_frame(NULL),
|
|
|
_frame_out(NULL),
|
|
|
- _min_fseek(3.0)
|
|
|
+ _eof_known(false)
|
|
|
{
|
|
|
}
|
|
|
|
|
|
@@ -103,10 +103,14 @@ init_from(FfmpegVideo *source) {
|
|
|
memset(_packet0, 0, sizeof(AVPacket));
|
|
|
memset(_packet1, 0, sizeof(AVPacket));
|
|
|
|
|
|
- fetch_packet(0.0);
|
|
|
+ fetch_packet(0);
|
|
|
_initial_dts = _packet0->dts;
|
|
|
fetch_frame(-1);
|
|
|
|
|
|
+ _current_frame = -1;
|
|
|
+ _eof_known = false;
|
|
|
+ _eof_frame = 0;
|
|
|
+
|
|
|
#ifdef HAVE_THREADS
|
|
|
set_max_readahead_frames(ffmpeg_max_readahead_frames);
|
|
|
#endif // HAVE_THREADS
|
|
|
@@ -124,7 +128,7 @@ FfmpegVideoCursor(FfmpegVideo *src) :
|
|
|
_lock("FfmpegVideoCursor::_lock"),
|
|
|
_action_cvar(_lock),
|
|
|
_thread_status(TS_stopped),
|
|
|
- _seek_time(0.0),
|
|
|
+ _seek_frame(0),
|
|
|
_packet0(NULL),
|
|
|
_packet1(NULL),
|
|
|
_format_ctx(NULL),
|
|
|
@@ -133,7 +137,7 @@ FfmpegVideoCursor(FfmpegVideo *src) :
|
|
|
_video_index(-1),
|
|
|
_frame(NULL),
|
|
|
_frame_out(NULL),
|
|
|
- _min_fseek(3.0)
|
|
|
+ _eof_known(false)
|
|
|
{
|
|
|
init_from(src);
|
|
|
}
|
|
|
@@ -299,14 +303,7 @@ stop_thread() {
|
|
|
// else starts the thread up again.
|
|
|
MutexHolder holder(_lock);
|
|
|
|
|
|
- Buffers::iterator bi;
|
|
|
- for (bi = _readahead_frames.begin(); bi != _readahead_frames.end(); ++bi) {
|
|
|
- internal_free_buffer(*bi);
|
|
|
- }
|
|
|
_readahead_frames.clear();
|
|
|
- for (bi = _recycled_frames.begin(); bi != _recycled_frames.end(); ++bi) {
|
|
|
- internal_free_buffer(*bi);
|
|
|
- }
|
|
|
_recycled_frames.clear();
|
|
|
}
|
|
|
|
|
|
@@ -322,13 +319,52 @@ is_thread_started() const {
|
|
|
return (_thread_status != TS_stopped);
|
|
|
}
|
|
|
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+// Function: FfmpegVideoCursor::set_time
|
|
|
+// Access: Published, Virtual
|
|
|
+// Description: See MovieVideoCursor::set_time().
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+bool FfmpegVideoCursor::
|
|
|
+set_time(double time, int loop_count) {
|
|
|
+ int frame = (int)(time / _video_timebase + 0.5);
|
|
|
+
|
|
|
+ if (_eof_known) {
|
|
|
+ if (loop_count == 0) {
|
|
|
+ frame = frame % _eof_frame;
|
|
|
+ } else {
|
|
|
+ int last_frame = _eof_frame * loop_count;
|
|
|
+ if (frame < last_frame) {
|
|
|
+ frame = frame % _eof_frame;
|
|
|
+ } else {
|
|
|
+ frame = _eof_frame - 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ffmpeg_cat.is_spam() /* && frame != _current_frame*/) {
|
|
|
+ ffmpeg_cat.spam()
|
|
|
+ << "set_time(" << time << "): " << frame << ", loop_count = " << loop_count << "\n";
|
|
|
+ }
|
|
|
+
|
|
|
+ _current_frame = frame;
|
|
|
+ if (_current_frame_buffer != NULL) {
|
|
|
+ // If we've previously returned a frame, don't bother asking for a
|
|
|
+ // next one if that frame is still valid.
|
|
|
+ return (_current_frame >= _current_frame_buffer->_end_frame ||
|
|
|
+ _current_frame < _current_frame_buffer->_begin_frame);
|
|
|
+ }
|
|
|
+
|
|
|
+ // If our last request didn't return a frame, try again.
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
////////////////////////////////////////////////////////////////////
|
|
|
// Function: FfmpegVideoCursor::fetch_buffer
|
|
|
// Access: Public, Virtual
|
|
|
// Description: See MovieVideoCursor::fetch_buffer.
|
|
|
////////////////////////////////////////////////////////////////////
|
|
|
-MovieVideoCursor::Buffer *FfmpegVideoCursor::
|
|
|
-fetch_buffer(double time) {
|
|
|
+PT(MovieVideoCursor::Buffer) FfmpegVideoCursor::
|
|
|
+fetch_buffer() {
|
|
|
MutexHolder holder(_lock);
|
|
|
|
|
|
// If there was an error at any point, just return NULL.
|
|
|
@@ -336,10 +372,10 @@ fetch_buffer(double time) {
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
- Buffer *frame = NULL;
|
|
|
+ PT(FfmpegBuffer) frame;
|
|
|
if (_thread_status == TS_stopped) {
|
|
|
// Non-threaded case. Just get the next frame directly.
|
|
|
- fetch_time(time);
|
|
|
+ advance_to_frame(_current_frame);
|
|
|
if (_frame_ready) {
|
|
|
frame = do_alloc_frame();
|
|
|
export_frame(frame);
|
|
|
@@ -352,46 +388,70 @@ fetch_buffer(double time) {
|
|
|
frame = _readahead_frames.front();
|
|
|
_readahead_frames.pop_front();
|
|
|
_action_cvar.notify();
|
|
|
- while (frame->_end_time < time && !_readahead_frames.empty()) {
|
|
|
+ while (frame->_end_frame < _current_frame && !_readahead_frames.empty()) {
|
|
|
// This frame is too old. Discard it.
|
|
|
if (ffmpeg_cat.is_debug()) {
|
|
|
ffmpeg_cat.debug()
|
|
|
<< "ffmpeg for " << _filename.get_basename()
|
|
|
- << " at time " << time << ", discarding frame at "
|
|
|
- << frame->_begin_time << "\n";
|
|
|
+ << " at frame " << _current_frame << ", discarding frame at "
|
|
|
+ << frame->_begin_frame << "\n";
|
|
|
}
|
|
|
do_recycle_frame(frame);
|
|
|
frame = _readahead_frames.front();
|
|
|
_readahead_frames.pop_front();
|
|
|
}
|
|
|
- if (frame->_begin_time > time) {
|
|
|
+ if (frame->_begin_frame > _current_frame) {
|
|
|
// This frame is too new. Empty all remaining frames and seek
|
|
|
// backwards.
|
|
|
+ if (ffmpeg_cat.is_debug()) {
|
|
|
+ ffmpeg_cat.debug()
|
|
|
+ << "ffmpeg for " << _filename.get_basename()
|
|
|
+ << " at frame " << _current_frame << ", encountered too-new frame at "
|
|
|
+ << frame->_begin_frame << "\n";
|
|
|
+ }
|
|
|
do_recycle_all_frames();
|
|
|
if (_thread_status == TS_wait || _thread_status == TS_seek || _thread_status == TS_readahead) {
|
|
|
_thread_status = TS_seek;
|
|
|
- _seek_time = time;
|
|
|
+ _seek_frame = _current_frame;
|
|
|
_action_cvar.notify();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- if (frame == NULL || frame->_end_time < time) {
|
|
|
+ if (frame == NULL || frame->_end_frame < _current_frame) {
|
|
|
// No frame available, or the frame is too old. Seek.
|
|
|
if (_thread_status == TS_wait || _thread_status == TS_seek || _thread_status == TS_readahead) {
|
|
|
_thread_status = TS_seek;
|
|
|
- _seek_time = time;
|
|
|
+ _seek_frame = _current_frame;
|
|
|
_action_cvar.notify();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- if (frame != NULL && (frame->_end_time < time || frame->_begin_time > time)) {
|
|
|
+ if (frame != NULL && (frame->_end_frame < _current_frame || frame->_begin_frame > _current_frame)) {
|
|
|
// The frame is too old or too new. Just recycle it.
|
|
|
do_recycle_frame(frame);
|
|
|
frame = NULL;
|
|
|
}
|
|
|
-
|
|
|
- return frame;
|
|
|
+
|
|
|
+ if (frame != NULL) {
|
|
|
+ if (_current_frame_buffer != NULL) {
|
|
|
+ do_recycle_frame(_current_frame_buffer);
|
|
|
+ }
|
|
|
+ _current_frame_buffer = frame;
|
|
|
+ if (ffmpeg_cat.is_debug()) {
|
|
|
+ ffmpeg_cat.debug()
|
|
|
+ << "ffmpeg for " << _filename.get_basename()
|
|
|
+ << " at frame " << _current_frame << ", returning frame at "
|
|
|
+ << frame->_begin_frame << "\n";
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (ffmpeg_cat.is_debug()) {
|
|
|
+ ffmpeg_cat.debug()
|
|
|
+ << "ffmpeg for " << _filename.get_basename()
|
|
|
+ << " at frame " << _current_frame << ", returning NULL\n";
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return frame.p();
|
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
|
@@ -403,8 +463,18 @@ fetch_buffer(double time) {
|
|
|
////////////////////////////////////////////////////////////////////
|
|
|
void FfmpegVideoCursor::
|
|
|
release_buffer(Buffer *buffer) {
|
|
|
- MutexHolder holder(_lock);
|
|
|
- do_recycle_frame(buffer);
|
|
|
+}
|
|
|
+
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+// Function: FfmpegVideoCursor::make_new_buffer
|
|
|
+// Access: Protected, Virtual
|
|
|
+// Description: May be called by a derived class to allocate a new
|
|
|
+// Buffer object.
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+PT(MovieVideoCursor::Buffer) FfmpegVideoCursor::
|
|
|
+make_new_buffer() {
|
|
|
+ PT(FfmpegBuffer) frame = new FfmpegBuffer(size_x() * size_y() * get_num_components());
|
|
|
+ return frame.p();
|
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
|
@@ -424,7 +494,7 @@ open_stream() {
|
|
|
if (!_source->get_subfile_info().is_empty()) {
|
|
|
// Read a subfile.
|
|
|
if (!_ffvfile.open_subfile(_source->get_subfile_info())) {
|
|
|
- movies_cat.info()
|
|
|
+ ffmpeg_cat.info()
|
|
|
<< "Couldn't open " << _source->get_subfile_info() << "\n";
|
|
|
close_stream();
|
|
|
return false;
|
|
|
@@ -433,7 +503,7 @@ open_stream() {
|
|
|
} else {
|
|
|
// Read a filename.
|
|
|
if (!_ffvfile.open_vfs(_filename)) {
|
|
|
- movies_cat.info()
|
|
|
+ ffmpeg_cat.info()
|
|
|
<< "Couldn't open " << _filename << "\n";
|
|
|
close_stream();
|
|
|
return false;
|
|
|
@@ -445,7 +515,7 @@ open_stream() {
|
|
|
nassertr(_format_ctx != NULL, false);
|
|
|
|
|
|
if (av_find_stream_info(_format_ctx) < 0) {
|
|
|
- movies_cat.info()
|
|
|
+ ffmpeg_cat.info()
|
|
|
<< "Couldn't find stream info\n";
|
|
|
close_stream();
|
|
|
return false;
|
|
|
@@ -458,11 +528,12 @@ open_stream() {
|
|
|
_video_index = i;
|
|
|
_video_ctx = _format_ctx->streams[i]->codec;
|
|
|
_video_timebase = av_q2d(_format_ctx->streams[i]->time_base);
|
|
|
+ _min_fseek = (int)(3.0 / _video_timebase);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (_video_ctx == NULL) {
|
|
|
- movies_cat.info()
|
|
|
+ ffmpeg_cat.info()
|
|
|
<< "Couldn't find video_ctx\n";
|
|
|
close_stream();
|
|
|
return false;
|
|
|
@@ -470,13 +541,13 @@ open_stream() {
|
|
|
|
|
|
AVCodec *pVideoCodec = avcodec_find_decoder(_video_ctx->codec_id);
|
|
|
if (pVideoCodec == NULL) {
|
|
|
- movies_cat.info()
|
|
|
+ ffmpeg_cat.info()
|
|
|
<< "Couldn't find codec\n";
|
|
|
close_stream();
|
|
|
return false;
|
|
|
}
|
|
|
if (avcodec_open(_video_ctx, pVideoCodec) < 0) {
|
|
|
- movies_cat.info()
|
|
|
+ ffmpeg_cat.info()
|
|
|
<< "Couldn't open codec\n";
|
|
|
close_stream();
|
|
|
return false;
|
|
|
@@ -485,10 +556,9 @@ open_stream() {
|
|
|
_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;
|
|
|
+ _length = (double)_format_ctx->duration / (double)AV_TIME_BASE;
|
|
|
_can_seek = true;
|
|
|
_can_seek_fast = true;
|
|
|
- _eof_reached = false;
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
@@ -581,8 +651,8 @@ st_thread_main(void *self) {
|
|
|
void FfmpegVideoCursor::
|
|
|
thread_main() {
|
|
|
MutexHolder holder(_lock);
|
|
|
- if (ffmpeg_cat.is_debug()) {
|
|
|
- ffmpeg_cat.debug()
|
|
|
+ if (ffmpeg_cat.is_spam()) {
|
|
|
+ ffmpeg_cat.spam()
|
|
|
<< "ffmpeg thread for " << _filename.get_basename() << " starting.\n";
|
|
|
}
|
|
|
|
|
|
@@ -600,8 +670,8 @@ thread_main() {
|
|
|
}
|
|
|
|
|
|
_thread_status = TS_stopped;
|
|
|
- if (ffmpeg_cat.is_debug()) {
|
|
|
- ffmpeg_cat.debug()
|
|
|
+ if (ffmpeg_cat.is_spam()) {
|
|
|
+ ffmpeg_cat.spam()
|
|
|
<< "ffmpeg thread for " << _filename.get_basename() << " stopped.\n";
|
|
|
}
|
|
|
}
|
|
|
@@ -630,7 +700,7 @@ do_poll() {
|
|
|
case TS_readahead:
|
|
|
if ((int)_readahead_frames.size() < _max_readahead_frames) {
|
|
|
// Time to read the next frame.
|
|
|
- Buffer *frame = do_alloc_frame();
|
|
|
+ PT(FfmpegBuffer) frame = do_alloc_frame();
|
|
|
nassertr(frame != NULL, false);
|
|
|
_lock.release();
|
|
|
fetch_frame(-1);
|
|
|
@@ -650,14 +720,14 @@ do_poll() {
|
|
|
return false;
|
|
|
|
|
|
case TS_seek:
|
|
|
- // Seek to a specific time.
|
|
|
+ // Seek to a specific frame.
|
|
|
{
|
|
|
- double seek_time = _seek_time;
|
|
|
+ int seek_frame = _seek_frame;
|
|
|
_thread_status = TS_seeking;
|
|
|
- Buffer *frame = do_alloc_frame();
|
|
|
+ PT(FfmpegBuffer) frame = do_alloc_frame();
|
|
|
nassertr(frame != NULL, false);
|
|
|
_lock.release();
|
|
|
- fetch_time(seek_time);
|
|
|
+ advance_to_frame(seek_frame);
|
|
|
if (_frame_ready) {
|
|
|
export_frame(frame);
|
|
|
_lock.acquire();
|
|
|
@@ -691,14 +761,15 @@ do_poll() {
|
|
|
// previously-recycled object. Assumes the lock is
|
|
|
// held.
|
|
|
////////////////////////////////////////////////////////////////////
|
|
|
-MovieVideoCursor::Buffer *FfmpegVideoCursor::
|
|
|
+PT(FfmpegVideoCursor::FfmpegBuffer) FfmpegVideoCursor::
|
|
|
do_alloc_frame() {
|
|
|
if (!_recycled_frames.empty()) {
|
|
|
- Buffer *frame = _recycled_frames.front();
|
|
|
+ PT(FfmpegBuffer) frame = _recycled_frames.front();
|
|
|
_recycled_frames.pop_front();
|
|
|
return frame;
|
|
|
}
|
|
|
- return internal_alloc_buffer();
|
|
|
+ PT(Buffer) buffer = make_new_buffer();
|
|
|
+ return (FfmpegBuffer *)buffer.p();
|
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
|
@@ -708,7 +779,7 @@ do_alloc_frame() {
|
|
|
// future reuse. Assumes the lock is held.
|
|
|
////////////////////////////////////////////////////////////////////
|
|
|
void FfmpegVideoCursor::
|
|
|
-do_recycle_frame(Buffer *frame) {
|
|
|
+do_recycle_frame(FfmpegBuffer *frame) {
|
|
|
_recycled_frames.push_back(frame);
|
|
|
}
|
|
|
|
|
|
@@ -721,12 +792,12 @@ do_recycle_frame(Buffer *frame) {
|
|
|
void FfmpegVideoCursor::
|
|
|
do_recycle_all_frames() {
|
|
|
while (!_readahead_frames.empty()) {
|
|
|
- Buffer *frame = _readahead_frames.front();
|
|
|
+ PT(FfmpegBuffer) frame = _readahead_frames.front();
|
|
|
_readahead_frames.pop_front();
|
|
|
- if (ffmpeg_cat.is_debug()) {
|
|
|
- ffmpeg_cat.debug()
|
|
|
+ if (ffmpeg_cat.is_spam()) {
|
|
|
+ ffmpeg_cat.spam()
|
|
|
<< "ffmpeg for " << _filename.get_basename()
|
|
|
- << " recycling frame at " << frame->_begin_time << "\n";
|
|
|
+ << " recycling frame at " << frame->_begin_frame << "\n";
|
|
|
}
|
|
|
_recycled_frames.push_back(frame);
|
|
|
}
|
|
|
@@ -736,32 +807,42 @@ do_recycle_all_frames() {
|
|
|
// Function: FfmpegVideoCursor::fetch_packet
|
|
|
// Access: Private
|
|
|
// Description: Called within the sub-thread. Fetches a video packet
|
|
|
-// and stores it in the packet0 buffer. Sets packet_time
|
|
|
+// and stores it in the packet0 buffer. Sets packet_frame
|
|
|
// to the packet's timestamp. If a packet could not be
|
|
|
-// read, the packet is cleared and the packet_time is
|
|
|
+// read, the packet is cleared and the packet_frame is
|
|
|
// set to the specified default value. Returns true on
|
|
|
// failure (such as the end of the video), or false on
|
|
|
// success.
|
|
|
////////////////////////////////////////////////////////////////////
|
|
|
bool FfmpegVideoCursor::
|
|
|
-fetch_packet(double default_time) {
|
|
|
+fetch_packet(int default_frame) {
|
|
|
if (_packet0->data) {
|
|
|
av_free_packet(_packet0);
|
|
|
}
|
|
|
while (av_read_frame(_format_ctx, _packet0) >= 0) {
|
|
|
if (_packet0->stream_index == _video_index) {
|
|
|
- _packet_time = _packet0->dts * _video_timebase;
|
|
|
+ _packet_frame = _packet0->dts;
|
|
|
return false;
|
|
|
}
|
|
|
av_free_packet(_packet0);
|
|
|
}
|
|
|
_packet0->data = 0;
|
|
|
- _packet_time = default_time;
|
|
|
- _eof_reached = false;
|
|
|
- if (ffmpeg_cat.is_debug()) {
|
|
|
- ffmpeg_cat.debug()
|
|
|
- << "end of video\n";
|
|
|
+
|
|
|
+ if (!_eof_known && default_frame != 0) {
|
|
|
+ _eof_frame = _packet_frame;
|
|
|
+ _eof_known = true;
|
|
|
}
|
|
|
+
|
|
|
+ if (ffmpeg_cat.is_spam()) {
|
|
|
+ if (_eof_known) {
|
|
|
+ ffmpeg_cat.spam()
|
|
|
+ << "end of video at frame " << _eof_frame << "\n";
|
|
|
+ } else {
|
|
|
+ ffmpeg_cat.spam()
|
|
|
+ << "end of video\n";
|
|
|
+ }
|
|
|
+ }
|
|
|
+ _packet_frame = default_frame;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
@@ -781,33 +862,35 @@ flip_packets() {
|
|
|
// Function: FfmpegVideoCursor::fetch_frame
|
|
|
// Access: Private
|
|
|
// Description: Called within the sub-thread. Slides forward until
|
|
|
-// the indicated time, then fetches a frame from the
|
|
|
+// the indicated frame, then fetches a frame from the
|
|
|
// stream and stores it in the frame buffer. Sets
|
|
|
-// _begin_time and _end_time to indicate the extents of
|
|
|
-// the frame. Returns true if the end of the video is
|
|
|
-// reached.
|
|
|
+// _begin_frame and _end_frame to indicate the extents of
|
|
|
+// the frame. Sets _frame_ready true to indicate a
|
|
|
+// frame is now available, or false if it is not (for
|
|
|
+// instance, because the end of the video was reached).
|
|
|
////////////////////////////////////////////////////////////////////
|
|
|
-bool FfmpegVideoCursor::
|
|
|
-fetch_frame(double time) {
|
|
|
+void FfmpegVideoCursor::
|
|
|
+fetch_frame(int frame) {
|
|
|
static PStatCollector fetch_buffer_pcollector("*:FFMPEG Video Decoding:Fetch");
|
|
|
PStatTimer timer(fetch_buffer_pcollector);
|
|
|
|
|
|
int finished = 0;
|
|
|
|
|
|
- if (_packet_time <= time) {
|
|
|
+ if (_packet_frame <= frame) {
|
|
|
_video_ctx->skip_frame = AVDISCARD_BIDIR;
|
|
|
// Put the current packet aside in case we discover it's the
|
|
|
// packet to keep.
|
|
|
flip_packets();
|
|
|
|
|
|
- // Get the next packet. The first packet beyond the time we're
|
|
|
+ // Get the next packet. The first packet beyond the frame we're
|
|
|
// looking for marks the point to stop.
|
|
|
- _begin_time = _packet_time;
|
|
|
- if (fetch_packet(time)) {
|
|
|
+ _begin_frame = _packet_frame;
|
|
|
+ if (fetch_packet(frame)) {
|
|
|
+ _end_frame = _packet_frame;
|
|
|
_frame_ready = false;
|
|
|
- return true;
|
|
|
+ return;
|
|
|
}
|
|
|
- while (_packet_time <= time) {
|
|
|
+ while (_packet_frame <= frame) {
|
|
|
static PStatCollector seek_pcollector("*:FFMPEG Video Decoding:Seek");
|
|
|
PStatTimer timer(seek_pcollector);
|
|
|
|
|
|
@@ -819,10 +902,11 @@ fetch_frame(double time) {
|
|
|
avcodec_decode_video2(_video_ctx, _frame, &finished, _packet1);
|
|
|
#endif
|
|
|
flip_packets();
|
|
|
- _begin_time = _packet_time;
|
|
|
- if (fetch_packet(time)) {
|
|
|
+ _begin_frame = _packet_frame;
|
|
|
+ if (fetch_packet(frame)) {
|
|
|
+ _end_frame = _packet_frame;
|
|
|
_frame_ready = false;
|
|
|
- return true;
|
|
|
+ return;
|
|
|
}
|
|
|
}
|
|
|
_video_ctx->skip_frame = AVDISCARD_DEFAULT;
|
|
|
@@ -839,7 +923,6 @@ 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
|
|
|
@@ -848,28 +931,28 @@ fetch_frame(double time) {
|
|
|
#else
|
|
|
avcodec_decode_video2(_video_ctx, _frame, &finished, _packet0);
|
|
|
#endif
|
|
|
- fetch_packet(_begin_time + 1.0);
|
|
|
+ _begin_frame = _packet_frame;
|
|
|
+ fetch_packet(_begin_frame + 1);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- _end_time = _packet_time;
|
|
|
+ _end_frame = _packet_frame;
|
|
|
_frame_ready = true;
|
|
|
- return false;
|
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
|
// Function: FfmpegVideoCursor::seek
|
|
|
// Access: Private
|
|
|
// Description: Called within the sub-thread. Seeks to a target
|
|
|
-// location. Afterward, the packet_time is guaranteed
|
|
|
-// to be less than or equal to the specified time.
|
|
|
+// location. Afterward, the packet_frame is guaranteed
|
|
|
+// to be less than or equal to the specified frame.
|
|
|
////////////////////////////////////////////////////////////////////
|
|
|
void FfmpegVideoCursor::
|
|
|
-seek(double t, bool backward) {
|
|
|
+seek(int frame, bool backward) {
|
|
|
static PStatCollector seek_pcollector("*:FFMPEG Video Decoding:Seek");
|
|
|
PStatTimer timer(seek_pcollector);
|
|
|
|
|
|
- PN_int64 target_ts = (PN_int64)(t / _video_timebase);
|
|
|
+ PN_int64 target_ts = (PN_int64)frame;
|
|
|
if (target_ts < (PN_int64)(_initial_dts)) {
|
|
|
// Attempts to seek before the first packet will fail.
|
|
|
target_ts = _initial_dts;
|
|
|
@@ -880,15 +963,26 @@ seek(double t, bool backward) {
|
|
|
//reset_stream();
|
|
|
}
|
|
|
|
|
|
-
|
|
|
if (av_seek_frame(_format_ctx, _video_index, target_ts, flags) < 0) {
|
|
|
- if (ffmpeg_cat.is_debug()) {
|
|
|
- ffmpeg_cat.debug()
|
|
|
+ if (ffmpeg_cat.is_spam()) {
|
|
|
+ ffmpeg_cat.spam()
|
|
|
<< "Seek failure.\n";
|
|
|
}
|
|
|
- reset_stream();
|
|
|
-
|
|
|
- av_seek_frame(_format_ctx, _video_index, target_ts, 0);
|
|
|
+
|
|
|
+ if (backward) {
|
|
|
+ // Now try to seek forward.
|
|
|
+ reset_stream();
|
|
|
+ return seek(frame, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Try a binary search to get a little closer.
|
|
|
+ if (binary_seek(_initial_dts, frame, frame, 1) < 0) {
|
|
|
+ if (ffmpeg_cat.is_spam()) {
|
|
|
+ ffmpeg_cat.spam()
|
|
|
+ << "Seek double failure.\n";
|
|
|
+ }
|
|
|
+ reset_stream();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
{
|
|
|
@@ -918,6 +1012,35 @@ seek(double t, bool backward) {
|
|
|
fetch_frame(-1);
|
|
|
}
|
|
|
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+// Function: FfmpegVideoCursor::binary_seek
|
|
|
+// Access: Private
|
|
|
+// Description: Casts about within the stream for a reasonably-close
|
|
|
+// frame to seek to. We're trying to get as close as
|
|
|
+// possible to target_frame.
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
+int FfmpegVideoCursor::
|
|
|
+binary_seek(int min_frame, int max_frame, int target_frame, int num_iterations) {
|
|
|
+ int try_frame = (min_frame + max_frame) / 2;
|
|
|
+ if (num_iterations > 5 || try_frame >= max_frame) {
|
|
|
+ // Success.
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (av_seek_frame(_format_ctx, _video_index, try_frame, AVSEEK_FLAG_BACKWARD) < 0) {
|
|
|
+ // Failure. Try lower.
|
|
|
+ if (binary_seek(min_frame, try_frame - 1, target_frame, num_iterations + 1) < 0) {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // Success. Try higher.
|
|
|
+ if (binary_seek(try_frame + 1, max_frame, target_frame, num_iterations + 1) < 0) {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
////////////////////////////////////////////////////////////////////
|
|
|
// Function: FfmpegVideoCursor::reset_stream
|
|
|
// Access: Private
|
|
|
@@ -926,8 +1049,8 @@ seek(double t, bool backward) {
|
|
|
////////////////////////////////////////////////////////////////////
|
|
|
void FfmpegVideoCursor::
|
|
|
reset_stream() {
|
|
|
- if (ffmpeg_cat.is_debug()) {
|
|
|
- ffmpeg_cat.debug()
|
|
|
+ if (ffmpeg_cat.is_spam()) {
|
|
|
+ ffmpeg_cat.spam()
|
|
|
<< "Resetting ffmpeg stream.\n";
|
|
|
}
|
|
|
|
|
|
@@ -939,77 +1062,65 @@ reset_stream() {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- fetch_packet(0.0);
|
|
|
+ fetch_packet(0);
|
|
|
_initial_dts = _packet0->dts;
|
|
|
fetch_frame(-1);
|
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
|
-// Function: FfmpegVideoCursor::fetch_time
|
|
|
+// Function: FfmpegVideoCursor::advance_to_frame
|
|
|
// Access: Private
|
|
|
// Description: Called within the sub-thread. Advance until the
|
|
|
-// specified time is in the export buffer.
|
|
|
+// specified frame is in the export buffer.
|
|
|
////////////////////////////////////////////////////////////////////
|
|
|
void FfmpegVideoCursor::
|
|
|
-fetch_time(double time) {
|
|
|
+advance_to_frame(int frame) {
|
|
|
static PStatCollector fetch_buffer_pcollector("*:FFMPEG Video Decoding:Fetch");
|
|
|
PStatTimer timer(fetch_buffer_pcollector);
|
|
|
|
|
|
- if (time < _begin_time) {
|
|
|
- // Time is in the past.
|
|
|
- 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";
|
|
|
+ if (frame < _begin_frame) {
|
|
|
+ // Frame is in the past.
|
|
|
+ if (ffmpeg_cat.is_spam()) {
|
|
|
+ ffmpeg_cat.spam()
|
|
|
+ << "Seeking backward to " << frame << " from " << _begin_frame << "\n";
|
|
|
+ }
|
|
|
+ seek(frame, true);
|
|
|
+ if (_begin_frame > frame) {
|
|
|
+ if (ffmpeg_cat.is_spam()) {
|
|
|
+ ffmpeg_cat.spam()
|
|
|
+ << "Ended up at " << _begin_frame << ", not far enough back!\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";
|
|
|
- }
|
|
|
+ if (ffmpeg_cat.is_spam()) {
|
|
|
+ ffmpeg_cat.spam()
|
|
|
+ << "Reseek to 0, got " << _begin_frame << "\n";
|
|
|
}
|
|
|
}
|
|
|
- if (time < _begin_time) {
|
|
|
- if (ffmpeg_cat.is_debug()) {
|
|
|
- ffmpeg_cat.debug()
|
|
|
- << "Now sliding forward to " << time << " from " << _begin_time << "\n";
|
|
|
+ if (frame > _end_frame) {
|
|
|
+ if (ffmpeg_cat.is_spam()) {
|
|
|
+ ffmpeg_cat.spam()
|
|
|
+ << "Now sliding forward to " << frame << " from " << _begin_frame << "\n";
|
|
|
}
|
|
|
- fetch_frame(time);
|
|
|
+ fetch_frame(frame);
|
|
|
}
|
|
|
|
|
|
- } else if (time < _end_time) {
|
|
|
- // Time is in the present: already have the frame.
|
|
|
- if (ffmpeg_cat.is_debug()) {
|
|
|
- ffmpeg_cat.debug()
|
|
|
- << "Currently have " << time << "\n";
|
|
|
+ } else if (frame < _end_frame) {
|
|
|
+ // Frame is in the present: already have the frame.
|
|
|
+ if (ffmpeg_cat.is_spam()) {
|
|
|
+ ffmpeg_cat.spam()
|
|
|
+ << "Currently have " << frame << " within " << _begin_frame << " .. " << _end_frame << "\n";
|
|
|
}
|
|
|
|
|
|
- } else if (time < _end_time + _min_fseek) {
|
|
|
- // Time is in the near future.
|
|
|
- if (ffmpeg_cat.is_debug()) {
|
|
|
- ffmpeg_cat.debug()
|
|
|
- << "Sliding forward to " << time << " from " << _begin_time << "\n";
|
|
|
+ } else if (frame < _end_frame + _min_fseek) {
|
|
|
+ // Frame is in the near future.
|
|
|
+ if (ffmpeg_cat.is_spam()) {
|
|
|
+ ffmpeg_cat.spam()
|
|
|
+ << "Sliding forward to " << frame << " from " << _begin_frame << "\n";
|
|
|
}
|
|
|
- fetch_frame(time);
|
|
|
+ fetch_frame(frame);
|
|
|
|
|
|
} else {
|
|
|
- // Time is in the far future. Seek forward, then read.
|
|
|
+ // Frame is in the far future. Seek forward, then read.
|
|
|
// There's a danger here: because keyframes are spaced
|
|
|
// unpredictably, trying to seek forward could actually
|
|
|
// move us backward in the stream! This must be avoided.
|
|
|
@@ -1017,31 +1128,31 @@ fetch_time(double time) {
|
|
|
// us backward, we increase the minimum threshold distance
|
|
|
// for forward-seeking in the future.
|
|
|
|
|
|
- if (ffmpeg_cat.is_debug()) {
|
|
|
- ffmpeg_cat.debug()
|
|
|
- << "Jumping forward to " << time << " from " << _begin_time << "\n";
|
|
|
+ if (ffmpeg_cat.is_spam()) {
|
|
|
+ ffmpeg_cat.spam()
|
|
|
+ << "Jumping forward to " << frame << " from " << _begin_frame << "\n";
|
|
|
}
|
|
|
- double base = _begin_time;
|
|
|
- seek(time, false);
|
|
|
- if (_begin_time < base) {
|
|
|
- _min_fseek += (base - _begin_time);
|
|
|
- if (ffmpeg_cat.is_debug()) {
|
|
|
- ffmpeg_cat.debug()
|
|
|
+ int base = _begin_frame;
|
|
|
+ seek(frame, false);
|
|
|
+ if (_begin_frame < base) {
|
|
|
+ _min_fseek += (base - _begin_frame);
|
|
|
+ if (ffmpeg_cat.is_spam()) {
|
|
|
+ ffmpeg_cat.spam()
|
|
|
<< "Wrong way! Increasing _min_fseek to " << _min_fseek << "\n";
|
|
|
}
|
|
|
}
|
|
|
- if (time < _begin_time) {
|
|
|
- if (ffmpeg_cat.is_debug()) {
|
|
|
- ffmpeg_cat.debug()
|
|
|
- << "Correcting, sliding forward to " << time << " from " << _begin_time << "\n";
|
|
|
+ if (frame > _end_frame) {
|
|
|
+ if (ffmpeg_cat.is_spam()) {
|
|
|
+ ffmpeg_cat.spam()
|
|
|
+ << "Correcting, sliding forward to " << frame << " from " << _begin_frame << "\n";
|
|
|
}
|
|
|
- fetch_frame(time);
|
|
|
+ fetch_frame(frame);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- if (ffmpeg_cat.is_debug()) {
|
|
|
- ffmpeg_cat.debug()
|
|
|
- << "Wanted " << time << ", got " << _begin_time << "\n";
|
|
|
+ if (ffmpeg_cat.is_spam()) {
|
|
|
+ ffmpeg_cat.spam()
|
|
|
+ << "Wanted " << frame << ", got " << _begin_frame << "\n";
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -1052,14 +1163,14 @@ fetch_time(double time) {
|
|
|
// of the frame buffer into the indicated target buffer.
|
|
|
////////////////////////////////////////////////////////////////////
|
|
|
void FfmpegVideoCursor::
|
|
|
-export_frame(MovieVideoCursor::Buffer *buffer) {
|
|
|
+export_frame(FfmpegBuffer *buffer) {
|
|
|
static PStatCollector export_frame_collector("*:FFMPEG Convert Video to BGR");
|
|
|
PStatTimer timer(export_frame_collector);
|
|
|
|
|
|
if (!_frame_ready) {
|
|
|
// No frame data ready, just fill with black.
|
|
|
- if (ffmpeg_cat.is_debug()) {
|
|
|
- ffmpeg_cat.debug()
|
|
|
+ if (ffmpeg_cat.is_spam()) {
|
|
|
+ ffmpeg_cat.spam()
|
|
|
<< "ffmpeg for " << _filename.get_basename()
|
|
|
<< ", no frame available.\n";
|
|
|
}
|
|
|
@@ -1069,8 +1180,8 @@ export_frame(MovieVideoCursor::Buffer *buffer) {
|
|
|
|
|
|
_frame_out->data[0] = buffer->_block + ((_size_y - 1) * _size_x * 3);
|
|
|
_frame_out->linesize[0] = _size_x * -3;
|
|
|
- buffer->_begin_time = _begin_time;
|
|
|
- buffer->_end_time = _end_time;
|
|
|
+ buffer->_begin_frame = _begin_frame;
|
|
|
+ buffer->_end_frame = _end_frame;
|
|
|
#ifdef HAVE_SWSCALE
|
|
|
nassertv(_convert_ctx != NULL && _frame != NULL && _frame_out != NULL);
|
|
|
sws_scale(_convert_ctx, _frame->data, _frame->linesize, 0, _size_y, _frame_out->data, _frame_out->linesize);
|