[*] 每日优化

This commit is contained in:
acgist
2023-07-07 08:30:09 +08:00
parent e4f2aa1a97
commit d415202d15
10 changed files with 274 additions and 82 deletions

View File

@@ -79,28 +79,6 @@ acgist/taoyao-signal-server
[部署文档](./docs/Deploy.md) [部署文档](./docs/Deploy.md)
### 集群
信令服务支持下挂多个媒体服务,但是信令服务本身不具备分布式集群功能,如需实现给出以下两种实现建议:
#### 信令分区
将信令服务进行分区管理,分区不要直接管理终端,优先选择分区,然后选择信令服务。
#### 代理终端
将下级信令服务的终端全部使用代理终端注册到上级信令服务,上级信令服务代理终端处理信令时直接路由到下级路由服务,这样一级一级路由直到发送给真正的终端为止。
## 重连
### 信令重连
所有终端信令默认支持重连
### 媒体重连
信令没有断开媒体重连依赖具体协议支持,如果信令断开默认关闭所有媒体,信令重连以后需要自己实现媒体重连(控制方主动邀请或者重连方主动进入)。
## 终端预览 ## 终端预览
![Web终端](./docs/image/web.jpg) ![Web终端](./docs/image/web.jpg)

112
docs/Design.md Normal file
View File

@@ -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]
## 多级平台
信令服务支持下挂多个媒体服务,但是信令服务本身不具备分布式集群功能,如需实现给出以下两种实现建议:
### 信令分区
将信令服务进行分区管理,分区不要直接管理终端,优先选择分区,然后选择信令服务。
### 代理终端
将下级信令服务的终端全部使用代理终端注册到上级信令服务,上级信令服务代理终端处理信令时直接路由到下级路由服务,这样一级一级路由直到发送给真正的终端为止。
## 重连
### 信令重连
所有终端信令默认支持重连
### 媒体重连
信令没有断开媒体重连依赖具体协议支持,如果信令断开默认关闭所有媒体,信令重连以后需要自己实现媒体重连(控制方主动邀请或者重连方主动进入)。

View File

@@ -1,6 +1,6 @@
# 拓扑网络 # 拓扑网络
多子网环境下的部署配置 多子网环境下的部署配置,当前设计只在顶层子网部署服务,每层子网采用双网卡服务器通过防火墙自动转发实现。
## 两个子网 ## 两个子网

View File

