blob: b668e395b87b3e61d600aabba4cfc96d4d09fa28 [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 server demultiplexer for a MPEG 1 or 2 Program Stream
// Implementation
#include "MPEG1or2FileServerDemux.hh"
#include "MPEG1or2DemuxedServerMediaSubsession.hh"
#include "ByteStreamFileSource.hh"
MPEG1or2FileServerDemux*
MPEG1or2FileServerDemux::createNew(UsageEnvironment& env, char const* fileName,
Boolean reuseFirstSource) {
return new MPEG1or2FileServerDemux(env, fileName, reuseFirstSource);
}
static float MPEG1or2ProgramStreamFileDuration(UsageEnvironment& env,
char const* fileName,
unsigned& fileSize); // forward
MPEG1or2FileServerDemux
::MPEG1or2FileServerDemux(UsageEnvironment& env, char const* fileName,
Boolean reuseFirstSource)
: Medium(env),
fReuseFirstSource(reuseFirstSource),
fSession0Demux(NULL), fLastCreatedDemux(NULL), fLastClientSessionId(~0) {
fFileName = strDup(fileName);
fFileDuration = MPEG1or2ProgramStreamFileDuration(env, fileName, fFileSize);
}
MPEG1or2FileServerDemux::~MPEG1or2FileServerDemux() {
Medium::close(fSession0Demux);
delete[] (char*)fFileName;
}
ServerMediaSubsession*
MPEG1or2FileServerDemux::newAudioServerMediaSubsession() {
return MPEG1or2DemuxedServerMediaSubsession::createNew(*this, 0xC0, fReuseFirstSource);
}
ServerMediaSubsession*
MPEG1or2FileServerDemux::newVideoServerMediaSubsession(Boolean iFramesOnly,
double vshPeriod) {
return MPEG1or2DemuxedServerMediaSubsession::createNew(*this, 0xE0, fReuseFirstSource,
iFramesOnly, vshPeriod);
}
ServerMediaSubsession*
MPEG1or2FileServerDemux::newAC3AudioServerMediaSubsession() {
return MPEG1or2DemuxedServerMediaSubsession::createNew(*this, 0xBD, fReuseFirstSource);
// because, in a VOB file, the AC3 audio has stream id 0xBD
}
MPEG1or2DemuxedElementaryStream*
MPEG1or2FileServerDemux::newElementaryStream(unsigned clientSessionId,
u_int8_t streamIdTag) {
MPEG1or2Demux* demuxToUse;
if (clientSessionId == 0) {
// 'Session 0' is treated especially, because its audio & video streams
// are created and destroyed one-at-a-time, rather than both streams being
// created, and then (later) both streams being destroyed (as is the case
// for other ('real') session ids). Because of this, a separate demux is
// used for session 0, and its deletion is managed by us, rather than
// happening automatically.
if (fSession0Demux == NULL) {
// Open our input file as a 'byte-stream file source':
ByteStreamFileSource* fileSource
= ByteStreamFileSource::createNew(envir(), fFileName);
if (fileSource == NULL) return NULL;
fSession0Demux = MPEG1or2Demux::createNew(envir(), fileSource, False/*note!*/);
}
demuxToUse = fSession0Demux;
} else {
// First, check whether this is a new client session. If so, create a new
// demux for it:
if (clientSessionId != fLastClientSessionId) {
// Open our input file as a 'byte-stream file source':
ByteStreamFileSource* fileSource
= ByteStreamFileSource::createNew(envir(), fFileName);
if (fileSource == NULL) return NULL;
fLastCreatedDemux = MPEG1or2Demux::createNew(envir(), fileSource, True);
// Note: We tell the demux to delete itself when its last
// elementary stream is deleted.
fLastClientSessionId = clientSessionId;
// Note: This code relies upon the fact that the creation of streams for
// different client sessions do not overlap - so one "MPEG1or2Demux" is used
// at a time.
}
demuxToUse = fLastCreatedDemux;
}
if (demuxToUse == NULL) return NULL; // shouldn't happen
return demuxToUse->newElementaryStream(streamIdTag);
}
static Boolean getMPEG1or2TimeCode(FramedSource* dataSource,
MPEG1or2Demux& parentDemux,
Boolean returnFirstSeenCode,
float& timeCode); // forward
static float MPEG1or2ProgramStreamFileDuration(UsageEnvironment& env,
char const* fileName,
unsigned& fileSize) {
FramedSource* dataSource = NULL;
float duration = 0.0; // until we learn otherwise
fileSize = 0; // ditto
do {
// Open the input file as a 'byte-stream file source':
ByteStreamFileSource* fileSource = ByteStreamFileSource::createNew(env, fileName);
if (fileSource == NULL) break;
dataSource = fileSource;
fileSize = (unsigned)(fileSource->fileSize());
if (fileSize == 0) break;
// Create a MPEG demultiplexor that reads from that source.
MPEG1or2Demux* baseDemux = MPEG1or2Demux::createNew(env, dataSource, True);
if (baseDemux == NULL) break;
// Create, from this, a source that returns raw PES packets:
dataSource = baseDemux->newRawPESStream();
// Read the first time code from the file:
float firstTimeCode;
if (!getMPEG1or2TimeCode(dataSource, *baseDemux, True, firstTimeCode)) break;
// Then, read the last time code from the file.
// (Before doing this, flush the demux's input buffers,
// and seek towards the end of the file, for efficiency.)
baseDemux->flushInput();
unsigned const startByteFromEnd = 100000;
unsigned newFilePosition
= fileSize < startByteFromEnd ? 0 : fileSize - startByteFromEnd;
if (newFilePosition > 0) fileSource->seekToByteAbsolute(newFilePosition);
float lastTimeCode;
if (!getMPEG1or2TimeCode(dataSource, *baseDemux, False, lastTimeCode)) break;
// Take the difference between these time codes as being the file duration:
float timeCodeDiff = lastTimeCode - firstTimeCode;
if (timeCodeDiff < 0) break;
duration = timeCodeDiff;
} while (0);
Medium::close(dataSource);
return duration;
}
#define MFSD_DUMMY_SINK_BUFFER_SIZE (6+65535) /* large enough for a PES packet */
class MFSD_DummySink: public MediaSink {
public:
MFSD_DummySink(MPEG1or2Demux& demux, Boolean returnFirstSeenCode);
virtual ~MFSD_DummySink();
char watchVariable;
private:
// redefined virtual function:
virtual Boolean continuePlaying();
private:
static void afterGettingFrame(void* clientData, unsigned frameSize,
unsigned numTruncatedBytes,
struct timeval presentationTime,
unsigned durationInMicroseconds);
void afterGettingFrame1();
private:
MPEG1or2Demux& fOurDemux;
Boolean fReturnFirstSeenCode;
unsigned char fBuf[MFSD_DUMMY_SINK_BUFFER_SIZE];
};
static void afterPlayingMFSD_DummySink(MFSD_DummySink* sink); // forward
static float computeSCRTimeCode(MPEG1or2Demux::SCR const& scr); // forward
static Boolean getMPEG1or2TimeCode(FramedSource* dataSource,
MPEG1or2Demux& parentDemux,
Boolean returnFirstSeenCode,
float& timeCode) {
// Start reading through "dataSource", until we see a SCR time code:
parentDemux.lastSeenSCR().isValid = False;
UsageEnvironment& env = dataSource->envir(); // alias
MFSD_DummySink sink(parentDemux, returnFirstSeenCode);
sink.startPlaying(*dataSource,
(MediaSink::afterPlayingFunc*)afterPlayingMFSD_DummySink, &sink);
env.taskScheduler().doEventLoop(&sink.watchVariable);
timeCode = computeSCRTimeCode(parentDemux.lastSeenSCR());
return parentDemux.lastSeenSCR().isValid;
}
////////// MFSD_DummySink implementation //////////
MFSD_DummySink::MFSD_DummySink(MPEG1or2Demux& demux, Boolean returnFirstSeenCode)
: MediaSink(demux.envir()),
watchVariable(0), fOurDemux(demux), fReturnFirstSeenCode(returnFirstSeenCode) {
}
MFSD_DummySink::~MFSD_DummySink() {
}
Boolean MFSD_DummySink::continuePlaying() {
if (fSource == NULL) return False; // sanity check
fSource->getNextFrame(fBuf, sizeof fBuf,
afterGettingFrame, this,
onSourceClosure, this);
return True;
}
void MFSD_DummySink::afterGettingFrame(void* clientData, unsigned /*frameSize*/,
unsigned /*numTruncatedBytes*/,
struct timeval /*presentationTime*/,
unsigned /*durationInMicroseconds*/) {
MFSD_DummySink* sink = (MFSD_DummySink*)clientData;
sink->afterGettingFrame1();
}
void MFSD_DummySink::afterGettingFrame1() {
if (fReturnFirstSeenCode && fOurDemux.lastSeenSCR().isValid) {
// We were asked to return the first SCR that we saw, and we've seen one,
// so we're done. (Handle this as if the input source had closed.)
onSourceClosure();
return;
}
continuePlaying();
}
static void afterPlayingMFSD_DummySink(MFSD_DummySink* sink) {
// Return from the "doEventLoop()" call:
sink->watchVariable = ~0;
}
static float computeSCRTimeCode(MPEG1or2Demux::SCR const& scr) {
double result = scr.remainingBits/90000.0 + scr.extension/300.0;
if (scr.highBit) {
// Add (2^32)/90000 == (2^28)/5625
double const highBitValue = (256*1024*1024)/5625.0;
result += highBitValue;
}
return (float)result;
}