2018 webRTC 정리

web | 19 April 2019

Tags | webrtc js p2p frontend

2018년도에 webRTC 를 이용한 ‘코딩 실시간 화상 강의실 서비스’ 를 만들던 경험을 떠올리며, webRTC API 를 이용한 애플리케이션 작성법을 정리해봅니다. 🎉

What is WebRTC?

WebRTC 는 브라우저나 모바일 application 에서 Real Time Communication (RTC) 를 편리하게 할 수 있는 api 를 만들고자 하는 목적으로 시작된 프로젝트입니다. Open Project 이지만 (2018년 12월 기준) Google Chrome Team 의 주도로, Mozilla, Opera 등의 단체의 지원을 받으며 진행되고 있습니다.

프로젝트의 진행 방식은, WebRTC API 최신 명세 (SPEC) 를 WebRTC M{xx} 와 같은 이름으로 공개하고, 거의 동시에 Chrome (혹은 Chrome Beta) 에 구현하여 릴리즈하는 것 같습니다. Opera, Safari, Firefox 등의 브라우저들은 이 공개된 명세에 발맞춰 따라옵니다.

WebRTC 로 할 수 있는 일은 굉장히 많습니다. 그 중에서도 이 글에서는 서버를 통하지 않고 클라이언트와 클라이언트 간의 p2p 영상/음성/데이터 통신을 하는 활용법에 초점을 맞춰서 webRTC 의 API 를 정리해보겠습니다.

WebRTC 공식 홈페이지 webrtc.org 링크

WebRTC 의 3가지 대표 API

2018년 말인 현재, 대부분의 브라우저가 webRTC API 표준을 지원합니다. 대부분의 브라우저에서 지원하는 webRTC 의 대표적인 표준 API 3가지를 소개하겠습니다.

  1. MediaStream (getUserMedia)
  2. RTCPeerConnection
  3. RTCDataChannel

API 1. MediaStream

사용자의 카메라와 마이크 같은 곳의 데이터 스트림에 접근합니다. 우리의 애플리케이션이 사용자의 음성, 영상 데이터를 채집해 올 때 자주 사용하게 됩니다.

API 2. RTCPeerConnection

암호화 및 대역폭 관리를 하는 기능을 가지고 있고, 오디오 또는 비디오 연결을 담당합니다. 애플리케이션이 채집한 음성 및 영상 데이터를 서로 주고 받는 채널을 추상화하였다고 생각하면 됩니다.

API 3. RTCDataChannel

음성 및 영상 데이터가 아닌, json/text 데이터들을 주고받는 채널을 추상화한 API 입니다.

webRTC application 이 수행하는 것

여러분이 만약 p2p 영상 및 음성 통신을 하는 webRTC application 을 구성한다면, 다음의 주요한 4가지 작업을 수행해야 할 것입니다.

no stage 설명
1 Fetching 상대 peer 에게 보낼 사용자의 음성 및 영상 데이터를 수집합니다.
2 Signaling 이 세상 어딘가에 있는 상대 peer 와 연결을 맺기 위해서, 상대 peer 의 정보를 탐색합니다.
3 Connection 발견한 peer 와 p2p connection 을 맺습니다. channel 을 개방해둡니다.
4 Communication 개방해놓은 채널을 통해 음성/영상/텍스트 데이터를 주고 받습니다.

위의 4가지 작업의 구체적인 방식과, 각각의 수행에 필요한 webRTC API 들을 알아보겠습니다.

1단계. Fetching

webRTC API 인 MediaStream, getUserMedia 를 이용해 사용자의 영상 및 음성 정보를 가져옵니다. 가져온 이후의 활용법은 4단계에서 자세히 다루겠습니다.

2단계. Signaling

잠깐! Signlaing 단계는 피어와 피어가 서로를 찾을 수 있도록 돕는 중간 매개자 역할을 하는 서버인 Signaling Server 를 필요로 합니다. Signaling Sever 의 구현 방식에는 제약이 없습니다. 오롯히 애플리케이션을 만드는 개발자의 몫입니다. 개발 엔지니어 개인 역량에 따라 구현 형태도 다르고, 정답도 없습니다. webRTC 애플리케이션 개발을 하면서 가장 어려웠던 부분이기도 합니다.

Signaling 단계는 서로 다른 두 peer (WebRTC Client) 가 Communication 하기 위한 준비단계로, 3가지 종류의 정보를 교환해야 합니다.

  1. Network 정보를 교환합니다.
    • ICE Framework 를 사용해 find candidate => ip 와 port 를 찾는다
    • 내 ip 와 port 정보
  2. Media Capability 를 교환합니다.
    • sdp (Session Description Protocol) 형식을 따르는 blob 인 offeranswer 를 주고 받으며 교환합니다.
    • 내 브라우저와 상대 peer 브라우저가 사용 가능한 코덱들과 해상도들은 무엇일까
  3. Session Control Messages 교환합니다
    • Session (통신연결) 의 초기화,종료
    • Error Report

조금 더 자세히 알아보겠습니다.

Network 정보 (ICE Candidate) 교환하기

세상 어딘가에 있는 상대 peer 를 찾아 연결을 맺기 위해선, 네트워크 정보를 교환해야합니다. 이 때, 중간 매개자 역할로서 별도의 서버인 Signaling Server 가 필요합니다. 순서는 다음과 같습니다.

