[asterisk-scf-commits] asterisk-scf/integration/file_media_service.git branch "initial_development" updated.

Commits to the Asterisk SCF project code repositories asterisk-scf-commits at lists.digium.com
Thu Nov 10 11:19:33 CST 2011


branch "initial_development" has been updated
       via  df5ac0294a5062be49a4269d0dbc65e20d8089c5 (commit)
       via  c549bff7dd9259488cd818d95a047d38227547c6 (commit)
      from  883f26046f8ebe1d4337581de99a07e32d86d3e9 (commit)

Summary of changes:
 src/CMakeLists.txt          |    7 +-
 src/ContainerImpl.cpp       |  707 ++++++++++++++++++------
 src/ContainerImpl.h         |    6 +
 src/ContainerRepository.cpp |    4 +
 src/MatroskaUtil.cpp        | 1269 +++++++++++++++++++++++++++++++++++++++++++
 src/MatroskaUtil.h          |  310 +++++++++++
 src/TODO.txt                |    5 +
 7 files changed, 2137 insertions(+), 171 deletions(-)
 mode change 100755 => 100644 src/MatroskaDefines.h
 create mode 100644 src/MatroskaUtil.cpp
 create mode 100644 src/MatroskaUtil.h
 mode change 100755 => 100644 src/ReplicationContext.h
 create mode 100644 src/TODO.txt


- Log -----------------------------------------------------------------
commit df5ac0294a5062be49a4269d0dbc65e20d8089c5
Author: Brent Eagles <beagles at digium.com>
Date:   Thu Nov 10 13:48:22 2011 -0330

    More mux code changes.

diff --git a/src/ContainerImpl.cpp b/src/ContainerImpl.cpp
index 4505fe2..6f14acf 100644
--- a/src/ContainerImpl.cpp
+++ b/src/ContainerImpl.cpp
@@ -30,6 +30,12 @@
 
 #include "MatroskaUtil.h"
 
+extern "C"
+{
+#include <config.h>
+#include <corec/helpers/file/streams.h>
+}
+
 using namespace AsteriskSCF::FileMediaService::MediaContainer::V1;
 using namespace AsteriskSCF::Media::File::V1;
 using namespace AsteriskSCF::Media::V1;
@@ -266,47 +272,17 @@ public:
     class SinkManager : public AsteriskSCF::Media::V1::StreamSource
     {
     public:
-        SinkManager(const string& id, const Logger& logger,
-            AVCodecContext* codecContext, AVCodec* codec,
-            const Ice::ObjectAdapterPtr& adapter) :
+        SinkManager(const string& id,
+                const FormatPtr& format,
+                double trackTimecodeScale,
+                const Ice::ObjectAdapterPtr& adapter,
+                const Logger& logger) :
             mId(id),
             mLogger(logger),
-            mCodecContext(codecContext),
-            mCodec(codec),
+            mTimeScale(trackTimecodeScale),
             mAdapter(adapter),
             mSeqno(0)
         {
-            mFormat = new AudioFormat;
-            switch (mCodecContext->sample_fmt) 
-            {
-            case SAMPLE_FMT_U8:
-                mFormat->sampleSize = 8;
-                break;
-            case SAMPLE_FMT_S16:
-                mFormat->sampleSize = 16;
-                break;
-            case SAMPLE_FMT_S32:
-                mFormat->sampleSize = 32;
-                break;
-            default:
-                mFormat->sampleSize = 8;
-                //
-                // hrmm.. there are floating point sample formats.
-                //
-            }
-
-            //
-            // TODO: Double check this calc. 
-            //
-            mFormat->sampleRate = mCodecContext->sample_rate;
-            mFormat->frameSize = mCodecContext->sample_rate * 20 / 1000;
-        }
-
-        ~SinkManager()
-        {
-            //
-            // TODO cleanup codec context and codec.
-            //
         }
 
         void addSink(const StreamSinkPrx& sink, const Ice::Current& current)
@@ -355,43 +331,51 @@ public:
             return mId;
         }
 
-        void requestFormat(const FormatPtr&, const Ice::Current&)
+        void requestFormat(const FormatPtr& f, const Ice::Current&)
         {
             //
             // You get what you got!
             //
             // TODO: well, it should allow it if the format actually matches!
-            // 
+            //
+            if (!mFormat && !f && mFormat->ice_staticId() == f->ice_staticId())
+            {
+                return;
+            }
             throw MediaFormatSwitchException();
         }
 
-        //
-        // TODO: Oh gross. Ok, so here is the deal'eo. For fixed/known length
-        // codecs, avcodec returns n frames in a packet, otherwise it returns a
-        // single frame.  Would have been a lot simpler if they always returned
-        // a single frame.  With that in mind.. this is pretty much broken for
-        // other formats.
-        //
-        void distributePacket(AVPacket& packet)
+        void distributePacket(TrackFrame* data)
         {
             //
             // We have to decode the whole packet and store it in our own frame(s), because
             // the next read or close of the avf context will invalidate the contents
             // of the packet.
             //
-            size_t bytesToDecode = packet.size;
-            unsigned char* buf = static_cast<unsigned char*>(packet.data);
+            size_t bytesToDecode = data->frameData.Size;
+            unsigned char* buf = static_cast<unsigned char*>(data->frameData.Data);
+            timecode_t startTimecode = data->frameData.Timecode / mTimeScale;
+            size_t sampleCount = 0;
+            size_t durationFactor = (mFormat->sampleSize / sizeof(buf[0])) * mFormat->sampleRate; // bits per second
 
             while(bytesToDecode != 0)
             {
                 FrameSeq frames;
                 AudioFramePtr frame = new AudioFrame;
                 frame->seqno = ++mSeqno;
-                frame->timestamp = packet.dts;
+
+                //
+                // 8000 represents the conversion from bits to bytes and seconds to milliseconds. On the first
+                // iteration, the startTimecode will simply be 0 the first frame's timestamp will be equal too that of
+                // that of the TrackFrame instance. As more frames get added, we increment the timecode according to the
+                // number of bytes allocated to frames and the sample size and sample rates of the format.
+                //
+                frame->timestamp = startTimecode + (sampleCount * durationFactor / 8000);
                 frame->mediaFormat = mFormat;
 
-                unsigned long samplesLeft = 
-                    static_cast<size_t>(mFormat->frameSize) > bytesToDecode ? bytesToDecode: mFormat->frameSize;
+                unsigned long sampleRangeEnd  = 
+                        static_cast<size_t>(mFormat->frameSize) > bytesToDecode ? bytesToDecode: mFormat->frameSize;
+                sampleCount += sampleRangeEnd;
 
                 //
                 // TODO: other sample formats.
@@ -399,21 +383,21 @@ public:
                 if (mFormat->sampleSize == 16)
                 {
                     short* buf16 = reinterpret_cast<short*>(buf);
-                    Ice::ShortSeq payloadData(buf16, buf16 + samplesLeft);
+                    Ice::ShortSeq payloadData(buf16, buf16 + sampleRangeEnd);
                     ShortSeqPayloadPtr payloadObj = new ShortSeqPayload;
                     payloadObj->payload = payloadData;
                     frame->payload = payloadObj;
                 }
                 else
                 {
-                    Ice::ByteSeq payloadData(buf, buf + samplesLeft);
+                    Ice::ByteSeq payloadData(buf, buf + sampleRangeEnd);
                     ByteSeqPayloadPtr payloadObj = new ByteSeqPayload;
                     payloadObj->payload = payloadData;
                     frame->payload = payloadObj;
                 }
 
-                bytesToDecode -= samplesLeft;
-                buf += samplesLeft;
+                bytesToDecode -= sampleRangeEnd;
+                buf += sampleRangeEnd;
 
                 //
                 // TODO: byte order!!! See RTP component for example.
@@ -522,21 +506,19 @@ public:
 
     private:
         boost::shared_mutex mLock;
-
-        typedef map<string, StreamSinkPrx> SinkMap;
-
-        SinkMap mSinks;
-
         string mId;
+        double mTimeScale;
+        AudioFormatPtr mFormat;
         Logger mLogger;
-        AVCodecContext* mCodecContext;
-        AVCodec* mCodec;
-        IceUtil::Time  mStartTime;
         Ice::ObjectAdapterPtr mAdapter;
+
+        IceUtil::Time  mStartTime;
         size_t mSeqno;
-        AudioFormatPtr mFormat;
         FrameSeq mQueuedFrames;
 
+        typedef map<string, StreamSinkPrx> SinkMap;
+        SinkMap mSinks;
+
         StreamSinkSeq getSinksImpl()
         {
             StreamSinkSeq results;
@@ -553,12 +535,14 @@ public:
             UniqueLock lock(mLock);
             mSinks.erase(id);
         }
-    };
+    }; /* end of class SinkManager */
+
     typedef IceUtil::Handle<SinkManager> SinkManagerPtr;
     typedef vector<SinkManagerPtr> SinkManagerSeq;
 
     PlaybackSessionImpl(const Ice::ObjectAdapterPtr& adapter, const string& id,
         const FileMediaSpecification& spec,
+        Matroska::Environment* matroskaEnvironment,
         const Logger& logger) :
         mObjectAdapter(adapter),
         mId(id),
