Live555还提供了录像的示例程序,在testProgs目录下的playCommon.cpp中,Live555录像的基本原理就是创建一个RTSPClient去请求指定rtsp地址的视频,然后保存到文件里。
playCommon.cpp打开一看就发现首先是各种全局函数的声明,然后是各种全局变量的声明,然后是main函数和各个函数的实现。main函数中首先还是创建TaskScheduler对象和UsageEnvironment对象,然后根据各种输入参数设置各种全局变量,最后就是创建一个RTSPClient对象请求指定rtsp地址的视频。
1 int main(int argc, char** argv) 2 { 3 // Begin by setting up our usage environment: 4 TaskScheduler* scheduler = BasicTaskScheduler::createNew(); 5 env = BasicUsageEnvironment::createNew(*scheduler); 6 7 /* 8 处理各种输入参数,在此省略 9 */ 10 streamURL = argv[1]; 11 12 // Create (or arrange to create) our client object: 13 if (createHandlerServerForREGISTERCommand) { 14 handlerServerForREGISTERCommand 15 = HandlerServerForREGISTERCommand::createNew(*env, continueAfterClientCreation0, 16 handlerServerForREGISTERCommandPortNum, authDBForREGISTER, 17 verbosityLevel, progName); 18 if (handlerServerForREGISTERCommand == NULL) { 19 *env << "Failed to create a server for handling incoming "REGISTER" commands: " << env->getResultMsg() << " "; 20 } else { 21 *env << "Awaiting an incoming "REGISTER" command on port " << handlerServerForREGISTERCommand->serverPortNum() << " "; 22 } 23 } else { 24 ourClient = createClient(*env, streamURL, verbosityLevel, progName); 25 if (ourClient == NULL) { 26 *env << "Failed to create " << clientProtocolName << " client: " << env->getResultMsg() << " "; 27 shutdown(); 28 } 29 continueAfterClientCreation1(); 30 } 31 32 // All subsequent activity takes place within the event loop: 33 env->taskScheduler().doEventLoop(); // does not return 34 35 return 0; // only to prevent compiler warning 36 } 37
createClient函数在openRTSP.cpp文件中定义,内容很简单,就是调用了RTSPClient::createNew函数创建了一个RTSPClient对象。我们来看continueAfterClientCreation1函数
1 void continueAfterClientCreation1() { 2 setUserAgentString(userAgent); 3 4 if (sendOptionsRequest) { 5 // Begin by sending an "OPTIONS" command: 6 getOptions(continueAfterOPTIONS); // 发送OPTIONS命令,我们也可以跳过这一步直接发送DESCRIBE命令 7 } else { 8 continueAfterOPTIONS(NULL, 0, NULL); 9 } 10 } 11 12 void getOptions(RTSPClient::responseHandler* afterFunc) { 13 ourRTSPClient->sendOptionsCommand(afterFunc, ourAuthenticator); 14 } 15 16 void continueAfterOPTIONS(RTSPClient*, int resultCode, char* resultString) { 17 if (sendOptionsRequestOnly) { 18 if (resultCode != 0) { 19 *env << clientProtocolName << " "OPTIONS" request failed: " << resultString << " "; 20 } else { 21 *env << clientProtocolName << " "OPTIONS" request returned: " << resultString << " "; 22 } 23 shutdown(); 24 } 25 delete[] resultString; 26 27 // Next, get a SDP description for the stream: 28 getSDPDescription(continueAfterDESCRIBE); // 发送DESCRIBE命令 29 } 30 31 void getSDPDescription(RTSPClient::responseHandler* afterFunc) { 32 ourRTSPClient->sendDescribeCommand(afterFunc, ourAuthenticator); 33 } 34 35 void continueAfterDESCRIBE(RTSPClient*, int resultCode, char* resultString) { 36 if (resultCode != 0) { 37 *env << "Failed to get a SDP description for the URL "" << streamURL << "": " << resultString << " "; 38 delete[] resultString; 39 shutdown(); 40 } 41 42 char* sdpDescription = resultString; 43 *env << "Opened URL "" << streamURL << "", returning a SDP description: " << sdpDescription << " "; 44 45 // Create a media session object from this SDP description: 46 session = MediaSession::createNew(*env, sdpDescription); //创建MediaSession 47 delete[] sdpDescription; 48 if (session == NULL) { 49 *env << "Failed to create a MediaSession object from the SDP description: " << env->getResultMsg() << " "; 50 shutdown(); 51 } else if (!session->hasSubsessions()) { 52 *env << "This session has no media subsessions (i.e., no "m=" lines) "; 53 shutdown(); 54 } 55 56 // Then, setup the "RTPSource"s for the session: 57 MediaSubsessionIterator iter(*session); 58 MediaSubsession *subsession; 59 Boolean madeProgress = False; 60 char const* singleMediumToTest = singleMedium; 61 while ((subsession = iter.next()) != NULL) { 62 // If we've asked to receive only a single medium, then check this now: 63 if (singleMediumToTest != NULL) { 64 if (strcmp(subsession->mediumName(), singleMediumToTest) != 0) { 65 *env << "Ignoring "" << subsession->mediumName() 66 << "/" << subsession->codecName() 67 << "" subsession, because we've asked to receive a single " << singleMedium 68 << " session only "; 69 continue; 70 } else { 71 // Receive this subsession only 72 singleMediumToTest = "xxxxx"; 73 // this hack ensures that we get only 1 subsession of this type 74 } 75 } 76 77 if (desiredPortNum != 0) { 78 subsession->setClientPortNum(desiredPortNum); //创建相关的RTPSource、Groupsock等资源 79 desiredPortNum += 2; 80 } 81 82 if (createReceivers) { //我们接收数据然后保存在文件中,createReceivers为true 83 if (!subsession->initiate(simpleRTPoffsetArg)) { //初始化MediaSubsession 84 *env << "Unable to create receiver for "" << subsession->mediumName() 85 << "/" << subsession->codecName() 86 << "" subsession: " << env->getResultMsg() << " "; 87 } else { 88 *env << "Created receiver for "" << subsession->mediumName() 89 << "/" << subsession->codecName() << "" subsession ("; 90 if (subsession->rtcpIsMuxed()) { 91 *env << "client port " << subsession->clientPortNum(); 92 } else { 93 *env << "client ports " << subsession->clientPortNum() 94 << "-" << subsession->clientPortNum()+1; 95 } 96 *env << ") "; 97 madeProgress = True; 98 99 if (subsession->rtpSource() != NULL) { 100 // Because we're saving the incoming data, rather than playing 101 // it in real time, allow an especially large time threshold 102 // (1 second) for reordering misordered incoming packets: 103 unsigned const thresh = 1000000; // 1 second 104 subsession->rtpSource()->setPacketReorderingThresholdTime(thresh); 105 106 // Set the RTP source's OS socket buffer size as appropriate - either if we were explicitly asked (using -B), 107 // or if the desired FileSink buffer size happens to be larger than the current OS socket buffer size. 108 // (The latter case is a heuristic, on the assumption that if the user asked for a large FileSink buffer size, 109 // then the input data rate may be large enough to justify increasing the OS socket buffer size also.) 110 int socketNum = subsession->rtpSource()->RTPgs()->socketNum(); 111 unsigned curBufferSize = getReceiveBufferSize(*env, socketNum); 112 if (socketInputBufferSize > 0 || fileSinkBufferSize > curBufferSize) { 113 unsigned newBufferSize = socketInputBufferSize > 0 ? socketInputBufferSize : fileSinkBufferSize; 114 newBufferSize = setReceiveBufferTo(*env, socketNum, newBufferSize); 115 if (socketInputBufferSize > 0) { // The user explicitly asked for the new socket buffer size; announce it: 116 *env << "Changed socket receive buffer size for the "" 117 << subsession->mediumName() 118 << "/" << subsession->codecName() 119 << "" subsession from " 120 << curBufferSize << " to " 121 << newBufferSize << " bytes "; 122 } 123 } 124 } 125 } 126 } else { 127 if (subsession->clientPortNum() == 0) { 128 *env << "No client port was specified for the "" 129 << subsession->mediumName() 130 << "/" << subsession->codecName() 131 << "" subsession. (Try adding the "-p <portNum>" option.) "; 132 } else { 133 madeProgress = True; 134 } 135 } 136 } 137 if (!madeProgress) shutdown(); 138 139 // Perform additional 'setup' on each subsession, before playing them: 140 setupStreams(); //对每个ServerMediaSubsession发送SETUP命令 141 }
上面的流程和RTSPClient端与服务器建立连接的过程基本类似,先发送OPTIONS命令,然后发送DESCRIBE命令,然后发送SETUP命令。我们在此也可以忽略发送OPTIONS命令,直接从发送DESCRIBE命令开始。在setupStreams函数中分别对每个ServerMediaSubsession发送SETUP命令,我们来看一下setupStreams函数
1 void setupStreams() { 2 static MediaSubsessionIterator* setupIter = NULL; 3 if (setupIter == NULL) setupIter = new MediaSubsessionIterator(*session); 4 while ((subsession = setupIter->next()) != NULL) { 5 // We have another subsession left to set up: 6 if (subsession->clientPortNum() == 0) continue; // port # was not set 7 8 setupSubsession(subsession, streamUsingTCP, forceMulticastOnUnspecified, continueAfterSETUP); //发送SETUP命令,建立与ServerMediaSubsession的连接 9 return; 10 } 11 12 // We're done setting up subsessions. //与所有的ServerMediaSubsession建立连接成功 13 delete setupIter; 14 if (!madeProgress) shutdown(); 15 16 // Create output files: 17 if (createReceivers) { 18 if (fileOutputInterval > 0) { 19 createPeriodicOutputFiles(); //创建周期性的输出文件,例如:我们可以设置每一个小时输出一个录像文件 20 } else { 21 createOutputFiles(""); 22 } 23 } 24 25 // Finally, start playing each subsession, to start the data flow: 26 if (duration == 0) { 27 if (scale > 0) duration = session->playEndTime() - initialSeekTime; // use SDP end time 28 else if (scale < 0) duration = initialSeekTime; 29 } 30 if (duration < 0) duration = 0.0; 31 32 endTime = initialSeekTime; 33 if (scale > 0) { 34 if (duration <= 0) endTime = -1.0f; 35 else endTime = initialSeekTime + duration; 36 } else { 37 endTime = initialSeekTime - duration; 38 if (endTime < 0) endTime = 0.0f; 39 } 40 // 发送PLAY命令请求开始播放视频 41 char const* absStartTime = initialAbsoluteSeekTime != NULL ? initialAbsoluteSeekTime : session->absStartTime(); 42 if (absStartTime != NULL) { 43 // Either we or the server have specified that seeking should be done by 'absolute' time: 44 startPlayingSession(session, absStartTime, session->absEndTime(), scale, continueAfterPLAY); 45 } else { 46 // Normal case: Seek by relative time (NPT): 47 startPlayingSession(session, initialSeekTime, endTime, scale, continueAfterPLAY); 48 } 49 } 50 51 void setupSubsession(MediaSubsession* subsession, Boolean streamUsingTCP, Boolean forceMulticastOnUnspecified, RTSPClient::responseHandler* afterFunc) { 52 53 ourRTSPClient->sendSetupCommand(*subsession, afterFunc, False, streamUsingTCP, forceMulticastOnUnspecified, ourAuthenticator); 54 } 55 56 void startPlayingSession(MediaSession* session, double start, double end, float scale, RTSPClient::responseHandler* afterFunc) { 57 printf(" %f - %f ",start,end); 58 ourRTSPClient->sendPlayCommand(*session, afterFunc, start, end, scale, ourAuthenticator); 59 }
在setupStreams函数中依次与每个ServerMediaSubsession建立连接,都建立连接成功后,然后就调用createPeriodicOutputFiles函数周期性地创建输出录像的文件,然后开始发送PLAY命令请求播放视频。接下来我们看看createPeriodicOutputFiles这个函数
1 void createPeriodicOutputFiles() { 2 // Create a filename suffix that notes the time interval that's being recorded: 3 char periodicFileNameSuffix[100]; 4 snprintf(periodicFileNameSuffix, sizeof periodicFileNameSuffix, "-%05d-%05d", 5 fileOutputSecondsSoFar, fileOutputSecondsSoFar + fileOutputInterval); 6 createOutputFiles(periodicFileNameSuffix); //创建输出文件 7 8 // Schedule an event for writing the next output file: //添加一个停止当前录像,创建新录像文件的任务 9 periodicFileOutputTask 10 = env->taskScheduler().scheduleDelayedTask(fileOutputInterval*1000000, 11 (TaskFunc*)periodicFileOutputTimerHandler, 12 (void*)NULL); 13 } 14 15 void createOutputFiles(char const* periodicFilenameSuffix) { 16 char outFileName[1000]; 17
// 创建对应的FileSink来获取和保存视频数据 18 if (outputQuickTimeFile || outputAVIFile) { 19 if (periodicFilenameSuffix[0] == '