如何基于Janus和WebRTC源码打造自己的实时互动应用

2023-10-10 19:10

本文主要是介绍如何基于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服务器发送到客户端的数据叫做拉流

推流信令

Native App Janus server 创建session 返回session id 创建handle 返回handle id 设置发送信息(音、视频、sdp) 返回一些codec信息、sdp Native App Janus server

最重要的两条信息,也是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 idhandle id, 除了一些keepalivecreate 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源码打造自己的实时互动应用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/182486

相关文章

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

hdu1394(线段树点更新的应用)

题意:求一个序列经过一定的操作得到的序列的最小逆序数 这题会用到逆序数的一个性质,在0到n-1这些数字组成的乱序排列,将第一个数字A移到最后一位,得到的逆序数为res-a+(n-a-1) 知道上面的知识点后,可以用暴力来解 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#in

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

zoj3820(树的直径的应用)

题意:在一颗树上找两个点,使得所有点到选择与其更近的一个点的距离的最大值最小。 思路:如果是选择一个点的话,那么点就是直径的中点。现在考虑两个点的情况,先求树的直径,再把直径最中间的边去掉,再求剩下的两个子树中直径的中点。 代码如下: #include <stdio.h>#include <string.h>#include <algorithm>#include <map>#

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

【区块链 + 人才服务】可信教育区块链治理系统 | FISCO BCOS应用案例

伴随着区块链技术的不断完善,其在教育信息化中的应用也在持续发展。利用区块链数据共识、不可篡改的特性, 将与教育相关的数据要素在区块链上进行存证确权,在确保数据可信的前提下,促进教育的公平、透明、开放,为教育教学质量提升赋能,实现教育数据的安全共享、高等教育体系的智慧治理。 可信教育区块链治理系统的顶层治理架构由教育部、高校、企业、学生等多方角色共同参与建设、维护,支撑教育资源共享、教学质量评估、

AI行业应用(不定期更新)

ChatPDF 可以让你上传一个 PDF 文件,然后针对这个 PDF 进行小结和提问。你可以把各种各样你要研究的分析报告交给它,快速获取到想要知道的信息。https://www.chatpdf.com/

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL