//////////////////////////////////////////////////////////////////////////// /// /// De-packetize incoming data and pass it to the elementary stream handler. /// /// \return Collator status code, CollatorNoError indicates success. /// CollatorStatus_t Collator_PesAudio_c::Input(PlayerInputDescriptor_t *Input, unsigned int DataLength, void *Data, bool NonBlocking, unsigned int *DataLengthRemaining) { CollatorStatus_t Status; // st_relayfs_write(ST_RELAY_TYPE_PES_AUDIO_BUFFER, ST_RELAY_SOURCE_AUDIO_COLLATOR, (unsigned char *)Data, DataLength, 0); COLLATOR_ASSERT(!NonBlocking); AssertComponentState("Collator_PesAudio_c::Input", ComponentRunning); InputEntry(Input, DataLength, Data, NonBlocking); ActOnInputDescriptor(Input); // // Copy our arguments to class members so the utility functions can // act upon it. // RemainingData = (unsigned char *)Data; RemainingLength = DataLength; // // Continually handle units of input until all input is exhausted (or an error occurs). // // Be aware that our helper functions may, during their execution, cause state changes // that result in a different branch being taken next time round the loop. // Status = CollatorNoError; while (Status == CollatorNoError && RemainingLength != 0) { //COLLATOR_DEBUG("De-PESing loop has %d bytes remaining\n", RemainingLength); //report_dump_hex( severity_note, RemainingData, min(RemainingLength, 188), 32, 0); if (SeekingPesHeader) { // // Try to lock to incoming PES headers // Status = SearchForPesHeader(); } else if (GotPartialPesHeader) { // // Read in the remains of the PES header // Status = ReadPartialPesHeader(); } else { // // Send the PES packet for frame level analysis // Status = ReadPesPacket(); } } if (Status != CollatorNoError) { // if anything when wrong then we need to resynchronize COLLATOR_DEBUG("General failure; seeking new PES header\n"); DiscardAccumulatedData(); SeekingPesHeader = true; } InputExit(); return Status; }
//////////////////////////////////////////////////////////////////////////// /// /// Accumulate incoming data until we have the full PES header. /// /// Strictly speaking this method handles two sub-states. In the first state /// we do not have sufficient data accumulated to determine how long the PES /// header is. In the second we still don't have a complete PES packet but /// at least we know how much more data we need. /// /// This code assumes that PES packet uses >=9 bytes PES headers rather than /// the 6 byte headers found in the program stream map, padding stream, /// private stream 2, etc. /// /// \return Collator status code, CollatorNoError indicates success. /// CollatorStatus_t Collator_PesAudio_c::ReadPartialPesHeader(void) { CollatorStatus_t Status; unsigned int PesHeaderBytes, BytesNeeded, BytesToRead; unsigned char PesPrivateData[MAX_PES_PRIVATE_DATA_LENGTH]; // if (GotPartialPesHeaderBytes < PES_INITIAL_HEADER_SIZE) { COLLATOR_DEBUG("Waiting for first part of PES header\n"); StoredPesHeader = BufferBase + AccumulatedDataSize - GotPartialPesHeaderBytes; BytesToRead = min(RemainingLength, PES_INITIAL_HEADER_SIZE - GotPartialPesHeaderBytes); Status = AccumulateData(BytesToRead, RemainingData); if (Status == CollatorNoError) { GotPartialPesHeaderBytes += BytesToRead; RemainingData += BytesToRead; RemainingLength -= BytesToRead; } else { COLLATOR_DEBUG("Cannot accumulate data #6 (%d)\n", Status); } return Status; } // // We now have accumulated sufficient data to know how long the PES header actually is! // // pass the stream_id field to the collator sub-class (might update Configuration.ExtendedHeaderLength) SetPesPrivateDataLength(StoredPesHeader[3]); PesHeaderBytes = PES_INITIAL_HEADER_SIZE + StoredPesHeader[8] + Configuration.ExtendedHeaderLength; BytesNeeded = PesHeaderBytes - GotPartialPesHeaderBytes; BytesToRead = min(RemainingLength, BytesNeeded); Status = AccumulateData(BytesToRead, RemainingData); if (Status == CollatorNoError) { GotPartialPesHeaderBytes += BytesToRead; RemainingData += BytesToRead; RemainingLength -= BytesToRead; COLLATOR_DEBUG("BytesNeeded %d; BytesToRead %d\n", BytesNeeded, BytesToRead); if (BytesNeeded == BytesToRead) { // // Woo hoo! We've got the whole header, examine it and change state // COLLATOR_DEBUG("Got entire PES header\n"); //report_dump_hex( severity_note, StoredPesHeader, PesHeaderBytes, 32, 0); Status = CollatorNoError; // strictly speaking this is a no-op but the code might change if (StoredPesHeader[0] != 0x00 || StoredPesHeader[1] != 0x00 || StoredPesHeader[2] != 0x01 || CollatorNoError != (Status = ReadPesHeader())) { COLLATOR_DEBUG("%s; seeking new PES header", (Status == CollatorNoError ? "Start code not where expected" : "Badly formed PES header")); SeekingPesHeader = true; DiscardAccumulatedData(); // we have contained the error by changing states... return CollatorNoError; } // // Placeholder: Generic stream id based PES filtering (configured by sub-class) could be inserted // here (set DiscardPesPacket to true to discard). // if (Configuration.ExtendedHeaderLength) { // store a pointer to the PES private header. it is located just above the end of the // accumulated data and is will be safely accumulated providing the private header is // smaller than the rest of the PES packet. if a very large PES private header is // encountered we will need to introduce a temporary buffer to store the header in. if (Configuration.ExtendedHeaderLength <= MAX_PES_PRIVATE_DATA_LENGTH) { memcpy(PesPrivateData, BufferBase + AccumulatedDataSize - Configuration.ExtendedHeaderLength, Configuration.ExtendedHeaderLength); } else { COLLATOR_ERROR("Implementation error: Pes Private data area too big for temporay buffer\n"); } Status = HandlePesPrivateData(PesPrivateData); if (Status != CollatorNoError) { COLLATOR_ERROR("Unhandled error when parsing PES private data\n"); return (Status); } } // discard the actual PES packet from the accumulate buffer AccumulatedDataSize -= PesHeaderBytes; // record the number of bytes we need to ignore before we reach the next start code PesPayloadRemaining = PesPayloadLength; // switch states and absorb the packet COLLATOR_DEBUG("Discovered PES packet (header %d bytes, payload %d bytes)\n", PesPacketLength - PesPayloadLength + 6, PesPayloadLength); GotPartialPesHeader = false; if (PassPesPrivateDataToElementaryStreamHandler && Configuration.ExtendedHeaderLength) { // update PesPacketLength (to ensure that GetOffsetIntoPacket gives the correct value) PesPayloadLength += Configuration.ExtendedHeaderLength; Status = HandleElementaryStream(Configuration.ExtendedHeaderLength, PesPrivateData); if (Status != CollatorNoError) { COLLATOR_ERROR("Failed not accumulate the PES private data area\n"); } } } } else { COLLATOR_DEBUG("Cannot accumulate data #7 (%d)\n", Status); } return Status; }
//////////////////////////////////////////////////////////////////////////// /// /// Handle a block of data that is not frame aligned. /// /// There may be the end of a frame, whole frames, the start of a frame or even just /// the middle of the frame. /// /// If we have incomplete blocks we build up a complete one in the saved data, /// in order to process we need to acquire a frame plus the next header (for sync check) /// we can end up with the save buffer having a frame + part of a header, and a secondary /// save with just part of a header. /// /// \return Collator status code, CollatorNoError indicates success. /// CollatorStatus_t Collator_PesAudio_c::HandleElementaryStream(unsigned int Length, unsigned char *Data) { CollatorStatus_t Status; // if (DiscardPesPacket) { COLLATOR_DEBUG("Discarding %d bytes of elementary stream\n", Length); return CodecNoError; } // // Copy our arguments to class members so the utility functions can // act upon it. // RemainingElementaryOrigin = Data; RemainingElementaryData = Data; RemainingElementaryLength = Length; // // Continually handle units of input until all input is exhausted (or an error occurs). // // Be aware that our helper functions may, during their execution, cause state changes // that result in a different branch being taken next time round the loop. // Status = CollatorNoError; while (Status == CollatorNoError && RemainingElementaryLength != 0) { COLLATOR_DEBUG("ES loop has %d bytes remaining\n", RemainingElementaryLength); //report_dump_hex( severity_note, RemainingElementaryData, min(RemainingElementaryLength, 188), 32, 0); switch (CollatorState) { case SeekingSyncWord: // // Try to lock to incoming frame headers // Status = SearchForSyncWord(); break; case GotSynchronized: case SeekingFrameEnd: // // Read in the remains of the frame header // Status = ReadPartialFrameHeader(); break; case ReadSubFrame: case SkipSubFrame: // // Squirrel away the frame // Status = ReadFrame(); break; case GotCompleteFrame: // // Pass the accumulated subframes to the frame parser // InternalFrameFlush(); CollatorState = ReadSubFrame; break; default: // should not occur... COLLATOR_DEBUG("General failure; wrong collator state"); Status = CollatorError; break; } } if (Status != CollatorNoError) { // if anything when wrong then we need to resynchronize COLLATOR_DEBUG("General failure; seeking new synchronization sequence\n"); DiscardAccumulatedData(); CollatorState = SeekingSyncWord; } return Status; }
//////////////////////////////////////////////////////////////////////////// /// /// Handle losing lock on the frame headers. /// /// This function is called to handle the data that was spuriously accumulated /// when the frame header was badly parsed. /// /// In principle this function is quite simple. We allocate a new accumulation buffer and /// use the currently accumulated data is the data source to run the elementary stream /// state machine. There is however a little extra logic to get rid of recursion. /// Specificially we change the error handling behaviour if this method is re-entered /// so that there error is reported back to the already executing copy of the method. /// /// \return Collator status code, CollatorNoError indicates success. /// CollatorStatus_t Collator_PesAudio_c::HandleMissingNextFrameHeader(void) { CollatorStatus_t Status; // // Mark the collator as having lost frame lock. // Yes! We really do want to do this before the re-entry checks. // CollatorState = SeekingSyncWord; // we really do want to do this before the re-entry checks AccumulatedFrameReady = false; // // Check for re-entry // if (AlreadyHandlingMissingNextFrameHeader) { COLLATOR_DEBUG("Re-entered the error recovery handler, initiating stack unwind\n"); return CollatorUnwindStack; } // // Check whether the sub-class wants trivial or aggressive error recovery // if (!ReprocessAccumulatedDataDuringErrorRecovery) { DiscardAccumulatedData(); return CollatorNoError; } // // Remember the original elementary stream pointers for when we return to 'normal' processing. // unsigned char *OldRemainingElementaryOrigin = RemainingElementaryOrigin; unsigned char *OldRemainingElementaryData = RemainingElementaryData; unsigned int OldRemainingElementaryLength = RemainingElementaryLength; // // Take ownership of the already accumulated data // Buffer_t ReprocessingDataBuffer = CodedFrameBuffer; unsigned char *ReprocessingData = BufferBase; unsigned int ReprocessingDataLength = AccumulatedDataSize; ReprocessingDataBuffer->SetUsedDataSize(ReprocessingDataLength); Status = ReprocessingDataBuffer->ShrinkBuffer(max(ReprocessingDataLength, 1)); if (Status != BufferNoError) { COLLATOR_ERROR("Failed to shrink the reprocessing buffer to size (%08x).\n", Status); // not fatal - we're merely wasting memory } // At the time of writing GetNewBuffer() doesn't check for leaks. This is good because otherwise // we wouldn't have transfer the ownership of the ReprocessingDataBuffer by making this call. Status = GetNewBuffer(); if (Status != CollatorNoError) { COLLATOR_ERROR("Cannot get new buffer during error recovery\n"); return CollatorError; } // // Remember that we are re-processing the previously accumulated elementary stream // AlreadyHandlingMissingNextFrameHeader = true; // // WARNING: From this point on we own the ReprocessingDataBuffer, have set the recursion avoidance // marker and may have damaged the RemainingElementaryData pointer. There should be no // short-circuit exit paths used after this point otherwise we risk avoiding the clean up // at the bottom of the method. // while (ReprocessingDataLength > 1) { // // Remove the first byte from the recovery buffer (to avoid detecting again the same start code). // ReprocessingData += 1; ReprocessingDataLength -= 1; // // Search for a start code in the reprocessing data. This allows us to throw away data that we // know will never need reprocessing which makes the recursion avoidance code more efficient. // RemainingElementaryOrigin = ReprocessingData; RemainingElementaryData = ReprocessingData; RemainingElementaryLength = ReprocessingDataLength; int CodeOffset; PotentialFrameHeaderLength = 0; // ensure no (now voided) historic data is considered by sub-class Status = FindNextSyncWord(&CodeOffset); if (Status == CodecNoError) { COLLATOR_ASSERT(CodeOffset >= 0); COLLATOR_DEBUG("Found start code during error recovery (byte %d of %d)\n", CodeOffset, ReprocessingDataLength); // We found a start code, snip off all preceding data ReprocessingData += CodeOffset; ReprocessingDataLength -= CodeOffset; } else { // We didn't find a start code, snip off everything except the last few bytes. This // final fragment may contain a partial start code so we want to pass if through the // elementary stream handler again. unsigned FinalBytes = min(ReprocessingDataLength, FrameHeaderLength - 1); COLLATOR_DEBUG("Found no start code during error recovery (processing final %d bytes of %d)\n", ReprocessingDataLength, FinalBytes); ReprocessingData += ReprocessingDataLength; ReprocessingDataLength = FinalBytes; ReprocessingData -= ReprocessingDataLength; } // // Process the elementary stream // Status = HandleElementaryStream(ReprocessingDataLength, ReprocessingData); if (CollatorNoError == Status) { COLLATOR_DEBUG("Error recovery completed, returning to normal processing\n"); // All data consumed and stored in the subsequent accumulation buffer break; // Success will propagate when we return Status } else if (CollatorUnwindStack == Status) { COLLATOR_DEBUG("Stack unwound successfully, re-trying error recovery\n"); // We found a frame header but lost lock again... let's have another go AccumulatedDataSize = 0; // make sure no accumulated data is carried round the loop continue; } else { COLLATOR_ERROR("Error handling elementary stream during error recovery\n"); break; // Failure will propagate when we return Status } } // // Free the buffer we just consumed and restore the original elementary stream pointers // RemainingElementaryOrigin = OldRemainingElementaryOrigin; RemainingElementaryData = OldRemainingElementaryData; RemainingElementaryLength = OldRemainingElementaryLength; (void) ReprocessingDataBuffer->DecrementReferenceCount(IdentifierCollator); AlreadyHandlingMissingNextFrameHeader = false; return Status; }
//{{{ Input //////////////////////////////////////////////////////////////////////////// /// /// Extract Frame length from Pes Private Data area if present. /// /// \return Collator status code, CollatorNoError indicates success. /// CollatorStatus_t Collator_PesFrame_c::Input(PlayerInputDescriptor_t *Input, unsigned int DataLength, void *Data, bool NonBlocking, unsigned int *DataLengthRemaining) { CollatorStatus_t Status = CollatorNoError; bool PrivateDataPresent; unsigned char *DataBlock = (unsigned char *)Data; unsigned char *PesHeader; unsigned char *PayloadStart; unsigned int PayloadLength; unsigned int PesLength; unsigned int Offset; COLLATOR_ASSERT(!NonBlocking); AssertComponentState("Collator_PesFrame_c::Input", ComponentRunning); InputEntry(Input, DataLength, Data, NonBlocking); Offset = 0; while (Offset < DataLength) { // Read the length of the payload PrivateDataPresent = false; PesHeader = DataBlock + Offset; PesLength = (PesHeader[4] << 8) + PesHeader[5]; if (PesLength != 0) PayloadLength = PesLength - PesHeader[8] - 3; else PayloadLength = 0; COLLATOR_DEBUG("DataLength %d, PesLength %d; PayloadLength %d, Offset %d\n", DataLength, PesLength, PayloadLength, Offset); Offset += PesLength + 6; // PES packet is PesLength + 6 bytes long Bits.SetPointer(PesHeader + 9); // Set bits pointer ready to process optional fields if ((PesHeader[7] & 0x80) == 0x80) // PTS present? //{{{ read PTS { Bits.FlushUnseen(4); PlaybackTime = (unsigned long long)(Bits.Get(3)) << 30; Bits.FlushUnseen(1); PlaybackTime |= Bits.Get(15) << 15; Bits.FlushUnseen(1); PlaybackTime |= Bits.Get(15); Bits.FlushUnseen(1); PlaybackTimeValid = true; COLLATOR_DEBUG("PTS %llu.\n", PlaybackTime); } //}}} if ((PesHeader[7] & 0xC0) == 0xC0) // DTS present? //{{{ read DTS { Bits.FlushUnseen(4); DecodeTime = (unsigned long long)(Bits.Get(3)) << 30; Bits.FlushUnseen(1); DecodeTime |= Bits.Get(15) << 15; Bits.FlushUnseen(1); DecodeTime |= Bits.Get(15); Bits.FlushUnseen(1); DecodeTimeValid = true; } //}}} else if ((PesHeader[7] & 0xC0) == 0x40) { COLLATOR_ERROR("Malformed pes header contains DTS without PTS.\n"); DiscardAccumulatedData(); // throw away previous frame as incomplete InputExit(); return CollatorError; } //}}} //{{{ walk down optional bits if ((PesHeader[7] & 0x20) == 0x20) // ESCR present Bits.FlushUnseen(48); // Size of ESCR if ((PesHeader[7] & 0x10) == 0x10) // ES Rate present Bits.FlushUnseen(24); // Size of ES Rate if ((PesHeader[7] & 0x08) == 0x08) // Trick mode control present Bits.FlushUnseen(8); // Size of Trick mode control if ((PesHeader[7] & 0x04) == 0x04) // Additional copy info present Bits.FlushUnseen(8); // Size of additional copy info if ((PesHeader[7] & 0x02) == 0x02) // PES CRC present Bits.FlushUnseen(16); // Size of previous packet CRC if ((PesHeader[7] & 0x01) == 0x01) // PES Extension flag { PrivateDataPresent = Bits.Get(1); Bits.FlushUnseen(7); // Size of Pes extension data } //}}} if (PrivateDataPresent) { if (RemainingDataLength != 0) { COLLATOR_ERROR("%s: Warning new frame indicated but %d bytes missing\n", __FUNCTION__, RemainingDataLength); DiscardAccumulatedData(); // throw away previous frame as incomplete } FrameSize = Bits.Get(8) + (Bits.Get(8) << 8) + (Bits.Get(8) << 16); RemainingDataLength = FrameSize; COLLATOR_DEBUG("%s: PlaybackTimeValid %d, PlaybackTime %llx, FrameSize %d\n", __FUNCTION__, PlaybackTimeValid, PlaybackTime, FrameSize); } if ((int)PayloadLength > RemainingDataLength) // Too much data - have some packets been lost? { if (RemainingDataLength != 0) { COLLATOR_ERROR("%s: Warning packet contains more bytes than needed %d bytes missing?\n", __FUNCTION__, RemainingDataLength); DiscardAccumulatedData(); // throw away previous frame as incomplete //RemainingDataLength = 0; //InputExit(); //return CollatorError; } RemainingDataLength = PayloadLength; // assume new packet is stand alone frame } AccumulateStartCode(PackStartCode(AccumulatedDataSize, 0x42)); PayloadStart = PesHeader + (PesLength + 6 - PayloadLength); Status = AccumulateData(PayloadLength, (unsigned char *)PayloadStart); if (Status != CollatorNoError) { InputExit(); return Status; } RemainingDataLength -= PayloadLength; if (RemainingDataLength <= 0) { Status = InternalFrameFlush(); // flush out collected frame if (Status != CollatorNoError) { InputExit(); return Status; } } COLLATOR_DEBUG("%s PrivateDataPresent %d, RemainingDataLength %d, PayloadLength %d\n", __FUNCTION__, PrivateDataPresent, RemainingDataLength, PayloadLength); } InputExit(); return CollatorNoError; }
//}}} //{{{ Input //////////////////////////////////////////////////////////////////////////// /// /// Extract Frame length from Pes Private Data area if present. /// /// \return Collator status code, CollatorNoError indicates success. /// CollatorStatus_t Collator_PesVideoMjpeg_c::Input(PlayerInputDescriptor_t *Input, unsigned int DataLength, void *Data, bool NonBlocking, unsigned int *DataLengthRemaining) { CollatorStatus_t Status = CollatorNoError; unsigned char *DataBlock = (unsigned char *)Data; unsigned char *PesHeader; unsigned int PesLength; unsigned int PayloadLength; unsigned int Offset; unsigned int CodeOffset; unsigned int Code; AssertComponentState("Collator_PacketPes_c::Input", ComponentRunning); COLLATOR_ASSERT(!NonBlocking); InputEntry(Input, DataLength, Data, NonBlocking); Offset = 0; RemainingData = (unsigned char *)Data; RemainingLength = DataLength; TerminationFlagIsSet = false; Offset = 0; while (Offset < DataLength) { // Read the length of the payload PesHeader = DataBlock + Offset; PesLength = (PesHeader[4] << 8) + PesHeader[5]; if (PesLength != 0) PayloadLength = PesLength - PesHeader[8] - 3; else PayloadLength = 0; COLLATOR_DEBUG("DataLength %d, PesLength %d; PayloadLength %d, Offset %d\n", DataLength, PesLength, PayloadLength, Offset); Offset += PesLength + 6; // PES packet is PesLength + 6 bytes long Bits.SetPointer(PesHeader + 9); // Set bits pointer ready to process optional fields if ((PesHeader[7] & 0x80) == 0x80) // PTS present? //{{{ read PTS { Bits.FlushUnseen(4); PlaybackTime = (unsigned long long)(Bits.Get(3)) << 30; Bits.FlushUnseen(1); PlaybackTime |= Bits.Get(15) << 15; Bits.FlushUnseen(1); PlaybackTime |= Bits.Get(15); Bits.FlushUnseen(1); PlaybackTimeValid = true; COLLATOR_DEBUG("PTS %llu.\n", PlaybackTime); } //}}} if ((PesHeader[7] & 0xC0) == 0xC0) // DTS present? //{{{ read DTS { Bits.FlushUnseen(4); DecodeTime = (unsigned long long)(Bits.Get(3)) << 30; Bits.FlushUnseen(1); DecodeTime |= Bits.Get(15) << 15; Bits.FlushUnseen(1); DecodeTime |= Bits.Get(15); Bits.FlushUnseen(1); DecodeTimeValid = true; } //}}} else if ((PesHeader[7] & 0xC0) == 0x40) { COLLATOR_ERROR("Malformed pes header contains DTS without PTS.\n"); DiscardAccumulatedData(); // throw away previous frame as incomplete InputExit(); return CollatorError; } RemainingData = PesHeader + (PesLength + 6 - PayloadLength); RemainingLength = PayloadLength; while (RemainingLength > 0) { Status = FindNextStartCode(&CodeOffset); if (Status != CollatorNoError) // Error indicates no start code found { Status = AccumulateData(RemainingLength, RemainingData); if (Status != CollatorNoError) DiscardAccumulatedData(); RemainingLength = 0; break; } // Got one accumulate up to and including it Status = AccumulateData(CodeOffset + 2, RemainingData); if (Status != CollatorNoError) { DiscardAccumulatedData(); break; } Code = RemainingData[CodeOffset + 1]; RemainingLength -= CodeOffset + 2; RemainingData += CodeOffset + 2; // Is it a block terminate code if ((Code == Configuration.BlockTerminateCode) && (AccumulatedDataSize > 2)) { AccumulatedDataSize -= 2; Status = InternalFrameFlush((Configuration.StreamTerminateFlushesFrame && (Code == Configuration.StreamTerminationCode))); if (Status != CollatorNoError) break; BufferBase[0] = 0xff; BufferBase[1] = Code; AccumulatedDataSize = 2; } // Accumulate the start code Status = AccumulateStartCode(PackStartCode(AccumulatedDataSize - 2, Code)); if (Status != CollatorNoError) { DiscardAccumulatedData(); InputExit(); return Status; } } } InputExit(); return CollatorNoError; }