一、 緒論
1.2 論文架構
瞭解上述三個問題點之後,我們便依序在論文中針對這些問題點進行剖析,首先在 第二章介紹串流伺服器的連線建立與 RTSP 信令溝通,內容包含 Live555 資源的簡介、
BSD socket、RTSP 以及 SDP。第三章是封包包裝及傳送,內容包含 MPEG4 elementary stream 的解析以及 RTP 封包的包裝。第四章是串流伺服器的設計討論,內容主要是針對 串流伺服器實作上的一些關鍵點深入探討,比如說串流伺服器的多工、RTSP 信令的辨 識以及媒體檔案訊框的取得。第五章則是串流程式開發手扎,內容主要是對於 Live 的 研究心得。最後第六章是結論。
第二章 連線建立與 RTSP 信令溝通
在瞭解基本串流伺服器的組成後,我們接下來開始論述 Live555 串流伺服器是如何 克服上述三個問題以及它的實作方式,在開始之前,我們必須先對 Live 所提供的資源 做一個簡要的介紹。
2.1 Live555串流伺服器組成
Live555 是一套提供GPL開放原始碼(open source)多媒體串流的C++函數庫,它提供 的函數庫可作為影音串流的共通平台~此共通平台定義同樣的信令協定以建立兩端的連 線,定義同樣的封包傳輸格式以及同樣的編解碼方式。這個函數庫包含四個小函數庫,
說明如下:
#1『UsageEnvironment』:一些程式的初始化功能。
#2『Groupsock』:允許呼叫 BSD Socket API。
#3『Medium』:主要提供了 RTP/ RTSP/ Container format 等功能。
#4『BasicUsageEnvironment』:提供整合的初始化程式。
透過前三個Libraries的互動,我們便能夠建構出一個串流伺服器的基本功能 了(如圖2.1(a)所示)。此外,如果串流伺服器想提供影像即時擷取傳送功能的話(Live Capture),我們會在程式中外掛一個Encoder(如圖2.1(b)所示),目的是把影像擷取裝置
所擷取到的raw data再經編碼壓縮處理,藉此得到更低的網路資料傳輸量。
圖2.1(a) Live555 Streaming Library 圖2.1(b) Live555 Streaming Server 下圖 2.2 所示為一個串流伺服器與用戶端的示意圖,我們可以看到不僅是個人電 腦,在一些嵌入式系統中也可以結合串流伺服器成為一個很好的應用(比如說監控系 統)。因此,討論串流伺服器的核心組成便是一個很好的研究主題,唯有瞭解其核心組 成,我們才有能力再去結合更多的應用,比如在影像處理方面,我們可以結合人臉辨識 成為一套很好的防盜監控系統,或是水庫水位自動監測警報系統等等。
圖 2.2 Overview of Streaming Server
2.2 連線建立的標準程序
接下來我們開始由 Live555 的串流伺服器程式開始追蹤,藉以瞭解其核心組成為 何,程式碼請自行參考 testOnDemandRTSPServer.cpp,首先我們看到的程式碼如#1 以及
#2 所示,其功能敘述於程式碼下方。
#1.TaskScheduler* scheduler = BasicTaskScheduler::createNew();
#2.env = BasicUsageEnvironment::createNew(*scheduler);
#1- BasicTaskScheduler 建構子中最重要的功能便是完成串流伺服器的 Socket 多工配 置,此部分我們會在第四章中詳述之。
#2- BasicUsageEnvironment 建構子主要是執行 WSAStartup( )函數,因為只要是在微軟 作業系統下所執行的網路程式都必須呼叫該函數來完成初始化的動作。
而因為上述 BasicTaskScheduler 以及 BasicUsageEnvironment 類別(class)中尚實作了 很多函數我們無法一一列舉說明,故在此僅將#1 以及#2 所共同完成的功能列舉如下:
1. Scheduling deferred events(socket 資料的接收與傳送)。
2. For assigning handlers for asynchronous read events
(針對讀取到的事件指派 handler 去處理之)。
3. For outputting error/warning messages(程式錯誤/警告訊息的輸出)。
接下來執行的程式如下紅框內所示,該行程式即是 Live555 Streaming Server 開始建 立 socket 標準連線程序的起點,也是我們核心組成的第一個要件。
#3 RTSPServer* rtspServer = RTSPServer::createNew(*env, 8554, authDB);
#3 程式將會開始應用 BSD socket API,如下圖 2.3 所示為整個 RTSPServer.cpp 所 應用到的 TCP socket API(傳送 RTSP 命令);圖 2.4 則為 RTSPServer.cpp 所應用到的 UDP socket API(傳送 RTP 封包),而串流伺服器一開始會建立一個 TCP 模式 RTSP socket,等到用戶端 connect()連線成功後即進入 RTSP 信令的辨識以及執行,等到收到 用戶端發送 RTSP PLAY 信令後便以 UDP 模式建立一個 RTP socket 來完成封包的傳送,
關於 RTSP 的部分我們在後面會解說。
(註) TCP 跟 UDP 的 socket 建立過程差別在於 UDP socket 不需要使用 listen()以及 accept()。
圖 2.3 TCP socket API flowchart
圖 2.4 UDP socket API flowchart
下圖 2.5
為
Live555 串流伺服器使用的通訊協定堆疊圖,目前上述 socket API 均已 整合在作業系統內,因此我們只要透過 socket API 的 system call,OS Kernel 便會幫我 們完成 TCP/UDP/IP 通訊協定的設定,簡化網路程式的設計。(註.微軟作業系統為 #include<winsock.h>,一般的 C 則為#include<sys/socket.h>)
圖 2.5Basic Streaming Server Protocol Stack
瞭解了串流伺服器的通訊協定堆疊圖後,我們接下來針對『連線建立的標準程序』所需 使用的 socket API 函數以及流程作簡單的介紹。
2.2.1 Socket
要進行網路 I/O,程式中至少要有一個 process 來呼叫 socket( )函式,藉以指定所需 的通訊協定類型(EX:使用 IPv4 的 TCP、使用 IPv6 的 UDP 等),socket( )會傳回一個 整數型態的 socket file descriptor,稱為承接口描述子(以下簡稱 socketfd)。其意義類似開 啟檔案時所需要用到的檔案指標(file descriptor),socketfd 目的是用來索引(index)一個
『socket』的資料結構,如下圖 2.6 所示,而 server/client 要能彼此通連即代表它們 socket 的資料結構內容都必須相同。
圖 2.6 Socket Data Structure 圖 2.7 Socket Descriptor Table 此外,每一個行程在 OS kernel 中都會擁有一個 socket 描述子表格 (descriptor
table),而表格中的 socketfd 就是指向(index)socket()在系統中所建立的 socket 資料結
構,如上圖 2.7 所示。
而 TCP socket 與 UDP socket 建立的差別如下所示,重點在於 socket()中的第二個引 數,如果要建立 TCP socket 的話第二個引數要填入 SOCK_STREAM;要建立 UDP socket 的話的話第二個引數要填入 SOCK_DGRAM,以下摘錄 RTSPServer.cpp 中分別呼叫 setupStreamSocket()以及 setupDatagramSocket()函數的程式碼。
(範例以及函數說明)
SOCKET PASCAL FAR socket (int af , /*protocol suite*/
int type , /*protocol type */
int protocol ) ; /*protocol name*/
伺服器端的程式在呼叫 socket()完成一個 socket 的資料結構之後,必須為這個 socket 的資料結構『命名』,也就是設定三個參數,分別是連線協定、連結埠號碼(port number) 以及位址(IP address),而用戶端便是以輸入伺服器的阜號以及 IP 位址來發送 connect 的 訊息。
在 socket『命名』的這個步驟要注意的是 port number 的設置,RFC 2326 中規定要 透過 RTSP 建立『溝通協調』管道的話該 socket 的阜號(port number)預設為 554,這邊要 注意的是盡量避免和一些網路上 well known 的阜號重複,比如說最常用的 http 協定其阜 號是 80,我們便不應該把我們 RTSP 的阜號也設為 80。
(註)socket 函數所產生的 socket 預設值為 Blocking socket。
2.2.2 Bind
設定初始值後,必須呼叫 bind()函數來完成 socket 的命名工作,也就是把命名所設定的資料(
port number 以及 IP address)和 socketfd 繫結在一起,
示意圖如右圖紅框所示。
(範例以及函數說明)
int PASCAL FAR bind (SOCKET s, /*an unbound socket*/
struct sockaddr FAR *addr, /*local port and IP addr*/
int namelen); /*addr structure length*/
2.2.3 Listen
當應用程式一旦 listen ( )成功之後,此 socket 便維持在等待連接狀態,我們稱為
listening socket;一個 socket 在呼叫 listen()成為 listening socket 之後,核心便會配置兩個 佇列如下圖 2.8 所示。
Listening socket 所接收的 client 連線我們稱之為『未完成連線佇列』; 這些未完成 連線的佇列要直到完成 3WHS 後在 OS 核心內才會變成『已完成連線佇列』; 這些已完 成的連線佇列接下來要呼叫 accept()才會變成一個 connected socket,圖 2.8 虛線中的 socket 總數不得超過 listen()函數中,第二個整數型態的引數數目[15]。
圖 2.8 Socket Queue
(範例以及函數說明)
int PASCAL FAR listen (SOCKET s, /*a named unconnected socket*/
int backlog) ; /*pending connect queue length*/
2.2.4 Accept
當 Server 收到用戶端連線要求並完成 3WHS 後,則使用 accept()來開啟另一
個新的 socket 來和用戶端進行連線,注意新的 socket 稱之為 connected socket,程式中 我們會以 accept()所傳回的一個整數型態變數來代表之,我們稱之為 connected socketfd
(以後簡稱 connfd)。原來的 listening socket 則持續監聽有無新的 client 的連線要求,這就 是一個串流伺服器可以同時服務多個 client 的關鍵。
如下圖 2.9 所示,「listening socket」是負責接收 client 端透過 connect()函數所發給
server 建立連線的請求,在 server 結束執行或被中止之前,此 listening socket 皆會存在;
而核心會針對每個已完成 accept()的 client 連線建立一個 connected socket 作為 server 與 client 資料 I/O 的介面,而當 server 服務結束或是 client 主動要求斷線時,此 connected socket 便會被關閉。
圖 2.9(a) Listening/Connected Socket
(範例以及函數說明)
SOCKET PASCAL FAR accept(SOCKET s, /*a listening socket*/
struct sockaddr FAR *addr, /*name of incoming socket*/
int FAR *addrlen); /*length of sockaddr*/
如下圖 2.9(b)所示,經由 accept()所產生的 connected socket 便是我們以後多媒體串 流通訊的 entry point,通常一個連線會需要三個 socket,分別作為 RTSP 、RTP 以及 RTCP 通訊之用。而由下圖 2.9(c)所示,我們可以看到由 BSD socket API 所建立的基本伺服器 與我們的串流伺服器(圖 2.9(b))有著些微的差異。
圖 2.9(b) Streaming Server flowchart 圖 2.9(c) Basic Server flowchart
這中間的主要差異便是在於 RTSP 以及 RTP socket 的設置,RTSP 以及 RTP 的部分 分別在第二章後半段及第三章會予以說明,而串流伺服器的多工(Socket I/O Multiplex) 細節在四章會詳細討論之。
2.2.5 Connect
用戶端會呼叫 connect()函式與伺服器端的 socket 完成連接的動作,也就是進行 TCP 3-way handshaking 以得知彼此的阜號以及位址,如下圖 2.10 所示當 client 呼叫 connect() 後即開始了 ARP 以及 TCP 3-way handshaking 的動作,在建立一個 TCP 的
oriented-connection 後,我們即可以在此連線之上傳送重要的 RTSP 控制訊息(註:
140.113.13.87 為 client ; 140.113.13.97 為 server)。
圖 2.10 Capturing Streaming Packet
(範例以及函數說明)
int PASCAL FAR connect(SOCKET s, /*an unconnected socket*/
struct sockaddr FAR *addr, /*remote port and IP address*/
int namelen); /*address structure length*/
2.2.6 Recv/Recvfrom
Server 端與 Client 端間己建立了連線之後,Recv/Recvfrom 目的便是讀取網路傳到 socket 介面的封包資料,通常 UDP socket 利用 recvfrom()來接收 RTP 封包資料,並且可 同時得知來源的 address;而 TCP socket 則使用 recv()來接收 RTSP 信令封包即可,因為 TCP 已經是一個 connection-oriented 的連線了。
(範例以及函數說明)
int PASCAL FAR recv(SOCKET s, /*associated socket*/
char FAR *buf, /*buffer for incoming data*/
int len, /*length of buf*/
int flags); /*option flags*/
(範例以及函數說明)
int PASCAL FAR recvfrom(SOCKET s, /*associated socket*/
int len, /*length of buf*/
int flags, /*option flags*/
struct sockaddr FAR *from, /*remote socket name*/
int fromlen); /*length of sockaddr*/
2.2.7 Send/Sendto
同樣是在 Server 端與 Client 端間己建立了連線之後,我們便利用 Send/Sendto 來進 行資料的傳送,而 UDP socket 利用 Sendto()來傳送 RTP 封包資料;而 TCP socket 則使用 Send()來傳送 RTSP 信令封包。
(範例以及函數說明)
int PASCAL FAR send(SOCKET s, /*associated socket*/
const char FAR *buf, /*buffer with outgoing data*/
int len, /*bytes to send*/
int flags); /*option flags*/
(範例以及函數說明)
int PASCAL FAR sendto(SOCKET s, /*a valid socket*/
const char FAR *buf, /*buffer with outgoing data*/
int len, /*bytes to send*/
int flags, /*option flags*/
struct sockaddr FAR *to, /*remote socket name*/
int tolen); /*length of sockaddr*/