live555 源碼分析(六:mediaServer分析(上) )

“ LIVE555媒體服務器”是完整的RTSP服務器應用程序。 它可以流式傳輸幾種媒體文件。詳情可以回去看第一篇的1.5節。

6.1 main函數

mediaServer是一個完整RTSP服務器程序,所以我們還是從main函數開始看起,然後通過創建的類的對象,畫一個類圖,把前面學習過的類全部串聯起來,這樣才清楚明白很多。

我把打印的信息去掉

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
  // 要對RTSP服務器實施客戶端訪問控制,請執行以下操作:
  authDB = new UserAuthenticationDatabase;
  authDB->addUserRecord("username1", "password1"); 
  // 用真實的字符串替換它們。對要允許訪問服務器的每個<用戶名>,<密碼>重複上述操作。
#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();

  // 另外,嘗試爲HTTP上的RTSP隧道創建HTTP服務器。
  // 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
}

main函數比較簡單,做的事情不多:

  1. 創建了延時調度任務的對象,BasicTaskScheduler。
  2. 創建基礎打印對象,BasicUsageEnvironment。
  3. 定義ACCESS_CONTROL宏,就需要訪問權限,創建訪問權限的對象UserAuthenticationDatabase。
  4. 創建RTSP對象,這個以前沒看過,現在要仔細分析分析,先嚐試端口554,然後再嘗試8554.
  5. 創建HTTP隧道端口,也是先嚐試80,然後8000,最後8080。
  6. 最後調用循環框架doEventLoop。

6.2 基礎部分

基礎部分,前面幾節已經分析過了,現在提供一個類圖,根據這個類圖就會很好的記住,前面分析的類,類太多了,還是需要用圖的方式來記憶,這個靠譜。

在這裏插入圖片描述
這個圖內容比較多,我們前面就是講了這麼多個類,這裏也剛好回憶回憶。

6.3 RTSP對象

這次學聰明瞭,先搞類圖,通過類圖分析。

在這裏插入圖片描述
這個過程就是在創建RTSP的對象的時候,執行的路徑。

我們現在一個一個看。

6.3.1 DynamicRTSPServer::createNew

DynamicRTSPServer*
DynamicRTSPServer::createNew(UsageEnvironment& env, Port ourPort,
			     UserAuthenticationDatabase* authDatabase,
			     unsigned reclamationTestSeconds) {
  int ourSocket = setUpOurSocket(env, ourPort);     //創建一個socket,這個等下再看
  if (ourSocket == -1) return NULL; 				//  判斷這個socket是否有效

  //調用構造函數
  return new DynamicRTSPServer(env, ourSocket, ourPort, authDatabase, reclamationTestSeconds);
}

reclamationTestSeconds:有一個默認參數的=65

6.3.2 DynamicRTSPServer::DynamicRTSPServer

DynamicRTSPServer::DynamicRTSPServer(UsageEnvironment& env, int ourSocket,
				     Port ourPort,
				     UserAuthenticationDatabase* authDatabase, unsigned reclamationTestSeconds)
  : RTSPServerSupportingHTTPStreaming(env, ourSocket, ourPort, authDatabase, reclamationTestSeconds) {
}

這個構造函數就是往父類的構造函數執行,一路執行。

6.3.3 RTSPServerSupportingHTTPStreaming::RTSPServerSupportingHTTPStreaming

RTSPServerSupportingHTTPStreaming
::RTSPServerSupportingHTTPStreaming(UsageEnvironment& env, int ourSocket, Port rtspPort,
				    UserAuthenticationDatabase* authDatabase, unsigned reclamationTestSeconds)
  : RTSPServer(env, ourSocket, rtspPort, authDatabase, reclamationTestSeconds) {
}

由調用父類的構造函數。

6.3.4 RTSPServer::RTSPServer

