了解janus

目標(biāo):Janus is an open source, general purpose, WebRTC server designed and developed by Meetecho.
技術(shù)棧:C,jcfg(libconfig)
代碼結(jié)構(gòu)

代碼結(jié)構(gòu)圖

描述:janus由Core實現(xiàn)了WebRTC所支持的所有協(xié)議(SDP, ICE, DTLS-SRTP, RTP/RTCP),并且管理插件的初始化,管理,關(guān)閉等。我們可以看到具體業(yè)務(wù)功能有Plugin提供,Core本身不能進(jìn)行任何具體業(yè)務(wù)實現(xiàn)。為了詳細(xì)了解這個系統(tǒng),我們可以帶著問題來了解:

  1. 系統(tǒng)的編譯依賴,及編譯先后順序是怎么樣的?
  2. 系統(tǒng)的Core的啟動,及Plugin啟動都做了那些事情?
  3. 簡單的單路通話(VideoCall)實現(xiàn)原理?
  4. 多路通話(VideoRoom) 實現(xiàn)原理?

1. 系統(tǒng)的編譯依賴,及編譯先后順序是怎么樣的?

我們來看右Doxygen生成的頭文件依賴


Doxygen頭文件依賴

我們看到依賴很簡單:

  1. plugins/plugin.h
  2. transports/transport.h
  3. loggers/logger.h
  4. events/eventhandler.h

所以在編譯時,Core是依賴到Plugins,Transports, Loggers,Events模塊的。

分析 Makefile.am 知道:
janus_SOURCES 包含了 plugins/plugin.c, transports/transport.c這兩個實現(xiàn)。所以Core只是把plugin.c, transport.c放到了這兩個子目錄中,實際他倆是Core的源文件。

然后看Plugin的編輯腳本,發(fā)現(xiàn)一個很神奇的事情,Plugin居然不依賴Core,不管是頭文件還是鏈接。這是不是跟接口編程一樣,框架提供接口,找一個現(xiàn)成的庫適配了這個接口就能在這個庫里面使用,現(xiàn)在厲害的在于,適配的時候都不依賴框架。

所以可以很明確的知道,他們的編譯可以分開,即先編譯Core,然后并行編譯Plugins,Transports,EventHandlers。

2. 系統(tǒng)的Core的啟動,及Plugin啟動都做了那些事情?

我們來分析main函數(shù):

  1. 首先在解析命令行參數(shù),和配置文件。
  2. 然后調(diào)用janus_log_init初始化日志模塊,這里面啟用了一個日志打印現(xiàn)成,并維護(hù)了一個log_buffer鏈表,及l(fā)og_buffer pool。
  3. 加載外部logger的so,并且初始化,最后調(diào)用janus_log_set_loggers關(guān)聯(lián)到j(luò)anus_log中去管理。
  4. 調(diào)用janus_auth_init初始化授權(quán)系統(tǒng)。
  5. 調(diào)用janus_recorder_init初始化路由系統(tǒng)。
  6. 調(diào)用janus_ice_init初始化ICE協(xié)議棧。
  7. 調(diào)用SSL_library_init, SSL_load_error_strings, OpenSSL_add_all_algorithms初始化OpenSSL環(huán)境。janus_dtls_srtp_init初始化DTLS功能。
  8. 調(diào)用janus_sctp_init初始化SCTP功能,多播通道。
  9. 初始化Core的全局變量。sessions 系統(tǒng)會話hash表,及sessions_watchdog_context,并啟動session watchdog線程。requests系統(tǒng)請求異步隊列,并啟動請求分配線程。tasks系統(tǒng)任務(wù)線程池,用來分配請求。
  10. 加載EventHandlers的so,并初始化。 調(diào)用janus_events_init關(guān)聯(lián)到管理中。
  11. 加載外部Plugins的so,并初始化。
  12. 加載外部Transports的so,并初始化。
  13. 給系統(tǒng)發(fā)送第一個Event,janus started。
  14. 啟動主線程循環(huán) g_main_loop_run

3. 簡單的單路通話(VideoCall)實現(xiàn)原理?

首先我們拋開系統(tǒng)來說,一個通話是由Alice(終端A)發(fā)起,然后服務(wù)器路由到Bob(終端B),這其中附帶SDP(Session Description Protocol)。我們先說最簡單的方式

        Server
     /          \
Alice  <--rtp--> Bob

其中SDP攜帶了Alice RTP/RTCP 的端口及IP,然后Bob回給服務(wù)器信令也攜帶了自己的SDP,同樣服務(wù)器把這個信令路由到Alice。Alice收到后,對著Bob給的端口RTP/RTCP通信。這樣就建立起來了Session,相互的數(shù)據(jù)互通了。

現(xiàn)在我們開始研究Janus怎么把這個過程實現(xiàn)的。

我們先查看Websocket Transport + VideoCall Plugin。

