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

相关文章

nginx -t、nginx -s stop 和 nginx -s reload 命令的详细解析(结合应用场景)

《nginx-t、nginx-sstop和nginx-sreload命令的详细解析(结合应用场景)》本文解析Nginx的-t、-sstop、-sreload命令,分别用于配置语法检... 以下是关于 nginx -t、nginx -s stop 和 nginx -s reload 命令的详细解析,结合实际应

Python办公自动化实战之打造智能邮件发送工具

《Python办公自动化实战之打造智能邮件发送工具》在数字化办公场景中,邮件自动化是提升工作效率的关键技能,本文将演示如何使用Python的smtplib和email库构建一个支持图文混排,多附件,多... 目录前言一、基础配置:搭建邮件发送框架1.1 邮箱服务准备1.2 核心库导入1.3 基础发送函数二、

PostgreSQL的扩展dict_int应用案例解析

《PostgreSQL的扩展dict_int应用案例解析》dict_int扩展为PostgreSQL提供了专业的整数文本处理能力,特别适合需要精确处理数字内容的搜索场景,本文给大家介绍PostgreS... 目录PostgreSQL的扩展dict_int一、扩展概述二、核心功能三、安装与启用四、字典配置方法

Python中re模块结合正则表达式的实际应用案例

《Python中re模块结合正则表达式的实际应用案例》Python中的re模块是用于处理正则表达式的强大工具,正则表达式是一种用来匹配字符串的模式,它可以在文本中搜索和匹配特定的字符串模式,这篇文章主... 目录前言re模块常用函数一、查看文本中是否包含 A 或 B 字符串二、替换多个关键词为统一格式三、提

Java MQTT实战应用

《JavaMQTT实战应用》本文详解MQTT协议,涵盖其发布/订阅机制、低功耗高效特性、三种服务质量等级(QoS0/1/2),以及客户端、代理、主题的核心概念,最后提供Linux部署教程、Sprin... 目录一、MQTT协议二、MQTT优点三、三种服务质量等级四、客户端、代理、主题1. 客户端(Clien

CSS3打造的现代交互式登录界面详细实现过程

《CSS3打造的现代交互式登录界面详细实现过程》本文介绍CSS3和jQuery在登录界面设计中的应用,涵盖动画、选择器、自定义字体及盒模型技术,提升界面美观与交互性,同时优化性能和可访问性,感兴趣的朋... 目录1. css3用户登录界面设计概述1.1 用户界面设计的重要性1.2 CSS3的新特性与优势1.

CSS中的Static、Relative、Absolute、Fixed、Sticky的应用与详细对比

《CSS中的Static、Relative、Absolute、Fixed、Sticky的应用与详细对比》CSS中的position属性用于控制元素的定位方式,不同的定位方式会影响元素在页面中的布... css 中的 position 属性用于控制元素的定位方式,不同的定位方式会影响元素在页面中的布局和层叠关

SpringBoot3应用中集成和使用Spring Retry的实践记录

《SpringBoot3应用中集成和使用SpringRetry的实践记录》SpringRetry为SpringBoot3提供重试机制,支持注解和编程式两种方式,可配置重试策略与监听器,适用于临时性故... 目录1. 简介2. 环境准备3. 使用方式3.1 注解方式 基础使用自定义重试策略失败恢复机制注意事项

使用Python和OpenCV库实现实时颜色识别系统

《使用Python和OpenCV库实现实时颜色识别系统》:本文主要介绍使用Python和OpenCV库实现的实时颜色识别系统,这个系统能够通过摄像头捕捉视频流,并在视频中指定区域内识别主要颜色(红... 目录一、引言二、系统概述三、代码解析1. 导入库2. 颜色识别函数3. 主程序循环四、HSV色彩空间详解

OpenCV实现实时颜色检测的示例

《OpenCV实现实时颜色检测的示例》本文主要介绍了OpenCV实现实时颜色检测的示例,通过HSV色彩空间转换和色调范围判断实现红黄绿蓝颜色检测,包含视频捕捉、区域标记、颜色分析等功能,具有一定的参考... 目录一、引言二、系统概述三、代码解析1. 导入库2. 颜色识别函数3. 主程序循环四、HSV色彩空间