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 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00015 **********/
00016 // "liveMedia"
00017 // Copyright (c) 1996-2008 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(float 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   if (fIndexFile != NULL) { // we support 'trick play'
00123     ClientTrickPlayState* client = lookupClient(clientSessionId);
00124     if (client != NULL && client->areChangingScale()) {
00125       // First, handle this like a "PAUSE", except that we back up to the previous VSH
00126       client->updateStateOnPlayChange(True);
00127       OnDemandServerMediaSubsession::pauseStream(clientSessionId, streamToken);
00128 
00129       // Then, adjust for the change of scale:
00130       client->updateStateOnScaleChange();
00131     }
00132   }
00133 
00134   // Call the original, default version of this routine:
00135   OnDemandServerMediaSubsession::startStream(clientSessionId, streamToken,
00136                                              rtcpRRHandler, rtcpRRHandlerClientData,
00137                                              rtpSeqNum, rtpTimestamp);
00138 }
00139 
00140 void MPEG2TransportFileServerMediaSubsession
00141 ::pauseStream(unsigned clientSessionId, void* streamToken) {
00142   if (fIndexFile != NULL) { // we support 'trick play'
00143     ClientTrickPlayState* client = lookupClient(clientSessionId);
00144     if (client != NULL) {
00145       client->updateStateOnPlayChange(False);
00146     }
00147   }
00148 
00149   // Call the original, default version of this routine:
00150   OnDemandServerMediaSubsession::pauseStream(clientSessionId, streamToken);
00151 }
00152 
00153 void MPEG2TransportFileServerMediaSubsession
00154 ::seekStream(unsigned clientSessionId, void* streamToken, float seekNPT) {
00155   if (fIndexFile != NULL) { // we support 'trick play'
00156     ClientTrickPlayState* client = lookupClient(clientSessionId);
00157     if (client != NULL) {
00158       client->updateStateFromNPT(seekNPT);
00159     }
00160   }
00161 
00162   // Call the original, default version of this routine:
00163   OnDemandServerMediaSubsession::seekStream(clientSessionId, streamToken, seekNPT);
00164 }
00165 
00166 void MPEG2TransportFileServerMediaSubsession
00167 ::setStreamScale(unsigned clientSessionId, void* streamToken, float scale) {
00168   if (fIndexFile != NULL) { // we support 'trick play'
00169     ClientTrickPlayState* client = lookupClient(clientSessionId);
00170     if (client != NULL) {
00171       client->setNextScale(scale); // scale won't take effect until the next "PLAY"
00172     }
00173   }
00174 
00175   // Call the original, default version of this routine:
00176   OnDemandServerMediaSubsession::setStreamScale(clientSessionId, streamToken, scale);
00177 }
00178 
00179 void MPEG2TransportFileServerMediaSubsession
00180 ::deleteStream(unsigned clientSessionId, void*& streamToken) {
00181   if (fIndexFile != NULL) { // we support 'trick play'
00182     ClientTrickPlayState* client = lookupClient(clientSessionId);
00183     if (client != NULL) {
00184       client->updateStateOnPlayChange(False);
00185     }
00186   }
00187 
00188   // Call the original, default version of this routine:
00189   OnDemandServerMediaSubsession::deleteStream(clientSessionId, streamToken);
00190 }
00191 
00192 FramedSource* MPEG2TransportFileServerMediaSubsession
00193 ::createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate) {
00194   estBitrate = 5000; // kbps, estimate
00195 
00196   // Create the video source:
00197   unsigned const inputDataChunkSize
00198     = TRANSPORT_PACKETS_PER_NETWORK_PACKET*TRANSPORT_PACKET_SIZE;
00199   ByteStreamFileSource* fileSource
00200     = ByteStreamFileSource::createNew(envir(), fFileName, inputDataChunkSize);
00201   if (fileSource == NULL) return NULL;
00202   fFileSize = fileSource->fileSize();
00203 
00204   // Create a framer for the Transport Stream:
00205   MPEG2TransportStreamFramer* framer
00206     = MPEG2TransportStreamFramer::createNew(envir(), fileSource);
00207 
00208   if (fIndexFile != NULL) { // we support 'trick play'
00209     // Keep state for this client (if we don't already have it):
00210     ClientTrickPlayState* client = lookupClient(clientSessionId);
00211     if (client == NULL) {
00212       client = new ClientTrickPlayState(fIndexFile);
00213       fClientSessionHashTable->Add((char const*)clientSessionId, client);
00214     }
00215     client->setSource(framer);
00216   }
00217 
00218   return framer;
00219 }
00220 
00221 RTPSink* MPEG2TransportFileServerMediaSubsession
00222 ::createNewRTPSink(Groupsock* rtpGroupsock,
00223                    unsigned char /*rtpPayloadTypeIfDynamic*/,
00224                    FramedSource* /*inputSource*/) {
00225   return SimpleRTPSink::createNew(envir(), rtpGroupsock,
00226                                   33, 90000, "video", "MP2T",
00227                                   1, True, False /*no 'M' bit*/);
00228 }
00229 
00230 void MPEG2TransportFileServerMediaSubsession::testScaleFactor(float& scale) {
00231   if (fIndexFile != NULL && fDuration > 0.0) {
00232     // We support any integral scale, other than 0
00233     int iScale = scale < 0.0 ? (int)(scale - 0.5f) : (int)(scale + 0.5f); // round
00234     if (iScale == 0) iScale = 1;
00235     scale = (float)iScale;
00236   } else {
00237     scale = 1.0f;
00238   }
00239 }
00240 
00241 float MPEG2TransportFileServerMediaSubsession::duration() const {
00242   return fDuration;
00243 }
00244 
00245 ClientTrickPlayState* MPEG2TransportFileServerMediaSubsession
00246 ::lookupClient(unsigned clientSessionId) {
00247   return (ClientTrickPlayState*)(fClientSessionHashTable->Lookup((char const*)clientSessionId));
00248 }
00249 
00250 
00252 
00253 ClientTrickPlayState::ClientTrickPlayState(MPEG2TransportStreamIndexFile* indexFile)
00254   : fIndexFile(indexFile),
00255     fOriginalTransportStreamSource(NULL),
00256     fTrickModeFilter(NULL), fTrickPlaySource(NULL),
00257     fFramer(NULL),
00258     fScale(1.0f), fNextScale(1.0f), fNPT(0.0f),
00259     fTSRecordNum(0), fIxRecordNum(0) {
00260 }
00261 
00262 void ClientTrickPlayState::updateStateFromNPT(float npt) {
00263   fNPT = npt;
00264   // Map "fNPT" to the corresponding Transport Stream and Index record numbers:
00265   unsigned long tsRecordNum, ixRecordNum;
00266   fIndexFile->lookupTSPacketNumFromNPT(fNPT, tsRecordNum, ixRecordNum);
00267 
00268   updateTSRecordNum();
00269   if (tsRecordNum != fTSRecordNum) {
00270     fTSRecordNum = tsRecordNum;
00271     fIxRecordNum = ixRecordNum;
00272 
00273     // Seek the source to the new record number:
00274     reseekOriginalTransportStreamSource();
00275     // Note: We assume that we're asked to seek only in normal
00276     // (i.e., non trick play) mode, so we don't seek within the trick
00277     // play source (if any).
00278 
00279     fFramer->clearPIDStatusTable();
00280   }
00281 }
00282 
00283 void ClientTrickPlayState::updateStateOnScaleChange() {
00284   fScale = fNextScale;
00285 
00286   // Change our source objects to reflect the change in scale:
00287   // First, close the existing trick play source (if any):
00288   if (fTrickPlaySource != NULL) {
00289     fTrickModeFilter->forgetInputSource();
00290         // so that the underlying Transport Stream source doesn't get deleted by:
00291     Medium::close(fTrickPlaySource);
00292     fTrickPlaySource = NULL;
00293     fTrickModeFilter = NULL;
00294   }
00295   if (fNextScale != 1.0f) {
00296     // Create a new trick play filter from the original Transport Stream source:
00297     UsageEnvironment& env = fIndexFile->envir(); // alias
00298     fTrickModeFilter = MPEG2TransportStreamTrickModeFilter
00299       ::createNew(env, fOriginalTransportStreamSource, fIndexFile, int(fNextScale));
00300     fTrickModeFilter->seekTo(fTSRecordNum, fIxRecordNum);
00301 
00302     // And generate a Transport Stream from this:
00303     fTrickPlaySource = MPEG2TransportStreamFromESSource::createNew(env);
00304     fTrickPlaySource->addNewVideoSource(fTrickModeFilter, VIDEO_MPEG_VERSION);
00305 
00306     fFramer->changeInputSource(fTrickPlaySource);
00307   } else {
00308     // Switch back to the original Transport Stream source:
00309     reseekOriginalTransportStreamSource();
00310     fFramer->changeInputSource(fOriginalTransportStreamSource);
00311   }
00312 }
00313 
00314 void ClientTrickPlayState::updateStateOnPlayChange(Boolean reverseToPreviousVSH) {
00315   updateTSRecordNum();
00316   if (fTrickPlaySource == NULL) {
00317     // We were in regular (1x) play. Use the index file to look up the
00318     // index record number and npt from the current transport number:
00319     fIndexFile->lookupPCRFromTSPacketNum(fTSRecordNum, reverseToPreviousVSH, fNPT, fIxRecordNum);
00320   } else {
00321     // We were in trick mode, and so already have the index record number.
00322     // Get the transport record number and npt from this:
00323     fIxRecordNum = fTrickModeFilter->nextIndexRecordNum();
00324     if ((long)fIxRecordNum < 0) fIxRecordNum = 0; // we were at the start of the file
00325     unsigned long transportRecordNum;
00326     float pcr;
00327     u_int8_t offset, size, recordType; // all dummy
00328     if (fIndexFile->readIndexRecordValues(fIxRecordNum, transportRecordNum,
00329                                           offset, size, pcr, recordType)) {
00330       fTSRecordNum = transportRecordNum;
00331       fNPT = pcr;
00332     }
00333   }
00334 }
00335 
00336 void ClientTrickPlayState::setSource(MPEG2TransportStreamFramer* framer) {
00337   fFramer = framer;
00338   fOriginalTransportStreamSource = (ByteStreamFileSource*)(framer->inputSource());
00339 }
00340 
00341 void ClientTrickPlayState::updateTSRecordNum(){
00342   if (fFramer != NULL) fTSRecordNum += fFramer->tsPacketCount();
00343 }
00344 
00345 void ClientTrickPlayState::reseekOriginalTransportStreamSource() {
00346   u_int64_t tsRecordNum64 = (u_int64_t)fTSRecordNum;
00347   fOriginalTransportStreamSource->seekToByteAbsolute(tsRecordNum64*TRANSPORT_PACKET_SIZE);
00348 }

Generated on Tue Jul 22 06:39:06 2008 for live by  doxygen 1.5.2