1/**********
2This library is free software; you can redistribute it and/or modify it under
3the terms of the GNU Lesser General Public License as published by the
4Free Software Foundation; either version 3 of the License, or (at your
5option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
6
7This library is distributed in the hope that it will be useful, but WITHOUT
8ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
10more details.
11
12You should have received a copy of the GNU Lesser General Public License
13along with this library; if not, write to the Free Software Foundation, Inc.,
1451 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
27MPEG2TransportStreamIndexFile
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
43MPEG2TransportStreamIndexFile* MPEG2TransportStreamIndexFile
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
58MPEG2TransportStreamIndexFile::~MPEG2TransportStreamIndexFile() {
59 closeFid();
60 delete[] fFileName;
61}
62
63void MPEG2TransportStreamIndexFile
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
128void MPEG2TransportStreamIndexFile
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
198Boolean MPEG2TransportStreamIndexFile
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
212float MPEG2TransportStreamIndexFile::getPlayingDuration() {
213 if (fNumIndexRecords == 0 || !readOneIndexRecord(fNumIndexRecords-1)) return 0.0f;
214
215 return pcrFromBuf();
216}
217
218int 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
228Boolean 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
238Boolean 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
248Boolean 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
260Boolean MPEG2TransportStreamIndexFile::readOneIndexRecord(unsigned long indexRecordNum) {
261 Boolean result = readIndexRecord(indexRecordNum);
262 closeFid();
263
264 return result;
265}
266
267void MPEG2TransportStreamIndexFile::closeFid() {
268 if (fFid != NULL) {
269 CloseInputFile(fFid);
270 fFid = NULL;
271 }
272}
273
274float 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
280unsigned long MPEG2TransportStreamIndexFile::tsPacketNumFromBuf() {
281 return (fBuf[10]<<24) | (fBuf[9]<<16) | (fBuf[8]<<8) | fBuf[7];
282}
283
284void 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
295Boolean 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