testProgs/playCommon.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 // Copyright (c) 1996-2008, Live Networks, Inc.  All rights reserved
00017 // A common framework, used for the "openRTSP" and "playSIP" applications
00018 // Implementation
00019 
00020 #include "playCommon.hh"
00021 #include "BasicUsageEnvironment.hh"
00022 #include "GroupsockHelper.hh"
00023 #ifdef SUPPORT_REAL_RTSP
00024 #include "../RealRTSP/include/RealRTSP.hh"
00025 #endif
00026 
00027 #if defined(__WIN32__) || defined(_WIN32)
00028 #define snprintf _snprintf
00029 #else
00030 #include <signal.h>
00031 #define USE_SIGNALS 1
00032 #endif
00033 
00034 // Forward function definitions:
00035 void setupStreams();
00036 void startPlayingStreams();
00037 void tearDownStreams();
00038 void closeMediaSinks();
00039 void subsessionAfterPlaying(void* clientData);
00040 void subsessionByeHandler(void* clientData);
00041 void sessionAfterPlaying(void* clientData = NULL);
00042 void sessionTimerHandler(void* clientData);
00043 void shutdown(int exitCode = 1);
00044 void signalHandlerShutdown(int sig);
00045 void checkForPacketArrival(void* clientData);
00046 void checkInterPacketGaps(void* clientData);
00047 void beginQOSMeasurement();
00048 
00049 char const* progName;
00050 UsageEnvironment* env;
00051 Medium* ourClient = NULL;
00052 MediaSession* session = NULL;
00053 TaskToken sessionTimerTask = NULL;
00054 TaskToken arrivalCheckTimerTask = NULL;
00055 TaskToken interPacketGapCheckTimerTask = NULL;
00056 TaskToken qosMeasurementTimerTask = NULL;
00057 Boolean createReceivers = True;
00058 Boolean outputQuickTimeFile = False;
00059 Boolean generateMP4Format = False;
00060 QuickTimeFileSink* qtOut = NULL;
00061 Boolean outputAVIFile = False;
00062 AVIFileSink* aviOut = NULL;
00063 Boolean audioOnly = False;
00064 Boolean videoOnly = False;
00065 char const* singleMedium = NULL;
00066 int verbosityLevel = 1; // by default, print verbose output
00067 double duration = 0;
00068 double durationSlop = -1.0; // extra seconds to play at the end
00069 double initialSeekTime = 0.0f;
00070 double scale = 1.0f;
00071 unsigned interPacketGapMaxTime = 0;
00072 unsigned totNumPacketsReceived = ~0; // used if checking inter-packet gaps
00073 Boolean playContinuously = False;
00074 int simpleRTPoffsetArg = -1;
00075 Boolean sendOptionsRequest = True;
00076 Boolean sendOptionsRequestOnly = False;
00077 Boolean oneFilePerFrame = False;
00078 Boolean notifyOnPacketArrival = False;
00079 Boolean streamUsingTCP = False;
00080 portNumBits tunnelOverHTTPPortNum = 0;
00081 char* username = NULL;
00082 char* password = NULL;
00083 char* proxyServerName = NULL;
00084 unsigned short proxyServerPortNum = 0;
00085 unsigned char desiredAudioRTPPayloadFormat = 0;
00086 char* mimeSubtype = NULL;
00087 unsigned short movieWidth = 240; // default
00088 Boolean movieWidthOptionSet = False;
00089 unsigned short movieHeight = 180; // default
00090 Boolean movieHeightOptionSet = False;
00091 unsigned movieFPS = 15; // default
00092 Boolean movieFPSOptionSet = False;
00093 char* fileNamePrefix = "";
00094 unsigned fileSinkBufferSize = 20000;
00095 unsigned socketInputBufferSize = 0;
00096 Boolean packetLossCompensate = False;
00097 Boolean syncStreams = False;
00098 Boolean generateHintTracks = False;
00099 unsigned qosMeasurementIntervalMS = 0; // 0 means: Don't output QOS data
00100 unsigned statusCode = 0;
00101 
00102 struct timeval startTime;
00103 
00104 void usage() {
00105   *env << "Usage: " << progName
00106        << " [-p <startPortNum>] [-r|-q|-4|-i] [-a|-v] [-V] [-d <duration>] [-D <max-inter-packet-gap-time> [-c] [-S <offset>] [-n] [-O]"
00107            << (controlConnectionUsesTCP ? " [-t|-T <http-port>]" : "")
00108        << " [-u <username> <password>"
00109            << (allowProxyServers ? " [<proxy-server> [<proxy-server-port>]]" : "")
00110        << "]" << (supportCodecSelection ? " [-A <audio-codec-rtp-payload-format-code>|-M <mime-subtype-name>]" : "")
00111        << " [-s <initial-seek-time>] [-z <scale>]"
00112        << " [-w <width> -h <height>] [-f <frames-per-second>] [-y] [-H] [-Q [<measurement-interval>]] [-F <filename-prefix>] [-b <file-sink-buffer-size>] [-B <input-socket-buffer-size>] [-I <input-interface-ip-address>] [-m] <url> (or " << progName << " -o [-V] <url>)\n";
00113   //##### Add "-R <dest-rtsp-url>" #####
00114   shutdown();
00115 }
00116 
00117 int main(int argc, char** argv) {
00118   // Begin by setting up our usage environment:
00119   TaskScheduler* scheduler = BasicTaskScheduler::createNew();
00120   env = BasicUsageEnvironment::createNew(*scheduler);
00121 
00122   progName = argv[0];
00123 
00124   gettimeofday(&startTime, NULL);
00125 
00126 #ifdef USE_SIGNALS
00127   // Allow ourselves to be shut down gracefully by a SIGHUP or a SIGUSR1:
00128   signal(SIGHUP, signalHandlerShutdown);
00129   signal(SIGUSR1, signalHandlerShutdown);
00130 #endif
00131 
00132   unsigned short desiredPortNum = 0;
00133 
00134   // unfortunately we can't use getopt() here, as Windoze doesn't have it
00135   while (argc > 2) {
00136     char* const opt = argv[1];
00137     if (opt[0] != '-') usage();
00138     switch (opt[1]) {
00139 
00140     case 'p': { // specify start port number
00141       int portArg;
00142       if (sscanf(argv[2], "%d", &portArg) != 1) {
00143         usage();
00144       }
00145       if (portArg <= 0 || portArg >= 65536 || portArg&1) {
00146         *env << "bad port number: " << portArg
00147                 << " (must be even, and in the range (0,65536))\n";
00148         usage();
00149       }
00150       desiredPortNum = (unsigned short)portArg;
00151       ++argv; --argc;
00152       break;
00153     }
00154 
00155     case 'r': { // do not receive data (instead, just 'play' the stream(s))
00156       createReceivers = False;
00157       break;
00158     }
00159 
00160     case 'q': { // output a QuickTime file (to stdout)
00161       outputQuickTimeFile = True;
00162       break;
00163     }
00164 
00165     case '4': { // output a 'mp4'-format file (to stdout)
00166       outputQuickTimeFile = True;
00167       generateMP4Format = True;
00168       break;
00169     }
00170 
00171     case 'i': { // output an AVI file (to stdout)
00172       outputAVIFile = True;
00173       break;
00174     }
00175 
00176     case 'I': { // specify input interface... 
00177       NetAddressList addresses(argv[2]);
00178       if (addresses.numAddresses() == 0) {
00179         *env << "Failed to find network address for \"" << argv[2] << "\"";
00180         break;
00181       }
00182       ReceivingInterfaceAddr = *(unsigned*)(addresses.firstAddress()->data());
00183       ++argv; --argc;
00184       break;
00185     }
00186 
00187     case 'a': { // receive/record an audio stream only
00188       audioOnly = True;
00189       singleMedium = "audio";
00190       break;
00191     }
00192 
00193     case 'v': { // receive/record a video stream only
00194       videoOnly = True;
00195       singleMedium = "video";
00196       break;
00197     }
00198 
00199     case 'V': { // disable verbose output
00200       verbosityLevel = 0;
00201       break;
00202     }
00203 
00204     case 'd': { // specify duration, or how much to delay after end time
00205       float arg;
00206       if (sscanf(argv[2], "%g", &arg) != 1) {
00207         usage();
00208       }
00209       if (argv[2][0] == '-') { // not "arg<0", in case argv[2] was "-0"
00210         // a 'negative' argument was specified; use this for "durationSlop":
00211         duration = 0; // use whatever's in the SDP
00212         durationSlop = -arg;
00213       } else {
00214         duration = arg;
00215         durationSlop = 0;
00216       }
00217       ++argv; --argc;
00218       break;
00219     }
00220 
00221     case 'D': { // specify maximum number of seconds to wait for packets:
00222       if (sscanf(argv[2], "%u", &interPacketGapMaxTime) != 1) {
00223         usage();
00224       }
00225       ++argv; --argc;
00226       break;
00227     }
00228 
00229     case 'c': { // play continuously
00230       playContinuously = True;
00231       break;
00232     }
00233 
00234     case 'S': { // specify an offset to use with "SimpleRTPSource"s
00235       if (sscanf(argv[2], "%d", &simpleRTPoffsetArg) != 1) {
00236         usage();
00237       }
00238       if (simpleRTPoffsetArg < 0) {
00239         *env << "offset argument to \"-S\" must be >= 0\n";
00240         usage();
00241       }
00242       ++argv; --argc;
00243       break;
00244     }
00245 
00246     case 'O': { // Don't send an "OPTIONS" request before "DESCRIBE"
00247       sendOptionsRequest = False;
00248       break;
00249     }
00250 
00251     case 'o': { // Send only the "OPTIONS" request to the server
00252       sendOptionsRequestOnly = True;
00253       break;
00254     }
00255 
00256     case 'm': { // output multiple files - one for each frame
00257       oneFilePerFrame = True;
00258       break;
00259     }
00260 
00261     case 'n': { // notify the user when the first data packet arrives
00262       notifyOnPacketArrival = True;
00263       break;
00264     }
00265 
00266     case 't': {
00267       // stream RTP and RTCP over the TCP 'control' connection
00268       if (controlConnectionUsesTCP) {
00269         streamUsingTCP = True;
00270       } else {
00271         usage();
00272       }
00273       break;
00274     }
00275 
00276     case 'T': {
00277       // stream RTP and RTCP over a HTTP connection
00278       if (controlConnectionUsesTCP) {
00279         if (argc > 3 && argv[2][0] != '-') {
00280           // The next argument is the HTTP server port number:
00281           if (sscanf(argv[2], "%hu", &tunnelOverHTTPPortNum) == 1
00282               && tunnelOverHTTPPortNum > 0) {
00283             ++argv; --argc;
00284             break;
00285           }
00286         }
00287       }
00288 
00289       // If we get here, the option was specified incorrectly:
00290       usage();
00291       break;
00292     }
00293 
00294     case 'u': { // specify a username and password
00295       username = argv[2];
00296       password = argv[3];
00297       argv+=2; argc-=2;
00298       if (allowProxyServers && argc > 3 && argv[2][0] != '-') {
00299         // The next argument is the name of a proxy server:
00300         proxyServerName = argv[2];
00301         ++argv; --argc;
00302 
00303         if (argc > 3 && argv[2][0] != '-') {
00304           // The next argument is the proxy server port number:
00305           if (sscanf(argv[2], "%hu", &proxyServerPortNum) != 1) {
00306             usage();
00307           }
00308           ++argv; --argc;
00309         }
00310       }
00311       break;
00312     }
00313 
00314     case 'A': { // specify a desired audio RTP payload format
00315       unsigned formatArg;
00316       if (sscanf(argv[2], "%u", &formatArg) != 1
00317           || formatArg >= 96) {
00318         usage();
00319       }
00320       desiredAudioRTPPayloadFormat = (unsigned char)formatArg;
00321       ++argv; --argc;
00322       break;
00323     }
00324 
00325     case 'M': { // specify a MIME subtype for a dynamic RTP payload type
00326       mimeSubtype = argv[2];
00327       if (desiredAudioRTPPayloadFormat==0) desiredAudioRTPPayloadFormat =96;
00328       ++argv; --argc;
00329       break;
00330     }
00331 
00332     case 'w': { // specify a width (pixels) for an output QuickTime or AVI movie
00333       if (sscanf(argv[2], "%hu", &movieWidth) != 1) {
00334         usage();
00335       }
00336       movieWidthOptionSet = True;
00337       ++argv; --argc;
00338       break;
00339     }
00340 
00341     case 'h': { // specify a height (pixels) for an output QuickTime or AVI movie
00342       if (sscanf(argv[2], "%hu", &movieHeight) != 1) {
00343         usage();
00344       }
00345       movieHeightOptionSet = True;
00346       ++argv; --argc;
00347       break;
00348     }
00349 
00350     case 'f': { // specify a frame rate (per second) for an output QT or AVI movie
00351       if (sscanf(argv[2], "%u", &movieFPS) != 1) {
00352         usage();
00353       }
00354       movieFPSOptionSet = True;
00355       ++argv; --argc;
00356       break;
00357     }
00358 
00359     case 'F': { // specify a prefix for the audio and video output files
00360       fileNamePrefix = argv[2];
00361       ++argv; --argc;
00362       break;
00363     }
00364 
00365     case 'b': { // specify the size of buffers for "FileSink"s
00366       if (sscanf(argv[2], "%u", &fileSinkBufferSize) != 1) {
00367         usage();
00368       }
00369       ++argv; --argc;
00370       break;
00371     }
00372 
00373     case 'B': { // specify the size of input socket buffers
00374       if (sscanf(argv[2], "%u", &socketInputBufferSize) != 1) {
00375         usage();
00376       }
00377       ++argv; --argc;
00378       break;
00379     }
00380 
00381     // Note: The following option is deprecated, and may someday be removed:
00382     case 'l': { // try to compensate for packet loss by repeating frames
00383       packetLossCompensate = True;
00384       break;
00385     }
00386 
00387     case 'y': { // synchronize audio and video streams
00388       syncStreams = True;
00389       break;
00390     }
00391 
00392     case 'H': { // generate hint tracks (as well as the regular data tracks)
00393       generateHintTracks = True;
00394       break;
00395     }
00396 
00397     case 'Q': { // output QOS measurements
00398       qosMeasurementIntervalMS = 1000; // default: 1 second
00399 
00400       if (argc > 3 && argv[2][0] != '-') {
00401         // The next argument is the measurement interval,
00402         // in multiples of 100 ms
00403         if (sscanf(argv[2], "%u", &qosMeasurementIntervalMS) != 1) {
00404           usage();
00405         }
00406         qosMeasurementIntervalMS *= 100;
00407         ++argv; --argc;
00408       }
00409       break;
00410     }
00411 
00412     case 's': { // specify initial seek time (trick play)
00413       float arg;
00414       if (sscanf(argv[2], "%g", &arg) != 1 || arg < 0) {
00415         usage();
00416       }
00417       initialSeekTime = arg;
00418       ++argv; --argc;
00419       break;
00420     }
00421 
00422     case 'z': { // scale (trick play)
00423       float arg;
00424       if (sscanf(argv[2], "%g", &arg) != 1 || arg == 0.0f) {
00425         usage();
00426       }
00427       scale = arg;
00428       ++argv; --argc;
00429       break;
00430     }
00431 
00432     default: {
00433       usage();
00434       break;
00435     }
00436     }
00437 
00438     ++argv; --argc;
00439   }
00440   if (argc != 2) usage();
00441   if (outputQuickTimeFile && outputAVIFile) {
00442     *env << "The -i and -q (or -4) flags cannot both be used!\n";
00443     usage();
00444   }
00445   Boolean outputCompositeFile = outputQuickTimeFile || outputAVIFile;
00446   if (!createReceivers && outputCompositeFile) {
00447     *env << "The -r and -q (or -4 or -i) flags cannot both be used!\n";
00448     usage();
00449   }
00450   if (outputCompositeFile && !movieWidthOptionSet) {
00451     *env << "Warning: The -q, -4 or -i option was used, but not -w.  Assuming a video width of "
00452          << movieWidth << " pixels\n";
00453   }
00454   if (outputCompositeFile && !movieHeightOptionSet) {
00455     *env << "Warning: The -q, -4 or -i option was used, but not -h.  Assuming a video height of "
00456          << movieHeight << " pixels\n";
00457   }
00458   if (outputCompositeFile && !movieFPSOptionSet) {
00459     *env << "Warning: The -q, -4 or -i option was used, but not -f.  Assuming a video frame rate of "
00460          << movieFPS << " frames-per-second\n";
00461   }
00462   if (audioOnly && videoOnly) {
00463     *env << "The -a and -v flags cannot both be used!\n";
00464     usage();
00465   }
00466   if (sendOptionsRequestOnly && !sendOptionsRequest) {
00467     *env << "The -o and -O flags cannot both be used!\n";
00468     usage();
00469   }
00470   if (tunnelOverHTTPPortNum > 0) {
00471     if (streamUsingTCP) {
00472       *env << "The -t and -T flags cannot both be used!\n";
00473       usage();
00474     } else {
00475       streamUsingTCP = True;
00476     }
00477   }
00478   if (!createReceivers && notifyOnPacketArrival) {
00479     *env << "Warning: Because we're not receiving stream data, the -n flag has no effect\n";
00480   }
00481   if (durationSlop < 0) {
00482     // This parameter wasn't set, so use a default value.
00483     // If we're measuring QOS stats, then don't add any slop, to avoid
00484     // having 'empty' measurement intervals at the end.
00485     durationSlop = qosMeasurementIntervalMS > 0 ? 0.0 : 5.0;
00486   }
00487 
00488   char* url = argv[1];
00489 
00490   // Create our client object:
00491   ourClient = createClient(*env, verbosityLevel, progName);
00492   if (ourClient == NULL) {
00493     *env << "Failed to create " << clientProtocolName
00494                 << " client: " << env->getResultMsg() << "\n";
00495     shutdown();
00496   }
00497 
00498   if (sendOptionsRequest) {
00499     // Begin by sending an "OPTIONS" command:
00500     char* optionsResponse
00501       = getOptionsResponse(ourClient, url, username, password);
00502     if (sendOptionsRequestOnly) {
00503       if (optionsResponse == NULL) {
00504         *env << clientProtocolName << " \"OPTIONS\" request failed: "
00505              << env->getResultMsg() << "\n";
00506       } else {
00507         *env << clientProtocolName << " \"OPTIONS\" request returned: "
00508              << optionsResponse << "\n";
00509       }
00510       shutdown();
00511     }
00512     delete[] optionsResponse;
00513   }
00514 
00515   // Open the URL, to get a SDP description:
00516   char* sdpDescription
00517     = getSDPDescriptionFromURL(ourClient, url, username, password,
00518                                proxyServerName, proxyServerPortNum,
00519                                desiredPortNum);
00520   if (sdpDescription == NULL) {
00521     *env << "Failed to get a SDP description from URL \"" << url
00522                 << "\": " << env->getResultMsg() << "\n";
00523     shutdown();
00524   }
00525 
00526   *env << "Opened URL \"" << url
00527           << "\", returning a SDP description:\n" << sdpDescription << "\n";
00528 
00529   // Create a media session object from this SDP description:
00530   session = MediaSession::createNew(*env, sdpDescription);
00531   delete[] sdpDescription;
00532   if (session == NULL) {
00533     *env << "Failed to create a MediaSession object from the SDP description: " << env->getResultMsg() << "\n";
00534     shutdown();
00535   } else if (!session->hasSubsessions()) {
00536     *env << "This session has no media subsessions (i.e., \"m=\" lines)\n";
00537     shutdown();
00538   }
00539 
00540   // Then, setup the "RTPSource"s for the session:
00541   MediaSubsessionIterator iter(*session);
00542   MediaSubsession *subsession;
00543   Boolean madeProgress = False;
00544   char const* singleMediumToTest = singleMedium;
00545   while ((subsession = iter.next()) != NULL) {
00546     // If we've asked to receive only a single medium, then check this now:
00547     if (singleMediumToTest != NULL) {
00548       if (strcmp(subsession->mediumName(), singleMediumToTest) != 0) {
00549                   *env << "Ignoring \"" << subsession->mediumName()
00550                           << "/" << subsession->codecName()
00551                           << "\" subsession, because we've asked to receive a single " << singleMedium
00552                           << " session only\n";
00553         continue;
00554       } else {
00555         // Receive this subsession only
00556         singleMediumToTest = "xxxxx";
00557             // this hack ensures that we get only 1 subsession of this type
00558       }
00559     }
00560 
00561     if (desiredPortNum != 0) {
00562       subsession->setClientPortNum(desiredPortNum);
00563       desiredPortNum += 2;
00564     }
00565 
00566     if (createReceivers) {
00567       if (!subsession->initiate(simpleRTPoffsetArg)) {
00568                 *env << "Unable to create receiver for \"" << subsession->mediumName()
00569                         << "/" << subsession->codecName()
00570                         << "\" subsession: " << env->getResultMsg() << "\n";
00571       } else {
00572                 *env << "Created receiver for \"" << subsession->mediumName()
00573                         << "/" << subsession->codecName()
00574                         << "\" subsession (client ports " << subsession->clientPortNum()
00575                         << "-" << subsession->clientPortNum()+1 << ")\n";
00576                 madeProgress = True;
00577 
00578                 if (subsession->rtpSource() != NULL) {
00579                   // Because we're saving the incoming data, rather than playing
00580                   // it in real time, allow an especially large time threshold
00581                   // (1 second) for reordering misordered incoming packets:
00582                   unsigned const thresh = 1000000; // 1 second 
00583                   subsession->rtpSource()->setPacketReorderingThresholdTime(thresh);
00584 
00585                   if (socketInputBufferSize > 0) {
00586                     // Set the RTP source's input buffer size as specified:
00587                     int socketNum
00588                       = subsession->rtpSource()->RTPgs()->socketNum();
00589                     unsigned curBufferSize
00590                       = getReceiveBufferSize(*env, socketNum);
00591                     unsigned newBufferSize
00592                       = setReceiveBufferTo(*env, socketNum, socketInputBufferSize);
00593                     *env << "Changed socket receive buffer size for the \""
00594                          << subsession->mediumName()
00595                          << "/" << subsession->codecName()
00596                          << "\" subsession from "
00597                          << curBufferSize << " to "
00598                          << newBufferSize << " bytes\n";
00599                   }
00600                 }
00601       }
00602     } else {
00603       if (subsession->clientPortNum() == 0) {
00604         *env << "No client port was specified for the \""
00605              << subsession->mediumName()
00606              << "/" << subsession->codecName()
00607              << "\" subsession.  (Try adding the \"-p <portNum>\" option.)\n";
00608       } else {  
00609                 madeProgress = True;
00610       }
00611     }
00612   }
00613   if (!madeProgress) shutdown();
00614 
00615   // Perform additional 'setup' on each subsession, before playing them:
00616   setupStreams();
00617 
00618   // Create output files:
00619   if (createReceivers) {
00620     if (outputQuickTimeFile) {
00621       // Create a "QuickTimeFileSink", to write to 'stdout':
00622       qtOut = QuickTimeFileSink::createNew(*env, *session, "stdout",
00623                                            fileSinkBufferSize,
00624                                            movieWidth, movieHeight,
00625                                            movieFPS,
00626                                            packetLossCompensate,
00627                                            syncStreams,
00628                                            generateHintTracks,
00629                                            generateMP4Format);
00630       if (qtOut == NULL) {
00631         *env << "Failed to create QuickTime file sink for stdout: " << env->getResultMsg();
00632         shutdown();
00633       }
00634 
00635       qtOut->startPlaying(sessionAfterPlaying, NULL);
00636     } else if (outputAVIFile) {
00637       // Create an "AVIFileSink", to write to 'stdout':
00638       aviOut = AVIFileSink::createNew(*env, *session, "stdout",
00639                                       fileSinkBufferSize,
00640                                       movieWidth, movieHeight,
00641                                       movieFPS,
00642                                       packetLossCompensate);
00643       if (aviOut == NULL) {
00644         *env << "Failed to create AVI file sink for stdout: " << env->getResultMsg();
00645         shutdown();
00646       }
00647 
00648       aviOut->startPlaying(sessionAfterPlaying, NULL);
00649 #ifdef SUPPORT_REAL_RTSP
00650     } else if (session->isRealNetworksRDT) {
00651       // For RealNetworks' sessions, we create a single output file,
00652       // named "output.rm".
00653       char outFileName[1000];
00654       if (singleMedium == NULL) {
00655         snprintf(outFileName, sizeof outFileName, "%soutput.rm", fileNamePrefix);
00656       } else {
00657         // output to 'stdout' as normal, even though we actually output all media
00658         sprintf(outFileName, "stdout");
00659       }
00660       FileSink* fileSink = FileSink::createNew(*env, outFileName,
00661                                                fileSinkBufferSize, oneFilePerFrame);
00662 
00663       // The output file needs to begin with a special 'RMFF' header,
00664       // in order for it to be usable.  Write this header first:
00665       unsigned headerSize;
00666       unsigned char* headerData = RealGenerateRMFFHeader(session, headerSize);
00667       struct timeval timeNow;
00668       gettimeofday(&timeNow, NULL);
00669       fileSink->addData(headerData, headerSize, timeNow);
00670       delete[] headerData;
00671 
00672       // Start playing the output file from the first subsession.
00673       // (Hack: Because all subsessions' data is actually multiplexed on the
00674       // single RTSP TCP connection, playing from one subsession is sufficient.)
00675       iter.reset();
00676       madeProgress = False;
00677       while ((subsession = iter.next()) != NULL) {
00678         if (subsession->readSource() == NULL) continue; // was not initiated
00679 
00680         fileSink->startPlaying(*(subsession->readSource()),
00681                                subsessionAfterPlaying, subsession);
00682         madeProgress = True;
00683         break; // play from one subsession only
00684       }
00685       if (!madeProgress) shutdown();
00686 #endif
00687     } else {
00688       // Create and start "FileSink"s for each subsession:
00689       madeProgress = False;
00690       iter.reset();
00691       while ((subsession = iter.next()) != NULL) {
00692         if (subsession->readSource() == NULL) continue; // was not initiated
00693         
00694         // Create an output file for each desired stream:
00695         char outFileName[1000];
00696         if (singleMedium == NULL) {
00697           // Output file name is
00698           //     "<filename-prefix><medium_name>-<codec_name>-<counter>"
00699           static unsigned streamCounter = 0;
00700           snprintf(outFileName, sizeof outFileName, "%s%s-%s-%d",
00701                    fileNamePrefix, subsession->mediumName(),
00702                    subsession->codecName(), ++streamCounter);
00703         } else {
00704           sprintf(outFileName, "stdout");
00705         }
00706         FileSink* fileSink;
00707         if (strcmp(subsession->mediumName(), "audio") == 0 &&
00708             (strcmp(subsession->codecName(), "AMR") == 0 ||
00709              strcmp(subsession->codecName(), "AMR-WB") == 0)) {
00710           // For AMR audio streams, we use a special sink that inserts AMR frame hdrs:
00711           fileSink = AMRAudioFileSink::createNew(*env, outFileName,
00712                                                  fileSinkBufferSize, oneFilePerFrame);
00713         } else if (strcmp(subsession->mediumName(), "video") == 0 &&
00714             (strcmp(subsession->codecName(), "H264") == 0)) {
00715       // For H.264 video stream, we use a special sink that insert start_codes:
00716           fileSink = H264VideoFileSink::createNew(*env, outFileName,
00717                                                  fileSinkBufferSize, oneFilePerFrame);
00718         } else {
00719           // Normal case:
00720           fileSink = FileSink::createNew(*env, outFileName,
00721                                          fileSinkBufferSize, oneFilePerFrame);
00722         }
00723         subsession->sink = fileSink;
00724         if (subsession->sink == NULL) {
00725           *env << "Failed to create FileSink for \"" << outFileName
00726                   << "\": " << env->getResultMsg() << "\n";
00727         } else {
00728           if (singleMedium == NULL) {
00729             *env << "Created output file: \"" << outFileName << "\"\n";
00730           } else {
00731             *env << "Outputting data from the \"" << subsession->mediumName()
00732                         << "/" << subsession->codecName()
00733                         << "\" subsession to 'stdout'\n";
00734           }
00735 
00736           if (strcmp(subsession->mediumName(), "video") == 0 &&
00737               strcmp(subsession->codecName(), "MP4V-ES") == 0 &&
00738               subsession->fmtp_config() != NULL) {
00739             // For MPEG-4 video RTP streams, the 'config' information
00740             // from the SDP description contains useful VOL etc. headers.
00741             // Insert this data at the front of the output file:
00742             unsigned configLen;
00743             unsigned char* configData
00744               = parseGeneralConfigStr(subsession->fmtp_config(), configLen);
00745             struct timeval timeNow;
00746             gettimeofday(&timeNow, NULL);
00747             fileSink->addData(configData, configLen, timeNow);
00748             delete[] configData;
00749           }
00750 
00751           subsession->sink->startPlaying(*(subsession->readSource()),
00752                                          subsessionAfterPlaying,
00753                                          subsession);
00754           
00755           // Also set a handler to be called if a RTCP "BYE" arrives
00756           // for this subsession:
00757           if (subsession->rtcpInstance() != NULL) {
00758             subsession->rtcpInstance()->setByeHandler(subsessionByeHandler,
00759                                                       subsession);
00760           }
00761 
00762           madeProgress = True;
00763         }
00764       }
00765       if (!madeProgress) shutdown();
00766     }
00767   }
00768     
00769   // Finally, start playing each subsession, to start the data flow:
00770 
00771   startPlayingStreams();
00772 
00773   env->taskScheduler().doEventLoop(); // does not return
00774 
00775   return 0; // only to prevent compiler warning
00776 }
00777 
00778 
00779 void setupStreams() {
00780   MediaSubsessionIterator iter(*session);
00781   MediaSubsession *subsession;
00782   Boolean madeProgress = False;
00783 
00784   while ((subsession = iter.next()) != NULL) {
00785     if (subsession->clientPortNum() == 0) continue; // port # was not set
00786 
00787     if (!clientSetupSubsession(ourClient, subsession, streamUsingTCP)) {
00788       *env << "Failed to setup \"" << subsession->mediumName()
00789                 << "/" << subsession->codecName()
00790                 << "\" subsession: " << env->getResultMsg() << "\n";
00791     } else {
00792       *env << "Setup \"" << subsession->mediumName()
00793                 << "/" << subsession->codecName()
00794                 << "\" subsession (client ports " << subsession->clientPortNum()
00795                 << "-" << subsession->clientPortNum()+1 << ")\n";
00796       madeProgress = True;
00797     }
00798   }
00799   if (!madeProgress) shutdown();
00800 }
00801 
00802 void startPlayingStreams() {
00803   if (duration == 0) {
00804     if (scale > 0) duration = session->playEndTime() - initialSeekTime; // use SDP end time
00805     else if (scale < 0) duration = initialSeekTime;
00806   }
00807   if (duration < 0) duration = 0.0;
00808 
00809   if (!clientStartPlayingSession(ourClient, session)) {
00810     *env << "Failed to start playing session: " << env->getResultMsg() << "\n";
00811     shutdown();
00812   } else {
00813     *env << "Started playing session\n";
00814   }
00815 
00816   if (qosMeasurementIntervalMS > 0) {
00817     // Begin periodic QOS measurements:
00818     beginQOSMeasurement();
00819   }
00820 
00821   // Figure out how long to delay (if at all) before shutting down, or
00822   // repeating the playing
00823   Boolean timerIsBeingUsed = False;
00824   double secondsToDelay = duration;
00825   if (duration > 0) {
00826     double const maxDelayTime
00827       = (double)( ((unsigned)0x7FFFFFFF)/1000000.0 );
00828     if (