#include "FFmpegImageStream.hpp" #include "FFmpegAudioStream.hpp" #include "FFmpegParameters.hpp" #include #include #include #define STREAM_TIMEOUT_IN_SECONDS_TO_CONSIDER_IT_DEAD 10 namespace osgFFmpeg { FFmpegImageStream::FFmpegImageStream() : m_decoder(0), m_commands(0), m_frame_published_flag(false), _lastUpdateTS(0.) { setOrigin(osg::Image::TOP_LEFT); std::auto_ptr decoder(new FFmpegDecoder); std::auto_ptr commands(new CommandQueue); m_decoder = decoder.release(); m_commands = commands.release(); } FFmpegImageStream::FFmpegImageStream(const FFmpegImageStream & image, const osg::CopyOp & copyop) : osg::ImageStream(image, copyop), OpenThreads::Thread() { // TODO: probably incorrect or incomplete } FFmpegImageStream::~FFmpegImageStream() { OSG_INFO<<"Destructing FFmpegImageStream..."<open(filename, parameters)) return false; setImage( m_decoder->video_decoder().width(), m_decoder->video_decoder().height(), 1, GL_RGB, GL_RGB, GL_UNSIGNED_BYTE, const_cast(m_decoder->video_decoder().image()), NO_DELETE ); setPixelAspectRatio(m_decoder->video_decoder().pixelAspectRatio()); OSG_NOTICE<<"ffmpeg::open("<video_decoder().pixelAspectRatio()<video_decoder().setUserData(this); m_decoder->video_decoder().setPublishCallback(publishNewFrame); if (m_decoder->audio_decoder().validContext()) { OSG_NOTICE<<"Attaching FFmpegAudioStream"<push(CMD_PLAY); #if 0 // Wait for at least one frame to be published before returning the call OpenThreads::ScopedLock lock(m_mutex); while (duration() > 0 && ! m_frame_published_flag) m_frame_published_cond.wait(&m_mutex); #endif } void FFmpegImageStream::pause() { m_commands->push(CMD_PAUSE); } void FFmpegImageStream::rewind() { m_commands->push(CMD_REWIND); } void FFmpegImageStream::seek(double time) { m_seek_time = time; m_commands->push(CMD_SEEK); } void FFmpegImageStream::quit(bool waitForThreadToExit) { // Stop the packet producer thread if (isRunning()) { m_commands->push(CMD_STOP); if (waitForThreadToExit) join(); } // Close the decoder (i.e. flush the decoder packet queues) m_decoder->close(waitForThreadToExit); } void FFmpegImageStream::setVolume(float volume) { m_decoder->audio_decoder().setVolume(volume); } float FFmpegImageStream::getVolume() const { return m_decoder->audio_decoder().getVolume(); } double FFmpegImageStream::getCreationTime() const { return m_decoder->creation_time(); } double FFmpegImageStream::getLength() const { return m_decoder->duration(); } double FFmpegImageStream::getReferenceTime () const { return m_decoder->reference(); } double FFmpegImageStream::getCurrentTime() const { return m_decoder->reference(); } double FFmpegImageStream::getFrameRate() const { return m_decoder->video_decoder().frameRate(); } bool FFmpegImageStream::isImageTranslucent() const { return m_decoder->video_decoder().alphaChannel(); } void FFmpegImageStream::run() { try { bool done = false; while (! done) { if (_status == PLAYING) { bool no_cmd; const Command cmd = m_commands->timedPop(no_cmd, 1); if (no_cmd) { m_decoder->readNextPacket(); } else done = ! handleCommand(cmd); // Security check to detect (and stop) dead streams if ( _lastUpdateTS > 0. && osg::Timer::instance()->delta_s(_lastUpdateTS, osg::Timer::instance()->tick()) > STREAM_TIMEOUT_IN_SECONDS_TO_CONSIDER_IT_DEAD ) { _status = INVALID; done = true; } } else { done = ! handleCommand(m_commands->pop()); } } } catch (const std::exception & error) { OSG_WARN << "FFmpegImageStream::run : " << error.what() << std::endl; } catch (...) { OSG_WARN << "FFmpegImageStream::run : unhandled exception" << std::endl; } OSG_NOTICE<<"Finished FFmpegImageStream::run()"<loop(getLoopingMode() == LOOPING); } bool FFmpegImageStream::handleCommand(const Command cmd) { switch (cmd) { case CMD_PLAY: cmdPlay(); return true; case CMD_PAUSE: cmdPause(); return true; case CMD_REWIND: cmdRewind(); return true; case CMD_SEEK: cmdSeek(m_seek_time); return true; case CMD_STOP: return false; default: assert(false); return false; } } void FFmpegImageStream::cmdPlay() { if (_status == PAUSED) { if (! m_decoder->audio_decoder().isRunning()) m_decoder->audio_decoder().start(); if (! m_decoder->video_decoder().isRunning()) m_decoder->video_decoder().start(); m_decoder->video_decoder().pause(false); m_decoder->audio_decoder().pause(false); } _status = PLAYING; } void FFmpegImageStream::cmdPause() { if (_status == PLAYING) { m_decoder->video_decoder().pause(true); m_decoder->audio_decoder().pause(true); } _status = PAUSED; } void FFmpegImageStream::cmdRewind() { m_decoder->rewind(); } void FFmpegImageStream::cmdSeek(double time) { m_decoder->seek(time); } void FFmpegImageStream::publishNewFrame(const FFmpegDecoderVideo &, void * user_data) { FFmpegImageStream * const this_ = reinterpret_cast(user_data); #if 1 this_->setImage( this_->m_decoder->video_decoder().width(), this_->m_decoder->video_decoder().height(), 1, GL_RGB, GL_RGB, GL_UNSIGNED_BYTE, const_cast(this_->m_decoder->video_decoder().image()), NO_DELETE ); #else /** \bug If viewer.realize() hasn't been already called, this doesn't work? */ this_->dirty(); #endif // Store the timestamp of this update. Needed to check dead streams this_->_lastUpdateTS = osg::Timer::instance()->tick(); OpenThreads::ScopedLock lock(this_->m_mutex); if (! this_->m_frame_published_flag) { this_->m_frame_published_flag = true; this_->m_frame_published_cond.signal(); } } } // namespace osgFFmpeg