From 75e7df522efc57785df7d460038184d4f239e159 Mon Sep 17 00:00:00 2001 From: acgist <289547414@qq.com> Date: Sat, 4 Feb 2023 16:40:50 +0800 Subject: [PATCH] =?UTF-8?q?[+]=20=E5=A4=A7=E5=B0=8F=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- taoyao-media-server/src/Config.js | 137 ++++++++++++++++++++++++++++++ taoyao-media-server/src/Server.js | 130 ++++++++++++++++++++++++++++ taoyao-media-server/src/Signal.js | 21 +++++ 3 files changed, 288 insertions(+) create mode 100644 taoyao-media-server/src/Config.js create mode 100644 taoyao-media-server/src/Server.js create mode 100644 taoyao-media-server/src/Signal.js diff --git a/taoyao-media-server/src/Config.js b/taoyao-media-server/src/Config.js new file mode 100644 index 0000000..45bec43 --- /dev/null +++ b/taoyao-media-server/src/Config.js @@ -0,0 +1,137 @@ +/** + * 配置 + */ +const os = require("os"); + +module.exports = { + // 系统名称 + name: "taoyao-media-server", + // 交互式命令行 + command: true, + // 信令服务 + https: { + listenIp: "0.0.0.0", + listenPort: process.env.HTTPS_LISTEN_PORT || 4443, + // WebSocket连接密码 + username: 'taoyao', + password: 'taoyao', + tls: { + cert: process.env.HTTPS_CERT_PUBLIC_KEY || `${__dirname}/certs/publicKey.pem`, + key: process.env.HTTPS_CERT_PRIVATE_KEY || `${__dirname}/certs/privateKey.pem`, + } + }, + // Mediasoup + mediasoup: { + // 按照CPU数量配置进程数量 + numWorkers: Object.keys(os.cpus()).length, + // Worker:https://mediasoup.org/documentation/v3/mediasoup/api/#WorkerSettings + workerSettings: { + logLevel: "warn", + logTags: [ + "bwe", + "ice", + "rtp", + "rtx", + "svc", + "dtls", + "info", + "sctp", + "srtp", + "rtcp", + "score", + "message", + "simulcast", + ], + rtcMinPort: process.env.MEDIASOUP_MIN_PORT || 40000, + rtcMaxPort: process.env.MEDIASOUP_MAX_PORT || 49999, + }, + // Router:https://mediasoup.org/documentation/v3/mediasoup/api/#RouterOptions + routerOptions: { + mediaCodecs: [ + { + kind: "audio", + mimeType: "audio/opus", + clockRate: 48000, + channels: 2, + }, + { + kind: "video", + mimeType: "video/VP8", + clockRate: 90000, + parameters: { + "x-google-start-bitrate": 1000, + }, + }, + { + kind: "video", + mimeType: "video/VP9", + clockRate: 90000, + parameters: { + "profile-id": 2, + "x-google-start-bitrate": 1000, + }, + }, + { + kind: "video", + mimeType: "video/h264", + clockRate: 90000, + parameters: { + "packetization-mode": 1, + "profile-level-id": "4d0032", + "level-asymmetry-allowed": 1, + "x-google-start-bitrate": 1000, + }, + }, + { + kind: "video", + mimeType: "video/h264", + clockRate: 90000, + parameters: { + "packetization-mode": 1, + "profile-level-id": "42e01f", + "level-asymmetry-allowed": 1, + "x-google-start-bitrate": 1000, + }, + }, + ], + }, + // WebRtcServer:https://mediasoup.org/documentation/v3/mediasoup/api/#WebRtcServerOptions + webRtcServerOptions: { + listenInfos: [ + { + protocol: "udp", + ip: process.env.MEDIASOUP_LISTEN_IP || "0.0.0.0", + announcedIp: process.env.MEDIASOUP_ANNOUNCED_IP, + port: 44444, + }, + { + protocol: "tcp", + ip: process.env.MEDIASOUP_LISTEN_IP || "0.0.0.0", + announcedIp: process.env.MEDIASOUP_ANNOUNCED_IP, + port: 44444, + }, + ], + }, + // WebRtcTransport:https://mediasoup.org/documentation/v3/mediasoup/api/#WebRtcTransportOptions + webRtcTransportOptions: { + listenIps: [ + { + ip: process.env.MEDIASOUP_LISTEN_IP || "0.0.0.0", + announcedIp: process.env.MEDIASOUP_ANNOUNCED_IP, + }, + ], + initialAvailableOutgoingBitrate: 1000000, + minimumAvailableOutgoingBitrate: 600000, + maxSctpMessageSize: 262144, + maxIncomingBitrate: 1500000, + }, + // PlainTransport:https://mediasoup.org/documentation/v3/mediasoup/api/#PlainTransportOptions + plainTransportOptions: { + listenIp: { + ip: process.env.MEDIASOUP_LISTEN_IP || "0.0.0.0", + announcedIp: process.env.MEDIASOUP_ANNOUNCED_IP, + }, + maxSctpMessageSize: 262144, + }, + }, +}; diff --git a/taoyao-media-server/src/Server.js b/taoyao-media-server/src/Server.js new file mode 100644 index 0000000..1e96a8b --- /dev/null +++ b/taoyao-media-server/src/Server.js @@ -0,0 +1,130 @@ +#!/usr/bin/env node +/** + * 服务 + */ +const fs = require("fs"); +const ws = require("ws"); +const https = require("https"); +const mediasoup = require("mediasoup"); +const config = require("./Config"); +const Logger = require("./Logger"); +const Signal = require("./Signal"); +const command = require("./Command"); + +// 日志 +const logger = new Logger(); +const signal = new Signal(); +// HTTPS server +let httpsServer; +// WebSocket server +let webSocketServer; +// Mediasoup Worker列表 +const mediasoupWorkers = []; +// Mediasoup Worker下个索引 +let nextMediasoupWorkerIndex = 0; + +process.title = config.name; +process.env.DEBUG = process.env.DEBUG || "*mediasoup* *INFO* *WARN* *ERROR*"; +logger.info("开始启动:%s", config.name); + +run(); + +async function run() { + // 启动Mediasoup服务 + await runMediasoupWorkers(); + // 启动服务 + await runSignalServer(); + logger.info("启动完成:%s", config.name); + // 交互式命令行 + if (config.command) { + await command(); + } +} + +async function runMediasoupWorkers() { + const { numWorkers } = config.mediasoup; + logger.info("启动Mediasoup服务(%d Worker)...", numWorkers); + for (let i = 0; i < numWorkers; i++) { + const worker = await mediasoup.createWorker({ + logLevel: config.mediasoup.workerSettings.logLevel, + logTags: config.mediasoup.workerSettings.logTags, + rtcMinPort: Number(config.mediasoup.workerSettings.rtcMinPort), + rtcMaxPort: Number(config.mediasoup.workerSettings.rtcMaxPort), + }); + worker.on("died", () => { + logger.error( + "Mediasoup Worker停止服务(两秒之后自动退出)... [PID:%d]", + worker.pid + ); + setTimeout(() => process.exit(1), 2000); + }); + mediasoupWorkers.push(worker); + // 配置WebRTC服务 + if (process.env.MEDIASOUP_USE_WEBRTC_SERVER !== "false") { + // 每个Worker端口不能相同 + const portIncrement = mediasoupWorkers.length - 1; + const webRtcServerOptions = JSON.parse(JSON.stringify(config.mediasoup.webRtcServerOptions)); + for (const listenInfo of webRtcServerOptions.listenInfos) { + listenInfo.port += portIncrement; + } + const webRtcServer = await worker.createWebRtcServer(webRtcServerOptions); + worker.appData.webRtcServer = webRtcServer; + } + // 记录日志 + setInterval(async () => { + const usage = await worker.getResourceUsage(); + logger.info( + "Mediasoup Worker使用情况 [pid:%d]: %o", + worker.pid, + usage + ); + }, 120 * 1000); + } +} + +async function runSignalServer() { + const tls = { + cert: fs.readFileSync(config.https.tls.cert), + key: fs.readFileSync(config.https.tls.key), + }; + logger.info("配置HTTPS服务..."); + httpsServer = https.createServer(tls, (request, response) => { + response.writeHead(200); + response.end("taoyao media server"); + }); + logger.info("配置WebSocket服务..."); + webSocketServer = new ws.Server({ server: httpsServer }); + webSocketServer.on("connection", (session) => { + session.on("open", (message) => { + logger.info("打开信令通道: %s", message); + }); + session.on("close", (code) => { + logger.info("关闭信令通道: %o", code); + }); + session.on("error", (e) => { + logger.error("信令通道异常: %o", e); + }); + session.on("message", (message) => { + logger.debug("收到信令消息: %s", message); + try { + signal.on(JSON.parse(message), session); + } catch (error) { + logger.error( + `处理信令消息异常: + %s + %o`, + message, + error + ); + } + }); + }); + logger.info("开启服务监听..."); + await new Promise((resolve) => { + httpsServer.listen( + Number(config.https.listenPort), + config.https.listenIp, + resolve + ); + }); +} diff --git a/taoyao-media-server/src/Signal.js b/taoyao-media-server/src/Signal.js new file mode 100644 index 0000000..ff65127 --- /dev/null +++ b/taoyao-media-server/src/Signal.js @@ -0,0 +1,21 @@ +/** + * 信令 + * 1. 终端媒体流向 + * 2. 处理音频视频:降噪、水印等等 + */ + +class Signal { + + /** + * 处理事件 + * + * @param {*} message 消息 + * @param {*} session websocket + */ + on(message, session) { + + } + +} + +module.exports = Signal;