“ 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函數比較簡單,做的事情不多:
- 創建了延時調度任務的對象,BasicTaskScheduler。
- 創建基礎打印對象,BasicUsageEnvironment。
- 定義ACCESS_CONTROL宏,就需要訪問權限,創建訪問權限的對象UserAuthenticationDatabase。
- 創建RTSP對象,這個以前沒看過,現在要仔細分析分析,先嚐試端口554,然後再嘗試8554.
- 創建HTTP隧道端口,也是先嚐試80,然後8000,最後8080。
- 最後調用循環框架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名字存儲到哈希表中。
在需要用到的時候使用。
雖然把這個創建的過程追蹤下來了,但是這一層一層的繼承,關係太多了,變量也太多了,所以還不是太明白,不過先這樣分析,以後多分析分析,可能就會頓悟了,加油。