step do
1 RTCPeerConnection Object 를 새롭게 생성하고 RTCPeerConnection.onicecandidate 핸들러를 통해 현재 내 client 의 Ice Candidate(Network 정보) 가 확보되면 실행될 callback 을 전달합니다.
2 Ice Candidate (내 네트워크 정보) 가 확보되면, 중간 매개자인 Signaling Server 을 통해 상대 peer 에게 serialized 된 ice candidate 정보를 전송합니다. (쌍방이 서로에게 합니다.)
3 상대 peer 의 candidate (네트워크 정보) 가 도착하면, RTCPeerConnection.addIceCandidate 를 통해 상대 peer 의 네트워크 정보를 등록합니다. (쌍방이 모두 합니다.)

Media Capability 교환하기 + Session Control Message 교환하기

상황을 가정해봅시다. A 와 B 가 webRTC 통신을 하려고합니다. 각자 브라우저에서 RTCPeerConnection 객체를 가지고 있고, 서로의 네트워크 정보 (ice candidate) 를 교환 후 각자의 RTCPeerConnection.addIceCandidate 를 통해 서로의 네트워크 정보를 등록하였습니다.

step do
1 B 가 RTCPeerConnection.createOffer 를 호출해 Offer SDP (Session Description Protocol) 을 생성합니다. 여기엔 내 브라우저에서 사용 가능한 코덱이나 해상도에 대한 정보가 들어있습니다.
2 B 가 Offer SDP 를 Signaling Server (매개자) 을 통해 전송합니다.
3 A 는 Signaling Channel 에서 Offer SDP 를 받아, RTCPeerConnection.setRemoteDescription 을 수행합니다.
4 A 의 RTCPeerConnection 객체는 상대 session 에 대한 정보를 알고 있게 되었고, RTCPeerConnection.createAnswer 를 호출하여 Answer SDP 를 생성하여 Signaling Channel 을 통해 B 에게 전달합니다.
5 B 도 마찬가지로 자신의 RTCPeerConnection.setRemoteDescription 을 호출해, 전달받은 Answer SDP 를 등록합니다.
6 A, B 각 측에서 setRemoteDescription 이 성공적으로 수행되었다면, 각 브라우저에서는 서로의 peer 에 대해 인지하고 있는 상태라고 할 수 있고, p2p 연결이 성공적으로 완료되었다고 할 수 있습니다.

3단계. Connection

어려운 Signaling 을 통해 상대 피어의 정보가 잘 등록된 RTCPeerConnection 를 얻었다면, 연결이 성공적으로 이루어진 것입니다.

4단계. Communication

보통 webRTC 를 통해서 peer 와 peer 가 주고받는 데이터는 크게 아래의 두가지입니다.

  1. video 나 audio 데이터 스트림
  2. 직렬화된 text 데이터

교환의 양상은, 연결이 이루어지기 전에 데아터 stream 이나 채널을 미리 준비하고, 연결이 완료되면 데이터를 받았을 때의 callback 을 통해 받은 데이터를 처리합니다. 조금 더 자세한 내용은 아래와 같습니다.

1. video 나 audio 데이터 스트림 의 경우

주는 입장 : 자신의 머신에서 (getUserMedia 등의 api 를 통해) video/audio 스트림 source 를 취득해 RTCPeerConnection 을 생성할 당시에 addTrack(데이터 stream 채널을 연결) 해줍니다. Signaling 을 통해 connection 이 이루어지기 전에 미리 되어야합니다.

받는 입장 : RTCPeerConnection.ontrack 의 callback 을 커스텀하게 설정해서, connection 이 성공적으로 이루어진 후에 상대방의 Track (video/audio stream) 이 감지되면 어떤 동작을 할지 설정할 수 있습니다. 보통 받은 track 의 데이터 스트림을 DOM 의 <video srcObject={??}/> element 에 연결해 보여줍니다.

2. 직렬화된 text 데이터 의 경우

주는 입장 : RTCPeerConnection.createDataChannel 을 통해, 특정 이름의 data 전달 채널을 개설할 수 있습니다. 이 또한 Signaling 을 통해 connection 이 이루어지기 전에 미리 되어야합니다.

받는 입장 : RTCPeerConnection.ondatachannel 의 callback 을 커스텀하게 설정해서, connection 이 성공적으로 이루어진 후에 상대방이 data channel 을 통해 어떤 데이터를 보냈을 때의 동작을 설정할 수 있습니다.

webRTC 애플리케이션을 만들 때 겪었던 어려움

1. 피어간의 연결이 끊겼을 때, 다시 연결을 맺어줘야 한다

인터넷의 문제나 예기치 못한 문제로 피어와 피어간에 맺어놓은 Connection 이 끊어지는 일이 굉장히 자주 발생합니다. 이 때 적절한 retry 로직으로 자연스럽게 재연결을 맺어주어야 합니다.

2. 안정적인 Signaling Server 를 구축해야한다

Signaling 단계가 성공하지 못하면 Connection 을 맺을 수 조차 없습니다. 따라서 안정적인 Signaling Server 를 구축하는 것이 무엇보다 중요했습니다.

참고한 자료들

Getting Started with webRTC : https://www.html5rocks.com/ko/tutorials/webrtc/basics/