You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于WebRTC与Socket的Android Java多人视频通话改造咨询

多人WebRTC音视频通话改造代码调整建议

一、信令服务端(Node.js)改造

  • 废弃一对一的点对点信令转发逻辑,改用房间(Room)机制:
    • 新增房间创建、加入、退出接口,服务端维护房间内用户列表
    • 新用户加入时,向房间内所有已有用户推送user-joined事件,同时向新用户返回房间内在线用户列表
    • 信令消息(offer/answer/iceCandidate)需携带房间ID和目标用户ID,服务端根据标识精准转发
  • 核心代码片段:
    // 房间存储结构
    const rooms = new Map(); // key: roomId, value: { users: Map(userId, socket) }
    
    // 处理用户加入房间
    socket.on('join-room', (roomId, userId) => {
      if (!rooms.has(roomId)) {
        rooms.set(roomId, { users: new Map() });
      }
      const room = rooms.get(roomId);
      // 通知老用户新成员加入
      room.users.forEach((userSocket) => {
        userSocket.emit('user-joined', userId);
      });
      // 向新用户推送已有成员列表
      socket.emit('existing-users', Array.from(room.users.keys()));
      room.users.set(userId, socket);
      
      // 处理用户断开
      socket.on('disconnect', () => {
        room.users.delete(userId);
        room.users.forEach((userSocket) => {
          userSocket.emit('user-left', userId);
        });
        if (room.users.size === 0) rooms.delete(roomId);
      });
    });
    
    // 转发信令消息
    socket.on('signal', (roomId, targetUserId, data) => {
      const room = rooms.get(roomId);
      if (room?.users.has(targetUserId)) {
        room.users.get(targetUserId).emit('signal', socket.id, data);
      }
    });
    

二、Android客户端(Java)改造

1. 多PeerConnection实例管理

  • 用Map<String, PeerConnection>替代单一PeerConnection对象,key为对方用户ID,value为对应实例
  • 收到服务端existing-users事件时,为每个老用户创建PeerConnection并发起offer;收到user-joined事件时,为新用户创建PeerConnection并处理信令交互
  • 核心代码片段:
    private Map<String, PeerConnection> peerConnections = new HashMap<>();
    private PeerConnectionFactory peerConnectionFactory;
    
    // 初始化PeerConnectionFactory(复用一对一逻辑)
    private void initPeerConnectionFactory(Context context, EglBase eglBase) {
      PeerConnectionFactory.initialize(
        PeerConnectionFactory.InitializationOptions.builder(context).createInitializationOptions());
      peerConnectionFactory = PeerConnectionFactory.builder()
        .setVideoDecoderFactory(new DefaultVideoDecoderFactory(eglBase.getEglBaseContext()))
        .setVideoEncoderFactory(new DefaultVideoEncoderFactory(eglBase.getEglBaseContext(), true, true))
        .createPeerConnectionFactory();
    }
    
    // 为指定用户创建PeerConnection
    private PeerConnection createPeerConnection(String userId, EglBase eglBase) {
      List<IceServer> iceServers = Collections.singletonList(new IceServer.Builder("stun:stun.l.google.com:19302").createIceServer());
      PeerConnection.RTCConfiguration rtcConfig = new PeerConnection.RTCConfiguration(iceServers);
      PeerConnection peerConnection = peerConnectionFactory.createPeerConnection(rtcConfig, new PeerConnectionObserver(userId));
      peerConnections.put(userId, peerConnection);
      // 复用本地音视频轨道
      localAudioTrack.addSink(peerConnection);
      localVideoTrack.addSink(peerConnection);
      return peerConnection;
    }
    
    // 处理新用户加入
    private void handleUserJoined(String userId, EglBase eglBase) {
      PeerConnection peerConnection = createPeerConnection(userId, eglBase);
      // 发起offer
      peerConnection.createOffer(new SdpObserver() {
        @Override
        public void onCreateSuccess(SessionDescription sdp) {
          peerConnection.setLocalDescription(new SdpObserver() {}, sdp);
          // 发送信令到服务端
          socket.emit("signal", roomId, userId, new JSONObject().put("type", "offer").put("sdp", sdp.description));
        }
        // 其他回调实现略
      }, new MediaConstraints());
    }
    
    // 处理收到的信令
    private void handleSignal(String fromUserId, JSONObject data) throws JSONException {
      PeerConnection peerConnection = peerConnections.get(fromUserId);
      if (peerConnection == null) {
        peerConnection = createPeerConnection(fromUserId, eglBase);
      }
      String type = data.getString("type");
      SessionDescription.Type sdpType = "offer".equals(type) ? SessionDescription.Type.OFFER : SessionDescription.Type.ANSWER;
      if ("offer".equals(type) || "answer".equals(type)) {
        peerConnection.setRemoteDescription(new SdpObserver() {}, new SessionDescription(sdpType, data.getString("sdp")));
        if ("offer".equals(type)) {
          // 创建answer并发送
          peerConnection.createAnswer(new SdpObserver() {
            @Override
            public void onCreateSuccess(SessionDescription sdp) {
              peerConnection.setLocalDescription(new SdpObserver() {}, sdp);
              try {
                socket.emit("signal", roomId, fromUserId, new JSONObject().put("type", "answer").put("sdp", sdp.description));
              } catch (JSONException e) {
                e.printStackTrace();
              }
            }
            // 其他回调实现略
          }, new MediaConstraints());
        }
      } else if ("iceCandidate".equals(type)) {
        IceCandidate candidate = new IceCandidate(
          data.getString("sdpMid"),
          data.getInt("sdpMLineIndex"),
          data.getString("candidate")
        );
        peerConnection.addIceCandidate(candidate);
      }
    }
    

2. 多视频流渲染管理

  • 动态创建SurfaceViewRenderer渲染每个远端视频流,用户离开时移除视图并释放资源
  • 核心代码片段:
    private Map<String, SurfaceViewRenderer> renderers = new HashMap<>();
    private LinearLayout videoContainer; // 布局容器
    
    // 添加远端视频渲染视图
    private void addRemoteVideoRenderer(String userId, VideoTrack videoTrack, EglBase eglBase) {
      SurfaceViewRenderer renderer = new SurfaceViewRenderer(context);
      renderer.init(eglBase.getEglBaseContext(), null);
      videoContainer.addView(renderer);
      videoTrack.addSink(renderer);
      renderers.put(userId, renderer);
    }
    
    // 处理用户离开
    private void handleUserLeft(String userId) {
      // 释放PeerConnection
      PeerConnection peerConnection = peerConnections.remove(userId);
      if (peerConnection != null) peerConnection.close();
      // 移除渲染视图
      SurfaceViewRenderer renderer = renderers.remove(userId);
      if (renderer != null) {
        videoContainer.removeView(renderer);
        renderer.release();
      }
    }
    

3. 音视频轨道复用

  • 复用同一套AudioTrack和VideoTrack,添加到所有PeerConnection实例中,避免重复初始化设备导致资源冲突

三、关键注意事项

  • ICE候选转发:每个PeerConnection的ICE候选需单独发送给对应目标用户,服务端需精准转发
  • 带宽优化:通过RTCConfiguration设置带宽限制,或启用Simulcast分层编码,缓解多人通话的带宽压力
  • 状态同步:服务端需确保房间用户状态实时同步,避免客户端保留无效的PeerConnection实例
  • 错误处理:新增PeerConnection创建失败、信令发送失败等场景的异常处理,防止单个用户问题影响全局通话

内容的提问来源于stack exchange,提问作者Bhaskar Jha

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 19:02:18