liveMedia/MatroskaFile.cpp

Go to the documentation of this file.
00001 /**********
00002 This library is free software; you can redistribute it and/or modify it under
00003 the terms of the GNU Lesser General Public License as published by the
00004 Free Software Foundation; either version 2.1 of the License, or (at your
00005 option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
00006 
00007 This library is distributed in the hope that it will be useful, but WITHOUT
00008 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
00009 FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
00010 more details.
00011 
00012 You should have received a copy of the GNU Lesser General Public License
00013 along with this library; if not, write to the Free Software Foundation, Inc.,
00014 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
00015 **********/
00016 // "liveMedia"
00017 // Copyright (c) 1996-2012 Live Networks, Inc.  All rights reserved.
00018 // A class that encapsulates a Matroska file.
00019 // Implementation
00020 
00021 #include "MatroskaFileParser.hh"
00022 #include "MatroskaDemuxedTrack.hh"
00023 #include <ByteStreamFileSource.hh>
00024 
00026 
00027 class CuePoint {
00028 public:
00029   CuePoint(double cueTime, u_int64_t clusterOffsetInFile, unsigned blockNumWithinCluster/* 1-based */);
00030   virtual ~CuePoint();
00031 
00032   static void addCuePoint(CuePoint*& root, double cueTime, u_int64_t clusterOffsetInFile, unsigned blockNumWithinCluster/* 1-based */,
00033                           Boolean& needToReviseBalanceOfParent);
00034     // If "cueTime" == "root.fCueTime", replace the existing data, otherwise add to the left or right subtree.
00035     // (Note that this is a static member function because - as a result of tree rotation - "root" might change.)
00036 
00037   Boolean lookup(double& cueTime, u_int64_t& resultClusterOffsetInFile, unsigned& resultBlockNumWithinCluster);
00038 
00039   static void fprintf(FILE* fid, CuePoint* cuePoint); // used for debugging; it's static to allow for "cuePoint == NULL"
00040 
00041 private:
00042   // The "CuePoint" tree is implemented as an AVL Tree, to keep it balanced (for efficient lookup).
00043   CuePoint* fSubTree[2]; // 0 => left; 1 => right
00044   CuePoint* left() const { return fSubTree[0]; }
00045   CuePoint* right() const { return fSubTree[1]; }
00046   char fBalance; // height of right subtree - height of left subtree
00047 
00048   static void rotate(unsigned direction/*0 => left; 1 => right*/, CuePoint*& root); // used to keep the tree in balance
00049 
00050   double fCueTime;
00051   u_int64_t fClusterOffsetInFile;
00052   unsigned fBlockNumWithinCluster; // 0-based
00053 };
00054 
00055 UsageEnvironment& operator<<(UsageEnvironment& env, const CuePoint* cuePoint); // used for debugging
00056 
00057 
00059 
00060 void MatroskaFile
00061 ::createNew(UsageEnvironment& env, char const* fileName, onCreationFunc* onCreation, void* onCreationClientData,
00062             char const* preferredLanguage) {
00063   new MatroskaFile(env, fileName, onCreation, onCreationClientData, preferredLanguage);
00064 }
00065 
00066 MatroskaFile::MatroskaFile(UsageEnvironment& env, char const* fileName, onCreationFunc* onCreation, void* onCreationClientData,
00067                            char const* preferredLanguage)
00068   : Medium(env),
00069     fFileName(strDup(fileName)), fOnCreation(onCreation), fOnCreationClientData(onCreationClientData),
00070     fPreferredLanguage(strDup(preferredLanguage)),
00071     fTimecodeScale(1000000), fSegmentDuration(0.0), fSegmentDataOffset(0), fClusterOffset(0), fCuesOffset(0), fCuePoints(NULL),
00072     fChosenVideoTrackNumber(0), fChosenAudioTrackNumber(0), fChosenSubtitleTrackNumber(0) {
00073   fDemuxesTable = HashTable::create(ONE_WORD_HASH_KEYS);
00074 
00075   // Initialize ourselves by parsing the file's 'Track' headers:
00076   fParserForInitialization
00077     = new MatroskaFileParser(*this, ByteStreamFileSource::createNew(envir(), fileName),
00078                              handleEndOfTrackHeaderParsing, this, NULL);
00079 }
00080 
00081 MatroskaFile::~MatroskaFile() {
00082   delete fParserForInitialization;
00083   delete fCuePoints;
00084 
00085   // Delete any outstanding "MatroskaDemux"s, and the table for them:
00086   MatroskaDemux* demux;
00087   while ((demux = (MatroskaDemux*)fDemuxesTable->RemoveNext()) != NULL) {
00088     delete demux;
00089   }
00090   delete fDemuxesTable;
00091 
00092   delete[] (char*)fPreferredLanguage;
00093   delete[] (char*)fFileName;
00094 }
00095 
00096 void MatroskaFile::handleEndOfTrackHeaderParsing(void* clientData) {
00097   ((MatroskaFile*)clientData)->handleEndOfTrackHeaderParsing();
00098 }
00099 
00100 class TrackChoiceRecord {
00101 public:
00102   unsigned trackNumber;
00103   u_int8_t trackType;
00104   unsigned choiceFlags;
00105 };
00106 
00107 void MatroskaFile::handleEndOfTrackHeaderParsing() {
00108   // Having parsed all of our track headers, iterate through the tracks to figure out which ones should be played.
00109   // The Matroska 'specification' is rather imprecise about this (as usual).  However, we use the following algorithm:
00110   // - Use one (but no more) enabled track of each type (video, audio, subtitle).  (Ignore all tracks that are not 'enabled'.)
00111   // - For each track type, choose the one that's 'forced'.
00112   //     - If more than one is 'forced', choose the first one that matches our preferred language, or the first if none matches.
00113   //     - If none is 'forced', choose the one that's 'default'.
00114   //     - If more than one is 'default', choose the first one that matches our preferred language, or the first if none matches.
00115   //     - If none is 'default', choose the first one that matches our preferred language, or the first if none matches.
00116   unsigned numTracks = fTracks.numTracks();
00117   if (numTracks > 0) {
00118     TrackChoiceRecord* trackChoice = new TrackChoiceRecord[numTracks];
00119     unsigned numEnabledTracks = 0;
00120     TrackTable::Iterator iter(fTracks);
00121     MatroskaTrack* track;
00122     while ((track = iter.next()) != NULL) {
00123       if (!track->isEnabled || track->trackType == 0 || track->codecID == NULL) continue; // track not enabled, or not fully-defined
00124 
00125       trackChoice[numEnabledTracks].trackNumber = track->trackNumber;
00126       trackChoice[numEnabledTracks].trackType = track->trackType;
00127 
00128       // Assign flags for this track so that, when sorted, the largest value becomes our choice:
00129       unsigned choiceFlags = 0;
00130       if (fPreferredLanguage != NULL && track->language != NULL && strcmp(fPreferredLanguage, track->language) == 0) {
00131         // This track matches our preferred language:
00132         choiceFlags |= 1;
00133       }
00134       if (track->isForced) {
00135         choiceFlags |= 4;
00136       } else if (track->isDefault) {
00137         choiceFlags |= 2;
00138       }
00139       trackChoice[numEnabledTracks].choiceFlags = choiceFlags;
00140 
00141       ++numEnabledTracks;
00142     }
00143 
00144     // Choose the desired track for each track type:
00145     for (u_int8_t trackType = 0x01; trackType != MATROSKA_TRACK_TYPE_OTHER; trackType <<= 1) {
00146       int bestNum = -1;
00147       int bestChoiceFlags = -1;
00148       for (unsigned i = 0; i < numEnabledTracks; ++i) {
00149         if (trackChoice[i].trackType == trackType && (int)trackChoice[i].choiceFlags > bestChoiceFlags) {
00150           bestNum = i;
00151           bestChoiceFlags = (int)trackChoice[i].choiceFlags;
00152         }
00153       }
00154       if (bestChoiceFlags >= 0) { // There is a track for this track type
00155         if (trackType == MATROSKA_TRACK_TYPE_VIDEO) fChosenVideoTrackNumber = trackChoice[bestNum].trackNumber;
00156         else if (trackType == MATROSKA_TRACK_TYPE_AUDIO) fChosenAudioTrackNumber = trackChoice[bestNum].trackNumber;
00157         else fChosenSubtitleTrackNumber = trackChoice[bestNum].trackNumber;
00158       }
00159     }
00160 
00161     delete[] trackChoice;
00162   }
00163   
00164 #ifdef DEBUG
00165   if (fChosenVideoTrackNumber > 0) fprintf(stderr, "Chosen video track: #%d\n", fChosenVideoTrackNumber); else fprintf(stderr, "No chosen video track\n");
00166   if (fChosenAudioTrackNumber > 0) fprintf(stderr, "Chosen audio track: #%d\n", fChosenAudioTrackNumber); else fprintf(stderr, "No chosen audio track\n");
00167   if (fChosenSubtitleTrackNumber > 0) fprintf(stderr, "Chosen subtitle track: #%d\n", fChosenSubtitleTrackNumber); else fprintf(stderr, "No chosen subtitle track\n");
00168 #endif
00169 
00170   // Delete our parser, because it's done its job now:
00171   delete fParserForInitialization; fParserForInitialization = NULL;
00172 
00173   // Finally, signal our caller that we've been created and initialized:
00174   if (fOnCreation != NULL) (*fOnCreation)(this, fOnCreationClientData);
00175 }
00176 
00177 MatroskaDemux* MatroskaFile::newDemux() {
00178   MatroskaDemux* demux = new MatroskaDemux(*this);
00179   fDemuxesTable->Add((char const*)demux, demux);
00180 
00181   return demux;
00182 }
00183 
00184 void MatroskaFile::removeDemux(MatroskaDemux* demux) {
00185   fDemuxesTable->Remove((char const*)demux);
00186 }
00187 
00188 float MatroskaFile::fileDuration() {
00189   if (fCuePoints == NULL) return 0.0; // Hack, because the RTSP server code assumes that duration > 0 => seekable. (fix this) #####
00190 
00191   return segmentDuration()*(timecodeScale()/1000000000.0f);
00192 }
00193 
00194 void MatroskaFile::addCuePoint(double cueTime, u_int64_t clusterOffsetInFile, unsigned blockNumWithinCluster) {
00195   Boolean dummy = False; // not used
00196   CuePoint::addCuePoint(fCuePoints, cueTime, clusterOffsetInFile, blockNumWithinCluster, dummy);
00197 }
00198 
00199 Boolean MatroskaFile::lookupCuePoint(double& cueTime, u_int64_t& resultClusterOffsetInFile, unsigned& resultBlockNumWithinCluster) {
00200   if (fCuePoints == NULL) return False;
00201 
00202   (void)fCuePoints->lookup(cueTime, resultClusterOffsetInFile, resultBlockNumWithinCluster);
00203   return True;
00204 }
00205 
00206 void MatroskaFile::printCuePoints(FILE* fid) {
00207   CuePoint::fprintf(fid, fCuePoints);
00208 }
00209 
00210 
00212 
00213 MatroskaFile::TrackTable::TrackTable()
00214   : fTable(HashTable::create(ONE_WORD_HASH_KEYS)) {
00215 }
00216 
00217 MatroskaFile::TrackTable::~TrackTable() {
00218   // Remove and delete all of our "MatroskaTrack" descriptors, and the hash table itself:
00219   MatroskaTrack* track;
00220   while ((track = (MatroskaTrack*)fTable->RemoveNext()) != NULL) {
00221     delete track;
00222   }
00223   delete fTable;
00224 } 
00225 
00226 void MatroskaFile::TrackTable::add(MatroskaTrack* newTrack, unsigned trackNumber) {
00227   if (newTrack != NULL && newTrack->trackNumber != 0) fTable->Remove((char const*)newTrack->trackNumber);
00228   MatroskaTrack* existingTrack = (MatroskaTrack*)fTable->Add((char const*)trackNumber, newTrack);
00229   delete existingTrack; // in case it wasn't NULL
00230 }
00231 
00232 MatroskaTrack* MatroskaFile::TrackTable::lookup(unsigned trackNumber) {
00233   return (MatroskaTrack*)fTable->Lookup((char const*)trackNumber);
00234 }
00235 
00236 unsigned MatroskaFile::TrackTable::numTracks() const { return fTable->numEntries(); }
00237 
00238 MatroskaFile::TrackTable::Iterator::Iterator(MatroskaFile::TrackTable& ourTable)
00239   : fOurTable(ourTable) {
00240   fIter = HashTable::Iterator::create(*(ourTable.fTable));
00241 }
00242 
00243 MatroskaFile::TrackTable::Iterator::~Iterator() {
00244   delete fIter;
00245 }
00246 
00247 MatroskaTrack* MatroskaFile::TrackTable::Iterator::next() {
00248   char const* key;
00249   return (MatroskaTrack*)fIter->next(key);
00250 }
00251 
00252 
00254 
00255 MatroskaTrack::MatroskaTrack()
00256   : trackNumber(0/*not set*/), trackType(0/*unknown*/),
00257     isEnabled(True), isDefault(True), isForced(False),
00258     defaultDuration(0),
00259     name(NULL), language(NULL), codecID(NULL),
00260     samplingFrequency(0), numChannels(2), mimeType(""),
00261     codecPrivateSize(0), codecPrivate(NULL), headerStrippedBytesSize(0), headerStrippedBytes(NULL),
00262     subframeSizeSize(0), durationImbalance(0) {
00263   prevPresentationTime.tv_sec = 0; prevPresentationTime.tv_usec = 0;
00264 }
00265 
00266 MatroskaTrack::~MatroskaTrack() {
00267   delete[] name; delete[] language; delete[] codecID;
00268   delete[] codecPrivate;
00269   delete[] headerStrippedBytes;
00270 }
00271 
00272 
00274 
00275 MatroskaDemux::MatroskaDemux(MatroskaFile& ourFile)
00276   : Medium(ourFile.envir()),
00277     fOurFile(ourFile), fDemuxedTracksTable(HashTable::create(ONE_WORD_HASH_KEYS)) {
00278   fOurParser = new MatroskaFileParser(ourFile, ByteStreamFileSource::createNew(envir(), ourFile.fileName()),
00279                                       handleEndOfFile, this, this);
00280 }
00281 
00282 MatroskaDemux::~MatroskaDemux() {
00283   // Begin by acting as if we've reached the end of the source file.  This should cause all of our demuxed tracks to get closed.
00284   handleEndOfFile();
00285 
00286   // Then delete our table of "MatroskaDemuxedTrack"s
00287   // - but not the "MatroskaDemuxedTrack"s themselves; that should have already happened:
00288   delete fDemuxedTracksTable;
00289 
00290   delete fOurParser;
00291   fOurFile.removeDemux(this);
00292 }
00293 
00294 FramedSource* MatroskaDemux::newDemuxedTrack(unsigned trackNumber) {
00295   FramedSource* track = new MatroskaDemuxedTrack(envir(), trackNumber, *this);
00296   fDemuxedTracksTable->Add((char const*)trackNumber, track);
00297   return track;
00298 }
00299 
00300 MatroskaDemuxedTrack* MatroskaDemux::lookupDemuxedTrack(unsigned trackNumber) {
00301   return (MatroskaDemuxedTrack*)fDemuxedTracksTable->Lookup((char const*)trackNumber);
00302 }
00303 
00304 void MatroskaDemux::removeTrack(unsigned trackNumber) {
00305   fDemuxedTracksTable->Remove((char const*)trackNumber);
00306   if (fDemuxedTracksTable->numEntries() == 0) {
00307     // We no longer have any demuxed tracks, so delete ourselves now:
00308     delete this;
00309   }
00310 }
00311 
00312 void MatroskaDemux::continueReading() {
00313   fOurParser->continueParsing();  
00314 }
00315 
00316 void MatroskaDemux::seekToTime(double& seekNPT) {
00317   if (fOurParser != NULL) fOurParser->seekToTime(seekNPT);
00318 }
00319 
00320 void MatroskaDemux::handleEndOfFile(void* clientData) {
00321   ((MatroskaDemux*)clientData)->handleEndOfFile();
00322 }
00323 
00324 void MatroskaDemux::handleEndOfFile() {
00325   // Iterate through all of our 'demuxed tracks', handling 'end of input' on each one.
00326   // Hack: Because this can cause the hash table to get modified underneath us, we don't call the handlers until after we've
00327   // first iterated through all of the tracks.
00328   unsigned numTracks = fDemuxedTracksTable->numEntries();
00329   if (numTracks == 0) return;
00330   MatroskaDemuxedTrack** tracks = new MatroskaDemuxedTrack*[numTracks];
00331 
00332   HashTable::Iterator* iter = HashTable::Iterator::create(*fDemuxedTracksTable);
00333   unsigned i, trackNumber;
00334   for (i = 0; i < numTracks; ++i) {
00335     tracks[i] = (MatroskaDemuxedTrack*)iter->next((char const*&)trackNumber);
00336   }
00337   delete iter;
00338 
00339   for (i = 0; i < numTracks; ++i) {
00340     if (tracks[i] == NULL) continue; // sanity check; shouldn't happen
00341     FramedSource::handleClosure(tracks[i]);
00342   }
00343 
00344   delete[] tracks;
00345 }
00346 
00347 
00349 
00350 CuePoint::CuePoint(double cueTime, u_int64_t clusterOffsetInFile, unsigned blockNumWithinCluster)
00351   : fBalance(0),
00352     fCueTime(cueTime), fClusterOffsetInFile(clusterOffsetInFile), fBlockNumWithinCluster(blockNumWithinCluster - 1) {
00353   fSubTree[0] = fSubTree[1] = NULL;
00354 }
00355 
00356 CuePoint::~CuePoint() {
00357   delete fSubTree[0]; delete fSubTree[1];
00358 }
00359 
00360 #ifndef ABS
00361 #define ABS(x) (x)<0 ? -(x) : (x)
00362 #endif
00363 
00364 void CuePoint::addCuePoint(CuePoint*& root, double cueTime, u_int64_t clusterOffsetInFile, unsigned blockNumWithinCluster,
00365                            Boolean& needToReviseBalanceOfParent) {
00366   needToReviseBalanceOfParent = False; // by default; may get changed below
00367 
00368   if (root == NULL) {
00369     root = new CuePoint(cueTime, clusterOffsetInFile, blockNumWithinCluster);
00370     needToReviseBalanceOfParent = True;
00371   } else if (cueTime == root->fCueTime) {
00372     // Replace existing data:
00373     root->fClusterOffsetInFile = clusterOffsetInFile;
00374     root->fBlockNumWithinCluster = blockNumWithinCluster - 1;
00375   } else {
00376     // Add to our left or right subtree:
00377     int direction = cueTime > root->fCueTime; // 0 (left) or 1 (right)
00378     Boolean needToReviseOurBalance = False;
00379     addCuePoint(root->fSubTree[direction], cueTime, clusterOffsetInFile, blockNumWithinCluster, needToReviseOurBalance);
00380 
00381     if (needToReviseOurBalance) {
00382       // We need to change our 'balance' number, perhaps while also performing a rotation to bring ourself back into balance:
00383       if (root->fBalance == 0) {
00384         // We were balanced before, but now we're unbalanced (by 1) on the "direction" side:
00385         root->fBalance = -1 + 2*direction; // -1 for "direction" 0; 1 for "direction" 1
00386         needToReviseBalanceOfParent = True;
00387       } else if (root->fBalance == 1 - 2*direction) { // 1 for "direction" 0; -1 for "direction" 1
00388         // We were unbalanced (by 1) on the side opposite to where we added an entry, so now we're balanced:
00389         root->fBalance = 0;
00390       } else {
00391         // We were unbalanced (by 1) on the side where we added an entry, so now we're unbalanced by 2, and have to rebalance:
00392         if (root->fSubTree[direction]->fBalance == -1 + 2*direction) { // -1 for "direction" 0; 1 for "direction" 1
00393           // We're 'doubly-unbalanced' on this side, so perform a single rotation in the opposite direction:
00394           root->fBalance = root->fSubTree[direction]->fBalance = 0;
00395           rotate(1-direction, root);
00396         } else {
00397           // This is the Left-Right case (for "direction" 0) or the Right-Left case (for "direction" 1); perform two rotations:
00398           char newParentCurBalance = root->fSubTree[direction]->fSubTree[1-direction]->fBalance;
00399           if (newParentCurBalance == 1 - 2*direction) { // 1 for "direction" 0; -1 for "direction" 1
00400             root->fBalance = 0;
00401             root->fSubTree[direction]->fBalance = -1 + 2*direction; // -1 for "direction" 0; 1 for "direction" 1
00402           } else if (newParentCurBalance == 0) {
00403             root->fBalance = 0;
00404             root->fSubTree[direction]->fBalance = 0;
00405           } else {
00406             root->fBalance = 1 - 2*direction; // 1 for "direction" 0; -1 for "direction" 1
00407             root->fSubTree[direction]->fBalance = 0;
00408           }
00409           rotate(direction, root->fSubTree[direction]);
00410 
00411           root->fSubTree[direction]->fBalance = 0; // the new root will be balanced
00412           rotate(1-direction, root);
00413         }
00414       }
00415     }
00416   }
00417 }
00418 
00419 Boolean CuePoint::lookup(double& cueTime, u_int64_t& resultClusterOffsetInFile, unsigned& resultBlockNumWithinCluster) {
00420   if (cueTime < fCueTime) {
00421     if (left() == NULL) {
00422       resultClusterOffsetInFile = 0;
00423       resultBlockNumWithinCluster = 0;
00424       return False;
00425     } else {
00426       return left()->lookup(cueTime, resultClusterOffsetInFile, resultBlockNumWithinCluster);
00427     }
00428   } else {
00429     if (right() == NULL || !right()->lookup(cueTime, resultClusterOffsetInFile, resultBlockNumWithinCluster)) {
00430       // Use this record:
00431       cueTime = fCueTime;
00432       resultClusterOffsetInFile = fClusterOffsetInFile;
00433       resultBlockNumWithinCluster = fBlockNumWithinCluster;
00434     }
00435     return True;
00436   }
00437 }
00438 
00439 void CuePoint::fprintf(FILE* fid, CuePoint* cuePoint) {
00440   if (cuePoint != NULL) {
00441     ::fprintf(fid, "[");
00442     fprintf(fid, cuePoint->left());
00443 
00444     ::fprintf(fid, ",%.1f{%d},", cuePoint->fCueTime, cuePoint->fBalance);
00445 
00446     fprintf(fid, cuePoint->right());
00447     ::fprintf(fid, "]");
00448   }
00449 }
00450 
00451 void CuePoint::rotate(unsigned direction/*0 => left; 1 => right*/, CuePoint*& root) {
00452   CuePoint* pivot = root->fSubTree[1-direction]; // ASSERT: pivot != NULL
00453   root->fSubTree[1-direction] = pivot->fSubTree[direction];
00454   pivot->fSubTree[direction] = root;
00455   root = pivot;
00456 }

Generated on Thu May 17 07:11:46 2012 for live by  doxygen 1.5.2