liveMedia/MPEG2TransportStreamIndexFile.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-2014 Live Networks, Inc.  All rights reserved.
00018 // A class that encapsulates MPEG-2 Transport Stream 'index files'/
00019 // These index files are used to implement 'trick play' operations
00020 // (seek-by-time, fast forward, reverse play) on Transport Stream files.
00021 //
00022 // Implementation
00023 
00024 #include "MPEG2TransportStreamIndexFile.hh"
00025 #include "InputFile.hh"
00026 
00027 MPEG2TransportStreamIndexFile
00028 ::MPEG2TransportStreamIndexFile(UsageEnvironment& env, char const* indexFileName)
00029   : Medium(env),
00030     fFileName(strDup(indexFileName)), fFid(NULL), fMPEGVersion(0), fCurrentIndexRecordNum(0),
00031     fCachedPCR(0.0f), fCachedTSPacketNumber(0), fNumIndexRecords(0) {
00032   // Get the file size, to determine how many index records it contains:
00033   u_int64_t indexFileSize = GetFileSize(indexFileName, NULL);
00034   if (indexFileSize % INDEX_RECORD_SIZE != 0) {
00035     env << "Warning: Size of the index file \"" << indexFileName
00036         << "\" (" << (unsigned)indexFileSize
00037         << ") is not a multiple of the index record size ("
00038         << INDEX_RECORD_SIZE << ")\n";
00039   }
00040   fNumIndexRecords = (unsigned long)(indexFileSize/INDEX_RECORD_SIZE);
00041 }
00042 
00043 MPEG2TransportStreamIndexFile* MPEG2TransportStreamIndexFile
00044 ::createNew(UsageEnvironment& env, char const* indexFileName) {
00045   if (indexFileName == NULL) return NULL;
00046   MPEG2TransportStreamIndexFile* indexFile
00047     = new MPEG2TransportStreamIndexFile(env, indexFileName);
00048 
00049   // Reject empty or non-existent index files:
00050   if (indexFile->getPlayingDuration() == 0.0f) {
00051     delete indexFile;
00052     indexFile = NULL;
00053   }
00054 
00055   return indexFile;
00056 }
00057 
00058 MPEG2TransportStreamIndexFile::~MPEG2TransportStreamIndexFile() {
00059   closeFid();
00060   delete[] fFileName;
00061 }
00062 
00063 void MPEG2TransportStreamIndexFile
00064 ::lookupTSPacketNumFromNPT(float& npt, unsigned long& tsPacketNumber,
00065                            unsigned long& indexRecordNumber) {
00066   if (npt <= 0.0 || fNumIndexRecords == 0) { // Fast-track a common case:
00067     npt = 0.0f;
00068     tsPacketNumber = indexRecordNumber = 0;
00069     return;
00070   }
00071 
00072   // If "npt" is the same as the one that we last looked up, return its cached result:
00073   if (npt == fCachedPCR) {
00074     tsPacketNumber = fCachedTSPacketNumber;
00075     indexRecordNumber = fCachedIndexRecordNumber;
00076     return;
00077   }
00078 
00079   // Search for the pair of neighboring index records whose PCR values span "npt".
00080   // Use the 'regula-falsi' method.
00081   Boolean success = False;
00082   unsigned long ixFound = 0;
00083   do {
00084     unsigned long ixLeft = 0, ixRight = fNumIndexRecords-1;
00085     float pcrLeft = 0.0f, pcrRight;
00086     if (!readIndexRecord(ixRight)) break;
00087     pcrRight = pcrFromBuf();
00088     if (npt > pcrRight) npt = pcrRight;
00089         // handle "npt" too large by seeking to the last frame of the file
00090 
00091     while (ixRight-ixLeft > 1 && pcrLeft < npt && npt <= pcrRight) {
00092       unsigned long ixNew = ixLeft
00093         + (unsigned long)(((npt-pcrLeft)/(pcrRight-pcrLeft))*(ixRight-ixLeft));
00094       if (ixNew == ixLeft || ixNew == ixRight) {
00095         // use bisection instead:
00096         ixNew = (ixLeft+ixRight)/2;
00097       }
00098       if (!readIndexRecord(ixNew)) break;
00099       float pcrNew = pcrFromBuf();
00100       if (pcrNew < npt) {
00101         pcrLeft = pcrNew;
00102         ixLeft = ixNew;
00103       } else {
00104         pcrRight = pcrNew;
00105         ixRight = ixNew;
00106       }
00107     }
00108     if (ixRight-ixLeft > 1 || npt <= pcrLeft || npt > pcrRight) break; // bad PCR values in index file?
00109 
00110     ixFound = ixRight;
00111     // "Rewind' until we reach the start of a Video Sequence or GOP header:
00112     success = rewindToCleanPoint(ixFound);
00113   } while (0);
00114 
00115   if (success && readIndexRecord(ixFound)) {
00116     // Return (and cache) information from record "ixFound":
00117     npt = fCachedPCR = pcrFromBuf();
00118     tsPacketNumber = fCachedTSPacketNumber = tsPacketNumFromBuf();
00119     indexRecordNumber = fCachedIndexRecordNumber = ixFound;
00120   } else {
00121     // An error occurred: Return the default values, for npt == 0:
00122     npt = 0.0f;
00123     tsPacketNumber = indexRecordNumber = 0;
00124   }
00125   closeFid();
00126 }
00127 
00128 void MPEG2TransportStreamIndexFile
00129 ::lookupPCRFromTSPacketNum(unsigned long& tsPacketNumber, Boolean reverseToPreviousCleanPoint,
00130                            float& pcr, unsigned long& indexRecordNumber) {
00131   if (tsPacketNumber == 0 || fNumIndexRecords == 0) { // Fast-track a common case:
00132     pcr = 0.0f;
00133     indexRecordNumber = 0;
00134     return;
00135   }
00136 
00137   // If "tsPacketNumber" is the same as the one that we last looked up, return its cached result:
00138   if (tsPacketNumber == fCachedTSPacketNumber) {
00139     pcr = fCachedPCR;
00140     indexRecordNumber = fCachedIndexRecordNumber;
00141     return;
00142   }
00143 
00144   // Search for the pair of neighboring index records whose TS packet #s span "tsPacketNumber".
00145   // Use the 'regula-falsi' method.
00146   Boolean success = False;
00147   unsigned long ixFound = 0;
00148   do {
00149     unsigned long ixLeft = 0, ixRight = fNumIndexRecords-1;
00150     unsigned long tsLeft = 0, tsRight;
00151     if (!readIndexRecord(ixRight)) break;
00152     tsRight = tsPacketNumFromBuf();
00153     if (tsPacketNumber > tsRight) tsPacketNumber = tsRight;
00154         // handle "tsPacketNumber" too large by seeking to the last frame of the file
00155 
00156     while (ixRight-ixLeft > 1 && tsLeft < tsPacketNumber && tsPacketNumber <= tsRight) {
00157       unsigned long ixNew = ixLeft
00158         + (unsigned long)(((tsPacketNumber-tsLeft)/(tsRight-tsLeft))*(ixRight-ixLeft));
00159       if (ixNew == ixLeft || ixNew == ixRight) {
00160         // Use bisection instead:
00161         ixNew = (ixLeft+ixRight)/2;
00162       }
00163       if (!readIndexRecord(ixNew)) break;
00164       unsigned long tsNew = tsPacketNumFromBuf();
00165       if (tsNew < tsPacketNumber) {
00166         tsLeft = tsNew;
00167         ixLeft = ixNew;
00168       } else {
00169         tsRight = tsNew;
00170         ixRight = ixNew;
00171       }
00172     }
00173     if (ixRight-ixLeft > 1 || tsPacketNumber <= tsLeft || tsPacketNumber > tsRight) break; // bad PCR values in index file?
00174 
00175     ixFound = ixRight;
00176     if (reverseToPreviousCleanPoint) {
00177       // "Rewind' until we reach the start of a Video Sequence or GOP header:
00178       success = rewindToCleanPoint(ixFound);
00179     } else {
00180       success = True;
00181     }
00182   } while (0);
00183 
00184   if (success && readIndexRecord(ixFound)) {
00185     // Return (and cache) information from record "ixFound":
00186     pcr = fCachedPCR = pcrFromBuf();
00187     fCachedTSPacketNumber = tsPacketNumFromBuf();
00188     if (reverseToPreviousCleanPoint) tsPacketNumber = fCachedTSPacketNumber;
00189     indexRecordNumber = fCachedIndexRecordNumber = ixFound;
00190   } else {
00191     // An error occurred: Return the default values, for tsPacketNumber == 0:
00192     pcr = 0.0f;
00193     indexRecordNumber = 0;
00194   }
00195   closeFid();
00196 }
00197 
00198 Boolean MPEG2TransportStreamIndexFile
00199 ::readIndexRecordValues(unsigned long indexRecordNum,
00200                         unsigned long& transportPacketNum, u_int8_t& offset,
00201                         u_int8_t& size, float& pcr, u_int8_t& recordType) {
00202   if (!readIndexRecord(indexRecordNum)) return False;
00203 
00204   transportPacketNum = tsPacketNumFromBuf();
00205   offset = offsetFromBuf();
00206   size = sizeFromBuf();
00207   pcr = pcrFromBuf();
00208   recordType = recordTypeFromBuf();
00209   return True;
00210 }
00211 
00212 float MPEG2TransportStreamIndexFile::getPlayingDuration() {
00213   if (fNumIndexRecords == 0 || !readOneIndexRecord(fNumIndexRecords-1)) return 0.0f;
00214 
00215   return pcrFromBuf();
00216 }
00217 
00218 int MPEG2TransportStreamIndexFile::mpegVersion() {
00219   if (fMPEGVersion != 0) return fMPEGVersion; // we already know it
00220 
00221   // Read the first index record, and figure out the MPEG version from its type:
00222   if (!readOneIndexRecord(0)) return 0; // unknown; perhaps the indecx file is empty?   
00223 
00224   setMPEGVersionFromRecordType(recordTypeFromBuf());
00225   return fMPEGVersion;
00226 }
00227 
00228 Boolean MPEG2TransportStreamIndexFile::openFid() {
00229   if (fFid == NULL && fFileName != NULL) {
00230     if ((fFid = OpenInputFile(envir(), fFileName)) != NULL) {
00231       fCurrentIndexRecordNum = 0;
00232     }
00233   }
00234 
00235   return fFid != NULL;
00236 }
00237 
00238 Boolean MPEG2TransportStreamIndexFile::seekToIndexRecord(unsigned long indexRecordNumber) {
00239   if (!openFid()) return False;
00240 
00241   if (indexRecordNumber == fCurrentIndexRecordNum) return True; // we're already there
00242 
00243   if (SeekFile64(fFid, (int64_t)(indexRecordNumber*INDEX_RECORD_SIZE), SEEK_SET) != 0) return False;
00244   fCurrentIndexRecordNum = indexRecordNumber;
00245   return True;
00246 }
00247 
00248 Boolean MPEG2TransportStreamIndexFile::readIndexRecord(unsigned long indexRecordNum) {
00249   do {
00250     if (!seekToIndexRecord(indexRecordNum)) break;
00251     if (fread(fBuf, INDEX_RECORD_SIZE, 1, fFid) != 1) break;
00252     ++fCurrentIndexRecordNum;
00253 
00254     return True;
00255   } while (0);
00256 
00257   return False; // an error occurred
00258 }
00259 
00260 Boolean MPEG2TransportStreamIndexFile::readOneIndexRecord(unsigned long indexRecordNum) {
00261   Boolean result = readIndexRecord(indexRecordNum);
00262   closeFid();
00263 
00264   return result;
00265 }
00266 
00267 void MPEG2TransportStreamIndexFile::closeFid() {
00268   if (fFid != NULL) {
00269     CloseInputFile(fFid);
00270     fFid = NULL;
00271   }
00272 }
00273 
00274 float MPEG2TransportStreamIndexFile::pcrFromBuf() {
00275   unsigned pcr_int = (fBuf[5]<<16) | (fBuf[4]<<8) | fBuf[3];
00276   u_int8_t pcr_frac = fBuf[6];
00277   return pcr_int + pcr_frac/256.0f;
00278 }
00279 
00280 unsigned long MPEG2TransportStreamIndexFile::tsPacketNumFromBuf() {
00281   return (fBuf[10]<<24) | (fBuf[9]<<16) | (fBuf[8]<<8) | fBuf[7];
00282 }
00283 
00284 void MPEG2TransportStreamIndexFile::setMPEGVersionFromRecordType(u_int8_t recordType) {
00285   if (fMPEGVersion != 0) return; // we already know it
00286  
00287   u_int8_t const recordTypeWithoutStartBit = recordType&~0x80;
00288   if (recordTypeWithoutStartBit >= 1 && recordTypeWithoutStartBit <= 4) fMPEGVersion = 2;
00289   else if (recordTypeWithoutStartBit >= 5 && recordTypeWithoutStartBit <= 10) fMPEGVersion = 5;
00290       // represents H.264
00291   else if (recordTypeWithoutStartBit >= 11 && recordTypeWithoutStartBit <= 16) fMPEGVersion = 6;
00292       // represents H.265
00293 }
00294 
00295 Boolean MPEG2TransportStreamIndexFile::rewindToCleanPoint(unsigned long&ixFound) {
00296   Boolean success = False; // until we learn otherwise
00297 
00298   while (ixFound > 0) {
00299     if (!readIndexRecord(ixFound)) break;
00300 
00301     u_int8_t recordType = recordTypeFromBuf();
00302     setMPEGVersionFromRecordType(recordType);
00303 
00304     // A 'clean point' is the start of a 'frame' from which a decoder can cleanly resume
00305     // handling the stream.  For H.264, this is a SPS.  For H.265, this is a VPS.
00306     // For MPEG-2, this is a Video Sequence Header, or a GOP. 
00307 
00308     if ((recordType&0x80) != 0) { // This is the start of a 'frame'
00309       recordType &=~ 0x80; // remove the 'start of frame' bit
00310       if (fMPEGVersion == 5) { // H.264
00311         if (recordType == 5/*SPS*/) {
00312           success = True;
00313           break;
00314         }
00315       } else if (fMPEGVersion == 6) { // H.265
00316         if (recordType == 11/*VPS*/) {
00317           success = True;
00318           break;
00319         }
00320       } else { // MPEG-1, 2, or 4
00321         if (recordType == 1/*VSH*/) {
00322           success = True;
00323           break;
00324         } else if (recordType == 2/*GOP*/) {
00325           // Hack: If the preceding record is for a Video Sequence Header, then use it instead:
00326           unsigned long newIxFound = ixFound;
00327 
00328           while (--newIxFound > 0) {
00329             if (!readIndexRecord(newIxFound)) break;
00330             recordType = recordTypeFromBuf();
00331             if ((recordType&0x7F) != 1) break; // not a Video Sequence Header
00332             if ((recordType&0x80) != 0) { // this is the start of the VSH; use it
00333               ixFound = newIxFound;
00334               break;
00335             }
00336           }
00337         }
00338         success = True;
00339         break;
00340       }
00341     }
00342 
00343     // Keep checking, from the previous record:
00344     --ixFound;
00345   }
00346   if (ixFound == 0) success = True; // use record 0 anyway
00347 
00348   return success;
00349 }

Generated on Wed Apr 23 16:12:00 2014 for live by  doxygen 1.5.2