第一步,Alice和Bob怎么和Janus建立信令通道呢?
1. Websocket Transport 用 libwebsockets 建立 ws/wss 服務(wù)器。
2. Alice和Bob建立webscoket連接,libwebsockets會分配一個ws_client,并回調(diào)WST(Websocket Transport)。WST在ws_client上建立messages隊列和用janus_transport_session_create建立Core Session。最后給Core發(fā)送一個connected消息,這個消息啥也沒干。
3. Alice建立連接后,會立馬發(fā)送一個create請求,WST收到請求后,把請求發(fā)送到Core。Core會立即創(chuàng)建一個Core Session,同時發(fā)送一個 Session 類型的created 消息。然后回復(fù)Alice建立連接成功。
4. 然后,Alice發(fā)送attach請求。Core會立即創(chuàng)建一個ICE Handle,準(zhǔn)備處理RTP會話,同時發(fā)送一個Handle類型的attach消息。然后回復(fù)成功。這一步很關(guān)鍵,create只是建立Core Session,并沒有做任何路由關(guān)聯(lián)。而attach則告訴Core,我要用的Plugin。這樣就在ICE Handle中建立了Plugin指針,并調(diào)用Plugin創(chuàng)建一個Plugin Session, 方便后續(xù)的message直接給Plugin。
5. 至此連接就建立了。當(dāng)然Janus還有一步,注冊用戶名,方便呼叫。Alice發(fā)送一個message請求,并且?guī)?code>body:{request:'register", username:"Alice"}。Core根據(jù)attach的信息,把消息給 VideoCall Plugin,VideoCall則建立一個用戶名對應(yīng)Session的hash表。

第二步,Alice和Bob怎么建立呼叫會話呢?(注意這里的會話與代碼中的會話不是一個意思)
1. Alice發(fā)送一個{janus:"message",body:{request:"call",username:"Bob"},jseq:{sdp:"xxx",type:"offer"}}的消息。這個是message消息,Core會直接給Plugin處理。VideoCall Plugin先回復(fù)Boback,再根據(jù)username找到Bob的Session,然后驗證sdp,給兩個Session做上通話標(biāo)記與關(guān)聯(lián)。然后給Bob發(fā)送一個叫incomingcallevent,帶上Alice發(fā)送過來的SDP。
2. Bob回一個{janus:"message",body:{request:"accept"},jseq:{sdp:"xxx",type:"answer"}}的消息。VideoCall Plugin先回復(fù)Boback,再解析sdp,提取相關(guān)信息。然后給Alice發(fā)送一個叫acceptevent,帶上Bob發(fā)送過來的SDP。
3. 這樣就建立一個通話。

第三步,Alice或者Bob如何掛機(jī)呢?
1. Bob發(fā)送一個{janus:"message",body:{request:"hangup"}}的消息。VideoCall Plugin收到消息后,先回復(fù)Bob ack,然后清除現(xiàn)場,然后給Alice發(fā)送發(fā)送一個hangupevent。
2. 這樣電話就掛斷了。

上面的都是信令層面如何交互,涉及RTP/RTCP的只有SDP,那么SDP如何在ICE框架下,把RTP會話建立起來的呢?

4. 多路通話(VideoRoom) 實現(xiàn)原理?

VideoRoom Plugin實現(xiàn)了SFU(Selective Forwarding Unit)功能。它實現(xiàn)了會議功能,基于發(fā)布/訂閱模式,任何一個參會者都可以推送自己的流,并訂閱別人的流。

我們先看看信令交互:
第一步,Alice和Bob怎么和Janus建立信令通道呢?
1. 同單路通話一樣,建立起來用戶通道。只是最后一步注冊用戶名不一樣了。它把這一步直接整合進(jìn)后面的join請求中去了,因為會議不需要像單路通話一樣去找對方,而是給一個會議id,用其它方式告知對方后,讓他加入進(jìn)來。
第二步,如何創(chuàng)建會議,并且加入會議呢?
1. 調(diào)式發(fā)現(xiàn),VideoRoom的示例并沒有實現(xiàn)創(chuàng)建會議的功能,而是直接加入了一個叫1234的默認(rèn)會議。
2. Alice發(fā)送{janus:"message",body:{request:"join",room:1234,ptype:"publisher",display:"Alice"}}的消息,VideoRoom Plugin收到消息后,會建立一個Publisher,已用戶id作為key,并且與Core Session關(guān)聯(lián)起來。這樣就Alice就加入了會議
3. 設(shè)置自己發(fā)布的推流,發(fā)送{janus:"message",jseq:{sdp:"xxx",type:"offer"},body:{request:"configure",audio:true,video:true}}的消息,VideoRoom Plugin根據(jù)參數(shù)設(shè)置一些參數(shù),然后通知其它參會者。
4. 退出會議。Alice發(fā)送一個{janus:"destory"}的請求。Core拿到消息后,首先調(diào)用WST結(jié)束連接。然后清理Core Session,然后清理ICE Handle。這里有個問題,Plugin Session 并沒有清理,在哪里,什么時機(jī)做的清理呢?這里用的是 ICE 框架的事件做的處理。

至此,我們了解了視頻通話,主要信令的交互過程,如何創(chuàng)建連接,如何建立會話,如何銷毀等等。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容