如何基于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

相关文章

Java调用C++动态库超详细步骤讲解(附源码)

《Java调用C++动态库超详细步骤讲解(附源码)》C语言因其高效和接近硬件的特性,时常会被用在性能要求较高或者需要直接操作硬件的场合,:本文主要介绍Java调用C++动态库的相关资料,文中通过代... 目录一、直接调用C++库第一步:动态库生成(vs2017+qt5.12.10)第二步:Java调用C++

基于Python打造一个可视化FTP服务器

《基于Python打造一个可视化FTP服务器》在日常办公和团队协作中,文件共享是一个不可或缺的需求,所以本文将使用Python+Tkinter+pyftpdlib开发一款可视化FTP服务器,有需要的小... 目录1. 概述2. 功能介绍3. 如何使用4. 代码解析5. 运行效果6.相关源码7. 总结与展望1

Python中随机休眠技术原理与应用详解

《Python中随机休眠技术原理与应用详解》在编程中,让程序暂停执行特定时间是常见需求,当需要引入不确定性时,随机休眠就成为关键技巧,下面我们就来看看Python中随机休眠技术的具体实现与应用吧... 目录引言一、实现原理与基础方法1.1 核心函数解析1.2 基础实现模板1.3 整数版实现二、典型应用场景2

Python实现无痛修改第三方库源码的方法详解

《Python实现无痛修改第三方库源码的方法详解》很多时候,我们下载的第三方库是不会有需求不满足的情况,但也有极少的情况,第三方库没有兼顾到需求,本文将介绍几个修改源码的操作,大家可以根据需求进行选择... 目录需求不符合模拟示例 1. 修改源文件2. 继承修改3. 猴子补丁4. 追踪局部变量需求不符合很

Python Dash框架在数据可视化仪表板中的应用与实践记录

《PythonDash框架在数据可视化仪表板中的应用与实践记录》Python的PlotlyDash库提供了一种简便且强大的方式来构建和展示互动式数据仪表板,本篇文章将深入探讨如何使用Dash设计一... 目录python Dash框架在数据可视化仪表板中的应用与实践1. 什么是Plotly Dash?1.1

Android Kotlin 高阶函数详解及其在协程中的应用小结

《AndroidKotlin高阶函数详解及其在协程中的应用小结》高阶函数是Kotlin中的一个重要特性,它能够将函数作为一等公民(First-ClassCitizen),使得代码更加简洁、灵活和可... 目录1. 引言2. 什么是高阶函数?3. 高阶函数的基础用法3.1 传递函数作为参数3.2 Lambda

Java中&和&&以及|和||的区别、应用场景和代码示例

《Java中&和&&以及|和||的区别、应用场景和代码示例》:本文主要介绍Java中的逻辑运算符&、&&、|和||的区别,包括它们在布尔和整数类型上的应用,文中通过代码介绍的非常详细,需要的朋友可... 目录前言1. & 和 &&代码示例2. | 和 ||代码示例3. 为什么要使用 & 和 | 而不是总是使

Python循环缓冲区的应用详解

《Python循环缓冲区的应用详解》循环缓冲区是一个线性缓冲区,逻辑上被视为一个循环的结构,本文主要为大家介绍了Python中循环缓冲区的相关应用,有兴趣的小伙伴可以了解一下... 目录什么是循环缓冲区循环缓冲区的结构python中的循环缓冲区实现运行循环缓冲区循环缓冲区的优势应用案例Python中的实现库

SpringBoot整合MybatisPlus的基本应用指南

《SpringBoot整合MybatisPlus的基本应用指南》MyBatis-Plus,简称MP,是一个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,下面小编就来和大家介绍一下... 目录一、MyBATisPlus简介二、SpringBoot整合MybatisPlus1、创建数据库和

python中time模块的常用方法及应用详解

《python中time模块的常用方法及应用详解》在Python开发中,时间处理是绕不开的刚需场景,从性能计时到定时任务,从日志记录到数据同步,时间模块始终是开发者最得力的工具之一,本文将通过真实案例... 目录一、时间基石:time.time()典型场景:程序性能分析进阶技巧:结合上下文管理器实现自动计时