Add a way to retrieve the creation time for MPEG-4 files.

---
 include/osg/ImageStream                     |    2 +-
 src/osgPlugins/ffmpeg/FFmpegDecoder.hpp     |    8 ++++++++
 src/osgPlugins/ffmpeg/FFmpegImageStream.cpp |    5 +++++
 src/osgPlugins/ffmpeg/FFmpegImageStream.hpp |    1 +
 4 files changed, 15 insertions(+), 1 deletions(-)

diff --git a/include/osg/ImageStream b/include/osg/ImageStream
index 2749cd2..4b4a6d5 100644
--- a/include/osg/ImageStream
+++ b/include/osg/ImageStream
@@ -76,7 +76,7 @@ class OSG_EXPORT ImageStream : public Image
         
         LoopingMode getLoopingMode() const { return _loopingMode; }
 
-
+        virtual double getCreationTime() const { return HUGE_VAL; }
         virtual double getLength() const { return 0.0; }
         virtual double getFrameRate() const { return 0.0; } 
 
diff --git a/src/osgPlugins/ffmpeg/FFmpegDecoder.hpp 
b/src/osgPlugins/ffmpeg/FFmpegDecoder.hpp
index 6176dcc..a783f01 100644
--- a/src/osgPlugins/ffmpeg/FFmpegDecoder.hpp
+++ b/src/osgPlugins/ffmpeg/FFmpegDecoder.hpp
@@ -26,6 +26,8 @@ class FormatContextPtr
         
         T* get() { return _ptr; }
 
+        operator bool() const { return _ptr != 0; }
+
         T * operator-> () const // never throws
         {
             return _ptr;
@@ -74,6 +76,7 @@ public:
     void loop(bool loop);
     bool loop() const;
 
+    double creation_time() const;
     double duration() const;
     double reference();
 
@@ -144,6 +147,11 @@ inline bool FFmpegDecoder::loop() const
     return m_loop;
 }
 