@@ -574,15 +558,9 @@ public:
 
     ~PlaybackSessionImpl()
     {
-        try
-        {
-            if (mInput)
-            {
-                av_close_input_file(mInput);
-            }
-        }
-        catch (...)
+        if (mStream)
         {
+            StreamClose(mStream);
         }
     }
 
@@ -600,69 +578,49 @@ public:
         //
         assert(!mInput);
 
-        int result = av_open_input_file(&mInput, filename.c_str(), 0, 0, 0);
-        if (result < 0)
-        {
-            //
-            // Crap!!! Actually no, this will never get to this point ..
-            // We should open thing snad pass it into the session instead.
-            //
-            mLogger(Error) << "Unable to open the data file";
-            mInput = 0;
-            return false;
-        }
-        result = av_find_stream_info(mInput);
-        if (result < 0)
+        mStream = StreamOpen(env->parserContext(), filename.c_str(), SFLAG_RDONLY);
+
+        if (mStream)
         {
-            mLogger(Error) << "Cannot read header information";
-            av_close_input_file(mInput);
-            mInput = 0;
+            mLogger(Debug) << "Unable to open stream for " << filename << " with matroska library."; 
             return false;
         }
 
-        if (mInput->nb_streams < 1)
+        mContainerReader.reset(new MatroskaReader(mStream));
+
+        if (mContainerReader->trackCount() < 1)
         {
-            mLogger(Error) << "Umm.. this file appears to have 0 streams in it.";
-            av_close_input_file(mInput);
-            mInput = 0;
+            mLogger(Error) << "File " << filename < " has 0 tracks!";
+            //
+            // We could let this clean up on it's own, but we might as well toast it now whilst we are
+            // here.
+            //
+            mContainerReader.reset();
+
+            //
+            // This we had definitely better do while we are here.
+            //
+            StreamClose(mStream);
+            mStream = 0;
             return false;
         }
 
-        mSinkManagers.resize(mInput->nb_streams);
-        for (size_t i = 0; i < mInput->nb_streams; ++i) // TODO: create the relevant sources.
+        mSinkManagers.resize(mContainerReader->trackCount());
+        for (size_t i = 0; i < mContainerReader->trackCount(); ++i) // TODO: create the relevant sources.
         {
             string sourceId = mId;
             sourceId += ".";
             sourceId += boost::lexical_cast<string>(i);
 
-            AVCodecContext* codecContext = mInput->streams[i]->codec;
-            if (!codecContext)
-            {
-                mLogger(Debug) << "No codec context for stream " << i << ", skipping.";
-                continue;
-            }
-            AVCodec* codec = avcodec_find_decoder(codecContext->codec_id);
-            if (!codec)
-            {
-                mLogger(Debug) << "Unable to find codec for " << i << " (" << codecContext->codec_id 
-                    << "), skipping.";
-                continue;
-            }
-
-            result = avcodec_open(codecContext, codec);
-            if (result < 0)
-            {
-                mLogger(Debug) << "Unable to open codec for " << i << " (" << codecContext->codec_id 
-                    << "), continuing hopefully.";
-            }
-
+            //
+            // TODO: replace codec 'figuring code' from avformat/avcodec.
+            //
 
-            mSinkManagers[i] = new SinkManager(sourceId, mLogger, codecContext, codec, mObjectAdapter);
+            mSinkManagers[i] = new SinkManager(sourceId, mLogger, mObjectAdapter);
             Ice::Identity id = mObjectAdapter->getCommunicator()->stringToIdentity(sourceId);
             mSources.push_back(StreamSourcePrx::uncheckedCast(mObjectAdapter->add(mSinkManagers[i], id)));
         }
 
-        av_init_packet(&mPacket);
         return true;
     }
 
@@ -793,7 +751,8 @@ public:
         //
         if (readPacket)
         {
-            if(av_read_frame(mInput, &mPacket) < 0)
+            TrackFrame* newFrame = mContainerReader->getNextFrame();
+            if (!newFrame)
             {
                 //
                 // This should indicate that the file is done, return false should break
@@ -801,7 +760,7 @@ public:
                 //
                 return false;
             }
-            int trackIndex = mPacket.stream_index;
+            int trackIndex = newFrame->track;
 
             if (trackIndex >= 0 && trackIndex < static_cast<long>(mSinkManagers.size())) 
             {
@@ -811,7 +770,7 @@ public:
                     //
                     // Distribute the data to whomever it was destined for.
                     //
-                    p->distributePacket(mPacket);
+                    p->distributePacket(newFrame);
                 }
             }
             else
@@ -898,12 +857,18 @@ private:
     Ice::ObjectAdapterPtr mObjectAdapter;
     const string mId;
     FileMediaSpecification mSpec;
+    Matroska::Environment* mMatroskaEnvironment;
+    boost::shared_ptr<ContainerReader> mContainerReader;
+
+    //
+    // TODO: a small resource guard wrapper class would be useful for the corec stream member.
+    //
+    stream* mStream;
+    
     Logger mLogger;
     CookieManager mCookieManager;
-    AVFormatContext* mInput;
     SinkManagerSeq mSinkManagers;
     StreamSourceSeq mSources;
-    AVPacket mPacket;
     IODriverThreadPtr mDriver;
     bool mStarted;
 };
@@ -912,6 +877,39 @@ typedef IceUtil::Handle<PlaybackSessionImpl> PlaybackSessionImplPtr;
 //
 // End of playback stuff.. start of recording stuff.
 //
+struct WritingRecord
+{
+    unsigned trackNumber;
+    FramePtr frame;
+};
+
+
+static bool frameTimestampComparator(const WritingRecord& a, const WritingRecordr& b)
+{
+    StreamFramePtr streamA = StreamFramePtr::dynamicCast(a.frame);
+    StreamFramePtr streamB = StreamFramePtr::dynamicCast(b.frame);
+    if (streamA && streamB)
+    {
+        return streamA->timestamp < streamB->timestamp;
+    }
+    if (streamA)
+    {
+        return true;
+    }
+    //
+    // If we are here, then a was not a stream frame. If b was a stream frame, then it
+    // takes precedent (we put stream frames first) so we need to swap.
+    //
+    if (!streamB)
+    {
+        return false;
+    }
+    //
+    // If we are here neither a or b are stream frames so the frames might as well stay in the
+    // order they are.
+    //
+    return true;
+};
 
 //
 // In progress.. lots of questions about multi-stream sinking.
