From d415202d1515625ff009ce8260cbe0c9033be6b0 Mon Sep 17 00:00:00 2001 From: acgist <289547414@qq.com> Date: Fri, 7 Jul 2023 08:30:09 +0800 Subject: [PATCH] =?UTF-8?q?[*]=20=E6=AF=8F=E6=97=A5=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 22 --- docs/Design.md | 112 +++++++++++++ docs/NetworkTopology.md | 2 +- taoyao-client-web/README.md | 11 ++ taoyao-client-web/src/App.vue | 4 +- taoyao-client-web/src/components/Config.js | 150 ++++++++++++------ .../src/components/LocalClient.vue | 1 + .../src/components/RemoteClient.vue | 2 + .../src/components/SessionClient.vue | 1 + taoyao-client-web/src/components/Taoyao.js | 51 ++++-- 10 files changed, 274 insertions(+), 82 deletions(-) create mode 100644 docs/Design.md diff --git a/README.md b/README.md index c3ad8ec..d5fb60f 100644 --- a/README.md +++ b/README.md @@ -79,28 +79,6 @@ acgist/taoyao-signal-server [部署文档](./docs/Deploy.md) -### 集群 - -信令服务支持下挂多个媒体服务,但是信令服务本身不具备分布式集群功能,如需实现给出以下两种实现建议: - -#### 信令分区 - -将信令服务进行分区管理,分区不要直接管理终端,优先选择分区,然后选择信令服务。 - -#### 代理终端 - -将下级信令服务的终端全部使用代理终端注册到上级信令服务,上级信令服务代理终端处理信令时直接路由到下级路由服务,这样一级一级路由直到发送给真正的终端为止。 - -## 重连 - -### 信令重连 - -所有终端信令默认支持重连 - -### 媒体重连 - -信令没有断开媒体重连依赖具体协议支持,如果信令断开默认关闭所有媒体,信令重连以后需要自己实现媒体重连(控制方主动邀请或者重连方主动进入)。 - ## 终端预览 ![Web终端](./docs/image/web.jpg) diff --git a/docs/Design.md b/docs/Design.md new file mode 100644 index 0000000..935f619 --- /dev/null +++ b/docs/Design.md @@ -0,0 +1,112 @@ +# 整体设计 + +## 设计 + +项目当前采用方案: + +* 监控(WebRTC) +* 会议(Mediasoup独立Router) + +### 监控(WebRTC) + +使用原始`WebRTC`实现,监控和被监控终端使用`P2P`连接。 + +#### 优点 + +* 实现逻辑简单 +* `P2P`媒体直连,服务端没有压力。 + +#### 缺点 + +* 终端上行流量较大(同时被多个终端监控时) + +### 监控(Mediasoup独立Router) + +使用`Mediasoup`实现,所有终端进入一个`Router`,通过`Mediasoup`转发媒体实现监控。 + +#### 优点 + +* 终端只会存在一路上行流量 + +#### 缺点 + +* 单个`Router`压力较大(监控终端数量较多时) + +### 监控(Mediasoup代理Router) + +使用`Mediasoup`实现,每个终端对应一个代理`Router`,通过`PipeTransport`转发代理`Router`媒体实现监控。 + +#### 优点 + +* 终端只会存在一路上行流量 +* 压力不会集中某个`Router` + +#### 缺点 + +* 实现逻辑复杂 +* 服务端压力较大(媒体通道生产者消费者数量增加) + +### 会议(Mediasoup独立Router) + +使用`Mediasoup`实现,每个会议对应一个`Router`,`Router`内部媒体直接转发实现会议。 + +#### 优点 + +* 实现逻辑清晰 + +#### 缺点 + +* 单个终端进入多个会议上行流量较大 + +### 会议(Mediasoup代理Router) + +会议使用`Mediasoup`实现,每个终端对应一个代理`Router`,通过`PipeTransport`转发代理`Router`媒体实现会议。 + +#### 优点 + +* 终端只会存在一路上行流量 + +#### 缺点 + +* 实现逻辑复杂 +* 服务端压力较大(媒体通道生产者消费者数量增加) + +## 子网媒体转发 + +多个子网不能互通时解决方案 + +### 监控 + +如果只有两个子网可以直接通过`TURN`服务实现,如果超过两个子网需要`TURN`服务和防火墙自动转发实现。 + +### 会议 + +#### 实现多级平台 + +实现多级平台的代理终端功能,最后通过`PipeTransport`转发媒体。 + +#### 地址重写和防火墙自动转发(当前采用) + +如果只有两个子网可以通过双网卡服务器直接通过`IP`地址重写实现,如果超过两个子网可以通过`IP`地址重写和防火墙自动转发实现,参考方案(拓扑网络)[./NetworkTopology.md] + +## 多级平台 + +信令服务支持下挂多个媒体服务,但是信令服务本身不具备分布式集群功能,如需实现给出以下两种实现建议: + +### 信令分区 + +将信令服务进行分区管理,分区不要直接管理终端,优先选择分区,然后选择信令服务。 + +### 代理终端 + +将下级信令服务的终端全部使用代理终端注册到上级信令服务,上级信令服务代理终端处理信令时直接路由到下级路由服务,这样一级一级路由直到发送给真正的终端为止。 + +## 重连 + +### 信令重连 + +所有终端信令默认支持重连 + +### 媒体重连 + +信令没有断开媒体重连依赖具体协议支持,如果信令断开默认关闭所有媒体,信令重连以后需要自己实现媒体重连(控制方主动邀请或者重连方主动进入)。 diff --git a/docs/NetworkTopology.md b/docs/NetworkTopology.md index 284bd44..bca3b6e 100644 --- a/docs/NetworkTopology.md +++ b/docs/NetworkTopology.md @@ -1,6 +1,6 @@ # 拓扑网络 -多子网环境下的部署配置 +多子网环境下的部署配置,当前设计只在顶层子网部署服务,每层子网采用双网卡服务器通过防火墙自动转发实现。 ## 两个子网 diff --git a/taoyao-client-web/README.md b/taoyao-client-web/README.md index be507d1..0dab357 100644 --- a/taoyao-client-web/README.md +++ b/taoyao-client-web/README.md @@ -6,6 +6,17 @@ * [mediasoup-client文档](https://mediasoup.org/documentation/v3/mediasoup-client) * [mediasoup-client接口](https://mediasoup.org/documentation/v3/mediasoup-client/api) +## 终端媒体 + +终端页面组件需要提供`media`方法,同时挂载到终端的`proxy`属性下面。 +媒体生成以后自动调用: + +``` +LocalClient.proxy.media(track, producer); +RemoteClient.proxy.media(track, consumer); +SessionClient.proxy.media(track); +``` + ## 终端列表 `Web`终端并未对整个终端列表以及状态进行维护,所以需要开发者自己实现。 diff --git a/taoyao-client-web/src/App.vue b/taoyao-client-web/src/App.vue index a84fe65..41faaa1 100644 --- a/taoyao-client-web/src/App.vue +++ b/taoyao-client-web/src/App.vue @@ -92,7 +92,7 @@ - + @@ -206,7 +206,7 @@ export default { * @param {*} response 回调 * @param {*} error 异常 * - * @return 是否继续执行 + * @return 是否执行完成 */ async callback(response, error) { const me = this; diff --git a/taoyao-client-web/src/components/Config.js b/taoyao-client-web/src/components/Config.js index 9f36c62..1808f23 100644 --- a/taoyao-client-web/src/components/Config.js +++ b/taoyao-client-web/src/components/Config.js @@ -1,88 +1,147 @@ +/** + * 配置:{ min: 8000, exact: 32000, ideal: 32000, max: 48000 } + */ /** * 音频默认配置 - * TODO:MediaStreamTrack.applyConstraints().then().catch(); - * const setting = { - * autoGainControl: true, - * noiseSuppression: true - * } - await track.applyConstraints(Object.assign(track.getSettings(), setting)); - * TODO:播放音量(audio标签配置)、采集音量 - * 支持属性:navigator.mediaDevices.getSupportedConstraints() + * * https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings */ const defaultAudioConfig = { - // 设备 - // deviceId : '', + // 指定设备 + // deviceId : '', + // 标识会话 + // groupId : '', // 音量(废弃):0.0~1.0 - // volume: 1.0, + // volume : 1.0, // 延迟时间(单位:秒):500毫秒以内较好 - // latency: 0.4, + // latency : 0.4, // 采样位数:8|16|32 - sampleSize: { min: 8, ideal: 16, max: 32 }, + sampleSize : { min: 8, ideal: 16, max: 32 }, // 采样率:8000|16000|32000|48000 - sampleRate: { min: 8000, ideal: 32000, max: 48000 }, + sampleRate : { min: 8000, ideal: 32000, max: 48000 }, // 声道数量:1|2 - channelCount: 1, + channelCount : 1, // 是否开启自动增益:true|false - autoGainControl: true, + autoGainControl : true, // 是否开启降噪功能:true|false noiseSuppression: true, // 是否开启回音消除:true|false echoCancellation: true, - // 消除回音方式:system|browser - echoCancellationType: "system", }; /** * 视频默认配置 + * + * https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings */ const defaultVideoConfig = { - // 设备 - // deviceId: '', + // 指定设备 + // deviceId : '', + // 标识会话 + // groupId : '', // 宽度 - width: { min: 720, ideal: 1280, max: 4096 }, + width : { min: 720, ideal: 1280, max: 4096 }, // 高度 - height: { min: 480, ideal: 720, max: 2160 }, + height : { min: 480, ideal: 720, max: 2160 }, // 帧率 - frameRate: { min: 15, ideal: 24, max: 45 }, - // 选摄像头:user|left|right|environment - facingMode: "environment", + frameRate : { min: 15, ideal: 24, max: 45 }, + // 摄像头:user|left|right|environment + facingMode : "environment", + // 裁剪 + // resizeMode : null, + // 宽高比 + // aspectRatio: 1.7777777778, }; /** - * VP9默认配置 + * 共享屏幕默认配置 + * + * https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings */ -const defaultKsvcEncodings = [{ scalabilityMode: "S3T3_KEY" }]; +const defaultShareScreenConfig = { + // 显示鼠标:always|motion|never + cursor : "always", + // 逻辑窗口捕获(没有完全显示) + logicalSurface: true, + // 视频来源:window|monitor|browser|application + displaySurface: "monitor", +} /** - * simulcast默认配置 - * TODO:update - * https://gitee.com/acgist/mediasoup-demo/commit/090c82920d1b8015d457e4fafbb06607cb232885 - * https://gitee.com/acgist/mediasoup-demo/commit/e4f70da0c69226b997d174c477d82f8dbb997e91 - * https://gitee.com/acgist/mediasoup-demo/commit/2c67601d0a231bf901242c8e14cdd0d1ba39f3a4 - * https://gitee.com/acgist/mediasoup-demo/commit/b9f3f28d2eab314b95392fa698d518177d5ad767 - * https://gitee.com/acgist/mediasoup-demo/commit/1c59132ca926a6f9ca0c5c2bb155fac58eed9b06 - * https://gitee.com/acgist/mediasoup-demo/commit/d15a859306e1ba5d031cde90d02593e095719cbc - * https://gitee.com/acgist/mediasoup-demo/commit/13cf71cc608690ff96ec12e6d3f1262b40c4d8f3 + * SVC默认配置 + * 支持编码:VP9 + * + * https://w3c.github.io/webrtc-svc/ + * https://mediasoup.org/documentation/v3/mediasoup/rtp-parameters-and-capabilities/ + * https://mediasoup.org/documentation/v3/mediasoup/rtp-parameters-and-capabilities/#SVC + */ +const defaultSvcEncodings = [ + { + dtx : true, + maxBitrate : 5000000, + scalabilityMode: 'L3T3_KEY' + } +]; + +/** + * Simulcast默认配置 + * 支持编码:VP8 H264 + * 可以根据数量减少配置数量 + * dtx:屏幕贡献开启效果显著 + * + * https://w3c.github.io/webrtc-svc/ + * https://mediasoup.org/documentation/v3/mediasoup/rtp-parameters-and-capabilities/ + * https://mediasoup.org/documentation/v3/mediasoup/rtp-parameters-and-capabilities/#Simulcast */ const defaultSimulcastEncodings = [ - { scaleResolutionDownBy: 4, maxBitrate: 500000, scalabilityMode: "S1T2" }, - { scaleResolutionDownBy: 2, maxBitrate: 1000000, scalabilityMode: "S1T2" }, - { scaleResolutionDownBy: 1, maxBitrate: 5000000, scalabilityMode: "S1T2" }, + { + dtx : true, + maxBitrate : 5000000, + scalabilityMode : 'L1T3', + scaleResolutionDownBy: 1, + }, + { + dtx : true, + maxBitrate : 1000000, + scalabilityMode : 'L1T3', + scaleResolutionDownBy: 2, + }, + { + dtx : true, + maxBitrate : 500000, + scalabilityMode : 'L1T3', + scaleResolutionDownBy: 4, + }, ]; /** * RTCPeerConnection默认配置 + * + * https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/RTCPeerConnection */ const defaultRTCPeerConnectionConfig = { - // ICE代理的服务器 - iceServers: [], + // ICE代理服务器 + iceServers : [ + { + // { "url": "stun:stun1.l.google.com:19302" }, + urls: [ + "stun:stun1.l.google.com:19302", + "stun:stun2.l.google.com:19302", + "stun:stun3.l.google.com:19302", + "stun:stun4.l.google.com:19302" + ] + } + ], + // 目标对等身份 + // peerIdentity : null, // 传输通道绑定策略:balanced|max-compat|max-bundle - bundlePolicy: "balanced", + bundlePolicy : "balanced", // RTCP多路复用策略:require|negotiate - rtcpMuxPolicy: "require", + rtcpMuxPolicy : "require", + // 连接证书 + // certificates : null, // ICE传输策略:all|relay - iceTransportPolicy: "all", + iceTransportPolicy : "all", // ICE候选个数 iceCandidatePoolSize: 8, }; @@ -90,7 +149,8 @@ const defaultRTCPeerConnectionConfig = { export { defaultAudioConfig, defaultVideoConfig, - defaultKsvcEncodings, + defaultShareScreenConfig, + defaultSvcEncodings, defaultSimulcastEncodings, defaultRTCPeerConnectionConfig, }; diff --git a/taoyao-client-web/src/components/LocalClient.vue b/taoyao-client-web/src/components/LocalClient.vue index df82d12..cd11d8d 100644 --- a/taoyao-client-web/src/components/LocalClient.vue +++ b/taoyao-client-web/src/components/LocalClient.vue @@ -126,6 +126,7 @@ export default { }, }; + diff --git a/taoyao-client-web/src/components/RemoteClient.vue b/taoyao-client-web/src/components/RemoteClient.vue index 18fbc1f..21f7ea4 100644 --- a/taoyao-client-web/src/components/RemoteClient.vue +++ b/taoyao-client-web/src/components/RemoteClient.vue @@ -18,6 +18,7 @@ @@ -120,6 +121,7 @@ export default { } }; + diff --git a/taoyao-client-web/src/components/SessionClient.vue b/taoyao-client-web/src/components/SessionClient.vue index 64639d7..415be70 100644 --- a/taoyao-client-web/src/components/SessionClient.vue +++ b/taoyao-client-web/src/components/SessionClient.vue @@ -116,6 +116,7 @@ export default { } }; + diff --git a/taoyao-client-web/src/components/Taoyao.js b/taoyao-client-web/src/components/Taoyao.js index f6c2ef7..2c10999 100644 --- a/taoyao-client-web/src/components/Taoyao.js +++ b/taoyao-client-web/src/components/Taoyao.js @@ -2,7 +2,8 @@ import * as mediasoupClient from "mediasoup-client"; import { defaultAudioConfig, defaultVideoConfig, - defaultKsvcEncodings, + defaultShareScreenConfig, + defaultSvcEncodings, defaultSimulcastEncodings, defaultRTCPeerConnectionConfig, } from "./Config.js"; @@ -484,12 +485,14 @@ class Taoyao extends RemoteClient { videoSource = "camera"; // 强制使用TCP forceTcp; + // 强制使用VP8 + forceVP8; // 强制使用VP9 forceVP9; // 强制使用H264 forceH264; // 同时上送多种质量媒体 - useSimulcast; + useLayers; // 是否消费数据 dataConsume; // 是否消费音频 @@ -858,6 +861,7 @@ class Taoyao extends RemoteClient { track.getCapabilities() ); } else if (self.videoSource === "screen") { + // TODO:默认配置 const stream = await navigator.mediaDevices.getDisplayMedia({ // 如果需要共享声音 audio: false, @@ -1605,10 +1609,6 @@ class Taoyao extends RemoteClient { */ async roomEnter(roomId, password) { const me = this; - if (!roomId) { - this.callbackError("无效房间"); - return; - } // TODO:已经进入房间忽略 me.roomId = roomId; let response = await me.request( @@ -1983,6 +1983,7 @@ class Taoyao extends RemoteClient { // await this.produceAudio(); // await this.produceVideo(); // await this.produceData(); + // TODO:返回通道还有音视频生产者 } /** * 生产音频 @@ -2113,17 +2114,23 @@ class Taoyao extends RemoteClient { if (!codec) { self.callbackError("不支持VP9视频编码"); } + } else if(self.forceVP8) { + codec = self.mediasoupDevice.rtpCapabilities.codecs.find( + (c) => c.mimeType.toLowerCase() === "video/vp8" + ); + if (!codec) { + self.callbackError("不支持VP8视频编码"); + } } - if (this.useSimulcast) { - const firstVideoCodec = - this.mediasoupDevice.rtpCapabilities.codecs.find( - (c) => c.kind === "video" - ); + if (this.useLayers) { + const firstVideoCodec = this.mediasoupDevice.rtpCapabilities.codecs.find( + (c) => c.kind === "video" + ); if ( (this.forceVP9 && codec) || firstVideoCodec.mimeType.toLowerCase() === "video/vp9" ) { - encodings = defaultKsvcEncodings; + encodings = defaultSvcEncodings; } else { encodings = defaultSimulcastEncodings; } @@ -2599,6 +2606,26 @@ class Taoyao extends RemoteClient { } } + /** + * TODO:设置track配置 + * + * @param {*} track + * @param {*} setting + */ + setTrack(track, setting) { + /* + * TODO:MediaStreamTrack.applyConstraints().then().catch(); + * const setting = { + * autoGainControl: true, + * noiseSuppression: true + * } + await track.applyConstraints(Object.assign(track.getSettings(), setting)); + * TODO:播放音量(audio标签配置)、采集音量 + * 支持属性:navigator.mediaDevices.getSupportedConstraints() + * https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings + */ + } + /** * 关闭视频房间媒体 */