| 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 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 |  | 
| 25 | enum 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 |  | 
| 46 | class IndexRecord { | 
| 47 | public: | 
| 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 |  | 
| 63 | private: | 
| 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 | 
| 77 | static 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 |  | 
| 98 | UsageEnvironment& 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 |  | 
| 109 | MPEG2IFrameIndexFromTransportStream* | 
| 110 | MPEG2IFrameIndexFromTransportStream::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 |  | 
| 125 | MPEG2IFrameIndexFromTransportStream | 
| 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 |  | 
| 139 | MPEG2IFrameIndexFromTransportStream::~MPEG2IFrameIndexFromTransportStream() { | 
| 140 |   delete fHeadIndexRecord; | 
| 141 |   delete[] fParseBuffer; | 
| 142 | } | 
| 143 |  | 
| 144 | void 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 |  | 
| 173 | void 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 |  | 
| 186 | void 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  | 
| 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  = 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 |  | 
| 285 | void 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 |  | 
| 298 | void 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 |  | 
| 317 | void 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 |  | 
| 331 | void 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 |  | 
| 365 | Boolean 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 |  | 
| 423 | Boolean 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 |  | 
| 610 | Boolean 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 |  | 
| 629 | void 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 |  | 
| 645 | void 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 |  | 
| 659 | IndexRecord::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 |  | 
| 666 | IndexRecord::~IndexRecord() { | 
| 667 |   IndexRecord* nextRecord = next(); | 
| 668 |   unlink(); | 
| 669 |   if (nextRecord != this) delete nextRecord; | 
| 670 | } | 
| 671 |  | 
| 672 | void IndexRecord::addAfter(IndexRecord* prev) { | 
| 673 |   fNext = prev->fNext; | 
| 674 |   fPrev = prev; | 
| 675 |   prev->fNext->fPrev = this; | 
| 676 |   prev->fNext = this; | 
| 677 | } | 
| 678 |  | 
| 679 | void IndexRecord::unlink() { | 
| 680 |   fNext->fPrev = fPrev; | 
| 681 |   fPrev->fNext = fNext; | 
| 682 |   fNext = fPrev = this; | 
| 683 | } | 
| 684 |  |