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