在当今通信技术变得越来越重要的远程世界中,WebRTC 的使用率创下历史新高也就不足为奇了。事实上,WebRTC是开源的,这使得开发者可以构建技术,使数据流变得更容易。其中一项技术是MediaSoup。MediaSoup 是一种媒体服务器,开发人员可以使用它来构建群聊、一对多广播和实时流媒体。
今天,我们将使用 MediaSoup 构建一个基本的语音和聊天应用程序,并解释它是如何完成的。可以在 GitHub 上找到完整的应用程序,其中包括有关如何运行它的说明。在这篇博文中,我们将介绍其工作原理的一些亮点。
什么是 MediaSoup?
MediaSoup 是一个开源的 SFU WebRTC 服务器。可以使用 MediaSoup 中继音频、视频和使用 SCTP 数据通道。它包括一个仅处理低级媒体层的 Node.js 模块,以及几个客户端库。
MediaSoup架构
如果你看一下架构,你会发现传输协议里面有多个层次。有工作者、路由器、传输者、消费者和生产者。
- Worker 是 MediaSoup 负责的单 CPU 线程 C++ 子进程。它们可以包括多个路由器,这些路由器充当 SFU 并负责消费者和生产者之间的媒体传输。
- WebRTC 传输包含消费者或生产者。简而言之,创建生产者是为了向服务器发送数据,而创建消费者是为了从服务器接收数据。在今天的示例应用程序中,假设音频通话中有 N 个参与者可用:总共有 N 个参与者的 N 个生产者。对于每个参与者,将有 N-1 个消费者,因为每个参与者都必须消费其他流。
让我们深入了解示例以获取更多详细信息。
实施我们的示例 MediaSoup 应用程序
该应用程序将是一个教程应用程序,具有一个工作人员、一个路由器和多个用于消费和生产的传输。也将有数据通道生产者和消费者。总的来说,发布看起来像这样:
初始化应用程序
应用程序服务器将是一个快速应用程序,其中套接字将使用 SocketIO 实现,SocketIO 是一个用于实时 Web 应用程序的事件驱动的 JavaScript 库。服务器的初始化如下:
(async () => {
try {
await runExpressApp();
await runWebServer();
await runSocketServer();
await runMediasoupWorker();
} catch (err) {
console.error(err);
}
})();
在这里,我们正在创建服务器并初始化 mediasoup 工作线程。在创建 worker 的同时,我们也在创建路由器,因为教程应用程序不需要多个路由器。
sync function runMediasoupWorker() {
worker = await mediasoup.createWorker({
logLevel: config.mediasoup.worker.logLevel,
logTags: config.mediasoup.worker.logTags,
rtcMinPort: config.mediasoup.worker.rtcMinPort,
rtcMaxPort: config.mediasoup.worker.rtcMaxPort,
});
worker.on('died', () => {
console.error('mediasoup worker died, exiting in 2 seconds... [pid:%d]', worker.pid);
setTimeout(() => process.exit(1), 2000);
});
const mediaCodecs = config.mediasoup.router.mediaCodecs;
mediasoupRouter = await worker.createRouter({ mediaCodecs });
rooms[roomName] = {
router: mediasoupRouter,
}
}
客户端代码将是连接服务器并发送所需命令的基本 SocketIO 客户端。我们将实现源代码中提供的 publish()、subscribe() 方法。
当我们连接到 websocket 时,我们还通过使用getRouterCapabilities消息从应用程序服务器获取 routerRtpCapabilities 来初始化客户端 MediaSoup 操作的设备。应用服务器将从其 router.rtpCapabilities 中提取 RtpCapabilities 。服务器端实现将如下所示:
socket.on('getRouterRtpCapabilities', (data, callback) => {
callback(mediasoupRouter.rtpCapabilities);
});
而客户端加载设备方法如下所示:
async function loadDevice(routerRtpCapabilities) {
try {
device = new mediasoup.Device();
} catch (error) {
if (error.name === 'UnsupportedError') {
console.error('browser not supported');
}
}
await device.load({ routerRtpCapabilities });
}
连接到生产者
初始连接后,要启动生产者,客户端将启动该过程。该过程将包括以下步骤:
- Client.js 向 server.js 发送创建传输的请求
const data = await socket.request('createProducerTransport', {
forceTcp: false,
rtpCapabilities: device.rtpCapabilities,
sctpCapabilities: device.sctpCapabilities,
});
if (data.error) {
console.error(data.error);
return;
}
- 将通过 router.createWebRTCTransport() 在 server.js 上创建一个 webrtcTransport
socket.on('createProducerTransport', async (data, callback) => {
try {
const { transport, params } = await createWebRtcTransport();
//producerTransport = transport;
addTransport(transport, roomName, false, socket.id, false)
callback(params);
} catch (err) {
console.error(err);
callback({ error: err.message });
}
});
- 它将使用 device.createSendTransport()与 client.js 上的响应数据一起复制
const transport = device.createSendTransport({...data, iceServers : [ {
'urls' : 'stun:stun1.l.google.com:19302'
}]});
- Client.js 将订阅连接和生产
- Client.js 将使用 getUserMedia 获取必要的轨道并调用 transport.produce()
- 传输将发出连接和生产事件,并在客户端返回一个生产者实例,该实例将传输数据。
navigator.mediaDevices.getUserMedia(mediaConstraints).then( (stream) => {
const newElem = document.createElement('div')
newElem.setAttribute('id', `localAudio`)
//append to the audio container
newElem.innerHTML = '<audio id="localAudio" autoplay></audio>'
videoContainer.appendChild(newElem)
document.getElementById("localAudio").srcObject = stream;
const track = stream.getAudioTracks()[0];
let params = { track };
params.codecOptions = {
opusStereo: 1,
opusDtx: 1
}
producer = transport.produce(params);
})
连接到消费者
将采取非常相似的步骤来连接到消费者。过程是:
- Client.js 向 server.js 发送 createTransport 请求
- router.createWebRTCTransport() 被调用并将数据发送到 client.js
- device.createReceiveTransport 是在 client.js 上创建的
- 这里与producing的一个关键区别在于,device.rtpParameters应该被发送到server.js,服务器会调用router.canConsume()方法来检查客户端是否可以接收到媒体流。
- 这一次,服务器将调用 transport.consume() 并将其复制到客户端。
数据通道的生产和消费与上述方法没有什么不同。唯一的区别在于方法名称,除了 rtpCapabilities 之外,还应发送 sctpStreamParameters。查看源代码以进一步实现。
结论
MediaSoup 是一个很棒的 SFU,您可以使用其 Node.js 模块轻松地将其集成到您的应用程序中。它是开源的,因此可以大大降低您的项目开支。但是,它确实有一个陡峭的学习曲线。
原文链接:https://webrtc.ventures/2022/05/webrtc-with-mediasoup/
本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/webrtc/15581.html