| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272 |
- /**
- * PANDA 3D SOFTWARE
- * Copyright (c) Carnegie Mellon University. All rights reserved.
- *
- * All use of this software is subject to the terms of the revised BSD
- * license. You should have received a copy of this license along
- * with this source code in a file named "LICENSE."
- *
- * @file ffmpegVideoCursor.cxx
- * @author jyelon
- * @date 2007-08-01
- */
- #include "ffmpegVideoCursor.h"
- #include "config_ffmpeg.h"
- #include "pStatCollector.h"
- #include "pStatTimer.h"
- #include "mutexHolder.h"
- #include "reMutexHolder.h"
- #include "ffmpegVideo.h"
- #include "bamReader.h"
- extern "C" {
- #include "libavcodec/avcodec.h"
- #include "libavformat/avformat.h"
- #include "libavutil/pixdesc.h"
- #ifdef HAVE_SWSCALE
- #include "libswscale/swscale.h"
- #endif
- }
- ReMutex FfmpegVideoCursor::_av_lock;
- TypeHandle FfmpegVideoCursor::_type_handle;
- TypeHandle FfmpegVideoCursor::FfmpegBuffer::_type_handle;
- PStatCollector FfmpegVideoCursor::_fetch_buffer_pcollector("*:FFMPEG Video Decoding:Fetch");
- PStatCollector FfmpegVideoCursor::_seek_pcollector("*:FFMPEG Video Decoding:Seek");
- PStatCollector FfmpegVideoCursor::_export_frame_pcollector("*:FFMPEG Convert Video to BGR");
- #if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(52, 32, 100)
- #define AV_PIX_FMT_FLAG_ALPHA PIX_FMT_ALPHA
- #endif
- /**
- * This constructor is only used when reading from a bam file.
- */
- FfmpegVideoCursor::
- FfmpegVideoCursor() :
- _max_readahead_frames(0),
- _thread_priority(ffmpeg_thread_priority),
- _lock("FfmpegVideoCursor::_lock"),
- _action_cvar(_lock),
- _thread_status(TS_stopped),
- _seek_frame(0),
- _packet(nullptr),
- _format_ctx(nullptr),
- _video_ctx(nullptr),
- _convert_ctx(nullptr),
- _pixel_format((int)AV_PIX_FMT_NONE),
- _video_index(-1),
- _frame(nullptr),
- _frame_out(nullptr),
- _eof_known(false)
- {
- }
- /**
- * Specifies the source of the video cursor. This is normally called only by
- * the constructor or when reading from a bam file.
- */
- void FfmpegVideoCursor::
- init_from(FfmpegVideo *source) {
- nassertv(_thread == nullptr && _thread_status == TS_stopped);
- nassertv(source != nullptr);
- _source = source;
- _filename = _source->get_filename();
- if (!open_stream()) {
- cleanup();
- return;
- }
- ReMutexHolder av_holder(_av_lock);
- #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 45, 101)
- _frame = av_frame_alloc();
- _frame_out = av_frame_alloc();
- #else
- _frame = avcodec_alloc_frame();
- _frame_out = avcodec_alloc_frame();
- #endif
- if ((_frame == nullptr)||(_frame_out == nullptr)) {
- cleanup();
- return;
- }
- #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 12, 100)
- _packet = av_packet_alloc();
- #else
- _packet = new AVPacket;
- av_init_packet(_packet);
- #endif
- fetch_packet(0);
- fetch_frame(-1);
- _initial_dts = _begin_frame;
- _current_frame = -1;
- _eof_known = false;
- _eof_frame = 0;
- // Check if we got an alpha format. Please note that some video codecs
- // (eg. libvpx) change the pix_fmt after decoding the first frame, which is
- // why we didn't do this earlier.
- switch (_video_ctx->pix_fmt) {
- case AV_PIX_FMT_GRAY8:
- _num_components = 1;
- _pixel_format = (int)AV_PIX_FMT_GRAY8;
- break;
- case AV_PIX_FMT_YA8:
- _num_components = 2;
- _pixel_format = (int)AV_PIX_FMT_YA8;
- break;
- default:
- const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(_video_ctx->pix_fmt);
- if (desc && (desc->flags & AV_PIX_FMT_FLAG_ALPHA) != 0) {
- _num_components = 4;
- _pixel_format = (int)AV_PIX_FMT_BGRA;
- } else {
- _num_components = 3;
- _pixel_format = (int)AV_PIX_FMT_BGR24;
- }
- break;
- }
- #ifdef HAVE_SWSCALE
- nassertv(_convert_ctx == nullptr);
- _convert_ctx = sws_getContext(_size_x, _size_y, _video_ctx->pix_fmt,
- _size_x, _size_y, (AVPixelFormat)_pixel_format,
- SWS_BILINEAR | SWS_PRINT_INFO, nullptr, nullptr, nullptr);
- #endif // HAVE_SWSCALE
- #ifdef HAVE_THREADS
- set_max_readahead_frames(ffmpeg_max_readahead_frames);
- #endif // HAVE_THREADS
- }
- /**
- *
- */
- FfmpegVideoCursor::
- FfmpegVideoCursor(FfmpegVideo *src) :
- _max_readahead_frames(0),
- _thread_priority(ffmpeg_thread_priority),
- _lock("FfmpegVideoCursor::_lock"),
- _action_cvar(_lock),
- _thread_status(TS_stopped),
- _seek_frame(0),
- _packet(nullptr),
- _format_ctx(nullptr),
- _video_ctx(nullptr),
- _convert_ctx(nullptr),
- _video_index(-1),
- _frame(nullptr),
- _frame_out(nullptr),
- _eof_known(false)
- {
- init_from(src);
- }
- /**
- *
- */
- FfmpegVideoCursor::
- ~FfmpegVideoCursor() {
- cleanup();
- }
- /**
- * Specifies the maximum number of frames that a sub-thread will attempt to
- * read ahead of the current frame. Setting this to a nonzero allows the
- * video decoding to take place in a sub-thread, which smoothes out the video
- * decoding time by spreading it evenly over several frames. Set this number
- * larger to increase the buffer between the currently visible frame and the
- * first undecoded frame; set it smaller to reduce memory consumption.
- *
- * Setting this to zero forces the video to be decoded in the main thread. If
- * threading is not available in the Panda build, this value is always zero.
- */
- void FfmpegVideoCursor::
- set_max_readahead_frames(int max_readahead_frames) {
- #ifndef HAVE_THREADS
- if (max_readahead_frames > 0) {
- ffmpeg_cat.warning()
- << "Couldn't set max_readahead_frames to " << max_readahead_frames
- << ": threading not available.\n";
- max_readahead_frames = 0;
- }
- #endif // HAVE_THREADS
- _max_readahead_frames = max_readahead_frames;
- if (_max_readahead_frames > 0) {
- if (_thread_status == TS_stopped) {
- start_thread();
- }
- } else {
- if (_thread_status != TS_stopped) {
- stop_thread();
- }
- }
- }
- /**
- * Returns the maximum number of frames that a sub-thread will attempt to read
- * ahead of the current frame. See set_max_readahead_frames().
- */
- int FfmpegVideoCursor::
- get_max_readahead_frames() const {
- return _max_readahead_frames;
- }
- /**
- * Changes the thread priority of the thread that decodes the ffmpeg video
- * stream (if max_readahead_frames is nonzero). Normally you shouldn't mess
- * with this, but there may be special cases where a precise balance of CPU
- * utilization between the main thread and the various ffmpeg service threads
- * may be needed.
- */
- void FfmpegVideoCursor::
- set_thread_priority(ThreadPriority thread_priority) {
- if (_thread_priority != thread_priority) {
- _thread_priority = thread_priority;
- if (is_thread_started()) {
- stop_thread();
- start_thread();
- }
- }
- }
- /**
- * Returns the current thread priority of the thread that decodes the ffmpeg
- * video stream (if max_readahead_frames is nonzero). See
- * set_thread_priority().
- */
- ThreadPriority FfmpegVideoCursor::
- get_thread_priority() const {
- return _thread_priority;
- }
- /**
- * Explicitly starts the ffmpeg decoding thread after it has been stopped by a
- * call to stop_thread(). The thread is normally started automatically, so
- * there is no need to call this method unless you have previously called
- * stop_thread() for some reason.
- */
- void FfmpegVideoCursor::
- start_thread() {
- MutexHolder holder(_lock);
- if (_thread_status == TS_stopped && _max_readahead_frames > 0) {
- // Get a unique name for the thread's sync name.
- ostringstream strm;
- strm << (void *)this;
- _sync_name = strm.str();
- // Create and start the thread object.
- _thread_status = TS_wait;
- _thread = new GenericThread(_filename.get_basename(), _sync_name, st_thread_main, this);
- if (!_thread->start(_thread_priority, true)) {
- // Couldn't start the thread.
- _thread = nullptr;
- _thread_status = TS_stopped;
- }
- }
- }
- /**
- * Explicitly stops the ffmpeg decoding thread. There is normally no reason
- * to do this unless you want to maintain precise control over what threads
- * are consuming CPU resources. Calling this method will make the video
- * update in the main thread, regardless of the setting of
- * max_readahead_frames, until you call start_thread() again.
- */
- void FfmpegVideoCursor::
- stop_thread() {
- if (_thread_status != TS_stopped) {
- PT(GenericThread) thread = _thread;
- {
- MutexHolder holder(_lock);
- if (_thread_status != TS_stopped) {
- _thread_status = TS_shutdown;
- }
- _action_cvar.notify();
- _thread = nullptr;
- }
- // Now that we've released the lock, we can join the thread.
- thread->join();
- }
- // This is a good time to clean up all of the allocated frame objects. It's
- // not really necessary to be holding the lock, since the thread is gone,
- // but we'll grab it anyway just in case someone else starts the thread up
- // again.
- MutexHolder holder(_lock);
- _readahead_frames.clear();
- }
- /**
- * Returns true if the thread has been started, false if not. This will
- * always return false if max_readahead_frames is 0.
- */
- bool FfmpegVideoCursor::
- is_thread_started() const {
- return (_thread_status != TS_stopped);
- }
- /**
- * See MovieVideoCursor::set_time().
- */
- bool FfmpegVideoCursor::
- set_time(double timestamp, int loop_count) {
- int frame = (int)(timestamp / _video_timebase + 0.5);
- if (_eof_known) {
- if (loop_count == 0) {
- frame = frame % (_eof_frame + 1);
- } else {
- int last_frame = (_eof_frame + 1) * loop_count;
- if (frame < last_frame) {
- frame = frame % (_eof_frame + 1);
- } else {
- frame = _eof_frame;
- }
- }
- }
- // No point in trying to position before the first frame.
- frame = max(frame, _initial_dts);
- if (ffmpeg_cat.is_spam() && frame != _current_frame) {
- ffmpeg_cat.spam()
- << "set_time(" << timestamp << "): " << frame
- << ", loop_count = " << loop_count << "\n";
- }
- _current_frame = frame;
- if (_current_frame_buffer != nullptr) {
- // 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;
- }
- /**
- * See MovieVideoCursor::fetch_buffer.
- */
- PT(MovieVideoCursor::Buffer) FfmpegVideoCursor::
- fetch_buffer() {
- MutexHolder holder(_lock);
- // If there was an error at any point, just return NULL.
- if (_format_ctx == nullptr) {
- return nullptr;
- }
- PT(FfmpegBuffer) frame;
- if (_thread_status == TS_stopped) {
- // Non-threaded case. Just get the next frame directly.
- advance_to_frame(_current_frame);
- if (_frame_ready) {
- frame = do_alloc_frame();
- export_frame(frame);
- }
- } else {
- // Threaded case. Wait for the thread to serve up the required frames.
- if (!_readahead_frames.empty()) {
- frame = _readahead_frames.front();
- _readahead_frames.pop_front();
- _action_cvar.notify();
- 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 frame " << _current_frame << ", discarding frame at "
- << frame->_begin_frame << "\n";
- }
- frame = _readahead_frames.front();
- _readahead_frames.pop_front();
- }
- 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_clear_all_frames();
- if (_thread_status == TS_wait || _thread_status == TS_seek || _thread_status == TS_readahead) {
- _thread_status = TS_seek;
- _seek_frame = _current_frame;
- _action_cvar.notify();
- }
- }
- }
- if (frame == nullptr || 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_frame = _current_frame;
- _action_cvar.notify();
- }
- }
- }
- if (frame != nullptr) {
- bool too_old = (frame->_end_frame < _current_frame && !ffmpeg_show_seek_frames);
- bool too_new = frame->_begin_frame > _current_frame;
- if (too_old || too_new) {
- // The frame is too old or too new. Just recycle it.
- frame = nullptr;
- }
- }
- if (frame != nullptr) {
- _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();
- }
- /**
- * 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(), _video_timebase);
- return frame.p();
- }
- /**
- * 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())) {
- ffmpeg_cat.info()
- << "Couldn't open " << _source->get_subfile_info() << "\n";
- close_stream();
- return false;
- }
- } else {
- // Read a filename.
- if (!_ffvfile.open_vfs(_filename)) {
- ffmpeg_cat.info()
- << "Couldn't open " << _filename << "\n";
- close_stream();
- return false;
- }
- }
- nassertr(_format_ctx == nullptr, false);
- _format_ctx = _ffvfile.get_format_context();
- nassertr(_format_ctx != nullptr, false);
- if (avformat_find_stream_info(_format_ctx, nullptr) < 0) {
- ffmpeg_cat.info()
- << "Couldn't find stream info\n";
- close_stream();
- return false;
- }
- nassertr(_video_ctx == nullptr, false);
- // As of libavformat version 57.41.100, AVStream.codec is deprecated in favor
- // of AVStream.codecpar. Fortunately, the two structures have
- // similarly-named members, so we can just switch out the declaration.
- #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 41, 100)
- AVCodecParameters *codecpar;
- #else
- AVCodecContext *codecpar;
- #endif
- // Find the video stream
- AVStream *stream = nullptr;
- for (int i = 0; i < (int)_format_ctx->nb_streams; ++i) {
- #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 41, 100)
- codecpar = _format_ctx->streams[i]->codecpar;
- #else
- codecpar = _format_ctx->streams[i]->codec;
- #endif
- if (codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
- _video_index = i;
- stream = _format_ctx->streams[i];
- break;
- }
- }
- if (stream == nullptr) {
- ffmpeg_cat.info()
- << "Couldn't find stream\n";
- close_stream();
- return false;
- }
- _video_timebase = av_q2d(stream->time_base);
- _min_fseek = (int)(3.0 / _video_timebase);
- AVCodec *pVideoCodec = nullptr;
- if (ffmpeg_prefer_libvpx) {
- #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 0, 0)
- if (codecpar->codec_id == AV_CODEC_ID_VP9) {
- pVideoCodec = avcodec_find_decoder_by_name("libvpx-vp9");
- } else
- #endif
- if (codecpar->codec_id == AV_CODEC_ID_VP8) {
- pVideoCodec = avcodec_find_decoder_by_name("libvpx");
- }
- }
- if (pVideoCodec == nullptr) {
- pVideoCodec = avcodec_find_decoder(codecpar->codec_id);
- }
- if (pVideoCodec == nullptr) {
- ffmpeg_cat.info()
- << "Couldn't find codec\n";
- close_stream();
- return false;
- }
- _video_ctx = avcodec_alloc_context3(pVideoCodec);
- if (_video_ctx == nullptr) {
- ffmpeg_cat.info()
- << "Couldn't allocate _video_ctx\n";
- close_stream();
- return false;
- }
- #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 41, 100)
- avcodec_parameters_to_context(_video_ctx, codecpar);
- #else
- avcodec_copy_context(_video_ctx, codecpar);
- #endif
- if (avcodec_open2(_video_ctx, pVideoCodec, nullptr) < 0) {
- ffmpeg_cat.info()
- << "Couldn't open codec\n";
- close_stream();
- return false;
- }
- _size_x = _video_ctx->width;
- _size_y = _video_ctx->height;
- _num_components = 3;
- _length = (double)_format_ctx->duration / (double)AV_TIME_BASE;
- _can_seek = true;
- _can_seek_fast = true;
- return true;
- }
- /**
- * 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);
- #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 52, 0)
- avcodec_free_context(&_video_ctx);
- #else
- delete _video_ctx;
- #endif
- }
- _video_ctx = nullptr;
- _ffvfile.close();
- _format_ctx = nullptr;
- _video_index = -1;
- }
- /**
- * Reset to a standard inactive state.
- */
- void FfmpegVideoCursor::
- cleanup() {
- stop_thread();
- close_stream();
- ReMutexHolder av_holder(_av_lock);
- #ifdef HAVE_SWSCALE
- if (_convert_ctx != nullptr) {
- sws_freeContext(_convert_ctx);
- }
- _convert_ctx = nullptr;
- #endif // HAVE_SWSCALE
- if (_frame) {
- av_free(_frame);
- _frame = nullptr;
- }
- if (_frame_out) {
- _frame_out->data[0] = nullptr;
- av_free(_frame_out);
- _frame_out = nullptr;
- }
- if (_packet) {
- #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 12, 100)
- av_packet_free(&_packet);
- #else
- if (_packet->data) {
- av_free_packet(_packet);
- }
- delete _packet;
- _packet = nullptr;
- #endif
- }
- }
- /**
- * The thread main function, static version (for passing to GenericThread).
- */
- void FfmpegVideoCursor::
- st_thread_main(void *self) {
- ((FfmpegVideoCursor *)self)->thread_main();
- }
- /**
- * The thread main function.
- */
- void FfmpegVideoCursor::
- thread_main() {
- if (ffmpeg_cat.is_spam()) {
- ffmpeg_cat.spam()
- << "ffmpeg thread for " << _filename.get_basename() << " starting.\n";
- }
- // First, push the first frame onto the readahead queue.
- if (_frame_ready) {
- PT(FfmpegBuffer) frame = do_alloc_frame();
- export_frame(frame);
- MutexHolder holder(_lock);
- _readahead_frames.push_back(frame);
- }
- // Now repeatedly wait for something interesting to do, until we're told to
- // shut down.
- MutexHolder holder(_lock);
- while (_thread_status != TS_shutdown) {
- nassertv(_thread_status != TS_stopped);
- _action_cvar.wait();
- while (do_poll()) {
- // Keep doing stuff as long as there's something to do.
- _lock.release();
- PStatClient::thread_tick(_sync_name);
- Thread::consider_yield();
- _lock.acquire();
- }
- }
- _thread_status = TS_stopped;
- if (ffmpeg_cat.is_spam()) {
- ffmpeg_cat.spam()
- << "ffmpeg thread for " << _filename.get_basename() << " stopped.\n";
- }
- }
- /**
- * Called within the sub-thread. Assumes the lock is already held. If there
- * is something for the thread to do, does it and returns true. If there is
- * nothing for the thread to do, returns false.
- */
- bool FfmpegVideoCursor::
- do_poll() {
- switch (_thread_status) {
- case TS_stopped:
- case TS_seeking:
- // This shouldn't be possible while the thread is running.
- nassertr(false, false);
- return false;
- case TS_wait:
- // The video hasn't started playing yet.
- return false;
- case TS_readahead:
- if ((int)_readahead_frames.size() < _max_readahead_frames) {
- // Time to read the next frame.
- PT(FfmpegBuffer) frame = do_alloc_frame();
- nassertr(frame != nullptr, false);
- _lock.release();
- fetch_frame(-1);
- if (_frame_ready) {
- export_frame(frame);
- _lock.acquire();
- _readahead_frames.push_back(frame);
- } else {
- // No frame.
- _lock.acquire();
- }
- return true;
- }
- // No room for the next frame yet. Wait for more.
- return false;
- case TS_seek:
- // Seek to a specific frame.
- {
- int seek_frame = _seek_frame;
- _thread_status = TS_seeking;
- PT(FfmpegBuffer) frame = do_alloc_frame();
- nassertr(frame != nullptr, false);
- _lock.release();
- advance_to_frame(seek_frame);
- if (_frame_ready) {
- export_frame(frame);
- _lock.acquire();
- do_clear_all_frames();
- _readahead_frames.push_back(frame);
- } else {
- _lock.acquire();
- do_clear_all_frames();
- }
- if (_thread_status == TS_seeking) {
- // After seeking, we automatically transition to readahead.
- _thread_status = TS_readahead;
- }
- }
- return true;
- case TS_shutdown:
- // Time to stop the thread.
- return false;
- }
- return false;
- }
- /**
- * Allocates a new Buffer object. Assumes the lock is held.
- */
- PT(FfmpegVideoCursor::FfmpegBuffer) FfmpegVideoCursor::
- do_alloc_frame() {
- PT(Buffer) buffer = make_new_buffer();
- return (FfmpegBuffer *)buffer.p();
- }
- /**
- * Empties the entire readahead_frames queue. Assumes the lock is held.
- */
- void FfmpegVideoCursor::
- do_clear_all_frames() {
- _readahead_frames.clear();
- }
- /**
- * Called within the sub-thread. Fetches a video packet 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_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(int default_frame) {
- if (ffmpeg_global_lock) {
- ReMutexHolder av_holder(_av_lock);
- return do_fetch_packet(default_frame);
- } else {
- return do_fetch_packet(default_frame);
- }
- }
- /**
- * As above, with the ffmpeg global lock held (if configured on).
- */
- bool FfmpegVideoCursor::
- do_fetch_packet(int default_frame) {
- if (_packet->data) {
- #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 12, 100)
- av_packet_unref(_packet);
- #else
- av_free_packet(_packet);
- #endif
- }
- while (av_read_frame(_format_ctx, _packet) >= 0) {
- if (_packet->stream_index == _video_index) {
- _packet_frame = _packet->dts;
- return false;
- }
- #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 12, 100)
- av_packet_unref(_packet);
- #else
- av_free_packet(_packet);
- #endif
- }
- _packet->data = nullptr;
- 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;
- }
- /**
- * Called within the sub-thread. Slides forward until the indicated frame,
- * then fetches a frame from the stream and stores it in the frame buffer.
- * Sets _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).
- */
- void FfmpegVideoCursor::
- fetch_frame(int frame) {
- PStatTimer timer(_fetch_buffer_pcollector);
- int finished = 0;
- if (_packet_frame <= frame) {
- finished = 0;
- // Get the next packet. The first packet beyond the frame we're looking
- // for marks the point to stop.
- while (_packet_frame <= frame) {
- PStatTimer timer(_seek_pcollector);
- // Decode the previous packet, and get the next one.
- decode_frame(finished);
- _begin_frame = _packet_frame;
- if (fetch_packet(frame)) {
- _end_frame = _packet_frame;
- _frame_ready = false;
- return;
- }
- }
- } else {
- // Just get the next frame.
- finished = 0;
- while (!finished && _packet->data) {
- decode_frame(finished);
- _begin_frame = _packet_frame;
- fetch_packet(_begin_frame + 1);
- }
- }
- _end_frame = _packet_frame;
- _frame_ready = true;
- }
- /**
- * Called within the sub-thread. Decodes the data in the specified packet
- * into _frame.
- */
- void FfmpegVideoCursor::
- decode_frame(int &finished) {
- if (ffmpeg_global_lock) {
- ReMutexHolder av_holder(_av_lock);
- do_decode_frame(finished);
- } else {
- do_decode_frame(finished);
- }
- }
- /**
- * As above, with the ffmpeg global lock held (if configured on).
- */
- void FfmpegVideoCursor::
- do_decode_frame(int &finished) {
- #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 37, 100)
- // While the audio cursor has a really nice async loop for decoding, we
- // don't really do that much with video since we're already delegated to
- // another thread here. This is just to silence the deprecation warning
- // on avcodec_decode_video2.
- avcodec_send_packet(_video_ctx, _packet);
- int ret = avcodec_receive_frame(_video_ctx, _frame);
- finished = (ret == 0);
- #else
- avcodec_decode_video2(_video_ctx, _frame, &finished, _packet);
- #endif
- }
- /**
- * Called within the sub-thread. Seeks to a target location. Afterward, the
- * packet_frame is guaranteed to be less than or equal to the specified frame.
- */
- void FfmpegVideoCursor::
- seek(int frame, bool backward) {
- PStatTimer timer(_seek_pcollector);
- if (ffmpeg_support_seek) {
- if (ffmpeg_global_lock) {
- ReMutexHolder av_holder(_av_lock);
- do_seek(frame, backward);
- } else {
- do_seek(frame, backward);
- }
- } else {
- // If seeking isn't supported, close-and-reopen.
- if (backward) {
- reset_stream();
- }
- }
- }
- /**
- * As above, with the ffmpeg global lock held (if configured on). Also only
- * if ffmpeg-support-seek is on.
- */
- void FfmpegVideoCursor::
- do_seek(int frame, bool backward) {
- int64_t target_ts = (int64_t)frame;
- if (target_ts < (int64_t)(_initial_dts)) {
- // Attempts to seek before the first packet will fail.
- target_ts = _initial_dts;
- }
- int flags = 0;
- if (backward) {
- flags = AVSEEK_FLAG_BACKWARD;
- }
- if (av_seek_frame(_format_ctx, _video_index, target_ts, flags) < 0) {
- if (ffmpeg_cat.is_spam()) {
- ffmpeg_cat.spam()
- << "Seek failure.\n";
- }
- if (backward) {
- // Now try to seek forward.
- reset_stream();
- seek(frame, false);
- return;
- }
- // 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();
- return;
- }
- }
- fetch_packet(0);
- fetch_frame(-1);
- }
- /**
- * 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;
- }
- /**
- * Resets the stream to its initial, first-opened state by closing and re-
- * opening it.
- */
- void FfmpegVideoCursor::
- reset_stream() {
- if (ffmpeg_cat.is_spam()) {
- ffmpeg_cat.spam()
- << "Resetting ffmpeg stream.\n";
- }
- close_stream();
- if (!open_stream()) {
- ffmpeg_cat.error()
- << "Stream error, invalidating movie.\n";
- cleanup();
- return;
- }
- fetch_packet(0);
- fetch_frame(-1);
- }
- /**
- * Called within the sub-thread. Advance until the specified frame is in the
- * export buffer.
- */
- void FfmpegVideoCursor::
- advance_to_frame(int frame) {
- PStatTimer timer(_fetch_buffer_pcollector);
- 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();
- if (ffmpeg_cat.is_spam()) {
- ffmpeg_cat.spam()
- << "Reseek to 0, got " << _begin_frame << "\n";
- }
- }
- if (frame > _end_frame) {
- if (ffmpeg_cat.is_spam()) {
- ffmpeg_cat.spam()
- << "Now sliding forward to " << frame << " from " << _begin_frame << "\n";
- }
- fetch_frame(frame);
- }
- } 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 (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(frame);
- } else {
- // 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. So the rule is, try the seek. If it hurts us by moving us
- // backward, we increase the minimum threshold distance for forward-
- // seeking in the future.
- if (ffmpeg_cat.is_spam()) {
- ffmpeg_cat.spam()
- << "Jumping forward to " << frame << " from " << _begin_frame << "\n";
- }
- 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 (frame > _end_frame) {
- if (ffmpeg_cat.is_spam()) {
- ffmpeg_cat.spam()
- << "Correcting, sliding forward to " << frame << " from " << _begin_frame << "\n";
- }
- fetch_frame(frame);
- }
- }
- if (ffmpeg_cat.is_spam()) {
- ffmpeg_cat.spam()
- << "Wanted " << frame << ", got " << _begin_frame << "\n";
- }
- }
- /**
- * Called within the sub-thread. Exports the contents of the frame buffer
- * into the indicated target buffer.
- */
- void FfmpegVideoCursor::
- export_frame(FfmpegBuffer *buffer) {
- PStatTimer timer(_export_frame_pcollector);
- if (!_frame_ready) {
- // No frame data ready, just fill with black.
- if (ffmpeg_cat.is_spam()) {
- ffmpeg_cat.spam()
- << "ffmpeg for " << _filename.get_basename()
- << ", no frame available.\n";
- }
- memset(buffer->_block, 0, buffer->_block_size);
- return;
- }
- _frame_out->data[0] = buffer->_block + ((_size_y - 1) * _size_x * _num_components);
- _frame_out->linesize[0] = _size_x * -_num_components;
- buffer->_begin_frame = _begin_frame;
- buffer->_end_frame = _end_frame;
- if (ffmpeg_global_lock) {
- ReMutexHolder av_holder(_av_lock);
- #ifdef HAVE_SWSCALE
- nassertv(_convert_ctx != nullptr && _frame != nullptr && _frame_out != nullptr);
- sws_scale(_convert_ctx, _frame->data, _frame->linesize, 0, _size_y, _frame_out->data, _frame_out->linesize);
- #else
- img_convert((AVPicture *)_frame_out, (AVPixelFormat)_pixel_format,
- (AVPicture *)_frame, _video_ctx->pix_fmt, _size_x, _size_y);
- #endif
- } else {
- #ifdef HAVE_SWSCALE
- nassertv(_convert_ctx != nullptr && _frame != nullptr && _frame_out != nullptr);
- sws_scale(_convert_ctx, _frame->data, _frame->linesize, 0, _size_y, _frame_out->data, _frame_out->linesize);
- #else
- img_convert((AVPicture *)_frame_out, (AVPixelFormat)_pixel_format,
- (AVPicture *)_frame, _video_ctx->pix_fmt, _size_x, _size_y);
- #endif
- }
- }
- /**
- * Tells the BamReader how to create objects of type FfmpegVideo.
- */
- void FfmpegVideoCursor::
- register_with_read_factory() {
- BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
- }
- /**
- * Writes the contents of this object to the datagram for shipping out to a
- * Bam file.
- */
- void FfmpegVideoCursor::
- write_datagram(BamWriter *manager, Datagram &dg) {
- MovieVideoCursor::write_datagram(manager, dg);
- // No need to write any additional data here--all of it comes implicitly
- // from the underlying MovieVideo, which we process in finalize().
- }
- /**
- * Called by the BamReader to perform any final actions needed for setting up
- * the object after all objects have been read and all pointers have been
- * completed.
- */
- void FfmpegVideoCursor::
- finalize(BamReader *) {
- if (_source != nullptr) {
- FfmpegVideo *video;
- DCAST_INTO_V(video, _source);
- init_from(video);
- }
- }
- /**
- * This function is called by the BamReader's factory when a new object of
- * type FfmpegVideo is encountered in the Bam file. It should create the
- * FfmpegVideo and extract its information from the file.
- */
- TypedWritable *FfmpegVideoCursor::
- make_from_bam(const FactoryParams ¶ms) {
- FfmpegVideoCursor *video = new FfmpegVideoCursor;
- DatagramIterator scan;
- BamReader *manager;
- parse_params(params, scan, manager);
- video->fillin(scan, manager);
- return video;
- }
- /**
- * This internal function is called by make_from_bam to read in all of the
- * relevant data from the BamFile for the new FfmpegVideo.
- */
- void FfmpegVideoCursor::
- fillin(DatagramIterator &scan, BamReader *manager) {
- MovieVideoCursor::fillin(scan, manager);
- // The MovieVideoCursor gets the underlying MovieVideo pointer. We need a
- // finalize callback so we can initialize ourselves once that has been read
- // completely.
- manager->register_finalize(this);
- }
- /**
- * 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.
- *
- * Returns 0 if the two buffers are of the same frame, <0 if this one comes
- * earlier than the other one, and >0 if the other one comes earlier.
- */
- int FfmpegVideoCursor::FfmpegBuffer::
- compare_timestamp(const Buffer *other) const {
- const FfmpegBuffer *fother;
- DCAST_INTO_R(fother, other, 0);
- if (_end_frame * _video_timebase <= fother->_begin_frame * fother->_video_timebase) {
- return -1;
- } else if (_begin_frame * _video_timebase >= fother->_end_frame * fother->_video_timebase) {
- return 1;
- }
- return 0;
- }
- /**
- * Returns the nearest timestamp value of this particular buffer. Ideally,
- * MovieVideoCursor::set_time() for this timestamp would return this buffer
- * again. This need be defined only if compare_timestamp() is also defined.
- */
- double FfmpegVideoCursor::FfmpegBuffer::
- get_timestamp() const {
- int mid_frame = (_begin_frame + _end_frame - 1) / 2;
- return mid_frame * _video_timebase;
- }
|