@@ -919,49 +917,282 @@ typedef IceUtil::Handle<PlaybackSessionImpl> PlaybackSessionImplPtr;
 class FrameWriter : public IceUtil::Shared
 {
 public:
-    FrameWriter(unsigned trackCount) :
-        mEarliest(IceUtil::Time::seconds(0)),
-        mLatest(IceUtil::Time::seconds(0))
+    FrameWriter(const boost::shared_ptr<ContainerWriter>& writer, unsigned trackCount) :
+        mWriter(writer)
     {
+        //
+        // TODO: exception for the assert so the problem can be handled in release builds as well.
+        //
+        assert(trackCount > 0);
+        assert(mWriter);
+
+        mTrackQueues.resize(trackCount);
     }
 
-    void write(const FrameSeq&, unsigned)
+    void write(unsigned trackIndex, const FrameSeq& frames)
     {
-        UniqueLock lock(mLock);
-
-        AVPacket out;
         //
-        // TODO: convert frames to apacket.
+        // Called from queueFrames when it is time
         //
-        av_interleaved_write_frame(mOutput, &out);
-
+        mWriter->addFrames(trackIndex, frames);
     }
 
     void queueFrames(unsigned trackIndex, const FrameSeq& frames)
     {
-        mFrameQueues[trackIndex].insert(mFrameQueues[trackIndex].end(), frames.begin(), frames.end());
+        boost::mutex::scoped_lock lock(mLock);
 
+        if (mTrackQueues.size() == 1)
+        {
+            assert(trackIndex == 1);
+            writeFrames(1, frames);
+            return;
+        }
+
+        //
+        // Ok, so we have multiple tracks! We probably should avoid negative time codes so we want to
+        // kind of group things together if we can.
         //
-        // Now here is the trick, we need one complete block of time for all incoming streams
-        // before we can mux them in. 
+        size_t index = trackIndex -1;
+        assert (index < mTrackQueues.size());
+        
+        bool alreadyHasQueuedFrames = ! mTrackQueues[index].frames.empty();
+        mTrackQueues[index].insert(mTrackQueues[index].frames.end(), frames.begin(), frames.end());
+
         //
-        if (mEarliest.toSeconds() == 0 && !frames.empty())
+        // We are going to assume frames are in proper chronological/serial order.  This might actually be cool for
+        // setting up test data, so let's not sweat it :)
+        //
+        IceUtil::Time earliestStamp(mTrackQueues[index].earliest);
+        for (FrameSeq::iterator iter = frames.begin(); iter != frames.end(); ++iter)
+        {
+            StreamFramePtr streamFrame = StreamFramePtr::dynamicCast(*iter);
+            if (streamFrame)
+            {
+                earliestStamp = IceUtil::Time::milliseconds(streamFrame->timestamp);
+                break;
+            }
+        }
+
+        IceUtil::Time latestStamp(mTrackQueues[index].latest);
+        for (FrameSeq::reverse_iterator iter = frames.rbegin(); iter < frames.rend(); ++iter)
+        {
+            StreamFramePtr streamFrame = StreamFramePtr::dynamicCast(*iter);
+            if (streamFrame)
+            {
+                latestStamp = IceUtil::Time::milliseconds(streamFrame->timestamp);
+                break;
+            }
+        }
+
+        bool earliestChanged  = false;
+        if (mTrackQueues[index].earliest.toSeconds() == 0 || earliestStamp < mTrackQueues[index].earliest.toSeconds())
+        {
+            mTrackQueues[index].earliest = earliestStamp;
+            earliestChanged = true;
+        }
+
+        bool latestChanged = false;
+        if (mTrackQueues[index].toSeconds() == 0 || latestStamp > mTrackQueues[index].latest.toSeconds())
         {
-            StreamFramePtr streamFrame = StreamFramePtr::dynamicCast(frames.front());
-            mEarliest = IceUtil::Time::milliseconds(streamFrame->timestamp);
-            streamFrame = StreamFramePtr::dynamicCast(frames.back());
-            mLatest = IceUtil::Time::milliseconds(steamFrame->timestamp);
+            mTrackQeueus[index].latest = latestStamp;
+            latestChanged = true;
         }
+
+        vector<TrackQueue>::iterator keyQueue = mTrackQueues.end(); 
+        //
+        // Weird name, I know, but basically we are going to find the earliest "latest" of all the queues.
+        // That sets a defacto end of the window. The write will be triggered when the latest earliest falls
+        // before that. Confused yet? Pictures please:
+        // t -012345678
+        // Q1 e------l
+        // Q2    e--l
+        // Q3      e--l
+        // Here, the earliest is Q1 at 0, the earliest latest is Q2 at 6 and latest earliest is Q3 at 5. We
+        // trigger because each queue has some frame for the windo of 0-6. After writing Q1 with have 1 frame at t 7
+        // Q2 will have no frames and Q3 will have 2 frames at 7 and 8. Muxing takes the frames and
+        // orders them by timestamp (duration is important as well, but we need to figure that the
+        // frames are going to arrive in some sensible size for interactive AV.
+        //
+        IceUtil::Time earliestLastest(latestStamp); 
+        
+        for (vector<TrackQueue>::iterator iter = mTrackQueues.begin(); iter != mTrackQueues.end(); ++iter)
+        {
+            if (iter->frames.empty() || iter->earliest.toSeconds() == 0)
+            {
+                //
+                // This basically indicates that we have received no frames for this track yet.
+                // While breaking out of here now is kind of *iffy* (we need to examine the external use
+                // cases for the potential and the validity of this kind of thing) it is pretty
+                // hard to mux without frames from each track weighing in.
+                //
+                return;
+            }
+            //
+            // The earliest is most important. We need to make sure we get that guy.
+            //
+            if (iter->earliest < earliestStamp)
+            {
+                earliestStamp = iter->earliest;
+            }
+            if (iter->earliest > latestStamp)
+            {
+                latestStamp = iter->earliest;
+            }
+            if (iter->latest < earliestLatest)
+            {
+                earliestLast = iter->latest;
+            }
+        }
+
+        //
+        // latestStamp is really latestEarliest here! If the latest earliest does not over lap with
+        // the earliest latest then we do not have any overlapping tracks.
+        //
+        if (latestStamp > earliestLatest)
+        {
+            return;
+        }
+
+        //
+        // So we have the window, collect the frames that fall in this window and then sort them. 
+        //
+        size_t trackNumber = 1;
+        vector<WritingRecord> framesToWrite;
+        for (vector<TrackQueue>::iterator iter = mTrackQueues.begin(); iter != mTrackQueues.end(); ++iter)
+        {
+            FrameSeq leftOvers;
+            for (FrameSeq::iterator frameIter = iter->frames.begin(); frameIter != iter->frames.end(); ++frameIter)
+            {
+                StreamFramePtr streamFrame = StreamFramePtr::dynamicCast(*frameIter);
+                if (streamFrame)
+                {
+                    IceUtil::Time t(IceUtil::Time::milliseconds(streamFrame->timeStamp));
+                    if (t < earliestLatest)
+                    {
+                        framesToWrite.push_back(*frameIter);
+                    }
+                    else
+                    {
+                        if (leftOvers.empty())
+                        {
+                            iter->earliest = t;
+                        }
+                        else
+                        {
+                            iter->latest = t;
+                        }
+                        leftOvers.push_back(*frameIter);
+                    }
+                }
+                else
+                {
+                    //
+                    // I think if we are going to store them.. this is the only way to do it.
+                    //
+                    WritingRecord newRecord;
+                    newRecord.trackNumber = trackNumber;
+                    newRecord.frame = *frameIter;
+                    framesToWrite.push_back(newRecord);
+                }
+            }
+
+            if (leftOvers.empty())
+            {
+                iter->earliest = IceUtil::Time::seconds(0);
+                iter->latest = iter->earliest;
+            }
+            iter->frames.swap(leftOvers);
+            ++trackNumber;
+        }
+        //
+        // If this happened then we something is either wrong with our frame picking or our trigger logic.
+        //
+        assert (framesToWrite.size > 0);
+        muxAndWrite(framesToWrite);
+    }
+
+
+    void close()
+    {
+        //
+        // We are closing, that means we need to collect, mux and write remaining frames.
+        //
+        boost::mutex::scoped_lock lock(mLock);
+        size_t trackNumber = 1;
+        vector<WritingRecord> framesToWrite;
+        for (vector<TrackQueue>::iterator iter = mTrackQueues.begin(); iter != mTrackQueues.end(); ++iter)
+        {
+            for (FrameSeq::iterator frameIter = iter->frames.begin(); frameIter != iter->frames.end(); ++frameIter)
+            {
+                WritingRecord newRecord;
+                newRecord.trackNumber = trackNumber;
+                newRecord.frame = *frameIter;
+                framesToWrite.push_back(newRecord);
+            }
+            ++trackNumber;
+        }
+        //
+        // TODO: Byt right 
+        //
+        muxAndWrite(framesToWrite);
     }
 
 private:
-    boost::shared_mutex mLock;
-    AVFormatContext* mOutput;
-    AVOutputFormat* mOutputFormat;
-    IceUtil::Time mEarliest; 
-    IceUtil::Time mLatest;
+    boost::mutex mLock;
+    boost::shared_ptr<ContainerWriter> mContainerWriter;
+
+    struct TrackQueue
+    {
+        IceUtil::Time earliest; 
+        IceUtil::Time latest;
+        FrameSeq frames;
+
+        TrackQueue() :
+            earliest(IceUtil::Time::seconds(0)),
+            latest(IceUtil::Time::seconds(0))
+        {
+        }
+    };
 
-    vector<FrameSeq> mFrameQueues;
+    vector<TrackQueue> mTrackQueues;
+    
+    /**
+     * Mux the frames and write them to the container writer. the argument is non-const because the writing records are
+     * sorted, requiring that the vector be modified. We could sort outside the function body *or* make a copy but a.)
+     * code duplication and b.) performance, so what the heck let's play a little fast and loose.
+     **/
+    void muxAndWrite(vector<WritingRecord>& framesToWrite)
+    {
+        //
+        // Now.. this part is basically muxing. We are going to interleave the frames of the different tracks
+        // by timestamp. What we are not going to do is break frames up. We could, but hopefully this won't be
+        // necessary.
+        //
+        sort(framesToWrite.begin(); framesToWrite.end(), frameTimestampComparator);
+        unsigned currentTrack = 0;
+        FrameSeq collectedFrames;
+        for (vector<WritingRecord>::iterator w = framesToWrite.begin(); w != framesToWrite.end(); ++w)
+        {
+            //
+            // This is fine because tracks are 1 based indexing and we've initialized currentTrack to 0
+            // to start with.
+            //
+            if (currentTrack != w->trackNumber)
+            {
+                if (!collectedFrames.empty())
+                {
+                    write(currentTrack, collectedFrames);
+                    collectedFrames.clear();
+                }
+                currentTrack = w->trackNumber;
+            }
+            collectedFrames.push_back(w->frame);
+        }
+        if (currentTrack != 0 && !collectedFrames.empty())
+        {
+            write(currentTrack, collectedFrames);
+        }
+    }
 };
 
 typedef IceUtil::Handle<FrameWriter> FrameWriterPtr;
