[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
Tue Oct 4 13:47:49 CDT 2011


branch "initial_development" has been updated
       via  0d32acb0c24b52ff4b8d0a0a85a2647ed73d0e34 (commit)
      from  4741733d1c0eb93737a3d62cea8baf774ab97b32 (commit)

Summary of changes:
 src/CMakeLists.txt                    |    2 +
 src/{Config.h => MatroskaCodec.cpp}   |   59 +-
 src/MatroskaCodec.h                   |   83 +++
 src/MatroskaUtils.cpp                 | 1005 +++++++++++++++++++++++++++++----
 src/MatroskaUtils.h                   |  226 ++++++--
 test/CMakeLists.txt                   |    3 -
 test/UnitTest_ContainerRepository.cpp |  161 +++++-
 7 files changed, 1321 insertions(+), 218 deletions(-)
 copy src/{Config.h => MatroskaCodec.cpp} (63%)
 mode change 100644 => 100755
 create mode 100755 src/MatroskaCodec.h


- Log -----------------------------------------------------------------
commit 0d32acb0c24b52ff4b8d0a0a85a2647ed73d0e34
Author: Brent Eagles <beagles at digium.com>
Date:   Tue Oct 4 16:16:58 2011 -0230

    Some reading support.

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 7f8d7ff..d1fcd97 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -25,6 +25,8 @@ astscf_component_add_files(FileMediaService
 	RepositoryConfigurationAdapter.h
 	RepositoryReplicationAdapter.h
         MatroskaDefines.h
+        MatroskaCodec.h
+        MatroskaCodec.cpp
         MatroskaUtils.h
         MatroskaUtils.cpp
 )
diff --git a/src/MatroskaCodec.cpp b/src/MatroskaCodec.cpp
new file mode 100755
index 0000000..fdfcd88
--- /dev/null
+++ b/src/MatroskaCodec.cpp
@@ -0,0 +1,30 @@
+/*
+ * Asterisk SCF -- An open-source communications framework.
+ *
+ * Copyright (C) 2011, Digium, Inc.
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk SCF project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE.txt file
+ * at the top of the source tree.
+ */
+
+#include "MatroskaCodec.h"
+
+using namespace AsteriskSCF::MatroskaContainer::Implementation;
+
+
+CodecPtr MatroskaCodecTranslator::create(const AsteriskSCF::Media::V1::FormatPtr& format)
+{
+    return CodecPtr();
+}
+
+AsteriskSCF::Media::V1::FormatPtr create(const CodecPtr& codec)
+{
+    return AsteriskSCF::Media::V1::FormatPtr();
+}
\ No newline at end of file
diff --git a/src/MatroskaCodec.h b/src/MatroskaCodec.h
new file mode 100755
index 0000000..526b322
--- /dev/null
+++ b/src/MatroskaCodec.h
@@ -0,0 +1,83 @@
+/*
+ * Asterisk SCF -- An open-source communications framework.
+ *
+ * Copyright (C) 2011, Digium, Inc.
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk SCF project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE.txt file
+ * at the top of the source tree.
+ */
+
+#include <AsteriskSCF/Media/MediaIf.h>
+#include <boost/shared_ptr.hpp>
+#include <vector>
+#include <string>
+
+namespace AsteriskSCF
+{
+namespace MatroskaContainer
+{
+namespace Implementation
+{
+
+/** 
+ * Base class for mapping codec information into matroska codec encodings.
+ * TODO: Matching implementation and factories for each codec of import/or translations
+ * to AsteriskSCF Media Format classes.
+ */
+class Codec
+{
+public:
+    virtual ~Codec() {}
+
+    virtual std::vector<unsigned char> privateBytes() const = 0;
+    virtual std::string id() const = 0;
+};
+typedef boost::shared_ptr<Codec> CodecPtr;
+
+/**
+ * Specialization for audio codecs.
+ */
+class AudioCodec : public Codec
+{
+public:
+    virtual unsigned int channels() = 0;
+    virtual double sampleRate() = 0;
+    virtual unsigned int bitDepth() = 0;
+    virtual double outputSampleRate() = 0;
+};
+typedef boost::shared_ptr<AudioCodec> AudioCodecPtr;
+
+/** 
+ * Specialization for video codecs.
+ */
+class VideoCodec : public Codec
+{
+public:
+    virtual unsigned int width() = 0;
+    virtual unsigned int height() = 0;
+    virtual unsigned int displayWidth() = 0;
+    virtual unsigned int displayHeight() = 0;
+};
+typedef boost::shared_ptr<VideoCodec> VideoCodecPtr;
+
+/**
+ * Facility for converting AsteriskSCF format objects to Matroska utility codecs and
+ * vice-versa.
+ */
+class MatroskaCodecTranslator
+{
+public:
+    static CodecPtr create(const AsteriskSCF::Media::V1::FormatPtr& format);
+    static AsteriskSCF::Media::V1::FormatPtr create(const CodecPtr& codec);
+};
+
+} /* End of namespace implementation */
+} /* End of namespace MatroskaContainer */
+} /* End of namespace AsteriskSCF */
\ No newline at end of file
diff --git a/src/MatroskaUtils.cpp b/src/MatroskaUtils.cpp
index 59a2726..148c5c3 100755
--- a/src/MatroskaUtils.cpp
+++ b/src/MatroskaUtils.cpp
@@ -14,6 +14,9 @@
  * at the top of the source tree.
  */
 
+// boost does something here that VC++ complains loudly and annoyingly about
+#pragma warning(disable: 4996)  
+
 #include "MatroskaUtils.h"
 
 #include <ebml/EbmlHead.h>
@@ -35,13 +38,15 @@
 #include <sstream>
 #include <map>
 #include <iostream>
+#include <list>
 
 #include <time.h>
 
 #include <boost/uuid/uuid.hpp>
 #include <boost/uuid/uuid_generators.hpp>
-
 #include <boost/enable_shared_from_this.hpp>
+#include <boost/thread/locks.hpp>
+#include <boost/thread/shared_mutex.hpp>
 
 using namespace libebml;
 using namespace libmatroska;
@@ -54,6 +59,21 @@ namespace MatroskaContainer
 namespace Implementation
 {
 
+//
+// A little range checking casting function I cooked up. It
+// isn't used consistently because I decided to write it part
+// way through and didn't retro fit.
+//
+inline unsigned char trackNo(unsigned long value)
+{
+    assert(value < 256);
+    if (value > 255)
+    {
+        throw InvalidTrackNumber();
+    }
+    return static_cast<unsigned char>(value);
+}
+
 int AudioTrackType::getType() const
 {
     return track_audio;
@@ -79,16 +99,40 @@ void Header::writeTo(const OpaqueContainerDataPtr& data)
     uintField = dynamic_cast<EbmlUInteger*>(&docTypeReadVer);
     assert(uintField);
     *uintField = 1;
+
+    //
+    // DO NOT allow default arg or pass false as the second argument! Many
+    // code examples do, but it will end in tears! The matroska lib will
+    // not write "default" values if you do and to it, and value that is
+    // set but is the same as it might have as a default is the same
+    // thing as you not setting it. 
+    //
     header.Render(*data->getIO(), true);
 }
+    
+namespace Internal
+{
 
-class SegmentImpl;
-typedef boost::shared_ptr<SegmentImpl> SegmentImplPtr;
+typedef boost::shared_ptr<KaxSegment> KaxSegmentPtr;
 
+
+class ContainerWriterImpl;
+typedef boost::shared_ptr<ContainerWriterImpl> ContainerWriterImplPtr;
+
+//
+// Internal wrapper around KaxCluster. Do NOT delete mCluster, you don't
+// own it! Cluster for the moment is about writing only.. the name should 
+// probably be changed.
+//
+// Note: the matroska website says that blocks that are part of a cluster
+// have timecodes relative to the parent cluster. This seems not to be
+// the case... mkvtoolnix does not generate them this way and matroska lib
+// freaks out if you try that.
+//
 class Cluster 
 {
 public:
-    Cluster(KaxCluster* cluster, const SegmentImplPtr& segment, 
+    Cluster(KaxCluster* cluster, const ContainerWriterImplPtr& segment, 
         uint64 initialTimeCode, uint64 lastTimeCode);
     uint64 startTimeCode() const;
     void render(IOCallback* io, KaxCues* cues);
@@ -98,25 +142,33 @@ public:
 
 private:
     KaxCluster* mCluster;
-    SegmentImplPtr mSegment;
+    ContainerWriterImplPtr mContainer;
     const uint64 mTimeCode;
     uint64 mSize;
 };
 typedef boost::shared_ptr<Cluster> ClusterPtr;
 
-class TrackImpl : public Track, public boost::enable_shared_from_this<TrackImpl>
+//
+// Implementation of the A::M::I::TrackWriter. Like many things, deleting the wrapped
+// KaxEntry will probably end in tears. If there is a proper time to kill it, 
+// it is not clear yet. It is probably simply a pointer to an internal structure
+// of KaxSegment so let's not worry about it for now.
+//
+class Writer : public TrackWriter, public boost::enable_shared_from_this<Writer>
 {
 public:
-    TrackImpl(const SegmentImplPtr& segment, KaxTrackEntry* t, unsigned char number);
+    Writer(const ContainerWriterImplPtr& containerWriter, KaxTrackEntry* t, unsigned char number);
     void setCodecAndType(const CodecPtr& codec, const TrackTypePtr& trackType);
     void addTag(const string& name, const string& value);
     void write(uint64 timeCode, uint32 duration, const unsigned char* data, size_t len);
+
+    uint64 getSize();
     TagSeq getTags();
     void finished();
     uint64 getID();
 
 private:
-    SegmentImplPtr mSegment;
+    ContainerWriterImplPtr mContainer;
     KaxTrackEntry* mTrack;
     CodecPtr mCodec;
     TrackTypePtr mTrackType;
@@ -127,31 +179,155 @@ private:
     uint64 mByteCount;
     uint64 mTotalTime;
 };
+typedef boost::shared_ptr<Writer> WriterPtr;
 
-typedef boost::shared_ptr<TrackImpl> TrackImplPtr;
+class ContainerReaderImpl;
+typedef boost::shared_ptr<ContainerReaderImpl> ContainerReaderImplPtr;
 
-class SegmentImpl : public Segment, public boost::enable_shared_from_this<SegmentImpl>
+/**
+ * I'm going to implement this stuff in line so it is a little more clear what is going
+ * on. I *will* document it, but for the moment this is more for me so I don't forget
+ * what I'm at.
+ */
+class BlockAccessTracker
 {
 public:
-    SegmentImpl();
-    void addTo(const OpaqueContainerDataPtr& data);
-    TrackPtr getTrack(unsigned char track);
-    void close();
+    BlockAccessTracker(KaxBlockGroup* block) :
+      mBlock(block)
+    {
+    }
+
+    ~BlockAccessTracker()
+    {
+        mBlock->ReleaseFrames();
+    }
+
+    KaxBlockGroup* blockGroup()
+    {
+        return mBlock;
+    }
+    
+private:
+    KaxBlockGroup* mBlock;
+};
+
+typedef boost::shared_ptr<BlockAccessTracker> BlockAccessTrackerPtr;
+typedef vector<BlockAccessTrackerPtr> BlockAccessTrackers;
+
+class TrackingDataBuf
+{
+public:
+    TrackingDataBuf(const BlockAccessTrackers& trackers);
+
+    size_t fillFrames(FrameSeq& frames, size_t frameCount);
 
     //
-    // Internal implementation details.
+    // Shouldn't be used by anybody.. more for testing than anything
     //
+    BlockAccessTrackers getTrackers() { return mTrackers; }
+    
+private:
+    BlockAccessTrackers  mTrackers;
+    BlockAccessTrackers::iterator mCurrentTracker;
+    size_t mCurrentFrame;
+};
+typedef boost::shared_ptr<TrackingDataBuf> TrackingDataBufPtr;
 
-    ClusterPtr getCluster(uint64 currentTimeCode);
-    KaxSegment& kax();
-    uint64 globalTimeScale() const;
-    uint64 addTime(uint64 moreTime);
+typedef vector<KaxCluster*> KaxClusterSeq;
+typedef vector<KaxBlockGroup*> KaxBlockGroupSeq;
+
+class TrackData
+{
+public:
+    TrackData(const KaxSegmentPtr& segment, const OpaqueContainerDataPtr& data);
+
+    void addBlock(KaxBlockGroup* block);
+
+    uint64 getStartTimeCode();
+    uint64 getLastTimeCode();
+    uint64 getDuration();
 
-    void commit(const TrackImplPtr& track);
+    TrackingDataBufPtr getFromUntil(uint64 startTime, uint64 endTime);
+    
+    TrackingDataBufPtr getBlocks(size_t pos, size_t count);
 
 private:
-    uint64 mSegmentStart;
-    KaxSegment mSegment;
+    KaxSegmentPtr mSegment;
+    BlockAccessTrackers mBlocks;
+    OpaqueContainerDataPtr mDataFile;
+
+    boost::shared_mutex mLock;
+    typedef boost::unique_lock<boost::shared_mutex> UniqueLock;
+    typedef boost::shared_lock<boost::shared_mutex> SharedLock;
+};
+
+typedef boost::shared_ptr<TrackData> TrackDataPtr;
+
+//
+// Track number to track data mapping!
+//
+typedef map<unsigned char, TrackDataPtr> TrackDataMap;
+
+//
+// A given container has only one pool and every container view gets
+// their data from here.
+// 
+class TrackDataPool
+{
+public:
+    TrackDataPool(const KaxSegmentPtr& container, const OpaqueContainerDataPtr& dataFile);
+    TrackDataPtr getTrackData(unsigned char trackNumber);
+private:
+    KaxSegmentPtr mSegment;
+    TrackDataMap mDataMap;
+    OpaqueContainerDataPtr mDataFile;
+    boost::shared_mutex mLock;
+    typedef boost::unique_lock<boost::shared_mutex> UniqueLock;
+    typedef boost::shared_lock<boost::shared_mutex> SharedLock;
+};
+typedef boost::shared_ptr<TrackDataPool> TrackDataPoolPtr;
+
+//
+// Implementation of the TrackReader
+//
+class Reader : public TrackReader , public boost::enable_shared_from_this<Reader>
+{
+public:
+    Reader(const ContainerReaderImplPtr& containerReader, KaxTrackEntry* t, unsigned char number,
+        const TrackDataPtr& data);
+
+    uint64 getSize();
+    TagSeq getTags();
+    uint64 getID();
+
+    void reset();
+
+    void getFrames(FrameSeq& frames, size_t requestedNumberOfFrames);
+    
+private:
+    ContainerReaderImplPtr mContainer;
+    TrackDataPtr mTrackData;
+    KaxTrackEntry* mTrack;
+    TrackingDataBufPtr mCurrentData;
+    const unsigned char mTrackNumber;
+    size_t mBlockIndex;
+};
+typedef boost::shared_ptr<Reader> ReaderPtr;
+
+//
+// Base class for both the reader and writers of KSegments.
+//
+class ContainerImplBase
+{
+public:
+    ContainerImplBase(const KaxSegmentPtr& segment);
+
+    KaxSegment& kax();
+
+    uint64 globalTimeScale() const;
+
+protected:
+    KaxSegmentPtr mSegment;
     KaxSeekHead* mSeekHead;
     EbmlVoid* mIndexSpace;
     KaxTracks* mTracks;
@@ -163,10 +339,11 @@ private:
     ClusterPtr mActiveCluster;
 
     //
-    // Might be configurable in the future? For now.. we'll be content to leave it as constant member.
+    // Should be configurable for new files. Existing files will have this set on them. If
+    // they do, we will probably want to normalize to something consistent for the service.
     //
-    const uint64 mTimeCodeScale;
-    const uint64 mMaxTimePerCluster;
+    uint64 mTimeCodeScale;
+    uint64 mMaxTimePerCluster;
     uint64 mPrevTime;
     uint16 mPrevTrack;
     uint64 mLastTime;
@@ -174,21 +351,312 @@ private:
     typedef map<uint64, TagSeq> TrackTags;
     TrackTags mTrackTags;
 
-    size_t mInitialSegmentSize;
     uint64 mClusterCount;
+
+    void initialize();
 };
 
-Cluster::Cluster(KaxCluster* cluster, const SegmentImplPtr& segment, 
+//
+// Wrapper around the "segment" for writing, where everything happens.
+//
+class ContainerWriterImpl : public ContainerWriter, public ContainerImplBase,
+    public boost::enable_shared_from_this<ContainerWriterImpl>
+{
+public:
+    //
+    // The default constructor is creating a new segment.
+    //
+    ContainerWriterImpl();
+
+
+    void addTo(const OpaqueContainerDataPtr& data);
+
+    size_t getTrackCount();
+    TrackWriterPtr getTrack(unsigned char track);
+
+    void close();
+
+    //
+    // Internal implementation details.
+    //
+
+    ClusterPtr getCluster(uint64 currentTimeCode);
+    uint64 addTime(uint64 moreTime);
+
+    void commit(const WriterPtr& track);
+
+private:
+    ClusterPtr mActiveCluster;
+};
+
+// 
+// Wrapper around the "segment" for reading, where everything happens... well, reading stuff that is...
+// the writing stuff is.. well you know.
+//
+class ContainerReaderImpl : public ContainerImplBase, public ContainerReader, 
+    public boost::enable_shared_from_this<ContainerReaderImpl>
+{
+public:
+    ContainerReaderImpl(const KaxSegmentPtr& segment, const OpaqueContainerDataPtr& dataFile);
+    size_t getTrackCount();
+    //
+    // I know it is kind of weird that matroska defines a track number as single byte value
+    // but the matroska lib defines track collection to be of possible size size_t. <shrug>
+    //
+    TrackReaderPtr getTrack(unsigned char trackNumber);
+    TrackReaderSeq getTracks();
+    void close();
+
+private:
+    OpaqueContainerDataPtr mDataFile;
+    TrackDataPoolPtr mTrackPool;
+
+    TrackDataPtr getTrackData(unsigned char trackNumber);
+};
+
+//
+// IMPL
+//
+////////////////////////////////////////////////////////////////////////////////////////////
+// TrackingDataBuf implementation.
+//
+TrackingDataBuf::TrackingDataBuf(const BlockAccessTrackers& trackers) :
+    mTrackers(trackers),
+    mCurrentTracker(mTrackers.begin()),
+    mCurrentFrame(0)
+{
+}
+
+class DataToFrameAdapter : public Frame
+{
+public:
+    DataToFrameAdapter(const DataBuffer& buf)
+    {
+        copy(buf.Buffer(), buf.Buffer()+buf.Size(), mData.begin());
+    }
+};
+
+size_t TrackingDataBuf::fillFrames(FrameSeq& frames, size_t frameCount)
+{
+    size_t framesFilled = 0;
+    //
+    // We descend through the structures and iterate through the collections
+    // until we successfully retrieve up to count bytes. We keep track of where 
+    // we were when this accomplished so the next read will start where we left 
+    // off.
+    //
+    // NOTE: this should be *heavily* unit tested.
+    // 
+    for(; mCurrentTracker != mTrackers.end() && frameCount > 0 ; ++mCurrentTracker)
+    {
+        KaxInternalBlock& current = (KaxInternalBlock&)(*(*mCurrentTracker)->blockGroup());
+        
+        for (; mCurrentFrame < current.NumberFrames() && frameCount > 0; ++mCurrentFrame, ++framesFilled)
+        {
+            DataBuffer& buf = current.GetBuffer(static_cast<unsigned int>(mCurrentFrame));
+            frames.push_back(FramePtr(new DataToFrameAdapter(buf)));
+            --frameCount;
+        }
+    }
+    return framesFilled;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////
+// TrackData implementation.
+//
+TrackData::TrackData(const KaxSegmentPtr& segment, const OpaqueContainerDataPtr& dataFile) :
+    mSegment(segment),
+    mDataFile(dataFile)
+{
+}
+
+void TrackData::addBlock(KaxBlockGroup* block)
+{
+    if (mBlocks.size() == 0)
+    {
+        mBlocks.push_back(BlockAccessTrackerPtr(new BlockAccessTracker(block)));
+        return;
+    }
+
+    if (block->GlobalTimecode() < mBlocks.front()->blockGroup()->GlobalTimecode())
+    {
+        mBlocks.insert(mBlocks.begin(), BlockAccessTrackerPtr(new BlockAccessTracker(block)));
+    }
+    else if (block->GlobalTimecode() > mBlocks.back()->blockGroup()->GlobalTimecode())
+    {
+        mBlocks.push_back(BlockAccessTrackerPtr(new BlockAccessTracker(block)));
+    }
+    else
+    {
+        for (BlockAccessTrackers::iterator i = mBlocks.end() -1; i != mBlocks.begin(); --i)
+        {
+            if (block->GlobalTimecode() > (*i)->blockGroup()->GlobalTimecode())
+            {
+                //
+                // This is actually safe because the "tack on" the last block case is handled.
+                //
+                mBlocks.insert(i + 1, new BlockAccessTracker(block));
+                break;
+            }
+        }
+    }
+}
+
+uint64 TrackData::getStartTimeCode()
+{
+    if (mBlocks.size() == 0)
+    {
+        return 0;
+    }
+
+    return mBlocks.front()->blockGroup()->GlobalTimecode();
+}
+
+uint64 TrackData::getLastTimeCode()
+{
+    if (mBlocks.size() == 0)
+    {
+        return 0;
+    }
+    return mBlocks.back()->blockGroup()->GlobalTimecode();
+}
+
+uint64 TrackData::getDuration()
+{
+    if (mBlocks.size() == 0)
+    {
+        return 0;
+    }
+    uint64 lastBlockDuration = 0;
+    if (!mBlocks.back()->blockGroup()->GetBlockDuration(lastBlockDuration))
+    {
+        //
+        // Probably want to log somethin' here. We don't have a proper duration on this last block.
+        //
+    }
+    return mBlocks.back()->blockGroup()->GlobalTimecode() - mBlocks.front()->blockGroup()->GlobalTimecode() 
+        + lastBlockDuration;
+}
+
+TrackingDataBufPtr TrackData::getFromUntil(uint64 startTime, uint64 endTime)
+{
+    SharedLock lock(mLock);
+    BlockAccessTrackers subset;
+    for (BlockAccessTrackers::const_iterator i = mBlocks.begin(); i != mBlocks.end(); ++i)
+    {
+        uint64 timecode = (*i)->blockGroup()->GlobalTimecode();
+        if (timecode >= startTime && timecode < endTime)
+        {
+            if (!(*i)->blockGroup()->ValueIsSet())
+            {
+                IOCallback* io = mDataFile->getIO();
+                if (io)
+                {
+                    (*i)->blockGroup()->ReadData(*(mDataFile->getIO()), SCOPE_ALL_DATA);
+                }
+            }
+            subset.push_back(*i);
+        }
+    }
+    return TrackingDataBufPtr(new TrackingDataBuf(subset)); 
+}
+
+TrackingDataBufPtr TrackData::getBlocks(size_t pos, size_t count)
+{
+    SharedLock lock(mLock);
+    if (pos > mBlocks.size())
+    {
+        return TrackingDataBufPtr();
+    }
+    BlockAccessTrackers subset;
+    subset.reserve(count);
+
+    for (size_t i = pos; (i < mBlocks.size()) && (i < (pos + count)); ++i)
+    {
+        if (!mBlocks[i]->blockGroup()->ValueIsSet())
+        {
+            IOCallback* io = mDataFile->getIO();
+            if (io)
+            {
+                mBlocks[i]->blockGroup()->ReadData(*mDataFile->getIO(), SCOPE_ALL_DATA);
+            }
+        }
+
+        subset.push_back(mBlocks[i]);
+    }
+    return TrackingDataBufPtr(new TrackingDataBuf(subset));
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////
+// TrackDataPool implementation.
+//
+TrackDataPool::TrackDataPool(const KaxSegmentPtr& segment, const OpaqueContainerDataPtr& dataFile) :
+    mSegment(segment),
+    mDataFile(dataFile)
+{
+    KaxTracks& tracks = GetChild<KaxTracks>(*mSegment);
+
+    for (size_t i = 0; i < tracks.ListSize(); ++i)
+    {
+        KaxTrackEntry* t = static_cast<KaxTrackEntry*>(tracks[static_cast<unsigned int>(i)]);
+        unsigned char trackNumber = (uint8)(t->TrackNumber());
+        mDataMap[trackNumber] = TrackDataPtr(new TrackData(segment, mDataFile));
+    }
+    KaxClusterSeq allClusters;
+    for (EBML_MASTER_ITERATOR clusterIter = mSegment->begin(); clusterIter != mSegment->end(); ++clusterIter)
+    {
+        if (EbmlId(**clusterIter) == EBML_ID(KaxCluster))
+        {
+            KaxCluster* cluster = static_cast<KaxCluster*>(*clusterIter);
+            allClusters.push_back(cluster);
+
+            for (EBML_MASTER_ITERATOR blockGroupIter = cluster->begin(); blockGroupIter != cluster->end();
+                 ++blockGroupIter)
+            {
+                if (EbmlId(**blockGroupIter) == EBML_ID(KaxBlockGroup))
+                {
+                    KaxBlockGroup* blockGroup = static_cast<KaxBlockGroup*>(*blockGroupIter);
+                    TrackDataMap::iterator mapEntry = mDataMap.find(trackNo(blockGroup->TrackNumber()));
+                    if (mapEntry == mDataMap.end())
+                    {
+                        //
+                        // This actually indicates that something is probably wrong with the source
+                        // matroska file.
+                        //
+                        mDataMap[trackNo(blockGroup->TrackNumber())] = TrackDataPtr(new TrackData(segment, mDataFile));
+                    }
+                    mDataMap[trackNo(blockGroup->TrackNumber())]->addBlock(blockGroup);
+                }
+            }
+        }
+    }
+}
+
+TrackDataPtr TrackDataPool::getTrackData(unsigned char trackNumber)
+{
+    SharedLock lock(mLock);
+    TrackDataMap::iterator mapEntry = mDataMap.find(trackNumber);
+    if (mapEntry == mDataMap.end())
+    {
+        throw InvalidTrackNumber();
+    }
+    return mapEntry->second;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////
+// Cluster implementation.
+//
+Cluster::Cluster(KaxCluster* cluster, const ContainerWriterImplPtr& container, 
         uint64 initialTimeCode, uint64 lastTimeCode) :
       mCluster(cluster),
-      mSegment(segment),
+      mContainer(container),
       mTimeCode(initialTimeCode),
       mSize(0)
 {
-    mCluster->SetParent(segment->kax());
-    mCluster->SetGlobalTimecodeScale(segment->globalTimeScale());
-    mCluster->SetPreviousTimecode(lastTimeCode * segment->globalTimeScale(), segment->globalTimeScale());
-    mCluster->InitTimecode(initialTimeCode, segment->globalTimeScale());
+    mCluster->SetParent(mContainer->kax());
+    mCluster->SetGlobalTimecodeScale(mContainer->globalTimeScale());
+    mCluster->SetPreviousTimecode(lastTimeCode * mContainer->globalTimeScale(), mContainer->globalTimeScale());
+    mCluster->InitTimecode(initialTimeCode, mContainer->globalTimeScale());
     mCluster->EnableChecksum();
     KaxClusterTimecode& tc = GetChild<KaxClusterTimecode>(*mCluster);
     EbmlUInteger* uintField = dynamic_cast<EbmlUInteger*>(&tc);
@@ -223,9 +691,12 @@ uint64 Cluster::size() const
     return mSize;
 }
 
-TrackImpl::TrackImpl(const SegmentImplPtr& segment, KaxTrackEntry* t, unsigned char number) :
-    Track(number),
-    mSegment(segment),
+////////////////////////////////////////////////////////////////////////////////////////////
+// Writer implementation.
+//
+Writer::Writer(const ContainerWriterImplPtr& container, KaxTrackEntry* t, unsigned char number) :
+    TrackWriter(number),
+    mContainer(container),
     mTrack(t),
     mGroup(0),
     mLastTimeCode(0),
@@ -235,8 +706,11 @@ TrackImpl::TrackImpl(const SegmentImplPtr& segment, KaxTrackEntry* t, unsigned c
 {
 }
 
-void TrackImpl::setCodecAndType(const CodecPtr& codec, const TrackTypePtr& trackType)
+void Writer::setCodecAndType(const CodecPtr& codec, const TrackTypePtr& trackType)
 {
+    //
+    // Generic codec info to matroska stuff translation
+    //
     mCodec = codec;
     mTrackType = trackType;
     EbmlUInteger* uintField = dynamic_cast<EbmlUInteger*>(&GetChild<KaxTrackType>(*mTrack));
@@ -245,10 +719,10 @@ void TrackImpl::setCodecAndType(const CodecPtr& codec, const TrackTypePtr& track
     EbmlString* stringField = dynamic_cast<EbmlString*>(&GetChild<KaxCodecID>(*mTrack));
     assert(stringField);
     *stringField = codec->id();
-    if (codec->bytes().size() != 0)
+    if (codec->privateBytes().size() != 0)
     {
         KaxCodecPrivate& privateData = GetChild<KaxCodecPrivate>(*mTrack);
-        privateData.CopyBuffer((unsigned char*)&(codec->bytes().begin()), static_cast<uint32>(codec->bytes().size()));
+        privateData.CopyBuffer((unsigned char*)&(codec->privateBytes().begin()), static_cast<uint32>(codec->privateBytes().size()));
     }
 
     if (mTrackType->getType() == track_audio)
@@ -336,14 +810,17 @@ void TrackImpl::setCodecAndType(const CodecPtr& codec, const TrackTypePtr& track
     }
 }
 
-void TrackImpl::addTag(const string& name, const string& value)
+void Writer::addTag(const string& name, const string& value)
 {
     mTags.push_back(Tag(name, value));
 }
 
-void TrackImpl::write(uint64 timeCode, uint32 duration, const unsigned char* data, size_t len)
+void Writer::write(uint64 timeCode, uint32 duration, const unsigned char* data, size_t len)
 {
-    ClusterPtr cluster = mSegment->getCluster(timeCode);
+    ClusterPtr cluster = mContainer->getCluster(timeCode);
+    //
+    // Lazy initializtion of the current block group.
+    //
     if (!mGroup)
     {
         mGroup = &cluster->kax()->GetNewBlock();
@@ -352,24 +829,55 @@ void TrackImpl::write(uint64 timeCode, uint32 duration, const unsigned char* dat
     }
     else if (mGroup->GetParentCluster() != cluster->kax())
     {
+        // 
+        // The cluster may have changed on us if we exceeded the limits. If we did, we need to 
+        // get a new block agroup and set it up.
+        //
         mGroup = &cluster->kax()->GetNewBlock();
         mGroup->SetParent(*cluster->kax());
         mGroup->SetParentTrack(*mTrack);
     }
+    //
+    // Keep track. While we do need the info for matroska, it is pretty handy for debugging
+    // anyway.
+    //
     mByteCount += len;
     mTotalTime += duration;
 
+    //
+    // Add the data to the actual block.
+    //
     KaxBlock& block = GetChild<KaxBlock>(*mGroup);
     block.SetParent(*cluster->kax());
+
+    //
+    // We need to allocate heap and copy the data to it, the block seems to take ownership.
+    //
     binary* copiedData = new binary[len];
     memcpy(copiedData, data, len);
     DataBuffer* buf = new DataBuffer(copiedData, static_cast<uint32>(len));
-    bool blockWantsMore = block.AddFrame(*mTrack, mLastTimeCode * mSegment->globalTimeScale(), *buf);
+
+    //
+    // The AddFrame call may return "false" indicating it does not want to have any more frames added
+    // to it. In which case we create a new blockgroup on the next go'round.
+    //
+    bool blockWantsMore = block.AddFrame(*mTrack, mLastTimeCode * mContainer->globalTimeScale(), *buf);
     cluster->increaseSize(buf->Size());
     mDuration += duration;
+    
+    //
+    // While a nuisance, we want to set the duration each time we change it
+    //
+    mGroup->SetBlockDuration(mDuration * mContainer->globalTimeScale());
+
+    //
+    // If we are creating a new group, we probably want to finish this one up with some tidying.
+    // Notice how the method name "SetBlockDuration" is on an instance of block group and how the 
+    // call to create the new blockgroup is GetNewBlock()? I noticed.... don't be confused, sometimes
+    // it is best just to go with it.
+    //
     if (!blockWantsMore)
     {
-        mGroup->SetBlockDuration(mDuration * mSegment->globalTimeScale());
         mGroup = 0;
         mLastTimeCode = mDuration + mLastTimeCode; 
         mDuration = 0;
@@ -383,69 +891,187 @@ void TrackImpl::write(uint64 timeCode, uint32 duration, const unsigned char* dat
     if ((uint8)(GetChild<KaxTrackType>(*mTrack)) == track_video)
     {
         //
-        // TODO: update cues.
+        // TODO: update cues. In addition to the other interesting bits, some of the code
+        // examples I looked at use a non-existent method. I checked.. it has been deprecated.
+        // I guess deprecated means "removed for good, too bad". Who knew?
         //
     }
 }
 
-TagSeq TrackImpl::getTags()
+uint64 Writer::getSize()
+{
+    return mByteCount;
+}
+
+TagSeq Writer::getTags()
 {
     return mTags;
 }
 
-void TrackImpl::finished()
+void Writer::finished()
 {
     if (!mTrackType || !mCodec)
     {
         throw IncompleteException();
     }
-    mSegment->addTime(mTotalTime);
-    mSegment->commit(shared_from_this());
-    cerr << "Track " << trackNumber() << " wrote " << mByteCount << " bytes" << endl;
+    mContainer->addTime(mTotalTime);
+    mContainer->commit(shared_from_this());
 }
 
-uint64 TrackImpl::getID()
+uint64 Writer::getID()
 {
     return static_cast<uint64>(GetChild<KaxTrackUID>(*mTrack));
 }
 
-SegmentImpl::SegmentImpl() :
-    mSegmentStart(0),
-    mTimeCodeScale(1000000),
-    mMaxTimePerCluster(1000),
-    mPrevTime(0xFFFFFFFFFFFFFFFF),
-    mPrevTrack(0xFFFF),
-    mLastTime(0),
-    mInitialSegmentSize(0),
-    mClusterCount(0)
+////////////////////////////////////////////////////////////////////////////////////////////
+// Reader implementation
+//
+Reader::Reader(const ContainerReaderImplPtr& containerReader, KaxTrackEntry* t, unsigned char number,
+    const TrackDataPtr& data) :
+     TrackReader(number),
+     mContainer(containerReader),
+     mTrackData(data),
+     mTrack(t),
+     mTrackNumber(number),
+     mBlockIndex(0)
+{
+    mCurrentData = mTrackData->getBlocks(mBlockIndex++, 1);
+}
+
+uint64 Reader::getSize()
+{
+    return 0; // XXX it is not clear whether this is meanignful.
+}
+
+TagSeq Reader::getTags()
+{
+    return TagSeq(); // XXX
+}
+
+uint64 Reader::getID()
+{
+    return static_cast<uint64>(GetChild<KaxTrackUID>(*mTrack));
+}
+
+void Reader::reset()
+{
+    mBlockIndex = 0;
+    mCurrentData = mTrackData->getBlocks(mBlockIndex, 1);
+}
+
+void Reader::getFrames(FrameSeq& frames, size_t requestedNumberOfFrames)
+{
+    if (!mCurrentData)
+    {
+        //
+        // We are out of data.
+        //
+        return;
+    }
+
+    for (size_t framesRead = mCurrentData->fillFrames(frames, requestedNumberOfFrames);
+         framesRead < requestedNumberOfFrames; )
+    {
+        //
+        // The current data set is exhausted.. let's go get some more blocks.
+        //
+        mCurrentData = mTrackData->getBlocks(mBlockIndex++, 1);
+        if (!mCurrentData)
+        {
+            //
+            // That's it folks.. no more data.
+            //
+            return;
+        }
+        framesRead += mCurrentData->fillFrames(frames, requestedNumberOfFrames - framesRead);
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////
+// ContainerImplBase implementation.
+//
+ContainerImplBase::ContainerImplBase(const KaxSegmentPtr& segment) :
+    mSegment(segment)
 {
+    if (mSegment->ValueIsSet())
+    {
+        KaxInfo& info = GetChild<KaxInfo>(*mSegment);
+        KaxTimecodeScale& scale = GetChild<KaxTimecodeScale>(info);
+        mTimeCodeScale = (uint64)scale;
+    }
+    else
+    {
+        mTimeCodeScale = 1000000;          // Means our times are all in milliseconds... TODO: define a constant.
+    }
+}
+
+KaxSegment& ContainerImplBase::kax()
+{
+    //
+    // Ok.. it grieves me to return a reference here, but I don't want to refactor anymore right now.
+    //
+    return *mSegment;
+}
+
+uint64 ContainerImplBase::globalTimeScale() const
+{
+    return mTimeCodeScale;
+}
+
+void ContainerImplBase::initialize()
+{
+    mMaxTimePerCluster = 1000;           // How many milliseconds per cluster.
+    mPrevTime = 0xFFFFFFFFFFFFFFFF;     // Dummy value to make sure we start off right.
+    mPrevTrack = 0xFFFF;                 // This isn't really used much as we haven't start setting up references.
+    mLastTime = 0;                       // The rest is general initialization.
+
     //
     // initialize helper members into the segment.
     //
-    mSeekHead = &GetChild<KaxSeekHead>(mSegment);
-    mIndexSpace = &GetChild<EbmlVoid>(mSegment);
-    mTracks = &GetChild<KaxTracks>(mSegment);
-    mLeftOverSeek = &GetChild<KaxSeekHead>(mSegment);
+    mSeekHead = &GetChild<KaxSeekHead>(*mSegment);
+    mIndexSpace = &GetChild<EbmlVoid>(*mSegment);
+    mTracks = &GetChild<KaxTracks>(*mSegment);
+    mLeftOverSeek = &GetChild<KaxSeekHead>(*mSegment);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////
+// ContainerWriterImpl implementation.
+//
+ContainerWriterImpl::ContainerWriterImpl() :
+    ContainerImplBase(KaxSegmentPtr(new KaxSegment))
+{
+    initialize();
 }
 
-void SegmentImpl::addTo(const OpaqueContainerDataPtr& data)
+void ContainerWriterImpl::addTo(const OpaqueContainerDataPtr& data)
 {
-    mSegmentStart = data->getIO()->getFilePointer();
     mContainer = data;
-    mInitialSegmentSize = mSegment.WriteHead(*(data->getIO()), 5);
+
+    //
+    // Write the "head" of the segment.. I guess. The number 5 means set everything for
+    // a file that might hold a huge bunch of data.. it is a size classification.. .maybe?
+    //
+    mSegment->WriteHead(*(data->getIO()), 5);
+
     //
-    // Make space for seek head.
+    // Make space for seek head. This could be rightsized... the number I picked was arbitrary.
+    // A lot of code examples used 1000 bytes, so maybe this is too big?
     //
     mIndexSpace->SetSize(2 * 1024);
     mIndexSpace->Render(*(data->getIO()));
 
-    KaxInfo& info = GetChild<KaxInfo>(mSegment);
+    //
+    // The info part of the segment talks about what who is doing what... presumably
+    // so an app that reads this file and chokes and dies because we did something
+    // wrong knows who to blame.
+    //
+    KaxInfo& info = GetChild<KaxInfo>(*mSegment);
     KaxWritingApp& writingApp = GetChild<KaxWritingApp>(info);
     EbmlUnicodeString* unicodeField = dynamic_cast<EbmlUnicodeString*>(&writingApp);
     assert(unicodeField);
-    *unicodeField = L"AsteriskSCF";
+    *unicodeField = L"Asterisk SCF";
     stringstream os;
-    os << "AsteriskSCF Matroska Container Library with libmatroska " << KaxCodeVersion << " and libebml " << EbmlCodeVersion;
+    os << "Asterisk SCF Matroska Container Library with libmatroska " << KaxCodeVersion << " and libebml " << EbmlCodeVersion;
     UTFstring muxerName;
     muxerName.SetUTF8(os.str());
     KaxMuxingApp& muxer = GetChild<KaxMuxingApp>(info);
@@ -472,11 +1098,21 @@ void SegmentImpl::addTo(const OpaqueContainerDataPtr& data)
     uuid.SetBuffer(buf, static_cast<uint32>(idValue.static_size()));
     info.Render(*(data->getIO()));
 
-    mSeekHead->IndexThis(info, mSegment);
-    mCues = &GetChild<KaxCues>(mSegment);
+    mSeekHead->IndexThis(info, *mSegment);
+    mCues = &GetChild<KaxCues>(*mSegment);
     mCues->SetGlobalTimecodeScale(mTimeCodeScale);
+    //
+    // We are writing the tracks here initially.. but we haven't added tracks yet. I don't 
+    // know either.. except VLC hates me if I don't do this.
+    //
     mTracks->Render(*(data->getIO()));
-    //mSeekHead->IndexThis(*mTracks, mSegment);
+  
+    //
+    // The problem is of course.. we don't know how many tracks are really going to be in
+    // here yet, so I write some more blank space. 1k might be too small here. If the track
+    // count is big enough, when we come back and write the real track info, we might
+    // clobber the first cluster.... things that make you go "hmmmm".
+    //
     EbmlVoid moreSpace;
     moreSpace.SetSize(1024);
     moreSpace.Render(*(data->getIO()));
@@ -485,15 +1121,25 @@ void SegmentImpl::addTo(const OpaqueContainerDataPtr& data)
     //
 }
 
-TrackPtr SegmentImpl::getTrack(unsigned char track) 
+size_t ContainerWriterImpl::getTrackCount()
+{
+    if (mTracks)
+    {
+        return mTracks->ListSize();
+    }
+    return 0;
+}
+
+TrackWriterPtr ContainerWriterImpl::getTrack(unsigned char track) 
 {
     if (track == 0)
     {
-        return TrackPtr();
+        throw InvalidTrackNumber("Track list index starts at 1.");
     }
     KaxTrackEntry* t = 0;
     if (mTracks->ListSize() < track)
     {
+
         //
         // We need to create a new track!
         //
@@ -503,8 +1149,13 @@ TrackPtr SegmentImpl::getTrack(unsigned char track)
         }
         else
         {
-            t = &GetNextChild<KaxTrackEntry>(*mTracks, static_cast<KaxTrackEntry&>(*(*mTracks)[static_cast<unsigned int>(mTracks->ListSize()-1)]));
+            t = &GetNextChild<KaxTrackEntry>(*mTracks, 
+                static_cast<KaxTrackEntry&>(*(*mTracks)[static_cast<unsigned int>(mTracks->ListSize()-1)]));
         }
+
+        //
+        // Initialize me! Mostly just generic stuff.
+        //
         assert(t);
         KaxTrackNumber& trackNumber = GetChild<KaxTrackNumber>(*t);
         EbmlUInteger* uintField = dynamic_cast<EbmlUInteger*>(&trackNumber);
@@ -528,28 +1179,37 @@ TrackPtr SegmentImpl::getTrack(unsigned char track)
         t = dynamic_cast<KaxTrackEntry*>((*mTracks)[track-1]);
         assert(t);
     }
-    return TrackPtr(new TrackImpl(shared_from_this(), t, track));
+    return TrackWriterPtr(new Writer(shared_from_this(), t, track));
 }
 
-void SegmentImpl::close()
+void ContainerWriterImpl::close()
 {
+    //
+    // Done are we? Make sure we write the last cluster out.
+    //
     if (mActiveCluster)
     {
         mActiveCluster->render(mContainer->getIO(), mCues);
         mActiveCluster.reset();
     }
-    cerr << "Cluster count : " << mClusterCount << endl;
 
+    //
+    // Now.. we are writing at the end of the file. Cues can go here.. that's okay.
+    //
     if (mCues && mCues->ElementSize() != 0)
     {
         mCues->Render(*mContainer->getIO());
         if (mSeekHead)
         {
-            mSeekHead->IndexThis(*mCues, mSegment);
+            mSeekHead->IndexThis(*mCues, *mSegment);
         }
     }
-        
-    KaxInfo& info = GetChild<KaxInfo>(mSegment);
+
+    //
+    // Info was back near the front remember? Remember to seek back before rendering.. oh and remember
+    // where we are before we move or we will be very unhappy.
+    //
+    KaxInfo& info = GetChild<KaxInfo>(*mSegment);
     KaxDuration& duration = GetChild<KaxDuration>(info);
     EbmlFloat* floatField = dynamic_cast<EbmlFloat*>(&duration);
     assert(floatField);
@@ -559,23 +1219,22 @@ void SegmentImpl::close()
     mContainer->getIO()->setFilePointer(info.GetElementPosition());
     info.Render(*mContainer->getIO());
 
-    //if (mLeftOverSeek && mLeftOverSeek->ListSize() != 0)
-    //{
-    //    mLeftOverSeek->Render(*mContainer->getIO());
-    //    if (mLeftOverSeek)
-    //    {
-    //        mSeekHead->IndexThis(*mCues, mSegment);
-    //    }
-    //}
+    //
+    // As I recall the tracks were right after info, so we can write them now too..
+    //
     size_t trackSize = mTracks->Render(*mContainer->getIO());
-    mSeekHead->IndexThis(*mTracks, mSegment);
+    mSeekHead->IndexThis(*mTracks, *mSegment);
 
+    // 
+    // Didn't forget to add the index entry? Good for you. Now where were we...?
+    //
     mContainer->getIO()->setFilePointer(bookmark);
 
     //
-    // TODO: Add tag support.
+    // Now is a good time to write the tags.. they are related to tracks, but they go in there
+    // own space.
     //
-    KaxTags& tags = GetChild<KaxTags>(mSegment);
+    KaxTags& tags = GetChild<KaxTags>(*mSegment);
     KaxTag* lastTag = 0;
     for (TrackTags::iterator i = mTrackTags.begin(); i != mTrackTags.end(); ++i)
     {
@@ -620,32 +1279,42 @@ void SegmentImpl::close()
             *unicodeField = utf;
         }
     }
-    size_t tagsSize = tags.Render(*mContainer->getIO());
 
-    size_t metaSeekSize = 0;
+    //
+    // But don't forget to write!
+    //
+    tags.Render(*mContainer->getIO());
+
     if (mSeekHead)
     {
-        mSeekHead->IndexThis(tags, mSegment);
+        mSeekHead->IndexThis(tags, *mSegment);
     }
 
     if (mIndexSpace)
     {
-        metaSeekSize = mIndexSpace->ReplaceWith(*mSeekHead, *mContainer->getIO());
+        //
+        // Now we have real index info, so lets go back and fix that. Umm.. did that re-render? I
+        // studies say yes. Why can't everything work that way?
+        //
+        mIndexSpace->ReplaceWith(*mSeekHead, *mContainer->getIO());
     }
+
+    //
+    // Let's find out where whether we need to something with our size and rewrite that "head" bit.
+    //
     mContainer->getIO()->setFilePointer(0, seek_end);
-    uint64 newSize = mContainer->getIO()->getFilePointer() - mSegment.GetElementPosition() - mSegment.HeadSize();
-    mContainer->getIO()->setFilePointer(mSegmentStart);
-    if (mSegment.ForceSize(newSize))
+    uint64 newSize = mContainer->getIO()->getFilePointer() - mSegment->GetElementPosition() - mSegment->HeadSize();
+    mContainer->getIO()->setFilePointer(mSegment->GetElementPosition());
+    if (mSegment->ForceSize(newSize))
     {
-        mSegment.OverwriteHead(*mContainer->getIO());
+        mSegment->OverwriteHead(*mContainer->getIO());
     }
 }
 
 //
 // Internal implementation details.
 //
-
-ClusterPtr SegmentImpl::getCluster(uint64 currentTimeCode)
+ClusterPtr ContainerWriterImpl::getCluster(uint64 currentTimeCode)
 {
     uint64 oldTimeCode = 0;
     if (mActiveCluster)
@@ -662,30 +1331,138 @@ ClusterPtr SegmentImpl::getCluster(uint64 currentTimeCode)
     return mActiveCluster;
 }
 
-KaxSegment& SegmentImpl::kax()
+uint64 ContainerWriterImpl::addTime(uint64 moreTime)
 {
-    return mSegment;
+    mLastTime += moreTime;
+    return mLastTime;
 }
 
-uint64 SegmentImpl::globalTimeScale() const
+void ContainerWriterImpl::commit(const WriterPtr& track)
 {
-    return mTimeCodeScale;
+    mTrackTags[track->getID()] = track->getTags();
 }
 
-uint64 SegmentImpl::addTime(uint64 moreTime)
+////////////////////////////////////////////////////////////////////////////////////////////
+// ContainerReaderImpl implementation.
+//
+ContainerReaderImpl::ContainerReaderImpl(const KaxSegmentPtr& segment, 
+    const OpaqueContainerDataPtr& dataFile) :
+    ContainerImplBase(segment),
+    mDataFile(dataFile),
+    mTrackPool(new TrackDataPool(segment, dataFile))
 {
-    mLastTime += moreTime;
-    return mLastTime;
+    initialize();
 }
 
-void SegmentImpl::commit(const TrackImplPtr& track)
+//
+// TODO: move method to base class.
+// Yeah, I know there is some code duplication. I had originally separated these because I suspected
+// that they would be different. I think they could probably be safely merged.
+//
+size_t ContainerReaderImpl::getTrackCount()
 {
-    mTrackTags[track->getID()] = track->getTags();
+    return mTracks->ListSize();
+}
+
+TrackReaderPtr ContainerReaderImpl::getTrack(unsigned char trackNumber)
+{
+    if (trackNumber == 0)
+    {
+        throw InvalidTrackNumber("track numbers start at 1.");
+    }
+
+    if (trackNumber > mTracks->ListSize())
+    {
+        throw InvalidTrackNumber();
+    }
+    KaxTrackEntry* t = dynamic_cast<KaxTrackEntry*>((*mTracks)[trackNumber-1]);
+    if (!t->ValueIsSet())
+    {
+        t->ReadData(*mContainer->getIO(), SCOPE_ALL_DATA);
+    }
+    return TrackReaderPtr(new Reader(shared_from_this(), t, trackNumber, 
+        mTrackPool->getTrackData(trackNumber)));
+}
+
+TrackReaderSeq ContainerReaderImpl::getTracks()
+{
+    //
+    // A little note on getTracks(): I could cache the track entries once they've been created, 
+    // but I expect a container file to be open by multiple threads. The Reader should be 
+    // unique to the thread, but I think the read clusters ought to pool and be shared.
+    //
+    TrackReaderSeq result;
+    for (uint32 i = 0; i < mTracks->ListSize(); ++i)
+    {
+        uint32 trackNumber = i + 1;
+        KaxTrackEntry* t = dynamic_cast<KaxTrackEntry*>((*mTracks)[i]);
+        if (!t->ValueIsSet())
+        {
+            t->ReadData(*mContainer->getIO(), SCOPE_ALL_DATA);
+        }
+        result.push_back(TrackReaderPtr(new Reader(shared_from_this(), t, trackNumber, 
+            mTrackPool->getTrackData(trackNumber))));
+    }
+    return result;
 }
 
-SegmentPtr Segment::create()
+void ContainerReaderImpl::close()
 {
-    return SegmentPtr(new SegmentImpl);
+    //
+    // Not sold that I want this method... I was kind of thinking that this would really
+    // be reference count based.
+    //
+}
+
+} /* End of namespace internal. */
+
+using namespace ::AsteriskSCF::MatroskaContainer::Implementation::Internal;
+
+ContainerWriterPtr ContainerWriter::create()
+{
+    return ContainerWriterPtr(new ContainerWriterImpl);
+}
+
+ContainerReaderPtr ContainerReader::read(const OpaqueContainerDataPtr& data)
+{
+    //
+    // Here is kind of the dilemna.. do we seek to where we expect things to be or not?
+    // Tell you what, let someone else decide! There is a second method that finds the right
+    // place in the file!
+    //
+    KaxSegmentPtr seg(new KaxSegment);
+    seg->ReadData(*data->getIO(), SCOPE_ALL_DATA);
+    return ContainerReaderPtr(new ContainerReaderImpl(seg, data));
+}
+
+ContainerReaderPtr ContainerReader::locateAndRead(const OpaqueContainerDataPtr& data)
+{
+    //
+    // Find the right place.. well that is all well and good, but I'm not sure that we
+    // can be sure how big EbmlHeader is going to be. So we will brute force it and 
+    // read the EbmlHead anyways. The chances of someone else having read that info first is
+    // slim anyways.
+    //
+    
+    //
+    // Make sure we go to the start of the file... in at least one version of the
+    // matroska library, all the read does is seek to EbmlHeader().GetSize() from current.
+    // If we are not at the start of the file.. kaboom. Full of sighs.
+    //
+    data->getIO()->setFilePointer(0);
+    EbmlHead header;
+
+    //
+    // The "true" is for "read fully"... which is garbage as it looks like some versions
+    // of libmatroska do not actually read anything at all for this call, but oh well.
+    //
+    header.ReadData(*data->getIO(), SCOPE_ALL_DATA);   
+    
+    //
+    // Ok, so now we are presumably at where the segment starts. We'll delegate to the
+    // non-seeky version for the actual creation.
+    //
+    return ContainerReader::read(data);
 }
 
 } /* End of namespace Implementation */
diff --git a/src/MatroskaUtils.h b/src/MatroskaUtils.h
index ce1aaf3..e4f5d7e 100755
--- a/src/MatroskaUtils.h
+++ b/src/MatroskaUtils.h
@@ -21,6 +21,8 @@
 #include <vector>
 #include <string>
 
+#include "MatroskaCodec.h"
+
 namespace AsteriskSCF
 {
 namespace MatroskaContainer
@@ -28,42 +30,18 @@ namespace MatroskaContainer
 namespace Implementation
 {
 
+/**
+ * File header that must be added to every matroska file being created before the "Segment" is added to it.
+ */
 class Header
 {
 public:
     void writeTo(const OpaqueContainerDataPtr& data);
 };
 
-class Codec
-{
-public:
-    virtual ~Codec() {}
-
-    virtual std::vector<unsigned char> bytes() const = 0;
-    virtual std::string id() const = 0;
-};
-typedef boost::shared_ptr<Codec> CodecPtr;
-
-class AudioCodec : public Codec
-{
-public:
-    virtual unsigned int channels() = 0;
-    virtual double sampleRate() = 0;
-    virtual unsigned int bitDepth() = 0;
-    virtual double outputSampleRate() = 0;
-};
-typedef boost::shared_ptr<AudioCodec> AudioCodecPtr;
-
-class VideoCodec : public Codec
-{
-public:
-    virtual unsigned int width() = 0;
-    virtual unsigned int height() = 0;
-    virtual unsigned int displayWidth() = 0;
-    virtual unsigned int displayHeight() = 0;
-};
-typedef boost::shared_ptr<VideoCodec> VideoCodecPtr;
-
+/**
+ * Simple track types.
+ */
 class TrackType
 {
 public:
@@ -84,9 +62,10 @@ public:
     int getType() const;
 };
 
-class Segment;
-typedef boost::shared_ptr<Segment> SegmentPtr;
-
+/**
+ * Matroska supports track tagging which is pretty awesome because we
+ * can add app-support data.
+ */
 struct Tag
 {
     std::string name;
@@ -95,61 +74,198 @@ struct Tag
     Tag(const std::string& n, const std::string& v) :
     name(n), value(v) {}
 };
-
 typedef std::vector<Tag> TagSeq;
 
-class Track
+class TrackBase 
 {
 public:
+    virtual ~TrackBase() {}
 
-    class IncompleteException
+    unsigned char trackNumber()
     {
-    };
+        return mTrackNumber;
+    }
 
-    virtual ~Track() {}
+    virtual uint64 getSize() = 0;
+    virtual TagSeq getTags() = 0;
+    virtual uint64 getID() = 0;
 
-    unsigned char trackNumber()
+protected:
+    unsigned char mTrackNumber;
+
+    TrackBase(unsigned char trackNumber) :
+         mTrackNumber(trackNumber)
     {
-        return mTrackNumber;
     }
+};
+
+/**
+ * Encapsulation of tracks for writing. Virtual because I'm trying to keep matroska-specific stuff
+ * out of the headers.
+ */
+class TrackWriter : public TrackBase
+{
+public:
+
+    class IncompleteException : public std::logic_error
+    {
+    public:
+        IncompleteException() :
+          std::logic_error("IncompleteException")
+        {
+        }
+    };
+
+    virtual ~TrackWriter() {}
 
+    //
+    // Move to a track writer class.
+    //
     virtual void setCodecAndType(const CodecPtr& codec, const TrackTypePtr& trackType) = 0;
     virtual void addTag(const std::string& name, const std::string& value) = 0;
     virtual void write(uint64 timeCode, uint32 duration, const unsigned char* data, size_t len) = 0;
-    virtual TagSeq getTags() = 0;
     virtual void finished() = 0;
-    virtual uint64 getID() = 0;
 
 protected:
-    unsigned char mTrackNumber;
+    TrackWriter(unsigned char trackNumber) :
+         TrackBase(trackNumber) {}
+};
+typedef boost::shared_ptr<TrackWriter> TrackWriterPtr;
 
-    Track(unsigned char track) :
-      mTrackNumber(track)
+class Frame
+{
+public:
+
+    std::vector<unsigned char>& data() const;
+    uint64 start();
+    uint64 duration();
+
+protected:
+    uint64 mStart;
+    uint64 mEnd;
+    std::vector<unsigned char> mData;
+};
+typedef boost::shared_ptr<Frame> FramePtr;
+typedef std::vector<FramePtr> FrameSeq;
+
+/**
+ * Encapsulation of tracks for reading.
+ */
+class TrackReader : public TrackBase
+{
+public:
+    /**
+     * Start at the beginning.
+     */
+    virtual void reset() = 0;
+
+    virtual void getFrames(FrameSeq& frames, size_t requestedNumberOfFrames) = 0;
+
+    /**
+     * Note the absence of a close method. This is because a track reader only provides
+     * a view on data that is read or in the process of being read. The resources
+     * allocated to this process belong to the library itself. A close() method
+     * is really not necessary. When the library realizes that the container and
+     * it's associated readers are no longer in use, it will free up any cached
+     * resources and close "stuff".
+     */
+
+protected:
+    TrackReader(unsigned char trackNumber) :
+         TrackBase(trackNumber)
     {
     }
 };
-typedef boost::shared_ptr<Track> TrackPtr;
+typedef boost::shared_ptr<TrackReader> TrackReaderPtr;
+typedef std::vector<TrackReaderPtr> TrackReaderSeq;
 
-class Segment
+class InvalidTrackNumber : public std::out_of_range
+{
+public:
+    InvalidTrackNumber(const std::string& hint) :
+        std::out_of_range(std::string("Track number does not exist: ") + hint)
+    {
+    }
+
+    InvalidTrackNumber() :
+        std::out_of_range("Track number does not exists")
+    {
+    }
+};
+
+class ContainerWriter;
+typedef boost::shared_ptr<ContainerWriter> ContainerWriterPtr;
+
+/**
+ * Regarding the matroska specific stuff... I forgot myself and used Matroska specific terminology
+ * if not types... I should rename this to be something more generic.
+ */
+class ContainerWriter
 {
     //
-    // Hidden.
+    // Hidden and unimplemented.
     //
-    Segment(const Segment&);
-    void operator=(const Segment&);
+    ContainerWriter(const ContainerWriter&);
+    void operator=(const ContainerWriter&);
+
 public:
 
-    virtual ~Segment() {}
+    virtual ~ContainerWriter() {}
     virtual void addTo(const OpaqueContainerDataPtr& data) = 0;
-    virtual TrackPtr getTrack(unsigned char trackNumber) = 0;
+
+    virtual size_t getTrackCount() = 0;
+    virtual TrackWriterPtr getTrack(unsigned char trackNumber) = 0;
+
+
+    virtual void close() = 0;
+
+    /**
+     * Create a Segment instance for writing a new container file.
+     */
+    static ContainerWriterPtr create();
+
+protected:
+    ContainerWriter() {}
+};
+
+class ContainerReader;
+typedef boost::shared_ptr<ContainerReader> ContainerReaderPtr;
+
+class ContainerReader
+{
+    //
+    // Hidden and unimplemented.
+    //
+    ContainerReader(const ContainerReader&);
+    void operator=(const ContainerReader&);
+
+public:
+    virtual ~ContainerReader() {}
+
+    virtual size_t getTrackCount() = 0;
+    virtual TrackReaderPtr getTrack(unsigned char trackNumber) =0;
+    virtual TrackReaderSeq getTracks() = 0;
+
     virtual void close() = 0;
 
-    static SegmentPtr create();
+    /**
+     * Attempt to read a segment from the container data at the current location.
+     */
+    static ContainerReaderPtr read(const OpaqueContainerDataPtr& data);
+
+    /**
+     * Seek to the expected location for the Matroska segment in the specified file
+     * and create a segement from the data there. We don't set the file pointer back
+     * to where it was.. so if it is necessary to go back there, you will have
+     * to track it yourself. Sorry.
+     */
+    static ContainerReaderPtr locateAndRead(const OpaqueContainerDataPtr& data);
 
 protected:
-    Segment() {}
+    ContainerReader() {}
+
 };
 
 } /* End of namespace Implementation */
 } /* End of namespace MatroskaContainer */
-} /* End of namepsace AsteriskSCF */
\ No newline at end of file
+} /* End of namepsace AsteriskSCF */
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index d452776..2f161c1 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -14,9 +14,6 @@ astscf_component_add_files(UnitTestContainerRepository ../src/ContainerImpl.h)
 astscf_component_add_files(UnitTestContainerRepository ../src/ContainerImpl.cpp)
 astscf_component_add_files(UnitTestContainerRepository ../src/ContainerInfoImpl.h)
 astscf_component_add_files(UnitTestContainerRepository ../src/ContainerInfoImpl.cpp)
-astscf_component_add_files(UnitTestContainerRepository ../src/MatroskaDefines.h)
-astscf_component_add_files(UnitTestContainerRepository ../src/MatroskaUtils.h)
-astscf_component_add_files(UnitTestContainerRepository ../src/MatroskaUtils.cpp)
 
 astscf_component_add_boost_libraries(UnitTestContainerRepository unit_test_framework core filesystem)
 astscf_component_add_slice_collection_libraries(UnitTestContainerRepository ASTSCF)
diff --git a/test/UnitTest_ContainerRepository.cpp b/test/UnitTest_ContainerRepository.cpp
index ed5a31e..356a900 100644
--- a/test/UnitTest_ContainerRepository.cpp
+++ b/test/UnitTest_ContainerRepository.cpp
@@ -14,9 +14,10 @@
  * at the top of the source tree.
  */
 
-//
-// Yes... we do intend to include the source file.
-//
+
+// boost does something here that VC++ complains loudly and annoyingly about
+#pragma warning(disable: 4996)  
+
 #define BOOST_TEST_NO_MAIN
 
 #include <boost/test/unit_test.hpp>
@@ -26,21 +27,47 @@
 #include <ebml/StdIOCallback.h>
 #include <matroska/FileKax.h>
 
+//
+// Yes... we do intend to include the source files.
+//
 #include "../src/MatroskaDefines.h"
-#include "../src/MatroskaUtils.h"
+#include "../src/MatroskaUtils.cpp"
 #include "../src/OpaqueContainerData.h"
 #include "../src/ContainerRepository.cpp"
 
+#pragma warning(push)
+#pragma warning(disable: 4244 4267)
+
 #include <pjmedia.h>
 #include <pjlib.h>
 
+#pragma warning(pop)
+
 using namespace AsteriskSCF::MatroskaContainer::Implementation;
+using namespace AsteriskSCF::MatroskaContainer::Implementation::Internal;
 using namespace AsteriskSCF::System::Logging;
 using namespace boost::unit_test;
 using namespace std;
 using namespace libmatroska;
 using namespace libebml;
 
+class NullOpaqueContainer : public OpaqueContainerData
+{
+public:
+    NullOpaqueContainer()
+    {
+    }
+
+    MatroskaFilePtr getFile() const
+    {
+        return MatroskaFilePtr();
+    }
+
+    IOCallback* getIO() const
+    {
+        return 0;
+    }
+};
 
 class TestOpaqueContainer : public OpaqueContainerData
 {
@@ -181,7 +208,7 @@ public:
             return MatroskaAudioPCM;
         }
 
-        vector<unsigned char> bytes() const { return vector<unsigned char>(); }
+        vector<unsigned char> privateBytes() const { return vector<unsigned char>(); }
 
         unsigned int channels() { return 1; }
         double sampleRate() { return mSampleRate; }
@@ -191,22 +218,21 @@ public:
         double mSampleRate;
     };
 
-    
-    
     //
     // MatroskaUtils directed tests.
     //
     void testSimpleMux()
     {
-        string containerFileName = mCommunicator->getProperties()->getPropertyWithDefault("Test.SimpleMux.MkvFile", "simplemux.mkv");
-        OpaqueContainerDataPtr container = new TestOpaqueContainer(containerFileName, true);
+        string containerFileName = mCommunicator->getProperties()->getPropertyWithDefault("Test.SimpleMux.MkvFile", 
+            "simplemux.mkv");
+        OpaqueContainerDataPtr container(new TestOpaqueContainer(containerFileName, true));
         BOOST_REQUIRE(container != 0);
         Header newHeader;
         newHeader.writeTo(container);
-        SegmentPtr segment(Segment::create());
-        BOOST_REQUIRE(segment);
-        segment->addTo(container);
-        TrackPtr track = segment->getTrack(1);
+        ContainerWriterPtr containerWriter(ContainerWriter::create());
+        BOOST_REQUIRE(containerWriter);
+        containerWriter->addTo(container);
+        TrackWriterPtr track = containerWriter->getTrack(1);
         BOOST_REQUIRE(track);
         TrackTypePtr trackType(new AudioTrackType);
         CodecPtr codec (new Mono16BitWav(44100));
@@ -240,7 +266,7 @@ public:
             cerr << "Wrote " << totalBytes << " bytes";
 
             track->finished();
-            segment->close();
+            containerWriter->close();
             BOOST_MESSAGE("Yay, we successfully wrote the data.. maybe, you will have to check it");
         }
         catch (const exception& ex)
@@ -253,11 +279,109 @@ public:
             BOOST_MESSAGE("UNEXPECTED EXCEPTION");
             pjmedia_port_destroy(port);
         }
-
     }
 
-    void testMoreComplexMux()
+    void testTrackDataOrdering()
     {
+        try
+        {
+            //
+            // We don't need to use real data to test this part.
+            //
+            KaxSegmentPtr segment(new KaxSegment);
+            KaxCluster peanutButterCluster;
+            KaxTrackEntry raceTrack;
+            raceTrack.SetGlobalTimecodeScale(1);
+
+            typedef boost::shared_ptr<KaxBlockGroup> KaxBlockPtr;
+            typedef vector<KaxBlockPtr> TestBlockSeq;
+
+            OpaqueContainerDataPtr container(new NullOpaqueContainer);
+
+            TestBlockSeq testBlocks;
+            TrackData trackData(segment, container);
+
+            KaxBlockPtr block32(new KaxBlockGroup);
+            block32->SetParent(peanutButterCluster);
+            block32->SetParentTrack(raceTrack);
+            block32->AddFrame(raceTrack, 320, *(new DataBuffer(new binary[2], 2)));
+            testBlocks.push_back(block32);
+            block32->SetBlockDuration(10);
+            trackData.addBlock(block32.get());
+
+            KaxBlockPtr block12(new KaxBlockGroup);
+            block12->SetParent(peanutButterCluster);
+            block12->SetParentTrack(raceTrack);
+            block12->AddFrame(raceTrack, 120, *(new DataBuffer(new binary[2], 2)));
+            block12->SetBlockDuration(10);
+            testBlocks.push_back(block12);
+            trackData.addBlock(block12.get());
+
+            KaxBlockPtr block33(new KaxBlockGroup);
+            block33->SetParent(peanutButterCluster);
+            block33->SetParentTrack(raceTrack);
+            block33->AddFrame(raceTrack, 330, *(new DataBuffer(new binary[2], 2)));
+            block33->SetBlockDuration(10);
+            testBlocks.push_back(block33);
+            trackData.addBlock(block33.get());
+
+            KaxBlockPtr block34(new KaxBlockGroup);
+            block34->SetParent(peanutButterCluster);
+            block34->SetParentTrack(raceTrack);
+            block34->AddFrame(raceTrack, 340, *(new DataBuffer(new binary[2], 2)));
+            block34->SetBlockDuration(10);
+            testBlocks.push_back(block34);
+            trackData.addBlock(block34.get());
+
+            KaxBlockPtr block5(new KaxBlockGroup);
+            block5->SetParent(peanutButterCluster);
+            block5->SetParentTrack(raceTrack);
+            block5->AddFrame(raceTrack, 50, *(new DataBuffer(new binary[2], 2)));
+            block5->SetBlockDuration(10);
+            testBlocks.push_back(block5);
+            trackData.addBlock(block5.get());
+
+            KaxBlockPtr block22(new KaxBlockGroup);
+            block22->SetParent(peanutButterCluster);
+            block22->AddFrame(raceTrack, 220, *(new DataBuffer(new binary[2], 2)));
+            block22->SetBlockDuration(10);
+            testBlocks.push_back(block22);
+            trackData.addBlock(block22.get());
+
+            uint64 startTime = trackData.getStartTimeCode();
+            BOOST_CHECK(startTime == 50);
+            uint64 lastTime = trackData.getLastTimeCode();
+            BOOST_CHECK(lastTime == 340);
+            uint64 duration = trackData.getDuration();
+            BOOST_CHECK(duration == (340 - 50 + 10));
+            TrackingDataBufPtr returnedBuffer = trackData.getBlocks(0, testBlocks.size());
+
+            BlockAccessTrackers trackers = returnedBuffer->getTrackers();
... 42 lines suppressed ...


-- 
asterisk-scf/integration/file_media_service.git



More information about the asterisk-scf-commits mailing list