int main(int argc, char** argv) { // Increase the maximum size of video frames that we can 'proxy' without truncation. // (Such frames are unreasonably large; the back-end servers should really not be sending frames this large!) OutPacketBuffer::maxSize = 100000; // bytes // Begin by setting up our usage environment: TaskScheduler* scheduler = BasicTaskScheduler::createNew(); env = BasicUsageEnvironment::createNew(*scheduler); *env << "LIVE555 Proxy Server\n" << "\t(LIVE555 Streaming Media library version " << LIVEMEDIA_LIBRARY_VERSION_STRING << "; licensed under the GNU LGPL)\n\n"; // Check command-line arguments: optional parameters, then one or more rtsp:// URLs (of streams to be proxied): progName = argv[0]; if (argc < 2) usage(); while (argc > 1) { // Process initial command-line options (beginning with "-"): char* const opt = argv[1]; if (opt[0] != '-') break; // the remaining parameters are assumed to be "rtsp://" URLs switch (opt[1]) { case 'v': { // verbose output verbosityLevel = 1; break; } case 'V': { // more verbose output verbosityLevel = 2; break; } case 't': { // Stream RTP and RTCP over the TCP 'control' connection. // (This is for the 'back end' (i.e., proxied) stream only.) streamRTPOverTCP = True; break; } case 'T': { // stream RTP and RTCP over a HTTP connection if (argc > 3 && argv[2][0] != '-') { // The next argument is the HTTP server port number: if (sscanf(argv[2], "%hu", &tunnelOverHTTPPortNum) == 1 && tunnelOverHTTPPortNum > 0) { ++argv; --argc; break; } } // If we get here, the option was specified incorrectly: usage(); break; } case 'p': { // specify a rtsp server port number if (argc > 3 && argv[2][0] != '-') { // The next argument is the rtsp server port number: if (sscanf(argv[2], "%hu", &rtspServerPortNum) == 1 && rtspServerPortNum > 0) { ++argv; --argc; break; } } // If we get here, the option was specified incorrectly: usage(); break; } case 'u': { // specify a username and password (to be used if the 'back end' (i.e., proxied) stream requires authentication) if (argc < 4) usage(); // there's no argv[3] (for the "password") username = argv[2]; password = argv[3]; argv += 2; argc -= 2; break; } case 'U': { // specify a username and password to use to authenticate incoming "REGISTER" commands if (argc < 4) usage(); // there's no argv[3] (for the "password") usernameForREGISTER = argv[2]; passwordForREGISTER = argv[3]; if (authDBForREGISTER == NULL) authDBForREGISTER = new UserAuthenticationDatabase; authDBForREGISTER->addUserRecord(usernameForREGISTER, passwordForREGISTER); argv += 2; argc -= 2; break; } case 'R': { // Handle incoming "REGISTER" requests by proxying the specified stream: proxyREGISTERRequests = True; break; } default: { usage(); break; } } ++argv; --argc; } if (argc < 2 && !proxyREGISTERRequests) usage(); // there must be at least one "rtsp://" URL at the end // Make sure that the remaining arguments appear to be "rtsp://" URLs: int i; for (i = 1; i < argc; ++i) { if (strncmp(argv[i], "rtsp://", 7) != 0) usage(); } // Do some additional checking for invalid command-line argument combinations: if (authDBForREGISTER != NULL && !proxyREGISTERRequests) { *env << "The '-U <username> <password>' option can be used only with -R\n"; usage(); } if (streamRTPOverTCP) { if (tunnelOverHTTPPortNum > 0) { *env << "The -t and -T options cannot both be used!\n"; usage(); } else { tunnelOverHTTPPortNum = (portNumBits)(~0); // hack to tell "ProxyServerMediaSession" to stream over TCP, but not using HTTP } } #ifdef ACCESS_CONTROL // To implement client access control to the RTSP server, do the following: authDB = new UserAuthenticationDatabase; authDB->addUserRecord("username1", "password1"); // replace these with real strings // Repeat this line with each <username>, <password> that you wish to allow access to the server. #endif // Create the RTSP server. Try first with the configured port number, // and then with the default port number (554) if different, // and then with the alternative port number (8554): RTSPServer* rtspServer; rtspServer = createRTSPServer(rtspServerPortNum); if (rtspServer == NULL) { if (rtspServerPortNum != 554) { *env << "Unable to create a RTSP server with port number " << rtspServerPortNum << ": " << env->getResultMsg() << "\n"; *env << "Trying instead with the standard port numbers (554 and 8554)...\n"; rtspServerPortNum = 554; rtspServer = createRTSPServer(rtspServerPortNum); } } if (rtspServer == NULL) { rtspServerPortNum = 8554; rtspServer = createRTSPServer(rtspServerPortNum); } if (rtspServer == NULL) { *env << "Failed to create RTSP server: " << env->getResultMsg() << "\n"; exit(1); } // Create a proxy for each "rtsp://" URL specified on the command line: for (i = 1; i < argc; ++i) { char const* proxiedStreamURL = argv[i]; char streamName[30]; if (argc == 2) { sprintf(streamName, "%s", "proxyStream"); // there's just one stream; give it this name } else { sprintf(streamName, "proxyStream-%d", i); // there's more than one stream; distinguish them by name } ServerMediaSession* sms = ProxyServerMediaSession::createNew(*env, rtspServer, proxiedStreamURL, streamName, username, password, tunnelOverHTTPPortNum, verbosityLevel); rtspServer->addServerMediaSession(sms); char* proxyStreamURL = rtspServer->rtspURL(sms); *env << "RTSP stream, proxying the stream \"" << proxiedStreamURL << "\"\n"; *env << "\tPlay this stream using the URL: " << proxyStreamURL << "\n"; delete[] proxyStreamURL; } if (proxyREGISTERRequests) { *env << "(We handle incoming \"REGISTER\" requests on port " << rtspServerPortNum << ")\n"; } // Also, attempt to create a HTTP server for RTSP-over-HTTP tunneling. // Try first with the default HTTP port (80), and then with the alternative HTTP // port numbers (8000 and 8080). if (rtspServer->setUpTunnelingOverHTTP(80) || rtspServer->setUpTunnelingOverHTTP(8000) || rtspServer->setUpTunnelingOverHTTP(8080)) { *env << "\n(We use port " << rtspServer->httpServerPortNum() << " for optional RTSP-over-HTTP tunneling.)\n"; } else { *env << "\n(RTSP-over-HTTP tunneling is not available.)\n"; } // Now, enter the event loop: env->taskScheduler().doEventLoop(); // does not return return 0; // only to prevent compiler warning }
// ----------------------------------------- // entry point // ----------------------------------------- int main(int argc, char** argv) { // default parameters const char *dev_name = "/dev/video0"; int format = V4L2_PIX_FMT_H264; int width = 640; int height = 480; int queueSize = 10; int fps = 25; unsigned short rtspPort = 8554; unsigned short rtspOverHTTPPort = 0; bool multicast = false; int verbose = 0; std::string outputFile; bool useMmap = true; std::string url = "unicast"; std::string murl = "multicast"; bool useThread = true; std::string maddr; bool repeatConfig = true; int timeout = 65; // decode parameters int c = 0; while ((c = getopt (argc, argv, "v::Q:O:" "I:P:T:m:u:M:ct:" "rsfF:W:H:" "h")) != -1) { switch (c) { case 'v': verbose = 1; if (optarg && *optarg=='v') verbose++; break; case 'Q': queueSize = atoi(optarg); break; case 'O': outputFile = optarg; break; // RTSP/RTP case 'I': ReceivingInterfaceAddr = inet_addr(optarg); break; case 'P': rtspPort = atoi(optarg); break; case 'T': rtspOverHTTPPort = atoi(optarg); break; case 'u': url = optarg; break; case 'm': multicast = true; murl = optarg; break; case 'M': multicast = true; maddr = optarg; break; case 'c': repeatConfig = false; break; case 't': timeout = atoi(optarg); break; // V4L2 case 'r': useMmap = false; break; case 's': useThread = false; break; case 'f': format = 0; break; case 'F': fps = atoi(optarg); break; case 'W': width = atoi(optarg); break; case 'H': height = atoi(optarg); break; case 'h': default: { std::cout << argv[0] << " [-v[v]] [-Q queueSize] [-O file]" << std::endl; std::cout << "\t [-I interface] [-P RTSP port] [-T RTSP/HTTP port] [-m multicast url] [-u unicast url] [-M multicast addr] [-c] [-t timeout]" << std::endl; std::cout << "\t [-r] [-s] [-W width] [-H height] [-F fps] [device] [device]" << std::endl; std::cout << "\t -v : verbose" << std::endl; std::cout << "\t -vv : very verbose" << std::endl; std::cout << "\t -Q length: Number of frame queue (default "<< queueSize << ")" << std::endl; std::cout << "\t -O output: Copy captured frame to a file or a V4L2 device" << std::endl; std::cout << "\t RTSP options :" << std::endl; std::cout << "\t -I addr : RTSP interface (default autodetect)" << std::endl; std::cout << "\t -P port : RTSP port (default "<< rtspPort << ")" << std::endl; std::cout << "\t -T port : RTSP over HTTP port (default "<< rtspOverHTTPPort << ")" << std::endl; std::cout << "\t -u url : unicast url (default " << url << ")" << std::endl; std::cout << "\t -m url : multicast url (default " << murl << ")" << std::endl; std::cout << "\t -M addr : multicast group:port (default is random_address:20000)" << std::endl; std::cout << "\t -c : don't repeat config (default repeat config before IDR frame)" << std::endl; std::cout << "\t -t secs : RTCP expiration timeout (default " << timeout << ")" << std::endl; std::cout << "\t V4L2 options :" << std::endl; std::cout << "\t -r : V4L2 capture using read interface (default use memory mapped buffers)" << std::endl; std::cout << "\t -s : V4L2 capture using live555 mainloop (default use a reader thread)" << std::endl; std::cout << "\t -f : V4L2 capture using current format (-W,-H,-F are ignore)" << std::endl; std::cout << "\t -W width : V4L2 capture width (default "<< width << ")" << std::endl; std::cout << "\t -H height: V4L2 capture height (default "<< height << ")" << std::endl; std::cout << "\t -F fps : V4L2 capture framerate (default "<< fps << ")" << std::endl; std::cout << "\t device : V4L2 capture device (default "<< dev_name << ")" << std::endl; exit(0); } } } std::list<std::string> devList; while (optind<argc) { devList.push_back(argv[optind]); optind++; } if (devList.empty()) { devList.push_back(dev_name); } // init logger initLogger(verbose); // create live555 environment TaskScheduler* scheduler = BasicTaskScheduler::createNew(); UsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler); // split multicast info std::istringstream is(maddr); std::string ip; getline(is, ip, ':'); struct in_addr destinationAddress; destinationAddress.s_addr = chooseRandomIPv4SSMAddress(*env); if (!ip.empty()) { destinationAddress.s_addr = inet_addr(ip.c_str()); } std::string port; getline(is, port, ':'); unsigned short rtpPortNum = 20000; if (!port.empty()) { rtpPortNum = atoi(port.c_str()); } unsigned short rtcpPortNum = rtpPortNum+1; unsigned char ttl = 5; // create RTSP server RTSPServer* rtspServer = createRTSPServer(*env, rtspPort, rtspOverHTTPPort, timeout); if (rtspServer == NULL) { LOG(ERROR) << "Failed to create RTSP server: " << env->getResultMsg(); } else { int nbSource = 0; std::list<std::string>::iterator devIt; for ( devIt=devList.begin() ; devIt!=devList.end() ; ++devIt) { std::string deviceName(*devIt); // Init capture LOG(NOTICE) << "Create V4L2 Source..." << deviceName; V4L2DeviceParameters param(deviceName.c_str(),format,width,height,fps, verbose); V4l2Capture* videoCapture = V4l2DeviceFactory::CreateVideoCapure(param, useMmap); if (videoCapture) { nbSource++; format = videoCapture->getFormat(); int outfd = -1; V4l2Output* out = NULL; if (!outputFile.empty()) { V4L2DeviceParameters outparam(outputFile.c_str(), videoCapture->getFormat(), videoCapture->getWidth(), videoCapture->getHeight(), 0,verbose); V4l2Output* out = V4l2DeviceFactory::CreateVideoOutput(outparam, useMmap); if (out != NULL) { outfd = out->getFd(); } } LOG(NOTICE) << "Start V4L2 Capture..." << deviceName; if (!videoCapture->captureStart()) { LOG(NOTICE) << "Cannot start V4L2 Capture for:" << deviceName; } V4L2DeviceSource* videoES = NULL; if (format == V4L2_PIX_FMT_H264) { videoES = H264_V4L2DeviceSource::createNew(*env, param, videoCapture, outfd, queueSize, useThread, repeatConfig); } else { videoES = V4L2DeviceSource::createNew(*env, param, videoCapture, outfd, queueSize, useThread); } if (videoES == NULL) { LOG(FATAL) << "Unable to create source for device " << deviceName; delete videoCapture; } else { // extend buffer size if needed if (videoCapture->getBufferSize() > OutPacketBuffer::maxSize) { OutPacketBuffer::maxSize = videoCapture->getBufferSize(); } StreamReplicator* replicator = StreamReplicator::createNew(*env, videoES, false); std::string baseUrl; if (devList.size() > 1) { baseUrl = basename(deviceName.c_str()); baseUrl.append("/"); } // Create Multicast Session if (multicast) { LOG(NOTICE) << "RTP address " << inet_ntoa(destinationAddress) << ":" << rtpPortNum; LOG(NOTICE) << "RTCP address " << inet_ntoa(destinationAddress) << ":" << rtcpPortNum; addSession(rtspServer, baseUrl+murl, MulticastServerMediaSubsession::createNew(*env,destinationAddress, Port(rtpPortNum), Port(rtcpPortNum), ttl, replicator,format)); // increment ports for next sessions rtpPortNum+=2; rtcpPortNum+=2; } // Create Unicast Session addSession(rtspServer, baseUrl+url, UnicastServerMediaSubsession::createNew(*env,replicator,format)); } if (out) { delete out; } } } if (nbSource>0) { // main loop signal(SIGINT,sighandler); env->taskScheduler().doEventLoop(&quit); LOG(NOTICE) << "Exiting...."; } Medium::close(rtspServer); } env->reclaim(); delete scheduler; return 0; }