@@ -983,12 +1214,17 @@ public:
 
         void write(const FrameSeq& newFrames, const Ice::Current&) 
         {
-            UniqueLock lock(mLock);
-            if (!mWriting)
             {
-                return;
+                UniqueLock lock(mLock);
+                if (!mWriting)
+                {
+                    return;
+                }
             }
 
+            //
+            // The lock really does not need to be held here. The writer has it's own.
+            //
             mWriter->queueFrames(mTrack, newFrames);
         }
 
@@ -1025,6 +1261,12 @@ public:
             mStartTime = startTime;
         }
 
+        void stopWriting()
+        {
+            UniqueLock lock(mLock);
+            mWriting = false;
+        }
+
     private:
         string mId;
         StreamSourcePrx mSource;
@@ -1034,14 +1276,17 @@ public:
         bool mWriting;
         FrameSeq mQueuedFrames;
     };
+    typedef IceUtil::Handle<SourceManager> SourceManagerPtr;
+    tyepdef vector<SourceManagerPtr> SourceManagers;
 
     RecordingSessionImpl(const Ice::ObjectAdapterPtr& adapter, const string& id, 
-        const FileMediaSpecification& spec) :
+            const FileMediaSpecification& spec,
+            const Matroska::Environment* matroskaEnvironment) :
         mObjectAdapter(adapter),
         mId(id),
         mSpec(spec),
-        mOutput(0),
-        mOutputFormat(0)
+        mMatroskaEnvironment(matroskaEnvironment),
+        mStream(0)
     {
 
     }
@@ -1082,21 +1327,33 @@ public:
 
     void start(const Ice::Current&)
     {
-
+        for (SourceManagers::const_iterator iter= mSourceManagers.begin(); iter != mSourceManagers.end(); ++iter)
+        {
+            iter->startWriting(IceUtil::Time::now());
+        }
     }
 
     void pause(const Ice::Current&)
     {
+        for (SourceManagers::const_iterator iter= mSourceManagers.begin(); iter != mSourceManagers.end(); ++iter)
+        {
+            iter->stopWriting();
+        }
     }
 
     void unpause(const Ice::Current&)
     {
+        for (SourceManagers::const_iterator iter= mSourceManagers.begin(); iter != mSourceManagers.end(); ++iter)
+        {
+            iter->startWriting(IceUtil::Time::now());
+        }
     }
 
     void restart(const Ice::Current&)
     {
         //
-        // TODO: this is trickier to implement than I thought!
+        // TODO: this is trickier to implement than I thought! I'm not at all sure whether we should
+        // support this or not.
         //
     }
 
@@ -1110,14 +1367,96 @@ public:
         catch (const Ice::Exception&)
         {
         }
+        if (mWriter)
+        {
+            try
+            {
+                mWriter->close();
+            }
+            //
+            // XXX log exceptions
+            //
+            catch (const exception&)
+            {
+            }
+            catch (...)
+            {
+            }
+        }
+    }
+
+    void setup(const string& filename)
+    {
+        //
+        // Matroska related initialization!
+        //
+        mStream = StreamOpen(env->parserContext(), filename.c_str(), SFLAG_WRONLY | SFLAG_CREATE);
+        if (!mStream)
+        {
+            //
+            // XXX: error handling.
+            //
+            assert("XXX: Error handling" == 0);
+        }
+        vector<string> trackIds;
+        try
+        {
+            mContainer.reset(new Matroska::ContainerWriter(mStream));
+            string containerId = string("ASCF.Recording.") + mId;
+            mContainer->populateHeader(string("ASCF.Recording.") + mId, filename);
+
+            int id = 0;
+            for (FormatSeq::const_iterator iter = mSpec.formats.begin(); iter != mSpec.formats.end(); ++iter)
+            {
+                string trackId = containerId;
+                trackId += boost::lexical_cast<string>(id++);
+                trackIds.push_back(trackId);
+                mContainer->addTrack(*iter, containerId);
+            }
+            mWriter = new FrameWriter(mContainer, mSpec.formats.size());
+        }
+        //
+        // XXX exceptions should be logged.
+        //
+        catch (const exception&)
+        {
+            if (mStream)
+            {
+                StreamClose(mStream);
+            }
+            mContainer.reset(0);
+        }
+        catch (...)
+        {
+            mContainer.reset(0);
+        }
+
+        //
+        // Now we need to create the sink servants. We don't try and create and activate all of the
+        // servants up above because exceptions are quite possible up there and cleaning up servants, etc
+        // in exception handlers is ickier.
+        //
+        int trackNumber = 1;
+        for (vector<string>::const_iterator iter = trackIds.begin(); iter != trackIds.end(); ++iter)
+        {
+            SourceManagerPtr s = new SourceManager(*iter, mWriter, trackNumber++);
+            mSourceManagers.push_back(s);
+            mSinks = StreamSinkPrx::uncheckedCast(mObjectAdapter->add(s,
+                            mObjectAdapter->getCommunicator()->stringToIdentity(*iter)));
+        }
     }
+
 private:
     Ice::ObjectAdapterPtr mObjectAdapter;
     const string mId;
     FileMediaSpecification mSpec;
     CookieManager mCookieManager;
-    AVFormatContext* mOutput;
-    AVOutputFormat* mOutputFormat;
+    Matroska::Environment* matroskaEnvironment;
+    boost::shared_ptr<Matroska::ContainerWriter> mContainer;
+    SinkSeq mSinks;
+    SourceManagers mSourceManagers;
+    stream* mStream;
+    FrameWriterPtr mWriter;
 };
 
 class ContainerServant : public ContainerImpl
@@ -1125,9 +1464,11 @@ class ContainerServant : public ContainerImpl
 public:
     ContainerServant(const ContainerInfoImplPtr& info, 
         const FileMediaSpecification& spec,
+        const Matroska::Environment* matroskaEnvironment,
         const Ice::ObjectAdapterPtr& adapter, const Logger& logger) :
        mInfo(info),
        mSpec(spec),
