testProgs/testRTSPClient.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 // Copyright (c) 1996-2013, Live Networks, Inc.  All rights reserved
00017 // A demo application, showing how to create and run a RTSP client (that can potentially receive multiple streams concurrently).
00018 //
00019 // NOTE: This code - although it builds a running application - is intended only to illustrate how to develop your own RTSP
00020 // client application.  For a full-featured RTSP client application - with much more functionality, and many options - see
00021 // "openRTSP": http://www.live555.com/openRTSP/
00022 
00023 #include "liveMedia.hh"
00024 #include "BasicUsageEnvironment.hh"
00025 
00026 // Forward function definitions:
00027 
00028 // RTSP 'response handlers':
00029 void continueAfterDESCRIBE(RTSPClient* rtspClient, int resultCode, char* resultString);
00030 void continueAfterSETUP(RTSPClient* rtspClient, int resultCode, char* resultString);
00031 void continueAfterPLAY(RTSPClient* rtspClient, int resultCode, char* resultString);
00032 
00033 // Other event handler functions:
00034 void subsessionAfterPlaying(void* clientData); // called when a stream's subsession (e.g., audio or video substream) ends
00035 void subsessionByeHandler(void* clientData); // called when a RTCP "BYE" is received for a subsession
00036 void streamTimerHandler(void* clientData);
00037   // called at the end of a stream's expected duration (if the stream has not already signaled its end using a RTCP "BYE")
00038 
00039 // The main streaming routine (for each "rtsp://" URL):
00040 void openURL(UsageEnvironment& env, char const* progName, char const* rtspURL);
00041 
00042 // Used to iterate through each stream's 'subsessions', setting up each one:
00043 void setupNextSubsession(RTSPClient* rtspClient);
00044 
00045 // Used to shut down and close a stream (including its "RTSPClient" object):
00046 void shutdownStream(RTSPClient* rtspClient, int exitCode = 1);
00047 
00048 // A function that outputs a string that identifies each stream (for debugging output).  Modify this if you wish:
00049 UsageEnvironment& operator<<(UsageEnvironment& env, const RTSPClient& rtspClient) {
00050   return env << "[URL:\"" << rtspClient.url() << "\"]: ";
00051 }
00052 
00053 // A function that outputs a string that identifies each subsession (for debugging output).  Modify this if you wish:
00054 UsageEnvironment& operator<<(UsageEnvironment& env, const MediaSubsession& subsession) {
00055   return env << subsession.mediumName() << "/" << subsession.codecName();
00056 }
00057 
00058 void usage(UsageEnvironment& env, char const* progName) {
00059   env << "Usage: " << progName << " <rtsp-url-1> ... <rtsp-url-N>\n";
00060   env << "\t(where each <rtsp-url-i> is a \"rtsp://\" URL)\n";
00061 }
00062 
00063 char eventLoopWatchVariable = 0;
00064 
00065 int main(int argc, char** argv) {
00066   // Begin by setting up our usage environment:
00067   TaskScheduler* scheduler = BasicTaskScheduler::createNew();
00068   UsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler);
00069 
00070   // We need at least one "rtsp://" URL argument:
00071   if (argc < 2) {
00072     usage(*env, argv[0]);
00073     return 1;
00074   }
00075 
00076   // There are argc-1 URLs: argv[1] through argv[argc-1].  Open and start streaming each one:
00077   for (int i = 1; i <= argc-1; ++i) {
00078     openURL(*env, argv[0], argv[i]);
00079   }
00080 
00081   // All subsequent activity takes place within the event loop:
00082   env->taskScheduler().doEventLoop(&eventLoopWatchVariable);
00083     // This function call does not return, unless, at some point in time, "eventLoopWatchVariable" gets set to something non-zero.
00084 
00085   return 0;
00086 
00087   // If you choose to continue the application past this point (i.e., if you comment out the "return 0;" statement above),
00088   // and if you don't intend to do anything more with the "TaskScheduler" and "UsageEnvironment" objects,
00089   // then you can also reclaim the (small) memory used by these objects by uncommenting the following code:
00090   /*
00091     env->reclaim(); env = NULL;
00092     delete scheduler; scheduler = NULL;
00093   */
00094 }
00095 
00096 // Define a class to hold per-stream state that we maintain throughout each stream's lifetime:
00097 
00098 class StreamClientState {
00099 public:
00100   StreamClientState();
00101   virtual ~StreamClientState();
00102 
00103 public:
00104   MediaSubsessionIterator* iter;
00105   MediaSession* session;
00106   MediaSubsession* subsession;
00107   TaskToken streamTimerTask;
00108   double duration;
00109 };
00110 
00111 // If you're streaming just a single stream (i.e., just from a single URL, once), then you can define and use just a single
00112 // "StreamClientState" structure, as a global variable in your application.  However, because - in this demo application - we're
00113 // showing how to play multiple streams, concurrently, we can't do that.  Instead, we have to have a separate "StreamClientState"
00114 // structure for each "RTSPClient".  To do this, we subclass "RTSPClient", and add a "StreamClientState" field to the subclass:
00115 
00116 class ourRTSPClient: public RTSPClient {
00117 public:
00118   static ourRTSPClient* createNew(UsageEnvironment& env, char const* rtspURL,
00119                                   int verbosityLevel = 0,
00120                                   char const* applicationName = NULL,
00121                                   portNumBits tunnelOverHTTPPortNum = 0);
00122 
00123 protected:
00124   ourRTSPClient(UsageEnvironment& env, char const* rtspURL,
00125                 int verbosityLevel, char const* applicationName, portNumBits tunnelOverHTTPPortNum);
00126     // called only by createNew();
00127   virtual ~ourRTSPClient();
00128 
00129 public:
00130   StreamClientState scs;
00131 };
00132 
00133 // Define a data sink (a subclass of "MediaSink") to receive the data for each subsession (i.e., each audio or video 'substream').
00134 // In practice, this might be a class (or a chain of classes) that decodes and then renders the incoming audio or video.
00135 // Or it might be a "FileSink", for outputting the received data into a file (as is done by the "openRTSP" application).
00136 // In this example code, however, we define a simple 'dummy' sink that receives incoming data, but does nothing with it.
00137 
00138 class DummySink: public MediaSink {
00139 public:
00140   static DummySink* createNew(UsageEnvironment& env,
00141                               MediaSubsession& subsession, // identifies the kind of data that's being received
00142                               char const* streamId = NULL); // identifies the stream itself (optional)
00143 
00144 private:
00145   DummySink(UsageEnvironment& env, MediaSubsession& subsession, char const* streamId);
00146     // called only by "createNew()"
00147   virtual ~DummySink();
00148 
00149   static void afterGettingFrame(void* clientData, unsigned frameSize,
00150                                 unsigned numTruncatedBytes,
00151                                 struct timeval presentationTime,
00152                                 unsigned durationInMicroseconds);
00153   void afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes,
00154                          struct timeval presentationTime, unsigned durationInMicroseconds);
00155 
00156 private:
00157   // redefined virtual functions:
00158   virtual Boolean continuePlaying();
00159 
00160 private:
00161   u_int8_t* fReceiveBuffer;
00162   MediaSubsession& fSubsession;
00163   char* fStreamId;
00164 };
00165 
00166 #define RTSP_CLIENT_VERBOSITY_LEVEL 1 // by default, print verbose output from each "RTSPClient"
00167 
00168 static unsigned rtspClientCount = 0; // Counts how many streams (i.e., "RTSPClient"s) are currently in use.
00169 
00170 void openURL(UsageEnvironment& env, char const* progName, char const* rtspURL) {
00171   // Begin by creating a "RTSPClient" object.  Note that there is a separate "RTSPClient" object for each stream that we wish
00172   // to receive (even if more than stream uses the same "rtsp://" URL).
00173   RTSPClient* rtspClient = ourRTSPClient::createNew(env, rtspURL, RTSP_CLIENT_VERBOSITY_LEVEL, progName);
00174   if (rtspClient == NULL) {
00175     env << "Failed to create a RTSP client for URL \"" << rtspURL << "\": " << env.getResultMsg() << "\n";
00176     return;
00177   }
00178 
00179   ++rtspClientCount;
00180 
00181   // Next, send a RTSP "DESCRIBE" command, to get a SDP description for the stream.
00182   // Note that this command - like all RTSP commands - is sent asynchronously; we do not block, waiting for a response.
00183   // Instead, the following function call returns immediately, and we handle the RTSP response later, from within the event loop:
00184   rtspClient->sendDescribeCommand(continueAfterDESCRIBE); 
00185 }
00186 
00187 
00188 // Implementation of the RTSP 'response handlers':
00189 
00190 void continueAfterDESCRIBE(RTSPClient* rtspClient, int resultCode, char* resultString) {
00191   do {
00192     UsageEnvironment& env = rtspClient->envir(); // alias
00193     StreamClientState& scs = ((ourRTSPClient*)rtspClient)->scs; // alias
00194 
00195     if (resultCode != 0) {
00196       env << *rtspClient << "Failed to get a SDP description: " << resultString << "\n";
00197       delete[] resultString;
00198       break;
00199     }
00200 
00201     char* const sdpDescription = resultString;
00202     env << *rtspClient << "Got a SDP description:\n" << sdpDescription << "\n";
00203 
00204     // Create a media session object from this SDP description:
00205     scs.session = MediaSession::createNew(env, sdpDescription);
00206     delete[] sdpDescription; // because we don't need it anymore
00207     if (scs.session == NULL) {
00208       env << *rtspClient << "Failed to create a MediaSession object from the SDP description: " << env.getResultMsg() << "\n";
00209       break;
00210     } else if (!scs.session->hasSubsessions()) {
00211       env << *rtspClient << "This session has no media subsessions (i.e., no \"m=\" lines)\n";
00212       break;
00213     }
00214 
00215     // Then, create and set up our data source objects for the session.  We do this by iterating over the session's 'subsessions',
00216     // calling "MediaSubsession::initiate()", and then sending a RTSP "SETUP" command, on each one.
00217     // (Each 'subsession' will have its own data source.)
00218     scs.iter = new MediaSubsessionIterator(*scs.session);
00219     setupNextSubsession(rtspClient);
00220     return;
00221   } while (0);
00222 
00223   // An unrecoverable error occurred with this stream.
00224   shutdownStream(rtspClient);
00225 }
00226 
00227 // By default, we request that the server stream its data using RTP/UDP.
00228 // If, instead, you want to request that the server stream via RTP-over-TCP, change the following to True:
00229 #define REQUEST_STREAMING_OVER_TCP False
00230 
00231 void setupNextSubsession(RTSPClient* rtspClient) {
00232   UsageEnvironment& env = rtspClient->envir(); // alias
00233   StreamClientState& scs = ((ourRTSPClient*)rtspClient)->scs; // alias
00234   
00235   scs.subsession = scs.iter->next();
00236   if (scs.subsession != NULL) {
00237     if (!scs.subsession->initiate()) {
00238       env << *rtspClient << "Failed to initiate the \"" << *scs.subsession << "\" subsession: " << env.getResultMsg() << "\n";
00239       setupNextSubsession(rtspClient); // give up on this subsession; go to the next one
00240     } else {
00241       env << *rtspClient << "Initiated the \"" << *scs.subsession
00242           << "\" subsession (client ports " << scs.subsession->clientPortNum() << "-" << scs.subsession->clientPortNum()+1 << ")\n";
00243 
00244       // Continue setting up this subsession, by sending a RTSP "SETUP" command:
00245       rtspClient->sendSetupCommand(*scs.subsession, continueAfterSETUP, False, REQUEST_STREAMING_OVER_TCP);
00246     }
00247     return;
00248   }
00249 
00250   // We've finished setting up all of the subsessions.  Now, send a RTSP "PLAY" command to start the streaming:
00251   if (scs.session->absStartTime() != NULL) {
00252     // Special case: The stream is indexed by 'absolute' time, so send an appropriate "PLAY" command:
00253     rtspClient->sendPlayCommand(*scs.session, continueAfterPLAY, scs.session->absStartTime(), scs.session->absEndTime());
00254   } else {
00255     scs.duration = scs.session->playEndTime() - scs.session->playStartTime();
00256     rtspClient->sendPlayCommand(*scs.session, continueAfterPLAY);
00257   }
00258 }
00259 
00260 void continueAfterSETUP(RTSPClient* rtspClient, int resultCode, char* resultString) {
00261   do {
00262     UsageEnvironment& env = rtspClient->envir(); // alias
00263     StreamClientState& scs = ((ourRTSPClient*)rtspClient)->scs; // alias
00264 
00265     if (resultCode != 0) {
00266       env << *rtspClient << "Failed to set up the \"" << *scs.subsession << "\" subsession: " << resultString << "\n";
00267       break;
00268     }
00269 
00270     env << *rtspClient << "Set up the \"" << *scs.subsession
00271         << "\" subsession (client ports " << scs.subsession->clientPortNum() << "-" << scs.subsession->clientPortNum()+1 << ")\n";
00272 
00273     // Having successfully setup the subsession, create a data sink for it, and call "startPlaying()" on it.
00274     // (This will prepare the data sink to receive data; the actual flow of data from the client won't start happening until later,
00275     // after we've sent a RTSP "PLAY" command.)
00276 
00277     scs.subsession->sink = DummySink::createNew(env, *scs.subsession, rtspClient->url());
00278       // perhaps use your own custom "MediaSink" subclass instead
00279     if (scs.subsession->sink == NULL) {
00280       env << *rtspClient << "Failed to create a data sink for the \"" << *scs.subsession
00281           << "\" subsession: " << env.getResultMsg() << "\n";
00282       break;
00283     }
00284 
00285     env << *rtspClient << "Created a data sink for the \"" << *scs.subsession << "\" subsession\n";
00286     scs.subsession->miscPtr = rtspClient; // a hack to let subsession handle functions get the "RTSPClient" from the subsession 
00287     scs.subsession->sink->startPlaying(*(scs.subsession->readSource()),
00288                                        subsessionAfterPlaying, scs.subsession);
00289     // Also set a handler to be called if a RTCP "BYE" arrives for this subsession:
00290     if (scs.subsession->rtcpInstance() != NULL) {
00291       scs.subsession->rtcpInstance()->setByeHandler(subsessionByeHandler, scs.subsession);
00292     }
00293   } while (0);
00294   delete[] resultString;
00295 
00296   // Set up the next subsession, if any:
00297   setupNextSubsession(rtspClient);
00298 }
00299 
00300 void continueAfterPLAY(RTSPClient* rtspClient, int resultCode, char* resultString) {
00301   Boolean success = False;
00302 
00303   do {
00304     UsageEnvironment& env = rtspClient->envir(); // alias
00305     StreamClientState& scs = ((ourRTSPClient*)rtspClient)->scs; // alias
00306 
00307     if (resultCode != 0) {
00308       env << *rtspClient << "Failed to start playing session: " << resultString << "\n";
00309       break;
00310     }
00311 
00312     // Set a timer to be handled at the end of the stream's expected duration (if the stream does not already signal its end
00313     // using a RTCP "BYE").  This is optional.  If, instead, you want to keep the stream active - e.g., so you can later
00314     // 'seek' back within it and do another RTSP "PLAY" - then you can omit this code.
00315     // (Alternatively, if you don't want to receive the entire stream, you could set this timer for some shorter value.)
00316     if (scs.duration > 0) {
00317       unsigned const delaySlop = 2; // number of seconds extra to delay, after the stream's expected duration.  (This is optional.)
00318       scs.duration += delaySlop;
00319       unsigned uSecsToDelay = (unsigned)(scs.duration*1000000);
00320       scs.streamTimerTask = env.taskScheduler().scheduleDelayedTask(uSecsToDelay, (TaskFunc*)streamTimerHandler, rtspClient);
00321     }
00322 
00323     env << *rtspClient << "Started playing session";
00324     if (scs.duration > 0) {
00325       env << " (for up to " << scs.duration << " seconds)";
00326     }
00327     env << "...\n";
00328 
00329     success = True;
00330   } while (0);
00331   delete[] resultString;
00332 
00333   if (!success) {
00334     // An unrecoverable error occurred with this stream.
00335     shutdownStream(rtspClient);
00336   }
00337 }
00338 
00339 
00340 // Implementation of the other event handlers:
00341 
00342 void subsessionAfterPlaying(void* clientData) {
00343   MediaSubsession* subsession = (MediaSubsession*)clientData;
00344   RTSPClient* rtspClient = (RTSPClient*)(subsession->miscPtr);
00345 
00346   // Begin by closing this subsession's stream:
00347   Medium::close(subsession->sink);
00348   subsession->sink = NULL;
00349 
00350   // Next, check whether *all* subsessions' streams have now been closed:
00351   MediaSession& session = subsession->parentSession();
00352   MediaSubsessionIterator iter(session);
00353   while ((subsession = iter.next()) != NULL) {
00354     if (subsession->sink != NULL) return; // this subsession is still active
00355   }
00356 
00357   // All subsessions' streams have now been closed, so shutdown the client:
00358   shutdownStream(rtspClient);
00359 }
00360 
00361 void subsessionByeHandler(void* clientData) {
00362   MediaSubsession* subsession = (MediaSubsession*)clientData;
00363   RTSPClient* rtspClient = (RTSPClient*)subsession->miscPtr;
00364   UsageEnvironment& env = rtspClient->envir(); // alias
00365 
00366   env << *rtspClient << "Received RTCP \"BYE\" on \"" << *subsession << "\" subsession\n";
00367 
00368   // Now act as if the subsession had closed:
00369   subsessionAfterPlaying(subsession);
00370 }
00371 
00372 void streamTimerHandler(void* clientData) {
00373   ourRTSPClient* rtspClient = (ourRTSPClient*)clientData;
00374   StreamClientState& scs = rtspClient->scs; // alias
00375 
00376   scs.streamTimerTask = NULL;
00377 
00378   // Shut down the stream:
00379   shutdownStream(rtspClient);
00380 }
00381 
00382 void shutdownStream(RTSPClient* rtspClient, int exitCode) {
00383   UsageEnvironment& env = rtspClient->envir(); // alias
00384   StreamClientState& scs = ((ourRTSPClient*)rtspClient)->scs; // alias
00385 
00386   // First, check whether any subsessions have still to be closed:
00387   if (scs.session != NULL) { 
00388     Boolean someSubsessionsWereActive = False;
00389     MediaSubsessionIterator iter(*scs.session);
00390     MediaSubsession* subsession;
00391 
00392     while ((subsession = iter.next()) != NULL) {
00393       if (subsession->sink != NULL) {
00394         Medium::close(subsession->sink);
00395         subsession->sink = NULL;
00396 
00397         if (subsession->rtcpInstance() != NULL) {
00398           subsession->rtcpInstance()->setByeHandler(NULL, NULL); // in case the server sends a RTCP "BYE" while handling "TEARDOWN"
00399         }
00400 
00401         someSubsessionsWereActive = True;
00402       }
00403     }
00404 
00405     if (someSubsessionsWereActive) {
00406       // Send a RTSP "TEARDOWN" command, to tell the server to shutdown the stream.
00407       // Don't bother handling the response to the "TEARDOWN".
00408       rtspClient->sendTeardownCommand(*scs.session, NULL);
00409     }
00410   }
00411 
00412   env << *rtspClient << "Closing the stream.\n";
00413   Medium::close(rtspClient);
00414     // Note that this will also cause this stream's "StreamClientState" structure to get reclaimed.
00415 
00416   if (--rtspClientCount == 0) {
00417     // The final stream has ended, so exit the application now.
00418     // (Of course, if you're embedding this code into your own application, you might want to comment this out,
00419     // and replace it with "eventLoopWatchVariable = 1;", so that we leave the LIVE555 event loop, and continue running "main()".)
00420     exit(exitCode);
00421   }
00422 }
00423 
00424 
00425 // Implementation of "ourRTSPClient":
00426 
00427 ourRTSPClient* ourRTSPClient::createNew(UsageEnvironment& env, char const* rtspURL,
00428                                         int verbosityLevel, char const* applicationName, portNumBits tunnelOverHTTPPortNum) {
00429   return new ourRTSPClient(env, rtspURL, verbosityLevel, applicationName, tunnelOverHTTPPortNum);
00430 }
00431 
00432 ourRTSPClient::ourRTSPClient(UsageEnvironment& env, char const* rtspURL,
00433                              int verbosityLevel, char const* applicationName, portNumBits tunnelOverHTTPPortNum)
00434   : RTSPClient(env,rtspURL, verbosityLevel, applicationName, tunnelOverHTTPPortNum) {
00435 }
00436 
00437 ourRTSPClient::~ourRTSPClient() {
00438 }
00439 
00440 
00441 // Implementation of "StreamClientState":
00442 
00443 StreamClientState::StreamClientState()
00444   : iter(NULL), session(NULL), subsession(NULL), streamTimerTask(NULL), duration(0.0) {
00445 }
00446 
00447 StreamClientState::~StreamClientState() {
00448   delete iter;
00449   if (session != NULL) {
00450     // We also need to delete "session", and unschedule "streamTimerTask" (if set)
00451     UsageEnvironment& env = session->envir(); // alias
00452 
00453     env.taskScheduler().unscheduleDelayedTask(streamTimerTask);
00454     Medium::close(session);
00455   }
00456 }
00457 
00458 
00459 // Implementation of "DummySink":
00460 
00461 // Even though we're not going to be doing anything with the incoming data, we still need to receive it.
00462 // Define the size of the buffer that we'll use:
00463 #define DUMMY_SINK_RECEIVE_BUFFER_SIZE 100000
00464 
00465 DummySink* DummySink::createNew(UsageEnvironment& env, MediaSubsession& subsession, char const* streamId) {
00466   return new DummySink(env, subsession, streamId);
00467 }
00468 
00469 DummySink::DummySink(UsageEnvironment& env, MediaSubsession& subsession, char const* streamId)
00470   : MediaSink(env),
00471     fSubsession(subsession) {
00472   fStreamId = strDup(streamId);
00473   fReceiveBuffer = new u_int8_t[DUMMY_SINK_RECEIVE_BUFFER_SIZE];
00474 }
00475 
00476 DummySink::~DummySink() {
00477   delete[] fReceiveBuffer;
00478   delete[] fStreamId;
00479 }
00480 
00481 void DummySink::afterGettingFrame(void* clientData, unsigned frameSize, unsigned numTruncatedBytes,
00482                                   struct timeval presentationTime, unsigned durationInMicroseconds) {
00483   DummySink* sink = (DummySink*)clientData;
00484   sink->afterGettingFrame(frameSize, numTruncatedBytes, presentationTime, durationInMicroseconds);
00485 }
00486 
00487 // If you don't want to see debugging output for each received frame, then comment out the following line:
00488 #define DEBUG_PRINT_EACH_RECEIVED_FRAME 1
00489 
00490 void DummySink::afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes,
00491                                   struct timeval presentationTime, unsigned /*durationInMicroseconds*/) {
00492   // We've just received a frame of data.  (Optionally) print out information about it:
00493 #ifdef DEBUG_PRINT_EACH_RECEIVED_FRAME
00494   if (fStreamId != NULL) envir() << "Stream \"" << fStreamId << "\"; ";
00495   envir() << fSubsession.mediumName() << "/" << fSubsession.codecName() << ":\tReceived " << frameSize << " bytes";
00496   if (numTruncatedBytes > 0) envir() << " (with " << numTruncatedBytes << " bytes truncated)";
00497   char uSecsStr[6+1]; // used to output the 'microseconds' part of the presentation time
00498   sprintf(uSecsStr, "%06u", (unsigned)presentationTime.tv_usec);
00499   envir() << ".\tPresentation time: " << (int)presentationTime.tv_sec << "." << uSecsStr;
00500   if (fSubsession.rtpSource() != NULL && !fSubsession.rtpSource()->hasBeenSynchronizedUsingRTCP()) {
00501     envir() << "!"; // mark the debugging output to indicate that this presentation time is not RTCP-synchronized
00502   }
00503 #ifdef DEBUG_PRINT_NPT
00504   envir() << "\tNPT: " << fSubsession.getNormalPlayTime(presentationTime);
00505 #endif
00506   envir() << "\n";
00507 #endif
00508   
00509   // Then continue, to request the next frame of data:
00510   continuePlaying();
00511 }
00512 
00513 Boolean DummySink::continuePlaying() {
00514   if (fSource == NULL) return False; // sanity check (should not happen)
00515 
00516   // Request the next frame of data from our input source.  "afterGettingFrame()" will get called later, when it arrives:
00517   fSource->getNextFrame(fReceiveBuffer, DUMMY_SINK_RECEIVE_BUFFER_SIZE,
00518                         afterGettingFrame, this,
00519                         onSourceClosure, this);
00520   return True;
00521 }

Generated on Mon Apr 29 13:28:04 2013 for live by  doxygen 1.5.2