Hi Robert,

As noted on osg-users, I've gone about as far as I probably will without a little help, so here's what I have for now. Most of it is in FFmpegDecoder, trying to detect when to open a stream and what kind of stream to open. I've also added slightly better error reporting, at least it's not just an int though I don't know what half the error codes mean... :-)

I've had limited success with mjpeg over http (video starts with a delay, and stops after 15-20 seconds) and no success at all with mpeg4 over rtsp (it seems to be getting data but can't decode it). This is all with an IP camera (Linksys PVC2300).

Let me know if you want me to make some tweaks. It may be that as we discussed, we might want to pass the format and codec as options instead of trying to guess in the future (because the guessing code could become huge if we want to cover every possible case), but for now this helped me move forward a bit. Hopefully someone else with a streaming camera and a little more ffmpeg experience will be inclined to try it out and fix some issues.

Thanks,

J-S
--
______________________________________________________
Jean-Sebastien Guay    [email protected]
                               http://www.cm-labs.com/
                        http://whitestar02.webhop.org/
#include "FFmpegDecoder.hpp"

#include <osg/Notify>
#include <osgDB/FileNameUtils>

#include <cassert>
#include <limits>
#include <stdexcept>
#include <string.h>
#include <iostream>


namespace osgFFmpeg {



FFmpegDecoder::FFmpegDecoder() :
    m_audio_stream(0),
    m_video_stream(0),
    m_audio_queue(100),
    m_video_queue(100),
    m_audio_decoder(m_audio_queue, m_clocks),
    m_video_decoder(m_video_queue, m_clocks),
    m_state(NORMAL),
    m_loop(false)
{

}



FFmpegDecoder::~FFmpegDecoder()
{
    close(true);
}


bool FFmpegDecoder::open(const std::string & filename)
{
    try
    {
        // Open video file
        AVFormatContext * p_format_context = 0;

        if (filename.compare(0, 5, "/dev/"   )==0 ||
            filename.compare(0, 7, "http://"; )==0 ||
            filename.compare(0, 8, "https://";)==0 || 
            filename.compare(0, 7, "rtsp://" )==0)
        {
            avdevice_register_all();
        
            osg::notify(osg::NOTICE)<<"Attempting to stream 
"<<filename<<std::endl;

            AVFormatParameters formatParams;
            memset(&formatParams, 0, sizeof(AVFormatParameters));
            AVInputFormat *iformat;

            formatParams.channel = 0;
            formatParams.standard = 0;
#if 1
            formatParams.width = 320;
            formatParams.height = 240;
#else
            formatParams.width = 640;
            formatParams.height = 480;
#endif            
            formatParams.time_base.num = 1;
            formatParams.time_base.den = 30;

            std::string format = "video4linux2";                    // by 
default
            if (filename.compare(0, 5, "/dev/") == 0)               // tested 
by Robert
                format = "video4linux2";                            // Is 
another format possible? Pass in options?
            else if (filename.compare(0, 7, "rtsp://") == 0)        // tested, 
does not work for now
                format = "rtsp";
            else if (filename.compare(0, 7, "http://";) == 0 ||      // tested 
with mjpeg stream, works
                     filename.compare(0, 8, "https://";) == 0)       // untested
            {
                // When streaming over http, the input format is just the one 
                // for the type of file to be opened.
                if (osgDB::getFileExtension(filename) == "mjpeg")
                    format = "mjpeg";
                else if (osgDB::getFileExtension(filename) == "mp4" ||
                         osgDB::getFileExtension(filename) == "m4v")
                    format = "m4v";
            }

            iformat = av_find_input_format(format.c_str());
            
            if (iformat)
            {
                osg::notify(osg::NOTICE)<<"Found input format: 
"<<format<<std::endl;
            }
            else
            {
                osg::notify(osg::NOTICE)<<"Failed to find input format: 
"<<format<<std::endl;
            }

            int error = av_open_input_file(&p_format_context, filename.c_str(), 
iformat, 0, &formatParams);
            if (error != 0)
            {
                std::string error_str;
                switch (error)
                {
                    //case AVERROR_UNKNOWN: error_str = "AVERROR_UNKNOWN"; 
break;   // same value as AVERROR_INVALIDDATA
                    case AVERROR_IO: error_str = "AVERROR_IO"; break;
                    case AVERROR_NUMEXPECTED: error_str = 
"AVERROR_NUMEXPECTED"; break;
                    case AVERROR_INVALIDDATA: error_str = 
"AVERROR_INVALIDDATA"; break;
                    case AVERROR_NOMEM: error_str = "AVERROR_NOMEM"; break;
                    case AVERROR_NOFMT: error_str = "AVERROR_NOFMT"; break;
                    case AVERROR_NOTSUPP: error_str = "AVERROR_NOTSUPP"; break;
                    case AVERROR_NOENT: error_str = "AVERROR_NOENT"; break;
                    case AVERROR_PATCHWELCOME: error_str = 
"AVERROR_PATCHWELCOME"; break;
                    default: error_str = "Unknown error"; break;
                }

                throw std::runtime_error("av_open_input_file() failed : " + 
error_str);
            }
        }
        else
        {
            if (av_open_input_file(&p_format_context, filename.c_str(), 0, 0, 
0) !=0 )
                throw std::runtime_error("av_open_input_file() failed");
        }
        
        m_format_context.reset(p_format_context);

        // Retrieve stream info
        if (av_find_stream_info(p_format_context) < 0)
            throw std::runtime_error("av_find_stream_info() failed");

        m_duration = double(m_format_context->duration) / AV_TIME_BASE;
        m_start = double(m_format_context->start_time) / AV_TIME_BASE;

        // TODO move this elsewhere
        m_clocks.reset(m_start);

        // Dump info to stderr
        dump_format(p_format_context, 0, filename.c_str(), false);

        // Find and open the first video and audio streams (note that audio 
stream is optional and only opened if possible)

        findVideoStream();
        findAudioStream();

        m_video_decoder.open(m_video_stream);

        try
        {
            m_audio_decoder.open(m_audio_stream);
        }

        catch (const std::runtime_error & error)
        {
            osg::notify(osg::WARN) << "FFmpegImageStream::open audio failed, 
audio stream will be disabled: " << error.what() << std::endl;
        }
    }

    catch (const std::runtime_error & error)
    {
        osg::notify(osg::WARN) << "FFmpegImageStream::open : " << error.what() 
<< std::endl;
        return false;
    }
    
    return true;
}



void FFmpegDecoder::close(bool waitForThreadToExit)
{
    flushAudioQueue();
    flushVideoQueue();
    
    m_audio_decoder.close(waitForThreadToExit);
    m_video_decoder.close(waitForThreadToExit);
}



bool FFmpegDecoder::readNextPacket()
{
    switch (m_state)
    {
    case NORMAL:
        return readNextPacketNormal();

    case END_OF_STREAM:
        return readNextPacketEndOfStream();

    case REWINDING:
        return readNextPacketRewinding();

    default:
        assert(false);
        return false;
    }
}



void FFmpegDecoder::rewind()
{
    m_pending_packet.clear();

    flushAudioQueue();
    flushVideoQueue();
    rewindButDontFlushQueues();
}




void FFmpegDecoder::findAudioStream()
{
    for (unsigned int i = 0; i < m_format_context->nb_streams; ++i)
    {
        if (m_format_context->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO)
        {
            m_audio_stream = m_format_context->streams[i];
            m_audio_index = i;
            return;
        }
    }

    m_audio_stream = 0;
    m_audio_index = std::numeric_limits<unsigned int>::max();
}



void FFmpegDecoder::findVideoStream()
{
    for (unsigned int i = 0; i < m_format_context->nb_streams; ++i)
    {
        if (m_format_context->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO)
        {
            m_video_stream = m_format_context->streams[i];
            m_video_index = i;
            return;
        }
    }

    throw std::runtime_error("could not find a video stream");
}



inline void FFmpegDecoder::flushAudioQueue()
{
    FFmpegPacketClear pc;
    m_audio_queue.flush(pc);
}



inline void FFmpegDecoder::flushVideoQueue()
{
    FFmpegPacketClear pc;
    m_video_queue.flush(pc);
}



bool FFmpegDecoder::readNextPacketNormal()
{
    AVPacket packet;

    if (! m_pending_packet)
    {
        bool end_of_stream = false;

        // Read the next frame packet
        if (av_read_frame(m_format_context.get(), &packet) < 0)
        {
            if (url_ferror(m_format_context->pb) == 0)
                end_of_stream = true;
            else
                throw std::runtime_error("av_read_frame() failed");
        }

        if (end_of_stream)
        {
            // If we reach the end of the stream, change the decoder state
            if (loop())
                rewindButDontFlushQueues();
            else
                m_state = END_OF_STREAM;

            return false;
        }
        else
        {
            // Make the packet data available beyond av_read_frame() logical 
scope.
            if (av_dup_packet(&packet) < 0)
                throw std::runtime_error("av_dup_packet() failed");

            m_pending_packet = FFmpegPacket(packet);            
        }
    }

    // Send data packet
    if (m_pending_packet.type == FFmpegPacket::PACKET_DATA)
    {
        if (m_pending_packet.packet.stream_index == m_audio_index)
        {
            if (m_audio_queue.timedPush(m_pending_packet, 10)) {
                m_pending_packet.release();
                return true;
            }
        }
        else if (m_pending_packet.packet.stream_index == m_video_index)
        {
            if (m_video_queue.timedPush(m_pending_packet, 10)) {
                m_pending_packet.release();
                return true;
            }
        }
        else
        {
            m_pending_packet.clear();
            return true;
        }
    }

    return false;
}



bool FFmpegDecoder::readNextPacketEndOfStream()
{
    const FFmpegPacket packet(FFmpegPacket::PACKET_END_OF_STREAM);

    m_audio_queue.timedPush(packet, 10);
    m_video_queue.timedPush(packet, 10);

    return false;
}
    


bool FFmpegDecoder::readNextPacketRewinding()
{
    const FFmpegPacket packet(FFmpegPacket::PACKET_FLUSH);

    if (m_audio_queue.timedPush(packet, 10) && m_video_queue.timedPush(packet, 
10))
        m_state = NORMAL;

    return false;
}



void FFmpegDecoder::rewindButDontFlushQueues()
{
    const AVRational AvTimeBaseQ = { 1, AV_TIME_BASE }; // = AV_TIME_BASE_Q

    const int64_t pos = m_clocks.getStartTime() * AV_TIME_BASE;
    const int64_t seek_target = av_rescale_q(pos, AvTimeBaseQ, 
m_video_stream->time_base);

    if (av_seek_frame(m_format_context.get(), m_video_index, seek_target, 
0/*AVSEEK_FLAG_BYTE |*/ /*AVSEEK_FLAG_BACKWARD*/) < 0)
        throw std::runtime_error("av_seek_frame failed()");

    m_state = REWINDING;
}



} // namespace osgFFmpeg
#ifndef HEADER_GUARD_FFMPEG_HEADERS_H
#define HEADER_GUARD_FFMPEG_HEADERS_H


extern "C"
{
#define __STDC_CONSTANT_MACROS
#ifdef WIN32
#include <errno.h>    // for error codes defined in avformat.h
#endif
#include <stdint.h>
#include <avcodec.h>
#include <avformat.h>
#include <avdevice.h>

#ifdef USE_SWSCALE    
    #include <swscale.h>
#endif

}



#endif // HEADER_GUARD_FFMPEG_HEADERS_H
/* -*-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.
*/

#include <osgDB/Registry>
#include <osgDB/FileNameUtils>
#include <osgDB/FileUtils>

#include "FFmpegHeaders.hpp"
#include "FFmpegImageStream.hpp"



/** Implementation heavily inspired by http://www.dranger.com/ffmpeg/ */

class ReaderWriterFFmpeg : public osgDB::ReaderWriter
{
public:

    ReaderWriterFFmpeg()
    {
        supportsProtocol("http","Read video/audio from http using ffmpeg.");
        supportsProtocol("rtsp","Read video/audio from rtsp using ffmpeg.");

        supportsExtension("ffmpeg", "");
        supportsExtension("avi",    "");
        supportsExtension("flv",    "Flash video");
        supportsExtension("mov",    "Quicktime");
        supportsExtension("ogg",    "Theora movie format");
        supportsExtension("mpg",    "Mpeg movie format");
        supportsExtension("mpv",    "Mpeg movie format");
        supportsExtension("wmv",    "Windows Media Video format");
        supportsExtension("mkv",    "Matroska");
        supportsExtension("mjpeg",  "Motion JPEG");
        supportsExtension("mp4",    "MPEG-4");
        supportsExtension("sav",    "MPEG-4");
        supportsExtension("3gp",    "MPEG-4");
        supportsExtension("sdp",    "MPEG-4");

        // Register all FFmpeg formats/codecs
        av_register_all();
    }

    virtual ~ReaderWriterFFmpeg()
    {

    }

    virtual const char * className() const
    {
        return "ReaderWriterFFmpeg";
    }

    virtual ReadResult readImage(const std::string & filename, const 
osgDB::ReaderWriter::Options * options) const
    {
        const std::string ext = osgDB::getLowerCaseFileExtension(filename);
        if (ext=="ffmpeg") return 
readImage(osgDB::getNameLessExtension(filename),options);

        if (filename.compare(0, 5, "/dev/")==0)
        {
            return readImageStream(filename, options);
        }
    
        if (! acceptsExtension(ext))
            return ReadResult::FILE_NOT_HANDLED;

        const std::string path = osgDB::containsServerAddress(filename) ?
            filename :
            osgDB::findDataFile(filename, options);

        if (path.empty())
            return ReadResult::FILE_NOT_FOUND;

        return readImageStream(filename, options);
    }
    
    ReadResult readImageStream(const std::string& filename, const 
osgDB::ReaderWriter::Options * options) const
    {
        osg::notify(osg::INFO) << "ReaderWriterFFmpeg::readImage " << filename 
<< std::endl;

        osg::ref_ptr<osgFFmpeg::FFmpegImageStream> image_stream(new 
osgFFmpeg::FFmpegImageStream);

        if (! image_stream->open(filename))
            return ReadResult::FILE_NOT_HANDLED;

        return image_stream.release();
    }

private:

};



REGISTER_OSGPLUGIN(ffmpeg, ReaderWriterFFmpeg)
_______________________________________________
osg-submissions mailing list
[email protected]
http://lists.openscenegraph.org/listinfo.cgi/osg-submissions-openscenegraph.org

Reply via email to