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-2012, 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       break;
00198     }
00199 
00200     char* const sdpDescription = resultString;
00201     env << *rtspClient << "Got a SDP description:\n" << sdpDescription << "\n";
00202 
00203     // Create a media session object from this SDP description:
00204     scs.session = MediaSession::createNew(env, sdpDescription);
00205     delete[] sdpDescription; // because we don't need it anymore
00206     if (scs.session == NULL) {
00207       env << *rtspClient << "Failed to create a MediaSession object from the SDP description: " << env.getResultMsg() << "\n";
00208       break;
00209     } else if (!scs.session->hasSubsessions()) {
00210       env << *rtspClient << "This session has no media subsessions (i.e., no \"m=\" lines)\n";
00211       break;
00212     }
00213 
00214     // Then, create and set up our data source objects for the session.  We do this by iterating over the session's 'subsessions',
00215     // calling "MediaSubsession::initiate()", and then sending a RTSP "SETUP" command, on each one.
00216     // (Each 'subsession' will have its own data source.)
00217     scs.iter = new MediaSubsessionIterator(*scs.session);
00218     setupNextSubsession(rtspClient);
00219     return;
00220   } while (0);
00221 
00222   // An unrecoverable error occurred with this stream.
00223   shutdownStream(rtspClient);
00224 }
00225 
00226 void setupNextSubsession(RTSPClient* rtspClient) {
00227   UsageEnvironment& env = rtspClient->envir(); // alias
00228   StreamClientState& scs = ((ourRTSPClient*)rtspClient)->scs; // alias
00229   
00230   scs.subsession = scs.iter->next();
00231   if (scs.subsession != NULL) {
00232     if (!scs.subsession->initiate()) {
00233       env << *rtspClient << "Failed to initiate the \"" << *scs.subsession << "\" subsession: " << env.getResultMsg() << "\n";
00234       setupNextSubsession(rtspClient); // give up on this subsession; go to the next one
00235     } else {
00236       env << *rtspClient << "Initiated the \"" << *scs.subsession
00237           << "\" subsession (client ports " << scs.subsession->clientPortNum() << "-" << scs.subsession->clientPortNum()+1 << ")\n";
00238 
00239       // Continue setting up this subsession, by sending a RTSP "SETUP" command:
00240       rtspClient->sendSetupCommand(*scs.subsession, continueAfterSETUP);
00241     }
00242     return;
00243   }
00244 
00245   // We've finished setting up all of the subsessions.  Now, send a RTSP "PLAY" command to start the streaming:
00246   scs.duration = scs.session->playEndTime() - scs.session->playStartTime();
00247   rtspClient->sendPlayCommand(*scs.session, continueAfterPLAY);
00248 }
00249 
00250 void continueAfterSETUP(RTSPClient* rtspClient, int resultCode, char* resultString) {
00251   do {
00252     UsageEnvironment& env = rtspClient->envir(); // alias
00253     StreamClientState& scs = ((ourRTSPClient*)rtspClient)->scs; // alias
00254 
00255     if (resultCode != 0) {
00256       env << *rtspClient << "Failed to set up the \"" << *scs.subsession << "\" subsession: " << env.getResultMsg() << "\n";
00257       break;
00258     }
00259 
00260     env << *rtspClient << "Set up the \"" << *scs.subsession
00261         << "\" subsession (client ports " << scs.subsession->clientPortNum() << "-" << scs.subsession->clientPortNum()+1 << ")\n";
00262 
00263     // Having successfully setup the subsession, create a data sink for it, and call "startPlaying()" on it.
00264     // (This will prepare the data sink to receive data; the actual flow of data from the client won't start happening until later,
00265     // after we've sent a RTSP "PLAY" command.)
00266 
00267     scs.subsession->sink = DummySink::createNew(env, *scs.subsession, rtspClient->url());
00268       // perhaps use your own custom "MediaSink" subclass instead
00269     if (scs.subsession->sink == NULL) {
00270       env << *rtspClient << "Failed to create a data sink for the \"" << *scs.subsession
00271           << "\" subsession: " << env.getResultMsg() << "\n";
00272       break;
00273     }
00274 
00275     env << *rtspClient << "Created a data sink for the \"" << *scs.subsession << "\" subsession\n";
00276     scs.subsession->miscPtr = rtspClient; // a hack to let subsession handle functions get the "RTSPClient" from the subsession 
00277     scs.subsession->sink->startPlaying(*(scs.subsession->readSource()),
00278                                        subsessionAfterPlaying, scs.subsession);
00279     // Also set a handler to be called if a RTCP "BYE" arrives for this subsession:
00280     if (scs.subsession->rtcpInstance() != NULL) {
00281       scs.subsession->rtcpInstance()->setByeHandler(subsessionByeHandler, scs.subsession);
00282     }
00283   } while (0);
00284 
00285   // Set up the next subsession, if any:
00286   setupNextSubsession(rtspClient);
00287 }
00288 
00289 void continueAfterPLAY(RTSPClient* rtspClient, int resultCode, char* resultString) {
00290   do {
00291     UsageEnvironment& env = rtspClient->envir(); // alias
00292     StreamClientState& scs = ((ourRTSPClient*)rtspClient)->scs; // alias
00293 
00294     if (resultCode != 0) {
00295       env << *rtspClient << "Failed to start playing session: " << resultString << "\n";
00296       break;
00297     }
00298 
00299     // Set a timer to be handled at the end of the stream's expected duration (if the stream does not already signal its end
00300     // using a RTCP "BYE").  This is optional.  If, instead, you want to keep the stream active - e.g., so you can later
00301     // 'seek' back within it and do another RTSP "PLAY" - then you can omit this code.
00302     // (Alternatively, if you don't want to receive the entire stream, you could set this timer for some shorter value.)
00303     if (scs.duration > 0) {
00304       unsigned const delaySlop = 2; // number of seconds extra to delay, after the stream's expected duration.  (This is optional.)
00305       scs.duration += delaySlop;
00306       unsigned uSecsToDelay = (unsigned)(scs.duration*1000000);
00307       scs.streamTimerTask = env.taskScheduler().scheduleDelayedTask(uSecsToDelay, (TaskFunc*)streamTimerHandler, rtspClient);
00308     }
00309 
00310     env << *rtspClient << "Started playing session";
00311     if (scs.duration > 0) {
00312       env << " (for up to " << scs.duration << " seconds)";
00313     }
00314     env << "...\n";
00315 
00316     return;
00317   } while (0);
00318 
00319   // An unrecoverable error occurred with this stream.
00320   shutdownStream(rtspClient);
00321 }
00322 
00323 
00324 // Implementation of the other event handlers:
00325 
00326 void subsessionAfterPlaying(void* clientData) {
00327   MediaSubsession* subsession = (MediaSubsession*)clientData;
00328   RTSPClient* rtspClient = (RTSPClient*)(subsession->miscPtr);
00329 
00330   // Begin by closing this subsession's stream:
00331   Medium::close(subsession->sink);
00332   subsession->sink = NULL;
00333 
00334   // Next, check whether *all* subsessions' streams have now been closed:
00335   MediaSession& session = subsession->parentSession();
00336   MediaSubsessionIterator iter(session);
00337   while ((subsession = iter.next()) != NULL) {
00338     if (subsession->sink != NULL) return; // this subsession is still active
00339   }
00340 
00341   // All subsessions' streams have now been closed, so shutdown the client:
00342   shutdownStream(rtspClient);
00343 }
00344 
00345 void subsessionByeHandler(void* clientData) {
00346   MediaSubsession* subsession = (MediaSubsession*)clientData;
00347   RTSPClient* rtspClient = (RTSPClient*)subsession->miscPtr;
00348   UsageEnvironment& env = rtspClient->envir(); // alias
00349 
00350   env << *rtspClient << "Received RTCP \"BYE\" on \"" << *subsession << "\" subsession\n";
00351 
00352   // Now act as if the subsession had closed:
00353   subsessionAfterPlaying(subsession);
00354 }
00355 
00356 void streamTimerHandler(void* clientData) {
00357   ourRTSPClient* rtspClient = (ourRTSPClient*)clientData;
00358   StreamClientState& scs = rtspClient->scs; // alias
00359 
00360   scs.streamTimerTask = NULL;
00361 
00362   // Shut down the stream:
00363   shutdownStream(rtspClient);
00364 }
00365 
00366 void shutdownStream(RTSPClient* rtspClient, int exitCode) {
00367   UsageEnvironment& env = rtspClient->envir(); // alias
00368   StreamClientState& scs = ((ourRTSPClient*)rtspClient)->scs; // alias
00369 
00370   // First, check whether any subsessions have still to be closed:
00371   if (scs.session != NULL) { 
00372     Boolean someSubsessionsWereActive = False;
00373     MediaSubsessionIterator iter(*scs.session);
00374     MediaSubsession* subsession;
00375 
00376     while ((subsession = iter.next()) != NULL) {
00377       if (subsession->sink != NULL) {
00378         Medium::close(subsession->sink);
00379         subsession->sink = NULL;
00380 
00381         if (subsession->rtcpInstance() != NULL) {
00382           subsession->rtcpInstance()->setByeHandler(NULL, NULL); // in case the server sends a RTCP "BYE" while handling "TEARDOWN"
00383         }
00384 
00385         someSubsessionsWereActive = True;
00386       }
00387     }
00388 
00389     if (someSubsessionsWereActive) {
00390       // Send a RTSP "TEARDOWN" command, to tell the server to shutdown the stream.
00391       // Don't bother handling the response to the "TEARDOWN".
00392       rtspClient->sendTeardownCommand(*scs.session, NULL);
00393     }
00394   }
00395 
00396   env << *rtspClient << "Closing the stream.\n";
00397   Medium::close(rtspClient);
00398     // Note that this will also cause this stream's "StreamClientState" structure to get reclaimed.
00399 
00400   if (--rtspClientCount == 0) {
00401     // The final stream has ended, so exit the application now.
00402     // (Of course, if you're embedding this code into your own application, you might want to comment this out,
00403     // and replace it with "eventLoopWatchVariable = 1;", so that we leave the LIVE555 event loop, and continue running "main()".)
00404     exit(exitCode);
00405   }
00406 }
00407 
00408 
00409 // Implementation of "ourRTSPClient":
00410 
00411 ourRTSPClient* ourRTSPClient::createNew(UsageEnvironment& env, char const* rtspURL,
00412                                         int verbosityLevel, char const* applicationName, portNumBits tunnelOverHTTPPortNum) {
00413   return new ourRTSPClient(env, rtspURL, verbosityLevel, applicationName, tunnelOverHTTPPortNum);
00414 }
00415 
00416 ourRTSPClient::ourRTSPClient(UsageEnvironment& env, char const* rtspURL,
00417                              int verbosityLevel, char const* applicationName, portNumBits tunnelOverHTTPPortNum)
00418   : RTSPClient(env,rtspURL, verbosityLevel, applicationName, tunnelOverHTTPPortNum) {
00419 }
00420 
00421 ourRTSPClient::~ourRTSPClient() {
00422 }
00423 
00424 
00425 // Implementation of "StreamClientState":
00426 
00427 StreamClientState::StreamClientState()
00428   : iter(NULL), session(NULL), subsession(NULL), streamTimerTask(NULL), duration(0.0) {
00429 }
00430 
00431 StreamClientState::~StreamClientState() {
00432   delete iter;
00433   if (session != NULL) {
00434     // We also need to delete "session", and unschedule "streamTimerTask" (if set)
00435     UsageEnvironment& env = session->envir(); // alias
00436 
00437     env.taskScheduler().unscheduleDelayedTask(streamTimerTask);
00438     Medium::close(session);
00439   }
00440 }
00441 
00442 
00443 // Implementation of "DummySink":
00444 
00445 // Even though we're not going to be doing anything with the incoming data, we still need to receive it.
00446 // Define the size of the buffer that we'll use:
00447 #define DUMMY_SINK_RECEIVE_BUFFER_SIZE 100000
00448 
00449 DummySink* DummySink::createNew(UsageEnvironment& env, MediaSubsession& subsession, char const* streamId) {
00450   return new DummySink(env, subsession, streamId);
00451 }
00452 
00453 DummySink::DummySink(UsageEnvironment& env, MediaSubsession& subsession, char const* streamId)
00454   : MediaSink(env),
00455     fSubsession(subsession) {
00456   fStreamId = strDup(streamId);
00457   fReceiveBuffer = new u_int8_t[DUMMY_SINK_RECEIVE_BUFFER_SIZE];
00458 }
00459 
00460 DummySink::~DummySink() {
00461   delete[] fReceiveBuffer;
00462   delete[] fStreamId;
00463 }
00464 
00465 void DummySink::afterGettingFrame(void* clientData, unsigned frameSize, unsigned numTruncatedBytes,
00466                                   struct timeval presentationTime, unsigned durationInMicroseconds) {
00467   DummySink* sink = (DummySink*)clientData;
00468   sink->afterGettingFrame(frameSize, numTruncatedBytes, presentationTime, durationInMicroseconds);
00469 }
00470 
00471 // If you don't want to see debugging output for each received frame, then comment out the following line:
00472 #define DEBUG_PRINT_EACH_RECEIVED_FRAME 1
00473 
00474 void DummySink::afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes,
00475                                   struct timeval presentationTime, unsigned /*durationInMicroseconds*/) {
00476   // We've just received a frame of data.  (Optionally) print out information about it:
00477 #ifdef DEBUG_PRINT_EACH_RECEIVED_FRAME
00478   if (fStreamId != NULL) envir() << "Stream \"" << fStreamId << "\"; ";
00479   envir() << fSubsession.mediumName() << "/" << fSubsession.codecName() << ":\tReceived " << frameSize << " bytes";
00480   if (numTruncatedBytes > 0) envir() << " (with " << numTruncatedBytes << " bytes truncated)";
00481   char uSecsStr[6+1]; // used to output the 'microseconds' part of the presentation time
00482   sprintf(uSecsStr, "%06u", (unsigned)presentationTime.tv_usec);
00483   envir() << ".\tPresentation time: " << (unsigned)presentationTime.tv_sec << "." << uSecsStr;
00484   if (fSubsession.rtpSource() != NULL && !fSubsession.rtpSource()->hasBeenSynchronizedUsingRTCP()) {
00485     envir() << "!"; // mark the debugging output to indicate that this presentation time is not RTCP-synchronized
00486   }
00487   envir() << "\n";
00488 #endif
00489   
00490   // Then continue, to request the next frame of data:
00491   continuePlaying();
00492 }
00493 
00494 Boolean DummySink::continuePlaying() {
00495   if (fSource == NULL) return False; // sanity check (should not happen)
00496 
00497   // Request the next frame of data from our input source.  "afterGettingFrame()" will get called later, when it arrives:
00498   fSource->getNextFrame(fReceiveBuffer, DUMMY_SINK_RECEIVE_BUFFER_SIZE,
00499                         afterGettingFrame, this,
00500                         onSourceClosure, this);
00501   return True;
00502 }

Generated on Thu May 17 07:11:48 2012 for live by  doxygen 1.5.2