@@ -6,6 +6,17 @@
* [mediasoup-client文档](https://mediasoup.org/documentation/v3/mediasoup-client) * [mediasoup-client文档](https://mediasoup.org/documentation/v3/mediasoup-client)
* [mediasoup-client接口](https://mediasoup.org/documentation/v3/mediasoup-client/api) * [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`终端并未对整个终端列表以及状态进行维护,所以需要开发者自己实现。 `Web`终端并未对整个终端列表以及状态进行维护,所以需要开发者自己实现。

View File

@@ -92,7 +92,7 @@
<!-- 本地终端 --> <!-- 本地终端 -->
<LocalClient v-if="taoyao && taoyao.roomId" ref="local-client" :client="taoyao" :taoyao="taoyao"></LocalClient> <LocalClient v-if="taoyao && taoyao.roomId" ref="local-client" :client="taoyao" :taoyao="taoyao"></LocalClient>
<!-- 远程终端 --> <!-- 远程终端 -->
<RemoteClient v-for="kv in remoteClients" :key="'remote-client-' + kv[0]" :ref="'remote-client-' + kv[0]" :client="kv[1]" :taoyao="taoyao"></RemoteClient> <RemoteClient v-for="kv in remoteClients" :key="'remote-client-' + kv[0]" :ref="'remote-client-' + kv[0]" :client="kv[1]" :taoyao="taoyao"></RemoteClient>
<!-- 远程会话 --> <!-- 远程会话 -->
<SessionClient v-for="kv in sessionClients" :key="'session-client-' + kv[0]" :ref="'session-client-' + kv[0]" :client="kv[1]" :taoyao="taoyao"></SessionClient> <SessionClient v-for="kv in sessionClients" :key="'session-client-' + kv[0]" :ref="'session-client-' + kv[0]" :client="kv[1]" :taoyao="taoyao"></SessionClient>
</div> </div>
@@ -206,7 +206,7 @@ export default {
* @param {*} response 回调 * @param {*} response 回调
* @param {*} error 异常 * @param {*} error 异常
* *
* @return 是否继续执行 * @return 是否执行完成
*/ */
async callback(response, error) { async callback(response, error) {
const me = this; const me = this;

View File

@@ -1,88 +1,147 @@
/**
* 配置:{ min: 8000, exact: 32000, ideal: 32000, max: 48000 }
*/
/** /**
* 音频默认配置 * 音频默认配置
* TODOMediaStreamTrack.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 * https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings
*/ */
const defaultAudioConfig = { const defaultAudioConfig = {
// 设备 // 指定设备
// deviceId : '', // deviceId : '',
// 标识会话
// groupId : '',
// 音量废弃0.0~1.0 // 音量废弃0.0~1.0
// volume: 1.0, // volume : 1.0,
// 延迟时间单位500毫秒以内较好 // 延迟时间单位500毫秒以内较好
// latency: 0.4, // latency : 0.4,
// 采样位数8|16|32 // 采样位数8|16|32
sampleSize: { min: 8, ideal: 16, max: 32 }, sampleSize : { min: 8, ideal: 16, max: 32 },
// 采样率8000|16000|32000|48000 // 采样率8000|16000|32000|48000
sampleRate: { min: 8000, ideal: 32000, max: 48000 }, sampleRate : { min: 8000, ideal: 32000, max: 48000 },
// 声道数量1|2 // 声道数量1|2
channelCount: 1, channelCount : 1,
// 是否开启自动增益true|false // 是否开启自动增益true|false
autoGainControl: true, autoGainControl : true,
// 是否开启降噪功能true|false // 是否开启降噪功能true|false
noiseSuppression: true, noiseSuppression: true,
// 是否开启回音消除true|false // 是否开启回音消除true|false
echoCancellation: true, echoCancellation: true,
// 消除回音方式system|browser
echoCancellationType: "system",
}; };
/** /**
* 视频默认配置 * 视频默认配置
*
* https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings
*/ */
const defaultVideoConfig = { 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 }, frameRate : { min: 15, ideal: 24, max: 45 },
// 摄像头user|left|right|environment // 摄像头user|left|right|environment
facingMode: "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默认配置 * SVC默认配置
* TODOupdate * 支持编码VP9
* https://gitee.com/acgist/mediasoup-demo/commit/090c82920d1b8015d457e4fafbb06607cb232885 *
* https://gitee.com/acgist/mediasoup-demo/commit/e4f70da0c69226b997d174c477d82f8dbb997e91 * https://w3c.github.io/webrtc-svc/
* https://gitee.com/acgist/mediasoup-demo/commit/2c67601d0a231bf901242c8e14cdd0d1ba39f3a4 * https://mediasoup.org/documentation/v3/mediasoup/rtp-parameters-and-capabilities/
* https://gitee.com/acgist/mediasoup-demo/commit/b9f3f28d2eab314b95392fa698d518177d5ad767 * https://mediasoup.org/documentation/v3/mediasoup/rtp-parameters-and-capabilities/#SVC
* https://gitee.com/acgist/mediasoup-demo/commit/1c59132ca926a6f9ca0c5c2bb155fac58eed9b06 */
* https://gitee.com/acgist/mediasoup-demo/commit/d15a859306e1ba5d031cde90d02593e095719cbc const defaultSvcEncodings = [
* https://gitee.com/acgist/mediasoup-demo/commit/13cf71cc608690ff96ec12e6d3f1262b40c4d8f3 {
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 = [ const defaultSimulcastEncodings = [
{ scaleResolutionDownBy: 4, maxBitrate: 500000, scalabilityMode: "S1T2" }, {
{ scaleResolutionDownBy: 2, maxBitrate: 1000000, scalabilityMode: "S1T2" }, dtx : true,
{ scaleResolutionDownBy: 1, maxBitrate: 5000000, scalabilityMode: "S1T2" }, maxBitrate : 5000000,
scalabilityMode : 'L1T3',
scaleResolutionDownBy: 1,
},
{
dtx : true,
maxBitrate : 1000000,
scalabilityMode : 'L1T3',
scaleResolutionDownBy: 2,
},
{
dtx : true,
maxBitrate : 500000,
scalabilityMode : 'L1T3',
scaleResolutionDownBy: 4,
},
]; ];
/** /**
* RTCPeerConnection默认配置 * RTCPeerConnection默认配置
*
* https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/RTCPeerConnection
*/ */
const defaultRTCPeerConnectionConfig = { const defaultRTCPeerConnectionConfig = {
// ICE代理服务器 // ICE代理服务器
iceServers: [], 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 // 传输通道绑定策略balanced|max-compat|max-bundle
bundlePolicy: "balanced", bundlePolicy : "balanced",
// RTCP多路复用策略require|negotiate // RTCP多路复用策略require|negotiate
rtcpMuxPolicy: "require", rtcpMuxPolicy : "require",
// 连接证书
// certificates : null,
// ICE传输策略all|relay // ICE传输策略all|relay
iceTransportPolicy: "all", iceTransportPolicy : "all",
// ICE候选个数 // ICE候选个数
iceCandidatePoolSize: 8, iceCandidatePoolSize: 8,
}; };
@@ -90,7 +149,8 @@ const defaultRTCPeerConnectionConfig = {
export { export {
defaultAudioConfig, defaultAudioConfig,
defaultVideoConfig, defaultVideoConfig,
defaultKsvcEncodings, defaultShareScreenConfig,
defaultSvcEncodings,
defaultSimulcastEncodings, defaultSimulcastEncodings,
defaultRTCPeerConnectionConfig, defaultRTCPeerConnectionConfig,
}; };

View File

@@ -126,6 +126,7 @@ export default {
}, },
}; };
</script> </script>
<style scoped> <style scoped>
.client .mic{background:linear-gradient(to top, var(--el-color-primary) var(--volume, 100%), transparent 0%);} .client .mic{background:linear-gradient(to top, var(--el-color-primary) var(--volume, 100%), transparent 0%);}
</style> </style>

View File

@@ -18,6 +18,7 @@
<el-button @click="taoyao.mediaConsumerStatus()" :icon="InfoFilled" circle title="媒体信息" /> <el-button @click="taoyao.mediaConsumerStatus()" :icon="InfoFilled" circle title="媒体信息" />
<el-popover placement="top" :width="240" trigger="hover"> <el-popover placement="top" :width="240" trigger="hover">
<template #reference> <template #reference>
<el-button>视频质量</el-button> <el-button>视频质量</el-button>
</template> </template>
<el-table :data="taoyao.options"> <el-table :data="taoyao.options">
@@ -120,6 +121,7 @@ export default {
} }
}; };
</script> </script>
<style scoped> <style scoped>
.client .mic{background:linear-gradient(to top, var(--el-color-primary) var(--volume, 100%), transparent 0%);} .client .mic{background:linear-gradient(to top, var(--el-color-primary) var(--volume, 100%), transparent 0%);}
</style> </style>

