[asterisk-scf-commits] asterisk-scf/integration/file_media_service.git branch "initial_development" updated.
Commits to the Asterisk SCF project code repositories
asterisk-scf-commits at lists.digium.com
Thu Nov 10 11:19:33 CST 2011
branch "initial_development" has been updated
via df5ac0294a5062be49a4269d0dbc65e20d8089c5 (commit)
via c549bff7dd9259488cd818d95a047d38227547c6 (commit)
from 883f26046f8ebe1d4337581de99a07e32d86d3e9 (commit)
Summary of changes:
src/CMakeLists.txt | 7 +-
src/ContainerImpl.cpp | 707 ++++++++++++++++++------
src/ContainerImpl.h | 6 +
src/ContainerRepository.cpp | 4 +
src/MatroskaUtil.cpp | 1269 +++++++++++++++++++++++++++++++++++++++++++
src/MatroskaUtil.h | 310 +++++++++++
src/TODO.txt | 5 +
7 files changed, 2137 insertions(+), 171 deletions(-)
mode change 100755 => 100644 src/MatroskaDefines.h
create mode 100644 src/MatroskaUtil.cpp
create mode 100644 src/MatroskaUtil.h
mode change 100755 => 100644 src/ReplicationContext.h
create mode 100644 src/TODO.txt
- Log -----------------------------------------------------------------
commit df5ac0294a5062be49a4269d0dbc65e20d8089c5
Author: Brent Eagles <beagles at digium.com>
Date: Thu Nov 10 13:48:22 2011 -0330
More mux code changes.
diff --git a/src/ContainerImpl.cpp b/src/ContainerImpl.cpp
index 4505fe2..6f14acf 100644
--- a/src/ContainerImpl.cpp
+++ b/src/ContainerImpl.cpp
@@ -30,6 +30,12 @@
#include "MatroskaUtil.h"
+extern "C"
+{
+#include <config.h>
+#include <corec/helpers/file/streams.h>
+}
+
using namespace AsteriskSCF::FileMediaService::MediaContainer::V1;
using namespace AsteriskSCF::Media::File::V1;
using namespace AsteriskSCF::Media::V1;
@@ -266,47 +272,17 @@ public:
class SinkManager : public AsteriskSCF::Media::V1::StreamSource
{
public:
- SinkManager(const string& id, const Logger& logger,
- AVCodecContext* codecContext, AVCodec* codec,
- const Ice::ObjectAdapterPtr& adapter) :
+ SinkManager(const string& id,
+ const FormatPtr& format,
+ double trackTimecodeScale,
+ const Ice::ObjectAdapterPtr& adapter,
+ const Logger& logger) :
mId(id),
mLogger(logger),
- mCodecContext(codecContext),
- mCodec(codec),
+ mTimeScale(trackTimecodeScale),
mAdapter(adapter),
mSeqno(0)
{
- mFormat = new AudioFormat;
- switch (mCodecContext->sample_fmt)
- {
- case SAMPLE_FMT_U8:
- mFormat->sampleSize = 8;
- break;
- case SAMPLE_FMT_S16:
- mFormat->sampleSize = 16;
- break;
- case SAMPLE_FMT_S32:
- mFormat->sampleSize = 32;
- break;
- default:
- mFormat->sampleSize = 8;
- //
- // hrmm.. there are floating point sample formats.
- //
- }
-
- //
- // TODO: Double check this calc.
- //
- mFormat->sampleRate = mCodecContext->sample_rate;
- mFormat->frameSize = mCodecContext->sample_rate * 20 / 1000;
- }
-
- ~SinkManager()
- {
- //
- // TODO cleanup codec context and codec.
- //
}
void addSink(const StreamSinkPrx& sink, const Ice::Current& current)
@@ -355,43 +331,51 @@ public:
return mId;
}
- void requestFormat(const FormatPtr&, const Ice::Current&)
+ void requestFormat(const FormatPtr& f, const Ice::Current&)
{
//
// You get what you got!
//
// TODO: well, it should allow it if the format actually matches!
- //
+ //
+ if (!mFormat && !f && mFormat->ice_staticId() == f->ice_staticId())
+ {
+ return;
+ }
throw MediaFormatSwitchException();
}
- //
- // TODO: Oh gross. Ok, so here is the deal'eo. For fixed/known length
- // codecs, avcodec returns n frames in a packet, otherwise it returns a
- // single frame. Would have been a lot simpler if they always returned
- // a single frame. With that in mind.. this is pretty much broken for
- // other formats.
- //
- void distributePacket(AVPacket& packet)
+ void distributePacket(TrackFrame* data)
{
//
// We have to decode the whole packet and store it in our own frame(s), because
// the next read or close of the avf context will invalidate the contents
// of the packet.
//
- size_t bytesToDecode = packet.size;
- unsigned char* buf = static_cast<unsigned char*>(packet.data);
+ size_t bytesToDecode = data->frameData.Size;
+ unsigned char* buf = static_cast<unsigned char*>(data->frameData.Data);
+ timecode_t startTimecode = data->frameData.Timecode / mTimeScale;
+ size_t sampleCount = 0;
+ size_t durationFactor = (mFormat->sampleSize / sizeof(buf[0])) * mFormat->sampleRate; // bits per second
while(bytesToDecode != 0)
{
FrameSeq frames;
AudioFramePtr frame = new AudioFrame;
frame->seqno = ++mSeqno;
- frame->timestamp = packet.dts;
+
+ //
+ // 8000 represents the conversion from bits to bytes and seconds to milliseconds. On the first
+ // iteration, the startTimecode will simply be 0 the first frame's timestamp will be equal too that of
+ // that of the TrackFrame instance. As more frames get added, we increment the timecode according to the
+ // number of bytes allocated to frames and the sample size and sample rates of the format.
+ //
+ frame->timestamp = startTimecode + (sampleCount * durationFactor / 8000);
frame->mediaFormat = mFormat;
- unsigned long samplesLeft =
- static_cast<size_t>(mFormat->frameSize) > bytesToDecode ? bytesToDecode: mFormat->frameSize;
+ unsigned long sampleRangeEnd =
+ static_cast<size_t>(mFormat->frameSize) > bytesToDecode ? bytesToDecode: mFormat->frameSize;
+ sampleCount += sampleRangeEnd;
//
// TODO: other sample formats.
@@ -399,21 +383,21 @@ public:
if (mFormat->sampleSize == 16)
{
short* buf16 = reinterpret_cast<short*>(buf);
- Ice::ShortSeq payloadData(buf16, buf16 + samplesLeft);
+ Ice::ShortSeq payloadData(buf16, buf16 + sampleRangeEnd);
ShortSeqPayloadPtr payloadObj = new ShortSeqPayload;
payloadObj->payload = payloadData;
frame->payload = payloadObj;
}
else
{
- Ice::ByteSeq payloadData(buf, buf + samplesLeft);
+ Ice::ByteSeq payloadData(buf, buf + sampleRangeEnd);
ByteSeqPayloadPtr payloadObj = new ByteSeqPayload;
payloadObj->payload = payloadData;
frame->payload = payloadObj;
}
- bytesToDecode -= samplesLeft;
- buf += samplesLeft;
+ bytesToDecode -= sampleRangeEnd;
+ buf += sampleRangeEnd;
//
// TODO: byte order!!! See RTP component for example.
@@ -522,21 +506,19 @@ public:
private:
boost::shared_mutex mLock;
-
- typedef map<string, StreamSinkPrx> SinkMap;
-
- SinkMap mSinks;
-
string mId;
+ double mTimeScale;
+ AudioFormatPtr mFormat;
Logger mLogger;
- AVCodecContext* mCodecContext;
- AVCodec* mCodec;
- IceUtil::Time mStartTime;
Ice::ObjectAdapterPtr mAdapter;
+
+ IceUtil::Time mStartTime;
size_t mSeqno;
- AudioFormatPtr mFormat;
FrameSeq mQueuedFrames;
+ typedef map<string, StreamSinkPrx> SinkMap;
+ SinkMap mSinks;
+
StreamSinkSeq getSinksImpl()
{
StreamSinkSeq results;
@@ -553,12 +535,14 @@ public:
UniqueLock lock(mLock);
mSinks.erase(id);
}
- };
+ }; /* end of class SinkManager */
+
typedef IceUtil::Handle<SinkManager> SinkManagerPtr;
typedef vector<SinkManagerPtr> SinkManagerSeq;
PlaybackSessionImpl(const Ice::ObjectAdapterPtr& adapter, const string& id,
const FileMediaSpecification& spec,
+ Matroska::Environment* matroskaEnvironment,
const Logger& logger) :
mObjectAdapter(adapter),
mId(id),
@@ -574,15 +558,9 @@ public:
~PlaybackSessionImpl()
{
- try
- {
- if (mInput)
- {
- av_close_input_file(mInput);
- }
- }
- catch (...)
+ if (mStream)
{
+ StreamClose(mStream);
}
}
@@ -600,69 +578,49 @@ public:
//
assert(!mInput);
- int result = av_open_input_file(&mInput, filename.c_str(), 0, 0, 0);
- if (result < 0)
- {
- //
- // Crap!!! Actually no, this will never get to this point ..
- // We should open thing snad pass it into the session instead.
- //
- mLogger(Error) << "Unable to open the data file";
- mInput = 0;
- return false;
- }
- result = av_find_stream_info(mInput);
- if (result < 0)
+ mStream = StreamOpen(env->parserContext(), filename.c_str(), SFLAG_RDONLY);
+
+ if (mStream)
{
- mLogger(Error) << "Cannot read header information";
- av_close_input_file(mInput);
- mInput = 0;
+ mLogger(Debug) << "Unable to open stream for " << filename << " with matroska library.";
return false;
}
- if (mInput->nb_streams < 1)
+ mContainerReader.reset(new MatroskaReader(mStream));
+
+ if (mContainerReader->trackCount() < 1)
{
- mLogger(Error) << "Umm.. this file appears to have 0 streams in it.";
- av_close_input_file(mInput);
- mInput = 0;
+ mLogger(Error) << "File " << filename < " has 0 tracks!";
+ //
+ // We could let this clean up on it's own, but we might as well toast it now whilst we are
+ // here.
+ //
+ mContainerReader.reset();
+
+ //
+ // This we had definitely better do while we are here.
+ //
+ StreamClose(mStream);
+ mStream = 0;
return false;
}
- mSinkManagers.resize(mInput->nb_streams);
- for (size_t i = 0; i < mInput->nb_streams; ++i) // TODO: create the relevant sources.
+ mSinkManagers.resize(mContainerReader->trackCount());
+ for (size_t i = 0; i < mContainerReader->trackCount(); ++i) // TODO: create the relevant sources.
{
string sourceId = mId;
sourceId += ".";
sourceId += boost::lexical_cast<string>(i);
- AVCodecContext* codecContext = mInput->streams[i]->codec;
- if (!codecContext)
- {
- mLogger(Debug) << "No codec context for stream " << i << ", skipping.";
- continue;
- }
- AVCodec* codec = avcodec_find_decoder(codecContext->codec_id);
- if (!codec)
- {
- mLogger(Debug) << "Unable to find codec for " << i << " (" << codecContext->codec_id
- << "), skipping.";
- continue;
- }
-
- result = avcodec_open(codecContext, codec);
- if (result < 0)
- {
- mLogger(Debug) << "Unable to open codec for " << i << " (" << codecContext->codec_id
- << "), continuing hopefully.";
- }
-
+ //
+ // TODO: replace codec 'figuring code' from avformat/avcodec.
+ //
- mSinkManagers[i] = new SinkManager(sourceId, mLogger, codecContext, codec, mObjectAdapter);
+ mSinkManagers[i] = new SinkManager(sourceId, mLogger, mObjectAdapter);
Ice::Identity id = mObjectAdapter->getCommunicator()->stringToIdentity(sourceId);
mSources.push_back(StreamSourcePrx::uncheckedCast(mObjectAdapter->add(mSinkManagers[i], id)));
}
- av_init_packet(&mPacket);
return true;
}
@@ -793,7 +751,8 @@ public:
//
if (readPacket)
{
- if(av_read_frame(mInput, &mPacket) < 0)
+ TrackFrame* newFrame = mContainerReader->getNextFrame();
+ if (!newFrame)
{
//
// This should indicate that the file is done, return false should break
@@ -801,7 +760,7 @@ public:
//
return false;
}
- int trackIndex = mPacket.stream_index;
+ int trackIndex = newFrame->track;
if (trackIndex >= 0 && trackIndex < static_cast<long>(mSinkManagers.size()))
{
@@ -811,7 +770,7 @@ public:
//
// Distribute the data to whomever it was destined for.
//
- p->distributePacket(mPacket);
+ p->distributePacket(newFrame);
}
}
else
@@ -898,12 +857,18 @@ private:
Ice::ObjectAdapterPtr mObjectAdapter;
const string mId;
FileMediaSpecification mSpec;
+ Matroska::Environment* mMatroskaEnvironment;
+ boost::shared_ptr<ContainerReader> mContainerReader;
+
+ //
+ // TODO: a small resource guard wrapper class would be useful for the corec stream member.
+ //
+ stream* mStream;
+
Logger mLogger;
CookieManager mCookieManager;
- AVFormatContext* mInput;
SinkManagerSeq mSinkManagers;
StreamSourceSeq mSources;
- AVPacket mPacket;
IODriverThreadPtr mDriver;
bool mStarted;
};
@@ -912,6 +877,39 @@ typedef IceUtil::Handle<PlaybackSessionImpl> PlaybackSessionImplPtr;
//
// End of playback stuff.. start of recording stuff.
//
+struct WritingRecord
+{
+ unsigned trackNumber;
+ FramePtr frame;
+};
+
+
+static bool frameTimestampComparator(const WritingRecord& a, const WritingRecordr& b)
+{
+ StreamFramePtr streamA = StreamFramePtr::dynamicCast(a.frame);
+ StreamFramePtr streamB = StreamFramePtr::dynamicCast(b.frame);
+ if (streamA && streamB)
+ {
+ return streamA->timestamp < streamB->timestamp;
+ }
+ if (streamA)
+ {
+ return true;
+ }
+ //
+ // If we are here, then a was not a stream frame. If b was a stream frame, then it
+ // takes precedent (we put stream frames first) so we need to swap.
+ //
+ if (!streamB)
+ {
+ return false;
+ }
+ //
+ // If we are here neither a or b are stream frames so the frames might as well stay in the
+ // order they are.
+ //
+ return true;
+};
//
// In progress.. lots of questions about multi-stream sinking.
@@ -919,49 +917,282 @@ typedef IceUtil::Handle<PlaybackSessionImpl> PlaybackSessionImplPtr;
class FrameWriter : public IceUtil::Shared
{
public:
- FrameWriter(unsigned trackCount) :
- mEarliest(IceUtil::Time::seconds(0)),
- mLatest(IceUtil::Time::seconds(0))
+ FrameWriter(const boost::shared_ptr<ContainerWriter>& writer, unsigned trackCount) :
+ mWriter(writer)
{
+ //
+ // TODO: exception for the assert so the problem can be handled in release builds as well.
+ //
+ assert(trackCount > 0);
+ assert(mWriter);
+
+ mTrackQueues.resize(trackCount);
}
- void write(const FrameSeq&, unsigned)
+ void write(unsigned trackIndex, const FrameSeq& frames)
{
- UniqueLock lock(mLock);
-
- AVPacket out;
//
- // TODO: convert frames to apacket.
+ // Called from queueFrames when it is time
//
- av_interleaved_write_frame(mOutput, &out);
-
+ mWriter->addFrames(trackIndex, frames);
}
void queueFrames(unsigned trackIndex, const FrameSeq& frames)
{
- mFrameQueues[trackIndex].insert(mFrameQueues[trackIndex].end(), frames.begin(), frames.end());
+ boost::mutex::scoped_lock lock(mLock);
+ if (mTrackQueues.size() == 1)
+ {
+ assert(trackIndex == 1);
+ writeFrames(1, frames);
+ return;
+ }
+
+ //
+ // Ok, so we have multiple tracks! We probably should avoid negative time codes so we want to
+ // kind of group things together if we can.
//
- // Now here is the trick, we need one complete block of time for all incoming streams
- // before we can mux them in.
+ size_t index = trackIndex -1;
+ assert (index < mTrackQueues.size());
+
+ bool alreadyHasQueuedFrames = ! mTrackQueues[index].frames.empty();
+ mTrackQueues[index].insert(mTrackQueues[index].frames.end(), frames.begin(), frames.end());
+
//
- if (mEarliest.toSeconds() == 0 && !frames.empty())
+ // We are going to assume frames are in proper chronological/serial order. This might actually be cool for
+ // setting up test data, so let's not sweat it :)
+ //
+ IceUtil::Time earliestStamp(mTrackQueues[index].earliest);
+ for (FrameSeq::iterator iter = frames.begin(); iter != frames.end(); ++iter)
+ {
+ StreamFramePtr streamFrame = StreamFramePtr::dynamicCast(*iter);
+ if (streamFrame)
+ {
+ earliestStamp = IceUtil::Time::milliseconds(streamFrame->timestamp);
+ break;
+ }
+ }
+
+ IceUtil::Time latestStamp(mTrackQueues[index].latest);
+ for (FrameSeq::reverse_iterator iter = frames.rbegin(); iter < frames.rend(); ++iter)
+ {
+ StreamFramePtr streamFrame = StreamFramePtr::dynamicCast(*iter);
+ if (streamFrame)
+ {
+ latestStamp = IceUtil::Time::milliseconds(streamFrame->timestamp);
+ break;
+ }
+ }
+
+ bool earliestChanged = false;
+ if (mTrackQueues[index].earliest.toSeconds() == 0 || earliestStamp < mTrackQueues[index].earliest.toSeconds())
+ {
+ mTrackQueues[index].earliest = earliestStamp;
+ earliestChanged = true;
+ }
+
+ bool latestChanged = false;
+ if (mTrackQueues[index].toSeconds() == 0 || latestStamp > mTrackQueues[index].latest.toSeconds())
{
- StreamFramePtr streamFrame = StreamFramePtr::dynamicCast(frames.front());
- mEarliest = IceUtil::Time::milliseconds(streamFrame->timestamp);
- streamFrame = StreamFramePtr::dynamicCast(frames.back());
- mLatest = IceUtil::Time::milliseconds(steamFrame->timestamp);
+ mTrackQeueus[index].latest = latestStamp;
+ latestChanged = true;
}
+
+ vector<TrackQueue>::iterator keyQueue = mTrackQueues.end();
+ //
+ // Weird name, I know, but basically we are going to find the earliest "latest" of all the queues.
+ // That sets a defacto end of the window. The write will be triggered when the latest earliest falls
+ // before that. Confused yet? Pictures please:
+ // t -012345678
+ // Q1 e------l
+ // Q2 e--l
+ // Q3 e--l
+ // Here, the earliest is Q1 at 0, the earliest latest is Q2 at 6 and latest earliest is Q3 at 5. We
+ // trigger because each queue has some frame for the windo of 0-6. After writing Q1 with have 1 frame at t 7
+ // Q2 will have no frames and Q3 will have 2 frames at 7 and 8. Muxing takes the frames and
+ // orders them by timestamp (duration is important as well, but we need to figure that the
+ // frames are going to arrive in some sensible size for interactive AV.
+ //
+ IceUtil::Time earliestLastest(latestStamp);
+
+ for (vector<TrackQueue>::iterator iter = mTrackQueues.begin(); iter != mTrackQueues.end(); ++iter)
+ {
+ if (iter->frames.empty() || iter->earliest.toSeconds() == 0)
+ {
+ //
+ // This basically indicates that we have received no frames for this track yet.
+ // While breaking out of here now is kind of *iffy* (we need to examine the external use
+ // cases for the potential and the validity of this kind of thing) it is pretty
+ // hard to mux without frames from each track weighing in.
+ //
+ return;
+ }
+ //
+ // The earliest is most important. We need to make sure we get that guy.
+ //
+ if (iter->earliest < earliestStamp)
+ {
+ earliestStamp = iter->earliest;
+ }
+ if (iter->earliest > latestStamp)
+ {
+ latestStamp = iter->earliest;
+ }
+ if (iter->latest < earliestLatest)
+ {
+ earliestLast = iter->latest;
+ }
+ }
+
+ //
+ // latestStamp is really latestEarliest here! If the latest earliest does not over lap with
+ // the earliest latest then we do not have any overlapping tracks.
+ //
+ if (latestStamp > earliestLatest)
+ {
+ return;
+ }
+
+ //
+ // So we have the window, collect the frames that fall in this window and then sort them.
+ //
+ size_t trackNumber = 1;
+ vector<WritingRecord> framesToWrite;
+ for (vector<TrackQueue>::iterator iter = mTrackQueues.begin(); iter != mTrackQueues.end(); ++iter)
+ {
+ FrameSeq leftOvers;
+ for (FrameSeq::iterator frameIter = iter->frames.begin(); frameIter != iter->frames.end(); ++frameIter)
+ {
+ StreamFramePtr streamFrame = StreamFramePtr::dynamicCast(*frameIter);
+ if (streamFrame)
+ {
+ IceUtil::Time t(IceUtil::Time::milliseconds(streamFrame->timeStamp));
+ if (t < earliestLatest)
+ {
+ framesToWrite.push_back(*frameIter);
+ }
+ else
+ {
+ if (leftOvers.empty())
+ {
+ iter->earliest = t;
+ }
+ else
+ {
+ iter->latest = t;
+ }
+ leftOvers.push_back(*frameIter);
+ }
+ }
+ else
+ {
+ //
+ // I think if we are going to store them.. this is the only way to do it.
+ //
+ WritingRecord newRecord;
+ newRecord.trackNumber = trackNumber;
+ newRecord.frame = *frameIter;
+ framesToWrite.push_back(newRecord);
+ }
+ }
+
+ if (leftOvers.empty())
+ {
+ iter->earliest = IceUtil::Time::seconds(0);
+ iter->latest = iter->earliest;
+ }
+ iter->frames.swap(leftOvers);
+ ++trackNumber;
+ }
+ //
+ // If this happened then we something is either wrong with our frame picking or our trigger logic.
+ //
+ assert (framesToWrite.size > 0);
+ muxAndWrite(framesToWrite);
+ }
+
+
+ void close()
+ {
+ //
+ // We are closing, that means we need to collect, mux and write remaining frames.
+ //
+ boost::mutex::scoped_lock lock(mLock);
+ size_t trackNumber = 1;
+ vector<WritingRecord> framesToWrite;
+ for (vector<TrackQueue>::iterator iter = mTrackQueues.begin(); iter != mTrackQueues.end(); ++iter)
+ {
+ for (FrameSeq::iterator frameIter = iter->frames.begin(); frameIter != iter->frames.end(); ++frameIter)
+ {
+ WritingRecord newRecord;
+ newRecord.trackNumber = trackNumber;
+ newRecord.frame = *frameIter;
+ framesToWrite.push_back(newRecord);
+ }
+ ++trackNumber;
+ }
+ //
+ // TODO: Byt right
+ //
+ muxAndWrite(framesToWrite);
}
private:
- boost::shared_mutex mLock;
- AVFormatContext* mOutput;
- AVOutputFormat* mOutputFormat;
- IceUtil::Time mEarliest;
- IceUtil::Time mLatest;
+ boost::mutex mLock;
+ boost::shared_ptr<ContainerWriter> mContainerWriter;
+
+ struct TrackQueue
+ {
+ IceUtil::Time earliest;
+ IceUtil::Time latest;
+ FrameSeq frames;
+
+ TrackQueue() :
+ earliest(IceUtil::Time::seconds(0)),
+ latest(IceUtil::Time::seconds(0))
+ {
+ }
+ };
- vector<FrameSeq> mFrameQueues;
+ vector<TrackQueue> mTrackQueues;
+
+ /**
+ * Mux the frames and write them to the container writer. the argument is non-const because the writing records are
+ * sorted, requiring that the vector be modified. We could sort outside the function body *or* make a copy but a.)
+ * code duplication and b.) performance, so what the heck let's play a little fast and loose.
+ **/
+ void muxAndWrite(vector<WritingRecord>& framesToWrite)
+ {
+ //
+ // Now.. this part is basically muxing. We are going to interleave the frames of the different tracks
+ // by timestamp. What we are not going to do is break frames up. We could, but hopefully this won't be
+ // necessary.
+ //
+ sort(framesToWrite.begin(); framesToWrite.end(), frameTimestampComparator);
+ unsigned currentTrack = 0;
+ FrameSeq collectedFrames;
+ for (vector<WritingRecord>::iterator w = framesToWrite.begin(); w != framesToWrite.end(); ++w)
+ {
+ //
+ // This is fine because tracks are 1 based indexing and we've initialized currentTrack to 0
+ // to start with.
+ //
+ if (currentTrack != w->trackNumber)
+ {
+ if (!collectedFrames.empty())
+ {
+ write(currentTrack, collectedFrames);
+ collectedFrames.clear();
+ }
+ currentTrack = w->trackNumber;
+ }
+ collectedFrames.push_back(w->frame);
+ }
+ if (currentTrack != 0 && !collectedFrames.empty())
+ {
+ write(currentTrack, collectedFrames);
+ }
+ }
};
typedef IceUtil::Handle<FrameWriter> FrameWriterPtr;
@@ -983,12 +1214,17 @@ public:
void write(const FrameSeq& newFrames, const Ice::Current&)
{
- UniqueLock lock(mLock);
- if (!mWriting)
{
- return;
+ UniqueLock lock(mLock);
+ if (!mWriting)
+ {
+ return;
+ }
}
+ //
+ // The lock really does not need to be held here. The writer has it's own.
+ //
mWriter->queueFrames(mTrack, newFrames);
}
@@ -1025,6 +1261,12 @@ public:
mStartTime = startTime;
}
+ void stopWriting()
+ {
+ UniqueLock lock(mLock);
+ mWriting = false;
+ }
+
private:
string mId;
StreamSourcePrx mSource;
@@ -1034,14 +1276,17 @@ public:
bool mWriting;
FrameSeq mQueuedFrames;
};
+ typedef IceUtil::Handle<SourceManager> SourceManagerPtr;
+ tyepdef vector<SourceManagerPtr> SourceManagers;
RecordingSessionImpl(const Ice::ObjectAdapterPtr& adapter, const string& id,
- const FileMediaSpecification& spec) :
+ const FileMediaSpecification& spec,
+ const Matroska::Environment* matroskaEnvironment) :
mObjectAdapter(adapter),
mId(id),
mSpec(spec),
- mOutput(0),
- mOutputFormat(0)
+ mMatroskaEnvironment(matroskaEnvironment),
+ mStream(0)
{
}
@@ -1082,21 +1327,33 @@ public:
void start(const Ice::Current&)
{
-
+ for (SourceManagers::const_iterator iter= mSourceManagers.begin(); iter != mSourceManagers.end(); ++iter)
+ {
+ iter->startWriting(IceUtil::Time::now());
+ }
}
void pause(const Ice::Current&)
{
+ for (SourceManagers::const_iterator iter= mSourceManagers.begin(); iter != mSourceManagers.end(); ++iter)
+ {
+ iter->stopWriting();
+ }
}
void unpause(const Ice::Current&)
{
+ for (SourceManagers::const_iterator iter= mSourceManagers.begin(); iter != mSourceManagers.end(); ++iter)
+ {
+ iter->startWriting(IceUtil::Time::now());
+ }
}
void restart(const Ice::Current&)
{
//
- // TODO: this is trickier to implement than I thought!
+ // TODO: this is trickier to implement than I thought! I'm not at all sure whether we should
+ // support this or not.
//
}
@@ -1110,14 +1367,96 @@ public:
catch (const Ice::Exception&)
{
}
+ if (mWriter)
+ {
+ try
+ {
+ mWriter->close();
+ }
+ //
+ // XXX log exceptions
+ //
+ catch (const exception&)
+ {
+ }
+ catch (...)
+ {
+ }
+ }
+ }
+
+ void setup(const string& filename)
+ {
+ //
+ // Matroska related initialization!
+ //
+ mStream = StreamOpen(env->parserContext(), filename.c_str(), SFLAG_WRONLY | SFLAG_CREATE);
+ if (!mStream)
+ {
+ //
+ // XXX: error handling.
+ //
+ assert("XXX: Error handling" == 0);
+ }
+ vector<string> trackIds;
+ try
+ {
+ mContainer.reset(new Matroska::ContainerWriter(mStream));
+ string containerId = string("ASCF.Recording.") + mId;
+ mContainer->populateHeader(string("ASCF.Recording.") + mId, filename);
+
+ int id = 0;
+ for (FormatSeq::const_iterator iter = mSpec.formats.begin(); iter != mSpec.formats.end(); ++iter)
+ {
+ string trackId = containerId;
+ trackId += boost::lexical_cast<string>(id++);
+ trackIds.push_back(trackId);
+ mContainer->addTrack(*iter, containerId);
+ }
+ mWriter = new FrameWriter(mContainer, mSpec.formats.size());
+ }
+ //
+ // XXX exceptions should be logged.
+ //
+ catch (const exception&)
+ {
+ if (mStream)
+ {
+ StreamClose(mStream);
+ }
+ mContainer.reset(0);
+ }
+ catch (...)
+ {
+ mContainer.reset(0);
+ }
+
+ //
+ // Now we need to create the sink servants. We don't try and create and activate all of the
+ // servants up above because exceptions are quite possible up there and cleaning up servants, etc
+ // in exception handlers is ickier.
+ //
+ int trackNumber = 1;
+ for (vector<string>::const_iterator iter = trackIds.begin(); iter != trackIds.end(); ++iter)
+ {
+ SourceManagerPtr s = new SourceManager(*iter, mWriter, trackNumber++);
+ mSourceManagers.push_back(s);
+ mSinks = StreamSinkPrx::uncheckedCast(mObjectAdapter->add(s,
+ mObjectAdapter->getCommunicator()->stringToIdentity(*iter)));
+ }
}
+
private:
Ice::ObjectAdapterPtr mObjectAdapter;
const string mId;
FileMediaSpecification mSpec;
CookieManager mCookieManager;
- AVFormatContext* mOutput;
- AVOutputFormat* mOutputFormat;
+ Matroska::Environment* matroskaEnvironment;
+ boost::shared_ptr<Matroska::ContainerWriter> mContainer;
+ SinkSeq mSinks;
+ SourceManagers mSourceManagers;
+ stream* mStream;
+ FrameWriterPtr mWriter;
};
class ContainerServant : public ContainerImpl
@@ -1125,9 +1464,11 @@ class ContainerServant : public ContainerImpl
public:
ContainerServant(const ContainerInfoImplPtr& info,
const FileMediaSpecification& spec,
+ const Matroska::Environment* matroskaEnvironment,
const Ice::ObjectAdapterPtr& adapter, const Logger& logger) :
mInfo(info),
mSpec(spec),
+ mMatroskaEnvironment(matroskaEnvironment),
mAdapter(adapter),
mLogger(logger)
{
@@ -1179,7 +1520,8 @@ public:
id += IceUtil::generateUUID();
Ice::Identity iceId = mAdapter->getCommunicator()->stringToIdentity(id);
- PlaybackSessionImplPtr playback = new PlaybackSessionImpl(mAdapter, id, mSpec, mLogger);
+ PlaybackSessionImplPtr playback = new PlaybackSessionImpl(mAdapter, id, mSpec, mMatroskaEnvironment,
+ mLogger);
AsteriskSCF::Media::File::V1::FileSessionPrx result =
AsteriskSCF::Media::File::V1::FileSessionPrx::uncheckedCast(mAdapter->add(playback, iceId));
//
@@ -1203,6 +1545,7 @@ private:
ContainerInfoImplPtr mInfo;
FileMediaSpecification mSpec;
+ Matroska::Environment* mMatroskaEnvironment;
Ice::ObjectAdapterPtr mAdapter;
Logger mLogger;
@@ -1255,10 +1598,11 @@ typedef IceUtil::Handle<ContainerServant> ContainerServantPtr;
ContainerImplPtr
ContainerImpl::create(const ContainerInfoImplPtr& info,
const FileMediaSpecification& spec,
+ const Matroska::Environment* matroskaEnvironment,
const Ice::ObjectAdapterPtr& adapter,
const AsteriskSCF::System::Logging::Logger& logger)
{
- return new ContainerServant(info, spec, adapter, logger);
+ return new ContainerServant(info, spec, matroskaEnvironment, adapter, logger);
}
diff --git a/src/ContainerImpl.h b/src/ContainerImpl.h
index e962b92..ac44345 100644
--- a/src/ContainerImpl.h
+++ b/src/ContainerImpl.h
@@ -21,6 +21,11 @@
#include "ContainerInfoImpl.h"
+namespace Matroska
+{
+class Environment;
+}
+
namespace AsteriskSCF
{
namespace FileMediaService
@@ -38,6 +43,7 @@ public:
static IceUtil::Handle<ContainerImpl> create(
const ContainerInfoImplPtr& info,
const AsteriskSCF::Media::File::V1::FileMediaSpecification& spec,
+ const Matroska::Environment* matroskaEnvironment,
const Ice::ObjectAdapterPtr& adapter,
const AsteriskSCF::System::Logging::Logger& logger);
};
diff --git a/src/ContainerRepository.cpp b/src/ContainerRepository.cpp
index 7542003..615e588 100644
--- a/src/ContainerRepository.cpp
+++ b/src/ContainerRepository.cpp
@@ -29,6 +29,8 @@
#include <Ice/Ice.h>
#include <IceUtil/UUID.h>
+#include "MatroskaUtil.h"
+
//
// Use latest boost stuff.
//
@@ -128,6 +130,8 @@ private:
set<string> mExtensions;
+ Matroska::Environment mMatroskaEnvironment;
+
//
// A *locked* accessor for obtaining the path string. Handy for methods that want the path,
// but don't have a lock yet. NOT for use in locked scenarios.
commit c549bff7dd9259488cd818d95a047d38227547c6
Author: Brent Eagles <beagles at digium.com>
Date: Tue Nov 1 16:32:32 2011 -0230
Add new libebml2/libmatroska2 library helper classes.
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 12d2a9d..4a0c4c0 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -21,10 +21,15 @@ astscf_component_add_files(FileMediaService
RepositoryReplicationAdapter.h
)
+astscf_component_add_files(FileMediaService
+ MatroskaUtil.h
+ MatroskaUtil.cpp
+ )
+
astscf_component_add_ice_libraries(FileMediaService IceStorm)
astscf_component_add_boost_libraries(FileMediaService core)
astscf_component_add_slice_collection_libraries(FileMediaService FILEMEDIASERVICE)
astscf_component_add_slice_collection_libraries(FileMediaService ASTSCF)
astscf_component_build_icebox(FileMediaService)
-target_link_libraries(FileMediaService logging-client astscf-ice-util-cpp avformat avcodec avutil)
+target_link_libraries(FileMediaService logging-client astscf-ice-util-cpp)
astscf_component_install(FileMediaService)
diff --git a/src/ContainerImpl.cpp b/src/ContainerImpl.cpp
index 3864aa6..4505fe2 100644
--- a/src/ContainerImpl.cpp
+++ b/src/ContainerImpl.cpp
@@ -28,17 +28,14 @@
#include <boost/thread/shared_mutex.hpp>
#include <boost/lexical_cast.hpp>
-extern "C"
-{
-#include <libavcodec/avcodec.h>
-#include <libavformat/avformat.h>
-}
+#include "MatroskaUtil.h"
using namespace AsteriskSCF::FileMediaService::MediaContainer::V1;
using namespace AsteriskSCF::Media::File::V1;
using namespace AsteriskSCF::Media::V1;
using namespace AsteriskSCF::System::Logging;
using namespace std;
+using namespace Matroska;
//
// TODO: While working on the snags of our file handling library, I'm going to assume a single
@@ -52,6 +49,9 @@ namespace FileMediaService
namespace Implementation
{
+typedef boost::unique_lock<boost::shared_mutex> UniqueLock;
+typedef boost::shared_lock<boost::shared_mutex> SharedLock;
+
/**
*
* Base class for file session driver. Basically defines and implements methods that
@@ -147,8 +147,6 @@ public:
private:
boost::shared_mutex mLock;
- typedef boost::shared_lock<boost::shared_mutex> SharedLock;
- typedef boost::unique_lock<boost::shared_mutex> UniqueLock;
FileSessionIODriverPtr mElement;
bool mDone;
};
@@ -225,8 +223,6 @@ public:
private:
boost::shared_mutex mLock;
- typedef boost::shared_lock<boost::shared_mutex> SharedLock;
- typedef boost::unique_lock<boost::shared_mutex> UniqueLock;
SessionCookies mCookies;
};
@@ -526,8 +522,6 @@ public:
private:
boost::shared_mutex mLock;
- typedef boost::shared_lock<boost::shared_mutex> SharedLock;
- typedef boost::unique_lock<boost::shared_mutex> UniqueLock;
typedef map<string, StreamSinkPrx> SinkMap;
@@ -592,6 +586,12 @@ public:
}
}
+ // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ //
+ // Needs to be rewritten according to whatever third party library we are
+ // using.
+ //
+ // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
bool setup(const string& filename)
{
mLogger(Debug) << "Setting up playback session with file: " << filename;
@@ -599,6 +599,7 @@ public:
// This should never be called twice.
//
assert(!mInput);
+
int result = av_open_input_file(&mInput, filename.c_str(), 0, 0, 0);
if (result < 0)
{
@@ -893,8 +894,6 @@ public:
private:
boost::shared_mutex mLock;
- typedef boost::shared_lock<boost::shared_mutex> SharedLock;
- typedef boost::unique_lock<boost::shared_mutex> UniqueLock;
Ice::ObjectAdapterPtr mObjectAdapter;
const string mId;
@@ -920,9 +919,9 @@ typedef IceUtil::Handle<PlaybackSessionImpl> PlaybackSessionImplPtr;
class FrameWriter : public IceUtil::Shared
{
public:
- FrameWriter(AVFormatContext* output, AVOutputFormat* fmt ):
- mOutput(output),
- mOutputFormat(fmt)
+ FrameWriter(unsigned trackCount) :
+ mEarliest(IceUtil::Time::seconds(0)),
+ mLatest(IceUtil::Time::seconds(0))
{
}
@@ -938,12 +937,31 @@ public:
}
+ void queueFrames(unsigned trackIndex, const FrameSeq& frames)
+ {
+ mFrameQueues[trackIndex].insert(mFrameQueues[trackIndex].end(), frames.begin(), frames.end());
+
+ //
+ // Now here is the trick, we need one complete block of time for all incoming streams
+ // before we can mux them in.
+ //
+ if (mEarliest.toSeconds() == 0 && !frames.empty())
+ {
+ StreamFramePtr streamFrame = StreamFramePtr::dynamicCast(frames.front());
+ mEarliest = IceUtil::Time::milliseconds(streamFrame->timestamp);
+ streamFrame = StreamFramePtr::dynamicCast(frames.back());
+ mLatest = IceUtil::Time::milliseconds(steamFrame->timestamp);
+ }
+ }
+
private:
boost::shared_mutex mLock;
- typedef boost::unique_lock<boost::shared_mutex> UniqueLock;
- typedef boost::shared_lock<boost::shared_mutex> SharedLock;
AVFormatContext* mOutput;
AVOutputFormat* mOutputFormat;
+ IceUtil::Time mEarliest;
+ IceUtil::Time mLatest;
+
+ vector<FrameSeq> mFrameQueues;
};
typedef IceUtil::Handle<FrameWriter> FrameWriterPtr;
@@ -955,17 +973,23 @@ public:
class SourceManager : public AsteriskSCF::Media::V1::StreamSink
{
public:
- SourceManager(const string& id, const FrameWriterPtr&, unsigned trackNo) :
+ SourceManager(const string& id, const FrameWriterPtr& writer, unsigned trackNo) :
mId(id),
- mTrack(trackNo)
+ mWriter(writer),
+ mTrack(trackNo),
+ mWriting(false)
{
}
- void write(const FrameSeq&, const Ice::Current&)
+ void write(const FrameSeq& newFrames, const Ice::Current&)
{
- //
- // The frames really need to be queued up and written
- //
+ UniqueLock lock(mLock);
+ if (!mWriting)
+ {
+ return;
+ }
+
+ mWriter->queueFrames(mTrack, newFrames);
}
void setSource(const StreamSourcePrx& source, const Ice::Current&)
@@ -991,19 +1015,24 @@ public:
string getId(const Ice::Current&)
{
- SharedLock lock(mLock);
return mId;
}
+ void startWriting(const IceUtil::Time& startTime)
+ {
+ UniqueLock lock(mLock);
+ mWriting = true;
+ mStartTime = startTime;
+ }
+
private:
string mId;
StreamSourcePrx mSource;
unsigned mTrack;
FrameWriterPtr mWriter;
-
- boost::shared_mutex mLock;
- typedef boost::unique_lock<boost::shared_mutex> UniqueLock;
- typedef boost::shared_lock<boost::shared_mutex> SharedLock;
+ IceUtil::Time mStartTime;
+ bool mWriting;
+ FrameSeq mQueuedFrames;
};
RecordingSessionImpl(const Ice::ObjectAdapterPtr& adapter, const string& id,
@@ -1171,12 +1200,6 @@ public:
private:
boost::shared_mutex mLock;
-
- //
- // I cannot be the only one in the world who is tired of typing this out each time.
- //
- typedef boost::shared_lock<boost::shared_mutex> SharedLock;
- typedef boost::unique_lock<boost::shared_mutex> UniqueLock;
ContainerInfoImplPtr mInfo;
FileMediaSpecification mSpec;
diff --git a/src/MatroskaDefines.h b/src/MatroskaDefines.h
old mode 100755
new mode 100644
diff --git a/src/MatroskaUtil.cpp b/src/MatroskaUtil.cpp
new file mode 100644
index 0000000..71bfd65
--- /dev/null
+++ b/src/MatroskaUtil.cpp
@@ -0,0 +1,1269 @@
+#include "MatroskaUtil.h"
+
+#define FILE_DLL /* */
+
+extern "C"
+{
+#include <config.h> // This may pose a bit of a problem as it seems likely
+ // that it will conflict with other files of the same name.
+#include <corec/helpers/file/streams.h>
+#include <corec/helpers/parser/parser.h>
+#include <corec/node/node.h>
+#include <corec/node/nodebase.h>
+#include <ebml/ebml.h>
+#include <matroska/matroska_sem.h>
+
+//
+// The initialization process includes "registering" these with the current
+// runtime.
+//
+extern const nodemeta LangStr_Class[];
+extern const nodemeta UrlPart_Class[];
+extern const nodemeta BufStream_Class[];
+extern const nodemeta MemStream_Class[];
+extern const nodemeta Streams_Class[];
+extern const nodemeta File_Class[];
+extern const nodemeta FileDb_Class[];
+extern const nodemeta VFS_Class[];
+extern const nodemeta Stdio_Class[];
+extern const nodemeta Matroska_Class[];
+extern const nodemeta EBMLElement_Class[];
+extern const nodemeta EBMLMaster_Class[];
+extern const nodemeta EBMLBinary_Class[];
+extern const nodemeta EBMLString_Class[];
+extern const nodemeta EBMLInteger_Class[];
+extern const nodemeta EBMLCRC_Class[];
+extern const nodemeta EBMLDate_Class[];
+extern const nodemeta EBMLVoid_Class[];
+};
+
+#include <iostream>
+
+using namespace AsteriskSCF::Media::V1;
+using namespace Matroska;
+using namespace std;
+
+string Matroska::errToString(err_t e)
+{
+ switch (e)
+ {
+ case ERR_NONE:
+ {
+ return "";
+ }
+ case ERR_BUFFER_FULL:
+ {
+ return "buffer full";
+ }
+ case ERR_OUT_OF_MEMORY:
+ {
+ return "out of memory";
+ }
+ case ERR_INVALID_DATA:
+ {
+ return "invalid data";
+ }
+
+ case ERR_INVALID_PARAM:
+ {
+ return "invalid param";
+ }
+ case ERR_NOT_SUPPORTED:
+ {
+ return "not supported";
+ }
+ case ERR_NEED_MORE_DATA:
+ {
+ return "need more data";
+ }
+ case ERR_FILE_NOT_FOUND:
+ {
+ return "file not found";
+ }
+ case ERR_END_OF_FILE:
+ {
+ return "end of file";
+ }
+ case ERR_DEVICE_ERROR:
+ {
+ return "device error";
+ }
+ case ERR_SYNCED:
+ {
+ return "synced";
+ }
+ case ERR_DATA_NOT_FOUND:
+ {
+ return "data not found";
+ }
+ case ERR_PROTO_NOT_FOUND:
+ {
+ return "proto not found";
+ }
+ case ERR_NOT_DIRECTORY:
+ {
+ return "not directory";
+ }
+ case ERR_NOT_COMPATIBLE:
+ {
+ return "not compatible";
+ }
+ case ERR_CONNECT_FAILED:
+ {
+ return "connect failed";
+ }
+ case ERR_DROPPING:
+ {
+ return "dropping";
+ }
+ case ERR_STOPPED:
+ {
+ return "stopped";
+ }
+ case ERR_UNAUTHORIZED:
+ {
+ return "unauthorized";
+ }
+ case ERR_LOADING_HEADER:
+ {
+ return "loading header";
+ }
+ case ERR_READ:
+ {
+ return "read";
+ }
+ case ERR_WRITE:
+ {
+ return "write";
+ }
+ case ERR_UNRESOLVED_ADDR:
+ {
+ return "unresolved addr";
+ }
+ case ERR_NO_NETWORK:
+ {
+ return "no network";
+ }
+ case ERR_TIME_OUT:
+ {
+ return "time out";
+ }
+ case ERR_KEY_NOT_UNIQUE:
+ {
+ return "key not unique";
+ }
+ case ERR_NOT_CONST:
+ {
+ return "not const";
+ }
+ case ERR_REDIRECTED:
+ {
+ return "redirected";
+ }
+ case ERR_CANCELED:
+ {
+ return "canceled";
+ }
+ case ERR_STREAM_CACHED:
+ {
+ return "stream cached";
+ }
+ case ERR_SERVER_ERROR:
+ {
+ return "server error";
+ }
+ case ERR_NOT_USABLE:
+ {
+ return "not usable";
+ }
+ }
+ return "(unknown)";
+}
+
+InitError::InitError(err_t e) :
+ runtime_error(errToString(e)),
+ mErr(e)
+{
+}
+
+err_t InitError::errorCode() const
+{
+ return mErr;
+}
+
+RenderError::RenderError(err_t e) :
+ runtime_error(errToString(e)),
+ mErr(e)
+{
+}
+
+err_t RenderError::errorCode() const
+{
+ return mErr;
+}
+
+ReadError::ReadError(err_t e) :
+ runtime_error(errToString(e)),
+ mErr(e)
+{
+}
+
+err_t ReadError::errorCode() const
+{
+ return mErr;
+}
+
+FormatError::FormatError(const string& msg) :
+ logic_error(msg)
+{
+}
+
+WriteError::WriteError(err_t e) :
+ runtime_error(errToString(e)),
+ mErr(e)
+{
+}
+
+err_t WriteError::errorCode() const
+{
+ return mErr;
+}
+
+InvalidTrackNumber::InvalidTrackNumber() :
+ runtime_error("the specified track number is out range")
+{
+}
+
+Environment::Environment() :
+ mContext(new parsercontext)
+{
+ ParserContext_Init(mContext, 0, 0, 0);
+ nodemodule* mod = reinterpret_cast<nodemodule*>(mContext);
+ NodeRegisterClassEx(mod, BufStream_Class);
+ NodeRegisterClassEx(mod, MemStream_Class);
+ NodeRegisterClassEx(mod, Streams_Class);
+ NodeRegisterClassEx(mod, File_Class);
+ NodeRegisterClassEx(mod, Stdio_Class);
+ NodeRegisterClassEx(mod, LangStr_Class);
+ NodeRegisterClassEx(mod, UrlPart_Class);
+ NodeRegisterClassEx(mod, Matroska_Class);
+ NodeRegisterClassEx(mod, EBMLElement_Class);
+ NodeRegisterClassEx(mod, EBMLMaster_Class);
+ NodeRegisterClassEx(mod, EBMLBinary_Class);
+ NodeRegisterClassEx(mod, EBMLString_Class);
+ NodeRegisterClassEx(mod, EBMLInteger_Class);
+ NodeRegisterClassEx(mod, EBMLCRC_Class);
+ NodeRegisterClassEx(mod, EBMLDate_Class);
+ NodeRegisterClassEx(mod, EBMLVoid_Class);
+
+ //
+ // May need to setup some more stuff.
+ //
+ err_t result = MATROSKA_Init(reinterpret_cast<nodecontext*>(mContext));
+ if (result != ERR_NONE)
+ {
+ throw InitError(result);
+ }
+}
+
+Environment::~Environment()
+{
+ MATROSKA_Done(reinterpret_cast<nodecontext*>(mContext));
+ delete mContext;
+}
+
+parsercontext* Environment::parserContext()
+{
+ return mContext;
+}
+
+//
+// A couple of helpers for node cleanup.
+//
+static void deleteNode(ebml_master** element)
+{
+ if(*element)
+ {
+ NodeDelete((node*)*element);
+ *element = 0;
+ }
+}
+
+static void deleteNode(ebml_element** element)
+{
+ if(*element)
+ {
+ NodeDelete((node*)*element);
+ *element = 0;
+ }
+}
+
+static bool checkValue(ebml_element* el, const ebml_context* contextClass, const int64_t limit,
+ string& message, stream* str)
+{
+ if (EBML_ElementIsType(el, contextClass))
+ {
+ if (EBML_ElementReadData(el, str, 0, 0, SCOPE_ALL_DATA, 0) != ERR_NONE)
+ {
+ message = "Unable to read data";
+ }
+ if (EBML_IntegerValue((ebml_integer*)el) > limit)
+ {
+ message = "Exceeds limit";
+ }
+ return true;
+ }
+ return false;
+}
+
+static bool checkValue(ebml_element* el, const ebml_context* contextClass,
+ const vector<string>& validValues, string& message, stream* str)
+{
+ if (EBML_ElementIsType(el, contextClass))
+ {
+ if (EBML_ElementReadData(el, str, 0, 0, SCOPE_ALL_DATA, 0) != ERR_NONE)
+ {
+ message = "Unable to read data";
+ return false;
+ }
+ //
+ // Mmmmm... platform dependent char sizes are going to mess up what I am about
+ // to do. Maybe boost has something to help out here.
+ //
+ tchar_t strValue[MAXLINE];
+ EBML_StringGet((ebml_string*)el, strValue, TSIZEOF(strValue));
+ bool matched = false;
+ for (vector<string>::const_iterator iter = validValues.begin();
+ iter != validValues.end() && !matched; ++iter)
+ {
+ matched = (string(strValue) == *iter);
+ }
+ return matched;
+ }
+ return false;
+}
+
+//
+// mkclean has this read/verify function that goes through the header elements,
+// verifying their values until it comes across the Matroska "segment". It seems
+// a good way to get to the segment whilst keeping out of trouble so I'm writing
+// a similar function here.
+//
+static ebml_element* checkMatroskaHeader(
+ const ebml_element* header,
+ const ebml_parser_context* parentContext,
+ stream* input)
+{
+ //
+ // We establish a local parser context as it constrains
+ // the scope of what we are going to do next to the header
+ // are of the file. If we used the parent context, we could
+ // hypothetically find ourselves in the segment, reading through
+ // level 1 elements.
+ //
+ ebml_parser_context context;
+ context.UpContext = parentContext;
+ context.Context = EBML_ElementContext(header);
+ context.EndPosition = EBML_ElementPositionEnd(header);
+ context.Profile = parentContext->Profile;
+
+ vector<string> supportedDocTypes;
+ supportedDocTypes.push_back("matroska");
+ supportedDocTypes.push_back("webm");
+
+ int upperElement = 0;
+ for (ebml_element* el = EBML_FindNextElement(input, &context, &upperElement, 1);
+ el != 0;
+ el = EBML_FindNextElement(input, &context, &upperElement, 1))
+ {
+ string errorMessage;
+ if (checkValue(el, &EBML_ContextReadVersion, EBML_MAX_VERSION, errorMessage, input))
+ {
+ if (!errorMessage.empty())
+ {
+ cerr << errorMessage << endl;
+ return 0;
+ }
+ }
+ else if (checkValue(el, &EBML_ContextMaxIdLength, EBML_MAX_ID, errorMessage, input))
+ {
+ if (!errorMessage.empty())
+ {
+ cerr << errorMessage << endl;
+ return 0;
+ }
+ }
+ else if (checkValue(el, &EBML_ContextMaxSizeLength, EBML_MAX_SIZE, errorMessage, input))
+ {
+ if (!errorMessage.empty())
+ {
+ cerr << errorMessage << endl;
+ return 0;
+ }
+ }
+ else if (checkValue(el, &EBML_ContextDocType, supportedDocTypes, errorMessage, input))
+ {
+ if (!errorMessage.empty())
+ {
+ cerr << errorMessage << endl;
+ return 0;
+ }
+ }
+ else if (checkValue(el, &EBML_ContextDocTypeReadVersion, MATROSKA_VERSION, errorMessage, input))
+ {
+ if (!errorMessage.empty())
+ {
+ cerr << errorMessage << endl;
+ return 0;
+ }
+ }
+ else if (EBML_ElementIsType(el, &MATROSKA_ContextSegment))
+ {
+ return el;
+ }
+ else
+ {
+ EBML_ElementSkipData(el, input, 0, 0, 0);
+ }
+ deleteNode(&el);
+ }
+ return 0;
+}
+
+ClusterCreeper::ClusterCreeper(matroska_cluster* cluster, stream* io,
+ ebml_master* tracks,
+ ebml_master* segment) :
+ mCluster(cluster),
+ mIO(io)
+{
+ MATROSKA_LinkClusterBlocks(cluster, segment, tracks, false);
+ //
+ // Firstly, we read the data in!
+ //
+ for (ebml_element* el = EBML_MasterChildren(cluster); el != 0; el = EBML_MasterNext(el))
+ {
+ if (EBML_ElementIsType(el, &MATROSKA_ContextBlockGroup))
+ {
+ for (ebml_element* b = EBML_MasterChildren(el); b != 0; b = EBML_MasterNext(b))
+ {
+ if (EBML_ElementIsType(b, &MATROSKA_ContextBlock))
+ {
+ err_t result = MATROSKA_BlockReadData((matroska_block*)b, mIO);
+
+ mBlocks.push_back((matroska_block*)b);
+ if (result != ERR_NONE)
+ {
+ cerr << "Damn! Coudn't read this block's data." << endl;
+ deleteNode(&el);
+ throw ReadError(result);
+ }
+ }
+ }
+ }
+ else if (EBML_ElementIsType(el, &MATROSKA_ContextSimpleBlock))
+ {
+ err_t result = MATROSKA_BlockReadData((matroska_block*)el, mIO);
+ if (result != ERR_NONE)
+ {
+ cerr << "Damn! Coudn't read this block's data." << endl;
+ deleteNode(&el);
+ throw ReadError(result);
+ }
+ mBlocks.push_back((matroska_block*)el);
+ }
+ }
+ mCurrentBlock = mBlocks.begin();
+ mCurrentFrame = 0;
+}
+
+ClusterCreeper::~ClusterCreeper()
+{
+ for (BlockSeq::iterator iter = mBlocks.begin(); iter != mBlocks.end(); ++iter)
+ {
+ MATROSKA_BlockReleaseData(*iter, true);
+ }
+}
+
+TrackFrame* ClusterCreeper::getNextFrame()
+{
+ if (mCurrentFrame >= MATROSKA_BlockGetFrameCount(*mCurrentBlock))
+ {
+ ++mCurrentBlock;
+ mCurrentFrame = 0;
+ if (mCurrentBlock == mBlocks.end())
+ {
+ return 0;
+ }
+ }
+
+ TrackFrame* result = new TrackFrame;
+ result->track = MATROSKA_BlockTrackNum(*mCurrentBlock);
+ err_t errCode = MATROSKA_BlockGetFrame(*mCurrentBlock, mCurrentFrame++, &(result->frameData), true);
+ if (errCode != ERR_NONE)
+ {
+ cerr << "Dang! Could not get this frame!" << endl;
+ throw ReadError(errCode);
+ }
+ return result;
+}
+
+ContainerReader::ContainerReader(stream* s) :
+ mStream(s),
+ mEndOfSegment(0),
+ mEbmlHeader(0),
+ mDuration(0.0)
+{
+ setFileSize();
+}
+
+ContainerReader::~ContainerReader()
+{
+ StreamClose(mStream);
+}
+
+size_t ContainerReader::fileSize()
+{
+ return mFileSize;
+}
+
+size_t ContainerReader::endOfSegment()
+{
+ return mEndOfSegment;
+}
+
+int64_t ContainerReader::timecodeScale()
+{
+ return mTimecodeScale;
+}
+
+double ContainerReader::duration()
+{
+ return mDuration;
+}
+
+size_t ContainerReader::trackCount()
+{
+ return mTrackCount;
+}
+
+int64_t ContainerReader::clusterCount()
+{
+ return mSections.clusterInformation.size();
+}
+
+//
+// Perform a series of operations designed to verify that we can
+// make sense of the file as a matroska file.
+//
+bool ContainerReader::validate()
+{
+ //
+ // TODO: Actually, we'll want to hold onto some of this stuff, so
+ // this needs to be reworked into a class/struct that is a data member
+ //
+ mEBMLContext.Context = &MATROSKA_ContextStream;
+ mEBMLContext.EndPosition = INVALID_FILEPOS_T;
+ mEBMLContext.UpContext = 0;
+ mEBMLContext.Profile = 0;
+
+ //
+ // topElement is read out of the FindNextElement call... I'm not 100% what it means
+ // yet.
+ //
+ int topElement = 0;
+ mEbmlHeader = (ebml_master*)EBML_FindNextElement(mStream, &mEBMLContext, &topElement, 0);
+ if (!mEbmlHeader || !EBML_ElementIsType((ebml_element*)mEbmlHeader, &EBML_ContextHead))
+ {
+ return false;
+ }
+
+ ebml_master* segment=
+ (ebml_master*)checkMatroskaHeader((ebml_element*)mEbmlHeader, &mEBMLContext, mStream);
+
+ if (!segment)
+ {
+ cerr << "Unable to verify Matroska segment" << endl;
+ return false;
+ }
+
+ if (EBML_ElementPositionEnd((ebml_element*)segment) != INVALID_FILEPOS_T)
+ {
+ mEndOfSegment = EBML_ElementPositionEnd((ebml_element*)segment);
+ }
+
+ mSegmentContext.Context = &MATROSKA_ContextSegment;
+ mSegmentContext.EndPosition = EBML_ElementPositionEnd((ebml_element*)segment);
+ mSegmentContext.UpContext = &mEBMLContext;
+ mSegmentContext.Profile = PROFILE_MATROSKA_V3;
+
+ topElement = 0;
+
+ //
+ // Gather level 1 elements in the segment. See matroska.org for spec details, but
+ // basically a matroska file is organized into multiple level 1 groups. The actual
+ // media data is in the cluster group, but you want the tracks and possibly
+ // the cues and tags as well. Cues make it possible to find your way through the
+ // clusters by time code a lot quicker than scanning the whole file.
+ //
+
+ //
+ // EBML_FindNextElement iterates through the structure of the file, only pulling out
+ // the most basic info. To get the actual data, you need to actually read it with
+ // EBML_ElementReadData. ElementReadData takes a pointer to the element being read,
+ // the stream and the parser context. The next arguments are to allow dummy records
+ // to be read, how much/deep to read the data, and the depth of the CRC checks.
+ // For the most part this is yes, give it all to me, and zero respectively. The
+ // exception is the clusters... since this is the data itself, we don't want to load
+ // it all into memory just now, so just the bones of each cluster is read. The data
+ // doesn't get loaded till we need it.
+ //
+
+ for (ebml_master* currentLevel1Group =
+ (ebml_master*)EBML_FindNextElement(mStream, &mSegmentContext, &topElement, 1);
+ currentLevel1Group != 0;
+ currentLevel1Group =
+ (ebml_master*)EBML_FindNextElement(mStream, &mSegmentContext, &topElement, 1))
+ {
+ if (EBML_ElementIsType((ebml_element*)currentLevel1Group , &MATROSKA_ContextInfo))
+ {
+ if (EBML_ElementReadData(currentLevel1Group, mStream, &mSegmentContext, 1,
+ SCOPE_ALL_DATA, 0) == ERR_NONE)
+ {
+ mSections.segmentInformation = currentLevel1Group;
+ }
+ }
+ else if (EBML_ElementIsType((ebml_element*)currentLevel1Group, &MATROSKA_ContextTracks))
+ {
+ if (EBML_ElementReadData(currentLevel1Group, mStream, &mSegmentContext, 1,
+ SCOPE_ALL_DATA, 0) == ERR_NONE)
+ {
+ mSections.trackInformation = currentLevel1Group;
+ }
+ }
+ else if (EBML_ElementIsType((ebml_element*)currentLevel1Group, &MATROSKA_ContextTags))
+ {
+ if (EBML_ElementReadData(currentLevel1Group, mStream, &mSegmentContext, 1,
+ SCOPE_ALL_DATA, 0) == ERR_NONE)
+ {
+ mSections.tagInformation = currentLevel1Group;
+ }
+ }
+ else if (EBML_ElementIsType((ebml_element*)currentLevel1Group, &MATROSKA_ContextCues))
+ {
+ if (EBML_ElementReadData(currentLevel1Group, mStream, &mSegmentContext, 1,
+ SCOPE_ALL_DATA, 0) == ERR_NONE)
+ {
+ mSections.cueInformation = currentLevel1Group;
+ }
+ }
+ else if (EBML_ElementIsType((ebml_element*)currentLevel1Group,
+ &MATROSKA_ContextAttachments))
+ {
+ if (EBML_ElementReadData(currentLevel1Group, mStream, &mSegmentContext, 1,
+ SCOPE_ALL_DATA, 0) == ERR_NONE)
+ {
+ mSections.attachmentInformation = currentLevel1Group;
+ }
+ }
+ else if (EBML_ElementIsType((ebml_element*)currentLevel1Group,
+ &MATROSKA_ContextCluster))
+ {
+ if (EBML_ElementReadData(currentLevel1Group, mStream, &mSegmentContext, false,
+ SCOPE_PARTIAL_DATA, 0) == ERR_NONE)
+ {
+ mSections.clusterInformation.push_back((matroska_cluster*)currentLevel1Group);
+
+ //
+ // mkvclean, which is used as the example to help figure
+ // this stuff out deletes the position and previous size
+ // from the cluster from the cluster. We won't do that for
+ // the time being.
+ //
+ ebml_master* weAreSkippingData =
+ (ebml_master*)EBML_ElementSkipData((ebml_element*)currentLevel1Group,
+ mStream, &mSegmentContext, 0, 1);
+ if (!weAreSkippingData)
+ {
+ //
+ // I *think* we have to break out of the for loop here.
+ // A 0 result from EBML_ElementSkipData probably
+ // indicates that we have reached the end of the road.
+ //
+ }
+ }
+ }
+ else
+ {
+ //
+ // This is just something else... could be void space put in the file
+ // to leave room for another top level group. Skip this.
+ //
+ ebml_master* zilch = (ebml_master*)EBML_ElementSkipData(
+ (ebml_element*)currentLevel1Group, mStream, &mSegmentContext, 0, 1);
+ assert(!zilch);
+ //
+ // Since this data is not useful to us, we need to clean it up now.
+ //
+ NodeDelete((node*)currentLevel1Group);
+ }
+ }
+
+ mTimecodeScale = MATROSKA_SegmentInfoTimecodeScale(mSections.segmentInformation);
+
+ if (mTimecodeScale == 0)
+ {
+ mTimecodeScale = 1000000;
+ }
+
+ ebml_float* duration = (ebml_float*)EBML_MasterFindChild(mSections.segmentInformation,
+ &MATROSKA_ContextDuration);
+ if (duration)
+ {
+ mDuration = EBML_FloatValue(duration);
+ }
+
+ mTrackCount = 0;
+
+ for (ebml_master* trackEntry = (ebml_master*)EBML_MasterFindChild(mSections.trackInformation, &MATROSKA_ContextTrackEntry);
+ trackEntry != 0; trackEntry = (ebml_master*)EBML_MasterNextChild(mSections.trackInformation, trackEntry))
... 873 lines suppressed ...
--
asterisk-scf/integration/file_media_service.git
More information about the asterisk-scf-commits
mailing list