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 filter that produces a sequence of I-frame indices from a MPEG-2 Transport Stream
19// Implementation
20
21#include "MPEG2IndexFromTransportStream.hh"
22
23////////// IndexRecord definition //////////
24
25enum RecordType {
26 RECORD_UNPARSED = 0,
27 RECORD_VSH = 1, // a MPEG Video Sequence Header
28 RECORD_GOP = 2,
29 RECORD_PIC_NON_IFRAME = 3, // includes slices
30 RECORD_PIC_IFRAME = 4, // includes slices
31 RECORD_NAL_H264_SPS = 5, // H.264
32 RECORD_NAL_H264_PPS = 6, // H.264
33 RECORD_NAL_H264_SEI = 7, // H.264
34 RECORD_NAL_H264_NON_IFRAME = 8, // H.264
35 RECORD_NAL_H264_IFRAME = 9, // H.264
36 RECORD_NAL_H264_OTHER = 10, // H.264
37 RECORD_NAL_H265_VPS = 11, // H.265
38 RECORD_NAL_H265_SPS = 12, // H.265
39 RECORD_NAL_H265_PPS = 13, // H.265
40 RECORD_NAL_H265_NON_IFRAME = 14, // H.265
41 RECORD_NAL_H265_IFRAME = 15, // H.265
42 RECORD_NAL_H265_OTHER = 16, // H.265
43 RECORD_JUNK
44};
45
46class IndexRecord {
47public:
48 IndexRecord(u_int8_t startOffset, u_int8_t size,
49 unsigned long transportPacketNumber, float pcr);
50 virtual ~IndexRecord();
51
52 RecordType& recordType() { return fRecordType; }
53 void setFirstFlag() { fRecordType = (RecordType)(((u_int8_t)fRecordType) | 0x80); }
54 u_int8_t startOffset() const { return fStartOffset; }
55 u_int8_t& size() { return fSize; }
56 float pcr() const { return fPCR; }
57 unsigned long transportPacketNumber() const { return fTransportPacketNumber; }
58
59 IndexRecord* next() const { return fNext; }
60 void addAfter(IndexRecord* prev);
61 void unlink();
62
63private:
64 // Index records are maintained in a doubly-linked list:
65 IndexRecord* fNext;
66 IndexRecord* fPrev;
67
68 RecordType fRecordType;
69 u_int8_t fStartOffset; // within the Transport Stream packet
70 u_int8_t fSize; // in bytes, following "fStartOffset".
71 // Note: fStartOffset + fSize <= TRANSPORT_PACKET_SIZE
72 float fPCR;
73 unsigned long fTransportPacketNumber;
74};
75
76#ifdef DEBUG
77static char const* recordTypeStr[] = {
78 "UNPARSED",
79 "VSH",
80 "GOP",
81 "PIC(non-I-frame)",
82 "PIC(I-frame)",
83 "SPS (H.264)",
84 "PPS (H.264)",
85 "SEI (H.264)",
86 "H.264 non-I-frame",
87 "H.264 I-frame",
88 "other NAL unit (H.264)",
89 "VPS (H.265)",
90 "SPS (H.265)",
91 "PPS (H.265)",
92 "H.265 non-I-frame",
93 "H.265 I-frame",
94 "other NAL unit (H.265)",
95 "JUNK"
96};
97
98UsageEnvironment& operator<<(UsageEnvironment& env, IndexRecord& r) {
99 return env << "[" << ((r.recordType()&0x80) != 0 ? "1" : "")
100 << recordTypeStr[r.recordType()&0x7F] << ":"
101 << (unsigned)r.transportPacketNumber() << ":" << r.startOffset()
102 << "(" << r.size() << ")@" << r.pcr() << "]";
103}
104#endif
105
106
107////////// MPEG2IFrameIndexFromTransportStream implementation //////////
108
109MPEG2IFrameIndexFromTransportStream*
110MPEG2IFrameIndexFromTransportStream::createNew(UsageEnvironment& env,
111 FramedSource* inputSource) {
112 return new MPEG2IFrameIndexFromTransportStream(env, inputSource);
113}
114
115// The largest expected frame size (in bytes):
116#define MAX_FRAME_SIZE 400000
117
118// Make our parse buffer twice as large as this, to ensure that at least one
119// complete frame will fit inside it:
120#define PARSE_BUFFER_SIZE (2*MAX_FRAME_SIZE)
121
122// The PID used for the PAT (as defined in the MPEG Transport Stream standard):
123#define PAT_PID 0
124
125MPEG2IFrameIndexFromTransportStream
126::MPEG2IFrameIndexFromTransportStream(UsageEnvironment& env,
127 FramedSource* inputSource)
128 : FramedFilter(env, inputSource),
129 fIsH264(False), fIsH265(False),
130 fInputTransportPacketCounter((unsigned)-1), fClosureNumber(0), fLastContinuityCounter(~0),
131 fFirstPCR(0.0), fLastPCR(0.0), fHaveSeenFirstPCR(False),
132 fPMT_PID(0x10), fVideo_PID(0xE0), // default values
133 fParseBufferSize(PARSE_BUFFER_SIZE),
134 fParseBufferFrameStart(0), fParseBufferParseEnd(4), fParseBufferDataEnd(0),
135 fHeadIndexRecord(NULL), fTailIndexRecord(NULL) {
136 fParseBuffer = new unsigned char[fParseBufferSize];
137}
138
139MPEG2IFrameIndexFromTransportStream::~MPEG2IFrameIndexFromTransportStream() {
140 delete fHeadIndexRecord;
141 delete[] fParseBuffer;
142}
143
144void MPEG2IFrameIndexFromTransportStream::doGetNextFrame() {
145 // Begin by trying to deliver an index record (for an already-parsed frame)
146 // to the client:
147 if (deliverIndexRecord()) return;
148
149 // No more index records are left to deliver, so try to parse a new frame:
150 if (parseFrame()) { // success - try again
151 doGetNextFrame();
152 return;
153 }
154
155 // We need to read some more Transport Stream packets. Check whether we have room:
156 if (fParseBufferSize - fParseBufferDataEnd < TRANSPORT_PACKET_SIZE) {
157 // There's no room left. Compact the buffer, and check again:
158 compactParseBuffer();
159 if (fParseBufferSize - fParseBufferDataEnd < TRANSPORT_PACKET_SIZE) {
160 envir() << "ERROR: parse buffer full; increase MAX_FRAME_SIZE\n";
161 // Treat this as if the input source ended:
162 handleInputClosure1();
163 return;
164 }
165 }
166
167 // Arrange to read a new Transport Stream packet:
168 fInputSource->getNextFrame(fInputBuffer, sizeof fInputBuffer,
169 afterGettingFrame, this,
170 handleInputClosure, this);
171}
172
173void MPEG2IFrameIndexFromTransportStream
174::afterGettingFrame(void* clientData, unsigned frameSize,
175 unsigned numTruncatedBytes,
176 struct timeval presentationTime,
177 unsigned durationInMicroseconds) {
178 MPEG2IFrameIndexFromTransportStream* source
179 = (MPEG2IFrameIndexFromTransportStream*)clientData;
180 source->afterGettingFrame1(frameSize, numTruncatedBytes,
181 presentationTime, durationInMicroseconds);
182}
183
184#define TRANSPORT_SYNC_BYTE 0x47
185
186void MPEG2IFrameIndexFromTransportStream
187::afterGettingFrame1(unsigned frameSize,
188 unsigned numTruncatedBytes,
189 struct timeval presentationTime,
190 unsigned durationInMicroseconds) {
191 if (frameSize < TRANSPORT_PACKET_SIZE || fInputBuffer[0] != TRANSPORT_SYNC_BYTE) {
192 if (fInputBuffer[0] != TRANSPORT_SYNC_BYTE) {
193 envir() << "Bad TS sync byte: 0x" << fInputBuffer[0] << "\n";
194 }
195 // Handle this as if the source ended:
196 handleInputClosure1();
197 return;
198 }
199
200 ++fInputTransportPacketCounter;
201
202 // Figure out how much of this Transport Packet contains PES data:
203 u_int8_t adaptation_field_control = (fInputBuffer[3]&0x30)>>4;
204 u_int8_t totalHeaderSize
205 = adaptation_field_control <= 1 ? 4 : 5 + fInputBuffer[4];
206 if ((adaptation_field_control == 2 && totalHeaderSize != TRANSPORT_PACKET_SIZE) ||
207 (adaptation_field_control == 3 && totalHeaderSize >= TRANSPORT_PACKET_SIZE)) {
208 envir() << "Bad \"adaptation_field_length\": " << fInputBuffer[4] << "\n";
209 doGetNextFrame();
210 return;
211 }
212
213 // Check for a PCR:
214 if (totalHeaderSize > 5 && (fInputBuffer[5]&0x10) != 0) {
215 // There's a PCR:
216 u_int32_t pcrBaseHigh
217 = (fInputBuffer[6]<<24)|(fInputBuffer[7]<<16)
218 |(fInputBuffer[8]<<8)|fInputBuffer[9];
219 float pcr = pcrBaseHigh/45000.0f;
220 if ((fInputBuffer[10]&0x80) != 0) pcr += 1/90000.0f; // add in low-bit (if set)
221 unsigned short pcrExt = ((fInputBuffer[10]&0x01)<<8) | fInputBuffer[11];
222 pcr += pcrExt/27000000.0f;
223
224 if (!fHaveSeenFirstPCR) {
225 fFirstPCR = pcr;
226 fHaveSeenFirstPCR = True;
227 } else if (pcr < fLastPCR) {
228 // The PCR timestamp has gone backwards. Display a warning about this
229 // (because it indicates buggy Transport Stream data), and compensate for it.
230 envir() << "\nWarning: At about " << fLastPCR-fFirstPCR
231 << " seconds into the file, the PCR timestamp decreased - from "
232 << fLastPCR << " to " << pcr << "\n";
233 fFirstPCR -= (fLastPCR - pcr);
234 }
235 fLastPCR = pcr;
236 }
237
238 // Get the PID from the packet, and check for special tables: the PAT and PMT:
239 u_int16_t PID = ((fInputBuffer[1]&0x1F)<<8) | fInputBuffer[2];
240 if (PID == PAT_PID) {
241 analyzePAT(&fInputBuffer[totalHeaderSize], TRANSPORT_PACKET_SIZE-totalHeaderSize);
242 } else if (PID == fPMT_PID) {
243 analyzePMT(&fInputBuffer[totalHeaderSize], TRANSPORT_PACKET_SIZE-totalHeaderSize);
244 }
245
246 // Ignore transport packets for non-video programs,
247 // or packets with no data, or packets that duplicate the previous packet:
248 u_int8_t continuity_counter = fInputBuffer[3]&0x0F;
249 if ((PID != fVideo_PID) ||
250 !(adaptation_field_control == 1 || adaptation_field_control == 3) ||
251 continuity_counter == fLastContinuityCounter) {
252 doGetNextFrame();
253 return;
254 }
255 fLastContinuityCounter = continuity_counter;
256
257 // Also, if this is the start of a PES packet, then skip over the PES header:
258 Boolean payload_unit_start_indicator = (fInputBuffer[1]&0x40) != 0;
259 if (payload_unit_start_indicator && totalHeaderSize < TRANSPORT_PACKET_SIZE - 8
260 && fInputBuffer[totalHeaderSize] == 0x00 && fInputBuffer[totalHeaderSize+1] == 0x00
261 && fInputBuffer[totalHeaderSize+2] == 0x01) {
262 u_int8_t PES_header_data_length = fInputBuffer[totalHeaderSize+8];
263 totalHeaderSize += 9 + PES_header_data_length;
264 if (totalHeaderSize >= TRANSPORT_PACKET_SIZE) {
265 envir() << "Unexpectedly large PES header size: " << PES_header_data_length << "\n";
266 // Handle this as if the source ended:
267 handleInputClosure1();
268 return;
269 }
270 }
271
272 // The remaining data is Video Elementary Stream data. Add it to our parse buffer:
273 unsigned vesSize = TRANSPORT_PACKET_SIZE - totalHeaderSize;
274 memmove(&fParseBuffer[fParseBufferDataEnd], &fInputBuffer[totalHeaderSize], vesSize);
275 fParseBufferDataEnd += vesSize;
276
277 // And add a new index record noting where it came from:
278 addToTail(new IndexRecord(totalHeaderSize, vesSize, fInputTransportPacketCounter,
279 fLastPCR - fFirstPCR));
280
281 // Try again:
282 doGetNextFrame();
283}
284
285void MPEG2IFrameIndexFromTransportStream::handleInputClosure(void* clientData) {
286 MPEG2IFrameIndexFromTransportStream* source
287 = (MPEG2IFrameIndexFromTransportStream*)clientData;
288 source->handleInputClosure1();
289}
290
291#define VIDEO_SEQUENCE_START_CODE 0xB3 // MPEG-1 or 2
292#define VISUAL_OBJECT_SEQUENCE_START_CODE 0xB0 // MPEG-4
293#define GROUP_START_CODE 0xB8 // MPEG-1 or 2
294#define GROUP_VOP_START_CODE 0xB3 // MPEG-4
295#define PICTURE_START_CODE 0x00 // MPEG-1 or 2
296#define VOP_START_CODE 0xB6 // MPEG-4
297
298void MPEG2IFrameIndexFromTransportStream::handleInputClosure1() {
299 if (++fClosureNumber == 1 && fParseBufferDataEnd > fParseBufferFrameStart
300 && fParseBufferDataEnd <= fParseBufferSize - 4) {
301 // This is the first time we saw EOF, and there's still data remaining to be
302 // parsed. Hack: Append a Picture Header code to the end of the unparsed
303 // data, and try again. This should use up all of the unparsed data.
304 fParseBuffer[fParseBufferDataEnd++] = 0;
305 fParseBuffer[fParseBufferDataEnd++] = 0;
306 fParseBuffer[fParseBufferDataEnd++] = 1;
307 fParseBuffer[fParseBufferDataEnd++] = PICTURE_START_CODE;
308
309 // Try again:
310 doGetNextFrame();
311 } else {
312 // Handle closure in the regular way:
313 handleClosure();
314 }
315}
316
317void MPEG2IFrameIndexFromTransportStream
318::analyzePAT(unsigned char* pkt, unsigned size) {
319 // Get the PMT_PID:
320 while (size >= 17) { // The table is large enough
321 u_int16_t program_number = (pkt[9]<<8) | pkt[10];
322 if (program_number != 0) {
323 fPMT_PID = ((pkt[11]&0x1F)<<8) | pkt[12];
324 return;
325 }
326
327 pkt += 4; size -= 4;
328 }
329}
330
331void MPEG2IFrameIndexFromTransportStream
332::analyzePMT(unsigned char* pkt, unsigned size) {
333 // Scan the "elementary_PID"s in the map, until we see the first video stream.
334
335 // First, get the "section_length", to get the table's size:
336 u_int16_t section_length = ((pkt[2]&0x0F)<<8) | pkt[3];
337 if ((unsigned)(4+section_length) < size) size = (4+section_length);
338
339 // Then, skip any descriptors following the "program_info_length":
340 if (size < 22) return; // not enough data
341 unsigned program_info_length = ((pkt[11]&0x0F)<<8) | pkt[12];
342 pkt += 13; size -= 13;
343 if (size < program_info_length) return; // not enough data
344 pkt += program_info_length; size -= program_info_length;
345
346 // Look at each ("stream_type","elementary_PID") pair, looking for a video stream:
347 while (size >= 9) {
348 u_int8_t stream_type = pkt[0];
349 u_int16_t elementary_PID = ((pkt[1]&0x1F)<<8) | pkt[2];
350 if (stream_type == 1 || stream_type == 2 ||
351 stream_type == 0x1B/*H.264 video*/ || stream_type == 0x24/*H.265 video*/) {
352 if (stream_type == 0x1B) fIsH264 = True;
353 else if (stream_type == 0x24) fIsH265 = True;
354 fVideo_PID = elementary_PID;
355 return;
356 }
357
358 u_int16_t ES_info_length = ((pkt[3]&0x0F)<<8) | pkt[4];
359 pkt += 5; size -= 5;
360 if (size < ES_info_length) return; // not enough data
361 pkt += ES_info_length; size -= ES_info_length;
362 }
363}
364
365Boolean MPEG2IFrameIndexFromTransportStream::deliverIndexRecord() {
366 IndexRecord* head = fHeadIndexRecord;
367 if (head == NULL) return False;
368
369 // Check whether the head record has been parsed yet:
370 if (head->recordType() == RECORD_UNPARSED) return False;
371
372 // Remove the head record (the one whose data we'll be delivering):
373 IndexRecord* next = head->next();
374 head->unlink();
375 if (next == head) {
376 fHeadIndexRecord = fTailIndexRecord = NULL;
377 } else {
378 fHeadIndexRecord = next;
379 }
380
381 if (head->recordType() == RECORD_JUNK) {
382 // Don't actually deliver the data to the client:
383 delete head;
384 // Try to deliver the next record instead:
385 return deliverIndexRecord();
386 }
387
388 // Deliver data from the head record:
389#ifdef DEBUG
390 envir() << "delivering: " << *head << "\n";
391#endif
392 if (fMaxSize < 11) {
393 fFrameSize = 0;
394 } else {
395 fTo[0] = (u_int8_t)(head->recordType());
396 fTo[1] = head->startOffset();
397 fTo[2] = head->size();
398 // Deliver the PCR, as 24 bits (integer part; little endian) + 8 bits (fractional part)
399 float pcr = head->pcr();
400 unsigned pcr_int = (unsigned)pcr;
401 u_int8_t pcr_frac = (u_int8_t)(256*(pcr-pcr_int));
402 fTo[3] = (unsigned char)(pcr_int);
403 fTo[4] = (unsigned char)(pcr_int>>8);
404 fTo[5] = (unsigned char)(pcr_int>>16);
405 fTo[6] = (unsigned char)(pcr_frac);
406 // Deliver the transport packet number (in little-endian order):
407 unsigned long tpn = head->transportPacketNumber();
408 fTo[7] = (unsigned char)(tpn);
409 fTo[8] = (unsigned char)(tpn>>8);
410 fTo[9] = (unsigned char)(tpn>>16);
411 fTo[10] = (unsigned char)(tpn>>24);
412 fFrameSize = 11;
413 }
414
415 // Free the (former) head record (as we're now done with it):
416 delete head;
417
418 // Complete delivery to the client:
419 afterGetting(this);
420 return True;
421}
422
423Boolean MPEG2IFrameIndexFromTransportStream::parseFrame() {
424 // At this point, we have a queue of >=0 (unparsed) index records, representing
425 // the data in the parse buffer from "fParseBufferFrameStart"
426 // to "fParseBufferDataEnd". We now parse through this data, looking for
427 // a complete 'frame', where a 'frame', in this case, means:
428 // for MPEG video: a Video Sequence Header, GOP Header, Picture Header, or Slice
429 // for H.264 or H.265 video: a NAL unit
430
431 // Inspect the frame's initial 4-byte code, to make sure it starts with a system code:
432 if (fParseBufferDataEnd-fParseBufferFrameStart < 4) return False; // not enough data
433 unsigned numInitialBadBytes = 0;
434 unsigned char const* p = &fParseBuffer[fParseBufferFrameStart];
435 if (!(p[0] == 0 && p[1] == 0 && p[2] == 1)) {
436 // There's no system code at the beginning. Parse until we find one:
437 if (fParseBufferParseEnd == fParseBufferFrameStart + 4) {
438 // Start parsing from the beginning of the frame data:
439 fParseBufferParseEnd = fParseBufferFrameStart;
440 }
441 unsigned char nextCode;
442 if (!parseToNextCode(nextCode)) return False;
443
444 numInitialBadBytes = fParseBufferParseEnd - fParseBufferFrameStart;
445 fParseBufferFrameStart = fParseBufferParseEnd;
446 fParseBufferParseEnd += 4; // skip over the code that we just saw
447 p = &fParseBuffer[fParseBufferFrameStart];
448 }
449
450 unsigned char curCode = p[3];
451 if (fIsH264) curCode &= 0x1F; // nal_unit_type
452 else if (fIsH265) curCode = (curCode&0x7E)>>1;
453
454 RecordType curRecordType;
455 unsigned char nextCode;
456 if (fIsH264) {
457 switch (curCode) {
458 case 1: // Coded slice of a non-IDR picture
459 curRecordType = RECORD_NAL_H264_NON_IFRAME;
460 if (!parseToNextCode(nextCode)) return False;
461 break;
462 case 5: // Coded slice of an IDR picture
463 curRecordType = RECORD_NAL_H264_IFRAME;
464 if (!parseToNextCode(nextCode)) return False;
465 break;
466 case 6: // Supplemental enhancement information (SEI)
467 curRecordType = RECORD_NAL_H264_SEI;
468 if (!parseToNextCode(nextCode)) return False;
469 break;
470 case 7: // Sequence parameter set (SPS)
471 curRecordType = RECORD_NAL_H264_SPS;
472 if (!parseToNextCode(nextCode)) return False;
473 break;
474 case 8: // Picture parameter set (PPS)
475 curRecordType = RECORD_NAL_H264_PPS;
476 if (!parseToNextCode(nextCode)) return False;
477 break;
478 default:
479 curRecordType = RECORD_NAL_H264_OTHER;
480 if (!parseToNextCode(nextCode)) return False;
481 break;
482 }
483 } else if (fIsH265) {
484 switch (curCode) {
485 case 19: // Coded slice segment of an IDR picture
486 case 20: // Coded slice segment of an IDR picture
487 curRecordType = RECORD_NAL_H265_IFRAME;
488 if (!parseToNextCode(nextCode)) return False;
489 break;
490 case 32: // Video parameter set (VPS)
491 curRecordType = RECORD_NAL_H265_VPS;
492 if (!parseToNextCode(nextCode)) return False;
493 break;
494 case 33: // Sequence parameter set (SPS)
495 curRecordType = RECORD_NAL_H265_SPS;
496 if (!parseToNextCode(nextCode)) return False;
497 break;
498 case 34: // Picture parameter set (PPS)
499 curRecordType = RECORD_NAL_H265_PPS;
500 if (!parseToNextCode(nextCode)) return False;
501 break;
502 default:
503 curRecordType = (curCode <= 31) ? RECORD_NAL_H265_NON_IFRAME : RECORD_NAL_H265_OTHER;
504 if (!parseToNextCode(nextCode)) return False;
505 break;
506 }
507 } else { // MPEG-1, 2, or 4
508 switch (curCode) {
509 case VIDEO_SEQUENCE_START_CODE:
510 case VISUAL_OBJECT_SEQUENCE_START_CODE:
511 curRecordType = RECORD_VSH;
512 while (1) {
513 if (!parseToNextCode(nextCode)) return False;
514 if (nextCode == GROUP_START_CODE ||
515 nextCode == PICTURE_START_CODE || nextCode == VOP_START_CODE) break;
516 fParseBufferParseEnd += 4; // skip over the code that we just saw
517 }
518 break;
519 case GROUP_START_CODE:
520 curRecordType = RECORD_GOP;
521 while (1) {
522 if (!parseToNextCode(nextCode)) return False;
523 if (nextCode == PICTURE_START_CODE || nextCode == VOP_START_CODE) break;
524 fParseBufferParseEnd += 4; // skip over the code that we just saw
525 }
526 break;
527 default: // picture
528 curRecordType = RECORD_PIC_NON_IFRAME; // may get changed to IFRAME later
529 while (1) {
530 if (!parseToNextCode(nextCode)) return False;
531 if (nextCode == VIDEO_SEQUENCE_START_CODE ||
532 nextCode == VISUAL_OBJECT_SEQUENCE_START_CODE ||
533 nextCode == GROUP_START_CODE || nextCode == GROUP_VOP_START_CODE ||
534 nextCode == PICTURE_START_CODE || nextCode == VOP_START_CODE) break;
535 fParseBufferParseEnd += 4; // skip over the code that we just saw
536 }
537 break;
538 }
539 }
540
541 if (curRecordType == RECORD_PIC_NON_IFRAME) {
542 if (curCode == VOP_START_CODE) { // MPEG-4
543 if ((fParseBuffer[fParseBufferFrameStart+4]&0xC0) == 0) {
544 // This is actually an I-frame. Note it as such:
545 curRecordType = RECORD_PIC_IFRAME;
546 }
547 } else { // MPEG-1 or 2
548 if ((fParseBuffer[fParseBufferFrameStart+5]&0x38) == 0x08) {
549 // This is actually an I-frame. Note it as such:
550 curRecordType = RECORD_PIC_IFRAME;
551 }
552 }
553 }
554
555 // There is now a parsed 'frame', from "fParseBufferFrameStart"
556 // to "fParseBufferParseEnd". Tag the corresponding index records to note this:
557 unsigned frameSize = fParseBufferParseEnd - fParseBufferFrameStart + numInitialBadBytes;
558#ifdef DEBUG
559 envir() << "parsed " << recordTypeStr[curRecordType] << "; length "
560 << frameSize << "\n";
561#endif
562 for (IndexRecord* r = fHeadIndexRecord; ; r = r->next()) {
563 if (numInitialBadBytes >= r->size()) {
564 r->recordType() = RECORD_JUNK;
565 numInitialBadBytes -= r->size();
566 } else {
567 r->recordType() = curRecordType;
568 }
569 if (r == fHeadIndexRecord) r->setFirstFlag();
570 // indicates that this is the first record for this frame
571
572 if (r->size() > frameSize) {
573 // This record contains extra data that's not part of the frame.
574 // Shorten this record, and move the extra data to a new record
575 // that comes afterwards:
576 u_int8_t newOffset = r->startOffset() + frameSize;
577 u_int8_t newSize = r->size() - frameSize;
578 r->size() = frameSize;
579#ifdef DEBUG
580 envir() << "tagged record (modified): " << *r << "\n";
581#endif
582
583 IndexRecord* newRecord
584 = new IndexRecord(newOffset, newSize, r->transportPacketNumber(), r->pcr());
585 newRecord->addAfter(r);
586 if (fTailIndexRecord == r) fTailIndexRecord = newRecord;
587#ifdef DEBUG
588 envir() << "added extra record: " << *newRecord << "\n";
589#endif
590 } else {
591#ifdef DEBUG
592 envir() << "tagged record: " << *r << "\n";
593#endif
594 }
595 frameSize -= r->size();
596 if (frameSize == 0) break;
597 if (r == fTailIndexRecord) { // this shouldn't happen
598 envir() << "!!!!!Internal consistency error!!!!!\n";
599 return False;
600 }
601 }
602
603 // Finally, update our parse state (to skip over the now-parsed data):
604 fParseBufferFrameStart = fParseBufferParseEnd;
605 fParseBufferParseEnd += 4; // to skip over the next code (that we found)
606
607 return True;
608}
609
610Boolean MPEG2IFrameIndexFromTransportStream
611::parseToNextCode(unsigned char& nextCode) {
612 unsigned char const* p = &fParseBuffer[fParseBufferParseEnd];
613 unsigned char const* end = &fParseBuffer[fParseBufferDataEnd];
614 while (p <= end-4) {
615 if (p[2] > 1) p += 3; // common case (optimized)
616 else if (p[2] == 0) ++p;
617 else if (p[0] == 0 && p[1] == 0) { // && p[2] == 1
618 // We found a code here:
619 nextCode = p[3];
620 fParseBufferParseEnd = p - &fParseBuffer[0]; // where we've gotten to
621 return True;
622 } else p += 3;
623 }
624
625 fParseBufferParseEnd = p - &fParseBuffer[0]; // where we've gotten to
626 return False; // no luck this time
627}
628
629void MPEG2IFrameIndexFromTransportStream::compactParseBuffer() {
630#ifdef DEBUG
631 envir() << "Compacting parse buffer: [" << fParseBufferFrameStart
632 << "," << fParseBufferParseEnd << "," << fParseBufferDataEnd << "]";
633#endif
634 memmove(&fParseBuffer[0], &fParseBuffer[fParseBufferFrameStart],
635 fParseBufferDataEnd - fParseBufferFrameStart);
636 fParseBufferDataEnd -= fParseBufferFrameStart;
637 fParseBufferParseEnd -= fParseBufferFrameStart;
638 fParseBufferFrameStart = 0;
639#ifdef DEBUG
640 envir() << "-> [" << fParseBufferFrameStart
641 << "," << fParseBufferParseEnd << "," << fParseBufferDataEnd << "]\n";
642#endif
643}
644
645void MPEG2IFrameIndexFromTransportStream::addToTail(IndexRecord* newIndexRecord) {
646#ifdef DEBUG
647 envir() << "adding new: " << *newIndexRecord << "\n";
648#endif
649 if (fTailIndexRecord == NULL) {
650 fHeadIndexRecord = fTailIndexRecord = newIndexRecord;
651 } else {
652 newIndexRecord->addAfter(fTailIndexRecord);
653 fTailIndexRecord = newIndexRecord;
654 }
655}
656
657////////// IndexRecord implementation //////////
658
659IndexRecord::IndexRecord(u_int8_t startOffset, u_int8_t size,
660 unsigned long transportPacketNumber, float pcr)
661 : fNext(this), fPrev(this), fRecordType(RECORD_UNPARSED),
662 fStartOffset(startOffset), fSize(size),
663 fPCR(pcr), fTransportPacketNumber(transportPacketNumber) {
664}
665
666IndexRecord::~IndexRecord() {
667 IndexRecord* nextRecord = next();
668 unlink();
669 if (nextRecord != this) delete nextRecord;
670}
671
672void IndexRecord::addAfter(IndexRecord* prev) {
673 fNext = prev->fNext;
674 fPrev = prev;
675 prev->fNext->fPrev = this;
676 prev->fNext = this;
677}
678
679void IndexRecord::unlink() {
680 fNext->fPrev = fPrev;
681 fPrev->fNext = fNext;
682 fNext = fPrev = this;
683}
684