Skip to content

Mediasoup의 연결 방식에 대해서 알아보자

Seungheon Han edited this page Dec 3, 2024 · 1 revision

1. WebSocket을 통해 서버에 연결 Mediasoup 클라이언트는 일반적으로 [Socket.io](http://socket.io/)를 사용해 Mediasoup 서버와 WebSocket 연결을 설정합니다. 이 WebSocket은 Mediasoup의 signaling 역할을 수행하며, 클라이언트가 서버에 이벤트를 전송하고 서버가 이를 처리하여 필요한 Mediasoup 객체들을 설정합니다.

const socket = io('<http://localhost:3000>'); // 서버 주소에 맞게 설정

2. 서버의 RTP Capabilities 요청 서버와 연결이 되면 클라이언트는 RTP Capabilities를 요청합니다. 이 정보는 서버에서 지원하는 코덱, 대역폭 등을 포함하고 있으며, 클라이언트의 WebRTC Device를 초기화하는 데 필요합니다.

// 클라이언트가 서버에 RTP Capabilities를 요청
socket.emit('getRtpCapabilities');

서버 측 처리 (getRtpCapabilities 이벤트 핸들러)


@SubscribeMessage('getRtpCapabilities')
handleGetRtpCapabilities(client: Socket) {
  const rtpCapabilities = this.webRtcGateway.router.rtpCapabilities;
  client.emit('rtpCapabilities', rtpCapabilities); // 클라이언트에 rtpCapabilities 전송
}

클라이언트 측 처리 서버로부터 rtpCapabilities를 받으면, Mediasoup 클라이언트의 Device 객체를 초기화합니다.

socket.on('rtpCapabilities', async (rtpCapabilities) => {
  await device.load({ routerRtpCapabilities: rtpCapabilities });
});

3. Transport 생성 요청 클라이언트가 미디어를 송출하거나 수신하기 위해서는 Transport가 필요합니다. 클라이언트는 서버에 Transport 생성을 요청하고, 서버는 Transport를 생성하여 필요한 정보(ICE 후보, DTLS 파라미터 등)를 클라이언트에 반환합니다.

// 클라이언트가 서버에 Transport 생성 요청
socket.emit('createTransport');

서버 측 처리 (createTransport 이벤트 핸들러)

@SubscribeMessage('createTransport')
async handleCreateTransport(client: Socket) {
  const transport = await this.webRtcGateway.createWebRtcTransport();
  client.emit('transportCreated', {
    id: transport.id,
    iceParameters: transport.iceParameters,
    iceCandidates: transport.iceCandidates,
    dtlsParameters: transport.dtlsParameters,
  });
}

클라이언트 측 처리 서버가 생성한 Transport의 정보를 받아, 클라이언트는 이를 사용하여 Transport를 설정합니다.

socket.on('transportCreated', async (data) => {
  sendTransport = device.createSendTransport(data);
});

4. Transport 연결 설정 (DTLS 협상) 클라이언트의 Transport는 서버와 DTLS(Datagram Transport Layer Security) 설정을 통해 보안 연결을 설정해야 합니다. 클라이언트는 이 설정을 서버에 전달하고, 서버는 이를 확인하여 연결을 확립합니다.

sendTransport.on('connect', ({ dtlsParameters }, callback) => {
  socket.emit('connectTransport', {
    transportId: sendTransport.id,
    dtlsParameters,
  });
  callback();
});

서버 측 처리 (connectTransport 이벤트 핸들러)

@SubscribeMessage('connectTransport')
async handleConnectTransport(
  client: Socket,
  payload: { transportId: string; dtlsParameters: any }
) {
  const transport = this.webRtcGateway.transports.find(
    (t) => t.id === payload.transportId
  );
  if (transport) {
    await transport.connect({ dtlsParameters: payload.dtlsParameters });
    client.emit('transportConnected', { transportId: payload.transportId });
  }
}

5. 클라이언트의 미디어 송출 (Produce) Transport가 연결되면, 클라이언트는 자신의 비디오 및 오디오 스트림을 Producer로 생성하여 서버로 송출할 수 있습니다. 이 과정에서 클라이언트는 미디어 track과 관련된 RTP 파라미터를 포함하여 서버에 produce 요청을 보냅니다.

// 비디오와 오디오 Producer 생성 및 서버로 송출
const videoTrack = stream.getVideoTracks()[0];
await sendTransport.produce({ track: videoTrack });
const audioTrack = stream.getAudioTracks()[0];
await sendTransport.produce({ track: audioTrack });

서버 측 처리 (produce 이벤트 핸들러)

@SubscribeMessage('produce')
async handleProduce(
  client: Socket,
  payload: { transportId: string; kind: 'audio' | 'video'; rtpParameters: any }
) {
  const transport = this.webRtcGateway.transports.find(
    (t) => t.id === payload.transportId
  );
  if (transport) {
    const producer = await this.webRtcGateway.createProducer(
      transport,
      payload.kind,
      payload.rtpParameters
    );
    client.emit('producerCreated', { producerId: producer.id });
  }
}

6. 다른 참여자의 미디어 수신 (Consume) 클라이언트가 다른 참여자의 스트림을 수신하려면 Consumer를 생성해야 합니다. 클라이언트는 서버에 consume 요청을 보내고, 서버는 해당 Producer와 Consumer 간의 연결을 설정하여 미디어를 수신할 수 있게 합니다.

// 서버에 Consumer 생성 요청
socket.emit('consume', {
  transportId: receiveTransport.id,
  producerId: remoteProducerId,
  rtpCapabilities: device.rtpCapabilities,
});

서버 측 처리 (consume 이벤트 핸들러)

@SubscribeMessage('consume')
async handleConsume(
  client: Socket,
  payload: { transportId: string; producerId: string; rtpCapabilities: RtpCapabilities }
) {
  const transport = this.webRtcGateway.transports.find(
    (t) => t.id === payload.transportId
  );
  if (transport) {
    const consumer = await this.webRtcGateway.createConsumer(
      transport,
      payload.producerId,
      payload.rtpCapabilities
    );
    if (consumer) {
      client.emit('consumerCreated', {
        id: consumer.id,
        producerId: consumer.producerId,
        kind: consumer.kind,
        rtpParameters: consumer.rtpParameters,
      });
    }
  }
}

7. 클라이언트의 Consumer 설정 서버로부터 Consumer가 생성된 정보와 RTP 파라미터를 수신한 후, 클라이언트는 이를 통해 다른 참여자의 미디어 스트림을 수신하고, 이를 자신의 비디오 및 오디오 요소에 출력할 수 있습니다.

socket.on('consumerCreated', async (data) => {
  const consumer = await receiveTransport.consume({
    id: data.id,
    producerId: data.producerId,
    kind: data.kind,
    rtpParameters: data.rtpParameters,
  });

  const mediaStream = new MediaStream();
  mediaStream.addTrack(consumer.track);
  if (consumer.kind === 'video') {
    document.getElementById('remoteVideo').srcObject = mediaStream;
  } else if (consumer.kind === 'audio') {
    document.getElementById('remoteAudio').srcObject = mediaStream;
  }
});

요약 Mediasoup 클라이언트와 서버 간의 연결 과정은 다음과 같습니다.

  1. 서버와 WebSocket 연결: Socket.io를 통해 signaling 연결 설정.
  2. 서버의 RTP Capabilities 요청: 클라이언트가 서버에서 지원하는 코덱 정보를 요청하고, 이를 통해 Device 초기화.
  3. Transport 생성: 클라이언트와 서버가 미디어 데이터를 주고받기 위한 Transport를 생성.
  4. Transport 연결 설정: DTLS 협상을 통해 보안 연결 설정.
  5. 미디어 송출 (Produce): 클라이언트의 오디오와 비디오 스트림을 서버로 전송.
  6. 미디어 수신 (Consume): 다른 참여자의 미디어를 수신할 수 있도록 Consumer 생성.
  7. 미디어 표시: 수신한 미디어 스트림을 클라이언트의 비디오/오디오 요소에 연결하여 표시.

이 과정을 통해 Mediasoup 클라이언트와 서버 간에 WebRTC를 이용한 실시간 미디어 스트리밍이 가능합니다.

공부하면서 궁금했던거

RTP Capabilities??

RTP Capabilities는 Mediasoup에서 미디어 코덱과 전송 설정을 정의하는 데 필요한 정보입니다. 클라이언트와 서버가 서로 미디어 데이터를 전송하려면, 서로 호환 가능한 코덱과 네트워크 설정이 필요합니다. RTP Capabilities는 클라이언트와 서버가 사용할 수 있는 코덱, 미디어 형식, 대역폭 등을 포함한 설정 정보를 의미합니다.

  • 주요 항목: 오디오 및 비디오 코덱의 종류, 샘플링 레이트, 대역폭, 채널 수 등.
  • 역할: 클라이언트와 서버가 동일한 RTP Capabilities를 공유해야 서로 호환 가능한 방식으로 미디어 데이터를 전송하고 수신할 수 있습니다.
  • 사용 예: 클라이언트가 서버에 getRtpCapabilities 요청을 보내면, 서버는 자신이 지원하는 RTP Capabilities 정보를 반환합니다. 이를 통해 클라이언트는 자신의 Device 객체를 초기화할 수 있습니다.

Device는 뭐지?

Device는 Mediasoup 클라이언트에서 WebRTC 연결을 설정하기 위해 사용되는 객체입니다. Mediasoup의 mediasoup-client 라이브러리에서 제공하는 객체로, 클라이언트가 Mediasoup 서버와 통신하기 위해 필요한 모든 정보를 가지고 있습니다.

  • 역할: 클라이언트의 Device는 Mediasoup 서버와 연결을 설정하는 데 필요한 RTP Capabilities 정보를 로드하고, 서버가 제공하는 설정에 맞춰 클라이언트 측에서 필요한 설정을 구성합니다.
  • 초기화 과정: 서버에서 받은 RTP Capabilities를 통해 Device.load() 메서드를 호출하여 초기화합니다. 초기화가 완료되면, 이후에 Transport, Producer, Consumer를 생성하고 서버와의 미디어 연결을 설정할 수 있습니다.

Transport는 뭐야? 그리고 연결마다 하나씩 만들어줘야 하는건가?

Transport는 Mediasoup에서 미디어 데이터가 전송되는 경로를 의미합니다. WebRTC에서 미디어 데이터를 송수신하기 위해 사용하는 전송 통로이며, Mediasoup에서는 WebRtcTransport와 PlainTransport 등 다양한 종류가 있습니다.

  • WebRtcTransport: 클라이언트(브라우저)와 Mediasoup 서버 간의 WebRTC 기반 전송에 사용됩니다. DTLS와 SRTP를 사용하여 보안 연결을 설정합니다.
  • PlainTransport: WebRTC 외의 다른 도구(FFmpeg, GStreamer 등)와의 연결에 사용되며, SRTP 또는 일반 RTP 전송이 가능합니다.
  • 일반적으로 각 클라이언트가 연결을 설정할 때마다 Transport를 생성해야 합니다. Mediasoup에서 클라이언트는 오디오와 비디오 같은 미디어 데이터를 서버로 송출하거나 서버에서 데이터를 수신할 때, 각각의 전송 방향에 맞는 Transport를 사용해야 합니다.
  • 예를 들어, 한 클라이언트가 서버로 비디오를 송출하는 용도의 SendTransport와 다른 클라이언트의 비디오를 수신하는 용도의 ReceiveTransport를 별도로 생성합니다.

dtlsParameters?

DTLS Parameters는 WebRTC에서 보안 연결을 설정하기 위한 DTLS (Datagram Transport Layer Security) 설정 정보입니다. DTLS는 WebRTC에서 미디어 데이터를 암호화하여 안전하게 전송하기 위해 사용됩니다.

  • 역할: 클라이언트와 서버가 DTLS Parameters를 교환하여 DTLS 핸드셰이크를 수행하고, 이를 통해 SRTP(Secure RTP) 연결을 설정하여 보안된 미디어 전송이 가능합니다.
  • 주요 항목: DTLS 롤(role) 및 핑거프린트(암호화에 필요한 인증서 정보 등).

Transport 연결 설정 과정에서 정확하게 하는 일이 뭘까?

Transport 연결 설정 과정에서 실제로 이루어지는 일은 DTLS를 통해 클라이언트와 서버 간의 보안 연결을 설정하는 것입니다. 이 과정은 WebRTC의 필수 단계이며, Mediasoup에서는 클라이언트와 서버가 서로 DTLS Parameters를 교환하여 수행됩니다.

👥 팀 강점

🧑‍💻 개발 일지

📌 ALL

📌 FE

📌 BE

💥 트러블 슈팅

📌 FE

📌 BE

🤔 고민

📚 학습 정리

📌 김광현

📌 백지연

📌 전희선

📌 한승헌

🤝 회의록

🗒️ 데일리 스크럼

💬 팀 회고


👨‍👩‍👧‍👦 소개

🌱 문화

🔨 기술 스택

⚙️ 서비스 아키텍쳐

🚧 CI/CD

🌊 Flow

💭 6주를 보내면서

Clone this wiki locally