1 /**********
2 This library is free software; you can redistribute it and/or modify it under
3 the terms of the GNU Lesser General Public License as published by the
4 Free Software Foundation; either version 3 of the License, or (at your
5 option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
6 
7 This library is distributed in the hope that it will be useful, but WITHOUT
8 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9 FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
10 more details.
11 
12 You should have received a copy of the GNU Lesser General Public License
13 along with this library; if not, write to the Free Software Foundation, Inc.,
14 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
15 **********/
16 // "liveMedia"
17 // Copyright (c) 1996-2020 Live Networks, Inc.  All rights reserved.
18 // A class that encapsulates MPEG-2 Transport Stream 'index files'/
19 // These index files are used to implement 'trick play' operations
20 // (seek-by-time, fast forward, reverse play) on Transport Stream files.
21 //
22 // Implementation
23 
24 #include "MPEG2TransportStreamIndexFile.hh"
25 #include "InputFile.hh"
26 
27 MPEG2TransportStreamIndexFile
MPEG2TransportStreamIndexFile(UsageEnvironment & env,char const * indexFileName)28 ::MPEG2TransportStreamIndexFile(UsageEnvironment& env, char const* indexFileName)
29   : Medium(env),
30     fFileName(strDup(indexFileName)), fFid(NULL), fMPEGVersion(0), fCurrentIndexRecordNum(0),
31     fCachedPCR(0.0f), fCachedTSPacketNumber(0), fNumIndexRecords(0) {
32   // Get the file size, to determine how many index records it contains:
33   u_int64_t indexFileSize = GetFileSize(indexFileName, NULL);
34   if (indexFileSize % INDEX_RECORD_SIZE != 0) {
35     env << "Warning: Size of the index file \"" << indexFileName
36  	<< "\" (" << (unsigned)indexFileSize
37 	<< ") is not a multiple of the index record size ("
38 	<< INDEX_RECORD_SIZE << ")\n";
39   }
40   fNumIndexRecords = (unsigned long)(indexFileSize/INDEX_RECORD_SIZE);
41 }
42 
43 MPEG2TransportStreamIndexFile* MPEG2TransportStreamIndexFile
createNew(UsageEnvironment & env,char const * indexFileName)44 ::createNew(UsageEnvironment& env, char const* indexFileName) {
45   if (indexFileName == NULL) return NULL;
46   MPEG2TransportStreamIndexFile* indexFile
47     = new MPEG2TransportStreamIndexFile(env, indexFileName);
48 
49   // Reject empty or non-existent index files:
50   if (indexFile->getPlayingDuration() == 0.0f) {
51     delete indexFile;
52     indexFile = NULL;
53   }
54 
55   return indexFile;
56 }
57 
~MPEG2TransportStreamIndexFile()58 MPEG2TransportStreamIndexFile::~MPEG2TransportStreamIndexFile() {
59   closeFid();
60   delete[] fFileName;
61 }
62 
63 void MPEG2TransportStreamIndexFile
lookupTSPacketNumFromNPT(float & npt,unsigned long & tsPacketNumber,unsigned long & indexRecordNumber)64 ::lookupTSPacketNumFromNPT(float& npt, unsigned long& tsPacketNumber,
65 			   unsigned long& indexRecordNumber) {
66   if (npt <= 0.0 || fNumIndexRecords == 0) { // Fast-track a common case:
67     npt = 0.0f;
68     tsPacketNumber = indexRecordNumber = 0;
69     return;
70   }
71 
72   // If "npt" is the same as the one that we last looked up, return its cached result:
73   if (npt == fCachedPCR) {
74     tsPacketNumber = fCachedTSPacketNumber;
75     indexRecordNumber = fCachedIndexRecordNumber;
76     return;
77   }
78 
79   // Search for the pair of neighboring index records whose PCR values span "npt".
80   // Use the 'regula-falsi' method.
81   Boolean success = False;
82   unsigned long ixFound = 0;
83   do {
84     unsigned long ixLeft = 0, ixRight = fNumIndexRecords-1;
85     float pcrLeft = 0.0f, pcrRight;
86     if (!readIndexRecord(ixRight)) break;
87     pcrRight = pcrFromBuf();
88     if (npt > pcrRight) npt = pcrRight;
89         // handle "npt" too large by seeking to the last frame of the file
90 
91     while (ixRight-ixLeft > 1 && pcrLeft < npt && npt <= pcrRight) {
92       unsigned long ixNew = ixLeft
93 	+ (unsigned long)(((npt-pcrLeft)/(pcrRight-pcrLeft))*(ixRight-ixLeft));
94       if (ixNew == ixLeft || ixNew == ixRight) {
95 	// use bisection instead:
96 	ixNew = (ixLeft+ixRight)/2;
97       }
98       if (!readIndexRecord(ixNew)) break;
99       float pcrNew = pcrFromBuf();
100       if (pcrNew < npt) {
101 	pcrLeft = pcrNew;
102 	ixLeft = ixNew;
103       } else {
104 	pcrRight = pcrNew;
105 	ixRight = ixNew;
106       }
107     }
108     if (ixRight-ixLeft > 1 || npt <= pcrLeft || npt > pcrRight) break; // bad PCR values in index file?
109 
110     ixFound = ixRight;
111     // "Rewind' until we reach the start of a Video Sequence or GOP header:
112     success = rewindToCleanPoint(ixFound);
113   } while (0);
114 
115   if (success && readIndexRecord(ixFound)) {
116     // Return (and cache) information from record "ixFound":
117     npt = fCachedPCR = pcrFromBuf();
118     tsPacketNumber = fCachedTSPacketNumber = tsPacketNumFromBuf();
119     indexRecordNumber = fCachedIndexRecordNumber = ixFound;
120   } else {
121     // An error occurred: Return the default values, for npt == 0:
122     npt = 0.0f;
123     tsPacketNumber = indexRecordNumber = 0;
124   }
125   closeFid();
126 }
127 
128 void MPEG2TransportStreamIndexFile
lookupPCRFromTSPacketNum(unsigned long & tsPacketNumber,Boolean reverseToPreviousCleanPoint,float & pcr,unsigned long & indexRecordNumber)129 ::lookupPCRFromTSPacketNum(unsigned long& tsPacketNumber, Boolean reverseToPreviousCleanPoint,
130 			   float& pcr, unsigned long& indexRecordNumber) {
131   if (tsPacketNumber == 0 || fNumIndexRecords == 0) { // Fast-track a common case:
132     pcr = 0.0f;
133     indexRecordNumber = 0;
134     return;
135   }
136 
137   // If "tsPacketNumber" is the same as the one that we last looked up, return its cached result:
138   if (tsPacketNumber == fCachedTSPacketNumber) {
139     pcr = fCachedPCR;
140     indexRecordNumber = fCachedIndexRecordNumber;
141     return;
142   }
143 
144   // Search for the pair of neighboring index records whose TS packet #s span "tsPacketNumber".
145   // Use the 'regula-falsi' method.
146   Boolean success = False;
147   unsigned long ixFound = 0;
148   do {
149     unsigned long ixLeft = 0, ixRight = fNumIndexRecords-1;
150     unsigned long tsLeft = 0, tsRight;
151     if (!readIndexRecord(ixRight)) break;
152     tsRight = tsPacketNumFromBuf();
153     if (tsPacketNumber > tsRight) tsPacketNumber = tsRight;
154         // handle "tsPacketNumber" too large by seeking to the last frame of the file
155 
156     while (ixRight-ixLeft > 1 && tsLeft < tsPacketNumber && tsPacketNumber <= tsRight) {
157       unsigned long ixNew = ixLeft
158 	+ (unsigned long)(((tsPacketNumber-tsLeft)/(tsRight-tsLeft))*(ixRight-ixLeft));
159       if (ixNew == ixLeft || ixNew == ixRight) {
160 	// Use bisection instead:
161 	ixNew = (ixLeft+ixRight)/2;
162       }
163       if (!readIndexRecord(ixNew)) break;
164       unsigned long tsNew = tsPacketNumFromBuf();
165       if (tsNew < tsPacketNumber) {
166 	tsLeft = tsNew;
167 	ixLeft = ixNew;
168       } else {
169 	tsRight = tsNew;
170 	ixRight = ixNew;
171       }
172     }
173     if (ixRight-ixLeft > 1 || tsPacketNumber <= tsLeft || tsPacketNumber > tsRight) break; // bad PCR values in index file?
174 
175     ixFound = ixRight;
176     if (reverseToPreviousCleanPoint) {
177       // "Rewind' until we reach the start of a Video Sequence or GOP header:
178       success = rewindToCleanPoint(ixFound);
179     } else {
180       success = True;
181     }
182   } while (0);
183 
184   if (success && readIndexRecord(ixFound)) {
185     // Return (and cache) information from record "ixFound":
186     pcr = fCachedPCR = pcrFromBuf();
187     fCachedTSPacketNumber = tsPacketNumFromBuf();
188     if (reverseToPreviousCleanPoint) tsPacketNumber = fCachedTSPacketNumber;
189     indexRecordNumber = fCachedIndexRecordNumber = ixFound;
190   } else {
191     // An error occurred: Return the default values, for tsPacketNumber == 0:
192     pcr = 0.0f;
193     indexRecordNumber = 0;
194   }
195   closeFid();
196 }
197 
198 Boolean MPEG2TransportStreamIndexFile
readIndexRecordValues(unsigned long indexRecordNum,unsigned long & transportPacketNum,u_int8_t & offset,u_int8_t & size,float & pcr,u_int8_t & recordType)199 ::readIndexRecordValues(unsigned long indexRecordNum,
200 			unsigned long& transportPacketNum, u_int8_t& offset,
201 			u_int8_t& size, float& pcr, u_int8_t& recordType) {
202   if (!readIndexRecord(indexRecordNum)) return False;
203 
204   transportPacketNum = tsPacketNumFromBuf();
205   offset = offsetFromBuf();
206   size = sizeFromBuf();
207   pcr = pcrFromBuf();
208   recordType = recordTypeFromBuf();
209   return True;
210 }
211 
getPlayingDuration()212 float MPEG2TransportStreamIndexFile::getPlayingDuration() {
213   if (fNumIndexRecords == 0 || !readOneIndexRecord(fNumIndexRecords-1)) return 0.0f;
214 
215   return pcrFromBuf();
216 }
217 
mpegVersion()218 int MPEG2TransportStreamIndexFile::mpegVersion() {
219   if (fMPEGVersion != 0) return fMPEGVersion; // we already know it
220 
221   // Read the first index record, and figure out the MPEG version from its type:
222   if (!readOneIndexRecord(0)) return 0; // unknown; perhaps the indecx file is empty?
223 
224   setMPEGVersionFromRecordType(recordTypeFromBuf());
225   return fMPEGVersion;
226 }
227 
openFid()228 Boolean MPEG2TransportStreamIndexFile::openFid() {
229   if (fFid == NULL && fFileName != NULL) {
230     if ((fFid = OpenInputFile(envir(), fFileName)) != NULL) {
231       fCurrentIndexRecordNum = 0;
232     }
233   }
234 
235   return fFid != NULL;
236 }
237 
seekToIndexRecord(unsigned long indexRecordNumber)238 Boolean MPEG2TransportStreamIndexFile::seekToIndexRecord(unsigned long indexRecordNumber) {
239   if (!openFid()) return False;
240 
241   if (indexRecordNumber == fCurrentIndexRecordNum) return True; // we're already there
242 
243   if (SeekFile64(fFid, (int64_t)(indexRecordNumber*INDEX_RECORD_SIZE), SEEK_SET) != 0) return False;
244   fCurrentIndexRecordNum = indexRecordNumber;
245   return True;
246 }
247 
readIndexRecord(unsigned long indexRecordNum)248 Boolean MPEG2TransportStreamIndexFile::readIndexRecord(unsigned long indexRecordNum) {
249   do {
250     if (!seekToIndexRecord(indexRecordNum)) break;
251     if (fread(fBuf, INDEX_RECORD_SIZE, 1, fFid) != 1) break;
252     ++fCurrentIndexRecordNum;
253 
254     return True;
255   } while (0);
256 
257   return False; // an error occurred
258 }
259 
readOneIndexRecord(unsigned long indexRecordNum)260 Boolean MPEG2TransportStreamIndexFile::readOneIndexRecord(unsigned long indexRecordNum) {
261   Boolean result = readIndexRecord(indexRecordNum);
262   closeFid();
263 
264   return result;
265 }
266 
closeFid()267 void MPEG2TransportStreamIndexFile::closeFid() {
268   if (fFid != NULL) {
269     CloseInputFile(fFid);
270     fFid = NULL;
271   }
272 }
273 
pcrFromBuf()274 float MPEG2TransportStreamIndexFile::pcrFromBuf() {
275   unsigned pcr_int = (fBuf[5]<<16) | (fBuf[4]<<8) | fBuf[3];
276   u_int8_t pcr_frac = fBuf[6];
277   return pcr_int + pcr_frac/256.0f;
278 }
279 
tsPacketNumFromBuf()280 unsigned long MPEG2TransportStreamIndexFile::tsPacketNumFromBuf() {
281   return (fBuf[10]<<24) | (fBuf[9]<<16) | (fBuf[8]<<8) | fBuf[7];
282 }
283 
setMPEGVersionFromRecordType(u_int8_t recordType)284 void MPEG2TransportStreamIndexFile::setMPEGVersionFromRecordType(u_int8_t recordType) {
285   if (fMPEGVersion != 0) return; // we already know it
286 
287   u_int8_t const recordTypeWithoutStartBit = recordType&~0x80;
288   if (recordTypeWithoutStartBit >= 1 && recordTypeWithoutStartBit <= 4) fMPEGVersion = 2;
289   else if (recordTypeWithoutStartBit >= 5 && recordTypeWithoutStartBit <= 10) fMPEGVersion = 5;
290       // represents H.264
291   else if (recordTypeWithoutStartBit >= 11 && recordTypeWithoutStartBit <= 16) fMPEGVersion = 6;
292       // represents H.265
293 }
294 
rewindToCleanPoint(unsigned long & ixFound)295 Boolean MPEG2TransportStreamIndexFile::rewindToCleanPoint(unsigned long&ixFound) {
296   Boolean success = False; // until we learn otherwise
297 
298   while (ixFound > 0) {
299     if (!readIndexRecord(ixFound)) break;
300 
301     u_int8_t recordType = recordTypeFromBuf();
302     setMPEGVersionFromRecordType(recordType);
303 
304     // A 'clean point' is the start of a 'frame' from which a decoder can cleanly resume
305     // handling the stream.  For H.264, this is a SPS.  For H.265, this is a VPS.
306     // For MPEG-2, this is a Video Sequence Header, or a GOP.
307 
308     if ((recordType&0x80) != 0) { // This is the start of a 'frame'
309       recordType &=~ 0x80; // remove the 'start of frame' bit
310       if (fMPEGVersion == 5) { // H.264
311         if (recordType == 5/*SPS*/) {
312 	  success = True;
313 	  break;
314 	}
315       } else if (fMPEGVersion == 6) { // H.265
316         if (recordType == 11/*VPS*/) {
317 	  success = True;
318 	  break;
319 	}
320       } else { // MPEG-1, 2, or 4
321 	if (recordType == 1/*VSH*/) {
322 	  success = True;
323 	  break;
324 	} else if (recordType == 2/*GOP*/) {
325 	  // Hack: If the preceding record is for a Video Sequence Header, then use it instead:
326 	  unsigned long newIxFound = ixFound;
327 
328 	  while (--newIxFound > 0) {
329 	    if (!readIndexRecord(newIxFound)) break;
330 	    recordType = recordTypeFromBuf();
331 	    if ((recordType&0x7F) != 1) break; // not a Video Sequence Header
332 	    if ((recordType&0x80) != 0) { // this is the start of the VSH; use it
333 	      ixFound = newIxFound;
334 	      break;
335 	    }
336 	  }
337         }
338         success = True;
339         break;
340       }
341     }
342 
343     // Keep checking, from the previous record:
344     --ixFound;
345   }
346   if (ixFound == 0) success = True; // use record 0 anyway
347 
348   return success;
349 }
350