+       mMatroskaEnvironment(matroskaEnvironment),
        mAdapter(adapter),
        mLogger(logger)
     {
@@ -1179,7 +1520,8 @@ public:
             id += IceUtil::generateUUID();
 
             Ice::Identity iceId = mAdapter->getCommunicator()->stringToIdentity(id);
-            PlaybackSessionImplPtr playback = new PlaybackSessionImpl(mAdapter, id, mSpec, mLogger);
+            PlaybackSessionImplPtr playback = new PlaybackSessionImpl(mAdapter, id, mSpec, mMatroskaEnvironment,
+                mLogger);
             AsteriskSCF::Media::File::V1::FileSessionPrx result =  
                 AsteriskSCF::Media::File::V1::FileSessionPrx::uncheckedCast(mAdapter->add(playback, iceId));
             //
@@ -1203,6 +1545,7 @@ private:
 
     ContainerInfoImplPtr mInfo;
     FileMediaSpecification mSpec;
+    Matroska::Environment* mMatroskaEnvironment;
     Ice::ObjectAdapterPtr mAdapter;
     Logger mLogger;
 
@@ -1255,10 +1598,11 @@ typedef IceUtil::Handle<ContainerServant> ContainerServantPtr;
 ContainerImplPtr
 ContainerImpl::create(const ContainerInfoImplPtr& info,
     const FileMediaSpecification& spec,
+    const Matroska::Environment* matroskaEnvironment,
     const Ice::ObjectAdapterPtr& adapter,
     const AsteriskSCF::System::Logging::Logger& logger)
 {
-    return new ContainerServant(info, spec, adapter, logger);
+    return new ContainerServant(info, spec, matroskaEnvironment, adapter, logger);
 }
 
 
diff --git a/src/ContainerImpl.h b/src/ContainerImpl.h
index e962b92..ac44345 100644
--- a/src/ContainerImpl.h
+++ b/src/ContainerImpl.h
@@ -21,6 +21,11 @@
 
 #include "ContainerInfoImpl.h"
 
+namespace Matroska
+{
+class Environment;
+}
+
 namespace AsteriskSCF
 {
 namespace FileMediaService
@@ -38,6 +43,7 @@ public:
     static IceUtil::Handle<ContainerImpl> create(
         const ContainerInfoImplPtr& info,
         const AsteriskSCF::Media::File::V1::FileMediaSpecification& spec, 
+        const Matroska::Environment* matroskaEnvironment,
         const Ice::ObjectAdapterPtr& adapter,
         const AsteriskSCF::System::Logging::Logger& logger);
 };
diff --git a/src/ContainerRepository.cpp b/src/ContainerRepository.cpp
index 7542003..615e588 100644
--- a/src/ContainerRepository.cpp
+++ b/src/ContainerRepository.cpp
@@ -29,6 +29,8 @@
 #include <Ice/Ice.h>
 #include <IceUtil/UUID.h>
 
+#include "MatroskaUtil.h"
+
 //
 // Use latest boost stuff.
 //
@@ -128,6 +130,8 @@ private:
 
     set<string> mExtensions;
 
+    Matroska::Environment mMatroskaEnvironment;
+
     //
     // A *locked* accessor for obtaining the path string. Handy for methods that want the path,
     // but don't have a lock yet. NOT for use in locked scenarios.

commit c549bff7dd9259488cd818d95a047d38227547c6
Author: Brent Eagles <beagles at digium.com>
Date:   Tue Nov 1 16:32:32 2011 -0230

    Add new libebml2/libmatroska2 library helper classes.

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 12d2a9d..4a0c4c0 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -21,10 +21,15 @@ astscf_component_add_files(FileMediaService
 	RepositoryReplicationAdapter.h
 )
 
+astscf_component_add_files(FileMediaService
+    MatroskaUtil.h
+    MatroskaUtil.cpp
+    )
+
 astscf_component_add_ice_libraries(FileMediaService IceStorm)
 astscf_component_add_boost_libraries(FileMediaService core)
 astscf_component_add_slice_collection_libraries(FileMediaService FILEMEDIASERVICE)
 astscf_component_add_slice_collection_libraries(FileMediaService ASTSCF)
 astscf_component_build_icebox(FileMediaService)
-target_link_libraries(FileMediaService logging-client astscf-ice-util-cpp avformat avcodec avutil)
+target_link_libraries(FileMediaService logging-client astscf-ice-util-cpp)
 astscf_component_install(FileMediaService)
diff --git a/src/ContainerImpl.cpp b/src/ContainerImpl.cpp
index 3864aa6..4505fe2 100644
--- a/src/ContainerImpl.cpp
+++ b/src/ContainerImpl.cpp
@@ -28,17 +28,14 @@
 #include <boost/thread/shared_mutex.hpp>
 #include <boost/lexical_cast.hpp>
 
-extern "C"
-{
-#include <libavcodec/avcodec.h>
-#include <libavformat/avformat.h>
-}
+#include "MatroskaUtil.h"
 
 using namespace AsteriskSCF::FileMediaService::MediaContainer::V1;
 using namespace AsteriskSCF::Media::File::V1;
 using namespace AsteriskSCF::Media::V1;
 using namespace AsteriskSCF::System::Logging;
 using namespace std;
+using namespace Matroska;
 
 //
 // TODO: While working on the snags of our file handling library, I'm going to assume a single
@@ -52,6 +49,9 @@ namespace FileMediaService
 namespace Implementation
 {
 
+typedef boost::unique_lock<boost::shared_mutex> UniqueLock;
+typedef boost::shared_lock<boost::shared_mutex> SharedLock;
+
 /**
  *
  * Base class for file session driver. Basically defines and implements methods that
@@ -147,8 +147,6 @@ public:
 
 private:
     boost::shared_mutex mLock;
-    typedef boost::shared_lock<boost::shared_mutex> SharedLock;
-    typedef boost::unique_lock<boost::shared_mutex> UniqueLock;
     FileSessionIODriverPtr mElement;
     bool mDone;
 };
@@ -225,8 +223,6 @@ public:
 
 private:
     boost::shared_mutex mLock;
-    typedef boost::shared_lock<boost::shared_mutex> SharedLock;
-    typedef boost::unique_lock<boost::shared_mutex> UniqueLock;
 
     SessionCookies mCookies;
 };
@@ -526,8 +522,6 @@ public:
 
     private:
         boost::shared_mutex mLock;
-        typedef boost::shared_lock<boost::shared_mutex> SharedLock;
-        typedef boost::unique_lock<boost::shared_mutex> UniqueLock;
 
         typedef map<string, StreamSinkPrx> SinkMap;
 
@@ -592,6 +586,12 @@ public:
         }
     }
 
+    // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+    //
+    // Needs to be rewritten according to whatever third party library we are 
+    // using.
+    //
+    // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
     bool setup(const string& filename)
     {
         mLogger(Debug) << "Setting up playback session with file: " << filename;
@@ -599,6 +599,7 @@ public:
         // This should never be called twice.
         //
         assert(!mInput);
+
         int result = av_open_input_file(&mInput, filename.c_str(), 0, 0, 0);
         if (result < 0)
         {
@@ -893,8 +894,6 @@ public:
 
 private:
     boost::shared_mutex mLock;
-    typedef boost::shared_lock<boost::shared_mutex> SharedLock;
-    typedef boost::unique_lock<boost::shared_mutex> UniqueLock;
 
     Ice::ObjectAdapterPtr mObjectAdapter;
     const string mId;
@@ -920,9 +919,9 @@ typedef IceUtil::Handle<PlaybackSessionImpl> PlaybackSessionImplPtr;
 class FrameWriter : public IceUtil::Shared
 {
 public:
-    FrameWriter(AVFormatContext* output, AVOutputFormat* fmt ): 
-        mOutput(output),
-        mOutputFormat(fmt)
+    FrameWriter(unsigned trackCount) :
+        mEarliest(IceUtil::Time::seconds(0)),
+        mLatest(IceUtil::Time::seconds(0))
     {
     }
 
@@ -938,12 +937,31 @@ public:
 
     }
 
+    void queueFrames(unsigned trackIndex, const FrameSeq& frames)
+    {
+        mFrameQueues[trackIndex].insert(mFrameQueues[trackIndex].end(), frames.begin(), frames.end());
+
+        //
+        // Now here is the trick, we need one complete block of time for all incoming streams
+        // before we can mux them in. 
+        //
+        if (mEarliest.toSeconds() == 0 && !frames.empty())
+        {
+            StreamFramePtr streamFrame = StreamFramePtr::dynamicCast(frames.front());
+            mEarliest = IceUtil::Time::milliseconds(streamFrame->timestamp);
+            streamFrame = StreamFramePtr::dynamicCast(frames.back());
+            mLatest = IceUtil::Time::milliseconds(steamFrame->timestamp);
+        }
+    }
+
 private:
     boost::shared_mutex mLock;
-    typedef boost::unique_lock<boost::shared_mutex> UniqueLock;
-    typedef boost::shared_lock<boost::shared_mutex> SharedLock;
     AVFormatContext* mOutput;
     AVOutputFormat* mOutputFormat;
+    IceUtil::Time mEarliest; 
+    IceUtil::Time mLatest;
+
+    vector<FrameSeq> mFrameQueues;
 };
 
 typedef IceUtil::Handle<FrameWriter> FrameWriterPtr;
@@ -955,17 +973,23 @@ public:
     class SourceManager : public AsteriskSCF::Media::V1::StreamSink
     {
     public:
-        SourceManager(const string& id, const FrameWriterPtr&, unsigned trackNo) :
+        SourceManager(const string& id, const FrameWriterPtr& writer, unsigned trackNo) :
             mId(id),
-            mTrack(trackNo)
+            mWriter(writer),
+            mTrack(trackNo),
+            mWriting(false)
         {
         }
 
-        void write(const FrameSeq&, const Ice::Current&) 
+        void write(const FrameSeq& newFrames, const Ice::Current&) 
         {
-            //
-            // The frames really need to be queued up and written
-            //
+            UniqueLock lock(mLock);
+            if (!mWriting)
+            {
+                return;
+            }
+
+            mWriter->queueFrames(mTrack, newFrames);
         }
 
         void setSource(const StreamSourcePrx& source, const Ice::Current&)
@@ -991,19 +1015,24 @@ public:
 
         string getId(const Ice::Current&)
         {
-            SharedLock lock(mLock);
             return mId;
         }
 
+        void startWriting(const IceUtil::Time& startTime)
+        {
+            UniqueLock lock(mLock);
+            mWriting = true;
+            mStartTime = startTime;
+        }
+
     private:
         string mId;
         StreamSourcePrx mSource;
         unsigned mTrack;
         FrameWriterPtr mWriter;
-
-        boost::shared_mutex mLock;
-        typedef boost::unique_lock<boost::shared_mutex> UniqueLock;
-        typedef boost::shared_lock<boost::shared_mutex> SharedLock;
+        IceUtil::Time mStartTime;
+        bool mWriting;
+        FrameSeq mQueuedFrames;
     };
 
     RecordingSessionImpl(const Ice::ObjectAdapterPtr& adapter, const string& id, 
@@ -1171,12 +1200,6 @@ public:
 
 private:
     boost::shared_mutex mLock;
-    
-    //
-    // I cannot be the only one in the world who is tired of typing this out each time.
-    //
-    typedef boost::shared_lock<boost::shared_mutex> SharedLock;
-    typedef boost::unique_lock<boost::shared_mutex> UniqueLock;
 
     ContainerInfoImplPtr mInfo;
     FileMediaSpecification mSpec;
diff --git a/src/MatroskaDefines.h b/src/MatroskaDefines.h
old mode 100755
new mode 100644
diff --git a/src/MatroskaUtil.cpp b/src/MatroskaUtil.cpp
new file mode 100644
index 0000000..71bfd65
--- /dev/null
+++ b/src/MatroskaUtil.cpp
@@ -0,0 +1,1269 @@
+#include "MatroskaUtil.h"
+
+#define FILE_DLL /* */
+
+extern "C"
+{
+#include <config.h>     // This may pose a bit of a problem as it seems likely 
+                        // that it will conflict with other files of the same name. 
+#include <corec/helpers/file/streams.h>
+#include <corec/helpers/parser/parser.h>
+#include <corec/node/node.h>
+#include <corec/node/nodebase.h>
+#include <ebml/ebml.h>
+#include <matroska/matroska_sem.h>
+
+//
+// The initialization process includes "registering" these with the current
+// runtime.
+//
+extern const nodemeta LangStr_Class[];
+extern const nodemeta UrlPart_Class[];
+extern const nodemeta BufStream_Class[];
+extern const nodemeta MemStream_Class[];
+extern const nodemeta Streams_Class[];
+extern const nodemeta File_Class[];
+extern const nodemeta FileDb_Class[];
+extern const nodemeta VFS_Class[];
+extern const nodemeta Stdio_Class[];
+extern const nodemeta Matroska_Class[];
+extern const nodemeta EBMLElement_Class[];
+extern const nodemeta EBMLMaster_Class[];
+extern const nodemeta EBMLBinary_Class[];
+extern const nodemeta EBMLString_Class[];
+extern const nodemeta EBMLInteger_Class[];
+extern const nodemeta EBMLCRC_Class[];
+extern const nodemeta EBMLDate_Class[];
+extern const nodemeta EBMLVoid_Class[];
+};
+
+#include <iostream>
+
+using namespace AsteriskSCF::Media::V1;
+using namespace Matroska;
+using namespace std;
+
+string Matroska::errToString(err_t e)
+{
+    switch (e)
+    {
+    case ERR_NONE:
+        {
+            return "";
+        }
+    case ERR_BUFFER_FULL:
+        {
+            return "buffer full";
+        }
+    case ERR_OUT_OF_MEMORY:
+        {
+            return "out of memory";
+        }
+    case ERR_INVALID_DATA:
+        {
+            return "invalid data";
+        }
+
+    case ERR_INVALID_PARAM:
+        {
+            return "invalid param";
+        }
+    case ERR_NOT_SUPPORTED:
+        {
+            return "not supported";
+        }
+    case ERR_NEED_MORE_DATA:
+        {
+            return "need more data";
+        }
+    case ERR_FILE_NOT_FOUND:
+        {
+            return "file not found";
+        }
+    case ERR_END_OF_FILE:
+        {
+            return "end of file";
+        }
+    case ERR_DEVICE_ERROR:
+        {
+            return "device error";
+        }
+    case ERR_SYNCED:
+        {
+            return "synced";
+        }
+    case ERR_DATA_NOT_FOUND:
+        {
+            return "data not found";
+        }
+    case ERR_PROTO_NOT_FOUND:
+        {
+            return "proto not found";
+        }
+    case ERR_NOT_DIRECTORY:
+        {
+            return "not directory";
+        }
+    case ERR_NOT_COMPATIBLE:
+        {
+            return "not compatible";
+        }
+    case ERR_CONNECT_FAILED:
+        {
+            return "connect failed";
+        }
+    case ERR_DROPPING:
+        {
+            return "dropping";
+        }
+    case ERR_STOPPED:
+        {
+            return "stopped";
+        }
+    case ERR_UNAUTHORIZED:
+        {
+            return "unauthorized";
+        }
+    case ERR_LOADING_HEADER:
+        {
+            return "loading header";
+        }
+    case ERR_READ:
+        {
+            return "read";
+        }
+    case ERR_WRITE:
+        {
+            return "write";
+        }
+    case ERR_UNRESOLVED_ADDR:
+        {
+            return "unresolved addr";
+        }
+    case ERR_NO_NETWORK:
+        {
+            return "no network";
+        }
+    case ERR_TIME_OUT:
+        {
+            return "time out";
+        }
+    case ERR_KEY_NOT_UNIQUE:
+        {
+            return "key not unique";
+        }
+    case ERR_NOT_CONST:
+        {
+            return "not const";
+        }
+    case ERR_REDIRECTED:
+        {
+            return "redirected";
+        }
+    case ERR_CANCELED:
+        {
+            return "canceled";
+        }
+    case ERR_STREAM_CACHED:
+        {
+            return "stream cached";
+        }
+    case ERR_SERVER_ERROR:
+        {
+            return "server error";
+        }
+    case ERR_NOT_USABLE:
+        {
+            return "not usable";
+        }
+    }
+    return "(unknown)";
+}
+
+InitError::InitError(err_t e) :
+    runtime_error(errToString(e)),
+    mErr(e)
+{
+}
+
+err_t InitError::errorCode() const
+{
+    return mErr;
+}
+
+RenderError::RenderError(err_t e) :
+    runtime_error(errToString(e)),
+    mErr(e)
+{
+}
+
+err_t RenderError::errorCode() const
+{
+    return mErr;
+}
+
+ReadError::ReadError(err_t e) :
+    runtime_error(errToString(e)),
+    mErr(e)
+{
+}
+
+err_t ReadError::errorCode() const
+{
+    return mErr;
+}
+
+FormatError::FormatError(const string& msg) :
+    logic_error(msg)
+{
+}
+
+WriteError::WriteError(err_t e) :
+    runtime_error(errToString(e)),
+    mErr(e)
+{
+}
+
+err_t WriteError::errorCode() const
+{
+    return mErr;
+}
+
+InvalidTrackNumber::InvalidTrackNumber() :
+    runtime_error("the specified track number is out range")
+{
+}
+
+Environment::Environment() :
+    mContext(new parsercontext)
+{
+    ParserContext_Init(mContext, 0, 0, 0);
+    nodemodule* mod = reinterpret_cast<nodemodule*>(mContext);
+    NodeRegisterClassEx(mod, BufStream_Class);
+    NodeRegisterClassEx(mod, MemStream_Class);
+    NodeRegisterClassEx(mod, Streams_Class);
+    NodeRegisterClassEx(mod, File_Class);
+    NodeRegisterClassEx(mod, Stdio_Class);
+    NodeRegisterClassEx(mod, LangStr_Class);
+    NodeRegisterClassEx(mod, UrlPart_Class);
+    NodeRegisterClassEx(mod, Matroska_Class);
+    NodeRegisterClassEx(mod, EBMLElement_Class);
+    NodeRegisterClassEx(mod, EBMLMaster_Class);
+    NodeRegisterClassEx(mod, EBMLBinary_Class);
+    NodeRegisterClassEx(mod, EBMLString_Class);
+    NodeRegisterClassEx(mod, EBMLInteger_Class);
+    NodeRegisterClassEx(mod, EBMLCRC_Class);
+    NodeRegisterClassEx(mod, EBMLDate_Class);
+    NodeRegisterClassEx(mod, EBMLVoid_Class);
+
+    //
+    // May need to setup some more stuff.
+    //
+    err_t result = MATROSKA_Init(reinterpret_cast<nodecontext*>(mContext));
+    if (result != ERR_NONE)
+    {
+        throw InitError(result);
+    }
+}
+
+Environment::~Environment()
+{
+    MATROSKA_Done(reinterpret_cast<nodecontext*>(mContext));
+    delete mContext;
+}
+
+parsercontext* Environment::parserContext()
+{
+    return mContext;
+}
+
+//
+// A couple of helpers for node cleanup.
+//
+static void deleteNode(ebml_master** element)
+{
+    if(*element)
+    {
+        NodeDelete((node*)*element);
+        *element = 0;
+    }
+}
+
+static void deleteNode(ebml_element** element)
+{
+    if(*element)
+    {
+        NodeDelete((node*)*element);
+        *element = 0;
+    }
+}
+
+static bool checkValue(ebml_element* el, const ebml_context* contextClass, const int64_t limit, 
+    string& message, stream* str)
+{
+    if (EBML_ElementIsType(el, contextClass))
+    {
+        if (EBML_ElementReadData(el, str, 0, 0, SCOPE_ALL_DATA, 0) != ERR_NONE)
+        {
+            message = "Unable to read data";
+        }
+        if (EBML_IntegerValue((ebml_integer*)el) > limit)
+        {
+            message = "Exceeds limit";
+        }
+        return true;
+    }
+    return false;
+}
+
+static bool checkValue(ebml_element* el, const ebml_context* contextClass, 
+    const vector<string>& validValues, string& message, stream* str)
+{
+    if (EBML_ElementIsType(el, contextClass))
+    {
+        if (EBML_ElementReadData(el, str, 0, 0, SCOPE_ALL_DATA, 0) != ERR_NONE)
+        {
+            message = "Unable to read data";
+            return false;
+        }
+        //
+        // Mmmmm... platform dependent char sizes are going to mess up what I am about
+        // to do. Maybe boost has something to help out here.
+        //
+        tchar_t strValue[MAXLINE];
+        EBML_StringGet((ebml_string*)el, strValue, TSIZEOF(strValue));
+        bool matched = false;
+        for (vector<string>::const_iterator iter = validValues.begin();
+            iter != validValues.end() && !matched; ++iter)
+        {
+            matched = (string(strValue) == *iter);
+        }
+        return matched;
+    }
+    return false;
+}
+
+//
+// mkclean has this read/verify function that goes through the header elements,
+// verifying their values until it comes across the Matroska "segment". It seems
+// a good way to get to the segment whilst keeping out of trouble so I'm writing 
+// a similar function here.
+//
+static ebml_element* checkMatroskaHeader(
+    const ebml_element* header, 
+    const ebml_parser_context* parentContext,
+    stream* input)
+{
+    //
+    // We establish a local parser context as it constrains
+    // the scope of what we are going to do next to the header
+    // are of the file. If we used the parent context, we could
+    // hypothetically find ourselves in the segment, reading through
+    // level 1 elements.
+    //
+    ebml_parser_context context;
+    context.UpContext = parentContext;
+    context.Context = EBML_ElementContext(header);
+    context.EndPosition = EBML_ElementPositionEnd(header);
+    context.Profile = parentContext->Profile;
+
+    vector<string> supportedDocTypes;
+    supportedDocTypes.push_back("matroska");
+    supportedDocTypes.push_back("webm");
+
+    int upperElement = 0;
+    for (ebml_element* el = EBML_FindNextElement(input, &context, &upperElement, 1);
+         el != 0; 
+         el = EBML_FindNextElement(input, &context, &upperElement, 1))
+    {
+        string errorMessage;
+        if (checkValue(el, &EBML_ContextReadVersion, EBML_MAX_VERSION, errorMessage, input))
+        {
+            if (!errorMessage.empty())
+            {
+                cerr << errorMessage << endl;
+                return 0;
+            }
+        }
+        else if (checkValue(el, &EBML_ContextMaxIdLength, EBML_MAX_ID, errorMessage, input))
+        {
+            if (!errorMessage.empty())
+            {
+                cerr << errorMessage << endl;
+                return 0;
+            }
+        }
+        else if (checkValue(el, &EBML_ContextMaxSizeLength, EBML_MAX_SIZE, errorMessage, input))
+        {
+            if (!errorMessage.empty())
+            {
+                cerr << errorMessage << endl;
+                return 0;
+            }
+        }
+        else if (checkValue(el, &EBML_ContextDocType, supportedDocTypes, errorMessage, input))
+        {
+            if (!errorMessage.empty())
+            {
+                cerr << errorMessage << endl;
+                return 0;
+            }
+        }
+        else if (checkValue(el, &EBML_ContextDocTypeReadVersion, MATROSKA_VERSION, errorMessage, input))
+        {
+            if (!errorMessage.empty())
+            {
+                cerr << errorMessage << endl;
+                return 0;
+            }
+        }
+        else if (EBML_ElementIsType(el, &MATROSKA_ContextSegment))
+        {
+            return el;
+        }
+        else
+        {
+            EBML_ElementSkipData(el, input, 0, 0, 0);
+        }
+        deleteNode(&el);
+    }
+    return 0;
+}
+
+ClusterCreeper::ClusterCreeper(matroska_cluster* cluster, stream* io,
+    ebml_master* tracks,
+    ebml_master* segment) :
+    mCluster(cluster),
+    mIO(io)
+{
+    MATROSKA_LinkClusterBlocks(cluster, segment, tracks, false);
+    //
+    // Firstly, we read the data in!
+    //
+    for (ebml_element* el = EBML_MasterChildren(cluster); el != 0; el = EBML_MasterNext(el))
+    {
+        if (EBML_ElementIsType(el, &MATROSKA_ContextBlockGroup))
+        {
+            for (ebml_element* b = EBML_MasterChildren(el); b != 0; b = EBML_MasterNext(b))
+            {
+                if (EBML_ElementIsType(b, &MATROSKA_ContextBlock))
+                {
+                    err_t result = MATROSKA_BlockReadData((matroska_block*)b, mIO);
+
+                    mBlocks.push_back((matroska_block*)b);
+                    if (result != ERR_NONE)
+                    {
+                        cerr << "Damn! Coudn't read this block's data." << endl;
+                        deleteNode(&el);
+                        throw ReadError(result);
+                    }
+                }
+            }
+        }
+        else if (EBML_ElementIsType(el, &MATROSKA_ContextSimpleBlock))
+        {
+            err_t result = MATROSKA_BlockReadData((matroska_block*)el, mIO);
+            if (result != ERR_NONE)
+            {
+                cerr << "Damn! Coudn't read this block's data." << endl;
+                deleteNode(&el);
+                throw ReadError(result);
+            }
+            mBlocks.push_back((matroska_block*)el);
+        }
+    }
+    mCurrentBlock = mBlocks.begin();
+    mCurrentFrame = 0;
+}
+
+ClusterCreeper::~ClusterCreeper()
+{
+    for (BlockSeq::iterator iter = mBlocks.begin(); iter != mBlocks.end(); ++iter)
+    {
+        MATROSKA_BlockReleaseData(*iter, true);
+    }
+}
+
+TrackFrame* ClusterCreeper::getNextFrame()
+{
+    if (mCurrentFrame >= MATROSKA_BlockGetFrameCount(*mCurrentBlock))
+    {
+        ++mCurrentBlock;
+        mCurrentFrame = 0;
+        if (mCurrentBlock == mBlocks.end())
+        {
+            return 0;
+        }
+    }
+
+    TrackFrame* result = new TrackFrame;
+    result->track = MATROSKA_BlockTrackNum(*mCurrentBlock);
+    err_t errCode = MATROSKA_BlockGetFrame(*mCurrentBlock, mCurrentFrame++, &(result->frameData), true);
+    if (errCode != ERR_NONE)
+    {
+        cerr << "Dang! Could not get this frame!" << endl;
+        throw ReadError(errCode);
+    }
+    return result;
+}
+
+ContainerReader::ContainerReader(stream* s) :
+    mStream(s),
+    mEndOfSegment(0),
+    mEbmlHeader(0),
+    mDuration(0.0)
+{
+    setFileSize();
+}
+
+ContainerReader::~ContainerReader()
+{
+    StreamClose(mStream);
+}
+
+size_t ContainerReader::fileSize()
+{
+    return mFileSize;
+}
+
+size_t ContainerReader::endOfSegment()
+{
+    return mEndOfSegment;
+}
+
+int64_t ContainerReader::timecodeScale()
+{
+    return mTimecodeScale;
+}
+
+double ContainerReader::duration()
+{
+    return mDuration;
+}
+
+size_t ContainerReader::trackCount()
+{
+    return mTrackCount;
+}
+
+int64_t ContainerReader::clusterCount()
+{
+    return mSections.clusterInformation.size();
+}
+
+//
+// Perform a series of operations designed to verify that we can
+// make sense of the file as a matroska file.
+//
+bool ContainerReader::validate()
+{
+    //
+    // TODO: Actually, we'll want to hold onto some of this stuff, so
+    // this needs to be reworked into a class/struct that is a data member
+    //
+    mEBMLContext.Context = &MATROSKA_ContextStream;
+    mEBMLContext.EndPosition = INVALID_FILEPOS_T;
+    mEBMLContext.UpContext = 0;
+    mEBMLContext.Profile = 0;
+
+    //
+    // topElement is read out of the FindNextElement call... I'm not 100% what it means 
+    // yet.
+    //
+    int topElement = 0; 
+    mEbmlHeader = (ebml_master*)EBML_FindNextElement(mStream, &mEBMLContext, &topElement, 0);
+    if (!mEbmlHeader || !EBML_ElementIsType((ebml_element*)mEbmlHeader, &EBML_ContextHead))
+    {
+        return false;
+    }
+
+    ebml_master* segment= 
+        (ebml_master*)checkMatroskaHeader((ebml_element*)mEbmlHeader, &mEBMLContext, mStream);
+
+    if (!segment) 
+    {
+        cerr << "Unable to verify Matroska segment" << endl;
+        return false;
+    }
+
+    if (EBML_ElementPositionEnd((ebml_element*)segment) != INVALID_FILEPOS_T)
+    {
+        mEndOfSegment = EBML_ElementPositionEnd((ebml_element*)segment);
+    }
+
+    mSegmentContext.Context = &MATROSKA_ContextSegment;
+    mSegmentContext.EndPosition = EBML_ElementPositionEnd((ebml_element*)segment);
+    mSegmentContext.UpContext = &mEBMLContext;
+    mSegmentContext.Profile = PROFILE_MATROSKA_V3;
+
+    topElement = 0;
+
+    //
+    // Gather level 1 elements in the segment. See matroska.org for spec details, but
+    // basically a matroska file is organized into multiple level 1 groups. The actual
+    // media data is in the cluster group, but you want the tracks and possibly
+    // the cues and tags as well. Cues make it possible to find your way through the
+    // clusters by time code a lot quicker than scanning the whole file.
+    //
+
+    //
+    // EBML_FindNextElement iterates through the structure of the file, only pulling out
+    // the most basic info. To get the actual data, you need to actually read it with
+    // EBML_ElementReadData. ElementReadData takes a pointer to the element being read,
+    // the stream and the parser context. The next arguments are to allow dummy records
+    // to be read, how much/deep to read the data, and the depth of the CRC checks. 
+    // For the most part this is yes, give it all to me, and zero respectively. The
+    // exception is the clusters... since this is the data itself, we don't want to load
+    // it all into memory just now, so just the bones of each cluster is read. The data
+    // doesn't get loaded till we need it. 
+    //
+
+    for (ebml_master* currentLevel1Group = 
+        (ebml_master*)EBML_FindNextElement(mStream, &mSegmentContext, &topElement, 1);
+        currentLevel1Group != 0;
+        currentLevel1Group = 
+        (ebml_master*)EBML_FindNextElement(mStream, &mSegmentContext, &topElement, 1))
+    {
+        if (EBML_ElementIsType((ebml_element*)currentLevel1Group , &MATROSKA_ContextInfo))
+        {
+            if (EBML_ElementReadData(currentLevel1Group, mStream, &mSegmentContext, 1,
+                    SCOPE_ALL_DATA, 0) == ERR_NONE)
+            {
+                mSections.segmentInformation = currentLevel1Group;
+            } 
+        }
+        else if (EBML_ElementIsType((ebml_element*)currentLevel1Group, &MATROSKA_ContextTracks))
+        {
+            if (EBML_ElementReadData(currentLevel1Group, mStream, &mSegmentContext, 1,
+                    SCOPE_ALL_DATA, 0) == ERR_NONE)
+            {
+                mSections.trackInformation = currentLevel1Group;
+            } 
+        }
+        else if (EBML_ElementIsType((ebml_element*)currentLevel1Group, &MATROSKA_ContextTags))
+        {
+            if (EBML_ElementReadData(currentLevel1Group, mStream, &mSegmentContext, 1,
+                    SCOPE_ALL_DATA, 0) == ERR_NONE)
+            {
+                mSections.tagInformation = currentLevel1Group;
+            } 
+        }
+        else if (EBML_ElementIsType((ebml_element*)currentLevel1Group, &MATROSKA_ContextCues))
+        {
+            if (EBML_ElementReadData(currentLevel1Group, mStream, &mSegmentContext, 1,
+                    SCOPE_ALL_DATA, 0) == ERR_NONE)
+            {
+                mSections.cueInformation = currentLevel1Group;
+            } 
+        }
+        else if (EBML_ElementIsType((ebml_element*)currentLevel1Group, 
+                &MATROSKA_ContextAttachments))
+        {
+            if (EBML_ElementReadData(currentLevel1Group, mStream, &mSegmentContext, 1,
+                    SCOPE_ALL_DATA, 0) == ERR_NONE)
+            {
+                mSections.attachmentInformation = currentLevel1Group;
+            } 
+        }
+        else if (EBML_ElementIsType((ebml_element*)currentLevel1Group, 
+                &MATROSKA_ContextCluster))
+        {
+            if (EBML_ElementReadData(currentLevel1Group, mStream, &mSegmentContext, false,
+                    SCOPE_PARTIAL_DATA, 0) == ERR_NONE)
+            {
+                mSections.clusterInformation.push_back((matroska_cluster*)currentLevel1Group);
+
+                //
+                // mkvclean, which is used as the example to help figure
+                // this stuff out deletes the position and previous size
+                // from the cluster from the cluster. We won't do that for
+                // the time being.
+                //
+                ebml_master* weAreSkippingData = 
+                    (ebml_master*)EBML_ElementSkipData((ebml_element*)currentLevel1Group,
+                        mStream, &mSegmentContext, 0, 1);
+                if (!weAreSkippingData)
+                {
+                    //
+                    // I *think* we have to break out of the for loop here.
+                    // A 0 result from EBML_ElementSkipData probably
+                    // indicates that we have reached the end of the road.
+                    // 
+                }
+            } 
+        }
+        else
+        {
+            //
+            // This is just something else... could be void space put in the file
+            // to leave room for another top level group. Skip this.
+            //
+            ebml_master* zilch = (ebml_master*)EBML_ElementSkipData(
+                (ebml_element*)currentLevel1Group, mStream, &mSegmentContext, 0, 1);
+            assert(!zilch); 
+            //
+            // Since this data is not useful to us, we need to clean it up now.
+            //
+            NodeDelete((node*)currentLevel1Group);
+        }
+    }
+
+    mTimecodeScale = MATROSKA_SegmentInfoTimecodeScale(mSections.segmentInformation);
+
+    if (mTimecodeScale == 0)
+    {
+        mTimecodeScale = 1000000;
+    }
+
+    ebml_float* duration = (ebml_float*)EBML_MasterFindChild(mSections.segmentInformation,
+        &MATROSKA_ContextDuration);
+    if (duration)
+    {
+        mDuration = EBML_FloatValue(duration);
+    }
+
+    mTrackCount = 0;
+
+    for (ebml_master* trackEntry = (ebml_master*)EBML_MasterFindChild(mSections.trackInformation, &MATROSKA_ContextTrackEntry);
+        trackEntry != 0; trackEntry = (ebml_master*)EBML_MasterNextChild(mSections.trackInformation, trackEntry))
... 873 lines suppressed ...


-- 
asterisk-scf/integration/file_media_service.git



More information about the asterisk-scf-commits mailing list