blob: 62c6e2621d8f348060591fd48aea536e84265124 [file] [log] [blame]
/**********
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();
}