[unity] WebRTC 사용법
Unity WebRTC 튜토리얼 및 샘플을 토대로 Unity에서 어떤 개념으로 화면 공유가 되는 건지 연구해 보자.
Unity용 WebRTC 샘플 파일 설치 및 소개는 블로그 이전 편을 참고하면 좋다.
네트워크 관련 지식이 많지 않아. 해당 샘플 파일을 이해하는데 초기 어려움이 많았다.
서버 없이 네트워크처럼 화면, 영상, 음성이 공유되는 원리를 먼저 이해하고 WebRTC를 연구해보면 더 좋을 것 같다.
샘플로 제공해주는 PeerConnectionSampe.cs 튜토리얼을 따라 하기 식으로 접근하여, 개념을 이해해보고자 했다.
참고로 아래 코드에는 샘플에 나오는 테스트 버튼 관련 변수 및 내용은 제외하였다.(WebRTC 부분만 연구)
1. 네임스페이스 추가
- Unity에서 WebRTC 사용시 아래와 같이 NameSpace를 추가해줘야 한다.
using UnityEngine;
using Unity.WebRTC;
2. Peer 선언 및 Delegate 선언
- RTCPeerConnection 클래스 변수 pc1, pc2를 선언한다.
. WebRTC가 Peer to Peer 형태로 연결되는 개념이기 때문에 저렇게 두 pc의 연결을 위한 객체라고 보면 된다.
- 각 Peer마다 DelegateOnIceCandidate Delegate를 선언한다.
. WebRTC 코드를 보면 Candidate, ICE라는 단어가 많이 나온다. 개념을 조금 잡고 코드를 보면 이해하기 좀 더 수월하다.
. Candidate : Stun 서버를 이용해 획득한 IP 주소 및 포트 정보 등의 연결 가능한 네트워크 주소들을 말한다.
. ICE : 두 개의 단말이 P2P 연결되도록 도와주는 프레임워크이다.
. 즉 해당 Delegate는 ICE 프레임워크를 통해서 두 개의 단말 네트워크 주소를 찾아주고, 연결 가능한 상태를 만들어 주는 역할이다.
- 변수 코드는 아래를 참고하자.
private RTCPeerConnection _pc1, _pc2;
private List<RTCRtpSender> pc1Senders;
private MediaStream videoStream, receiveStream;
private DelegateOnIceConnectionChange pc1OnIceConnectionChange;
private DelegateOnIceConnectionChange pc2OnIceConnectionChange;
private DelegateOnIceCandidate pc1OnIceCandidate;
private DelegateOnIceCandidate pc2OnIceCandidate;
private DelegateOnTrack pc2Ontrack;
private DelegateOnNegotiationNeeded pc1OnNegotiationNeeded;
private bool videoUpdateStarted;
3. 초기화
- Unity 기본 이벤트 함수인 Awake() 및 Start() 함수에서 초기화를 진행한다.
- Awake()함수에서 WebRTC의 Initialize 함수를 통해 초기화해준다.
. 초기화 시 텍스쳐 사이즈 제한을 매개변수로 넣어줄 수 있다.
- Start() 함수에서 Delegate 할당 및 비디오 및 미디어 스트림 트랙 이벤트를 등록해준다.
private void Awake()
{
//WebRTC 초기화.
WebRTC.Initialize();
}
private void Start()
{
pc1OnIceConnectionChange = state => { OnIceConnectionChange(_pc1, state); };
pc2OnIceConnectionChange = state => { OnIceConnectionChange(_pc2, state); };
pc1OnIceCandidate = candidate => { OnIceCandidate(_pc1, candidate); };
pc2OnIceCandidate = candidate => { OnIceCandidate(_pc2, candidate); };
pc2Ontrack = e =>
{
receiveStream.AddTrack(e.Track);
};
pc1OnNegotiationNeeded = () => { StartCoroutine(PeerNegotiationNeeded(_pc1)); };
}
4. 네트워크 통신 경로 등록
- 통신하는 두 단말기의 프로토콜은 동일해야 한다.
- AddIceCandidate() 함수를 통해 서로 단말에 대한 네트워크 정보를 추가해준다.
private void OnIceCandidate(RTCPeerConnection pc, RTCIceCandidate candidate)
{
switch((ProtocolOption)dropDownProtocol.value)
{
case ProtocolOption.Default:
break;
case ProtocolOption.UDP:
if (candidate.Protocol != RTCIceProtocol.Udp)
return;
break;
case ProtocolOption.TCP:
if (candidate.Protocol != RTCIceProtocol.Tcp)
return;
break;
}
GetOtherPc(pc).AddIceCandidate(candidate);
}
enum ProtocolOption
{
Default,
UDP,
TCP
}
5. 시그널링
- 두 단말 간에 스트림을 교환하기 위해서는 시그널링을 해줘야 한다.
. 시그널링이란 WebRTC 통신에 사용할 프로토콜과 미디어 코덱, 데이터 전송 방법 등을 포함한 통신 규격을 교환하기 위해 두 단말 간 제어 정보를 교환하는 과정을 말한다.
- 시그널링을 위해서 먼저 SDP(Session Descriptin protocol) 개념을 알아야 한다.
. SDP : WebRTC에서 스트리밍 미디어의 해상도나 형식, 코덱 등의 멀티미디어 초기 인수를 설명하기 위해 채택한 프로토콜이다.
. 예를 들어 피어가 미디어 스트림을 교환할 것을 원할 경우 아래와 같은 프로세스가 진행된다.
1. 요청하면, 상대 피어로부터 응답이 오기를 기다린다.
2. 응답을 기다린 후 ICE 중에 최적의 경로를 결정하고 안정적인 경로를 찾는다.
3. ICE 후보가 선택되면, 기본 IP 주소 , 포트 정보, 미디어 정보를 피어 간 합의를 완료한다.
- Unity에서 샘플 코드를 보면 다음과 같다.
IEnumerator PeerNegotiationNeeded(RTCPeerConnection pc)
{
var op = pc.CreateOffer();
yield return op;
if (!op.IsError)
{
if (pc.SignalingState != RTCSignalingState.Stable)
{
yield break;
}
yield return StartCoroutine(OnCreateOfferSuccess(pc, op.Desc));
}
}
private IEnumerator OnCreateOfferSuccess(RTCPeerConnection pc, RTCSessionDescription desc)
{
var op = pc.SetLocalDescription(ref desc);
yield return op;
if (!op.IsError)
{
OnSetLocalSuccess(pc);
}
else
{
var error = op.Error;
OnSetSessionDescriptionError(ref error);
yield break;
}
var otherPc = GetOtherPc(pc);
var op2 = otherPc.SetRemoteDescription(ref desc);
yield return op2;
if (!op2.IsError)
{
OnSetRemoteSuccess(otherPc);
}
else
{
var error = op2.Error;
OnSetSessionDescriptionError(ref error);
yield break;
}
var op3 = otherPc.CreateAnswer();
yield return op3;
if (!op3.IsError)
{
yield return OnCreateAnswerSuccess(otherPc, op3.Desc);
}
else
{
OnCreateSessionDescriptionError(op3.Error);
}
}
6. 데이터 송수신
- 영상 공유의 경우 WebRTC.Update() 함수를 통해, 매 프레임 업데이트해준다.
private void AddTracks()
{
foreach (var track in videoStream.GetTracks())
{
pc1Senders.Add(_pc1.AddTrack(track, videoStream));
}
if (WebRTCSettings.UseVideoCodec != null)
{
var codecs = new[] {WebRTCSettings.UseVideoCodec};
foreach (var transceiver in _pc1.GetTransceivers())
{
if (pc1Senders.Contains(transceiver.Sender))
{
transceiver.SetCodecPreferences(codecs);
}
}
}
if (!videoUpdateStarted)
{
StartCoroutine(WebRTC.Update());
videoUpdateStarted = true;
}
}
receiveStream.OnAddTrack = e =>
{
if (e.Track is VideoStreamTrack track)
{
track.OnVideoReceived += tex =>
{
receiveImage.texture = tex;
receiveImage.color = Color.white;
};
}
};
7. 결과 화면
- 왼쪽 화면(PC1)을 오른쪽 화면(PC2)으로 화면 공유가 진행된다.
- CandidateId를 보면 저로 다른 두 개의 Peer가 연결된 걸 확인할 수 있다.
- Unity window 메뉴 중 Analysis -> WebRTC Stats를 열어보면 연결된 Peer 정보를 확인할 수 있다.
메타버스 시대에 화상서비스를 위해서는 WebRTC는 필수인 영역 같다.
지금은 간단히 알아본 WebRTC였지만, 프로젝트 기회가 된다면 좀 더 깊이 연구해보면 좋을 것 같다는 생각이 든다.
[참고]
2. WebRTC Org
댓글