本文主要是介绍如何基于Janus和WebRTC源码打造自己的实时互动应用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
如何基于Janus和WebRTC源码打造自己的实时互动应用
- 简介
- Janus服务端的部署
- 部署Janus
- 源码下载和安装
- 测试部署环境
- 信令介绍
- 推流信令
- 拉流信令
- Janus信令特点
- 程序设计
- 推流流程:
- 拉流流程
- 总结
简介
本片文章主要介绍利用Janus服务器、现有WebRTC源码打造一款可以自定义的实时、互动方案,对学习直播、空中课堂等一些产品具有一定借鉴和参考意义。
Janus服务端的部署
Janus 是由Meetecho公司开发的一组提供音视频服务的网关,支持实时文本、音视频、录制、回放等功能的服务端程序。个人认为对于我们产品初期的预研究、开发和迭代都有极大的参考意义,有兴趣的朋友可以在Meetecho Demo查看他的例子。
部署Janus
源码下载和安装
下载地址
https://github.com/meetecho/janus-gateway.git
把代码clone到本地之后,按照github下面的步骤,是可以编译通过的
以下是我的电脑上编译的
Compiler: gcc
libsrtp version: 2.x
SSL/crypto library: OpenSSL
DTLS set-timeout: not available
Mutex implementation: GMutex (native futex on Linux)
DataChannels support: yes
Recordings post-processor: no
TURN REST API client: yes
Doxygen documentation: no
Transports:REST (HTTP/HTTPS): yesWebSockets: yesRabbitMQ: noMQTT: noUnix Sockets: yesNanomsg: no
Plugins:Echo Test: yesStreaming: yesVideo Call: yesSIP Gateway: yesNoSIP (RTP Bridge): yesAudio Bridge: yesVideo Room: yesVoice Mail: yesRecord&Play: yesText Room: yesLua Interpreter: noDuktape Interpreter: no
Event handlers:Sample event handler: yesWebSocket ev. handler: yesRabbitMQ event handler:noMQTT event handler: noNanomsg event handler: no
External loggers:JSON file logger: no
JavaScript modules: no
我们可以看到Janus支持了"Plugins"就是对应官网的几个demo,而"Transports"代表的是提供给客户端使用的的链接方式。"yes"代表是在本地电脑上的Janus服务即将提供这种功能,如果你是"no"的一般都是本地电脑上没有找到依赖项。找到对应的安装包手动安装就可以解决了。对我们今天的文章来说,我们只需要一下配置就可以了;
Transports:REST (HTTP/HTTPS): yesWebSockets: yes
Plugins:Video Room: yes
注意:
当make install 结束之后只是把可执行程序和依赖库copy到对应的地方,这时候还需要最后一步
make configs
这时候程序就可以正常运行起来了。
测试部署环境
由于Google的webrtc需要运行在https的环境下,所以搭建一个测试环境其实是一个很复杂的事情,设计到ssl证书,域名等一些列操作,这里不在赘述了,我们这里为了测试,只需要对文件js文件进行简单的修改。
- 进入到源码目录html
- 修改videoroomtest.js文件,添加一行
server = "http://" + "localhost" + ":8088/janus";
var server = null;
if(window.location.protocol === 'http:')server = "http://" + window.location.hostname + ":8088/janus";
elseserver = "https://" + "localhost" + ":8089/janus";
//强制修改链接方式为http的方式
server = "http://" + "localhost" + ":8088/janus";
- 用chrome打开videoroomtest.html
- 点击页面上的
Start
按钮,然后在Jone the room
的文本框里输入111
并点击之 - 再打开一个页面,重复以上操作,输入
333
,。
最终我们可以看到类似
至此,我们的Janus服务器部署大功告成。
## 打造属于自己的WebRTC应用
在打造App之前先对Janus的videoroom
插件做一个简单的介绍,
- 负责信令的收发,媒体的中转,peer 的维护;
- 媒体是单向的要么
sendonly
,或者recvonly
- 所有的客户端和Janus媒体数据交互实际上的通过Janus管理peer进行交互,每个peer的实现的又是使用libnice库,这样相当于是客户端和Janus或者Janus和客户端组成了一组p2p连接。
想开发属于自己的RTC应用,无论是移动端(Android, iOS), 这里需要做好两点.
- 采用什么样的交互,即客户端应用如何进行通信。
- peerconnection native api的使用
信令介绍
我们暂且把客户端发到Janus服务器的媒体服务器的流的行为叫推流
相反,我们把Janus服务器发送到客户端的数据叫做拉流
推流信令
最重要的两条信息,也是peerconnection协商的主要内容,俗称offer/answer
模型
- 客户端发出去的
offer
{"janus": "message","body": {"request": "configure","audio": true,"video": true},"transaction": "uhVmrA3m1zpF","jsep": {"type": "offer","sdp": "v=0\r\no=- 7188370618827213634 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0 1\r\na=msid-semantic: WMS lRp1CXMUAO275BhiToZVq6jbSE1dXSLV9hLh\r\nm=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=ice-ufrag:VUb2\r\na=ice-pwd:SlG64/+DM1GUEl6btHpcKkd7\r\na=ice-options:trickle\r\na=fingerprint:sha-256 9C:A6:A3:40:AA:CE:9C:6D:73:AD:53:59:B6:7A:C5:5A:02:0D:70:49:DA:AA:6D:AC:0A:26:39:E3:E3:CF:AE:48\r\na=setup:actpass\r\na=mid:0\r\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\na=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\na=sendonly\r\na=msid:lRp1CXMUAO275BhiToZVq6jbSE1dXSLV9hLh c740d785-f057-434e-a8cf-8f231f2f6696\r\na=rtcp-mux\r\na=rtpmap:111 opus/48000/2\r\na=rtcp-fb:111 transport-cc\r\na=fmtp:111 minptime=10;useinbandfec=1\r\na=rtpmap:103 ISAC/16000\r\na=rtpmap:104 ISAC/32000\r\na=rtpmap:9 G722/8000\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:106 CN/32000\r\na=rtpmap:105 CN/16000\r\na=rtpmap:13 CN/8000\r\na=rtpmap:110 telephone-event/48000\r\na=rtpmap:112 telephone-event/32000\r\na=rtpmap:113 telephone-event/16000\r\na=rtpmap:126 telephone-event/8000\r\na=ssrc:130909908 cname:Wn97EKOJFPlcc9wm\r\na=ssrc:130909908 msid:lRp1CXMUAO275BhiToZVq6jbSE1dXSLV9hLh c740d785-f057-434e-a8cf-8f231f2f6696\r\na=ssrc:130909908 mslabel:lRp1CXMUAO275BhiToZVq6jbSE1dXSLV9hLh\r\na=ssrc:130909908 label:c740d785-f057-434e-a8cf-8f231f2f6696\r\nm=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 122 127 121 125 107 108 109 124 120 123\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=ice-ufrag:VUb2\r\na=ice-pwd:SlG64/+DM1GUEl6btHpcKkd7\r\na=ice-options:trickle\r\na=fingerprint:sha-256 9C:A6:A3:40:AA:CE:9C:6D:73:AD:53:59:B6:7A:C5:5A:02:0D:70:49:DA:AA:6D:AC:0A:26:39:E3:E3:CF:AE:48\r\na=setup:actpass\r\na=mid:1\r\na=extmap:14 urn:ietf:params:rtp-hdrext:toffset\r\na=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:13 urn:3gpp:video-orientation\r\na=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\na=extmap:11 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type\r\na=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing\r\na=extmap:8 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07\r\na=extmap:9 http://www.webrtc.org/experiments/rtp-hdrext/color-space\r\na=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\na=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\na=sendonly\r\na=msid:lRp1CXMUAO275BhiToZVq6jbSE1dXSLV9hLh d6a76d64-297a-4b35-b813-30fe03fc6370\r\na=rtcp-mux\r\na=rtcp-rsize\r\na=rtpmap:96 VP8/90000\r\na=rtcp-fb:96 goog-remb\r\na=rtcp-fb:96 transport-cc\r\na=rtcp-fb:96 ccm fir\r\na=rtcp-fb:96 nack\r\na=rtcp-fb:96 nack pli\r\na=rtpmap:97 rtx/90000\r\na=fmtp:97 apt=96\r\na=rtpmap:98 VP9/90000\r\na=rtcp-fb:98 goog-remb\r\na=rtcp-fb:98 transport-cc\r\na=rtcp-fb:98 ccm fir\r\na=rtcp-fb:98 nack\r\na=rtcp-fb:98 nack pli\r\na=fmtp:98 profile-id=0\r\na=rtpmap:99 rtx/90000\r\na=fmtp:99 apt=98\r\na=rtpmap:100 VP9/90000\r\na=rtcp-fb:100 goog-remb\r\na=rtcp-fb:100 transport-cc\r\na=rtcp-fb:100 ccm fir\r\na=rtcp-fb:100 nack\r\na=rtcp-fb:100 nack pli\r\na=fmtp:100 profile-id=2\r\na=rtpmap:101 rtx/90000\r\na=fmtp:101 apt=100\r\na=rtpmap:102 H264/90000\r\na=rtcp-fb:102 goog-remb\r\na=rtcp-fb:102 transport-cc\r\na=rtcp-fb:102 ccm fir\r\na=rtcp-fb:102 nack\r\na=rtcp-fb:102 nack pli\r\na=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f\r\na=rtpmap:122 rtx/90000\r\na=fmtp:122 apt=102\r\na=rtpmap:127 H264/90000\r\na=rtcp-fb:127 goog-remb\r\na=rtcp-fb:127 transport-cc\r\na=rtcp-fb:127 ccm fir\r\na=rtcp-fb:127 nack\r\na=rtcp-fb:127 nack pli\r\na=fmtp:127 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f\r\na=rtpmap:121 rtx/90000\r\na=fmtp:121 apt=127\r\na=rtpmap:125 H264/90000\r\na=rtcp-fb:125 goog-remb\r\na=rtcp-fb:125 transport-cc\r\na=rtcp-fb:125 ccm fir\r\na=rtcp-fb:125 nack\r\na=rtcp-fb:125 nack pli\r\na=fmtp:125 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\r\na=rtpmap:107 rtx/90000\r\na=fmtp:107 apt=125\r\na=rtpmap:108 H264/90000\r\na=rtcp-fb:108 goog-remb\r\na=rtcp-fb:108 transport-cc\r\na=rtcp-fb:108 ccm fir\r\na=rtcp-fb:108 nack\r\na=rtcp-fb:108 nack pli\r\na=fmtp:108 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f\r\na=rtpmap:109 rtx/90000\r\na=fmtp:109 apt=108\r\na=rtpmap:124 red/90000\r\na=rtpmap:120 rtx/90000\r\na=fmtp:120 apt=124\r\na=rtpmap:123 ulpfec/90000\r\na=ssrc-group:FID 4008269645 4107779015\r\na=ssrc:4008269645 cname:Wn97EKOJFPlcc9wm\r\na=ssrc:4008269645 msid:lRp1CXMUAO275BhiToZVq6jbSE1dXSLV9hLh d6a76d64-297a-4b35-b813-30fe03fc6370\r\na=ssrc:4008269645 mslabel:lRp1CXMUAO275BhiToZVq6jbSE1dXSLV9hLh\r\na=ssrc:4008269645 label:d6a76d64-297a-4b35-b813-30fe03fc6370\r\na=ssrc:4107779015 cname:Wn97EKOJFPlcc9wm\r\na=ssrc:4107779015 msid:lRp1CXMUAO275BhiToZVq6jbSE1dXSLV9hLh d6a76d64-297a-4b35-b813-30fe03fc6370\r\na=ssrc:4107779015 mslabel:lRp1CXMUAO275BhiToZVq6jbSE1dXSLV9hLh\r\na=ssrc:4107779015 label:d6a76d64-297a-4b35-b813-30fe03fc6370\r\n"}
}
- Janus接收到的
answer
[{"janus": "event","session_id": 315440728471870,"transaction": "uhVmrA3m1zpF","sender": 2867164819615564,"plugindata": {"plugin": "janus.plugin.videoroom","data": {"videoroom": "event","room": 1234,"configured": "ok","audio_codec": "opus","video_codec": "vp8"}},"jsep": {"type": "answer","sdp": "v=0\r\no=- 7188370618827213634 2 IN IP4 10.0.31.174\r\ns=VideoRoom 1234\r\nt=0 0\r\na=group:BUNDLE 0 1\r\na=msid-semantic: WMS janus\r\nm=audio 9 UDP/TLS/RTP/SAVPF 111\r\nc=IN IP4 10.0.31.174\r\na=recvonly\r\na=mid:0\r\na=rtcp-mux\r\na=ice-ufrag:dg6Y\r\na=ice-pwd:CPaZBRu+lV+71UPL3OjDPx\r\na=ice-options:trickle\r\na=fingerprint:sha-256 5F:EC:38:77:05:DC:0E:B5:8C:20:F0:41:E2:A5:12:9F:FB:29:B1:16:48:43:47:7B:45:6D:48:09:BF:24:C9:A1\r\na=setup:active\r\na=rtpmap:111 opus/48000/2\r\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\na=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\na=msid:janus janusa0\r\na=ssrc:3573227541 cname:janus\r\na=ssrc:3573227541 msid:janus janusa0\r\na=ssrc:3573227541 mslabel:janus\r\na=ssrc:3573227541 label:janusa0\r\na=candidate:1 1 udp 2013266431 10.0.31.174 59761 typ host\r\na=end-of-candidates\r\nm=video 9 UDP/TLS/RTP/SAVPF 96 97\r\nc=IN IP4 10.0.31.174\r\na=recvonly\r\na=mid:1\r\na=rtcp-mux\r\na=ice-ufrag:dg6Y\r\na=ice-pwd:CPaZBRu+lV+71UPL3OjDPx\r\na=ice-options:trickle\r\na=fingerprint:sha-256 5F:EC:38:77:05:DC:0E:B5:8C:20:F0:41:E2:A5:12:9F:FB:29:B1:16:48:43:47:7B:45:6D:48:09:BF:24:C9:A1\r\na=setup:active\r\na=rtpmap:96 VP8/90000\r\na=rtcp-fb:96 ccm fir\r\na=rtcp-fb:96 nack\r\na=rtcp-fb:96 nack pli\r\na=rtcp-fb:96 goog-remb\r\na=rtcp-fb:96 transport-cc\r\na=extmap:13 urn:3gpp:video-orientation\r\na=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\na=extmap:8 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07\r\na=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\na=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\na=rtpmap:97 rtx/90000\r\na=fmtp:97 apt=96\r\na=msid:janus janusv0\r\na=ssrc:859461873 cname:janus\r\na=ssrc:859461873 msid:janus janusv0\r\na=ssrc:859461873 mslabel:janus\r\na=ssrc:859461873 label:janusv0\r\na=ssrc:3525673182 cname:janus\r\na=ssrc:3525673182 msid:janus janusv0\r\na=ssrc:3525673182 mslabel:janus\r\na=ssrc:3525673182 label:janusv0\r\na=candidate:1 1 udp 2013266431 10.0.31.174 59761 typ host\r\na=end-of-candidates\r\n"}}
]
拉流信令
拉流部分的处理流程和发送是差不多的,只是这时候offer
是由Janus服务器发出的,用户在处理完内容后回应answer
消息,这里的时序图不在画出,具体的内容可通过wireshark抓包分析内容
- Janus发出去的
offer
{"janus": "event","session_id": 4686150751808021,"transaction": "mb6tqmHrrggV","sender": 6786058001814846,"plugindata": {"plugin": "janus.plugin.videoroom","data": {"videoroom": "attached","room": 1234,"id": 7101231539588320,"display": "111"}},"jsep": {"type": "offer","sdp": "v=0\r\no=- 1585297336467626 1 IN IP4 10.0.31.174\r\ns=VideoRoom 1234\r\nt=0 0\r\na=group:BUNDLE audio video\r\na=msid-semantic: WMS janus\r\nm=audio 9 UDP/TLS/RTP/SAVPF 111\r\nc=IN IP4 10.0.31.174\r\na=sendonly\r\na=mid:audio\r\na=rtcp-mux\r\na=ice-ufrag:fPAo\r\na=ice-pwd:QInAfKXHgx/V75sHent4Mg\r\na=ice-options:trickle\r\na=fingerprint:sha-256 5F:EC:38:77:05:DC:0E:B5:8C:20:F0:41:E2:A5:12:9F:FB:29:B1:16:48:43:47:7B:45:6D:48:09:BF:24:C9:A1\r\na=setup:actpass\r\na=rtpmap:111 opus/48000/2\r\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=extmap:2 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=rtcp-fb:111 transport-cc\r\na=msid:janus janusa0\r\na=ssrc:1432073087 cname:janus\r\na=ssrc:1432073087 msid:janus janusa0\r\na=ssrc:1432073087 mslabel:janus\r\na=ssrc:1432073087 label:janusa0\r\na=candidate:1 1 udp 2013266431 10.0.31.174 45676 typ host\r\na=end-of-candidates\r\nm=video 9 UDP/TLS/RTP/SAVPF 96 97\r\nc=IN IP4 10.0.31.174\r\na=sendonly\r\na=mid:video\r\na=rtcp-mux\r\na=ice-ufrag:fPAo\r\na=ice-pwd:QInAfKXHgx/V75sHent4Mg\r\na=ice-options:trickle\r\na=fingerprint:sha-256 5F:EC:38:77:05:DC:0E:B5:8C:20:F0:41:E2:A5:12:9F:FB:29:B1:16:48:43:47:7B:45:6D:48:09:BF:24:C9:A1\r\na=setup:actpass\r\na=rtpmap:96 VP8/90000\r\na=rtcp-fb:96 ccm fir\r\na=rtcp-fb:96 nack\r\na=rtcp-fb:96 nack pli\r\na=rtcp-fb:96 goog-remb\r\na=extmap:2 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\na=extmap:13 urn:3gpp:video-orientation\r\na=rtpmap:97 rtx/90000\r\na=fmtp:97 apt=96\r\na=ssrc-group:FID 3680266443 1738706053\r\na=msid:janus janusv0\r\na=ssrc:3680266443 cname:janus\r\na=ssrc:3680266443 msid:janus janusv0\r\na=ssrc:3680266443 mslabel:janus\r\na=ssrc:3680266443 label:janusv0\r\na=ssrc:1738706053 cname:janus\r\na=ssrc:1738706053 msid:janus janusv0\r\na=ssrc:1738706053 mslabel:janus\r\na=ssrc:1738706053 label:janusv0\r\na=candidate:1 1 udp 2013266431 10.0.31.174 45676 typ host\r\na=end-of-candidates\r\n"}
}
- 客户端接收到的
answer
{"janus": "message","body": {"request": "start","room": 1234},"transaction": "3Rg4j0PsFhVi","jsep": {"type": "answer","sdp": "v=0\r\no=- 3017476953675512810 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE audio video\r\na=msid-semantic: WMS\r\nm=audio 9 UDP/TLS/RTP/SAVPF 111\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=ice-ufrag:Ipts\r\na=ice-pwd:l5/vd/bLIfAJlZR53RKvU7aJ\r\na=ice-options:trickle\r\na=fingerprint:sha-256 0B:9E:78:6B:AF:22:1A:75:9E:61:EC:05:41:38:8B:E9:AD:CB:4C:E6:B5:D9:93:BB:08:F3:B2:55:C0:58:9A:7F\r\na=setup:active\r\na=mid:audio\r\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=extmap:2 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=recvonly\r\na=rtcp-mux\r\na=rtpmap:111 opus/48000/2\r\na=rtcp-fb:111 transport-cc\r\na=fmtp:111 minptime=10;useinbandfec=1\r\nm=video 9 UDP/TLS/RTP/SAVPF 96 97\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=ice-ufrag:Ipts\r\na=ice-pwd:l5/vd/bLIfAJlZR53RKvU7aJ\r\na=ice-options:trickle\r\na=fingerprint:sha-256 0B:9E:78:6B:AF:22:1A:75:9E:61:EC:05:41:38:8B:E9:AD:CB:4C:E6:B5:D9:93:BB:08:F3:B2:55:C0:58:9A:7F\r\na=setup:active\r\na=mid:video\r\na=extmap:13 urn:3gpp:video-orientation\r\na=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\na=extmap:2 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=recvonly\r\na=rtcp-mux\r\na=rtpmap:96 VP8/90000\r\na=rtcp-fb:96 goog-remb\r\na=rtcp-fb:96 ccm fir\r\na=rtcp-fb:96 nack\r\na=rtcp-fb:96 nack pli\r\na=rtpmap:97 rtx/90000\r\na=fmtp:97 apt=96\r\n"},"session_id": 4686150751808021,"handle_id": 6786058001814846
}
Janus信令特点
- 客户端返回的重要消息一般情况下都有
session id
和handle id
, 除了一些keepalive
,create
etc. - 大部分异步调用的消息都会收到
ack
,表明Janus收到消息,等到真正处理完成会返回一条真正的响应消息。异步消息有:"join"、"joinandconfigure"、 "configure"、"publish"、"unpublish"、 "start"、"pause"、"switch"、"leave"
etc. - 拉流之前由于是服务器发送的
offer
, 因此客户端必须要先发送一条消息,这条消息大家可以通过抓包自行分析 keepalive
必须周期性的发送,否则Janus达到一定的超时时间后会自行断开
总之Janus信令是一个相对复杂的设计,大家在信令开发的时候,尽量参考抓包、Janus源码,以及官方提供的文档进行编程。最重要的是要细心,有时候你的流程是对的,但是消息内容是错的,造成不必要的时间浪费。
程序设计
信令部分不再多说,都是http消息的构造和处理。
客户端WebRTC部分我这里着重讲一下
推流流程:
- 创建
offer
peerConnetClient.createPeerConnection(new NullVideoSink(),Collections.singletonList(videoSink),proxyCapturer,signalingParameters);peerConnetClient.createOffer();
- 创建
offer
成功之后,会生成sdp
信息,这时候通过本地sdp设置一些编码、传输信息
public void onCreateSuccess(final SessionDescription origSdp) {if (localSdp != null) {reportError("Multiple SDP create.");return;}String sdpDescription = origSdp.description;if (preferIsac) {sdpDescription = preferCodec(sdpDescription, AUDIO_CODEC_ISAC, true);}if (isVideoCallEnabled()) {sdpDescription =preferCodec(sdpDescription, getSdpVideoCodecName(peerConnectionParameters), false);}final SessionDescription sdp = new SessionDescription(origSdp.type, sdpDescription);localSdp = sdp;executor.execute(() -> {if (peerConnection != null && !isError) {Log.d(TAG, "Set local SDP from " + sdp.type);peerConnection.setLocalDescription(sdpObserver, sdp);}});}
- 把创建后的
sdp
发给Janus
public void onLocalDescription(SessionDescription sdp) {wsClient.publish(sessionId, handleId, true, true, sdp);}
- 发送客户端本地的候选给Janus
public void onIceCandidate(IceCandidate candidate) {wsClient.publishICE(sessionId, handleId, candidate); }
至此整个推流过程正常建立起来
拉流流程
- 请求拉流
public int subscribe() {streamStatus = StreamStat.CONNECTING;wsClient.subscribe(sessionId, handlerId, feedId, privateId, streamName);return 0;}
- 收到Janus发给的
offer
public void dealWithSubscribeJsep(SessionDescription sdp) {//remoteProxyRenderer.setTarget(remoteVideoRenderer);List<PeerConnection.IceServer> iceServers = new ArrayList<>();AppRTCClient.SignalingParameters signalingParameters =new AppRTCClient.SignalingParameters(iceServers,false, // iceServers, initiator.null,null,null, // clientId, wssUrl, wssPostUrl.null,null); // offerSdp, iceCandidates.peerConnetClient.createPeerConnection((VideoSink) remoteVideoRenderer,remoteProxyRenderer,(VideoCapturer) null,signalingParameters);peerConnetClient.setRemoteDescription(sdp);peerConnetClient.createAnswer();}
- 创建
answer
成功之后,会生成sdp
信息,这时候通过本地sdp设置一些codec、传输信息
public void onCreateSuccess(final SessionDescription origSdp) {if (localSdp != null) {reportError("Multiple SDP create.");return;}String sdpDescription = origSdp.description;if (preferIsac) {sdpDescription = preferCodec(sdpDescription, AUDIO_CODEC_ISAC, true);}if (isVideoCallEnabled()) {sdpDescription =preferCodec(sdpDescription, getSdpVideoCodecName(peerConnectionParameters), false);}final SessionDescription sdp = new SessionDescription(origSdp.type, sdpDescription);localSdp = sdp;executor.execute(() -> {if (peerConnection != null && !isError) {Log.d(TAG, "Set local SDP from " + sdp.type);peerConnection.setLocalDescription(sdpObserver, sdp);}});}
- 发送
sdp
给Janus服务器
public void onLocalDescription(SessionDescription sdp) {wsClient.subscribeJSEP(handlerId, sdp);}
支持拉流完成
展示下Android 与web端交互的效果
总结
基于WebRTC打造的是一个相对复杂的过程,涉及到流媒体的很多知识,能够做出来只是走完了第一步,能够做的兼容性够好,也许有一天移动的native开发可以被替代,但掌握webrtc 的native的代码开发套路可以对音视频这种强实时通信方面的产品的开发是大有裨益的。
这篇关于如何基于Janus和WebRTC源码打造自己的实时互动应用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!