Hi JS, I have merged your error code changes and the additional formats to ReaderWriterFFmpeg.cpp, but haven't merged the http & rtsp as I'd like to be able to test them directly myself and to would like to invesitgate the appropriate ffmpeg way to handle this type of input source.
What makes me more cautious about the code path is that my own /dev/ code is hardwired to a low resolution for the stereo webcam I've been experimenting and plan to remove this code and replace with more generic code down the line, and you've just bolted extra code on to this experimental code path that. I'd prefer to see a device code path kept separate from a network streaming path. I also tested watching movies over http using the original ffmpeg code, it had problems but it worked so I'm curious to as why you needed to use this other route. Could you provide example files for http and rtsp so that we can all test against the same sources and can observe the same issue. Cheers, Robert. On Fri, Apr 10, 2009 at 8:43 PM, Jean-Sébastien Guay < [email protected]> wrote: > 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 > >
_______________________________________________ osg-submissions mailing list [email protected] http://lists.openscenegraph.org/listinfo.cgi/osg-submissions-openscenegraph.org
