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-2013 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   FramedSource* inputSource = ByteStreamFileSource::createNew(envir(), fileName);
00076   if (inputSource == NULL) {
00077     // The specified input file does not exist!
00078     fParserForInitialization = NULL;
00079     handleEndOfTrackHeaderParsing(); // we have no file, and thus no tracks, but we still need to signal this
00080   } else {
00081     // Initialize ourselves by parsing the file's 'Track' headers:
00082     fParserForInitialization = new MatroskaFileParser(*this, inputSource, handleEndOfTrackHeaderParsing, this, NULL);
00083   }
00084 }
00085 
00086 MatroskaFile::~MatroskaFile() {
00087   delete fParserForInitialization;
00088   delete fCuePoints;
00089 
00090   // Delete any outstanding "MatroskaDemux"s, and the table for them:
00091   MatroskaDemux* demux;
00092   while ((demux = (MatroskaDemux*)fDemuxesTable->RemoveNext()) != NULL) {
00093     delete demux;
00094   }
00095   delete fDemuxesTable;
00096 
00097   delete[] (char*)fPreferredLanguage;
00098   delete[] (char*)fFileName;
00099 }
00100 
00101 void MatroskaFile::handleEndOfTrackHeaderParsing(void* clientData) {
00102   ((MatroskaFile*)clientData)->handleEndOfTrackHeaderParsing();
00103 }
00104 
00105 class TrackChoiceRecord {
00106 public:
00107   unsigned trackNumber;
00108   u_int8_t trackType;
00109   unsigned choiceFlags;
00110 };
00111 
00112 void MatroskaFile::handleEndOfTrackHeaderParsing() {
00113   // Having parsed all of our track headers, iterate through the tracks to figure out which ones should be played.
00114   // The Matroska 'specification' is rather imprecise about this (as usual).  However, we use the following algorithm:
00115   // - Use one (but no more) enabled track of each type (video, audio, subtitle).  (Ignore all tracks that are not 'enabled'.)
00116   // - For each track type, choose the one that's 'forced'.
00117   //     - If more than one is 'forced', choose the first one that matches our preferred language, or the first if none matches.
00118   //     - If none is 'forced', choose the one that's 'default'.
00119   //     - If more than one is 'default', choose the first one that matches our preferred language, or the first if none matches.
00120   //     - If none is 'default', choose the first one that matches our preferred language, or the first if none matches.
00121   unsigned numTracks = fTracks.numTracks();
00122   if (numTracks > 0) {
00123     TrackChoiceRecord* trackChoice = new TrackChoiceRecord[numTracks];
00124     unsigned numEnabledTracks = 0;
00125     TrackTable::Iterator iter(fTracks);
00126     MatroskaTrack* track;
00127     while ((track = iter.next()) != NULL) {
00128       if (!track->isEnabled || track->trackType == 0 || track->codecID == NULL) continue; // track not enabled, or not fully-defined
00129 
00130       trackChoice[numEnabledTracks].trackNumber = track->trackNumber;
00131       trackChoice[numEnabledTracks].trackType = track->trackType;
00132 
00133       // Assign flags for this track so that, when sorted, the largest value becomes our choice:
00134       unsigned choiceFlags = 0;
00135       if (fPreferredLanguage != NULL && track->language != NULL && strcmp(fPreferredLanguage, track->language) == 0) {
00136         // This track matches our preferred language:
00137         choiceFlags |= 1;
00138       }
00139       if (track->isForced) {
00140         choiceFlags |= 4;
00141       } else if (track->isDefault) {
00142         choiceFlags |= 2;
00143       }
00144       trackChoice[numEnabledTracks].choiceFlags = choiceFlags;
00145 
00146       ++numEnabledTracks;
00147     }
00148 
00149     // Choose the desired track for each track type:
00150     for (u_int8_t trackType = 0x01; trackType != MATROSKA_TRACK_TYPE_OTHER; trackType <<= 1) {
00151       int bestNum = -1;
00152       int bestChoiceFlags = -1;
00153       for (unsigned i = 0; i < numEnabledTracks; ++i) {
00154         if (trackChoice[i].trackType == trackType && (int)trackChoice[i].choiceFlags > bestChoiceFlags) {
00155           bestNum = i;
00156           bestChoiceFlags = (int)trackChoice[i].choiceFlags;
00157         }
00158       }
00159       if (bestChoiceFlags >= 0) { // There is a track for this track type
00160         if (trackType == MATROSKA_TRACK_TYPE_VIDEO) fChosenVideoTrackNumber = trackChoice[bestNum].trackNumber;
00161         else if (trackType == MATROSKA_TRACK_TYPE_AUDIO) fChosenAudioTrackNumber = trackChoice[bestNum].trackNumber;
00162         else fChosenSubtitleTrackNumber = trackChoice[bestNum].trackNumber;
00163       }
00164     }
00165 
00166     delete[] trackChoice;
00167   }
00168   
00169 #ifdef DEBUG
00170   if (fChosenVideoTrackNumber > 0) fprintf(stderr, "Chosen video track: #%d\n", fChosenVideoTrackNumber); else fprintf(stderr, "No chosen video track\n");
00171   if (fChosenAudioTrackNumber > 0) fprintf(stderr, "Chosen audio track: #%d\n", fChosenAudioTrackNumber); else fprintf(stderr, "No chosen audio track\n");
00172   if (fChosenSubtitleTrackNumber > 0) fprintf(stderr, "Chosen subtitle track: #%d\n", fChosenSubtitleTrackNumber); else fprintf(stderr, "No chosen subtitle track\n");
00173 #endif
00174 
00175   // Delete our parser, because it's done its job now:
00176   delete fParserForInitialization; fParserForInitialization = NULL;
00177 
00178   // Finally, signal our caller that we've been created and initialized:
00179   if (fOnCreation != NULL) (*fOnCreation)(this, fOnCreationClientData);
00180 }
00181 
00182 MatroskaDemux* MatroskaFile::newDemux() {
00183   MatroskaDemux* demux = new MatroskaDemux(*this);
00184   fDemuxesTable->Add((char const*)demux, demux);
00185 
00186   return demux;
00187 }
00188 
00189 void MatroskaFile::removeDemux(MatroskaDemux* demux) {
00190   fDemuxesTable->Remove((char const*)demux);
00191 }
00192 
00193 float MatroskaFile::fileDuration() {
00194   if (fCuePoints == NULL) return 0.0; // Hack, because the RTSP server code assumes that duration > 0 => seekable. (fix this) #####
00195 
00196   return segmentDuration()*(timecodeScale()/1000000000.0f);
00197 }
00198 
00199 void MatroskaFile::addCuePoint(double cueTime, u_int64_t clusterOffsetInFile, unsigned blockNumWithinCluster) {
00200   Boolean dummy = False; // not used
00201   CuePoint::addCuePoint(fCuePoints, cueTime, clusterOffsetInFile, blockNumWithinCluster, dummy);
00202 }
00203 
00204 Boolean MatroskaFile::lookupCuePoint(double& cueTime, u_int64_t& resultClusterOffsetInFile, unsigned& resultBlockNumWithinCluster) {
00205   if (fCuePoints == NULL) return False;
00206 
00207   (void)fCuePoints->lookup(cueTime, resultClusterOffsetInFile, resultBlockNumWithinCluster);
00208   return True;
00209 }
00210 
00211 void MatroskaFile::printCuePoints(FILE* fid) {
00212   CuePoint::fprintf(fid, fCuePoints);
00213 }
00214 
00215 
00217 
00218 MatroskaFile::TrackTable::TrackTable()
00219   : fTable(HashTable::create(ONE_WORD_HASH_KEYS)) {
00220 }
00221 
00222 MatroskaFile::TrackTable::~TrackTable() {
00223   // Remove and delete all of our "MatroskaTrack" descriptors, and the hash table itself:
00224   MatroskaTrack* track;
00225   while ((track = (MatroskaTrack*)fTable->RemoveNext()) != NULL) {
00226     delete track;
00227   }
00228   delete fTable;
00229 } 
00230 
00231 void MatroskaFile::TrackTable::add(MatroskaTrack* newTrack, unsigned trackNumber) {
00232   if (newTrack != NULL && newTrack->trackNumber != 0) fTable->Remove((char const*)newTrack->trackNumber);
00233   MatroskaTrack* existingTrack = (MatroskaTrack*)fTable->Add((char const*)trackNumber, newTrack);
00234   delete existingTrack; // in case it wasn't NULL
00235 }
00236 
00237 MatroskaTrack* MatroskaFile::TrackTable::lookup(unsigned trackNumber) {
00238   return (MatroskaTrack*)fTable->Lookup((char const*)trackNumber);
00239 }
00240 
00241 unsigned MatroskaFile::TrackTable::numTracks() const { return fTable->numEntries(); }
00242 
00243 MatroskaFile::TrackTable::Iterator::Iterator(MatroskaFile::TrackTable& ourTable) {
00244   fIter = HashTable::Iterator::create(*(ourTable.fTable));
00245 }
00246 
00247 MatroskaFile::TrackTable::Iterator::~Iterator() {
00248   delete fIter;
00249 }
00250 
00251 MatroskaTrack* MatroskaFile::TrackTable::Iterator::next() {
00252   char const* key;
00253   return (MatroskaTrack*)fIter->next(key);
00254 }
00255 
00256 
00258 
00259 MatroskaTrack::MatroskaTrack()
00260   : trackNumber(0/*not set*/), trackType(0/*unknown*/),
00261     isEnabled(True), isDefault(True), isForced(False),
00262     defaultDuration(0),
00263     name(NULL), language(NULL), codecID(NULL),
00264     samplingFrequency(0), numChannels(2), mimeType(""),
00265     codecPrivateSize(0), codecPrivate(NULL), headerStrippedBytesSize(0), headerStrippedBytes(NULL),
00266     subframeSizeSize(0) {
00267 }
00268 
00269 MatroskaTrack::~MatroskaTrack() {
00270   delete[] name; delete[] language; delete[] codecID;
00271   delete[] codecPrivate;
00272   delete[] headerStrippedBytes;
00273 }
00274 
00275 
00277 
00278 MatroskaDemux::MatroskaDemux(MatroskaFile& ourFile)
00279   : Medium(ourFile.envir()),
00280     fOurFile(ourFile), fDemuxedTracksTable(HashTable::create(ONE_WORD_HASH_KEYS)) {
00281   fOurParser = new MatroskaFileParser(ourFile, ByteStreamFileSource::createNew(envir(), ourFile.fileName()),
00282                                       handleEndOfFile, this, this);
00283 }
00284 
00285 MatroskaDemux::~MatroskaDemux() {
00286   // 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.
00287   handleEndOfFile();
00288 
00289   // Then delete our table of "MatroskaDemuxedTrack"s
00290   // - but not the "MatroskaDemuxedTrack"s themselves; that should have already happened:
00291   delete fDemuxedTracksTable;
00292 
00293   delete fOurParser;
00294   fOurFile.removeDemux(this);
00295 }
00296 
00297 FramedSource* MatroskaDemux::newDemuxedTrack(unsigned trackNumber) {
00298   FramedSource* track = new MatroskaDemuxedTrack(envir(), trackNumber, *this);
00299   fDemuxedTracksTable->Add((char const*)trackNumber, track);
00300   return track;
00301 }
00302 
00303 MatroskaDemuxedTrack* MatroskaDemux::lookupDemuxedTrack(unsigned trackNumber) {
00304   return (MatroskaDemuxedTrack*)fDemuxedTracksTable->Lookup((char const*)trackNumber);
00305 }
00306 
00307 void MatroskaDemux::removeTrack(unsigned trackNumber) {
00308   fDemuxedTracksTable->Remove((char const*)trackNumber);
00309   if (fDemuxedTracksTable->numEntries() == 0) {
00310     // We no longer have any demuxed tracks, so delete ourselves now:
00311     delete this;
00312   }
00313 }
00314 
00315 void MatroskaDemux::continueReading() {
00316   fOurParser->continueParsing();  
00317 }
00318 
00319 void MatroskaDemux::seekToTime(double& seekNPT) {
00320   if (fOurParser != NULL) fOurParser->seekToTime(seekNPT);
00321 }
00322 
00323 void MatroskaDemux::handleEndOfFile(void* clientData) {
00324   ((MatroskaDemux*)clientData)->handleEndOfFile();
00325 }
00326 
00327 void MatroskaDemux::handleEndOfFile() {
00328   // Iterate through all of our 'demuxed tracks', handling 'end of input' on each one.
00329   // Hack: Because this can cause the hash table to get modified underneath us, we don't call the handlers until after we've
00330   // first iterated through all of the tracks.
00331   unsigned numTracks = fDemuxedTracksTable->numEntries();
00332   if (numTracks == 0) return;
00333   MatroskaDemuxedTrack** tracks = new MatroskaDemuxedTrack*[numTracks];
00334 
00335   HashTable::Iterator* iter = HashTable::Iterator::create(*fDemuxedTracksTable);
00336   unsigned i;
00337   char const* trackNumber;
00338 
00339   for (i = 0; i < numTracks; ++i) {
00340     tracks[i] = (MatroskaDemuxedTrack*)iter->next(trackNumber);
00341   }
00342   delete iter;
00343 
00344   for (i = 0; i < numTracks; ++i) {
00345     if (tracks[i] == NULL) continue; // sanity check; shouldn't happen
00346     FramedSource::handleClosure(tracks[i]);
00347   }
00348 
00349   delete[] tracks;
00350 }
00351 
00352 
00354 
00355 CuePoint::CuePoint(double cueTime, u_int64_t clusterOffsetInFile, unsigned blockNumWithinCluster)
00356   : fBalance(0),
00357     fCueTime(cueTime), fClusterOffsetInFile(clusterOffsetInFile), fBlockNumWithinCluster(blockNumWithinCluster - 1) {
00358   fSubTree[0] = fSubTree[1] = NULL;
00359 }
00360 
00361 CuePoint::~CuePoint() {
00362   delete fSubTree[0]; delete fSubTree[1];
00363 }
00364 
00365 #ifndef ABS
00366 #define ABS(x) (x)<0 ? -(x) : (x)
00367 #endif
00368 
00369 void CuePoint::addCuePoint(CuePoint*& root, double cueTime, u_int64_t clusterOffsetInFile, unsigned blockNumWithinCluster,
00370                            Boolean& needToReviseBalanceOfParent) {
00371   needToReviseBalanceOfParent = False; // by default; may get changed below
00372 
00373   if (root == NULL) {
00374     root = new CuePoint(cueTime, clusterOffsetInFile, blockNumWithinCluster);
00375     needToReviseBalanceOfParent = True;
00376   } else if (cueTime == root->fCueTime) {
00377     // Replace existing data:
00378     root->fClusterOffsetInFile = clusterOffsetInFile;
00379     root->fBlockNumWithinCluster = blockNumWithinCluster - 1;
00380   } else {
00381     // Add to our left or right subtree:
00382     int direction = cueTime > root->fCueTime; // 0 (left) or 1 (right)
00383     Boolean needToReviseOurBalance = False;
00384     addCuePoint(root->fSubTree[direction], cueTime, clusterOffsetInFile, blockNumWithinCluster, needToReviseOurBalance);
00385 
00386     if (needToReviseOurBalance) {
00387       // We need to change our 'balance' number, perhaps while also performing a rotation to bring ourself back into balance:
00388       if (root->fBalance == 0) {
00389         // We were balanced before, but now we're unbalanced (by 1) on the "direction" side:
00390         root->fBalance = -1 + 2*direction; // -1 for "direction" 0; 1 for "direction" 1
00391         needToReviseBalanceOfParent = True;
00392       } else if (root->fBalance == 1 - 2*direction) { // 1 for "direction" 0; -1 for "direction" 1
00393         // We were unbalanced (by 1) on the side opposite to where we added an entry, so now we're balanced:
00394         root->fBalance = 0;
00395       } else {
00396         // We were unbalanced (by 1) on the side where we added an entry, so now we're unbalanced by 2, and have to rebalance:
00397         if (root->fSubTree[direction]->fBalance == -1 + 2*direction) { // -1 for "direction" 0; 1 for "direction" 1
00398           // We're 'doubly-unbalanced' on this side, so perform a single rotation in the opposite direction:
00399           root->fBalance = root->fSubTree[direction]->fBalance = 0;
00400           rotate(1-direction, root);
00401         } else {
00402           // This is the Left-Right case (for "direction" 0) or the Right-Left case (for "direction" 1); perform two rotations:
00403           char newParentCurBalance = root->fSubTree[direction]->fSubTree[1-direction]->fBalance;
00404           if (newParentCurBalance == 1 - 2*direction) { // 1 for "direction" 0; -1 for "direction" 1
00405             root->fBalance = 0;
00406             root->fSubTree[direction]->fBalance = -1 + 2*direction; // -1 for "direction" 0; 1 for "direction" 1
00407           } else if (newParentCurBalance == 0) {
00408             root->fBalance = 0;
00409             root->fSubTree[direction]->fBalance = 0;
00410           } else {
00411             root->fBalance = 1 - 2*direction; // 1 for "direction" 0; -1 for "direction" 1
00412             root->fSubTree[direction]->fBalance = 0;
00413           }
00414           rotate(direction, root->fSubTree[direction]);
00415 
00416           root->fSubTree[direction]->fBalance = 0; // the new root will be balanced
00417           rotate(1-direction, root);
00418         }
00419       }
00420     }
00421   }
00422 }
00423 
00424 Boolean CuePoint::lookup(double& cueTime, u_int64_t& resultClusterOffsetInFile, unsigned& resultBlockNumWithinCluster) {
00425   if (cueTime < fCueTime) {
00426     if (left() == NULL) {
00427       resultClusterOffsetInFile = 0;
00428       resultBlockNumWithinCluster = 0;
00429       return False;
00430     } else {
00431       return left()->lookup(cueTime, resultClusterOffsetInFile, resultBlockNumWithinCluster);
00432     }
00433   } else {
00434     if (right() == NULL || !right()->lookup(cueTime, resultClusterOffsetInFile, resultBlockNumWithinCluster)) {
00435       // Use this record:
00436       cueTime = fCueTime;
00437       resultClusterOffsetInFile = fClusterOffsetInFile;
00438       resultBlockNumWithinCluster = fBlockNumWithinCluster;
00439     }
00440     return True;
00441   }
00442 }
00443 
00444 void CuePoint::fprintf(FILE* fid, CuePoint* cuePoint) {
00445   if (cuePoint != NULL) {
00446     ::fprintf(fid, "[");
00447     fprintf(fid, cuePoint->left());
00448 
00449     ::fprintf(fid, ",%.1f{%d},", cuePoint->fCueTime, cuePoint->fBalance);
00450 
00451     fprintf(fid, cuePoint->right());
00452     ::fprintf(fid, "]");
00453   }
00454 }
00455 
00456 void CuePoint::rotate(unsigned direction/*0 => left; 1 => right*/, CuePoint*& root) {
00457   CuePoint* pivot = root->fSubTree[1-direction]; // ASSERT: pivot != NULL
00458   root->fSubTree[1-direction] = pivot->fSubTree[direction];
00459   pivot->fSubTree[direction] = root;
00460   root = pivot;
00461 }

Generated on Tue Jun 18 13:16:51 2013 for live by  doxygen 1.5.2