/********** This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. (See .) This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA **********/ // "liveMedia" // Copyright (c) 1996-2016 Live Networks, Inc. All rights reserved. // A filter that produces a sequence of I-frame indices from a MPEG-2 Transport Stream // Implementation #include "MPEG2IndexFromTransportStream.hh" ////////// IndexRecord definition ////////// enum RecordType { RECORD_UNPARSED = 0, RECORD_VSH = 1, // a MPEG Video Sequence Header RECORD_GOP = 2, RECORD_PIC_NON_IFRAME = 3, // includes slices RECORD_PIC_IFRAME = 4, // includes slices RECORD_NAL_H264_SPS = 5, // H.264 RECORD_NAL_H264_PPS = 6, // H.264 RECORD_NAL_H264_SEI = 7, // H.264 RECORD_NAL_H264_NON_IFRAME = 8, // H.264 RECORD_NAL_H264_IFRAME = 9, // H.264 RECORD_NAL_H264_OTHER = 10, // H.264 RECORD_NAL_H265_VPS = 11, // H.265 RECORD_NAL_H265_SPS = 12, // H.265 RECORD_NAL_H265_PPS = 13, // H.265 RECORD_NAL_H265_NON_IFRAME = 14, // H.265 RECORD_NAL_H265_IFRAME = 15, // H.265 RECORD_NAL_H265_OTHER = 16, // H.265 RECORD_JUNK }; class IndexRecord { public: IndexRecord(u_int8_t startOffset, u_int8_t size, unsigned long transportPacketNumber, float pcr); virtual ~IndexRecord(); RecordType& recordType() { return fRecordType; } void setFirstFlag() { fRecordType = (RecordType)(((u_int8_t)fRecordType) | 0x80); } u_int8_t startOffset() const { return fStartOffset; } u_int8_t& size() { return fSize; } float pcr() const { return fPCR; } unsigned long transportPacketNumber() const { return fTransportPacketNumber; } IndexRecord* next() const { return fNext; } void addAfter(IndexRecord* prev); void unlink(); private: // Index records are maintained in a doubly-linked list: IndexRecord* fNext; IndexRecord* fPrev; RecordType fRecordType; u_int8_t fStartOffset; // within the Transport Stream packet u_int8_t fSize; // in bytes, following "fStartOffset". // Note: fStartOffset + fSize <= TRANSPORT_PACKET_SIZE float fPCR; unsigned long fTransportPacketNumber; }; #ifdef DEBUG static char const* recordTypeStr[] = { "UNPARSED", "VSH", "GOP", "PIC(non-I-frame)", "PIC(I-frame)", "SPS (H.264)", "PPS (H.264)", "SEI (H.264)", "H.264 non-I-frame", "H.264 I-frame", "other NAL unit (H.264)", "VPS (H.265)", "SPS (H.265)", "PPS (H.265)", "H.265 non-I-frame", "H.265 I-frame", "other NAL unit (H.265)", "JUNK" }; UsageEnvironment& operator<<(UsageEnvironment& env, IndexRecord& r) { return env << "[" << ((r.recordType()&0x80) != 0 ? "1" : "") << recordTypeStr[r.recordType()&0x7F] << ":" << (unsigned)r.transportPacketNumber() << ":" << r.startOffset() << "(" << r.size() << ")@" << r.pcr() << "]"; } #endif ////////// MPEG2IFrameIndexFromTransportStream implementation ////////// MPEG2IFrameIndexFromTransportStream* MPEG2IFrameIndexFromTransportStream::createNew(UsageEnvironment& env, FramedSource* inputSource) { return new MPEG2IFrameIndexFromTransportStream(env, inputSource); } // The largest expected frame size (in bytes): #define MAX_FRAME_SIZE 400000 // Make our parse buffer twice as large as this, to ensure that at least one // complete frame will fit inside it: #define PARSE_BUFFER_SIZE (2*MAX_FRAME_SIZE) // The PID used for the PAT (as defined in the MPEG Transport Stream standard): #define PAT_PID 0 MPEG2IFrameIndexFromTransportStream ::MPEG2IFrameIndexFromTransportStream(UsageEnvironment& env, FramedSource* inputSource) : FramedFilter(env, inputSource), fIsH264(False), fIsH265(False), fInputTransportPacketCounter((unsigned)-1), fClosureNumber(0), fLastContinuityCounter(~0), fFirstPCR(0.0), fLastPCR(0.0), fHaveSeenFirstPCR(False), fPMT_PID(0x10), fVideo_PID(0xE0), // default values fParseBufferSize(PARSE_BUFFER_SIZE), fParseBufferFrameStart(0), fParseBufferParseEnd(4), fParseBufferDataEnd(0), fHeadIndexRecord(NULL), fTailIndexRecord(NULL) { fParseBuffer = new unsigned char[fParseBufferSize]; } MPEG2IFrameIndexFromTransportStream::~MPEG2IFrameIndexFromTransportStream() { delete fHeadIndexRecord; delete[] fParseBuffer; } void MPEG2IFrameIndexFromTransportStream::doGetNextFrame() { // Begin by trying to deliver an index record (for an already-parsed frame) // to the client: if (deliverIndexRecord()) return; // No more index records are left to deliver, so try to parse a new frame: if (parseFrame()) { // success - try again doGetNextFrame(); return; } // We need to read some more Transport Stream packets. Check whether we have room: if (fParseBufferSize - fParseBufferDataEnd < TRANSPORT_PACKET_SIZE) { // There's no room left. Compact the buffer, and check again: compactParseBuffer(); if (fParseBufferSize - fParseBufferDataEnd < TRANSPORT_PACKET_SIZE) { envir() << "ERROR: parse buffer full; increase MAX_FRAME_SIZE\n"; // Treat this as if the input source ended: handleInputClosure1(); return; } } // Arrange to read a new Transport Stream packet: fInputSource->getNextFrame(fInputBuffer, sizeof fInputBuffer, afterGettingFrame, this, handleInputClosure, this); } void MPEG2IFrameIndexFromTransportStream ::afterGettingFrame(void* clientData, unsigned frameSize, unsigned numTruncatedBytes, struct timeval presentationTime, unsigned durationInMicroseconds) { MPEG2IFrameIndexFromTransportStream* source = (MPEG2IFrameIndexFromTransportStream*)clientData; source->afterGettingFrame1(frameSize, numTruncatedBytes, presentationTime, durationInMicroseconds); } #define TRANSPORT_SYNC_BYTE 0x47 void MPEG2IFrameIndexFromTransportStream ::afterGettingFrame1(unsigned frameSize, unsigned numTruncatedBytes, struct timeval presentationTime, unsigned durationInMicroseconds) { if (frameSize < TRANSPORT_PACKET_SIZE || fInputBuffer[0] != TRANSPORT_SYNC_BYTE) { if (fInputBuffer[0] != TRANSPORT_SYNC_BYTE) { envir() << "Bad TS sync byte: 0x" << fInputBuffer[0] << "\n"; } // Handle this as if the source ended: handleInputClosure1(); return; } ++fInputTransportPacketCounter; // Figure out how much of this Transport Packet contains PES data: u_int8_t adaptation_field_control = (fInputBuffer[3]&0x30)>>4; u_int8_t totalHeaderSize = adaptation_field_control <= 1 ? 4 : 5 + fInputBuffer[4]; if ((adaptation_field_control == 2 && totalHeaderSize != TRANSPORT_PACKET_SIZE) || (adaptation_field_control == 3 && totalHeaderSize >= TRANSPORT_PACKET_SIZE)) { envir() << "Bad \"adaptation_field_length\": " << fInputBuffer[4] << "\n"; doGetNextFrame(); return; } // Check for a PCR: if (totalHeaderSize > 5 && (fInputBuffer[5]&0x10) != 0) { // There's a PCR: u_int32_t pcrBaseHigh = (fInputBuffer[6]<<24)|(fInputBuffer[7]<<16) |(fInputBuffer[8]<<8)|fInputBuffer[9]; float pcr = pcrBaseHigh/45000.0f; if ((fInputBuffer[10]&0x80) != 0) pcr += 1/90000.0f; // add in low-bit (if set) unsigned short pcrExt = ((fInputBuffer[10]&0x01)<<8) | fInputBuffer[11]; pcr += pcrExt/27000000.0f; if (!fHaveSeenFirstPCR) { fFirstPCR = pcr; fHaveSeenFirstPCR = True; } else if (pcr < fLastPCR) { // The PCR timestamp has gone backwards. Display a warning about this // (because it indicates buggy Transport Stream data), and compensate for it. envir() << "\nWarning: At about " << fLastPCR-fFirstPCR << " seconds into the file, the PCR timestamp decreased - from " << fLastPCR << " to " << pcr << "\n"; fFirstPCR -= (fLastPCR - pcr); } fLastPCR = pcr; } // Get the PID from the packet, and check for special tables: the PAT and PMT: u_int16_t PID = ((fInputBuffer[1]&0x1F)<<8) | fInputBuffer[2]; if (PID == PAT_PID) { analyzePAT(&fInputBuffer[totalHeaderSize], TRANSPORT_PACKET_SIZE-totalHeaderSize); } else if (PID == fPMT_PID) { analyzePMT(&fInputBuffer[totalHeaderSize], TRANSPORT_PACKET_SIZE-totalHeaderSize); } // Ignore transport packets for non-video programs, // or packets with no data, or packets that duplicate the previous packet: u_int8_t continuity_counter = fInputBuffer[3]&0x0F; if ((PID != fVideo_PID) || !(adaptation_field_control == 1 || adaptation_field_control == 3) || continuity_counter == fLastContinuityCounter) { doGetNextFrame(); return; } fLastContinuityCounter = continuity_counter; // Also, if this is the start of a PES packet, then skip over the PES header: Boolean payload_unit_start_indicator = (fInputBuffer[1]&0x40) != 0; if (payload_unit_start_indicator && totalHeaderSize < TRANSPORT_PACKET_SIZE - 8 && fInputBuffer[totalHeaderSize] == 0x00 && fInputBuffer[totalHeaderSize+1] == 0x00 && fInputBuffer[totalHeaderSize+2] == 0x01) { u_int8_t PES_header_data_length = fInputBuffer[totalHeaderSize+8]; totalHeaderSize += 9 + PES_header_data_length; if (totalHeaderSize >= TRANSPORT_PACKET_SIZE) { envir() << "Unexpectedly large PES header size: " << PES_header_data_length << "\n"; // Handle this as if the source ended: handleInputClosure1(); return; } } // The remaining data is Video Elementary Stream data. Add it to our parse buffer: unsigned vesSize = TRANSPORT_PACKET_SIZE - totalHeaderSize; memmove(&fParseBuffer[fParseBufferDataEnd], &fInputBuffer[totalHeaderSize], vesSize); fParseBufferDataEnd += vesSize; // And add a new index record noting where it came from: addToTail(new IndexRecord(totalHeaderSize, vesSize, fInputTransportPacketCounter, fLastPCR - fFirstPCR)); // Try again: doGetNextFrame(); } void MPEG2IFrameIndexFromTransportStream::handleInputClosure(void* clientData) { MPEG2IFrameIndexFromTransportStream* source = (MPEG2IFrameIndexFromTransportStream*)clientData; source->handleInputClosure1(); } #define VIDEO_SEQUENCE_START_CODE 0xB3 // MPEG-1 or 2 #define VISUAL_OBJECT_SEQUENCE_START_CODE 0xB0 // MPEG-4 #define GROUP_START_CODE 0xB8 // MPEG-1 or 2 #define GROUP_VOP_START_CODE 0xB3 // MPEG-4 #define PICTURE_START_CODE 0x00 // MPEG-1 or 2 #define VOP_START_CODE 0xB6 // MPEG-4 void MPEG2IFrameIndexFromTransportStream::handleInputClosure1() { if (++fClosureNumber == 1 && fParseBufferDataEnd > fParseBufferFrameStart && fParseBufferDataEnd <= fParseBufferSize - 4) { // This is the first time we saw EOF, and there's still data remaining to be // parsed. Hack: Append a Picture Header code to the end of the unparsed // data, and try again. This should use up all of the unparsed data. fParseBuffer[fParseBufferDataEnd++] = 0; fParseBuffer[fParseBufferDataEnd++] = 0; fParseBuffer[fParseBufferDataEnd++] = 1; fParseBuffer[fParseBufferDataEnd++] = PICTURE_START_CODE; // Try again: doGetNextFrame(); } else { // Handle closure in the regular way: handleClosure(); } } void MPEG2IFrameIndexFromTransportStream ::analyzePAT(unsigned char* pkt, unsigned size) { // Get the PMT_PID: while (size >= 17) { // The table is large enough u_int16_t program_number = (pkt[9]<<8) | pkt[10]; if (program_number != 0) { fPMT_PID = ((pkt[11]&0x1F)<<8) | pkt[12]; return; } pkt += 4; size -= 4; } } void MPEG2IFrameIndexFromTransportStream ::analyzePMT(unsigned char* pkt, unsigned size) { // Scan the "elementary_PID"s in the map, until we see the first video stream. // First, get the "section_length", to get the table's size: u_int16_t section_length = ((pkt[2]&0x0F)<<8) | pkt[3]; if ((unsigned)(4+section_length) < size) size = (4+section_length); // Then, skip any descriptors following the "program_info_length": if (size < 22) return; // not enough data unsigned program_info_length = ((pkt[11]&0x0F)<<8) | pkt[12]; pkt += 13; size -= 13; if (size < program_info_length) return; // not enough data pkt += program_info_length; size -= program_info_length; // Look at each ("stream_type","elementary_PID") pair, looking for a video stream: while (size >= 9) { u_int8_t stream_type = pkt[0]; u_int16_t elementary_PID = ((pkt[1]&0x1F)<<8) | pkt[2]; if (stream_type == 1 || stream_type == 2 || stream_type == 0x1B/*H.264 video*/ || stream_type == 0x24/*H.265 video*/) { if (stream_type == 0x1B) fIsH264 = True; else if (stream_type == 0x24) fIsH265 = True; fVideo_PID = elementary_PID; return; } u_int16_t ES_info_length = ((pkt[3]&0x0F)<<8) | pkt[4]; pkt += 5; size -= 5; if (size < ES_info_length) return; // not enough data pkt += ES_info_length; size -= ES_info_length; } } Boolean MPEG2IFrameIndexFromTransportStream::deliverIndexRecord() { IndexRecord* head = fHeadIndexRecord; if (head == NULL) return False; // Check whether the head record has been parsed yet: if (head->recordType() == RECORD_UNPARSED) return False; // Remove the head record (the one whose data we'll be delivering): IndexRecord* next = head->next(); head->unlink(); if (next == head) { fHeadIndexRecord = fTailIndexRecord = NULL; } else { fHeadIndexRecord = next; } if (head->recordType() == RECORD_JUNK) { // Don't actually deliver the data to the client: delete head; // Try to deliver the next record instead: return deliverIndexRecord(); } // Deliver data from the head record: #ifdef DEBUG envir() << "delivering: " << *head << "\n"; #endif if (fMaxSize < 11) { fFrameSize = 0; } else { fTo[0] = (u_int8_t)(head->recordType()); fTo[1] = head->startOffset(); fTo[2] = head->size(); // Deliver the PCR, as 24 bits (integer part; little endian) + 8 bits (fractional part) float pcr = head->pcr(); unsigned pcr_int = (unsigned)pcr; u_int8_t pcr_frac = (u_int8_t)(256*(pcr-pcr_int)); fTo[3] = (unsigned char)(pcr_int); fTo[4] = (unsigned char)(pcr_int>>8); fTo[5] = (unsigned char)(pcr_int>>16); fTo[6] = (unsigned char)(pcr_frac); // Deliver the transport packet number (in little-endian order): unsigned long tpn = head->transportPacketNumber(); fTo[7] = (unsigned char)(tpn); fTo[8] = (unsigned char)(tpn>>8); fTo[9] = (unsigned char)(tpn>>16); fTo[10] = (unsigned char)(tpn>>24); fFrameSize = 11; } // Free the (former) head record (as we're now done with it): delete head; // Complete delivery to the client: afterGetting(this); return True; } Boolean MPEG2IFrameIndexFromTransportStream::parseFrame() { // At this point, we have a queue of >=0 (unparsed) index records, representing // the data in the parse buffer from "fParseBufferFrameStart" // to "fParseBufferDataEnd". We now parse through this data, looking for // a complete 'frame', where a 'frame', in this case, means: // for MPEG video: a Video Sequence Header, GOP Header, Picture Header, or Slice // for H.264 or H.265 video: a NAL unit // Inspect the frame's initial 4-byte code, to make sure it starts with a system code: if (fParseBufferDataEnd-fParseBufferFrameStart < 4) return False; // not enough data unsigned numInitialBadBytes = 0; unsigned char const* p = &fParseBuffer[fParseBufferFrameStart]; if (!(p[0] == 0 && p[1] == 0 && p[2] == 1)) { // There's no system code at the beginning. Parse until we find one: if (fParseBufferParseEnd == fParseBufferFrameStart + 4) { // Start parsing from the beginning of the frame data: fParseBufferParseEnd = fParseBufferFrameStart; } unsigned char nextCode; if (!parseToNextCode(nextCode)) return False; numInitialBadBytes = fParseBufferParseEnd - fParseBufferFrameStart; fParseBufferFrameStart = fParseBufferParseEnd; fParseBufferParseEnd += 4; // skip over the code that we just saw p = &fParseBuffer[fParseBufferFrameStart]; } unsigned char curCode = p[3]; if (fIsH264) curCode &= 0x1F; // nal_unit_type else if (fIsH265) curCode = (curCode&0x7E)>>1; RecordType curRecordType; unsigned char nextCode; if (fIsH264) { switch (curCode) { case 1: // Coded slice of a non-IDR picture curRecordType = RECORD_NAL_H264_NON_IFRAME; if (!parseToNextCode(nextCode)) return False; break; case 5: // Coded slice of an IDR picture curRecordType = RECORD_NAL_H264_IFRAME; if (!parseToNextCode(nextCode)) return False; break; case 6: // Supplemental enhancement information (SEI) curRecordType = RECORD_NAL_H264_SEI; if (!parseToNextCode(nextCode)) return False; break; case 7: // Sequence parameter set (SPS) curRecordType = RECORD_NAL_H264_SPS; if (!parseToNextCode(nextCode)) return False; break; case 8: // Picture parameter set (PPS) curRecordType = RECORD_NAL_H264_PPS; if (!parseToNextCode(nextCode)) return False; break; default: curRecordType = RECORD_NAL_H264_OTHER; if (!parseToNextCode(nextCode)) return False; break; } } else if (fIsH265) { switch (curCode) { case 19: // Coded slice segment of an IDR picture case 20: // Coded slice segment of an IDR picture curRecordType = RECORD_NAL_H265_IFRAME; if (!parseToNextCode(nextCode)) return False; break; case 32: // Video parameter set (VPS) curRecordType = RECORD_NAL_H265_VPS; if (!parseToNextCode(nextCode)) return False; break; case 33: // Sequence parameter set (SPS) curRecordType = RECORD_NAL_H265_SPS; if (!parseToNextCode(nextCode)) return False; break; case 34: // Picture parameter set (PPS) curRecordType = RECORD_NAL_H265_PPS; if (!parseToNextCode(nextCode)) return False; break; default: curRecordType = (curCode <= 31) ? RECORD_NAL_H265_NON_IFRAME : RECORD_NAL_H265_OTHER; if (!parseToNextCode(nextCode)) return False; break; } } else { // MPEG-1, 2, or 4 switch (curCode) { case VIDEO_SEQUENCE_START_CODE: case VISUAL_OBJECT_SEQUENCE_START_CODE: curRecordType = RECORD_VSH; while (1) { if (!parseToNextCode(nextCode)) return False; if (nextCode == GROUP_START_CODE || nextCode == PICTURE_START_CODE || nextCode == VOP_START_CODE) break; fParseBufferParseEnd += 4; // skip over the code that we just saw } break; case GROUP_START_CODE: curRecordType = RECORD_GOP; while (1) { if (!parseToNextCode(nextCode)) return False; if (nextCode == PICTURE_START_CODE || nextCode == VOP_START_CODE) break; fParseBufferParseEnd += 4; // skip over the code that we just saw } break; default: // picture curRecordType = RECORD_PIC_NON_IFRAME; // may get changed to IFRAME later while (1) { if (!parseToNextCode(nextCode)) return False; if (nextCode == VIDEO_SEQUENCE_START_CODE || nextCode == VISUAL_OBJECT_SEQUENCE_START_CODE || nextCode == GROUP_START_CODE || nextCode == GROUP_VOP_START_CODE || nextCode == PICTURE_START_CODE || nextCode == VOP_START_CODE) break; fParseBufferParseEnd += 4; // skip over the code that we just saw } break; } } if (curRecordType == RECORD_PIC_NON_IFRAME) { if (curCode == VOP_START_CODE) { // MPEG-4 if ((fParseBuffer[fParseBufferFrameStart+4]&0xC0) == 0) { // This is actually an I-frame. Note it as such: curRecordType = RECORD_PIC_IFRAME; } } else { // MPEG-1 or 2 if ((fParseBuffer[fParseBufferFrameStart+5]&0x38) == 0x08) { // This is actually an I-frame. Note it as such: curRecordType = RECORD_PIC_IFRAME; } } } // There is now a parsed 'frame', from "fParseBufferFrameStart" // to "fParseBufferParseEnd". Tag the corresponding index records to note this: unsigned frameSize = fParseBufferParseEnd - fParseBufferFrameStart + numInitialBadBytes; #ifdef DEBUG envir() << "parsed " << recordTypeStr[curRecordType] << "; length " << frameSize << "\n"; #endif for (IndexRecord* r = fHeadIndexRecord; ; r = r->next()) { if (numInitialBadBytes >= r->size()) { r->recordType() = RECORD_JUNK; numInitialBadBytes -= r->size(); } else { r->recordType() = curRecordType; } if (r == fHeadIndexRecord) r->setFirstFlag(); // indicates that this is the first record for this frame if (r->size() > frameSize) { // This record contains extra data that's not part of the frame. // Shorten this record, and move the extra data to a new record // that comes afterwards: u_int8_t newOffset = r->startOffset() + frameSize; u_int8_t newSize = r->size() - frameSize; r->size() = frameSize; #ifdef DEBUG envir() << "tagged record (modified): " << *r << "\n"; #endif IndexRecord* newRecord = new IndexRecord(newOffset, newSize, r->transportPacketNumber(), r->pcr()); newRecord->addAfter(r); if (fTailIndexRecord == r) fTailIndexRecord = newRecord; #ifdef DEBUG envir() << "added extra record: " << *newRecord << "\n"; #endif } else { #ifdef DEBUG envir() << "tagged record: " << *r << "\n"; #endif } frameSize -= r->size(); if (frameSize == 0) break; if (r == fTailIndexRecord) { // this shouldn't happen envir() << "!!!!!Internal consistency error!!!!!\n"; return False; } } // Finally, update our parse state (to skip over the now-parsed data): fParseBufferFrameStart = fParseBufferParseEnd; fParseBufferParseEnd += 4; // to skip over the next code (that we found) return True; } Boolean MPEG2IFrameIndexFromTransportStream ::parseToNextCode(unsigned char& nextCode) { unsigned char const* p = &fParseBuffer[fParseBufferParseEnd]; unsigned char const* end = &fParseBuffer[fParseBufferDataEnd]; while (p <= end-4) { if (p[2] > 1) p += 3; // common case (optimized) else if (p[2] == 0) ++p; else if (p[0] == 0 && p[1] == 0) { // && p[2] == 1 // We found a code here: nextCode = p[3]; fParseBufferParseEnd = p - &fParseBuffer[0]; // where we've gotten to return True; } else p += 3; } fParseBufferParseEnd = p - &fParseBuffer[0]; // where we've gotten to return False; // no luck this time } void MPEG2IFrameIndexFromTransportStream::compactParseBuffer() { #ifdef DEBUG envir() << "Compacting parse buffer: [" << fParseBufferFrameStart << "," << fParseBufferParseEnd << "," << fParseBufferDataEnd << "]"; #endif memmove(&fParseBuffer[0], &fParseBuffer[fParseBufferFrameStart], fParseBufferDataEnd - fParseBufferFrameStart); fParseBufferDataEnd -= fParseBufferFrameStart; fParseBufferParseEnd -= fParseBufferFrameStart; fParseBufferFrameStart = 0; #ifdef DEBUG envir() << "-> [" << fParseBufferFrameStart << "," << fParseBufferParseEnd << "," << fParseBufferDataEnd << "]\n"; #endif } void MPEG2IFrameIndexFromTransportStream::addToTail(IndexRecord* newIndexRecord) { #ifdef DEBUG envir() << "adding new: " << *newIndexRecord << "\n"; #endif if (fTailIndexRecord == NULL) { fHeadIndexRecord = fTailIndexRecord = newIndexRecord; } else { newIndexRecord->addAfter(fTailIndexRecord); fTailIndexRecord = newIndexRecord; } } ////////// IndexRecord implementation ////////// IndexRecord::IndexRecord(u_int8_t startOffset, u_int8_t size, unsigned long transportPacketNumber, float pcr) : fNext(this), fPrev(this), fRecordType(RECORD_UNPARSED), fStartOffset(startOffset), fSize(size), fPCR(pcr), fTransportPacketNumber(transportPacketNumber) { } IndexRecord::~IndexRecord() { IndexRecord* nextRecord = next(); unlink(); if (nextRecord != this) delete nextRecord; } void IndexRecord::addAfter(IndexRecord* prev) { fNext = prev->fNext; fPrev = prev; prev->fNext->fPrev = this; prev->fNext = this; } void IndexRecord::unlink() { fNext->fPrev = fPrev; fPrev->fNext = fNext; fNext = fPrev = this; }