| /********** |
| 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 3 of the License, or (at your |
| option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.) |
| |
| 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-2020 Live Networks, Inc. All rights reserved. |
| // A filter that converts a MPEG Transport Stream file - with corresponding index file |
| // - to a corresponding Video Elementary Stream. It also uses a "scale" parameter |
| // to implement 'trick mode' (fast forward or reverse play, using I-frames) on |
| // the video stream. |
| // Implementation |
| |
| #include "MPEG2TransportStreamTrickModeFilter.hh" |
| #include <ByteStreamFileSource.hh> |
| |
| // Define the following to be True if we want the output file to have the same frame rate as the original file. |
| // (Because the output file contains I-frames only, this means that each I-frame will appear in the output file |
| // several times, and therefore the output file's bitrate will be significantly higher than that of the original.) |
| // Define the following to be False if we want the output file to include each I-frame no more than once. |
| // (This means that - except for high 'scale' values - both the output frame rate and the output bit rate |
| // will be less than that of the original.) |
| #define KEEP_ORIGINAL_FRAME_RATE False |
| |
| MPEG2TransportStreamTrickModeFilter* MPEG2TransportStreamTrickModeFilter |
| ::createNew(UsageEnvironment& env, FramedSource* inputSource, |
| MPEG2TransportStreamIndexFile* indexFile, int scale) { |
| return new MPEG2TransportStreamTrickModeFilter(env, inputSource, indexFile, scale); |
| } |
| |
| MPEG2TransportStreamTrickModeFilter |
| ::MPEG2TransportStreamTrickModeFilter(UsageEnvironment& env, FramedSource* inputSource, |
| MPEG2TransportStreamIndexFile* indexFile, int scale) |
| : FramedFilter(env, inputSource), |
| fHaveStarted(False), fIndexFile(indexFile), fScale(scale), fDirection(1), |
| fState(SKIPPING_FRAME), fFrameCount(0), |
| fNextIndexRecordNum(0), fNextTSPacketNum(0), |
| fCurrentTSPacketNum((unsigned long)(-1)), fUseSavedFrameNextTime(False) { |
| if (fScale < 0) { // reverse play |
| fScale = -fScale; |
| fDirection = -1; |
| } |
| } |
| |
| MPEG2TransportStreamTrickModeFilter::~MPEG2TransportStreamTrickModeFilter() { |
| } |
| |
| Boolean MPEG2TransportStreamTrickModeFilter::seekTo(unsigned long tsPacketNumber, |
| unsigned long indexRecordNumber) { |
| seekToTransportPacket(tsPacketNumber); |
| fNextIndexRecordNum = indexRecordNumber; |
| return True; |
| } |
| |
| #define isIFrameStart(type) ((type) == 0x81/*actually, a VSH*/ || (type) == 0x85/*actually, a SPS, for H.264*/ || (type) == 0x8B/*actually, a VPS, for H.265*/) |
| // This relies upon I-frames always being preceded by a VSH+GOP (for MPEG-2 data), |
| // by a SPS (for H.264 data), or by a VPS (for H.265 data) |
| #define isNonIFrameStart(type) ((type) == 0x83 || (type) == 0x88/*for H.264*/ || (type) == 0x8E/*for H.265*/) |
| |
| void MPEG2TransportStreamTrickModeFilter::doGetNextFrame() { |
| // fprintf(stderr, "#####DGNF1\n"); |
| // If our client's buffer size is too small, then deliver |
| // a 0-byte 'frame', to tell it to process all of the data that it has |
| // already read, before asking for more data from us: |
| if (fMaxSize < TRANSPORT_PACKET_SIZE) { |
| fFrameSize = 0; |
| afterGetting(this); |
| return; |
| } |
| |
| while (1) { |
| // Get the next record from our index file. |
| // This tells us the type of frame this data is, which Transport Stream packet |
| // (from the input source) the data comes from, and where in the Transport Stream |
| // packet it comes from: |
| u_int8_t recordType; |
| float recordPCR; |
| Boolean endOfIndexFile = False; |
| if (!fIndexFile->readIndexRecordValues(fNextIndexRecordNum, |
| fDesiredTSPacketNum, fDesiredDataOffset, |
| fDesiredDataSize, recordPCR, |
| recordType)) { |
| // We ran off the end of the index file. If we're not delivering a |
| // pre-saved frame, then handle this the same way as if the |
| // input Transport Stream source ended. |
| if (fState != DELIVERING_SAVED_FRAME) { |
| onSourceClosure1(); |
| return; |
| } |
| endOfIndexFile = True; |
| } else if (!fHaveStarted) { |
| fFirstPCR = recordPCR; |
| fHaveStarted = True; |
| } |
| // fprintf(stderr, "#####read index record %ld: ts %ld: %c, PCR %f\n", fNextIndexRecordNum, fDesiredTSPacketNum, isIFrameStart(recordType) ? 'I' : isNonIFrameStart(recordType) ? 'j' : 'x', recordPCR); |
| fNextIndexRecordNum |
| += (fState == DELIVERING_SAVED_FRAME) ? 1 : fDirection; |
| |
| // Handle this index record, depending on the record type and our current state: |
| switch (fState) { |
| case SKIPPING_FRAME: |
| case SAVING_AND_DELIVERING_FRAME: { |
| // if (fState == SKIPPING_FRAME) fprintf(stderr, "\tSKIPPING_FRAME\n"); else fprintf(stderr, "\tSAVING_AND_DELIVERING_FRAME\n");//##### |
| if (isIFrameStart(recordType)) { |
| // Save a record of this frame: |
| fSavedFrameIndexRecordStart = fNextIndexRecordNum - fDirection; |
| fUseSavedFrameNextTime = True; |
| // fprintf(stderr, "\trecording\n");//##### |
| if ((fFrameCount++)%fScale == 0 && fUseSavedFrameNextTime) { |
| // A frame is due now. |
| fFrameCount = 1; // reset to avoid overflow |
| if (fDirection > 0) { |
| // Begin delivering this frame, as we're scanning it: |
| fState = SAVING_AND_DELIVERING_FRAME; |
| // fprintf(stderr, "\tdelivering\n");//##### |
| fDesiredDataPCR = recordPCR; // use this frame's PCR |
| attemptDeliveryToClient(); |
| return; |
| } else { |
| // Deliver this frame, then resume normal scanning: |
| // (This relies on the index records having begun with an I-frame.) |
| fState = DELIVERING_SAVED_FRAME; |
| fSavedSequentialIndexRecordNum = fNextIndexRecordNum; |
| fDesiredDataPCR = recordPCR; |
| // use this frame's (not the saved frame's) PCR |
| fNextIndexRecordNum = fSavedFrameIndexRecordStart; |
| // fprintf(stderr, "\tbeginning delivery of saved frame\n");//##### |
| } |
| } else { |
| // No frame is needed now: |
| fState = SKIPPING_FRAME; |
| } |
| } else if (isNonIFrameStart(recordType)) { |
| if ((fFrameCount++)%fScale == 0 && fUseSavedFrameNextTime) { |
| // A frame is due now, so begin delivering the one that we had saved: |
| // (This relies on the index records having begun with an I-frame.) |
| fFrameCount = 1; // reset to avoid overflow |
| fState = DELIVERING_SAVED_FRAME; |
| fSavedSequentialIndexRecordNum = fNextIndexRecordNum; |
| fDesiredDataPCR = recordPCR; |
| // use this frame's (not the saved frame's) PCR |
| fNextIndexRecordNum = fSavedFrameIndexRecordStart; |
| // fprintf(stderr, "\tbeginning delivery of saved frame\n");//##### |
| } else { |
| // No frame is needed now: |
| fState = SKIPPING_FRAME; |
| } |
| } else { |
| // Not the start of a frame, but deliver it, if it's needed: |
| if (fState == SAVING_AND_DELIVERING_FRAME) { |
| // fprintf(stderr, "\tdelivering\n");//##### |
| fDesiredDataPCR = recordPCR; // use this frame's PCR |
| attemptDeliveryToClient(); |
| return; |
| } |
| } |
| break; |
| } |
| case DELIVERING_SAVED_FRAME: { |
| // fprintf(stderr, "\tDELIVERING_SAVED_FRAME\n");//##### |
| if (endOfIndexFile |
| || (isIFrameStart(recordType) |
| && fNextIndexRecordNum-1 != fSavedFrameIndexRecordStart) |
| || isNonIFrameStart(recordType)) { |
| // fprintf(stderr, "\tended delivery of saved frame\n");//##### |
| // We've reached the end of the saved frame, so revert to the |
| // original sequence of index records: |
| fNextIndexRecordNum = fSavedSequentialIndexRecordNum; |
| fUseSavedFrameNextTime = KEEP_ORIGINAL_FRAME_RATE; |
| fState = SKIPPING_FRAME; |
| } else { |
| // Continue delivering: |
| // fprintf(stderr, "\tdelivering\n");//##### |
| attemptDeliveryToClient(); |
| return; |
| } |
| break; |
| } |
| } |
| } |
| } |
| |
| void MPEG2TransportStreamTrickModeFilter::doStopGettingFrames() { |
| FramedFilter::doStopGettingFrames(); |
| fIndexFile->stopReading(); |
| } |
| |
| void MPEG2TransportStreamTrickModeFilter::attemptDeliveryToClient() { |
| if (fCurrentTSPacketNum == fDesiredTSPacketNum) { |
| // fprintf(stderr, "\t\tdelivering ts %d:%d, %d bytes, PCR %f\n", fCurrentTSPacketNum, fDesiredDataOffset, fDesiredDataSize, fDesiredDataPCR);//##### |
| // We already have the Transport Packet that we want. Deliver its data: |
| memmove(fTo, &fInputBuffer[fDesiredDataOffset], fDesiredDataSize); |
| fFrameSize = fDesiredDataSize; |
| float deliveryPCR = fDirection*(fDesiredDataPCR - fFirstPCR)/fScale; |
| if (deliveryPCR < 0.0) deliveryPCR = 0.0; |
| fPresentationTime.tv_sec = (unsigned long)deliveryPCR; |
| fPresentationTime.tv_usec |
| = (unsigned long)((deliveryPCR - fPresentationTime.tv_sec)*1000000.0f); |
| // fprintf(stderr, "#####DGNF9\n"); |
| |
| afterGetting(this); |
| } else { |
| // Arrange to read the Transport Packet that we want: |
| readTransportPacket(fDesiredTSPacketNum); |
| } |
| } |
| |
| void MPEG2TransportStreamTrickModeFilter::seekToTransportPacket(unsigned long tsPacketNum) { |
| if (tsPacketNum == fNextTSPacketNum) return; // we're already there |
| |
| ByteStreamFileSource* tsFile = (ByteStreamFileSource*)fInputSource; |
| u_int64_t tsPacketNum64 = (u_int64_t)tsPacketNum; |
| tsFile->seekToByteAbsolute(tsPacketNum64*TRANSPORT_PACKET_SIZE); |
| |
| fNextTSPacketNum = tsPacketNum; |
| } |
| |
| void MPEG2TransportStreamTrickModeFilter::readTransportPacket(unsigned long tsPacketNum) { |
| seekToTransportPacket(tsPacketNum); |
| fInputSource->getNextFrame(fInputBuffer, TRANSPORT_PACKET_SIZE, |
| afterGettingFrame, this, |
| onSourceClosure, this); |
| } |
| |
| void MPEG2TransportStreamTrickModeFilter |
| ::afterGettingFrame(void* clientData, unsigned frameSize, |
| unsigned /*numTruncatedBytes*/, |
| struct timeval presentationTime, |
| unsigned /*durationInMicroseconds*/) { |
| MPEG2TransportStreamTrickModeFilter* filter = (MPEG2TransportStreamTrickModeFilter*)clientData; |
| filter->afterGettingFrame1(frameSize); |
| } |
| |
| void MPEG2TransportStreamTrickModeFilter::afterGettingFrame1(unsigned frameSize) { |
| if (frameSize != TRANSPORT_PACKET_SIZE) { |
| // Treat this as if the input source ended: |
| onSourceClosure1(); |
| return; |
| } |
| |
| fCurrentTSPacketNum = fNextTSPacketNum; // i.e., the one that we just read |
| ++fNextTSPacketNum; |
| |
| // Attempt deliver again: |
| attemptDeliveryToClient(); |
| } |
| |
| void MPEG2TransportStreamTrickModeFilter::onSourceClosure(void* clientData) { |
| MPEG2TransportStreamTrickModeFilter* filter = (MPEG2TransportStreamTrickModeFilter*)clientData; |
| filter->onSourceClosure1(); |
| } |
| |
| void MPEG2TransportStreamTrickModeFilter::onSourceClosure1() { |
| fIndexFile->stopReading(); |
| handleClosure(); |
| } |