View File

@@ -116,6 +116,7 @@ export default {
} }
}; };
</script> </script>
<style scoped> <style scoped>
.client .mic{background:linear-gradient(to top, var(--el-color-primary) var(--volume, 100%), transparent 0%);} .client .mic{background:linear-gradient(to top, var(--el-color-primary) var(--volume, 100%), transparent 0%);}
</style> </style>

View File

@@ -2,7 +2,8 @@ import * as mediasoupClient from "mediasoup-client";
import { import {
defaultAudioConfig, defaultAudioConfig,
defaultVideoConfig, defaultVideoConfig,
defaultKsvcEncodings, defaultShareScreenConfig,
defaultSvcEncodings,
defaultSimulcastEncodings, defaultSimulcastEncodings,
defaultRTCPeerConnectionConfig, defaultRTCPeerConnectionConfig,
} from "./Config.js"; } from "./Config.js";
@@ -484,12 +485,14 @@ class Taoyao extends RemoteClient {
videoSource = "camera"; videoSource = "camera";
// 强制使用TCP // 强制使用TCP
forceTcp; forceTcp;
// 强制使用VP8
forceVP8;
// 强制使用VP9 // 强制使用VP9
forceVP9; forceVP9;
// 强制使用H264 // 强制使用H264
forceH264; forceH264;
// 同时上送多种质量媒体 // 同时上送多种质量媒体
useSimulcast; useLayers;
// 是否消费数据 // 是否消费数据
dataConsume; dataConsume;
// 是否消费音频 // 是否消费音频
@@ -858,6 +861,7 @@ class Taoyao extends RemoteClient {
track.getCapabilities() track.getCapabilities()
); );
} else if (self.videoSource === "screen") { } else if (self.videoSource === "screen") {
// TODO默认配置
const stream = await navigator.mediaDevices.getDisplayMedia({ const stream = await navigator.mediaDevices.getDisplayMedia({
// 如果需要共享声音 // 如果需要共享声音
audio: false, audio: false,
@@ -1605,10 +1609,6 @@ class Taoyao extends RemoteClient {
*/ */
async roomEnter(roomId, password) { async roomEnter(roomId, password) {
const me = this; const me = this;
if (!roomId) {
this.callbackError("无效房间");
return;
}
// TODO已经进入房间忽略 // TODO已经进入房间忽略
me.roomId = roomId; me.roomId = roomId;
let response = await me.request( let response = await me.request(
@@ -1983,6 +1983,7 @@ class Taoyao extends RemoteClient {
// await this.produceAudio(); // await this.produceAudio();
// await this.produceVideo(); // await this.produceVideo();
// await this.produceData(); // await this.produceData();
// TODO返回通道还有音视频生产者
} }
/** /**
* 生产音频 * 生产音频
@@ -2113,17 +2114,23 @@ class Taoyao extends RemoteClient {
if (!codec) { if (!codec) {
self.callbackError("不支持VP9视频编码"); 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) { if (this.useLayers) {
const firstVideoCodec = const firstVideoCodec = this.mediasoupDevice.rtpCapabilities.codecs.find(
this.mediasoupDevice.rtpCapabilities.codecs.find( (c) => c.kind === "video"
(c) => c.kind === "video" );
);
if ( if (
(this.forceVP9 && codec) || (this.forceVP9 && codec) ||
firstVideoCodec.mimeType.toLowerCase() === "video/vp9" firstVideoCodec.mimeType.toLowerCase() === "video/vp9"
) { ) {
encodings = defaultKsvcEncodings; encodings = defaultSvcEncodings;
} else { } else {
encodings = defaultSimulcastEncodings; encodings = defaultSimulcastEncodings;
} }
@@ -2599,6 +2606,26 @@ class Taoyao extends RemoteClient {
} }
} }
/**
* TODO设置track配置
*
* @param {*} track
* @param {*} setting
*/
setTrack(track, setting) {
/*
* TODOMediaStreamTrack.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
*/
}
/** /**
* 关闭视频房间媒体 * 关闭视频房间媒体
*/ */