[WebRTC] KMS 기반 SFU 그룹 콜 로직 정리

BigChoi·2024년 3월 18일
0

WebRTC

목록 보기
2/2

WebRTC 예제를 만든지 어언 2년이 지났다.

업무로 바쁜시기를 보내기도 했고, 나름 2년동안 뒹굴면서
'개발의 지식이 조금을 올라가지 않았을까?' 하는 기대감 + ChatGPT의 성능 향상이 되어 감에 따라 GPT의 도움을 조금 받으면
2년 전에는 구축하지 못한 KMS를 구축할 수 있지 않을까 하는 생각에 공부를 시작했다.

약 2주간 필자가 스터디한 내용을 모두 정리해보고자 한다.

WebRTC는 무엇인가?

WebRTC. 개발자라면 한 번쯤은 들어봤을 단어이다.
WebRTC는 Web Real-Time Communications의 약자로, 별도의 소프트웨어 없이 음성 및 영상과 같은 미디어 스트림을 교환하거나, 텍스트나 파일과 같은 데이터를 브라우저끼리 주고 받을 수 있게 만든 기술이다. 이는 기본적으로 P2P통신방식으로 이루어진다.

위와 같이 두 개의 브라우저가 서로 WebRTC로 연결하기 위해서는 실시간 시그널링을 위해 socket 서버가 필요하다.
socket 서버를 통해 SDP와 ICE를 교환이 이루어지고 연결이 성공적으로 이루어지면 각 브라우저가 가지고 있는 미디어 스트림을 주고받을 수 있게 된다.

1:1 WebRTC의 기본적인 로직은 아래와 같이 이루어진다.

(1개의 커넥션 기준)
1. A 클라이언트는 브라우저의 미디어 스트림을 획득하고, 새로운 peerConnection을 생성한다.(이하 peerConnection을 peer로 약칭한다.)
2. peer에 획득한 미디어 스트림을 추가하고, offer를 생성하고, localDescription로 설정한다.
3. 생성한 offer를 B 클라이언트에게 보낸다.
4. B 클라이언트 또한 브라우저의 미디어 스트림을 획득하고, 새로운 peer를 생성한다.
5. peer에 획득한 미디어 스트림을 추가하고, A 클라이언트에서 전달받은 offer를 setRemoteDescription한다.
6. B 클라이언트는 answer을 생성하고, localDescription로 설정한 뒤 Answer을 A 클라이언트에게 전달한다.
7. A 클라이언트와 B 클라이언트 모두 SDP(offer/answer)를 설정하고 나면 candidate 후보를 주고 받으며, candidate 후보를 등록하고 나면 연결이 성공적으로 이루어 진다.

단순해보이지만 복잡한 과정, 복잡해보이지만 단순학 과정이 모두 성공적으로 이루어지면 Peer를 통해 서로의 미디어 스트림을 교환하거나 텍스트나 파일과 같은 데이터를 실시간으로 주고받을 수 있게 된다.

위의 과정을 최대한 단순한 절차로 정리해보았지만, 연결이 수립되는 과정이 비동기로 이루어지기 때문에, 실제로 개발을 하다보면 복잡해보이는 것도 같다.

WebRTC의 다양한 구현방법 (Mesh, SFU, MCU)

WebRTC 구현 방법에 따라 Mesh/ MCU / SFU로 나뉜다.

Mesh

위의 목차에서 설명한 프로세스가 기본적은 Mesh 방식이다. 브라우저끼리 시그널링을 통해 peer연결을 수리하게 된다.
예를 들어 A, B 클라이언트 2명이 Mesh 방식으로 WebRTC를 구현한다면,

A의 클라이언트는 1개의 uplink와 1개의 downlink를 가지게 된다.

A, B, C 클라이언트 3명이 Mesh방식으로 WebRTC를 구현한다면,

A는 B와 C 클라이언트에게 보낼 2개의 uplink를, B와 C의 클라이언트에게서 받을 2개의 downlink를 갖게 된다.

이렇게 되면 n명이 Mesh 방식으로 WebRTC를 구현한다면 (n-1) * 2의 peer를 가지게 된다.

MCU

MCU와 SFU방식의 구현은 별도의 미디어 서버가 필수적이다.
미디어 서버 또한 여러 종류가 있지만 필자는 KMS(kurento Media Server)를 사용해서 구축하였다.

MCU에 대해 이어서 얘기하자면, 기본적으로 하나의 uplink와 하나의 downlink를 갖는다는 특징이 있다.

n명이 MCU 방식으로 WebRTC를 구현한다면 2개의 peer를 가지게 된다.

미디어 서버를 통해 uplink와 downlink를 관리하므로 비료적 클라이언트의 부하가 낮아진다는 장점이 있지만
자연스럽게 미디어 서버의 부하가 커지게 된다. 또한 미디어 서버 내에서 모든 미디어 스트림을 1차적으로 가공 후에 전송되기 때문에 실시간성 Mesh에 비해 떨어지기는 한다.