RTSPServer::RTSPServer(UsageEnvironment& env,
		       int ourSocket, Port ourPort,
		       UserAuthenticationDatabase* authDatabase,
		       unsigned reclamationSeconds)
  : GenericMediaServer(env, ourSocket, ourPort, reclamationSeconds),
    fHTTPServerSocket(-1), fHTTPServerPort(0),
    fClientConnectionsForHTTPTunneling(NULL), // will get created if needed
    fTCPStreamingDatabase(HashTable::create(ONE_WORD_HASH_KEYS)),
    fPendingRegisterOrDeregisterRequests(HashTable::create(ONE_WORD_HASH_KEYS)),
    fRegisterOrDeregisterRequestCounter(0), fAuthDB(authDatabase), fAllowStreamingRTPOverTCP(True) {
}

這個構造函數也調用了父類的構造函數,通過也初始化了一些變量。
fHTTPServerSocket
fHTTPServerPort
fClientConnectionsForHTTPTunneling
fTCPStreamingDatabase
fPendingRegisterOrDeregisterRequests
fRegisterOrDeregisterRequestCounter
fAuthDB:訪問權限變量
fAllowStreamingRTPOverTCP

6.3.5 GenericMediaServer::GenericMediaServer

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)),
    fPreviousClientSessionId(0)
{
  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);
}

int fServerSocket
Port fServerPort
unsigned fReclamationSeconds
HashTable* fServerMediaSessions
HashTable* fClientConnections
HashTable* fClientSessions
u_int32_t fPreviousClientSessionId

env.taskScheduler().turnOnBackgroundReadHandling(fServerSocket, incomingConnectionHandler, this);

要監聽的 Server socket 及該 socket 上的 I/O 事件處理程序,註冊給任務調度器。事件循環中檢測到 socket 上出現 I/O 事件時,該處理程序會被調用到。註冊的事件處理程序爲 GenericMediaServer::incomingConnectionHandler() 。
(會把用到底層這些數據結構的都畫在一張圖中,這樣比較簡單明瞭)
在這裏插入圖片描述

6.3.6 ignoreSigPipeOnSocket

這是一個單獨的函數,網絡有關的,可以看一下其他博客

void ignoreSigPipeOnSocket(int socketNum) {
  #ifdef USE_SIGNALS
  #ifdef SO_NOSIGPIPE
  int set_option = 1;
  setsockopt(socketNum, SOL_SOCKET, SO_NOSIGPIPE, &set_option, sizeof set_option);
  #else
  signal(SIGPIPE, SIG_IGN);
  #endif
  #endif
}

已發現一個問題,對一個對端已經關閉的socket調用兩次write, 第二次將會生成SIGPIPE信號, 該信號默認結束進程.
https://blog.csdn.net/weixin_33841722/article/details/92183262
https://blog.csdn.net/xinguan1267/article/details/17357093

6.3.7 Medium::Medium

Medium::Medium(UsageEnvironment& env)
	: fEnviron(env), fNextTask(NULL) {
  // First generate a name for the new medium:
  MediaLookupTable::ourMedia(env)->generateNewName(fMediumName, mediumNameMaxLen);
  env.setResultMsg(fMediumName);

  // Then add it to our table:
  MediaLookupTable::ourMedia(env)->addNew(this, fMediumName);
}

Medium類中也保存了一個env的變量,不知道是做啥用的,先不管,往下走。
UsageEnvironment& fEnviron
char fMediumName[mediumNameMaxLen]: //存儲名字和編號的
TaskToken fNextTask

6.3.8 MediaLookupTable::ourMedia

接下來就分析一下Medium的構造函數。
先看第一個函數,

MediaLookupTable* MediaLookupTable::ourMedia(UsageEnvironment& env) {
  _Tables* ourTables = _Tables::getOurTables(env);
  if (ourTables->mediaTable == NULL) {
    // 創建一個新表來記錄要在此環境中創建的媒體:
    ourTables->mediaTable = new MediaLookupTable(env);   //這是構造函數
  }
  return ourTables->mediaTable;
}

這裏就有一箇中間類,_Table類,我現在也不知道這個類是幹嘛的,不過先分析,分析到後面,自然就知道了。

