[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Gnash-commit] gnash ChangeLog libmedia/FLVParser.cpp libmedia...
From: |
Sandro Santilli |
Subject: |
[Gnash-commit] gnash ChangeLog libmedia/FLVParser.cpp libmedia... |
Date: |
Mon, 16 Jun 2008 09:05:03 +0000 |
CVSROOT: /sources/gnash
Module name: gnash
Changes by: Sandro Santilli <strk> 08/06/16 09:05:03
Modified files:
. : ChangeLog
libmedia : FLVParser.cpp FLVParser.h MediaParser.cpp
MediaParser.h
libmedia/ffmpeg: MediaParserFfmpeg.cpp MediaParserFfmpeg.h
server/asobj : NetStream.cpp NetStreamFfmpeg.cpp
NetStreamFfmpeg.h
Log message:
Load and parse media files in a separate thread (with compile-time
opt-out)
and other cleanups
CVSWeb URLs:
http://cvs.savannah.gnu.org/viewcvs/gnash/ChangeLog?cvsroot=gnash&r1=1.6937&r2=1.6938
http://cvs.savannah.gnu.org/viewcvs/gnash/libmedia/FLVParser.cpp?cvsroot=gnash&r1=1.17&r2=1.18
http://cvs.savannah.gnu.org/viewcvs/gnash/libmedia/FLVParser.h?cvsroot=gnash&r1=1.16&r2=1.17
http://cvs.savannah.gnu.org/viewcvs/gnash/libmedia/MediaParser.cpp?cvsroot=gnash&r1=1.2&r2=1.3
http://cvs.savannah.gnu.org/viewcvs/gnash/libmedia/MediaParser.h?cvsroot=gnash&r1=1.24&r2=1.25
http://cvs.savannah.gnu.org/viewcvs/gnash/libmedia/ffmpeg/MediaParserFfmpeg.cpp?cvsroot=gnash&r1=1.13&r2=1.14
http://cvs.savannah.gnu.org/viewcvs/gnash/libmedia/ffmpeg/MediaParserFfmpeg.h?cvsroot=gnash&r1=1.9&r2=1.10
http://cvs.savannah.gnu.org/viewcvs/gnash/server/asobj/NetStream.cpp?cvsroot=gnash&r1=1.98&r2=1.99
http://cvs.savannah.gnu.org/viewcvs/gnash/server/asobj/NetStreamFfmpeg.cpp?cvsroot=gnash&r1=1.150&r2=1.151
http://cvs.savannah.gnu.org/viewcvs/gnash/server/asobj/NetStreamFfmpeg.h?cvsroot=gnash&r1=1.75&r2=1.76
Patches:
Index: ChangeLog
===================================================================
RCS file: /sources/gnash/gnash/ChangeLog,v
retrieving revision 1.6937
retrieving revision 1.6938
diff -u -b -r1.6937 -r1.6938
--- ChangeLog 16 Jun 2008 08:50:21 -0000 1.6937
+++ ChangeLog 16 Jun 2008 09:05:00 -0000 1.6938
@@ -1,3 +1,17 @@
+2008-06-16 Sandro Santilli <address@hidden>
+
+ * libmedia/MediaParser.{cpp,h}: move more functionality to
+ the base class: management of encoded audio/video queues and
+ threaded loading (compile-time opt-out available);
+ change seek() signature to allow for InvalidTime return.
+ * libmedia/FLVParser.{cpp,h}: adapt to MediaParser changes.
+ * libmedia/ffmpeg/MediaParserFfmpeg.{cpp,h}: adapt to MediaParser
+ changes.
+ * server/asobj/NetStream.cpp: notify buffer time to parser, when
+ available (it'll take care of it).
+ * server/asobj/NetStreamFfmpeg.{cpp,h}: push time *ahead* to decoded
+ audio queue.
+
2008-06-15 Benjamin Wolsey <address@hidden>
* server/vm/ASHandlers.{cpp,h}: use std::string instead of C strings
Index: libmedia/FLVParser.cpp
===================================================================
RCS file: /sources/gnash/gnash/libmedia/FLVParser.cpp,v
retrieving revision 1.17
retrieving revision 1.18
diff -u -b -r1.17 -r1.18
--- libmedia/FLVParser.cpp 10 Jun 2008 06:12:11 -0000 1.17
+++ libmedia/FLVParser.cpp 16 Jun 2008 09:05:01 -0000 1.18
@@ -43,6 +43,7 @@
:
MediaParser(lt),
_lastParsedPosition(0),
+ _bytesLoaded(0),
_nextAudioFrame(0),
_nextVideoFrame(0),
_audio(false),
@@ -58,27 +59,55 @@
}
-boost::uint32_t
-FLVParser::seek(boost::uint32_t time)
+// would be called by main thread
+bool
+FLVParser::seek(boost::uint32_t& time)
{
- if ( time ) // seek to 0 should be fine..
+ //GNASH_REPORT_FUNCTION;
+
+ if ( time )
{
- LOG_ONCE( log_unimpl("%s", __PRETTY_FUNCTION__) );
+ LOG_ONCE( log_unimpl("FLVParser::seek with non-zero arg") );
+ return false;
}
- // In particular, what to do if there's no frames in queue ?
- // just seek to the the later available first timestamp
- _audioFrames.clear();
- _videoFrames.clear();
- _stream->seek(0);
- _parsingComplete=false;
- return 0;
+ boost::mutex::scoped_lock streamLock(_streamMutex);
+ // we might obtain this lock while the parser is pushing the last
+ // encoded frame on the queue, or while it is waiting on the wakeup
+ // condition
+
+ // Setting _seekRequested to true will make the parser thread
+ // take care of cleaning up the buffers before going on with
+ // parsing, thus fixing the case in which streamLock was obtained
+ // while the parser was pushing to queue
+ _seekRequested = true;
+
+ _lastParsedPosition=9; // 9 is FLV header size...
+ _parsingComplete=false; // or NetStream will send the Play.Stop event...
+
+ time = 0; // this is the only place we want to go to
+
+ // Finally, in case the parser is now waiting on the wakeup condition,
+ // we wake it up.
+ //
+ // WARNING: a race condition is pending here:
+ // If we notify *before* the parser waits, it'll still wait undefinitely
+ // here, or we might notify the wakeup before the thread
+ // starts waiting on it (it'll have _qMutex locked when this happens).
+ //
+ //boost::mutex::scoped_lock qLock(_qMutex);
+
+ // we might just clean the buffers here..
+ _parserThreadWakeup.notify_all();
+
+ return true;
}
+// would be called by parser thread
bool
FLVParser::parseNextChunk()
{
- static const int tagsPerChunk=10;
+ static const int tagsPerChunk=1;
for (int i=0; i<tagsPerChunk; ++i)
{
if ( ! parseNextTag() ) return false;
@@ -86,10 +115,22 @@
return true;
}
+// would be called by parser thread
bool FLVParser::parseNextTag()
{
+ // lock the stream while reading from it, so actionscript
+ // won't mess with the parser on seek or on getBytesLoaded
+ boost::mutex::scoped_lock streamLock(_streamMutex);
+
if ( _parsingComplete ) return false;
+ if ( _seekRequested )
+ {
+ clearBuffers();
+ _seekRequested = false;
+ }
+
+
// Seek to next frame and skip the size of the last tag
if ( _stream->seek(_lastParsedPosition+4) )
{
@@ -116,6 +157,11 @@
_lastParsedPosition += 15 + bodyLength;
+ if ( _lastParsedPosition > _bytesLoaded ) {
+ boost::mutex::scoped_lock lock(_bytesLoadedMutex);
+ _bytesLoaded = _lastParsedPosition;
+ }
+
// check for empty tag
if (bodyLength == 0) return true;
@@ -130,7 +176,16 @@
{
std::auto_ptr<EncodedAudioFrame> frame =
readAudioFrame(bodyLength-1, timestamp);
if ( ! frame.get() ) { log_error("could not read audio
frame?"); }
- else _audioFrames.push_back(frame.release());
+ else
+ {
+ // Release the stream lock
+ // *before* pushing the frame as that
+ // might block us waiting for buffers flush
+ // the _qMutex...
+ // We've done using the stream for this tag parsing
anyway
+ streamLock.unlock();
+ pushEncodedAudioFrame(frame);
+ }
// If this is the first audioframe no info about the
// audio format has been noted, so we do that now
@@ -149,9 +204,10 @@
_audioInfo.reset( new AudioInfo((tag[11] & 0xf0) >> 4,
samplerate, samplesize, (tag[11] & 0x01) >> 0, 0, FLASH) );
}
-
+ return true;
}
- else if (tag[0] == VIDEO_TAG)
+
+ if (tag[0] == VIDEO_TAG)
{
bool isKeyFrame = (tag[11] & 0xf0) >> 4;
UNUSED(isKeyFrame); // may be used for building seekable
indexes...
@@ -159,8 +215,11 @@
size_t dataPosition = _stream->tell();
std::auto_ptr<EncodedVideoFrame> frame =
readVideoFrame(bodyLength-1, timestamp);
- if ( ! frame.get() ) { log_error("could not read video
frame?"); }
- else _videoFrames.push_back(frame.release());
+ if ( ! frame.get() )
+ {
+ log_error("could not read video frame?");
+ return true;
+ }
// If this is the first videoframe no info about the
// video format has been noted, so we do that now
@@ -179,7 +238,7 @@
size_t bkpos = _stream->tell();
if ( _stream->seek(dataPosition) ) {
- log_error(" Couldn't seek to VideoTag
data position");
+ log_error(" Couldn't seek to VideoTag
data position -- should never happen, as we just read that!");
_parsingComplete=true;
return false;
}
@@ -235,7 +294,19 @@
_videoInfo.reset( new VideoInfo(codec, width, height, 0
/*frameRate*/, 0 /*duration*/, FLASH /*codec type*/) );
}
- } else if (tag[0] == META_TAG) {
+ // Release the stream lock
+ // *before* pushing the frame as that
+ // might block us waiting for buffers flush
+ // the _qMutex...
+ streamLock.unlock();
+ pushEncodedVideoFrame(frame);
+
+ return true;
+
+ }
+
+ if (tag[0] == META_TAG)
+ {
LOG_ONCE( log_unimpl("FLV MetaTag parser") );
// Extract information from the meta tag
/*_stream->seek(_lastParsedPosition+16);
@@ -251,15 +322,18 @@
amf::AMF* amfParser = new amf::AMF();
amfParser->parseAMF(metaTag);*/
- } else {
+ return true;
+
+ }
+
log_error("Unknown FLV tag type %d", tag[0]);
//_parsingComplete = true;
//return false;
- }
return true;
}
+// would be called by MAIN thread
bool FLVParser::parseHeader()
{
// seek to the begining of the file
@@ -272,7 +346,7 @@
log_error("FLVParser::parseHeader: couldn't read 9 bytes of
header");
return false;
}
- _lastParsedPosition = 9;
+ _lastParsedPosition = _bytesLoaded = 9;
// Check if this is really a FLV file
if (header[0] != 'F' || header[1] != 'L' || header[2] != 'V') return
false;
@@ -309,10 +383,12 @@
boost::uint64_t
FLVParser::getBytesLoaded() const
{
- return _lastParsedPosition;
- //return _stream->getBytesLoaded();
+ boost::mutex::scoped_lock lock(_bytesLoadedMutex);
+ //log_debug("FLVParser::getBytesLoaded returning %d/%d", _bytesLoaded,
_stream->size()); // _stream->size would need mutex-protection..
+ return _bytesLoaded;
}
+// would be called by parser thread
/*private*/
std::auto_ptr<EncodedAudioFrame>
FLVParser::readAudioFrame(boost::uint32_t dataSize, boost::uint32_t timestamp)
@@ -339,6 +415,7 @@
return frame;
}
+// would be called by parser thread
/*private*/
std::auto_ptr<EncodedVideoFrame>
FLVParser::readVideoFrame(boost::uint32_t dataSize, boost::uint32_t timestamp)
Index: libmedia/FLVParser.h
===================================================================
RCS file: /sources/gnash/gnash/libmedia/FLVParser.h,v
retrieving revision 1.16
retrieving revision 1.17
diff -u -b -r1.16 -r1.17
--- libmedia/FLVParser.h 9 Jun 2008 14:31:53 -0000 1.16
+++ libmedia/FLVParser.h 16 Jun 2008 09:05:01 -0000 1.17
@@ -29,6 +29,8 @@
#include <vector>
#include <memory>
+#include <boost/thread/mutex.hpp>
+
namespace gnash {
namespace media {
@@ -175,18 +177,8 @@
/// Kills the parser...
~FLVParser();
- /// \brief
- /// Seeks to the closest possible position the given position,
- /// and returns the new position.
- //
- ///
- /// TODO: throw something for sending Seek.InvalidTime ?
- /// (triggered by seeks beyond the end of video or beyond what's
- /// downloaded so far)
- ///
- boost::uint32_t seek(boost::uint32_t);
-
- virtual bool parseNextChunk();
+ // see dox in MediaParser.h
+ virtual bool seek(boost::uint32_t&);
/// Parses next tag from the file
//
@@ -200,6 +192,8 @@
private:
+ virtual bool parseNextChunk();
+
/// Parses the header of the file
bool parseHeader();
@@ -207,8 +201,20 @@
inline boost::uint32_t getUInt24(boost::uint8_t* in);
/// The position where the parsing should continue from.
+ /// Will be reset on seek, and will be protected by the _streamMutex
boost::uint64_t _lastParsedPosition;
+ /// On seek, this flag will be set, while holding a lock on
_streamMutex.
+ /// The parser, when obtained a lock on _streamMutex, will check this
+ /// flag, if found to be true will clear the buffers and reset to false.
+ bool _seekRequested;
+
+ /// Number of bytes loaded
+ boost::uint64_t _bytesLoaded;
+
+ /// Mutex protecting _bytesLoaded (read by main, set by parser)
+ mutable boost::mutex _bytesLoadedMutex;
+
/// Audio frame cursor position
//
/// This is the video frame number that will
Index: libmedia/MediaParser.cpp
===================================================================
RCS file: /sources/gnash/gnash/libmedia/MediaParser.cpp,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -b -r1.2 -r1.3
--- libmedia/MediaParser.cpp 10 Jun 2008 06:12:12 -0000 1.2
+++ libmedia/MediaParser.cpp 16 Jun 2008 09:05:01 -0000 1.3
@@ -21,6 +21,8 @@
#include "MediaParser.h"
#include "log.h"
+#include <boost/bind.hpp>
+
namespace gnash {
namespace media {
@@ -29,15 +31,31 @@
_isAudioMp3(false),
_isAudioNellymoser(false),
_stream(stream),
- //_stream(new LoadThread),
- _parsingComplete(false)
-{
- //_stream->setStream(stream);
+ _parsingComplete(false),
+ _parserThread(0),
+ _parserThreadStartBarrier(2),
+ _parserThreadKillRequested(false),
+ _bufferTime(100) // 100 ms
+{
+#ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
+ log_debug("Starting MediaParser thread");
+ _parserThread.reset( new boost::thread(boost::bind(parserLoopStarter,
this)) );
+ _parserThreadStartBarrier.wait();
+#endif
}
boost::uint64_t
MediaParser::getBufferLength() const
{
+#ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
+ boost::mutex::scoped_lock lock(_qMutex);
+#endif
+ return getBufferLengthNoLock();
+}
+
+boost::uint64_t
+MediaParser::getBufferLengthNoLock() const
+{
bool hasVideo = _videoInfo.get();
bool hasAudio = _audioInfo.get();
@@ -77,11 +95,16 @@
const EncodedVideoFrame*
MediaParser::peekNextVideoFrame() const
{
- if (_videoFrames.empty())
+#ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
+ boost::mutex::scoped_lock lock(_qMutex);
+#else // ndef LOAD_MEDIA_IN_A_SEPARATE_THREAD
+ while (!parsingCompleted() && _videoInfo.get() && _videoFrames.empty())
{
- log_debug("MediaParser::peekNextVideoFrame: no more video
frames here...");
- return 0;
+ const_cast<MediaParser*>(this)->parseNextChunk();
}
+#endif
+
+ if (!_videoInfo.get() || _videoFrames.empty()) return 0;
return _videoFrames.front();
}
@@ -97,20 +120,46 @@
std::auto_ptr<EncodedVideoFrame>
MediaParser::nextVideoFrame()
{
+#ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
+ boost::mutex::scoped_lock lock(_qMutex);
+#else // ndef LOAD_MEDIA_IN_A_SEPARATE_THREAD
+ while (!parsingCompleted() && _videoInfo.get() && _videoFrames.empty())
+ {
+ const_cast<MediaParser*>(this)->parseNextChunk();
+ }
+#endif
+
std::auto_ptr<EncodedVideoFrame> ret;
if (_videoFrames.empty()) return ret;
ret.reset(_videoFrames.front());
_videoFrames.pop_front();
+#ifdef GNASH_DEBUG_MEDIAPARSER
+ log_debug("nextVideoFrame: waking up parser (in case it was sleeping)");
+#endif // GNASH_DEBUG_MEDIAPARSER
+ _parserThreadWakeup.notify_all(); // wake it up, to refill the buffer,
SHOULDN'T WE HOLD A LoCK HERE?
return ret;
}
std::auto_ptr<EncodedAudioFrame>
MediaParser::nextAudioFrame()
{
+#ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
+ boost::mutex::scoped_lock lock(_qMutex);
+#else // ndef LOAD_MEDIA_IN_A_SEPARATE_THREAD
+ while (!parsingCompleted() && _audioInfo.get() && _audioFrames.empty())
+ {
+ const_cast<MediaParser*>(this)->parseNextChunk();
+ }
+#endif
+
std::auto_ptr<EncodedAudioFrame> ret;
if (_audioFrames.empty()) return ret;
ret.reset(_audioFrames.front());
_audioFrames.pop_front();
+#ifdef GNASH_DEBUG_MEDIAPARSER
+ log_debug("nextAudioFrame: waking up parser (in case it was sleeping)");
+#endif // GNASH_DEBUG_MEDIAPARSER
+ _parserThreadWakeup.notify_all(); // wake it up, to refill the buffer,
SHOULDN'T WE HOLD A LoCK HERE?
return ret;
}
@@ -126,12 +175,26 @@
const EncodedAudioFrame*
MediaParser::peekNextAudioFrame() const
{
+#ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
+ boost::mutex::scoped_lock lock(_qMutex);
+#else // ndef LOAD_MEDIA_IN_A_SEPARATE_THREAD
+ while (!parsingCompleted() && _audioInfo.get() && _audioFrames.empty())
+ {
+ const_cast<MediaParser*>(this)->parseNextChunk();
+ }
+#endif
if (!_audioInfo.get() || _audioFrames.empty()) return 0;
return _audioFrames.front();
}
MediaParser::~MediaParser()
{
+ if ( _parserThread.get() )
+ {
+ requestParserThreadKill();
+ _parserThread->join();
+ }
+
for (VideoFrames::iterator i=_videoFrames.begin(),
e=_videoFrames.end(); i!=e; ++i)
{
@@ -145,6 +208,135 @@
}
}
+void
+MediaParser::clearBuffers()
+{
+#ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
+ boost::mutex::scoped_lock lock(_qMutex);
+#endif
+
+ for (VideoFrames::iterator i=_videoFrames.begin(),
+ e=_videoFrames.end(); i!=e; ++i)
+ {
+ delete (*i);
+ }
+
+ for (AudioFrames::iterator i=_audioFrames.begin(),
+ e=_audioFrames.end(); i!=e; ++i)
+ {
+ delete (*i);
+ }
+
+ _audioFrames.clear();
+ _videoFrames.clear();
+
+ _parserThreadWakeup.notify_all(); // wake it up, to refill the buffer
+}
+
+void
+MediaParser::pushEncodedAudioFrame(std::auto_ptr<EncodedAudioFrame> frame)
+{
+#ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
+ boost::mutex::scoped_lock lock(_qMutex);
+#endif
+
+ // If last frame on queue has a timestamp > then this one, that's
either due
+ // to seek-back (most commonly) or a wierdly encoded media file.
+ // In any case, we'll flush the queue and restart from the new timestamp
+ if ( ! _audioFrames.empty() && _audioFrames.back()->timestamp >
frame->timestamp )
+ {
+ log_debug("Timestamp of last audio frame in queue (%d) "
+ "greater then timestamp in the frame being "
+ "pushed to it (%d). Flushing %d queue elements.",
+ _audioFrames.back()->timestamp, frame->timestamp,
+ _audioFrames.size());
+ for (AudioFrames::iterator i=_audioFrames.begin(),
+ e=_audioFrames.end(); i!=e; ++i)
+ {
+ delete (*i);
+ }
+ _audioFrames.clear();
+ }
+
+ _audioFrames.push_back(frame.release());
+#ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
+ waitIfNeeded(lock); // if the push reaches a "buffer full" condition,
wait to be waken up
+#endif
+}
+
+void
+MediaParser::pushEncodedVideoFrame(std::auto_ptr<EncodedVideoFrame> frame)
+{
+#ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
+ boost::mutex::scoped_lock lock(_qMutex);
+#endif
+
+ // If last frame on queue has a timestamp > then this one, that's
either due
+ // to seek-back (most commonly) or a wierdly encoded media file.
+ // In any case, we'll flush the queue and restart from the new timestamp
+ if ( ! _videoFrames.empty() && _videoFrames.back()->timestamp() >
frame->timestamp() )
+ {
+ log_debug("Timestamp of last video frame in queue (%d) "
+ "greater then timestamp in the frame being "
+ "pushed to it (%d). Flushing %d queue elements.",
+ _videoFrames.back()->timestamp(), frame->timestamp(),
+ _videoFrames.size());
+ for (VideoFrames::iterator i=_videoFrames.begin(),
+ e=_videoFrames.end(); i!=e; ++i)
+ {
+ delete (*i);
+ }
+ _videoFrames.clear();
+ }
+
+ _videoFrames.push_back(frame.release());
+#ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
+ waitIfNeeded(lock); // if the push reaches a "buffer full" condition,
wait to be waken up
+#endif
+}
+
+void
+MediaParser::waitIfNeeded(boost::mutex::scoped_lock& lock)
+{
+ // We hold a lock on the queue here...
+ bool pc=parsingCompleted();
+ bool bf=bufferFull();
+ if ( pc||bf ) // TODO: or seekRequested ?
+ {
+#ifdef GNASH_DEBUG_MEDIAPARSER
+ log_debug("Parser thread waiting on wakeup lock,
parsingComplete=%d, bufferFull=%d", pc, bf);
+#endif // GNASH_DEBUG_MEDIAPARSER
+ _parserThreadWakeup.wait(lock);
+#ifdef GNASH_DEBUG_MEDIAPARSER
+ log_debug("Parser thread finished waiting on wakeup lock");
+#endif // GNASH_DEBUG_MEDIAPARSER
+ }
+}
+
+bool
+MediaParser::bufferFull() const
+{
+ // Callers are expected to hold a lock on _qMutex
+ int bl = getBufferLengthNoLock();
+ int bt = getBufferTime();
+#ifdef GNASH_DEBUG_MEDIAPARSER
+ log_debug("bufferFull: %d/%d", bl, bt);
+#endif // GNASH_DEBUG_MEDIAPARSER
+ return bl >= bt;
+}
+
+void
+MediaParser::parserLoop()
+{
+ _parserThreadStartBarrier.wait();
+ while (!parserThreadKillRequested())
+ {
+ parseNextChunk();
+ usleep(100); // no rush....
+ }
+}
+
+
} // end of gnash::media namespace
} // end of gnash namespace
Index: libmedia/MediaParser.h
===================================================================
RCS file: /sources/gnash/gnash/libmedia/MediaParser.h,v
retrieving revision 1.24
retrieving revision 1.25
diff -u -b -r1.24 -r1.25
--- libmedia/MediaParser.h 10 Jun 2008 06:12:12 -0000 1.24
+++ libmedia/MediaParser.h 16 Jun 2008 09:05:02 -0000 1.25
@@ -28,9 +28,16 @@
#include "dsodefs.h" // DSOEXPORT
#include <boost/scoped_array.hpp>
+#include <boost/thread/thread.hpp>
+#include <boost/thread/condition.hpp>
+#include <boost/thread/barrier.hpp>
#include <memory>
#include <deque>
+// Undefine this to load/parse media files in main thread
+#define LOAD_MEDIA_IN_A_SEPARATE_THREAD 1
+
+
namespace gnash {
namespace media {
@@ -235,6 +242,22 @@
//
virtual ~MediaParser();
+ /// \brief
+ /// Seeks to the closest possible position the given position,
+ /// and returns the new position.
+ //
+ ///
+ /// TODO: throw something for sending Seek.InvalidTime ?
+ /// (triggered by seeks beyond the end of video or beyond what's
+ /// downloaded so far)
+ ///
+ /// @param time input/output parameter, input requests a time, output
+ /// return the actual time seeked to.
+ ///
+ /// @return true if the seek was valid, false otherwise
+ ///
+ virtual bool seek(boost::uint32_t& time)=0;
+
/// Returns mininum length of available buffers in milliseconds
//
/// TODO: FIXME: NOTE: this is currently used by NetStream.bufferLength
@@ -246,6 +269,20 @@
///
DSOEXPORT boost::uint64_t getBufferLength() const;
+ /// Return the time we want the parser thread to maintain in the buffer
+ DSOEXPORT boost::uint64_t getBufferTime() const
+ {
+ boost::mutex::scoped_lock lock(_bufferTimeMutex);
+ return _bufferTime;
+ }
+
+ /// Set the time we want the parser thread to maintain in the buffer
+ DSOEXPORT void setBufferTime(boost::uint64_t t)
+ {
+ boost::mutex::scoped_lock lock(_bufferTimeMutex);
+ _bufferTime=t;
+ }
+
/// Get timestamp of the video frame which would be returned on
nextVideoFrame
//
/// @return false if there no video frame left
@@ -308,51 +345,13 @@
///
AudioInfo* getAudioInfo() { return _audioInfo.get(); }
- /// Seeks to the closest possible position the given position.
- //
- /// Valid seekable position are constrained by key-frames when
- /// video data is available. Actual seek position should be always
- /// less of equal the requested one.
- ///
- /// @return the position the seek reached
- ///
- virtual boost::uint32_t seek(boost::uint32_t) { return 0; }
-
- /// Returns the framedelay from the last to the current
- /// audioframe in milliseconds. This is used for framerate.
- //
- /// @return the diff between the current and last frame
- ///
- //virtual boost::uint32_t audioFrameDelay() { return 0; }
-
- /// Returns the framedelay from the last to the current
- /// videoframe in milliseconds.
- //
- /// @return the diff between the current and last frame
- ///
- //virtual boost::uint32_t videoFrameDelay() { return 0; }
-
- /// Returns the framerate of the video
- //
- /// @return the framerate of the video
- ///
- //virtual boost::uint16_t videoFrameRate() { return 0; }
-
- /// Returns the last parsed position in the file in bytes
- //virtual boost::uint32_t getLastParsedPos() { return 0; }
-
- /// Parse next input chunk
- //
- /// Returns true if something was parsed, false otherwise.
- /// See parsingCompleted().
- ///
- virtual bool parseNextChunk() { return false; }
-
/// Return true of parsing is completed
//
/// If this function returns true, any call to nextVideoFrame()
/// or nextAudioFrame() will always return NULL
///
+ /// TODO: make thread-safe
+ ///
bool parsingCompleted() const { return _parsingComplete; }
/// Return number of bytes parsed so far
@@ -364,22 +363,28 @@
return _stream->size();
}
+ /// Parse next chunk of input
+ //
+ /// The implementations are required to parse a small chunk
+ /// of input, so to avoid blocking too much if parsing conditions
+ /// change (ie: seek or destruction requested)
+ ///
+ /// When LOAD_MEDIA_IN_A_SEPARATE_THREAD is defined, this should
+ /// never be called by users (consider protected).
+ ///
+ virtual bool parseNextChunk()=0;
+
protected:
- typedef std::deque<EncodedVideoFrame*> VideoFrames;
- typedef std::deque<EncodedAudioFrame*> AudioFrames;
- /// Queue of video frames (the video buffer)
- //
- /// Elements owned by this class.
- ///
- VideoFrames _videoFrames;
+ /// Clear the a/v buffers
+ void clearBuffers();
- /// Queue of audio frames (the audio buffer)
- //
- /// Elements owned by this class.
- ///
- AudioFrames _audioFrames;
+ /// Push an encoded audio frame to buffer, will wait on a condition if
buffer is full
+ void pushEncodedAudioFrame(std::auto_ptr<EncodedAudioFrame> frame);
+
+ /// Push an encoded video frame to buffer, will wait on a condition if
buffer is full
+ void pushEncodedVideoFrame(std::auto_ptr<EncodedVideoFrame> frame);
/// Return pointer to next encoded video frame in buffer
//
@@ -413,17 +418,99 @@
/// The stream used to access the file
std::auto_ptr<IOChannel> _stream;
+ mutable boost::mutex _streamMutex;
/// Whether the parsing is complete or not
bool _parsingComplete;
+ static void parserLoopStarter(MediaParser* mp)
+ {
+ mp->parserLoop();
+ }
+
+ /// The parser loop runs in a separate thread
+ /// and calls parseNextChunk until killed.
+ ///
+ /// parseNextChunk is expected to push encoded frames
+ /// on the queue, which may trigger the thread to be
+ /// put to sleep when queues are full or parsing
+ /// was completed.
+ ///
+ void parserLoop();
+
+ bool parserThreadKillRequested() const
+ {
+ boost::mutex::scoped_lock lock(_parserThreadKillRequestMutex);
+ return _parserThreadKillRequested;
+ }
+
+ boost::uint64_t _bufferTime;
+ mutable boost::mutex _bufferTimeMutex;
+
+ std::auto_ptr<boost::thread> _parserThread;
+ boost::barrier _parserThreadStartBarrier;
+ mutable boost::mutex _parserThreadKillRequestMutex;
+ bool _parserThreadKillRequested;
+ boost::condition _parserThreadWakeup;
+
+ bool _seekRequest;
+ mutable boost::mutex _seekRequestMutex;
+
+ bool seekRequested() const;
+
+ void seekBegin();
+ void seekEnd();
+
+ /// Wait on the _parserThreadWakeup condition if buffer is full
+ /// or parsing was completed.
+ ///
+ /// Callers *must* pass a locked lock on _qMutex
+ ///
+ void waitIfNeeded(boost::mutex::scoped_lock& qMutexLock);
+
+ void wakeupParserThread();
+
+ /// mutex protecting access to the a/v encoded frames queues
+ mutable boost::mutex _qMutex;
+
private:
+ // Method to check if buffer is full w/out locking the _qMutex
+ // This is intended for being called by waitIfNeeded, which
+ // is passed a locked lock on _qMutex
+ bool bufferFull() const;
+
+ typedef std::deque<EncodedVideoFrame*> VideoFrames;
+ typedef std::deque<EncodedAudioFrame*> AudioFrames;
+
+ /// Queue of video frames (the video buffer)
+ //
+ /// Elements owned by this class.
+ ///
+ VideoFrames _videoFrames;
+
+ /// Queue of audio frames (the audio buffer)
+ //
+ /// Elements owned by this class.
+ ///
+ AudioFrames _audioFrames;
+
+ void requestParserThreadKill()
+ {
+ boost::mutex::scoped_lock lock(_parserThreadKillRequestMutex);
+ _parserThreadKillRequested=true;
+ _parserThreadWakeup.notify_all();
+ }
+
/// Return diff between timestamp of last and first audio frame
boost::uint64_t audioBufferLength() const;
/// Return diff between timestamp of last and first video frame
boost::uint64_t videoBufferLength() const;
+
+ /// A getBufferLength method not locking the _qMutex (expected to be
locked by caller already).
+ boost::uint64_t getBufferLengthNoLock() const;
+
};
Index: libmedia/ffmpeg/MediaParserFfmpeg.cpp
===================================================================
RCS file: /sources/gnash/gnash/libmedia/ffmpeg/MediaParserFfmpeg.cpp,v
retrieving revision 1.13
retrieving revision 1.14
diff -u -b -r1.13 -r1.14
--- libmedia/ffmpeg/MediaParserFfmpeg.cpp 10 Jun 2008 06:12:12 -0000
1.13
+++ libmedia/ffmpeg/MediaParserFfmpeg.cpp 16 Jun 2008 09:05:02 -0000
1.14
@@ -82,9 +82,12 @@
return ret;
}
-boost::uint32_t
-MediaParserFfmpeg::seek(boost::uint32_t pos)
+bool
+MediaParserFfmpeg::seek(boost::uint32_t& pos)
{
+ LOG_ONCE(log_unimpl("MediaParserFfmpeg::seek()"));
+ return false;
+
log_debug("MediaParserFfmpeg::seek(%d) TESTING", pos);
AVStream* videostream = _formatCtx->streams[_videoStreamIndex];
@@ -150,7 +153,7 @@
info->dataPosition = pos;
info->timestamp = timestamp;
- _videoFrames.push_back(info); // takes ownership
+ pushEncodedVideoFrame(info);
return true;
#endif
@@ -180,7 +183,7 @@
frame->dataSize = packet.size
frame->timestamp = timestamp;
- _audioFrames.push_back(frame.release()); // takes ownership
+ pushEncodedAudioFrame(frame);
return true;
#endif
@@ -244,13 +247,6 @@
{
_lastParsedPosition = curPos;
}
- log_debug("parseNextFrame: parsed %d+%d/%d bytes (byteIOCxt: pos:%d,
buf_ptr:%p, buf_end:%p); "
- " AVFormatContext: data_offset:%d, cur_ptr:%p,; "
- "%d video frames, %d audio frames",
- curPos, _formatCtx->cur_ptr-_formatCtx->cur_pkt.data,
_stream->size(),
- _byteIOCxt.pos, (void*)_byteIOCxt.buf_ptr,
(void*)_byteIOCxt.buf_end,
- _formatCtx->data_offset, (void*)_formatCtx->cur_ptr,
- _videoFrames.size(), _audioFrames.size());
return ret;
}
Index: libmedia/ffmpeg/MediaParserFfmpeg.h
===================================================================
RCS file: /sources/gnash/gnash/libmedia/ffmpeg/MediaParserFfmpeg.h,v
retrieving revision 1.9
retrieving revision 1.10
diff -u -b -r1.9 -r1.10
--- libmedia/ffmpeg/MediaParserFfmpeg.h 9 Jun 2008 14:31:53 -0000 1.9
+++ libmedia/ffmpeg/MediaParserFfmpeg.h 16 Jun 2008 09:05:02 -0000 1.10
@@ -65,7 +65,7 @@
~MediaParserFfmpeg();
// See dox in MediaParser.h
- virtual boost::uint32_t seek(boost::uint32_t);
+ virtual bool seek(boost::uint32_t&);
// See dox in MediaParser.h
virtual bool parseNextChunk();
Index: server/asobj/NetStream.cpp
===================================================================
RCS file: /sources/gnash/gnash/server/asobj/NetStream.cpp,v
retrieving revision 1.98
retrieving revision 1.99
diff -u -b -r1.98 -r1.99
--- server/asobj/NetStream.cpp 6 Jun 2008 21:06:55 -0000 1.98
+++ server/asobj/NetStream.cpp 16 Jun 2008 09:05:02 -0000 1.99
@@ -191,6 +191,9 @@
{
time = fn.arg(0).to_number();
}
+
+ // TODO: don't allow a limit < 100
+
ns->setBufferTime(boost::uint32_t(time*1000));
return as_value();
@@ -523,6 +526,7 @@
{
// The argument is in milliseconds,
m_bufferTime = time;
+ if ( m_parser.get() ) m_parser->setBufferTime(time);
}
long
Index: server/asobj/NetStreamFfmpeg.cpp
===================================================================
RCS file: /sources/gnash/gnash/server/asobj/NetStreamFfmpeg.cpp,v
retrieving revision 1.150
retrieving revision 1.151
diff -u -b -r1.150 -r1.151
--- server/asobj/NetStreamFfmpeg.cpp 9 Jun 2008 18:12:57 -0000 1.150
+++ server/asobj/NetStreamFfmpeg.cpp 16 Jun 2008 09:05:02 -0000 1.151
@@ -36,6 +36,7 @@
#include "VideoDecoder.h"
#include "AudioDecoder.h"
#include "MediaHandler.h"
+#include "VM.h"
#include "SystemClock.h"
#include "gnash.h" // get_sound_handler()
@@ -59,7 +60,7 @@
//#define GNASH_DEBUG_STATUS
// Define the following macro to have decoding activity debugged
-//#define GNASH_DEBUG_DECODING
+//#define GNASH_DEBUG_DECODING 1
namespace gnash {
@@ -69,22 +70,14 @@
_decoding_state(DEC_NONE),
-#ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
- _parserThread(NULL),
- _parserThreadBarrier(2), // main and decoder threads
- _parserKillRequest(false),
-#endif
-
- m_last_video_timestamp(0),
- m_last_audio_timestamp(0),
-
+ // TODO: if audio is available, use _audioClock instead of SystemClock
+ // as additional source
_playbackClock(new InterruptableVirtualClock(new SystemClock)),
_playHead(_playbackClock.get()),
- m_unqueued_data(NULL),
-
_soundHandler(get_sound_handler()),
- _mediaHandler(media::MediaHandler::get())
+ _mediaHandler(media::MediaHandler::get()),
+ _audioQueueSize(0)
{
}
@@ -119,10 +112,6 @@
{
GNASH_REPORT_FUNCTION;
-#ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
- killParserThread();
-#endif
-
// When closing gnash before playback is finished, the soundhandler
// seems to be removed before netstream is destroyed.
if (_soundHandler)
@@ -132,9 +121,6 @@
m_imageframe.reset();
- delete m_unqueued_data;
- m_unqueued_data = NULL;
-
}
void
@@ -196,12 +182,6 @@
if (_soundHandler)
_soundHandler->attach_aux_streamer(audio_streamer, this);
-#ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
- // This starts the parser thread
- _parserThread = new
boost::thread(boost::bind(NetStreamFfmpeg::parseAllInput, this));
- _parserThreadBarrier.wait();
-#endif // LOAD_MEDIA_IN_A_SEPARATE_THREAD
-
return;
}
@@ -267,6 +247,8 @@
return false;
}
+ m_parser->setBufferTime(m_bufferTime);
+
initVideoDecoder(*m_parser);
initAudioDecoder(*m_parser);
@@ -274,6 +256,7 @@
_playHead.setState(PlayHead::PLAY_PLAYING);
decodingStatus(DEC_BUFFERING);
+ _playbackClock->pause(); // NOTE: should be paused already
#ifdef GNASH_DEBUG_STATUS
log_debug("Setting playStart status");
@@ -284,59 +267,6 @@
}
-#ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
-// to be run in parser thread
-void NetStreamFfmpeg::parseAllInput(NetStreamFfmpeg* ns)
-{
- //GNASH_REPORT_FUNCTION;
-
- ns->_parserThreadBarrier.wait();
-
- // Parse in a thread...
- while ( 1 )
- {
- // this one will lock _parserKillRequestMutex
- if ( ns->parserThreadKillRequested() ) break;
-
- {
- boost::mutex::scoped_lock lock(ns->_parserMutex);
- if ( ns->m_parser->parsingCompleted() ) break;
- ns->m_parser->parseNextTag();
- }
-
- usleep(10); // task switch (after lock was released!)
- }
-}
-
-void
-NetStreamFfmpeg::killParserThread()
-{
- GNASH_REPORT_FUNCTION;
-
- {
- boost::mutex::scoped_lock lock(_parserKillRequestMutex);
- _parserKillRequest = true;
- }
-
- // might as well be never started
- if ( _parserThread )
- {
- _parserThread->join();
- }
-
- delete _parserThread;
- _parserThread = NULL;
-}
-
-bool
-NetStreamFfmpeg::parserThreadKillRequested()
-{
- boost::mutex::scoped_lock lock(_parserKillRequestMutex);
- return _parserKillRequest;
-}
-
-#endif // LOAD_MEDIA_IN_A_SEPARATE_THREAD
-
// audio callback is running in sound handler thread
bool NetStreamFfmpeg::audio_streamer(void *owner, boost::uint8_t *stream, int
len)
{
@@ -376,6 +306,8 @@
ns->_audioQueue.pop_front();
}
+ ns->_audioQueueSize -= n; // we consumed 'n' bytes here
+
}
return true;
@@ -396,12 +328,25 @@
}
boost::uint64_t nextTimestamp;
+ bool parsingComplete = m_parser->parsingCompleted();
if ( ! m_parser->nextVideoFrameTimestamp(nextTimestamp) )
{
#ifdef GNASH_DEBUG_DECODING
- log_debug("getDecodedVideoFrame(%d): no more video frames in
input (nextVideoFrameTimestamp returned false)");
+ log_debug("getDecodedVideoFrame(%d): "
+ "no more video frames in input "
+ "(nextVideoFrameTimestamp returned false, "
+ "parsingComplete=%d)",
+ ts, parsingComplete);
#endif // GNASH_DEBUG_DECODING
+
+ if ( parsingComplete )
+ {
decodingStatus(DEC_STOPPED);
+#ifdef GNASH_DEBUG_STATUS
+ log_debug("getDecodedVideoFrame setting playStop status
(parsing complete and nextVideoFrameTimestamp() returned false)");
+#endif
+ setStatus(playStop);
+ }
return video;
}
@@ -473,6 +418,18 @@
return video;
}
+#if 0 // TODO: check if the video is a cue point, if so, call
processNotify(onCuePoint, object..)
+ // NOTE: should only be done for SWF>=8 ?
+ if ( 1 ) // frame->isKeyFrame() )
+ {
+ as_object* infoObj = new as_object();
+ string_table& st = getVM().getStringTable();
+ infoObj->set_member(st.find("time"),
as_value(double(frame->timestamp())));
+ infoObj->set_member(st.find("type"), as_value("navigation"));
+ processNotify("onCuePoint", infoObj);
+ }
+#endif
+
assert( _videoDecoder.get() ); // caller should check this
assert( ! _videoDecoder->peek() ); // everything we push, we'll pop
too..
@@ -530,10 +487,6 @@
{
GNASH_REPORT_FUNCTION;
-#ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
- boost::mutex::scoped_lock lock(_parserMutex);
-#endif // LOAD_MEDIA_IN_A_SEPARATE_THREAD
-
// We'll mess with the input here
if ( ! m_parser.get() )
{
@@ -544,8 +497,6 @@
// Don't ask me why, but NetStream::seek() takes seconds...
boost::uint32_t pos = posSeconds*1000;
- long newpos = 0;
-
// We'll pause the clock source and mark decoders as buffering.
// In this way, next advance won't find the source time to
// be a lot of time behind and chances to get audio buffer
@@ -553,14 +504,21 @@
// ::advance will resume the playbackClock if DEC_BUFFERING...
//
_playbackClock->pause();
- decodingStatus(DEC_BUFFERING);
// Seek to new position
- newpos = m_parser->seek(pos);
+ boost::uint32_t newpos = pos;
+ if ( ! m_parser->seek(newpos) )
+ {
+ //log_error("Seek to invalid time");
+#ifdef GNASH_DEBUG_STATUS
+ log_debug("Setting invalidTime status");
+#endif
+ setStatus(invalidTime);
+ _playbackClock->resume(); // we won't be *BUFFERING*, so resume
now
+ return;
+ }
log_debug("m_parser->seek(%d) returned %d", pos, newpos);
- m_last_audio_timestamp = m_last_video_timestamp = newpos;
-
{ // cleanup audio queue, so won't be consumed while seeking
boost::mutex::scoped_lock lock(_audioQueueMutex);
for (AudioQueue::iterator i=_audioQueue.begin(),
e=_audioQueue.end();
@@ -573,7 +531,7 @@
// 'newpos' will always be on a keyframe (supposedly)
_playHead.seekTo(newpos);
- _qFillerResume.notify_all(); // wake it decoder is sleeping
+ decodingStatus(DEC_BUFFERING);
refreshVideoFrame(true);
}
@@ -581,8 +539,12 @@
void
NetStreamFfmpeg::parseNextChunk()
{
- // TODO: parse as much as possible w/out blocking
- // (will always block currently..)
+ // If we parse too much we might block
+ // the main thread, if we parse too few
+ // we'll get bufferEmpty often.
+ // I guess 2 chunks (frames) would be fine..
+ //
+ m_parser->parseNextChunk();
m_parser->parseNextChunk();
}
@@ -603,8 +565,8 @@
{
#ifdef GNASH_DEBUG_DECODING
log_debug("%p.refreshAudioBuffer: doing nothing as playhead is
paused - "
- "bufferLength=%d, bufferTime=%d",
- this, bufferLen, m_bufferTime);
+ "bufferLength=%d/%d",
+ this, bufferLength(), m_bufferTime);
#endif // GNASH_DEBUG_DECODING
return;
}
@@ -614,7 +576,7 @@
#ifdef GNASH_DEBUG_DECODING
log_debug("%p.refreshAudioBuffer: doing nothing "
"as current position was already decoded - "
- "bufferLength=%d, bufferTime=%d",
+ "bufferLength=%d/%d",
this, bufferLen, m_bufferTime);
#endif // GNASH_DEBUG_DECODING
return;
@@ -641,25 +603,129 @@
// nothing to do if we don't have an audio decoder
if ( ! _audioDecoder.get() ) return;
+ // just flush any pending audio frame if we're
+ // not willing to play sounds anyway
+ if ( ! _soundHandler )
+ {
+ while ( media::raw_mediadata_t* audio = decodeNextAudioFrame() )
+ delete audio;
+ _playHead.setAudioConsumed();
+ return;
+ }
+
bool consumed = false;
boost::uint64_t nextTimestamp;
while ( 1 )
{
+
+ boost::mutex::scoped_lock lock(_audioQueueMutex);
+
+ // The sound_handler mixer will pull decoded
+ // audio frames off the _audioQueue whenever
+ // new audio has to be played.
+ // This is done based on the output frequency,
+ // currently hard-coded to be 44100 samples per second.
+ //
+ // Our job here would be to provide that much data.
+ // We're in an ::advance loop, so must provide enough
+ // data for the mixer to fetch till next advance.
+ // Assuming we know the ::advance() frame rate (which we don't
+ // yet) the computation would be something along these lines:
+ //
+ // 44100/1 == samplesPerAdvance/secsPerAdvance
+ // samplesPerAdvance = secsPerAdvance*(44100/1)
+ //
+ // For example, at 12FPS we have:
+ //
+ // secsPerAdvance = 1/12 = .083333
+ // samplesPerAdvance = .08333*44100 =~ 3675
+ //
+ // Now, to know how many samples are on the queue
+ // we need to know the size in bytes of each sample.
+ // If I'm not wrong this is again hard-coded to 2 bytes,
+ // so we'd have:
+ //
+ // bytesPerAdvance = samplesPerAdvance / sampleSize
+ // bytesPerAdvance = 3675 / 2 =~ 1837
+ //
+ // Finally we'll need to find number of bytes in the
+ // queue to really tell how many there are (don't think
+ // it's a fixed size for each element).
+ //
+ // For now we use the hard-coded value of 20, arbitrarely
+ // assuming there is an average of 184 samples per frame.
+ //
+ // - If we push too few samples, we'll hear silence gaps
(underrun)
+ // - If we push too many samples the audio mixer consumer
+ // won't be able to consume all before our next filling
+ // iteration (overrun)
+ //
+ // For *underrun* conditions we kind of have an handling, that
is
+ // sending the BufferEmpty event and closing the time tap (this
is
+ // done by ::advance directly).
+ //
+ // For *overrun* conditions we currently don't have any
handling.
+ // One possibility could be closing the time tap till we've done
+ // consuming the queue.
+ //
+ //
+
+ float swfFPS = 25; // TODO: get this host app (gnash -d affects
this)
+ double msecsPerAdvance = 10000/swfFPS;
+
+ static const int outSampleSize = 2; // <--- 2 is output
sample size
+ static const int outSampleFreq = 44100; // <--- 44100 is output
audio frequency
+ //int samplesPerAdvance =
(int)std::floor(secsPerAdvance*outSampleFreq); // round up
+ //unsigned int bufferLimit = outSampleSize*samplesPerAdvance;
+
+ unsigned int bufferLimit = 20;
+ unsigned int bufferSize = _audioQueue.size();
+ if ( bufferSize > bufferLimit )
+ {
+ // we won't buffer more then 'bufferLimit' frames in
the queue
+ // to avoid ending up with a huge queue which will take
some
+ // time before being consumed by audio mixer, but still
marked
+ // as "consumed". Keeping decoded frames buffer low
would also
+ // reduce memory use.
+ //
+ // The alternative would be always decode on demand
from the
+ // audio consumer thread, but would introduce a lot of
thread-safety
+ // issues: playhead would need protection, input would
need protection.
+ //
+//#ifdef GNASH_DEBUG_DECODING
+ log_debug("%p.pushDecodedAudioFrames(%d) : buffer
overrun (%d/%d).",
+ this, ts, bufferSize, bufferLimit);
+//#endif // GNASH_DEBUG_DECODING
+
+ // we may want to pause the playbackClock here...
+ _playbackClock->pause();
+
+ return;
+ }
+
+ lock.unlock(); // no need to keep the audio queue locked while
decoding..
+
+ bool parsingComplete = m_parser->parsingCompleted();
if ( ! m_parser->nextAudioFrameTimestamp(nextTimestamp) )
{
#ifdef GNASH_DEBUG_DECODING
log_debug("%p.pushDecodedAudioFrames(%d): "
"no more audio frames in input "
- "(nextAudioFrameTimestamp returned false)",
- this, ts);
+ "(nextAudioFrameTimestamp returned false,
parsingComplete=%d)",
+ this, ts, parsingComplete);
#endif // GNASH_DEBUG_DECODING
+
+ if ( parsingComplete )
+ {
consumed = true;
decodingStatus(DEC_STOPPED);
#ifdef GNASH_DEBUG_STATUS
- log_debug("Setting playStop status");
+ log_debug("pushDecodedAudioFrames setting
playStop status (parsing complete and nextAudioFrameTimestamp returned false)");
#endif
setStatus(playStop);
+ }
+
break;
}
@@ -671,30 +737,8 @@
this, ts, nextTimestamp);
#endif // GNASH_DEBUG_DECODING
consumed = true;
- break; // next frame is in the future
- }
- boost::mutex::scoped_lock lock(_audioQueueMutex);
-
- static const unsigned int bufferLimit = 20;
- if ( _audioQueue.size() > bufferLimit )
- {
- // we won't buffer more then 'bufferLimit' frames in
the queue
- // to avoid ending up with a huge queue which will take
some
- // time before being consumed by audio mixer, but still
marked
- // as "consumed". Keeping decoded frames buffer low
would also
- // reduce memory use.
- //
- // The alternative would be always decode on demand
from the
- // audio consumer thread, but would introduce a lot of
thread-safety
- // issues: playhead would need protection, input would
need protection.
- //
-//#ifdef GNASH_DEBUG_DECODING
- log_debug("%p.pushDecodedAudioFrames(%d) : queue size
over limit (%d), "
- "audio won't be consumed (buffer overrun?)",
- this, ts, bufferLimit);
-//#endif // GNASH_DEBUG_DECODING
- return;
+ if ( nextTimestamp > ts+msecsPerAdvance ) break; //
next frame is in the future
}
media::raw_mediadata_t* audio = decodeNextAudioFrame();
@@ -706,16 +750,35 @@
break;
}
+ lock.lock(); // now needs locking
+
#ifdef GNASH_DEBUG_DECODING
// this one we might avoid :) -- a less intrusive logging could
// be take note about how many things we're pushing over
log_debug("pushDecodedAudioFrames(%d) pushing %dth frame with
timestamp %d", ts, _audioQueue.size()+1, nextTimestamp);
#endif
_audioQueue.push_back(audio);
+ _audioQueueSize += audio->m_size;
+ }
+
+ // If we consumed audio of current position, feel free to advance if
needed,
+ // resuming playbackClock too..
+ if ( consumed )
+ {
+ // resume the playback clock, assuming the
+ // only reason for it to be paused is we
+ // put in pause mode due to buffer overrun
+ // (ie: the sound handler is slow at consuming
+ // the audio data).
+#ifdef GNASH_DEBUG_DECODING
+ log_debug("resuming playback clock on audio consume");
+#endif // GNASH_DEBUG_DECODING
+ assert(decodingStatus()!=DEC_BUFFERING);
+ _playbackClock->resume();
+
+ _playHead.setAudioConsumed();
}
- // If we consumed audio of current position, feel free to advance if
needed
- if ( consumed ) _playHead.setAudioConsumed();
}
@@ -736,7 +799,7 @@
// so this is to avoid that.
boost::uint32_t parserTime = m_parser->getBufferLength();
boost::uint32_t playHeadTime = time();
- boost::uint32_t bufferLen = parserTime > playHeadTime ?
parserTime-playHeadTime : 0;
+ boost::uint32_t bufferLen = bufferLength();
#endif
if ( ! alsoIfPaused && _playHead.getState() == PlayHead::PLAY_PAUSED )
@@ -778,14 +841,10 @@
{
#ifdef GNASH_DEBUG_DECODING
log_debug("%p.refreshVideoFrame(): "
- "no more video frames to decode, "
- "sending STOP event",
+ "no more video frames to decode "
+ "(DEC_STOPPED, null from getDecodedVideoFrame)",
this);
#endif // GNASH_DEBUG_DECODING
-#ifdef GNASH_DEBUG_STATUS
- log_debug("Setting playStop status");
-#endif
- setStatus(playStop);
}
else
{
@@ -823,33 +882,27 @@
// pass them to a event handler
processStatusNotifications();
-#ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
- // stop parser thread while advancing
- boost::mutex::scoped_lock lock(_parserMutex);
-#endif // LOAD_MEDIA_IN_A_SEPARATE_THREAD
-
// Nothing to do if we don't have a parser
if ( ! m_parser.get() ) return;
- // bufferLength() would lock the mutex (which we already hold),
- // so this is to avoid that.
- boost::uint32_t parserTime = m_parser->getBufferLength();
- boost::uint32_t playHeadTime = time();
- boost::uint32_t bufferLen = parserTime > playHeadTime ?
parserTime-playHeadTime : 0;
-
-#ifndef LOAD_MEDIA_IN_A_SEPARATE_THREAD
- // Fill the buffer some more if not full ...
- if ( bufferLen < m_bufferTime && ! m_parser->parsingCompleted() )
+ if ( decodingStatus() == DEC_STOPPED )
{
- parseNextChunk();
+ //log_debug("NetStreamFfmpeg::advance: dec stopped...");
+ // nothing to do if we're stopped...
+ return;
}
-#endif // LOAD_MEDIA_IN_A_SEPARATE_THREAD
+ bool parsingComplete = m_parser->parsingCompleted();
+#ifndef LOAD_MEDIA_IN_A_SEPARATE_THREAD
+ if ( ! parsingComplete ) parseNextChunk();
+#endif
+
+ size_t bufferLen = bufferLength();
// Check decoding status
if ( decodingStatus() == DEC_DECODING && bufferLen == 0 )
{
- if ( ! m_parser->parsingCompleted() )
+ if ( ! parsingComplete )
{
#ifdef GNASH_DEBUG_DECODING
log_debug("%p.advance: buffer empty while decoding,"
@@ -875,7 +928,7 @@
if ( decodingStatus() == DEC_BUFFERING )
{
- if ( bufferLen < m_bufferTime && ! m_parser->parsingCompleted()
)
+ if ( bufferLen < m_bufferTime && ! parsingComplete )
{
#ifdef GNASH_DEBUG_DECODING
log_debug("%p.advance: buffering"
@@ -962,10 +1015,6 @@
long
NetStreamFfmpeg::bytesTotal ()
{
-#ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
- boost::mutex::scoped_lock lock(_parserMutex);
-#endif // LOAD_MEDIA_IN_A_SEPARATE_THREAD
-
if ( ! m_parser.get() )
{
log_debug("bytesTotal: no parser, no party");
Index: server/asobj/NetStreamFfmpeg.h
===================================================================
RCS file: /sources/gnash/gnash/server/asobj/NetStreamFfmpeg.h,v
retrieving revision 1.75
retrieving revision 1.76
diff -u -b -r1.75 -r1.76
--- server/asobj/NetStreamFfmpeg.h 9 Jun 2008 14:31:55 -0000 1.75
+++ server/asobj/NetStreamFfmpeg.h 16 Jun 2008 09:05:02 -0000 1.76
@@ -52,10 +52,6 @@
#include <memory>
#include <cassert>
-
-/// Uncomment the following to load media in a separate thread
-//#define LOAD_MEDIA_IN_A_SEPARATE_THREAD
-
// Forward declarations
namespace gnash {
class IOChannel;
@@ -96,11 +92,6 @@
// See dox in NetStream.h
void advance();
-#ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
- /// The parsing thread. Sets up the decoder, and decodes.
- static void parseAllInput(NetStreamFfmpeg* ns);
-#endif
-
/// Callback used by sound_handler to get audio data
//
/// This is a sound_handler::aux_streamer_ptr type.
@@ -126,7 +117,7 @@
DEC_NONE,
DEC_STOPPED,
DEC_DECODING,
- DEC_BUFFERING,
+ DEC_BUFFERING
};
/// Gets video info from the parser and initializes _videoDecoder
@@ -234,58 +225,12 @@
/// Audio decoder
std::auto_ptr<media::AudioDecoder> _audioDecoder;
-#ifdef LOAD_MEDIA_IN_A_SEPARATE_THREAD
- /// The parser thread
- boost::thread* _parserThread;
-
- /// Barrier to synchronize thread and thread starter
- boost::barrier _parserThreadBarrier;
-
- /// Mutex serializing access to parser,
- /// when reading from a separate thread
- boost::mutex _parserMutex;
-
- /// Kill decoder thread, if any
- //
- /// POSTCONDITIONS:
- /// _decodeThread is NULL
- /// decoder thread is not running
- ///
- /// Uses the _qMutex
- ///
- void killParserThread();
-
- /// Return true if kill of parser thread was requested
- bool parserThreadKillRequested();
-
- /// Protected by _parserKillRequestMutex
- bool _parserKillRequest;
-
- /// Mutex protecting _parserKillRequest
- boost::mutex _parserKillRequestMutex;
-
-#endif // LOAD_MEDIA_IN_A_SEPARATE_THREAD
-
-
- // The timestamp of the last decoded video frame, in seconds.
- volatile boost::uint32_t m_last_video_timestamp;
-
- // The timestamp of the last decoded audio frame, in seconds.
- volatile boost::uint32_t m_last_audio_timestamp;
-
- /// Queues filler will wait on this condition when queues are full
- boost::condition _qFillerResume;
-
/// Virtual clock used as playback clock source
std::auto_ptr<InterruptableVirtualClock> _playbackClock;
/// Playback control device
PlayHead _playHead;
- // When the queues are full, this is where we keep the audio/video frame
- // there wasn't room for on its queue
- media::raw_mediadata_t* m_unqueued_data;
-
// Current sound handler
media::sound_handler* _soundHandler;
@@ -310,6 +255,9 @@
/// and consumed by sound_handler callback (audio_streamer)
AudioQueue _audioQueue;
+ /// Number of bytes in the audio queue, protected by _audioQueueMutex
+ size_t _audioQueueSize;
+
/// The queue needs to be protected as sound_handler callback
/// is invoked by a separate thread (dunno if it makes sense actually)
boost::mutex _audioQueueMutex;