353 lines
14 KiB
C++
Executable File
353 lines
14 KiB
C++
Executable File
/**********
|
|
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 2.1 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-2016 Live Networks, Inc. All rights reserved.
|
|
// A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
|
|
// on demand, from a MPEG-2 Transport Stream file.
|
|
// Implementation
|
|
|
|
#include "MPEG2TransportFileServerMediaSubsession.hh"
|
|
#include "SimpleRTPSink.hh"
|
|
|
|
MPEG2TransportFileServerMediaSubsession*
|
|
MPEG2TransportFileServerMediaSubsession::createNew(UsageEnvironment& env,
|
|
char const* fileName,
|
|
char const* indexFileName,
|
|
Boolean reuseFirstSource) {
|
|
MPEG2TransportStreamIndexFile* indexFile;
|
|
if (indexFileName != NULL && reuseFirstSource) {
|
|
// It makes no sense to support trick play if all clients use the same source. Fix this:
|
|
env << "MPEG2TransportFileServerMediaSubsession::createNew(): ignoring the index file name, because \"reuseFirstSource\" is set\n";
|
|
indexFile = NULL;
|
|
} else {
|
|
indexFile = MPEG2TransportStreamIndexFile::createNew(env, indexFileName);
|
|
}
|
|
return new MPEG2TransportFileServerMediaSubsession(env, fileName, indexFile,
|
|
reuseFirstSource);
|
|
}
|
|
|
|
MPEG2TransportFileServerMediaSubsession
|
|
::MPEG2TransportFileServerMediaSubsession(UsageEnvironment& env,
|
|
char const* fileName,
|
|
MPEG2TransportStreamIndexFile* indexFile,
|
|
Boolean reuseFirstSource)
|
|
: FileServerMediaSubsession(env, fileName, reuseFirstSource),
|
|
fIndexFile(indexFile), fDuration(0.0), fClientSessionHashTable(NULL) {
|
|
if (fIndexFile != NULL) { // we support 'trick play'
|
|
fDuration = fIndexFile->getPlayingDuration();
|
|
fClientSessionHashTable = HashTable::create(ONE_WORD_HASH_KEYS);
|
|
}
|
|
}
|
|
|
|
MPEG2TransportFileServerMediaSubsession
|
|
::~MPEG2TransportFileServerMediaSubsession() {
|
|
if (fIndexFile != NULL) { // we support 'trick play'
|
|
Medium::close(fIndexFile);
|
|
|
|
// Clean out the client session hash table:
|
|
while (1) {
|
|
ClientTrickPlayState* client
|
|
= (ClientTrickPlayState*)(fClientSessionHashTable->RemoveNext());
|
|
if (client == NULL) break;
|
|
delete client;
|
|
}
|
|
delete fClientSessionHashTable;
|
|
}
|
|
}
|
|
|
|
#define TRANSPORT_PACKET_SIZE 188
|
|
#define TRANSPORT_PACKETS_PER_NETWORK_PACKET 7
|
|
// The product of these two numbers must be enough to fit within a network packet
|
|
|
|
void MPEG2TransportFileServerMediaSubsession
|
|
::startStream(unsigned clientSessionId, void* streamToken, TaskFunc* rtcpRRHandler,
|
|
void* rtcpRRHandlerClientData, unsigned short& rtpSeqNum,
|
|
unsigned& rtpTimestamp,
|
|
ServerRequestAlternativeByteHandler* serverRequestAlternativeByteHandler,
|
|
void* serverRequestAlternativeByteHandlerClientData) {
|
|
if (fIndexFile != NULL) { // we support 'trick play'
|
|
ClientTrickPlayState* client = lookupClient(clientSessionId);
|
|
if (client != NULL && client->areChangingScale()) {
|
|
// First, handle this like a "PAUSE", except that we back up to the previous VSH
|
|
client->updateStateOnPlayChange(True);
|
|
OnDemandServerMediaSubsession::pauseStream(clientSessionId, streamToken);
|
|
|
|
// Then, adjust for the change of scale:
|
|
client->updateStateOnScaleChange();
|
|
}
|
|
}
|
|
|
|
// Call the original, default version of this routine:
|
|
OnDemandServerMediaSubsession::startStream(clientSessionId, streamToken,
|
|
rtcpRRHandler, rtcpRRHandlerClientData,
|
|
rtpSeqNum, rtpTimestamp,
|
|
serverRequestAlternativeByteHandler, serverRequestAlternativeByteHandlerClientData);
|
|
}
|
|
|
|
void MPEG2TransportFileServerMediaSubsession
|
|
::pauseStream(unsigned clientSessionId, void* streamToken) {
|
|
if (fIndexFile != NULL) { // we support 'trick play'
|
|
ClientTrickPlayState* client = lookupClient(clientSessionId);
|
|
if (client != NULL) {
|
|
client->updateStateOnPlayChange(False);
|
|
}
|
|
}
|
|
|
|
// Call the original, default version of this routine:
|
|
OnDemandServerMediaSubsession::pauseStream(clientSessionId, streamToken);
|
|
}
|
|
|
|
void MPEG2TransportFileServerMediaSubsession
|
|
::seekStream(unsigned clientSessionId, void* streamToken, double& seekNPT, double streamDuration, u_int64_t& numBytes) {
|
|
// Begin by calling the original, default version of this routine:
|
|
OnDemandServerMediaSubsession::seekStream(clientSessionId, streamToken, seekNPT, streamDuration, numBytes);
|
|
|
|
// Then, special handling specific to indexed Transport Stream files:
|
|
if (fIndexFile != NULL) { // we support 'trick play'
|
|
ClientTrickPlayState* client = lookupClient(clientSessionId);
|
|
if (client != NULL) {
|
|
unsigned long numTSPacketsToStream = client->updateStateFromNPT(seekNPT, streamDuration);
|
|
numBytes = numTSPacketsToStream*TRANSPORT_PACKET_SIZE;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MPEG2TransportFileServerMediaSubsession
|
|
::setStreamScale(unsigned clientSessionId, void* streamToken, float scale) {
|
|
if (fIndexFile != NULL) { // we support 'trick play'
|
|
ClientTrickPlayState* client = lookupClient(clientSessionId);
|
|
if (client != NULL) {
|
|
client->setNextScale(scale); // scale won't take effect until the next "PLAY"
|
|
}
|
|
}
|
|
|
|
// Call the original, default version of this routine:
|
|
OnDemandServerMediaSubsession::setStreamScale(clientSessionId, streamToken, scale);
|
|
}
|
|
|
|
void MPEG2TransportFileServerMediaSubsession
|
|
::deleteStream(unsigned clientSessionId, void*& streamToken) {
|
|
if (fIndexFile != NULL) { // we support 'trick play'
|
|
ClientTrickPlayState* client = lookupClient(clientSessionId);
|
|
if (client != NULL) {
|
|
client->updateStateOnPlayChange(False);
|
|
}
|
|
}
|
|
|
|
// Call the original, default version of this routine:
|
|
OnDemandServerMediaSubsession::deleteStream(clientSessionId, streamToken);
|
|
}
|
|
|
|
ClientTrickPlayState* MPEG2TransportFileServerMediaSubsession::newClientTrickPlayState() {
|
|
return new ClientTrickPlayState(fIndexFile);
|
|
}
|
|
|
|
FramedSource* MPEG2TransportFileServerMediaSubsession
|
|
::createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate) {
|
|
// Create the video source:
|
|
unsigned const inputDataChunkSize
|
|
= TRANSPORT_PACKETS_PER_NETWORK_PACKET*TRANSPORT_PACKET_SIZE;
|
|
ByteStreamFileSource* fileSource
|
|
= ByteStreamFileSource::createNew(envir(), fFileName, inputDataChunkSize);
|
|
if (fileSource == NULL) return NULL;
|
|
fFileSize = fileSource->fileSize();
|
|
|
|
// Use the file size and the duration to estimate the stream's bitrate:
|
|
if (fFileSize > 0 && fDuration > 0.0) {
|
|
estBitrate = (unsigned)((int64_t)fFileSize/(125*fDuration) + 0.5); // kbps, rounded
|
|
} else {
|
|
estBitrate = 5000; // kbps, estimate
|
|
}
|
|
|
|
|
|
// Create a framer for the Transport Stream:
|
|
MPEG2TransportStreamFramer* framer
|
|
= MPEG2TransportStreamFramer::createNew(envir(), fileSource);
|
|
|
|
if (fIndexFile != NULL) { // we support 'trick play'
|
|
// Keep state for this client (if we don't already have it):
|
|
ClientTrickPlayState* client = lookupClient(clientSessionId);
|
|
if (client == NULL) {
|
|
client = newClientTrickPlayState();
|
|
fClientSessionHashTable->Add((char const*)clientSessionId, client);
|
|
}
|
|
client->setSource(framer);
|
|
}
|
|
|
|
return framer;
|
|
}
|
|
|
|
RTPSink* MPEG2TransportFileServerMediaSubsession
|
|
::createNewRTPSink(Groupsock* rtpGroupsock,
|
|
unsigned char /*rtpPayloadTypeIfDynamic*/,
|
|
FramedSource* /*inputSource*/) {
|
|
return SimpleRTPSink::createNew(envir(), rtpGroupsock,
|
|
33, 90000, "video", "MP2T",
|
|
1, True, False /*no 'M' bit*/);
|
|
}
|
|
|
|
void MPEG2TransportFileServerMediaSubsession::testScaleFactor(float& scale) {
|
|
if (fIndexFile != NULL && fDuration > 0.0) {
|
|
// We support any integral scale, other than 0
|
|
int iScale = scale < 0.0 ? (int)(scale - 0.5f) : (int)(scale + 0.5f); // round
|
|
if (iScale == 0) iScale = 1;
|
|
scale = (float)iScale;
|
|
} else {
|
|
scale = 1.0f;
|
|
}
|
|
}
|
|
|
|
float MPEG2TransportFileServerMediaSubsession::duration() const {
|
|
return fDuration;
|
|
}
|
|
|
|
ClientTrickPlayState* MPEG2TransportFileServerMediaSubsession
|
|
::lookupClient(unsigned clientSessionId) {
|
|
return (ClientTrickPlayState*)(fClientSessionHashTable->Lookup((char const*)clientSessionId));
|
|
}
|
|
|
|
|
|
////////// ClientTrickPlayState implementation //////////
|
|
|
|
ClientTrickPlayState::ClientTrickPlayState(MPEG2TransportStreamIndexFile* indexFile)
|
|
: fIndexFile(indexFile),
|
|
fOriginalTransportStreamSource(NULL),
|
|
fTrickModeFilter(NULL), fTrickPlaySource(NULL),
|
|
fFramer(NULL),
|
|
fScale(1.0f), fNextScale(1.0f), fNPT(0.0f),
|
|
fTSRecordNum(0), fIxRecordNum(0) {
|
|
}
|
|
|
|
unsigned long ClientTrickPlayState::updateStateFromNPT(double npt, double streamDuration) {
|
|
fNPT = (float)npt;
|
|
// Map "fNPT" to the corresponding Transport Stream and Index record numbers:
|
|
unsigned long tsRecordNum, ixRecordNum;
|
|
fIndexFile->lookupTSPacketNumFromNPT(fNPT, tsRecordNum, ixRecordNum);
|
|
|
|
updateTSRecordNum();
|
|
if (tsRecordNum != fTSRecordNum) {
|
|
fTSRecordNum = tsRecordNum;
|
|
fIxRecordNum = ixRecordNum;
|
|
|
|
// Seek the source to the new record number:
|
|
reseekOriginalTransportStreamSource();
|
|
// Note: We assume that we're asked to seek only in normal
|
|
// (i.e., non trick play) mode, so we don't seek within the trick
|
|
// play source (if any).
|
|
|
|
fFramer->clearPIDStatusTable();
|
|
}
|
|
|
|
unsigned long numTSRecordsToStream = 0;
|
|
float pcrLimit = 0.0;
|
|
if (streamDuration > 0.0) {
|
|
// fNPT might have changed when we looked it up in the index file. Adjust "streamDuration" accordingly:
|
|
streamDuration += npt - (double)fNPT;
|
|
|
|
if (streamDuration > 0.0) {
|
|
// Specify that we want to stream no more data than this.
|
|
|
|
if (fNextScale == 1.0f) {
|
|
// We'll be streaming from the original file.
|
|
// Use the index file to figure out how many Transport Packets we get to stream:
|
|
unsigned long toTSRecordNum, toIxRecordNum;
|
|
float toNPT = (float)(fNPT + streamDuration);
|
|
fIndexFile->lookupTSPacketNumFromNPT(toNPT, toTSRecordNum, toIxRecordNum);
|
|
if (toTSRecordNum > tsRecordNum) { // sanity check
|
|
numTSRecordsToStream = toTSRecordNum - tsRecordNum;
|
|
}
|
|
} else {
|
|
// We'll be streaming from the trick play stream.
|
|
// It'd be difficult to figure out how many Transport Packets we need to stream, so instead set a PCR
|
|
// limit in the trick play stream. (We rely upon the fact that PCRs in the trick play stream start at 0.0)
|
|
int direction = fNextScale < 0.0 ? -1 : 1;
|
|
pcrLimit = (float)(streamDuration/(fNextScale*direction));
|
|
}
|
|
}
|
|
}
|
|
fFramer->setNumTSPacketsToStream(numTSRecordsToStream);
|
|
fFramer->setPCRLimit(pcrLimit);
|
|
|
|
return numTSRecordsToStream;
|
|
}
|
|
|
|
void ClientTrickPlayState::updateStateOnScaleChange() {
|
|
fScale = fNextScale;
|
|
|
|
// Change our source objects to reflect the change in scale:
|
|
// First, close the existing trick play source (if any):
|
|
if (fTrickPlaySource != NULL) {
|
|
fTrickModeFilter->forgetInputSource();
|
|
// so that the underlying Transport Stream source doesn't get deleted by:
|
|
Medium::close(fTrickPlaySource);
|
|
fTrickPlaySource = NULL;
|
|
fTrickModeFilter = NULL;
|
|
}
|
|
if (fNextScale != 1.0f) {
|
|
// Create a new trick play filter from the original Transport Stream source:
|
|
UsageEnvironment& env = fIndexFile->envir(); // alias
|
|
fTrickModeFilter = MPEG2TransportStreamTrickModeFilter
|
|
::createNew(env, fOriginalTransportStreamSource, fIndexFile, int(fNextScale));
|
|
fTrickModeFilter->seekTo(fTSRecordNum, fIxRecordNum);
|
|
|
|
// And generate a Transport Stream from this:
|
|
fTrickPlaySource = MPEG2TransportStreamFromESSource::createNew(env);
|
|
fTrickPlaySource->addNewVideoSource(fTrickModeFilter, fIndexFile->mpegVersion());
|
|
|
|
fFramer->changeInputSource(fTrickPlaySource);
|
|
} else {
|
|
// Switch back to the original Transport Stream source:
|
|
reseekOriginalTransportStreamSource();
|
|
fFramer->changeInputSource(fOriginalTransportStreamSource);
|
|
}
|
|
}
|
|
|
|
void ClientTrickPlayState::updateStateOnPlayChange(Boolean reverseToPreviousVSH) {
|
|
updateTSRecordNum();
|
|
if (fTrickPlaySource == NULL) {
|
|
// We were in regular (1x) play. Use the index file to look up the
|
|
// index record number and npt from the current transport number:
|
|
fIndexFile->lookupPCRFromTSPacketNum(fTSRecordNum, reverseToPreviousVSH, fNPT, fIxRecordNum);
|
|
} else {
|
|
// We were in trick mode, and so already have the index record number.
|
|
// Get the transport record number and npt from this:
|
|
fIxRecordNum = fTrickModeFilter->nextIndexRecordNum();
|
|
if ((long)fIxRecordNum < 0) fIxRecordNum = 0; // we were at the start of the file
|
|
unsigned long transportRecordNum;
|
|
float pcr;
|
|
u_int8_t offset, size, recordType; // all dummy
|
|
if (fIndexFile->readIndexRecordValues(fIxRecordNum, transportRecordNum,
|
|
offset, size, pcr, recordType)) {
|
|
fTSRecordNum = transportRecordNum;
|
|
fNPT = pcr;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ClientTrickPlayState::setSource(MPEG2TransportStreamFramer* framer) {
|
|
fFramer = framer;
|
|
fOriginalTransportStreamSource = (ByteStreamFileSource*)(framer->inputSource());
|
|
}
|
|
|
|
void ClientTrickPlayState::updateTSRecordNum(){
|
|
if (fFramer != NULL) fTSRecordNum += (unsigned long)(fFramer->tsPacketCount());
|
|
}
|
|
|
|
void ClientTrickPlayState::reseekOriginalTransportStreamSource() {
|
|
u_int64_t tsRecordNum64 = (u_int64_t)fTSRecordNum;
|
|
fOriginalTransportStreamSource->seekToByteAbsolute(tsRecordNum64*TRANSPORT_PACKET_SIZE);
|
|
}
|