class _Tables {
public:
  static _Tables* getOurTables(UsageEnvironment& env, Boolean createIfNotPresent = True);
      // returns a pointer to a "_Tables" structure (creating it if necessary)
  void reclaimIfPossible();
      // used to delete ourselves when we're no longer used

  MediaLookupTable* mediaTable;
  void* socketTable;

protected:
  _Tables(UsageEnvironment& env);
  virtual ~_Tables();

private:
  UsageEnvironment& fEnv;
};

這個_Table類也是挺簡單的,就是有一個fEnv的變量,這個_Table也存儲了一個env變量。

下面我們來看看實際操作的函數:

_Tables* _Tables::getOurTables(UsageEnvironment& env, Boolean createIfNotPresent) {
  if (env.liveMediaPriv == NULL && createIfNotPresent) {
    env.liveMediaPriv = new _Tables(env);
  }
  return (_Tables*)(env.liveMediaPriv);
}

void _Tables::reclaimIfPossible() {
  if (mediaTable == NULL && socketTable == NULL) {
    fEnv.liveMediaPriv = NULL;
    delete this;
  }
}

_Tables::_Tables(UsageEnvironment& env)
  : mediaTable(NULL), socketTable(NULL), fEnv(env) {
}

_Tables::~_Tables() {
}

這些函數也挺簡單的,就要注意兩個變量:
env.liveMediaPriv:這個是env中的一個指針,當初不知道是咋回事,現在知道在這裏賦值了,所以要記住。
UsageEnvironment& fEnv:還有一個就是他自己的env,這個env也是上層一層一層傳下來的,變量是同一個。
MediaLookupTable mediaTable*:
void socketTable*:

6.3.9 MediaLookupTable::MediaLookupTable

不知道不覺中來了到構造函數。這個MediaLookupTable是負責管理Medium的。

MediaLookupTable::MediaLookupTable(UsageEnvironment& env)
  : fEnv(env), fTable(HashTable::create(STRING_HASH_KEYS)), fNameGenerator(0) {
}

UsageEnvironment& fEnv:還是env那個變量
HashTable fTable*: 創建了一個哈希表
unsigned fNameGenerator: liveMedium名字的編號

可以看看MediaLookupTable這個類定義:

// 通過字符串名稱查找Medium的數據結構.
// (它僅用於實現“ Medium”,但是如果開發人員希望使用它來迭代我們創建的整個“ Medium”對象集,則在此處將其可見.)
class MediaLookupTable {
public:
  static MediaLookupTable* ourMedia(UsageEnvironment& env);
  HashTable const& getTable() { return *fTable; }

protected:
  MediaLookupTable(UsageEnvironment& env);
  virtual ~MediaLookupTable();

private:
  friend class Medium;

  Medium* lookup(char const* name) const;
  // Returns NULL if none already exists

  void addNew(Medium* medium, char* mediumName);
  void remove(char const* name);

  void generateNewName(char* mediumName, unsigned maxLen);

private:
  UsageEnvironment& fEnv;
  HashTable* fTable;
  unsigned fNameGenerator;   // liveMedium名字的編號
};

6.3.10 MediaLookupTable::generateNewName

void MediaLookupTable::generateNewName(char* mediumName,
				       unsigned /*maxLen*/) {
  // 我們確實應該在這裏使用snprintf(),但並非所有系統都擁有它
  sprintf(mediumName, "liveMedia%d", fNameGenerator++);
}

這個函數比較簡單,就是用sprintf設置一個名字。

6.3.11 MediaLookupTable::addNew

然後我們來到最後一個函數

void MediaLookupTable::addNew(Medium* medium, char* mediumName) {
  fTable->Add(mediumName, (void*)medium);
}

這個函數就是把medium對象的指針,和medium名字存儲到哈希表中。
在需要用到的時候使用。
在這裏插入圖片描述
雖然把這個創建的過程追蹤下來了,但是這一層一層的繼承,關係太多了,變量也太多了,所以還不是太明白,不過先這樣分析,以後多分析分析,可能就會頓悟了,加油。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章