webrtc之Mesh方案实现多人音视频

274 阅读5分钟

上一篇分析了多人音视频的实现方案,其中最简单、最容易实现的就是Mesh方案了。 Mesh方案的实现思路是每个用户都与其他用户建立连接,在客户端直接传输媒体流,不走服务器。具有低延迟、画质高、难度低的优势。当然在4-5个人之内能很好的控制,再多的话带宽成本就很高了。下面就来看看Mesh方案是如何实现多人音视频的。

注意:该小节是在一对多的基础上升级的!!

分析

上一篇就说过,它的实现方案就是一对多直播模式的升级版,即每个人都是主播,都与其他用户建立起连接。那么应该如何实现呢?很简单,当一个用户进入到房间后,就将这个用户打上newUser的标识。然后房间内的已有用户获取到最新的列表,找出那个带有newUser的用户并主动建立连接。而那个新用户则啥也不用干,等着被连接就行。然后,当又有一个新的用户进来,也加上newUser,并将之前的新用户去掉newUser标识。如此,便实现了每个用户都能相互建立连接且只连接一次。

注意,当两个用户建立两个连接时是会报错的,虽然看起来不会影响使用,但后续实现其他功能时就会很难搞。

OK,根据上面描述的过程,来验证上一篇说的连接次数。

  • 当A进入,只有一个人不操作。
  • 当B再进入,A则与B连接,1次。
  • 当C再进入,AB都与C连接,2次。加上之前的1次,共3次。
  • 当D再进入,ABC都与D连接,3次。加上之前的3次,共6次。

上一篇说的连接次数如下:

  1. 当只有A和B,1 次
  2. 当有ABC三个人,2 + 1 = 3 次
  3. 当有ABCD四个人,3 + 2 + 1 = 6 次
  4. 当有ABCDE五个人, 4 + 3 + 2 + 1 = 10 次
  5. 当有N个人, 有 N * (N - 1) / 2 次

这样是不是就对上了。可以很明显的看到当人数增多时,连接次数几乎是垂直增长。它的增长图如下,可以直观的感受一下:

39.png

所以,该方案的优势和劣势都非常明显。简单高效,但只限于4-5人。

实操

上面说了它是一对多直播模式的升级版,所以代码也是在一对多的基础上进行简单改动即可。

其实只需要改动两个地方即可,一个是获取用户列表的地方,另一个是连接发送offer的地方,代码如下:

这个是在获取用户列表的地方改动

// 一对多
linkSocket.value.on("roomUserList", async (data) => {
  roomUserList.value = data;

  if (roomUserList.value.length) {
    await initMeetingRoomPc();
    initDanmuContainer();
  }
});


// ------------------------------------------------------


// 多对多
linkSocket.value.on("roomUserList", (data) => {
  roomUserList.value = data.filter((e) => e.userId !== String(userId));
  console.log("房间成员列表", data);

  // 拿到房间成员列表后 初始化pc
  newUser.value = data.find((e) => e.isNew) ?? undefined; // 找到新用户

  // 如果新用户是自己 则啥也不用干 等着被连接就行
  // 不是自己 则要去连接新用户的pc
  if (newUser.value?.userId !== userId) {
    initMeetingRoomPc();
  }
});

这个是连接时发送offer的地方改动

// 一对多
const initMeetingRoomPc = async () => {
  if (userInfo.pub) {
    localStream.value = await getLocalUserMedia({ video: true, audio: true });
    //将本地直播流挂到video标签,在自己的页面显示
    setDomVideoStream("video", localStream.value);
  }

  const localUserId = userInfo.userId;
  // 找到当前房间的视频流发布者,即主播
  const pub = roomUserList.value.find((item) => item.pub === "pub");

  if (!pub) {
    return;
  }

  // 如果是自己 且 自己是主播 则直接返回
  if (pub.userId === localUserId) {
    return;
  }

  publisher.value = pub;

  // 和发布者建立rtc连接 不发送自己的视频流
  const pcKey = localUserId + "-" + publisher.value.userId;
  let pc = RtcPcMaps.get(pcKey);
  if (!pc) {
    pc = new PeerConnection();
    RtcPcMaps.set(pcKey, pc);
  }

  console.log(RtcPcMaps, "RtcPcMaps-------------");

  // sendrecv 表示发送和接收都开启 sendonly 表示只发送不接收 recvonly 表示只接收不发送
  pc.addTransceiver("audio", { direction: "recvonly" });
  pc.addTransceiver("video", { direction: "recvonly" });

  onPcEvent(pc, localUserId, publisher.value.userId);

  // 创建数据通道
  await createDataChannels(pc, localUserId, publisher.value.userId);

  // 创建offer
  const offer = await pc.createOffer();
  // 设置offer为本地描述
  await pc.setLocalDescription(offer);
  // 发送offer给远端
  const params = {
    userId: localUserId,
    targetUserId: publisher.value.userId,
    offer,
  };
  linkSocket.value.emit("offer", params);
};


// ------------------------------------------------------


// 多对多
const initMeetingRoomPc = async () => {
  if (!newUser.value) return;
  const localUserId = props.userId;
  const NUser = newUser.value;

  const pcKey = localUserId + "-" + NUser?.userId;
  let pc = RtcPcMaps.get(pcKey);
  if (!pc) {
    pc = new PeerConnection();
    RtcPcMaps.set(pcKey, pc);
  }

  console.log(RtcPcMaps, "RtcPcMaps-----------------");

  for (const track of localStream.value.getTracks()) {
    const sender = pc.getSenders().find((s) => s.track === track);
    if (sender) {
      // 如果发送器已经存在,则更新发送器的轨道
      sender.replaceTrack(track);
    } else {
      // 否则,添加新的轨道
      pc.addTrack(track);
    }
  }

  onPcEvent(pc, localUserId, NUser?.userId);
  // 创建offer
  const offer = await pc.createOffer();
  // 设置本地描述
  await pc.setLocalDescription(offer);
  // 发送offer给被呼叫端
  linkSocket.value.emit("offer", {
    offer,
    targetUserId: NUser?.userId,
    userId: localUserId,
  });
};

仔细对比看看,其实改动很小。看多对多的initMeetingRoomPc函数,其实就是一对一里面的initCallerInfo,忘记了的话可以回头看看。

一对多的本质上还是一对一,只是连接次数变了,变成了一个人连接多个人。然后多对多在一对多的基础上升级,变成多个一对多,但本质上还是一对一。所以,不管是一对多还是多对多,连接方式都还是一对一,代码都没啥区别。主要是连接次数的变化,一对多是 N 次,多对多是 N * (N - 1) / 2 次。

小节

本小节介绍了如何使用Mesh方案来实现多人音视频通话。该方案的优点是延迟低、画质好、数据传输全在客户端,不经过服务器。但缺点也很明显,只限于4-5个人,当人数过多时,成本会飞速上升。然后分析了如何实现,以及它和一对多音视频、一对一音视频的相同点和不同点,最后给出了具体实现代码。

下一小节内容还没想好,等周末再更新了0.0