/* [<][>][^][v][top][bottom][index][help] */
// MediaParser.h: Base class for media parsers
//
// Copyright (C) 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program 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
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#ifndef GNASH_MEDIAPARSER_H
#define GNASH_MEDIAPARSER_H
#include "IOChannel.h" // for inlines
#include "dsodefs.h" // DSOEXPORT
#include <boost/scoped_array.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/thread/thread.hpp>
#include <boost/thread/condition.hpp>
#include <boost/thread/barrier.hpp>
#include <memory>
#include <deque>
#include <map>
#include <vector>
#include <iosfwd> // for output operator forward declarations
// Undefine this to load/parse media files in main thread
#define LOAD_MEDIA_IN_A_SEPARATE_THREAD 1
namespace gnash {
class SimpleBuffer;
}
namespace gnash {
namespace media {
/// Video frame types
enum videoFrameType
{
/// Key frames
KEY_FRAME = 1,
/// Interlaced frames
INTER_FRAME = 2,
/// Disposable interlaced frames
DIS_INTER_FRAME = 3
};
/// The type of the codec id passed in the AudioInfo or VideoInfo class
enum codecType
{
/// The internal flash codec ids
CODEC_TYPE_FLASH,
/// Custom codecs ids
CODEC_TYPE_CUSTOM
};
/// Video codec ids as defined in flash
enum videoCodecType
{
/// H263/SVQ3 video codec
VIDEO_CODEC_H263 = 2,
/// Screenvideo codec
VIDEO_CODEC_SCREENVIDEO = 3,
/// On2 VP6 video codec
VIDEO_CODEC_VP6 = 4,
/// On2 VP6 Alpha video codec
VIDEO_CODEC_VP6A = 5,
/// Screenvideo2 codec
VIDEO_CODEC_SCREENVIDEO2 = 6,
/// MPEG-4 Part 10, or Advanced Video Coding
VIDEO_CODEC_H264 = 7
// NOTE: if you add more elements here remember to
// also add them to the output operator!
};
DSOEXPORT std::ostream& operator<< (std::ostream& os, const videoCodecType& t);
/// Audio codec ids as defined in flash
//
/// For some encodings, audio data is organized
/// in logical frames. The structure of such frames
/// (header/payload) is codec dependent.
/// The actual size of each frame may not be known
/// w/out parsing the encoded stream, as it
/// might be specified in the header of each frame.
///
/// Other encodings are loosier on frames. For these
/// you can define a frame any way you want, as long
/// as a frame doesn't contain partial samples.
///
/// For FFMPEG, you can NOT construct a parser for the
/// loosy-framed codecs.
///
/// Parser-needing codecs will be documented as such.
///
enum audioCodecType
{
/// Signed Linear PCM, unspecified byte order
//
/// Use of this codec is deprecated (but still supported) due to
/// the unspecified byte order (you can only play >8bit samples
/// in a sane way when the endiannes of encoding and decoding
/// hosts match).
///
/// 90% of the times the actual encoder did run on windows, so
/// it is a good bet to guess for little-endian.
/// SampleSize may be 8 or 16 bits.
///
AUDIO_CODEC_RAW = 0,
/// ADPCM format
//
/// SWF support 2, 3, 4, and 5 bits / sample.
/// ADPCM "frames" consits of 4096 ADPCM codes per channel.
///
/// For streaming there is no concept of "seekSamples" like
/// MP3 streaming implements. Thus ADPCM ist suboptimal for
/// streaming as it is difficult to match sound frames with
/// movie frames.
/// Uncompressed SampleSize is always 16 bit.
///
AUDIO_CODEC_ADPCM = 1,
/// MP3 format
//
/// MP3 is supported for SWF4 and later.
/// MP3 sound is structured in frames consiting of a fixed sized
/// header (32Bit) and compressed sound data. Compressed sound
/// data always contains a fixed number of sound samples (576 or 1152).
/// For streaming sound an additional field is necessary (seekSamples)
/// to keep track of samples exceeding movie frame border.
///
/// MP3 header contains all necessary information to decode a single
/// frame. From this information one can derive the number of samples
/// and the frame's size.
/// Uncompressed SampleSize is always 16 bit.
///
AUDIO_CODEC_MP3 = 2,
/// Linear PCM, strictly little-endian
AUDIO_CODEC_UNCOMPRESSED = 3,
/// Proprietary simple format. Always 5Khz mono ?
//
/// SWF6 and later.
/// Data is organized in frames of 256 samples.
///
AUDIO_CODEC_NELLYMOSER_8HZ_MONO = 5,
/// Proprietary simple format
//
/// SWF6 and later.
/// Data is organized in frames of 256 samples.
///
AUDIO_CODEC_NELLYMOSER = 6,
/// Advanced Audio Coding
AUDIO_CODEC_AAC = 10,
/// Always 16kHz mono
AUDIO_CODEC_SPEEX = 11
// NOTE: if you add more elements here remember to
// also add them to the output operator!
};
DSOEXPORT std::ostream& operator<< (std::ostream& os, const audioCodecType& t);
/// Information about an audio stream
//
/// The information stored is codec-id,
/// samplerate, samplesize, stereo, duration and codec-type.
///
/// Additionally, an abstract ExtraInfo can be hold.
///
class AudioInfo
{
public:
/// Construct an AudioInfo object
//
/// @param codeci
/// Audio codec id.
/// To be interpreted as a media::audioCodecType if the typei
/// parameter is CODEC_TYPE_FLASH; otherwise it's an opaque number to use
/// for codec information transfer between a MediaParser and a
/// AudioDecoder from the same %media handler module.
///
/// @param sampleRatei
/// Nominal sample rate.
/// @todo document units.
///
/// @param sampleSizei
/// Sample size, in bytes.
///
/// @param stereoi
/// Sample type (stereo if true, mono otherwise).
/// @todo document if and how intepretation of sampleSizei changes
///
/// @param durationi
/// Nominal audio stream duration, in milliseconds.
///
/// @param typei
/// Changes interpretation of the codeci parameter.
///
AudioInfo(int codeci, boost::uint16_t sampleRatei,
boost::uint16_t sampleSizei, bool stereoi,
boost::uint64_t durationi, codecType typei)
:
codec(codeci),
sampleRate(sampleRatei),
sampleSize(sampleSizei),
stereo(stereoi),
duration(durationi),
type(typei)
{
}
/// Codec identifier
//
/// This has to be interpreted as audioCodecType if codecType type is CODEC_TYPE_FLASH
/// or interpretation is opaque and we rely on the assumption that the AudioInfo
/// creator and the AudioInfo user have a way to get a shared interpretation
///
int codec;
boost::uint16_t sampleRate;
/// Size of each sample, in bytes
boost::uint16_t sampleSize;
bool stereo;
boost::uint64_t duration;
codecType type;
/// Extra info about an audio stream
//
/// Abstract class to hold any additional info
/// when required for proper decoder initialization.
///
class ExtraInfo {
public:
virtual ~ExtraInfo() {}
};
/// Extra info about audio stream, if when needed
//
/// Could be ExtraVideoInfoFlv or a media-handler specific info
///
std::auto_ptr<ExtraInfo> extra;
};
/// Information about a video stream
//
/// The information stored is codec-id, width, height, framerate and duration.
///
/// Additionally, an abstract ExtraInfo can be hold.
///
class VideoInfo
{
public:
/// Construct a VideoInfo object
//
/// @param codeci
/// Video codec id.
/// To be interpreted as a media::videoCodecType if the typei
/// parameter is CODEC_TYPE_FLASH; otherwise it's an opaque number to use
/// for codec information transfer between a MediaParser and a
/// VideoDecoder from the same %media handler module.
///
/// @param widthi
/// Video frame width.
/// @todo check if still needed.
///
/// @param heighti
/// Video frame height.
/// @todo check if still needed.
///
/// @param frameRatei
/// Nominal video frame rate.
/// @todo document units.
///
/// @param durationi
/// Nominal video duration.
/// @todo check if still needed, if so document units!
///
/// @param typei
/// Changes interpretation of the codeci parameter.
///
VideoInfo(int codeci, boost::uint16_t widthi, boost::uint16_t heighti,
boost::uint16_t frameRatei, boost::uint64_t durationi,
codecType typei)
:
codec(codeci),
width(widthi),
height(heighti),
frameRate(frameRatei),
duration(durationi),
type(typei)
{
}
int codec;
boost::uint16_t width;
boost::uint16_t height;
boost::uint16_t frameRate;
boost::uint64_t duration;
codecType type;
/// Extra info about a video stream
//
/// Abstract class to hold any additional info
/// when required for proper decoder initialization
///
class ExtraInfo {
public:
virtual ~ExtraInfo() {}
};
/// Extra info about video stream, if when needed
//
/// Could be ExtraAudioInfoFlv or a media-handler specific info
///
std::auto_ptr<ExtraInfo> extra;
};
DSOEXPORT std::ostream& operator << (std::ostream& os, const VideoInfo& vi);
class EncodedExtraData {
public:
virtual ~EncodedExtraData() {}
};
/// An encoded video frame
class EncodedVideoFrame
{
public:
/// Create an encoded video frame
//
/// @param data
/// Data buffer, ownership transferred
///
/// @param size
/// Size of the data buffer
///
/// @param frameNum
/// Frame number.
///
/// @param timestamp
/// Presentation timestamp, in milliseconds.
///
EncodedVideoFrame(boost::uint8_t* data, boost::uint32_t size,
unsigned int frameNum,
boost::uint64_t timestamp=0)
:
_size(size),
_data(data),
_frameNum(frameNum),
_timestamp(timestamp)
{}
/// Return pointer to actual data. Ownership retained by this class.
const boost::uint8_t* data() const { return _data.get(); }
/// Return size of data buffer.
boost::uint32_t dataSize() const { return _size; }
/// Return video frame presentation timestamp
boost::uint64_t timestamp() const { return _timestamp; }
/// Return video frame number
unsigned frameNum() const { return _frameNum; }
// FIXME: should have better encapsulation for this sort of stuff.
std::auto_ptr<EncodedExtraData> extradata;
private:
boost::uint32_t _size;
boost::scoped_array<boost::uint8_t> _data;
unsigned int _frameNum;
boost::uint64_t _timestamp;
};
/// An encoded audio frame
class EncodedAudioFrame
{
public:
boost::uint32_t dataSize;
boost::scoped_array<boost::uint8_t> data;
boost::uint64_t timestamp;
// FIXME: should have better encapsulation for this sort of stuff.
std::auto_ptr<EncodedExtraData> extradata;
};
/// The MediaParser class provides cursor-based access to encoded %media frames
//
/// Cursor-based access allow seeking as close as possible to a specified time
/// and fetching frames from there on, sequentially.
/// See seek(), nextVideoFrame(), nextAudioFrame()
///
/// Input is received from a IOChannel object.
///
class MediaParser
{
public:
/// A container for executable MetaTags contained in media streams.
//
/// Presently only known in FLV.
typedef std::multimap<boost::uint64_t, boost::shared_ptr<SimpleBuffer> >
MetaTags;
typedef std::vector<MetaTags::mapped_type> OrderedMetaTags;
MediaParser(std::auto_ptr<IOChannel> stream);
// Classes with virtual methods (virtual classes)
// must have a virtual destructor, or the destructors
// of subclasses will never be invoked, tipically resulting
// in memory leaks..
//
virtual ~MediaParser();
/// \brief
/// Seeks to the closest possible position the given position,
/// and returns the new position.
//
///
/// @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
/// but is bogus as it doesn't take the *current* playhead cursor time
/// into account. A proper way would be having a getLastBufferTime ()
/// interface here, returning minimun timestamp of last available
/// frames and let NetSTream::bufferLength() use that with playhead
/// time to find out...
///
DSOEXPORT boost::uint64_t getBufferLength() const;
/// Return true if both audio and video buffers are empty
//
/// NOTE: locks _qMutex
DSOEXPORT bool isBufferEmpty() 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
//
/// @param t
/// Number of milliseconds to keep in the buffers.
///
DSOEXPORT void setBufferTime(boost::uint64_t t)
{
boost::mutex::scoped_lock lock(_bufferTimeMutex);
_bufferTime=t;
}
/// Get timestamp of the next frame available, if any
//
/// @param ts will be set to timestamp of next available frame
/// @return false if no frame is available yet
///
/// NOTE: locks _qMutex
///
DSOEXPORT bool nextFrameTimestamp(boost::uint64_t& ts) const;
/// Get timestamp of the video frame which would be returned on nextVideoFrame
//
/// @return false if there no video frame left
/// (either none or no more)
///
/// NOTE: locks _qMutex
///
DSOEXPORT bool nextVideoFrameTimestamp(boost::uint64_t& ts) const;
/// Returns the next video frame in the parsed buffer, advancing video cursor.
//
/// If no frame has been played before the first frame is returned.
/// If there is no more frames in the parsed buffer NULL is returned.
/// you can check with parsingCompleted() to know wheter this is due to
/// EOF reached.
///
DSOEXPORT std::auto_ptr<EncodedVideoFrame> nextVideoFrame();
/// Get timestamp of the audio frame which would be returned on nextAudioFrame
//
/// @return false if there no video frame left
/// (either none or no more)
///
/// NOTE: locks _qMutex
///
DSOEXPORT bool nextAudioFrameTimestamp(boost::uint64_t& ts) const;
/// Returns the next audio frame in the parsed buffer, advancing audio cursor.
//
/// If no frame has been played before the first frame is returned.
/// If there is no more frames in the parsed buffer NULL is returned.
/// you can check with parsingCompleted() to know wheter this is due to
/// EOF reached.
///
DSOEXPORT std::auto_ptr<EncodedAudioFrame> nextAudioFrame();
/// Returns a VideoInfo class about the videostream
//
/// @return a VideoInfo class about the videostream,
/// or zero if unknown (no video or not enough data parsed yet).
///
VideoInfo* getVideoInfo() { return _videoInfo.get(); }
/// Returns a AudioInfo class about the audiostream
//
/// @return a AudioInfo class about the audiostream,
/// or zero if unknown (no audio or not enough data parsed yet).
///
AudioInfo* getAudioInfo() { return _audioInfo.get(); }
/// 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 true of indexing is completed
//
/// If this function returns false, parseNextChunk will
/// be called even when buffers are full. Parsers
/// supporting indexing separated from parsing should
/// override this method and have parseNextChunk figure
/// if they only need to index or to parse based on bufferFull.
///
virtual bool indexingCompleted() const { return true; }
/// Return number of bytes parsed so far
virtual boost::uint64_t getBytesLoaded() const { return 0; }
/// Return total number of bytes in input
boost::uint64_t getBytesTotal() const
{
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;
/// Retrieve any parsed metadata tags up to a specified timestamp.
//
/// @param ts The latest timestamp to retrieve metadata for.
/// @param tags This is filled with shared pointers to metatags in
/// timestamp order. Ownership of the data is shared. It
/// is destroyed automatically along with the last owner.
//
/// Metadata is currently only parsed from FLV streams. The default
/// is a no-op.
virtual void fetchMetaTags(OrderedMetaTags& tags, boost::uint64_t ts);
protected:
/// Subclasses *must* set the following variables: @{
/// Info about the video stream (if any)
std::auto_ptr<VideoInfo> _videoInfo;
/// Info about the audio stream (if any)
std::auto_ptr<AudioInfo> _audioInfo;
/// Whether the parsing is complete or not
bool _parsingComplete;
/// Number of bytes loaded
boost::uint64_t _bytesLoaded;
/// }@
/// Start the parser thread
void startParserThread();
/// Stop the parser thread
//
/// This method should be always called
/// by destructors of subclasses to ensure
/// the parser thread won't attempt to access
/// destroyed structures.
///
void stopParserThread();
/// Clear the a/v buffers
void clearBuffers();
/// Push an encoded audio frame to buffer.
//
/// Will wait on a condition if buffer is full or parsing was completed
///
void pushEncodedAudioFrame(std::auto_ptr<EncodedAudioFrame> frame);
/// Push an encoded video frame to buffer.
//
/// Will wait on a condition if buffer is full or parsing was completed
///
void pushEncodedVideoFrame(std::auto_ptr<EncodedVideoFrame> frame);
/// The stream used to access the file
std::auto_ptr<IOChannel> _stream;
mutable boost::mutex _streamMutex;
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;
/// 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;
/// Mutex protecting _bytesLoaded (read by main, set by parser)
mutable boost::mutex _bytesLoadedMutex;
/// 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, and by parseNextChunk
/// to determine whether to index-only or also push on queue.
///
bool bufferFull() const;
/// 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 _seekRequest;
private:
typedef std::deque<EncodedVideoFrame*> VideoFrames;
typedef std::deque<EncodedAudioFrame*> AudioFrames;
/// Return pointer to next encoded video frame in buffer
//
/// If no video is present, or queue is empty, 0 is returned
///
/// NOTE: Caller is expected to hold a lock on _qMutex
///
const EncodedVideoFrame* peekNextVideoFrame() const;
/// Return pointer to next encoded audio frame in buffer
//
/// If no video is present, or queue is empty, 0 is returned
///
/// NOTE: Caller is expected to hold a lock on _qMutex
///
const EncodedAudioFrame* peekNextAudioFrame() const;
/// 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;
};
} // gnash.media namespace
} // namespace gnash
#endif // __MEDIAPARSER_H__