liveMedia/MPEG2TransportFileServerMediaSubsession.cpp

Go to the documentation of this file.
00001 /**********
00002 This library is free software; you can redistribute it and/or modify it under
00003 the terms of the GNU Lesser General Public License as published by the
00004 Free Software Foundation; either version 2.1 of the License, or (at your
00005 option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
00006 
00007 This library is distributed in the hope that it will be useful, but WITHOUT
00008 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
00009 FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
00010 more details.
00011 
00012 You should have received a copy of the GNU Lesser General Public License
00013 along with this library; if not, write to the Free Software Foundation, Inc.,
00014 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
00015 **********/
00016 // "liveMedia"
00017 // Copyright (c) 1996-2010 Live Networks, Inc.  All rights reserved.
00018 // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
00019 // on demand, from a MPEG-2 Transport Stream file.
00020 // Implementation
00021 
00022 #include "MPEG2TransportFileServerMediaSubsession.hh"
00023 #include "SimpleRTPSink.hh"
00024 #include "ByteStreamFileSource.hh"
00025 #include "MPEG2TransportStreamTrickModeFilter.hh"
00026 #include "MPEG2TransportStreamFromESSource.hh"
00027 #include "MPEG2TransportStreamFramer.hh"
00028 
00029 // We assume that the video - in the original Transport Stream file - is MPEG-2.
00030 // If, instead, it is MPEG-1, then change the following definition to 1:
00031 #define VIDEO_MPEG_VERSION 2
00032 
00034 
00035 // This class encapsulates the 'trick play' state for each current client (for
00036 // a given "MPEG2TransportFileServerMediaSubsession" - i.e., Transport Stream file).
00037 
00038 class ClientTrickPlayState {
00039 public:
00040   ClientTrickPlayState(MPEG2TransportStreamIndexFile* indexFile);
00041 
00042   // Functions to bring "fNPT", "fTSRecordNum" and "fIxRecordNum" in sync:
00043   void updateStateFromNPT(double npt);
00044   void updateStateOnScaleChange();
00045   void updateStateOnPlayChange(Boolean reverseToPreviousVSH);
00046 
00047   void handleStreamDeletion();
00048   void setSource(MPEG2TransportStreamFramer* framer);
00049 
00050   void setNextScale(float nextScale) { fNextScale = nextScale; }
00051   Boolean areChangingScale() const { return fNextScale != fScale; }
00052 
00053 private:
00054   void updateTSRecordNum();
00055   void reseekOriginalTransportStreamSource();
00056 
00057 private:
00058   MPEG2TransportStreamIndexFile* fIndexFile;
00059   ByteStreamFileSource* fOriginalTransportStreamSource;
00060   MPEG2TransportStreamTrickModeFilter* fTrickModeFilter;
00061   MPEG2TransportStreamFromESSource* fTrickPlaySource;
00062   MPEG2TransportStreamFramer* fFramer;
00063   float fScale, fNextScale, fNPT;
00064   unsigned long fTSRecordNum, fIxRecordNum;
00065 };
00066 
00067 
00069 
00070 MPEG2TransportFileServerMediaSubsession*
00071 MPEG2TransportFileServerMediaSubsession::createNew(UsageEnvironment& env,
00072                                                    char const* fileName,
00073                                                    char const* indexFileName,
00074                                                    Boolean reuseFirstSource) {
00075   if (indexFileName != NULL && reuseFirstSource) {
00076     // It makes no sense to support trick play if all clients use the same source.  Fix this:
00077     env << "MPEG2TransportFileServerMediaSubsession::createNew(): ignoring the index file name, because \"reuseFirstSource\" is set\n";
00078     indexFileName = NULL;
00079   }
00080   MPEG2TransportStreamIndexFile* indexFile = MPEG2TransportStreamIndexFile::createNew(env, indexFileName);
00081   return new MPEG2TransportFileServerMediaSubsession(env, fileName, indexFile,
00082                                                      reuseFirstSource);
00083 }
00084 
00085 MPEG2TransportFileServerMediaSubsession
00086 ::MPEG2TransportFileServerMediaSubsession(UsageEnvironment& env,
00087                                           char const* fileName,
00088                                           MPEG2TransportStreamIndexFile* indexFile,
00089                                           Boolean reuseFirstSource)
00090   : FileServerMediaSubsession(env, fileName, reuseFirstSource),
00091     fIndexFile(indexFile), fDuration(0.0), fClientSessionHashTable(NULL) {
00092   if (fIndexFile != NULL) { // we support 'trick play'
00093     fDuration = fIndexFile->getPlayingDuration();
00094     fClientSessionHashTable = HashTable::create(ONE_WORD_HASH_KEYS);
00095   }
00096 }
00097 
00098 MPEG2TransportFileServerMediaSubsession
00099 ::~MPEG2TransportFileServerMediaSubsession() {
00100   if (fIndexFile != NULL) { // we support 'trick play'
00101     Medium::close(fIndexFile);
00102 
00103     // Clean out the client session hash table:
00104     while (1) {
00105       ClientTrickPlayState* client
00106         = (ClientTrickPlayState*)(fClientSessionHashTable->RemoveNext());
00107       if (client == NULL) break;
00108       delete client;
00109     }
00110     delete fClientSessionHashTable;
00111   }
00112 }
00113 
00114 #define TRANSPORT_PACKET_SIZE 188
00115 #define TRANSPORT_PACKETS_PER_NETWORK_PACKET 7
00116 // The product of these two numbers must be enough to fit within a network packet
00117 
00118 void MPEG2TransportFileServerMediaSubsession
00119 ::startStream(unsigned clientSessionId, void* streamToken, TaskFunc* rtcpRRHandler,
00120               void* rtcpRRHandlerClientData, unsigned short& rtpSeqNum,
00121               unsigned& rtpTimestamp,
00122               ServerRequestAlternativeByteHandler* serverRequestAlternativeByteHandler,
00123               void* serverRequestAlternativeByteHandlerClientData) {
00124   if (fIndexFile != NULL) { // we support 'trick play'
00125     ClientTrickPlayState* client = lookupClient(clientSessionId);
00126     if (client != NULL && client->areChangingScale()) {
00127       // First, handle this like a "PAUSE", except that we back up to the previous VSH
00128       client->updateStateOnPlayChange(True);
00129       OnDemandServerMediaSubsession::pauseStream(clientSessionId, streamToken);
00130 
00131       // Then, adjust for the change of scale:
00132       client->updateStateOnScaleChange();
00133     }
00134   }
00135 
00136   // Call the original, default version of this routine:
00137   OnDemandServerMediaSubsession::startStream(clientSessionId, streamToken,
00138                                              rtcpRRHandler, rtcpRRHandlerClientData,
00139                                              rtpSeqNum, rtpTimestamp,
00140                                              serverRequestAlternativeByteHandler, serverRequestAlternativeByteHandlerClientData);
00141 }
00142 
00143 void MPEG2TransportFileServerMediaSubsession
00144 ::pauseStream(unsigned clientSessionId, void* streamToken) {
00145   if (fIndexFile != NULL) { // we support 'trick play'
00146     ClientTrickPlayState* client = lookupClient(clientSessionId);
00147     if (client != NULL) {
00148       client->updateStateOnPlayChange(False);
00149     }
00150   }
00151 
00152   // Call the original, default version of this routine:
00153   OnDemandServerMediaSubsession::pauseStream(clientSessionId, streamToken);
00154 }
00155 
00156 void MPEG2TransportFileServerMediaSubsession
00157 ::seekStream(unsigned clientSessionId, void* streamToken, double seekNPT) {
00158   if (fIndexFile != NULL) { // we support 'trick play'
00159     ClientTrickPlayState* client = lookupClient(clientSessionId);
00160     if (client != NULL) {
00161       client->updateStateFromNPT(seekNPT);
00162     }
00163   }
00164 
00165   // Call the original, default version of this routine:
00166   OnDemandServerMediaSubsession::seekStream(clientSessionId, streamToken, seekNPT);
00167 }
00168 
00169 void MPEG2TransportFileServerMediaSubsession
00170 ::setStreamScale(unsigned clientSessionId, void* streamToken, float scale) {
00171   if (fIndexFile != NULL) { // we support 'trick play'
00172     ClientTrickPlayState* client = lookupClient(clientSessionId);
00173     if (client != NULL) {
00174       client->setNextScale(scale); // scale won't take effect until the next "PLAY"
00175     }
00176   }
00177 
00178   // Call the original, default version of this routine:
00179   OnDemandServerMediaSubsession::setStreamScale(clientSessionId, streamToken, scale);
00180 }
00181 
00182 void MPEG2TransportFileServerMediaSubsession
00183 ::deleteStream(unsigned clientSessionId, void*& streamToken) {
00184   if (fIndexFile != NULL) { // we support 'trick play'
00185     ClientTrickPlayState* client = lookupClient(clientSessionId);
00186     if (client != NULL) {
00187       client->updateStateOnPlayChange(False);
00188     }
00189   }
00190 
00191   // Call the original, default version of this routine:
00192   OnDemandServerMediaSubsession::deleteStream(clientSessionId, streamToken);
00193 }
00194 
00195 FramedSource* MPEG2TransportFileServerMediaSubsession
00196 ::createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate) {
00197   estBitrate = 5000; // kbps, estimate
00198 
00199   // Create the video source:
00200   unsigned const inputDataChunkSize
00201     = TRANSPORT_PACKETS_PER_NETWORK_PACKET*TRANSPORT_PACKET_SIZE;
00202   ByteStreamFileSource* fileSource
00203     = ByteStreamFileSource::createNew(envir(), fFileName, inputDataChunkSize);
00204   if (fileSource == NULL) return NULL;
00205   fFileSize = fileSource->fileSize();
00206 
00207   // Create a framer for the Transport Stream:
00208   MPEG2TransportStreamFramer* framer
00209     = MPEG2TransportStreamFramer::createNew(envir(), fileSource);
00210 
00211   if (fIndexFile != NULL) { // we support 'trick play'
00212     // Keep state for this client (if we don't already have it):
00213     ClientTrickPlayState* client = lookupClient(clientSessionId);
00214     if (client == NULL) {
00215       client = new ClientTrickPlayState(fIndexFile);
00216       fClientSessionHashTable->Add((char const*)clientSessionId, client);
00217     }
00218     client->setSource(framer);
00219   }
00220 
00221   return framer;
00222 }
00223 
00224 RTPSink* MPEG2TransportFileServerMediaSubsession
00225 ::createNewRTPSink(Groupsock* rtpGroupsock,
00226                    unsigned char /*rtpPayloadTypeIfDynamic*/,
00227                    FramedSource* /*inputSource*/) {
00228   return SimpleRTPSink::createNew(envir(), rtpGroupsock,
00229                                   33, 90000, "video", "MP2T",
00230                                   1, True, False /*no 'M' bit*/);
00231 }
00232 
00233 void MPEG2TransportFileServerMediaSubsession::testScaleFactor(float& scale) {
00234   if (fIndexFile != NULL && fDuration > 0.0) {
00235     // We support any integral scale, other than 0
00236     int iScale = scale < 0.0 ? (int)(scale - 0.5f) : (int)(scale + 0.5f); // round
00237     if (iScale == 0) iScale = 1;
00238     scale = (float)iScale;
00239   } else {
00240     scale = 1.0f;
00241   }
00242 }
00243 
00244 float MPEG2TransportFileServerMediaSubsession::duration() const {
00245   return fDuration;
00246 }
00247 
00248 ClientTrickPlayState* MPEG2TransportFileServerMediaSubsession
00249 ::lookupClient(unsigned clientSessionId) {
00250   return (ClientTrickPlayState*)(fClientSessionHashTable->Lookup((char const*)clientSessionId));
00251 }
00252 
00253 
00255 
00256 ClientTrickPlayState::ClientTrickPlayState(MPEG2TransportStreamIndexFile* indexFile)
00257   : fIndexFile(indexFile),
00258     fOriginalTransportStreamSource(NULL),
00259     fTrickModeFilter(NULL), fTrickPlaySource(NULL),
00260     fFramer(NULL),
00261     fScale(1.0f), fNextScale(1.0f), fNPT(0.0f),
00262     fTSRecordNum(0), fIxRecordNum(0) {
00263 }
00264 
00265 void ClientTrickPlayState::updateStateFromNPT(double npt) {
00266   fNPT = (float)npt;
00267   // Map "fNPT" to the corresponding Transport Stream and Index record numbers:
00268   unsigned long tsRecordNum, ixRecordNum;
00269   fIndexFile->lookupTSPacketNumFromNPT(fNPT, tsRecordNum, ixRecordNum);
00270 
00271   updateTSRecordNum();
00272   if (tsRecordNum != fTSRecordNum) {
00273     fTSRecordNum = tsRecordNum;
00274     fIxRecordNum = ixRecordNum;
00275 
00276     // Seek the source to the new record number:
00277     reseekOriginalTransportStreamSource();
00278     // Note: We assume that we're asked to seek only in normal
00279     // (i.e., non trick play) mode, so we don't seek within the trick
00280     // play source (if any).
00281 
00282     fFramer->clearPIDStatusTable();
00283   }
00284 }
00285 
00286 void ClientTrickPlayState::updateStateOnScaleChange() {
00287   fScale = fNextScale;
00288 
00289   // Change our source objects to reflect the change in scale:
00290   // First, close the existing trick play source (if any):
00291   if (fTrickPlaySource != NULL) {
00292     fTrickModeFilter->forgetInputSource();
00293         // so that the underlying Transport Stream source doesn't get deleted by:
00294     Medium::close(fTrickPlaySource);
00295     fTrickPlaySource = NULL;
00296     fTrickModeFilter = NULL;
00297   }
00298   if (fNextScale != 1.0f) {
00299     // Create a new trick play filter from the original Transport Stream source:
00300     UsageEnvironment& env = fIndexFile->envir(); // alias
00301     fTrickModeFilter = MPEG2TransportStreamTrickModeFilter
00302       ::createNew(env, fOriginalTransportStreamSource, fIndexFile, int(fNextScale));
00303     fTrickModeFilter->seekTo(fTSRecordNum, fIxRecordNum);
00304 
00305     // And generate a Transport Stream from this:
00306     fTrickPlaySource = MPEG2TransportStreamFromESSource::createNew(env);
00307     fTrickPlaySource->addNewVideoSource(fTrickModeFilter, VIDEO_MPEG_VERSION);
00308 
00309     fFramer->changeInputSource(fTrickPlaySource);
00310   } else {
00311     // Switch back to the original Transport Stream source:
00312     reseekOriginalTransportStreamSource();
00313     fFramer->changeInputSource(fOriginalTransportStreamSource);
00314   }
00315 }
00316 
00317 void ClientTrickPlayState::updateStateOnPlayChange(Boolean reverseToPreviousVSH) {
00318   updateTSRecordNum();
00319   if (fTrickPlaySource == NULL) {
00320     // We were in regular (1x) play. Use the index file to look up the
00321     // index record number and npt from the current transport number:
00322     fIndexFile->lookupPCRFromTSPacketNum(fTSRecordNum, reverseToPreviousVSH, fNPT, fIxRecordNum);
00323   } else {
00324     // We were in trick mode, and so already have the index record number.
00325     // Get the transport record number and npt from this:
00326     fIxRecordNum = fTrickModeFilter->nextIndexRecordNum();
00327     if ((long)fIxRecordNum < 0) fIxRecordNum = 0; // we were at the start of the file
00328     unsigned long transportRecordNum;
00329     float pcr;
00330     u_int8_t offset, size, recordType; // all dummy
00331     if (fIndexFile->readIndexRecordValues(fIxRecordNum, transportRecordNum,
00332                                           offset, size, pcr, recordType)) {
00333       fTSRecordNum = transportRecordNum;
00334       fNPT = pcr;
00335     }
00336   }
00337 }
00338 
00339 void ClientTrickPlayState::setSource(MPEG2TransportStreamFramer* framer) {
00340   fFramer = framer;
00341   fOriginalTransportStreamSource = (ByteStreamFileSource*)(framer->inputSource());
00342 }
00343 
00344 void ClientTrickPlayState::updateTSRecordNum(){
00345   if (fFramer != NULL) fTSRecordNum += fFramer->tsPacketCount();
00346 }
00347 
00348 void ClientTrickPlayState::reseekOriginalTransportStreamSource() {
00349   u_int64_t tsRecordNum64 = (u_int64_t)fTSRecordNum;
00350   fOriginalTransportStreamSource->seekToByteAbsolute(tsRecordNum64*TRANSPORT_PACKET_SIZE);
00351 }

Generated on Fri Sep 3 02:35:41 2010 for live by  doxygen 1.5.2