字體:小 中 大 | |
|
|
2014/01/23 14:03:50瀏覽31133|回應2|推薦9 | |
前言 公司最近指派了ㄧ個 RTSP 伺服器的項目到我的頭上,由於期限給得很短,又要立軍令狀,還要跨平台!?所以這一陣子壓力很大,吃足了苦頭,幾乎每天都失眠。因此為了日後方便緬懷這段艱苦的歲月,特撰此文以紀念之。 夢裡尋它千百度 嚴格來說如果從頭來寫 RTSP 伺服器並不是一、兩支小貓可以辦的到的。因為光單是這個項目,人家就可以單獨開公司賺錢了,比方說 Helix 就是一個例子。所以以我這個光桿司令的作法,就只能找開放源碼(Open Source)來改了,但網路上支援 RTSP 的源碼有很多,像LIVE555、RED5、HELIX、DARWIN、FFMPEG、VLC 甚至一些不明來源的源碼都可以用。只不過框架清楚適合改成公司所需要的樣子卻不多。比方說 VLC、FFMPEG 搞成單一媒體串流還行,但是短時間要改成守護進程(Daemon)的模式,讓眾多客戶端連線,那還不如殺了我先!另外,HELIX、DARWIN 是要錢的,RED5 好像有點肥!其它不明來源的源碼不是 VC 版本沒法跨平台,就是只支援一、兩種編解碼器(Codec),似乎就剩下 LIVE555 可以用了! 只是尋找適合的開放源碼需要時間,首先需要下載源碼作本機編譯(Native Compile)先,接著再作交叉編譯(Cross Compile),然後放到目標機器(Target Machine)上跑跑,看看是不是我們所需要的?接著再大致流覽一下源碼,看看容易不容易閱讀,最後才能決定採用哪一種方案!?再運氣好的情況下,一套流程跑下來,總得花個兩、三天的時間。運氣不好的,比方說找的源碼用到三方工具鍊(ToolChain)或者公用函式庫(Utility),那還得先編譯這些雜七雜八的東西,才可以開始編譯源碼。命苦一點的,有時還得修改那些 gcc 已經不支援的語法,或生成文件(Makefile)。命更衰一點的,使用的公用函式庫與目標機器上的版本不合?這時候還得想辦法作靜態編譯(Static Compile)或者在目標機器上另作符號連結(Symbolic Link 或稱軟連結 (Soft Link)),總之不搞掉半條命還真沒辦法進行測試。這時候所需要的時間就更多了。 兩個禮拜,老闆就給兩個禮拜進行評估,中間還不時穿插一些騷擾與嘲諷。所以只能趕鴨子上架選 LIVE555 了,原因是它的範例還挺完整的。只是,伺服器原本就用了很多的執行緒,又加上它用了大量的繼承、多型與函數指標,讓我在追蹤源碼的時候,經常莫名其妙斷了頭緒。比方說 MediaSource 是負責資料來源的存取類別,而 MediaSink 則是資料發送的類別。但是多層類別繼承下,很多時候你不知道父代類別的執行程序到底是屬於哪一個子代類別?這讓想要了解程式控制流程的人,一個頭兩個大。雖然官網文件中提供了實體關係模型(ER-MODEL, Entity-Relationship Model),但是對於剛入門,尚未了解控制流程的人來說,看了也是白搭。 所以要完成一個具有公司特色的 RTSP 伺服器,必須先了解 LIVE555 的架構,而欲了解 LIVE555 的架構,則必須先了解 RTSP 整個的控制流程,如此方能有效的解決問題。如果單純從官方文件看起,曠日廢時不說,也容易迷失方向。 何謂即時串流協定(RTSP, Real Time Streaming Protocol) ? 這是一個用來控制聲音或影像的多媒體串流協議,並允許同時多個串流需求控制,它的語法和運作跟 HTTP (Hypertext Transfer Protocol) 1.1 類似,使用純文本來發送信息,但是傳輸媒體資料時則另外使用實時傳輸協議 (RTP, Real-time Transport Protocol) 作為媒體資料的傳送,而使用實時傳輸控制協議 (RTCP, Real Time Control Protocol) 作為 RTP 媒體流提供信道外(out-of-band)控制。 RFC (Request For Comment) 組織定義了 RTP 協定,其原始編號為 RFC 1889,現在則由 RFC 3550 取代之。RTP 基本上使用用戶數據報協議(UDP, User Datagram Protocol)來傳送音頻與視頻,當然也可以指定使用傳輸控制協定(TCP, Transmission Control Protocol),只不過 RTP 使用的連線埠號 (Port) 必須為偶數號碼,而 RTCP 的連線埠號必須為該偶數號碼的下一個數字。需要注意的是 UDP 本身並不提供服務品質或傳輸可靠性的保證,所以 RTP 只能利用時間戳記 (Timestamp)、序號 (Sequence Number),用來達成同步等能力,以及決定封包是否遺失等資訊。 值得注意的是 HTTP 協議是沒有狀態的,在發送一個命令後,連接會斷開,而且命令之間沒有依賴性。而 RTSP 的命令需要知道現在正處於一個什麼狀態,也就是說 RTSP 的命令總是按照順序來發送,某個命令總在另外一個命令之前要發送。RTSP 不管處於什麼狀態都不會去斷掉連接。同時 RTSP 在成功建立連線之後會根據階段作業描述協定 (SDP, Session Description Protocol (RFC 4566))產生數對連線 (RTP vs RTCP),例如影像、聲音、字幕等。 HTTP 協議預設使用 80 埠號,而 RTSP 預設使用 554 埠號。如果一些服務器因為某些安全的原因而封掉了這個埠號,那代理和防火牆可能不讓 RTSP 消息通過,需要管理員去放開 554 埠號,而使得 RTSP 協議能通過。不過 LIVE555 預設並偵測使用 80, 8000, 8080 三個埠號。 RTSP 交握程序 在 LIVE555 中關於 RTSP 交握程序,是交由 RTSPServer 這個類別來處理。根據 Socket 每次的連線產生 RTSPClientConnection 類別的實例(Instance),然後 RTSPClientConnection 根據解析(Parse)監聽內容呼叫對應 RTSP 命令的處理函數。例如客戶端首次連線必先傳送 OPTIONS 這個命令,如下所示這是在詢問伺服器端能夠支援哪些 RTSP 命令。 OPTIONS rtsp://10.128.20.180:8080/uvf_channel=1 RTSP/1.0 上面 OPTIONS 是 RTSP 命令,後面緊接著 URL 字串。CSeq 則代表命令序號。 RTSP/1.0 200 OK 上面是伺服器回應,它告訴客戶端,支援的命令有 OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER 等八種。RTPS/1.0 代表使用 RTSP 的版本號,接著是狀態碼 (200 表示正常),然後是狀態的描述。 DESCRIBE rtsp://10.128.20.180:8080/uvf_channel=1 RTSP/1.0 客戶端發送媒體描述的請求命令。此時 LIVE555 會嘗試存取媒體,以便在當中取得 SDP 訊息。然後再發送給客戶端。 RTSP/1.0 200 OK 如上,伺服器端回傳 SDP 格式的媒體描述文。此時 LIVE555 會斷開與媒體之間的存取。 SETUP rtsp://10.128.20.180/uvf_channel=1/track1 RTSP/1.0
CSeq: 4
User-Agent: LibVLC/2.0.8 (LIVE555 Streaming Media v2011.12.23)
Transport: RTP/AVP;unicast;client_port=43202-43203
客戶端要求進行視訊連線。
RTSP/1.0 200 OK
CSeq: 4
Date: Thu, Jan 23 2014 02:53:55 GMT
Transport: RTP/AVP;unicast;destination=10.128.20.119;source=10.128.20.180;client_port=43202-43203;server_port=6970-6971
Session: 71DBAC09 // 會話代碼 32 位元的亂數值。
此時 LIVE555 的 RTSPClientConnection 類別的實例會建立一個 RTSPClientSession 類別的實例,然後呼叫對應 SETUP 命令的處理函數,建立 ServerMediaSession 類別的實例。然後根據客戶端的請求的 URL 後輟建立相對應的 MediaSource 與 MediaSink。
LIVE555 中對於 MediaSource 與 MediaSink 基本上是一對一的關係。但也可以是一對多的關係,視需求可以繼承變更 RTSPServer 這個類別中 createNewSMS 的設計。
SETUP rtsp://10.128.20.180/uvf_channel=1/track2 RTSP/1.0
CSeq: 5
User-Agent: LibVLC/2.0.8 (LIVE555 Streaming Media v2011.12.23)
Transport: RTP/AVP;unicast;client_port=34286-34287
Session: 71DBAC09
客戶端要求進行音訊連線。在這個範例中 SETUP 命令會進行兩次。
RTSP/1.0 200 OK
CSeq: 5
Date: Thu, Jan 23 2014 02:53:55 GMT
Transport: RTP/AVP;unicast;destination=10.128.20.119;source=10.128.20.180;client_port=34286-34287;server_port=6972-6973
Session: 71DBAC09
建立音訊媒體的存取。
PLAY rtsp://10.128.20.180/uvf_channel=1/ RTSP/1.0
CSeq: 6
User-Agent: LibVLC/2.0.8 (LIVE555 Streaming Media v2011.12.23)
Session: 71DBAC09
Range: npt=0.000-
_port=34286-34287
Session: 71DBAC09
客戶端要求傳送媒體資料。
RTSP/1.0 200 OK
CSeq: 6
Date: Thu, Jan 23 2014 02:53:55 GMT
Range: npt=0.000-
Session: 71DBAC09
RTP-Info: url=rtsp://10.128.20.180/uvf_channel=1/track1;seq=28483;rtptime=2077980000,url=rtsp://10.128.20.180/uvf_channel=1/track2;seq=21997;rtptime=2992102192
伺服器應答要求傳送媒體資料。
GET_PARAMETER rtsp://10.128.20.180/uvf_channel=1/ RTSP/1.0
CSeq: 7
User-Agent: LibVLC/2.0.8 (LIVE555 Streaming Media v2011.12.23)
Session: 71DBAC09
0.000-
客戶端要求傳送其他參數資料。
RTSP/1.0 200 OK
CSeq: 7
Date: Thu, Jan 23 2014 02:53:55 GMT
Session: 71DBAC09
Content-Length: 10
2013.11.29
伺服器應答要求傳送參數資料。
TEARDOWN rtsp://10.128.20.180/uvf_channel=1/ RTSP/1.0
CSeq: 8
User-Agent: LibVLC/2.0.8 (LIVE555 Streaming Media v2011.12.23)
Session: 71DBAC09
客戶端要求停止傳送。
RTSP/1.0 200 OK
CSeq: 8
Date: Thu, Jan 23 2014 02:53:59 GMT
LIVE555 此時斷開會話連線,關閉媒體的存取。
LIVE555 的 Task
一般來說伺服器都是多工的方式呈現,不是用 fork 來複製進程,就是用 pthread 來產生執行緒。但在 LIVE555 中出現另外一種模式 Task ,這是用 select 指令所做出來,所以 LIVE555 在 Makefile 裡面不用引入 libpthread.so 這個函式庫,這是蠻特別的地方。或許這是因為要跨平台的考量。但每一個需要背景執行的程序都要開一個檔案或 Socket 指標還蠻討厭的。
LIVE555 的編譯
LIVE555 不是用傳統的 configure 稿本來產生 Makefile 的,它是用 genMakefile 這個搞本檔案然後參考各目錄下的 Makefile.head 與 Makefile.tail 以及跟目錄的 config.xxx 來產生 Makefile 的,所以跟目錄下下
./genMakefile xxx 就可以產生不同平台的 Makefile。其中 xxx 就是 config.xxx 的 xxx 。
時間戳 (Timestamp)
時間戳的觀念在 RTSP 等串流伺服器中佔著很重要的部分,如果處理的不好,很容易在客戶端裡看不見任何東西。
所謂的時間戳就是上一個幀(Frame)與下一個幀的間隔時間,當網路順暢且伺服器尚有餘力的時候,一般播放不會成為問題,但是一旦某方面出現問題時,往往當幀送到客戶端的時候已經發生逾時。此時像 VLC 採取的策略是直接丟棄這個幀,甚至直接送出 TEARDOWN 的命令。所以在資料進出同步的處理上需要比較小心,具體可以參考 live555ProxyServer 這個類別。
結語
LIVE555 是個很優秀的開放源碼,不但範例多,思路也很清楚。不過畢竟是開放源碼,很多東西還是有所不足。比方說編解碼器就少了一點,一些常見的多媒體格式也不支援。抽象類別既多且深,使得想要新增一個新的編解碼器變的很不好入手!?當然這麼說是很過分的!畢竟原作者也沒收你的錢,憑什麼要考慮的面面俱到!?只是老闆們似乎都不這麼想,只會死命的追著你的進度!
『不是改改就能用了!』這是常常從老闆那裏聽到的一句話?
靠!如果這句話是真的!這一陣子我就不用活的這麼累了!
參考連結 RTP 附錄 標准 RTSP 消息的錯誤代碼 100: Continue (all 100 range)110: Connect Timeout 200: OK 201: Created 250: Low on Storage Space 300: Multiple Choices 301: Moved Permanently 302: Moved Temporarily 303: See Other 304: Not Modified 305: Use Proxy 350: Going Away 351: Load Balancing 400: Bad Request 401: Unauthorized 402: Payment Required 403: Forbidden 404: Not Found 405: Method Not Allowed 406: Not Acceptable 407: Proxy Authentication Required 408: Request Time-out 410: Gone 411: Length Required 412: Precondition Failed 413: Request Entity Too Large 414: Request-URI Too Large 415: Unsupported Media Type 451: Parameter Not Understood 452: reserved 453: Not Enough Bandwidth 454: Session Not Found 455: Method Not Valid in This State 456: Header Field Not Valid for Resource 457: Invalid Range 458: Parameter Is Read-Only 459: Aggregate operation not allowed 460: Only aggregate operation allowed 461: Unsupported transport 462: Destination unreachable 500: Internal Server Error 501: Not Implemented 502: Bad Gateway 503: Service Unavailable 504: Gateway Time-out 505: RTSP Version not supported 551: Option not supported SDP 協議介紹 SDP, Session Description Protocol,會話 描述協議,完全是一種會話描述格式 ─ 它不屬於傳輸協議 ─ 它只使用不同的適當的傳輸協議,包括 SIP(Session Initiation Protocol,會話初始協議)、SAP(Session Annou ncement Protocol,會話通告協議)實時流協議(RTSP)、MIME 擴展協議的電子郵件以及超文本傳輸協議 (HTTP)。SDP 協議是也是基於文本的協議, 這樣就能保證協議的可擴展性比較強,這樣就使其具有廣泛的應用范圍。SDP 不支持會話內容或媒體編碼的協商,所以在流媒體中只用來描述媒體信息。媒體協商這一塊要用 RTSP 來實現。SDP 是會話描述協議的縮寫,是描述流媒體初始化參數的格式,由 IETF 作為RFC 4566 頒布。流媒體是指在傳輸過程中看到或聽到的內容,SDP 包通常包括以下信息: SDP協議格式 SDP 描述由許多文本行組成,文本行的格式為<類型>=<值>,<類型>是一個字母,<值>是結構化的文本串,其格式依<類型>而定。 <type>= bwtype 可以是 CT 或 AS,CT 方式是設置整個會議的帶寬,AS 是設置單個會話的帶寬。缺省帶寬是千比特每秒。 t= |
|
( 知識學習|隨堂筆記 ) |