[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