SFU

SFU는 하나의 uplink를 가지는 것은 동일하지만, downlink가 n-1개가 필요하게 된다.

클라이언트와 서버의 부하를 적절하게 나누고 있지만, 여전히 미디어 서버에서 부하를 부담한다는 이슈가 있다.

Mesh 방식을 제외한 MCU와 SFU방식은 당연하게도 높은 CPU의 컴퓨터가 필요로 하게 되고, 로드밸런싱, Redis와 같은 기술들을 추가적으로 요구하게 된다.

그래서 SFU 서버는 어떻게?

필자는 docker image를 이용해서 KMS를 구축했다.

//docker-compose.yml

version: '3.3'

services:
  kurento-media-server:
    image: kurento/kurento-media-server:7.0
    container_name: kurento-media-server
    ports:
      - "8888:8888" # Kurento Media Server의 기본 WebSocket 포트
    environment:
      - KMS_MIN_PORT=49152
      - KMS_MAX_PORT=65535
      - KMS_STUN_IP=[STUN 서버의 아이피]
      - KMS_STUN_PORT=[STUN 서버의 포트]
      - KMS_TURN_URL=[TURN 서버의 접속정보]
    volumes:
      - ./recordings:/var/lib/kurento/recordings # 녹화 파일 저장 경로
    restart: unless-stopped
    networks:
      - kurento-network
networks:
  kurento-network:
    external: true

도커의 여러 서비스를 등록했지만, KMS 세팅만 보자면 위와 같다.
STUN서버와 TURN url을 기입하고 docker-compose 파일을 실행시키면 된다.

$ docker compose up -d

정상적으로 세팅했다면 도커 컨테이너 로그에서 Kurento Media Server started 라는 문구를 볼 수 있다!

서버 실행과 별개로 nodejs로 js 파일을 실행해서 접속이 원활하게 이루어지는지도 확인할 수 있다.

// test-kms-connection.js

const WebSocket = require('ws');

const ws = new WebSocket('ws://kurento-media-server:8888/kurento');

ws.on('open', function open() {
  console.log('Connected to KMS');
  ws.close();
});

ws.on('error', function error(err) {
  console.error('Failed to connect to KMS:', err);
});

연결이 잘 되지 않는 것 같으면 위의 코드를 node환경에서 실행해보자!

접속이 원할하게 이루어지면 이제 로직은 생각하며, 개발을 진행하면 된다.

SFU => 1개의 uplink n-1개의 downlink

KMS에는 여러가지 구성요소가 있지만 여기서 다뤄볼 구성요소는 kurento client와 media pipeline webrtc endpoint 3가지이다.

Kurento Client

Kurento Client는 KMS의 API를 통해 서버와 통신하는 클라이언트 라이브러리이다. 필자는 node 기반 socket 서버를 구축하여 사용 중이므로
npm에서 kurento-client를 설치해서 사용하였다.

글을 작성하는 기준 7.0.0버전의 docker 이미지를 사용하고 있기 때문에 client 또한 KMS의 버전에 맞춰 설치하였다.

npm install kurento-client@7.0.0

kurento client를 생성하여 API에 맞게 미디어 파이프라인을 생성하고, 미디어 요소를 구성하며, 미디어 스트림의 흐름을 제어할 수 있다.

Media Pipeline

Media Pipeline은 미디어 데이터의 흐름을 정의하는 KMS의 추상적인 개념이다.
미디어 파이프라인은 미디어 처리 과정에서 발생하는 다양한 작업(캡처, 인코딩, 필터링, 녹화 등)을 담당하는 미디어 요소들을 연결하는 역할을 한다.

WebRTC Endpoint

WebRTC Endpoint는 KMS에서 WebRTC 프로토콜을 통한 미디어 스트림의 송수신을 담당하는 미디어 요소이다. WebRTC Endpoint는 브라우저, 모
바일 애플리케이션, 또는 다른 WebRTC 호환 장치들과의 P2P 연결을 설정한다. 이 요소는 STUN/TURN 서버를 통한 NAT 트래버설, 미디어 스트림의 인코딩 및 디코딩, 미디어 스트림의 암호화와 같은 WebRTC 연결의 다양한 측면을 관리한다.
WebRTC Endpoint는 미디어 파이프라인 내에서 사용되며, 다른 미디어 요소들과 함께 연결되어 복잡한 미디어 처리 로직을 구현할 수 있다.

직접 구현하면서 위의 로직을 기반으로 프로덕트에 적용하기에는 다소 무리가 있는 것이 사실이다.
여전히 불안정하지만, 조금씩 가다듬고 Redis나 nginx 로드밸런싱 기술을 도입하면, 우리가 가진 프로덕트에 도입할 수 있을 거란 희망이 보였다.

혹시나 보면서 궁금한 게 있거나 이해가 안되는 부분이 생기면 댓글 남겨주세요~!

profile
천천히 한 걸음씩

0개의 댓글