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

相关文章

5分钟获取deepseek api并搭建简易问答应用

《5分钟获取deepseekapi并搭建简易问答应用》本文主要介绍了5分钟获取deepseekapi并搭建简易问答应用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需... 目录1、获取api2、获取base_url和chat_model3、配置模型参数方法一:终端中临时将加

JavaScript中的isTrusted属性及其应用场景详解

《JavaScript中的isTrusted属性及其应用场景详解》在现代Web开发中,JavaScript是构建交互式应用的核心语言,随着前端技术的不断发展,开发者需要处理越来越多的复杂场景,例如事件... 目录引言一、问题背景二、isTrusted 属性的来源与作用1. isTrusted 的定义2. 为

Go中sync.Once源码的深度讲解

《Go中sync.Once源码的深度讲解》sync.Once是Go语言标准库中的一个同步原语,用于确保某个操作只执行一次,本文将从源码出发为大家详细介绍一下sync.Once的具体使用,x希望对大家有... 目录概念简单示例源码解读总结概念sync.Once是Go语言标准库中的一个同步原语,用于确保某个操

Python调用另一个py文件并传递参数常见的方法及其应用场景

《Python调用另一个py文件并传递参数常见的方法及其应用场景》:本文主要介绍在Python中调用另一个py文件并传递参数的几种常见方法,包括使用import语句、exec函数、subproce... 目录前言1. 使用import语句1.1 基本用法1.2 导入特定函数1.3 处理文件路径2. 使用ex

用Java打造简易计算器的实现步骤

《用Java打造简易计算器的实现步骤》:本文主要介绍如何设计和实现一个简单的Java命令行计算器程序,该程序能够执行基本的数学运算(加、减、乘、除),文中通过代码介绍的非常详细,需要的朋友可以参考... 目录目标:一、项目概述与功能规划二、代码实现步骤三、测试与优化四、总结与收获总结目标:简单计算器,设计

将Python应用部署到生产环境的小技巧分享

《将Python应用部署到生产环境的小技巧分享》文章主要讲述了在将Python应用程序部署到生产环境之前,需要进行的准备工作和最佳实践,包括心态调整、代码审查、测试覆盖率提升、配置文件优化、日志记录完... 目录部署前夜:从开发到生产的心理准备与检查清单环境搭建:打造稳固的应用运行平台自动化流水线:让部署像

Linux中Curl参数详解实践应用

《Linux中Curl参数详解实践应用》在现代网络开发和运维工作中,curl命令是一个不可或缺的工具,它是一个利用URL语法在命令行下工作的文件传输工具,支持多种协议,如HTTP、HTTPS、FTP等... 目录引言一、基础请求参数1. -X 或 --request2. -d 或 --data3. -H 或

在Ubuntu上部署SpringBoot应用的操作步骤

《在Ubuntu上部署SpringBoot应用的操作步骤》随着云计算和容器化技术的普及,Linux服务器已成为部署Web应用程序的主流平台之一,Java作为一种跨平台的编程语言,具有广泛的应用场景,本... 目录一、部署准备二、安装 Java 环境1. 安装 JDK2. 验证 Java 安装三、安装 mys

Python中构建终端应用界面利器Blessed模块的使用

《Python中构建终端应用界面利器Blessed模块的使用》Blessed库作为一个轻量级且功能强大的解决方案,开始在开发者中赢得口碑,今天,我们就一起来探索一下它是如何让终端UI开发变得轻松而高... 目录一、安装与配置:简单、快速、无障碍二、基本功能:从彩色文本到动态交互1. 显示基本内容2. 创建链

Java汇编源码如何查看环境搭建

《Java汇编源码如何查看环境搭建》:本文主要介绍如何在IntelliJIDEA开发环境中搭建字节码和汇编环境,以便更好地进行代码调优和JVM学习,首先,介绍了如何配置IntelliJIDEA以方... 目录一、简介二、在IDEA开发环境中搭建汇编环境2.1 在IDEA中搭建字节码查看环境2.1.1 搭建步