+inline double FFmpegDecoder::creation_time() const
+{
+   if(m_format_context) return m_format_context->timestamp;
+   else return HUGE_VAL;
+}
 
 inline double FFmpegDecoder::duration() const
 {
diff --git a/src/osgPlugins/ffmpeg/FFmpegImageStream.cpp 
b/src/osgPlugins/ffmpeg/FFmpegImageStream.cpp
index e14fbd9..bd25ef8 100644
--- a/src/osgPlugins/ffmpeg/FFmpegImageStream.cpp
+++ b/src/osgPlugins/ffmpeg/FFmpegImageStream.cpp
@@ -162,6 +162,11 @@ 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(); 
diff --git a/src/osgPlugins/ffmpeg/FFmpegImageStream.hpp 
b/src/osgPlugins/ffmpeg/FFmpegImageStream.hpp
index 033a892..4395a58 100644
--- a/src/osgPlugins/ffmpeg/FFmpegImageStream.hpp
+++ b/src/osgPlugins/ffmpeg/FFmpegImageStream.hpp
@@ -36,6 +36,7 @@ namespace osgFFmpeg
         virtual void setVolume(float volume);
         virtual float getVolume() const;
 
+        virtual double getCreationTime() const;
         virtual double getLength() const;
         virtual double getReferenceTime () const; 
         virtual double getFrameRate() const;
-- 
1.7.0

/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield 
 *
 * This library is open source and may be redistributed and/or modified under  
 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or 
 * (at your option) any later version.  The full license is in LICENSE file
 * included with this distribution, and on the openscenegraph.org website.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 * OpenSceneGraph Public License for more details.
*/

#ifndef OSG_IMAGESTREAM
#define OSG_IMAGESTREAM 1

#include <osg/Image>
#include <osg/AudioStream>

namespace osg {

/**
  * Image Stream class.
*/
class OSG_EXPORT ImageStream : public Image
{
    public:
        ImageStream();

        /** Copy constructor using CopyOp to manage deep vs shallow copy. */
        ImageStream(const ImageStream& image,const CopyOp& 
copyop=CopyOp::SHALLOW_COPY);

        virtual Object* cloneType() const { return new ImageStream(); }
        virtual Object* clone(const CopyOp& copyop) const { return new 
ImageStream(*this,copyop); }
        virtual bool isSameKindAs(const Object* obj) const { return 
dynamic_cast<const ImageStream*>(obj)!=0; }
        virtual const char* libraryName() const { return "osg"; }
        virtual const char* className() const { return "ImageStream"; }

        /** Return -1 if *this < *rhs, 0 if *this==*rhs, 1 if *this>*rhs. */
        virtual int compare(const Image& rhs) const;

        enum StreamStatus
        {
            INVALID,
            PLAYING,
            PAUSED,
            REWINDING
        };
        
        virtual void seek(double /*time*/) {}

        virtual void play() { _status=PLAYING; }

        virtual void pause() { _status=PAUSED; }

        virtual void rewind() { _status=REWINDING; }
        
        virtual void quit(bool /*waitForThreadToExit*/ = true) {}

        StreamStatus getStatus() { return _status; }
        

        enum LoopingMode
        {
            NO_LOOPING,
            LOOPING
        };
        
        void setLoopingMode(LoopingMode mode)
        {
            if (_loopingMode == mode) return;
            
            _loopingMode = mode;
            applyLoopingMode();
        }
        
        LoopingMode getLoopingMode() const { return _loopingMode; }

        virtual double getCreationTime() const { return HUGE_VAL; }
        virtual double getLength() const { return 0.0; }
        virtual double getFrameRate() const { return 0.0; } 

        virtual void setReferenceTime(double) {}
        virtual double getReferenceTime() const { return 0.0; }
                
        virtual void setTimeMultiplier(double) {}
        virtual double getTimeMultiplier() const { return 0.0; }
        
        virtual void setVolume(float) {}
        virtual float getVolume() const { return 0.0f; }

        typedef std::vector< osg::ref_ptr<osg::AudioStream> > AudioStreams;
        void setAudioStreams(const AudioStreams& asl) { _audioStreams = asl; }
        AudioStreams& getAudioStreams() { return _audioStreams; }
        const AudioStreams& getAudioStreams() const { return _audioStreams; }
        

    protected:
        virtual void applyLoopingMode() {}

        virtual ~ImageStream() {}

        StreamStatus    _status;
        LoopingMode     _loopingMode;
        
        AudioStreams    _audioStreams;
};

} // namespace

#endif
#ifndef HEADER_GUARD_OSGFFMPEG_FFMPEG_DECODER_H
#define HEADER_GUARD_OSGFFMPEG_FFMPEG_DECODER_H

#include "FFmpegDecoderAudio.hpp"
#include "FFmpegDecoderVideo.hpp"

#include <osg/Notify>


namespace osgFFmpeg {

class FormatContextPtr
{
    public:
    
        typedef AVFormatContext T;
    
        explicit FormatContextPtr() : _ptr(0) {}
        explicit FormatContextPtr(T* ptr) : _ptr(ptr) {}
        
        ~FormatContextPtr()
        {
            cleanup();
        }
        
        T* get() { return _ptr; }

        operator bool() const { return _ptr != 0; }

        T * operator-> () const // never throws
        {
            return _ptr;
        }

        void reset(T* ptr) 
        {
            if (ptr==_ptr) return;
            cleanup();
            _ptr = ptr;
        }

        void cleanup()
        {
            if (_ptr) 
            {
                OSG_NOTICE<<"Calling av_close_input_file("<<_ptr<<")"<<std::endl;
                av_close_input_file(_ptr);
            }
            _ptr = 0;
        }
        
        

    protected:
    
        T* _ptr;
};


class FFmpegDecoder : public osg::Referenced
{
public:

    FFmpegDecoder();
    ~FFmpegDecoder();

    bool open(const std::string & filename);
    void close(bool waitForThreadToExit);

    bool readNextPacket();
    void rewind();
    void seek(double time);
    void pause();

    void loop(bool loop);
    bool loop() const;

    double creation_time() const;
    double duration() const;
    double reference();

    FFmpegDecoderAudio & audio_decoder();
    FFmpegDecoderVideo & video_decoder();
    FFmpegDecoderAudio const & audio_decoder() const;
    FFmpegDecoderVideo const & video_decoder() const;

protected:

    enum State
    {
        NORMAL,
        PAUSE,
        END_OF_STREAM,
        REWINDING,
        SEEKING
    };

    typedef BoundedMessageQueue<FFmpegPacket> PacketQueue;

    void findAudioStream();
    void findVideoStream();
    void flushAudioQueue();
    void flushVideoQueue();
    bool readNextPacketNormal();
    bool readNextPacketEndOfStream();
    bool readNextPacketRewinding();
    bool readNextPacketSeeking();
    bool readNextPacketPause();
    void rewindButDontFlushQueues();
    void seekButDontFlushQueues(double time);

    FormatContextPtr    m_format_context;
    AVStream *          m_audio_stream;
    AVStream *          m_video_stream;

    int                 m_audio_index;
    int                 m_video_index;

    FFmpegClocks        m_clocks;
    FFmpegPacket        m_pending_packet;
    PacketQueue         m_audio_queue;
    PacketQueue         m_video_queue;
    
    FFmpegDecoderAudio  m_audio_decoder;
    FFmpegDecoderVideo  m_video_decoder;

    double              m_duration;
    double              m_start;

    State               m_state;
    bool                m_loop;
};





inline void FFmpegDecoder::loop(const bool loop)
{
    m_loop = loop;
}


inline bool FFmpegDecoder::loop() const
{
    return m_loop;
}

inline double FFmpegDecoder::creation_time() const
{
   if(m_format_context) return m_format_context->timestamp;
   else return HUGE_VAL;
}

inline double FFmpegDecoder::duration() const
{
    return double(m_format_context->duration) / AV_TIME_BASE;    
}

inline double FFmpegDecoder::reference()
{
    return m_clocks.getCurrentTime();    
}


inline FFmpegDecoderAudio & FFmpegDecoder::audio_decoder()
{
    return m_audio_decoder;
}


inline FFmpegDecoderVideo & FFmpegDecoder::video_decoder()
{
    return m_video_decoder;
}


inline FFmpegDecoderAudio const & FFmpegDecoder::audio_decoder() const
{
    return m_audio_decoder;
}


inline FFmpegDecoderVideo const & FFmpegDecoder::video_decoder() const
{
    return m_video_decoder;
}



} // namespace osgFFmpeg



#endif // HEADER_GUARD_OSGFFMPEG_FFMPEG_DECODER_H
#ifndef HEADER_GUARD_OSGFFMPEG_FFMPEG_IMAGE_STREAM_H
#define HEADER_GUARD_OSGFFMPEG_FFMPEG_IMAGE_STREAM_H

#include "FFmpegDecoder.hpp"
#include "MessageQueue.hpp"

#include <osg/ImageStream>

#include <OpenThreads/Condition>
#include <OpenThreads/Thread>

namespace osgFFmpeg
{

    template <class T>
    class MessageQueue;

    class FFmpegImageStream : public osg::ImageStream, public OpenThreads::Thread
    {
    public:

        FFmpegImageStream();
        FFmpegImageStream(const FFmpegImageStream & image, const osg::CopyOp & copyop = osg::CopyOp::SHALLOW_COPY);

        META_Object(osgFFmpeg, FFmpegImageStream);

        bool open(const std::string & filename);

        virtual void play();
        virtual void pause();
        virtual void rewind();
        virtual void seek(double time);
        virtual void quit(bool waitForThreadToExit = true);

        virtual void setVolume(float volume);
        virtual float getVolume() const;

        virtual double getCreationTime() const;
        virtual double getLength() const;
        virtual double getReferenceTime () const; 
        virtual double getFrameRate() const;

        virtual bool isImageTranslucent() const;

    private:

        enum Command
        {
            CMD_PLAY,
            CMD_PAUSE,
            CMD_STOP,
            CMD_REWIND,
            CMD_SEEK
        };

        typedef MessageQueue<Command> CommandQueue;
        typedef OpenThreads::Mutex Mutex;
        typedef OpenThreads::Condition Condition;

        virtual ~FFmpegImageStream();
        virtual void run();
        virtual void applyLoopingMode();

        bool handleCommand(Command cmd);

        void cmdPlay();
        void cmdPause();
        void cmdRewind();
        void cmdSeek(double time);

        static void publishNewFrame(const FFmpegDecoderVideo &, void * user_data);

        osg::ref_ptr<FFmpegDecoder>    m_decoder;
        CommandQueue *    m_commands;

        Mutex            m_mutex;
        Condition        m_frame_published_cond;
        bool             m_frame_published_flag;
        double           m_seek_time;
    };

}



#endif // HEADER_GUARD_OSGFFMPEG_FFMPEG_IMAGE_STREAM_H
#include "FFmpegImageStream.hpp"
#include "FFmpegAudioStream.hpp"

#include <OpenThreads/ScopedLock>
#include <osg/Notify>

#include <memory>



namespace osgFFmpeg {



FFmpegImageStream::FFmpegImageStream() :
    m_decoder(0),
    m_commands(0),
    m_frame_published_flag(false)
{
    setOrigin(osg::Image::TOP_LEFT);

    std::auto_ptr<FFmpegDecoder> decoder(new FFmpegDecoder);
    std::auto_ptr<CommandQueue> commands(new CommandQueue);

    m_decoder = decoder.release();
    m_commands = commands.release();
}



FFmpegImageStream::FFmpegImageStream(const FFmpegImageStream & image, const osg::CopyOp & copyop) :
    osg::ImageStream(image, copyop)
{
    // TODO: probably incorrect or incomplete
}



FFmpegImageStream::~FFmpegImageStream()
{
    OSG_INFO<<"Destructing FFmpegImageStream..."<<std::endl;

    quit(true);
    
    OSG_INFO<<"Have done quit"<<std::endl;

    // release athe audio streams to make sure that the decoder doesn't retain any external
    // refences.
    getAudioStreams().clear();

    // destroy the decoder and associated threads
    m_decoder = 0;


    delete m_commands;

    OSG_INFO<<"Destructed FFMpegImageStream."<<std::endl;
}



bool FFmpegImageStream::open(const std::string & filename)
{
    setFileName(filename);

    if (! m_decoder->open(filename))
        return false;

    setImage(
        m_decoder->video_decoder().width(), m_decoder->video_decoder().height(), 1, GL_RGBA, GL_BGRA, GL_UNSIGNED_BYTE,
        const_cast<unsigned char *>(m_decoder->video_decoder().image()), NO_DELETE
    );


    setPixelAspectRatio(m_decoder->video_decoder().pixelAspectRatio());

    OSG_NOTICE<<"ffmpeg::open("<<filename<<") size("<<s()<<", "<<t()<<") aspect ratio "<<m_decoder->video_decoder().pixelAspectRatio()<<std::endl;

#if 1
    // swscale is reported errors and then crashing when rescaling video of size less than 10 by 10.
    if (s()<=10 || t()<=10) return false;
#endif

    m_decoder->video_decoder().setUserData(this);
    m_decoder->video_decoder().setPublishCallback(publishNewFrame);

    if (m_decoder->audio_decoder().validContext())
    {
        OSG_NOTICE<<"Attaching FFmpegAudioStream"<<std::endl;

        getAudioStreams().push_back(new FFmpegAudioStream(m_decoder.get()));
    }

    _status = PAUSED;
    applyLoopingMode();

    start(); // start thread

    return true;
}



void FFmpegImageStream::play()
{
    m_commands->push(CMD_PLAY);

#if 0
    // Wait for at least one frame to be published before returning the call
    OpenThreads::ScopedLock<Mutex> 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::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);
            }
            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()"<<std::endl;
}



void FFmpegImageStream::applyLoopingMode()
{
    m_decoder->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<FFmpegImageStream*>(user_data);

#if 1
    this_->setImage(
        this_->m_decoder->video_decoder().width(), this_->m_decoder->video_decoder().height(), 1, GL_RGBA, GL_BGRA, GL_UNSIGNED_BYTE,
        const_cast<unsigned char *>(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

    OpenThreads::ScopedLock<Mutex> lock(this_->m_mutex);

    if (! this_->m_frame_published_flag)
    {
        this_->m_frame_published_flag = true;
        this_->m_frame_published_cond.signal();
    }
}



} // namespace osgFFmpeg
_______________________________________________
osg-submissions mailing list
[email protected]
http://lists.openscenegraph.org/listinfo.cgi/osg-submissions-openscenegraph.org

Reply via email to