LIVE555學習3:live555MediaServer講解——Live555從啟動到響應Client過程分析
文章目錄
- 1 概述
- 2 程式碼分析
- 2.1 doEventLoop
- 2.2 計劃任務
- 2.3 RTSP服務
- 2.3.1 呼叫關係
- 2.3.2 Server監聽埠的建立
- 2.3.3 計劃任務的新增
- 2.3.4 incomingConnectionHandler
- 2.3.5 Client 監聽埠的建立
- 2.3.6 計劃任務的新增
- 2.3.7 incomingRequestHandler
- 2.3.8 handleRequestBytes
- 3 小結
參考博文:
Live555學習之(三)------建立RTSP連線的過程(RTSP伺服器端)
https://www.cnblogs.com/jqctop1/p/4386533.html
live555學習筆記5-RTSP服務運作
https://blog.csdn.net/niu_gao/article/details/6911130
1 概述
在前面文章《LIVE555學習1:Linux下live555的編譯及測試》中使用了live555MediaServer測試程式,可以將本地的視訊檔案通過流傳送給RTSP Client端。於是便好奇,對live555MediaServer.cpp中live555從啟動到響應RTSP Client之間的流程進行了大致的分析,使之有個大致的印象,這樣才能更好進行隨後細緻的學習。
下面分析的是live555MediaServer.cpp中的程式,只是對大致流程進行分析,不涉及到具體程式碼的講解,剛剛開始live555的學習,對於各種虛擬函式,各種繼承感到頭疼。。。。
2 程式碼分析
live555MediaServer.cpp中的主函式如下:
int main(int argc, char** argv) { // Begin by setting up our usage environment: TaskScheduler* scheduler = BasicTaskScheduler::createNew(); UsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler); UserAuthenticationDatabase* authDB = NULL; #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 the above with each <username>, <password> that you wish to allow // access to the server. #endif // Create the RTSP server. Try first with the default port number (554), // and then with the alternative port number (8554): RTSPServer* rtspServer; portNumBits rtspServerPortNum = 554; rtspServer = DynamicRTSPServer::createNew(*env, rtspServerPortNum, authDB); if (rtspServer == NULL) { rtspServerPortNum = 8554; rtspServer = DynamicRTSPServer::createNew(*env, rtspServerPortNum, authDB); } if (rtspServer == NULL) { *env << "Failed to create RTSP server: " << env->getResultMsg() << "\n"; exit(1); } *env << "LIVE555 Media Server\n"; *env << "\tversion " << MEDIA_SERVER_VERSION_STRING << " (LIVE555 Streaming Media library version " << LIVEMEDIA_LIBRARY_VERSION_STRING << ").\n"; char* urlPrefix = rtspServer->rtspURLPrefix(); *env << "Play streams from this server using the URL\n\t" << urlPrefix << "<filename>\nwhere <filename> is a file present in the current directory.\n"; *env << "Each file's type is inferred from its name suffix:\n"; *env << "\t\".264\" => a H.264 Video Elementary Stream file\n"; *env << "\t\".265\" => a H.265 Video Elementary Stream file\n"; *env << "\t\".aac\" => an AAC Audio (ADTS format) file\n"; *env << "\t\".ac3\" => an AC-3 Audio file\n"; *env << "\t\".amr\" => an AMR Audio file\n"; *env << "\t\".dv\" => a DV Video file\n"; *env << "\t\".m4e\" => a MPEG-4 Video Elementary Stream file\n"; *env << "\t\".mkv\" => a Matroska audio+video+(optional)subtitles file\n"; *env << "\t\".mp3\" => a MPEG-1 or 2 Audio file\n"; *env << "\t\".mpg\" => a MPEG-1 or 2 Program Stream (audio+video) file\n"; *env << "\t\".ogg\" or \".ogv\" or \".opus\" => an Ogg audio and/or video file\n"; *env << "\t\".ts\" => a MPEG Transport Stream file\n"; *env << "\t\t(a \".tsx\" index file - if present - provides server 'trick play' support)\n"; *env << "\t\".vob\" => a VOB (MPEG-2 video with AC-3 audio) file\n"; *env << "\t\".wav\" => a WAV Audio file\n"; *env << "\t\".webm\" => a WebM audio(Vorbis)+video(VP8) file\n"; *env << "See http://www.live555.com/mediaServer/ for additional documentation.\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 << "(We use port " << rtspServer->httpServerPortNum() << " for optional RTSP-over-HTTP tunneling, or for HTTP live streaming (for indexed Transport Stream files only).)\n"; } else { *env << "(RTSP-over-HTTP tunneling is not available.)\n"; } env->taskScheduler().doEventLoop(); // does not return return 0; // only to prevent compiler warning }
除去一些無用的資訊,這個函式裡面主要做了兩件事,第一件是建立一個RTSP Serve服務,第二件是進入doEventLoop進行迴圈監聽。
下面瞭解一下這兩件事的邏輯。
2.1 doEventLoop
在建立完全部的服務之後,函式會進入到doEventLoop函式中去,這個函式的定義如下:
virtual void doEventLoop(char volatile* watchVariable = NULL) = 0;
可以看到,這函式其實是有引數的,如果我們指定了引數,就可以控制這個函式返回或者做一些其他的事情,如果我們不不指定引數,則這個函式一直執行,不再返回,這個函式是一個事件迴圈函式,用於排程事件。接下來,我們進入到這個函式的內部來看一下:
doEventLoop(BasicTaskScheduler0.cpp)
void BasicTaskScheduler0::doEventLoop(char volatile* watchVariable) {
// Repeatedly loop, handling readble sockets and timed events:
while (1) {
if (watchVariable != NULL && *watchVariable != 0) break;
SingleStep();
}
}
在這個函式中,是一個迴圈函式,每一次迴圈首先對傳遞進來的變數進行了判斷,用於確定是否要退出,然後就會進入SingleStep函式,接下來進入SingleStep函式一探真容:
SingleStep(BasicTaskScheduler.cpp)
這個函式程式碼有點多,就不再貼出全部的程式碼了,這個函式的主要作用是處理套接字和定時事件,即:
- ①select各個socket
- ②找出第一個應執行的socket任務(handler)並執行之
- ③找到第一個應響應的事件,並執行之
- ④找到第一個應執行的延遲任務並執行之
下面是函式的呼叫過程,記錄一下:
main (live555MediaServer.cpp) ---> doEventLoop(BaskTaskSchduler0.cpp) ---> SingleStep(BaskTaskSchduler.cpp)
2.2 計劃任務
在上面的SingleStep函式中,我們看到函式會單執行緒執行這些任務,但是這些任務是如何來的?是如何新增的?
在上面,我們看到SingleStep中,函式迴圈執行三種任務做:Socket handler、event handler、定時任務(delay task),這三種任務又是什麼?
在上面主函式中,我們看到是這樣呼叫doEventLoop的:
env->taskScheduler().doEventLoop(); // does not return
上面的taskScheduler又是什麼鬼?
關於taskScheduler這一塊,這裡就不再獻醜了,分享一篇大神博文,寫的很詳細:
live555學習筆記4-計劃任務(TaskScheduler)深入探討
https://blog.csdn.net/niu_gao/article/details/6910549
在上面這篇博文中,我們可以看到三種任務的新增方式,如下:
delay task為:
void setBackgroundHandling(int socketNum, int conditionSet ,BackgroundHandlerProc* handlerProc, void* clientData)
event handler為:
EventTriggerId createEventTrigger(TaskFunc* eventHandlerProc)
delay task為:
TaskToken scheduleDelayedTask(int64_t microseconds, TaskFunc* proc,void* clientData)
關於任務是如何新增的,可以參考下面的博文:
live555 中的socket的任務排程分析
https://www.cnblogs.com/superPerfect/p/3611625.html
本文只是想介紹一些大致流程,所以對程式碼不會細緻講解,上面兩篇博文已經很詳細地說明了計劃任務相關的資訊。
而在接下來的分析中,我們也會看到計劃任務相關的,例如:
env.taskScheduler().turnOnBackgroundReadHandling(fServerSocket, incomingConnectionHandler, this);
envir().taskScheduler().setBackgroundHandling(fOurSocket, SOCKET_READABLE|SOCKET_EXCEPTION, incomingRequestHandler, this);
在主函式中,我們介紹了doEventLoop函式,接下來,則要介紹一下另外一個重要的部分—RTSP服務。
2.3 RTSP服務
2.3.1 呼叫關係
首先記錄一下部分函式之間的呼叫關係,接下來的分析就是按照這個呼叫關係依次進行分析的。
下面加粗的部分為重點部分。
main (live555MediaServer.cpp) ---> createNew(DynamicRTSPServer.cpp) ---> DynamicRTSPServer (DynamicRTSPServer.cpp) --->RTSPServerSupportingHTTPStreaming(RTSPServerSupportingHTTPStreaming.cpp) ---> RTSPServer(RTSPServer.cpp) --->GenericMediaServer(GenericMediaServer.cpp) ---> incomingConnectionHandler(GenericMediaServer.cpp) ---> incomingConnectionHandlerOnSocket(GenericMediaServer.cpp) ---> createNewClientConnection(RTSPServer.cpp)--->RTSPClientConnection(RTSPServer.cpp) ---> ClientConnection(GenericMediaServer.cpp)--->incomingRequestHandler(GenericMediaServer.cpp) ---> handleRequestBytes(RTSPServer.cpp)
在handleRequestBytes中,有對如下方法的對應函式解析
handleCmd_OPTIONS/handleCmd_DESCRIBE/handleCmd_SETUP/handleCmd_PLAY/handleCmd_PAUSE/handleCmd_TEARDOWN
2.3.2 Server監聽埠的建立
如下,在createNew函式中,建立了一個554埠的socket,
DynamicRTSPServer*
DynamicRTSPServer::createNew(UsageEnvironment& env, Port ourPort,
UserAuthenticationDatabase* authDatabase,
unsigned reclamationTestSeconds) {
int ourSocket = setUpOurSocket(env, ourPort);
if (ourSocket == -1) return NULL;
return new DynamicRTSPServer(env, ourSocket, ourPort, authDatabase, reclamationTestSeconds);
}
2.3.3 計劃任務的新增
在建立完554埠的socket後,需要將這個socket加入計劃任務,然後輪訓進行監聽。
函式如下:
GenericMediaServer
::GenericMediaServer(UsageEnvironment& env, int ourSocket, Port ourPort,
unsigned reclamationSeconds)
: Medium(env),
fServerSocket(ourSocket), fServerPort(ourPort), fReclamationSeconds(reclamationSeconds),
fServerMediaSessions(HashTable::create(STRING_HASH_KEYS)),
fClientConnections(HashTable::create(ONE_WORD_HASH_KEYS)),
fClientSessions(HashTable::create(STRING_HASH_KEYS)) {
ignoreSigPipeOnSocket(fServerSocket); // so that clients on the same host that are killed don't also kill us
// Arrange to handle connections from others:
env.taskScheduler().turnOnBackgroundReadHandling(fServerSocket, incomingConnectionHandler, this);
}
注意到函式的最後部分是將這個socket加入了計劃任務,當監聽到這個socket的時候就會呼叫incomingConnectionHandler函式。
2.3.4 incomingConnectionHandler
當select到server socket的時候,就會呼叫incomingConnectionHandler這個函式,然後處理一些資訊。如下:
void GenericMediaServer::incomingConnectionHandler(void* instance, int /*mask*/) {
GenericMediaServer* server = (GenericMediaServer*)instance;
server->incomingConnectionHandler();
}
void GenericMediaServer::incomingConnectionHandler() {
incomingConnectionHandlerOnSocket(fServerSocket);
}
可以看到,最終是呼叫了incomingConnectionHandlerOnSocket函式,我們接下來分析這個函式。
2.3.5 Client 監聽埠的建立
incomingConnectionHandlerOnSocket函式原型如下:
void GenericMediaServer::incomingConnectionHandlerOnSocket(int serverSocket) {
struct sockaddr_in clientAddr;
SOCKLEN_T clientAddrLen = sizeof clientAddr;
int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &clientAddrLen);
if (clientSocket < 0) {
int err = envir().getErrno();
if (err != EWOULDBLOCK) {
envir().setResultErrMsg("accept() failed: ");
}
return;
}
ignoreSigPipeOnSocket(clientSocket); // so that clients on the same host that are killed don't also kill us
makeSocketNonBlocking(clientSocket);
increaseSendBufferTo(envir(), clientSocket, 50*1024);
#ifdef DEBUG
envir() << "accept()ed connection from " << AddressString(clientAddr).val() << "\n";
#endif
// Create a new object for handling this connection:
(void)createNewClientConnection(clientSocket, clientAddr);
}
通過以上函式發現,當收到代表客戶端的連線時,獲取了一個Client Socket,然後會使用這個新的Socket繼續與RTSP Client進行通訊。
接下來繼續看一下createNewClientConnection:
GenericMediaServer::ClientConnection*
RTSPServer::createNewClientConnection(int clientSocket, struct sockaddr_in clientAddr) {
return new RTSPClientConnection(*this, clientSocket, clientAddr);
}
在這個createNewClientConnection中,我們看到建立了一個RTSPClientConnection物件,所以每來一個RTSP Client的連線,這時候就會建立一個RTSP Client物件,用於和RTSP Client進行RTSP會話互動。
繼續往下,看看這個RTSPClientConnection物件做了什麼工作?
2.3.6 計劃任務的新增
經過一系列的呼叫,我們可以看到如下函式:
GenericMediaServer::ClientConnection
::ClientConnection(GenericMediaServer& ourServer, int clientSocket, struct sockaddr_in clientAddr)
: fOurServer(ourServer), fOurSocket(clientSocket), fClientAddr(clientAddr) {
// Add ourself to our 'client connections' table:
fOurServer.fClientConnections->Add((char const*)this, this);
// Arrange to handle incoming requests:
resetRequestBuffer();
envir().taskScheduler()
.setBackgroundHandling(fOurSocket, SOCKET_READABLE|SOCKET_EXCEPTION, incomingRequestHandler, this);
}
在函式的最後,我們看到又把自己的socket handler加入到了計劃任務,並且當監聽到資料的時候,就會呼叫incomingRequestHandler函式。
2.3.7 incomingRequestHandler
在incomingRequestHandler函式中,可以看到,最終呼叫的是handleRequestBytes。
2.3.8 handleRequestBytes
這個函式便是最終函式,在這個函式裡面,會分析接收到的RTSP Client端的會話(OPTIONS/DESCRIBE/SETUP/TEARDOWN/PLAY/PAUSE/GET_PARAMETER/SET_PARAMETER)
然後呼叫相應的函式進行迴應,如:handleCmd_DESCRIBE、handleCmd_OPTIONS、handleCmd_GET_PARAMETER、handleCmd_SET_PARAMETER、handleCmd_SETUP、handleCmd_TEARDOWN、handleCmd_PLAY、handleCmd_PAUSE等。
這裡只是介紹一下整個流程,所以RTSP會話的互動過程這裡就不再詳細介紹了。
3 小結
通過以上的分析,可以看到當RTSP Server Socket建立之後,就將其加入計劃任務進行監聽,當有RTSP Client連線時候,建立一個屬於RTSP Client的物件,並將新的Socket加入計劃任務,隨後使用這個Socket與Client進行RTSP會話。