# 1.概要?
? ? 在Janus的眾多插件中,有一個房間插件videoRoom,由于它實現視頻會議sfu功能,通過改造能適合我們很多業務場景。而且在Janus眾多的業務插件中`VideoRoom`應該也是最復雜的一個,如果你們撐握了它,就輕而易舉了解其他插件。
? ? ?janus 提供了很多管理房間的api接口,包括房間的創建,修改,刪除,轉推等操作。學習這些api有助于了解janus房間管理,同時也可以學習房間管理需要提供哪些標準的api接口。
## 1.VideoRoom插件
`? ?VideoRoom`是Janus的一個插件,實現了一個SFU(Selective Forwarding Unit)型的音視頻會議。如果你從數據轉發的角度看,也可以把它認為是一個音視頻`路由器`。
`? ?VideoRoom`實現的音視頻會議是基于`發布/訂閱`模式。每個`參與方`都可以發布自己的實時音視頻流,因此它可以實現幾種不同的場景,比如泛娛樂化直播或多人的實時互動產品(如音視頻會議、在線教育小班課等)。m/n? ? ? ? ? 76
? ? ? ? ?主handle是發布handle。考慮到此插件允許一個`參與方`可以打開多個WebRTC?`PeerConnection`(如每個`參與方`可以有1個用于推流的`PeerConnection`和N個拉流的`PeerConnection`),所以每個`參與方`需要為訂閱不同的流`attach`到`VideoRoom`插件幾次(每`attach`一次就會生成一個`Handle`,每個`Handle`就是一個上下文)。因此,對于每個`參與方`至少要有一個`Handle`用于管理與插件的關系(如加入一個房間,離開一個房間,靜音/取消靜音,發布,接收事件)。
? ? ? ? 每當`參與方`需要訂閱另一個參與方發布的音視頻流時,它需要創建一個新的`Handle`。新創建的`Handle`在邏輯上屬于**“slave”**`Handle`,它不能像**“master”**`Handle`一樣可以做取消房間靜音這樣的操作。因此,**從**`Handle`唯一目的是提供一個上下文,在該上下文中創建一個`recvonly`類型的`PeerConnection`來訂閱發布者的音視頻流。
通過上面的描述我們可以知道,主Handle用于管理,而從Handle用于訂閱音視頻流。
> 注意,現在`WebRTC`已經實現了SSRC復用(Unified Plan),這意味著你可以使用相同的`Janus Handle`?和`PeerConnection`同時接收多路音視頻流。
## 2.房間參數? ?
? ? ?VideoRoom插件功能非常強大,也很靈活,它有很多的配置項,你可以通過`conf/janus.plugin.videoroom.jcfg`來修改它們。當然Janus也支持動態API修改配置,如通過API創建房間等。
### 2.1 創建房間
要增加更多房間或修改現有房間信息,你可以向Janus發送下面格式的請求:
~~~
room- <唯一的房間ID>:{ description = 房間的描述信息 is_private = true | false(是否是私有房間? 如果創建的是私有房間,則無法通過list指令進行查看) secret = <可選項,操作房間所需的密碼,如果設置了,則像做銷毀房間這樣的操作時你要帶上它才行> PIN = <可選項,加入會議房間的密碼> require_pvtid = true | false(是否訂閱音視頻流時,需要提供一個與發布者相關的有效private_id, 默認為false) publishers = <房間內發布者的最大數>(例如,一個視頻會議可以有6個發布者,而廣播只有一個,默認= 3) bitrate = <房間里發布者發送數據的最大比特率>(例如128000) fir_freq = <向發布者發送FIR指令的頻率>(0 =禁用) audiocodec = opus | g722 | pcmu | pcma | isac32 | isac16(發布者可以使用的音頻編解碼器列表,默認為opus。編碼器按優先順序以逗號分隔) videocodec = vp8 | vp9 | h264 | av1 | h265(發布者可以使用的視頻編解碼器列表,默認為vp8。可以按優先級順序用逗號分隔,例如,vp9,vp8,h264) vp9_profile = VP9首選的profile("2" 表示 "profile-id = 2" ) h264_profile = H.264首選的profile("42e01f" 表示 "profile-level-id = 42e01f" ) opus_fec = true | false(是否使用帶內FEC;僅適用于Opus,默認為false) video_svc = true | false(是否啟用SVC支持;僅適用于VP9,默認為false) audiolevel_ext = true | false(對于發布者是否使用RTP擴展ssrc-audio-level?默認為 true) audiolevel_event = true | false(是否將audiolevel事件發送給其他用戶) audio_active_packets = 100(音頻保活包個數,默認值= 100,2秒) audio_level_average = 25(音頻音量級別的平均值,127 =靜音,0 ='太大聲',默認= 25) videoorient_ext = true | false(發布者是否使用RTP擴展video-orientation? 默認= true) playoutdelay_ext = true | false(發布者是否使用RTP擴展playout-delay? 默認= true) transport_wide_cc_ext = true | false(發布者是否使用RTP擴展 transport-wide-cc? 默認= true) record = true | false(該房間是否啟錄制?默認= false) rec_dir = <啟用錄制后,錄制文件存放的目錄> lock_record = true | false(是否鎖定錄制狀態? 默認= false) notify_joining = true | false(可選,當有新的參與方加入房音后,是否通知房間里的所有參與者? Videoroom插件默認僅通知發布者,啟用此功能可能會導致額外的通知傳輸。 該功能與require_pvtid一起啟用時,對管理員管理僅收聽的參與者特別有用。默認= false) require_e2ee = true | false(是否啟用端到端加密? 默認= false)}
~~~
## 3\. Video Room API接口
`? VideoRoom`?插件支持很多API操作。這些API中,一些是同步請求,一些則是異步請求。但無論是同步還是異步請求,當遇到無效的JSON格式或無效的請求時,都使用同步進行錯誤響應。
? ? ?接下來,我們首先看看都有那些同步請求API。`create`,`destroy`,`edit`,`exists`,`list`,`allowed`,`kick`和`listparticipants`是同步請求API。`create`允許您動態創建一個新的音視頻房間;`edit`允許您動態編輯房間的屬性(例如 修改PIN碼);`destroy`首先釋放視頻資源,然后踢除房間里的所有用戶,最后銷毀音視頻房間;`exists`檢查指定的音視頻房間是否存在;`list`列出所有有效的音視頻房間;?`listparticipants`列出指定房間中所有激活的參與者及其詳細信息。
? ? ?異步請求API有:`join`,`joinandconfigure`,`configure`,`publish`,`unpublish`,`start`,`pause`,`switch`和`leave`。`join`允許你加入指定的音視頻房間;`configure`可用于修改某些屬性(例如,比特率范圍);`joinandconfigure`的含義是將前兩個請求合并為一個請求(該請求僅適用于發布者);`publish`發布媒體流給所有訂閱者;?`unpublish`正好與`publish`相反;`start`允許你開始接收訂閱的媒體流;`pause`暫停發送媒體流;`switch`更改指定`PeerConnection`的媒體源(例如,你正在看A,現在改為看B),但無需為此創建新的Handle;`leave`離開視頻房間。
下面咱們對上面提到的API做一下詳細分析,首先看一下`create`API,它用于創建新的音視頻房間,其格式如下:
~~~
{ "request":"create", "room":<可選,房間ID。如果不填,則由插件隨機生成>, "permanent":<true | false,是否創建永久房間,默認= false>, "description":"<可選,房間的名稱>", "secret":"<可選,編輯/銷毀房間時用的密碼>", "pin":"<可選,加入房間的密碼>", "is_private":<true | false,是否是私有房間?如果是私有房間則不會出現在房間列表中>, "allowed":[可選,用戶加入房間的token數組], ...}
~~~
上面的說明已經非常清楚了,這里我就不做簡贅述了。
如果`create`成功,則會返回`created`響應,格式如下:
~~~
{ "videoroom":“created", "room":<房間ID>, "permanent":<是否是創建的永久房間?是則為true,否則為false>}
~~~
> 注意,如果你請求創建一個永久房間,但`permanet`返回的是false,很可能是因為權限的問題導致的。
如果`create`請求失敗,則返回錯誤信息,格式如下:
~~~
{ "videoroom":"event", "error_code":<錯誤碼,每個錯誤碼的含義需要看插件實現代碼中的宏定義>, "error":"<錯誤描述字符串>"}
~~~
> 這里需要注意的是,所有請求的錯誤響應格式都與上面一樣。
默認情況下,所有用戶都可以創建房間,但你可以通過在`VideoRoom`插件的配置文件中增加`admin_key`項來限制此功能。此時,只有帶了正確的`admin_key`值的`create`請求才能成功創建房間。你也可以選擇將此功能擴展到RTP轉發,只轉發受信任的客戶端的RTP包。
房間創建好后,您可以用`edit`API編輯其中的部分(但不是全部)屬性。`edit`允許你修改房間描述,密碼,PIN碼以及是否為私有。但你將無法修改他的靜態屬性,例如房間ID,采樣率,與擴展相關的內容等。如果你有興趣更改ACL,還需要查看`allowed`是否允許。
一個`edit`請求格式如下:
~~~
{ "request":"edit", "room":<房間ID>, "secret":"<房間密碼>", "new_description":"<房間的新名稱,可選>", "new_secret":"<房間的新密碼,可選>", "new_pin":"<新PIN碼,可選>", "new_is_private":<true | false,房間是否為私有房間?>, "new_require_pvtid":<true | false,房間是否要求訂閱者提供private_id>, "new_bitrate":<比特率>, "new_fir_freq":<發送PLI請求關鍵幀的時間間隔>, "new_publishers":<房間里發布者的最大數>, "new_lock_record":<true | false,如否可以改變錄制狀態>, "permanent":<true | false,該房間是否是永久房間?默認= false>}
~~~
`edit`請求成功,剛收到`edited`響應:
~~~
{ "videoroom":"edited", "room":<房間ID>}
~~~
接下來我們來看看`destroy`API,無論你是通過動態創建的還是靜態創建的房間,均可使用`destroy`銷毀它,其格式如下:
~~~
{ "request":"destroy", "room":<房間ID>, "secret":"<房間密碼>", "permanent":<true | false,是否是永久房間,默認= false>}
~~~
成功銷毀房間后將收到`destroyed`響應,其格式如下:
~~~
{ "videoroom":"destroyed", "room":<房間ID>}
~~~
銷毀房間后,在房間內的所有參與者都會收到`destroyed`事件,如下所示:
~~~
{ "videoroom":"destroyed", "room":<房間ID>}
~~~
Janus中還提供了`exists`API,來檢查房間是否存在,該請求的格式如下:
~~~
{ "request":"exists", "room":<房間ID>}
~~~
請求成功將收到success響應:
~~~
{ "videoroom":“success", "room":<房間ID>, “exists":<true | false 房間是否存在>}
~~~
`allowed`API可以打開/關閉對令牌的檢測,它還可以增加/刪除允許的用戶,其請求格式如下:
~~~
{ "request":"allowed", "secret":"<房間密碼,如果已配置,則是必需的>", "action":"enable | disable | add | remove", "room":<房間ID>, "allowed":[ //字符串數組 ]}
~~~
成功請求將返回success響應:
~~~
{ "videoroom":“success", "room":<房間ID>, “allowed":[ //更新后完整的令牌列表 ]}
~~~
如果你是房間管理員(即你創建了該房間并可以加密訪問),則你可以使用`kick`API踢除房間內的用戶。
> 注意,這只會將用戶踢出房間,但并不能阻止他們重新加入。要禁止他們加入,你需要先從授權用戶列表中刪除他們(請參閱allowed請求),然后再將其踢掉。`kick`請求的格式如下:
~~~
{ "request":"kick", "secret":"<房間密碼>", "room":<房間ID>, "id":<被踢用戶ID>}
~~~
請求成功將收到success響應:
~~~
{ "videoroom":"success",}
~~~
你還可以通過`list`API獲取可用房間的列表(不包括配置或創建為私有的房間),其格式如下:
~~~
{ "request":“list"}
~~~
請求成功將返回success響應,響應中會帶有有效的房間列表:
~~~
{ "videoroom":“成功", "rooms":[//房間對象數組 {// 第一個房間 "room":<房間ID>, "description":"<房間名稱>", "pin_required":<true | false,是否需要輸入PIN嗎才能加入此房間>, "max_publishers":<房間內發布者最大數量,> "bitrate":<發布者使用的(通過REMB)比特率上限>, "bitrate_cap":<true | false,上述上限是否可以動態更改?>, "fir_freq":<發送PLI/FIR請求關鍵幀的時間間隔>, "audiocodec":"<音頻編解碼器列表,每個編碼器以逗號分隔>", "videocodec":"<視頻編解碼器列表,每個編碼器以逗號分隔>", "record":<true | false,是否打開了錄制功能>, "record_dir":"<如果開啟了錄掉,.mjr文件保存的路徑>", "lock_record":<true | false,是否只能通過密碼才能更改房間記錄狀態>, "num_participants":<房間內參與人的個數> }, //其他房間 ]}
~~~
當然,你要獲取特定房間中的參與者列表,可以使用`listparticipants`請求,其格式如下:
~~~
{ "request":"listparticipants", "room":<房間ID>}
~~~
請求成功將返回一個`participants`響應:
~~~
{ "videoroom":"participants", "room":<房間ID>, “participants":[//參與者對象的數組 {//參與者#1 "id":<用戶ID>, "display":"<用戶名;可選>", "publisher":"<true | false,用戶是否是房間的發布者>", "talking":<true | false,用戶是否可以說話(僅當使用音頻級別時)> }, //其他參與者 ]}
~~~
上面是Janus中的同步API。異步API都是與`參與者`有關,即`參與者`如何發布,訂閱或管理他們正在發送或接收的媒體流。
## 4\. VideoRoom 發布者
在VideoRoom中,`發布者`是指那些能夠在房間中發布`媒體流`的參與者。
當你以`發布者`的身份加入到房間里時,您應該發送`join`請求,并且將ptype設置為`publisher`。請求的具體格式如下:
~~~
{ "request":"join", "ptype":"pbulisher", "room":<房間ID>, "id":<發布者ID;可選,如果缺少,將由插件選擇>, "display":"<發布者名稱;可選>", "token":"<邀請令牌,如果房間有ACL時需要該字段;可選>"}
~~~
`joid`請求成功將收到`joined`事件,其中包含當前激活的`發布者`列表,以及任選的`參加者`列表。`joined`事件格式如下:
~~~
{ "videoroom":"joined", "room":<房間ID>, "description":<房間名,如果有的話>, "id":<用戶ID>, "private_id":<與參與者相關聯的不同唯一ID;打算是私人的>, “publishers":[ { "id":<活動發布者#1的唯一ID>, "display":"<發布者#1的名稱,如果有的話>", "audio_codec":"<發布者#1使用的音頻編解碼器,如果有的話>", "video_codec":"<發布者#1使用的視頻編解碼器,如果有的話>", "simulcast":"<如果發布者使用simulcast,則為true(僅VP8和H.264)>", "talking":<true | false,發布者開啟語音聊天(僅在使用音頻級別的情況下)>, }, //其他活躍的發布者 ], "attendees":[//僅當房間的notify_joining設置為TRUE時存在 { "id":<與會者#1的唯一ID>, "display":"<與會者#1的名稱,如果有的話>" }, //其他參加者 ]}
~~~
> 注意,如果房間中當前沒有人,則`發布者`列表為空。上面格式中的`private_id`屬性只有在用戶訂閱時才起作用。
對于房間里的訂閱者來說,會收到`event`通知。
~~~
{ "videoroom":"event", "room":<房間ID>, “joining":{ "id":<參與者ID>, "display":"<參與者名稱>" }}
~~~
如果你想成為`發布者`,則發送`publish`請求。該請求必須跟著一個`JSEP SDP Offer`,用于協商新的`PeerConnection`。插件會將其與房間配置進行匹配(例如,確保房間中使用協商的編解碼器),并使用`JSEP SDP answer`進行答復從而完成`PeerConnection`的設置。建立`PeerConnection`后,發布者立即處于活動狀態,其他參與者就可以訂閱它發布的流啦。
`publish`請求格式如下:
~~~
{ "request":"publish", "audio":<true | false,是否應該轉發音頻;默認為true>, "video":<true | false,是否應該轉發視頻;默認為true>, "data":<true | false,是否應該轉發數據;默認為true>, "audiocodec":"<在協商協議中首選的音頻編解碼器;可選>", "videocodec":"<在協商協議中首選的視頻編解碼器;可選>", "bitrate":<通過REMB返回的比特率上限;可選,如果存在則覆蓋全局房間值>, "record":<true | false,是否應該記錄此發布者;可選> "filename":"<錄制文件名;可選>", "display":"<用戶名稱;可選>", "audio_level_average":"<音頻音量平均值,此設置覆蓋房間的audio_level_average;可選>", "audio_active_packets":"<音頻保活包數,此設置覆蓋房間audio_active_packets;可選>}
~~~
此請求應該與發布者的`JSEP SDP Offer`一起提供,插件收到此消息后,將協商與之匹配的`JSEP SDP Answer`。如果成功,`configured`事件將被返回,其格式如下:
~~~
{ "videoroom":"event", "configured":“ok"}
~~~
該事件將與準備好的`JSEP SDP Answer`一起發送給客戶端。
你也可以用`configure`請求代替`publish`。兩者的功能在`發布`上是等效的,但從語義的角度來看,`publish`是發布時要發送的正確消息。`configure`請求也可以用于更新發布者會話的某些屬性,在這種情況下,就不能用`publish`請求了。
> 需要注意的是,如果用戶已經發送過`publish`了,再發送`publish`將導致失敗。
其實,您可以將`join`和`publish`兩個API合并為一個API請求。比如你一開始以`參與者`的身份加入,隨后變為`發布者`,這時你就可以將他們合并。你可以使用`joinandconfigure`請求來做到這一點,該請求將這兩個請求(join與publish)結合在一起。如果成功,則響應一個`joined`事件,并且將`JSEP SDP Answer`一起發送出去。
一旦`PeerConnection`設置成功,且發布者處于激活狀態,`event`就會被發向房間中的所有`參與者`。其格式如下所示:
~~~
{ "videoroom":"event", "room":<房間ID>, “publishes":[ { "id":<新發布者的唯一ID>, "display":"<新發布者的名稱,如果有的話>", "audio_codec":"<新布者使用的音頻編解碼器,如果有的話>", "video_codec":"<新發布使用的視頻編解碼器,如果有的話>", "simulcast":"<如果發布者使用simucast,則為true(僅VP8和H.264)>", "talking":<true | false,發布者是否在講話(僅在使用音頻級別的情況下)>, } ]}
~~~
要停止發布并刪除相關的`PeerConnection`,可以使用該`unpublish`請求:
~~~
{ "request":“unpublish"}
~~~
當插件收到這條請求后,它會刪除對應的`PeerConnection`,并將發布者從活動列表中刪除。如果成功,響應如下所示:
~~~
{ "videoroom":"event", “unpublish":“ok"}
~~~
當`PeerConnection`刪除后,插件還將向所有其他`參與者`通知該流不再可用的消息:
~~~
{ "videoroom":"event", "room":<房間ID>, "unpublished":<發布者的ID>}
~~~
> 注意,不光收到`unpublish`消息會觸發上面的事件通知,其實無論什么情況下,只要`發布者`提供的流消失了(例如,句柄已關閉或用戶失去連接),都會發同樣的`事件`。此外,你可以使用同一句柄的上下文多次執行`發布`或`取消發布`操作。
正如我們上面講過的,你可以使用`configure`請求調整發布者會話的某些屬性。該請求的格式如下:
~~~
{ "request":"configure", "audio":<true | false,取決于是否應該轉發音頻;默認為true>, "video":<true | false,取決于是否應該轉發視頻;默認為true>, "data":<true | false,取決于是否應該轉發數據;默認為true>, "bitrate":<比特率上限;可選,如果存在則覆蓋全局房間值(除非設置了bitrate_cap)>, "keyframe":<true | false,是否向發布者發送關鍵幀請求>, "record":<true | false,是否開啟錄制;可選> "filename":"<如果開啟了錄制,指明錄制路徑/文件;可選>", "display":"<用戶名稱;可選>", "audio_active_packets":"<音頻保活包個數,audio_active_packets;可選>", "audio_level_average":"<音頻音量平均值,audio_level_average;可選>",}
~~~
`configure`基本上與`publish`的屬性相同。這就是為什么兩個請求都可以用來開始發布的原因。如果`configure`成功,則返回`configured`事件,格式如下:
~~~
{ "videoroom":"event", "configured":“ok"}
~~~
當發送`configure`請求RTP擴展`ssrc-audio-level`時,如果`audiolevel_event`設置為true ,則可能會向所有發布者發送一些臨時事件。這些事件將具有以下格式:
~~~
{ "videoroom":<"talking"|"stopped-talking",是否發布者開始或停止發言>, "room":<房間的唯一ID>, "id":<發布者的唯一ID>, "audio-level-dBov-avg":<音平音量的平均值,127 =靜音,0 ='太大聲'>}
~~~
## 5\. VideoRoom 級聯
`? VideoRoom`插件的主要目的是從WebRTC源(發布者)獲取媒體,并將其轉發到WebRTC目的地(訂閱者),但實際上存在幾種方案,可以將媒體轉發給外部(不一定與WebRTC兼容)組件。例如,用于媒體處理,外部錄制,轉碼,級聯等等。`rtp_forward`顧名思義,就是將發布者發送的RTP包(普通或加密)實時轉發到遠程后端。
您可以使用`rtp_forward`請求為現有發布者添加新的RTP轉發器,其格式如下:
~~~
{ "request":"rtp_forward", "room":<房間ID>, "publisher_id":<發布者ID>, "host":"<將RTP和數據包轉發到的host主機IP地址>", "host_family":"<ipv4 | ipv6,使用IPv4還是IPv6;默認情況下,無論我們得到什么>", "audio_port":<音頻RTP數據包轉發到的端口>, "audio_ssrc":<音頻SSRC,用于流式傳輸;可選> "audio_pt":<音頻有效負載類型;可選> "audio_rtcp_port":<接收方接收音頻RTCP反饋端口;可選,當前未用于音頻>, "video_port":<將視頻RTP數據包轉發到的端口>, "video_ssrc":<視頻 SSRC;可選> "video_pt":<視頻有效載荷類型;可選> "video_rtcp_port":<接收方接收視頻RTCP反饋端口;可選> "video_port_2":<如果simulcast,則視頻第二個的RTP數據端口>, "video_ssrc_2":<如果simulcast,則視頻第二個的SSRC;可選> "video_pt_2":<如果simulcast,則視頻第二個的有效載荷類型;可選> "video_port_3":<如果simulcast,則視頻第三個RTP數據包端口>, "video_ssrc_3":<如果simulcast,則視頻第三個SSRC;可選> "video_pt_3":<如果simulcast,則視頻第三個的有效載荷類型;可選> "data_port":<數據通道消息端口>, "srtp_suite":<身份驗證標簽的長度(32或80);可選> "srtp_crypto":"<用作加密的密鑰(如SDES中的base64編碼的密鑰;可選>"}
~~~
> 注意,如上所述,如果您配置了admin\_key屬性,則在請求中也需要提供它,否則未授權的請求將被拒絕。默認情況下,沒有對rtp\_forward進行限制。
如果請求成功則返回`rtp_forward`響應,其中格式如下:
~~~
{ "videoroom":"rtp_forward", "room":<房間ID>, "publisher_id":<發布者ID> "rtp_stream":{ "host":"<接收流的主機IP,如果未解析,則與請求相同>", "audio":<音頻RTP端口,與請求相同(如果已配置)>, "audio_rtcp":<音頻RTCP端口,與請求相同(如果已配置)>, "audio_stream_id":<分配給音頻RTP轉發器的唯一數字ID,如果有的話,> "video":<視頻RTP端口,與請求相同(如果已配置)>, "video_rtcp":<視頻RTCP端口,如果配置,則與請求相同,> "video_stream_id":<分配給主視頻RTP轉發器的唯一數字ID,如果有的話,> "video_2":<第二個視頻端口,與請求相同(如果已配置)>, "video_stream_id_2":<分配給第二層視頻RTP轉發器的唯一數字ID,如果有的話,> "video_3":<第三個視頻端口,與請求相同(如果已配置)>, "video_stream_id_3":<分配給第三個視頻RTP轉發器的唯一數字ID,如果有,> "data":<數據端口,與請求相同(如果已配置)>, "data_stream_id":<分配給數據通道消息轉發器的唯一數字ID(如果有)> }}
~~~
要停止以前創建的RTP轉發器,可以使用`stop_rtp_forward`請求,其格式如下:
~~~
{ "request":"stop_rtp_forward", "room":<房間ID>, "publisher_id":<發布者ID>, "stream_id":<RTP轉發器ID>}
~~~
請求成功,則返回`stop_rtp_forward`響應:
~~~
{ "videoroom":"stop_rtp_forward", "room":<房間ID>, "publisher_id":<發布者ID,與請求相同,> "stream_id":<流ID,與請求相同>}
~~~
如果要獲取特定房間中所有轉發器的列表,可以使用`listforwarders`請求,其格式如下:
~~~
{ "request":"listforwarders", "room":<房間的唯一數字ID>, "secret":"<房間密碼;如果已配置,則是必需的>"}
~~~
請求成功,則返回forwarders響應,其中包括RTP轉發器列表:
~~~
{ "videoroom":"forwarders",
"room":<房間的唯一ID>,
"rtp_forwarders":[//具有RTP轉發器的發布者數組
{//發布者#1
"publisher_id":<發布者#1的唯一數字ID>,
"rtp_forwarders":[// RTP轉發器數組
{// RTP轉發器#1
"audio_stream_id":<音頻RTP轉發器的唯一ID,如果有的話>, "video_stream_id":<視頻RTP轉發器的唯一ID,如果有的話>,
"data_stream_id":<數據通道消息轉發器的唯一ID(如果有)>, "ip":"<接收端IP>",
"port":<接收端端口>,
"rtcp_port":<接收端RTCP端口,如果有的話>,
"ssrc":<轉發器正在使用的SSRC,如果有的話>,
"pt":<轉發器正在使用的有效負載類型>,
"substream":<視頻子流,如果有>,
"srtp":<true | false,RTP流是否已加密>
}, //此發布者的其他轉發器
],
},
//其他發布者
]
~~~
在會議進行期間啟用或禁用錄制,您可以使用`enable_recording`請求,該請求的格式如下:
~~~
{ "request":"enable_recording", "room":<房間ID>, "secret":"<房間密碼;如果已配置,則是必需的>" "record":<true | false,是否自動記錄此會議室中的參與者>,}
~~~
> 注意,參與者通常也可以通過configure請求來更改自己的錄制狀態:這樣做是為了獲得最大的靈活性,您可能希望單獨記錄一些流,而不是全局或自動記錄一些內容,到特定文件。就是說,如果你希望確保在啟用全局錄制后參與者不能停止其錄制,或者在不應該錄制該會議室的情況下啟動它,那么您應該確保在創建會議室時使用lock\_record屬性,將其設置為true。這樣,只有在提供了房間密碼的情況下,才能更改錄制狀態,從而確保只有管理員才能執行此操作。
最后,您可以使用`leave`請求離開會議室。如果您是會議室中的活動發布者,這也將隱式取消你的發布。該leave請求如下所示:
~~~
{ "request":"leave"}
~~~
如果成功,響應將如下所示:
~~~
{ "videoroom":"event", "leave":"ok"}
~~~
其他參與者將收到”leave”事件,格式如下:
~~~
{ "videoroom":"event", "room":<房間ID>, "leave:<離開的參與者的唯一ID>}
~~~
如果您是活躍的發布者,則其他用戶也將收到相應的`unpublish`事件,以通知他們該流不再可用。如果您只是潛伏而不是發布者,則其他參與者將僅收到”leave”事件。
## VideoRoom 訂閱者
訂閱者在加入房間時,join請求的ptype屬性應該設置為`subscriber`,并指定要訂閱的確切的媒體流。該請求的確切語法如下:
~~~
{
"request":"join", "ptype":"subscriber",
"room":<房間ID>, "feed":<發布者ID;強制性>,
"private_id":<發起此請求的用戶ID;可選的,除非房間配置要求>
"close_pc":<true | false,發布者離開時是否應自動關閉PeerConnection;默認為true>,
"audio":<true | false,是否轉發音頻;默認為true>,
"video":<true | false,是否轉發視頻;默認為true>,
"data":<true | false,是否轉發數據;默認為true>,
"offer_audio":<true | false; 是否應該協商音頻;如果發布者的音頻>,默認為true
"offer_video":<true | false; 是否應該協商視頻;如果發布者的視頻>,默認為true
"offer_data":<true | false; 是否應該協商數據通道;如果發布者的datachannels>為默認值,則為true "substream":<啟用了simulcast情況下,要接收的子流(0-2);可選>
"temporal":<啟用simulcast情況下,要接收的時間層(0-2);可選>
"fallback":<多少時間(在我們這里,默認為250000)沒有接收到數據包將使我們下降到下面的子流>, "spatial_layer":<啟用VP9-SVC時要接收的空間層(0-2);可選>
"temporal_layer":<啟用VP9-SVC時要接收的時間層(0-2);可選>
}
~~~
如您所見,只要指定好要訂閱的發布者ID,并在需要時指定好`private_id`(訂閱者ID),其它的都可以不設置。不過請求中的`offer_audio`,`offer_video`和`offer_data`特別有意思,你可以通過它們訂閱媒體的一個子集(音頻\\視頻\\數據)。
默認情況下,發送`join`請求時會導致插件層創建`SDP Offer`,用以協商發布者提供那些媒體。此外,如果發布者發布的是`simulcast`或`VP9 SVC`,那么你還可以訂閱你感興趣的子流,例如,獲得最佳質量的中間質量。更有意思的是,你可以使用`configure`請求隨時動態更改這些設置。
上面的請求如果成功,將生成一個新的`JSEP SDP Offer`,并伴隨一個attached事件:
~~~
{ "videoroom":"attached", "room":<房間ID>, "feed":<發布者ID>, "display":"<發布者的名稱,如果有的話>"}
~~~
在此階段,為了完成`PeerConnection`的設置,訂閱者應將`JSEP SDP Answer`發送回插件。此操作是通過`start`請求來完成的,在這種情況下,請求必須與`JSEP SDP Answer`相關聯,但是不需要任何參數:
~~~
{ "request":“start"}
~~~
如果成功,此請求將返回一個started事件:
~~~
{ "videoroom":"event", "started":"ok"}
~~~
完成此操作后,所需要做的就是等待WebRTC?`PeerConnection`建立成功。一旦`PeerConnection`建立成功,Streaming插件就可以開始向訂閱的觀眾轉發媒體了。
> 注意,在需要重新協商(例如出于ICE重啟目的)的情況下,您也可以使用我們剛經歷的相同步驟(watch請求,然后插件創建`JSEP Offer`,最后客戶端發送`start`請求和`JSEP Answer`)。
作為`訂閱者`,您可以發送`pause`臨時暫停或發送`start`恢復整個媒體的傳送(在這種情況下,不附帶任何JSEP SDP Answer)。因為上下文中已經有了相關信息,所以不需要重新進行協商。
~~~
{ "request":"pause"}
{ "request":"start"}
~~~
當然,它們會分別導致paused和started事件:
~~~
{ "videoroom":"event", "paused":"ok"}
{ "videoroom":"event", "started":"ok"}
~~~
`configure`請求可以對`訂閱`做更多深入操作。該請求允許`訂閱者`動態更改與媒體訂閱有關的某些屬性,`configure`請求的格式如下:
~~~
{ "request":"configure",
"audio":<true | false,是否應該轉發音頻;可選>
"video":<true | false,是否應該轉發視頻;可選>
"data":<true | false,是否轉發數據;可選>
"substream":<啟用simulcast情況下,要接收的子流(0-2);可選>
"temporal":<啟用simulcast,要接收的時間層(0-2);可選>
"fallback":<多少時間(在我們這里,默認為250000)沒有接收到數據包將使我們下降到下面的子流>,
"spatial_layer":<啟用VP9-SVC時要接收的空間層(0-2);可選>
"temporal_layer":<啟用VP9-SVC時要接收的時間層(0-2);可選>
"audio_level_average":"<如果提供,將覆蓋此用戶的房間audio_level_average;可選>", "audio_active_packets":"<如果提供,將覆蓋此用戶的房間audio_active_packets;可選>
}
~~~
正如你所看到的audio,video和data屬性可以用作媒體級的暫停/恢復功能,而pause與start只是簡單地暫停/恢復所有數據流。
下面來說說`switch`,switch 請求格式如下:
~~~
{
"request":"switch",
"feed":<要切換到的新發布者的唯一ID;強制性>,
"audio":<true | false,取決于是否應該中繼音頻;可選>
"video":<true | false,取決于是否應該中繼視頻;可選>
"data":<true | false,取決于是否應該中繼數據通道消息;可選>}
~~~
如果成功,您將退訂之前的發布者,然后訂閱新的發布者。確認切換成功的事件如下所示:
~~~
{
"videoroom":"event",
"switched":"ok",
"room":<房間ID>,
"id":<新發布者的唯一ID>
}
~~~
最后,要停止訂閱并刪除相關的PeerConnection,可以使用該leave請求。由于上下文是隱式的,因此不需要其他參數:
~~~
{
"request":"leave"
}
~~~
如果成功,該插件將嘗試拆除PeerConnection,并發送回一個left事件:
~~~
{
"videoroom":"event",
"left":"ok",
}
~~~
## 小結
`VideoRoom`插件是Janus的一個特別重要的插件,對于該插件的理解對于我們理解整個Janus有至關重要的意義。本文說細分析了`VideoRoom`插件中所有的信令,大體上我們可以將它們人成兩在類,一類是房間管理信令,另一類是用戶信令。
這些信令設計的非常巧妙,對我們研發自己的SFU會議系統是一個很好的借鑒。
- 序言
- 編解碼
- H264
- HEVC碼流解析
- H264編碼原理
- 多媒體封裝
- MP4
- 學好 MP4,讓直播更給力
- AAC
- FLV
- 流媒體協議
- RTSP
- RTCP
- RTP
- H265 RTP封包筆記
- SDP
- RTMP
- RTMP URL
- rtmp url基礎
- webrtc
- 編譯
- 最簡單的編譯webrtc方案
- Webrtc音視頻會議之Webrtc“不求甚解”
- Webrtc音視頻會議之Mesh/MCU/SFU三種架構
- 音頻傳輸之Jitter Buffer設計與實現
- Janus
- Webrtc音視頻會議之Janus編譯
- Webrtc音視頻會議之Janus源碼架構設計
- webrtc服務器-janus房間管理
- 源碼分析
- WebRTC視頻JitterBuffer詳解
- 走讀Webrtc 中的視頻JitterBuffer(一)
- 走讀webrtc 中的視頻JitterBuffer(二)
- webrtc視頻幀率控制算法機制
- 目標碼率丟幀-1
- 目標幀率丟幀-2
- 29 如何使用Medooze 實現多方視頻會議
- FFmpeg
- FFmpeg編譯
- Window10下編譯最新版FFmpeg的方法步驟
- FFMPEG靜態庫編譯
- ffmpeg實現畫中畫
- FFmpeg推流器
- ffmpeg-aac
- OpenCV
- OpenCV學習筆記——視頻的邊緣檢測
- 圖像特征點匹配(視頻質量診斷、畫面抖動檢測)
- 圖像質量診斷