1. 程式人生 > >LIVE555學習3:live555MediaServer講解——Live555從啟動到響應Client過程分析

LIVE555學習3:live555MediaServer講解——Live555從啟動到響應Client過程分析

文章目錄

參考博文:
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會話。