使用 MediaSoup 构建 WebRTC 语音和聊天应用程序

在当今通信技术变得越来越重要的远程世界中,WebRTC 的使用率创下历史新高也就不足为奇了。事实上,WebRTC是开源的,这使得开发者可以构建技术,使数据流变得更容易。其中一项技术是MediaSoup。MediaSoup 是一种媒体服务器,开发人员可以使用它来构建群聊、一对多广播和实时流媒体。 

今天,我们将使用 MediaSoup 构建一个基本的语音和聊天应用程序,并解释它是如何完成的。可以在 GitHub 上找到完整的应用程序,其中包括有关如何运行它的说明。在这篇博文中,我们将介绍其工作原理的一些亮点。

什么是 MediaSoup?

MediaSoup 是一个开源的 SFU WebRTC 服务器。可以使用 MediaSoup 中继音频、视频和使用 SCTP 数据通道。它包括一个仅处理低级媒体层的 Node.js 模块,以及几个客户端库。 

MediaSoup架构

使用 MediaSoup 构建 WebRTC 语音和聊天应用程序
MediaSoup架构

如果你看一下架构,你会发现传输协议里面有多个层次。有工作者、路由器、传输者、消费者和生产者。

  • Worker 是 MediaSoup 负责的单 CPU 线程 C++ 子进程。它们可以包括多个路由器,这些路由器充当 SFU 并负责消费者和生产者之间的媒体传输。 
  • WebRTC 传输包含消费者或生产者。简而言之,创建生产者是为了向服务器发送数据,而创建消费者是为了从服务器接收数据。在今天的示例应用程序中,假设音频通话中有 N 个参与者可用:总共有 N 个参与者的 N 个生产者。对于每个参与者,将有 N-1 个消费者,因为每个参与者都必须消费其他流。 

让我们深入了解示例以获取更多详细信息。

实施我们的示例 MediaSoup 应用程序

该应用程序将是一个教程应用程序,具有一个工作人员、一个路由器和多个用于消费和生产的传输。也将有数据通道生产者和消费者。总的来说,发布看起来像这样:

使用 MediaSoup 构建 WebRTC 语音和聊天应用程序
应用发布图

初始化应用程序

应用程序服务器将是一个快速应用程序,其中套接字将使用 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 });
     }
   });
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

(0)

相关推荐

发表回复

登录后才能评论