WebRTC (w. Swift)

DevDreamer·2024년 6월 8일

목차

  • webrtc
  • 준비물
  • 설계
  • 코드설명



WebRTC

WebRTC란 서버를 거치지 않고 데이터를 주고받는(p2p) 서비스를 지원해주는 프레임워크이다.

그럼 어떻게 WebRTC를 동작시킬 수 있을까?
여기서는 1:1을 가정하겠다.
클라이언트A와 B씨가 통신한다.
1. offer를 주고받는다
2. answer를 주고받는다
3. candidate를 주고받는다.

이게 끝이다.
이것만 기억하고 순서대로 코드를 작성하면 된다.



준비물

  1. signaling서버
  2. stun, turn서버 설정

위에 적은 것들 모두 우리가 설정해줘야 한다.

signaling 서버는 websocket을 이용하는데 이 websocket을 우리가 구현하면 된다.
stun, turn서버는 여기를 참고하였다. 그나마 turn서버는 간단하다.



설계

나는 프로젝트에서 1:1 통신을 가정하였으며 양방향이 아닌, 단방향으로 구현하였다. webrtc는 양방향을 지원하지만, 그게 된다면 단방향도 된다는 뜻이 아니겠는가?

제일 핵심적인건 다음과 같다.
1. localTrack
2. remoteTrack
3. peerConnection
(그외: 4. WebRTCDelegate, 5. localRenderView, 6. remoteRenderView 등등...)

[local/remote]Track이란 말 그대로의 상대에게 보내는/받기 위한 접시같은 것과 비슷한 트랙이다.
만약 내 화면을 보내고 싶다면? localTrack에 카메라나 디바이스의 input source를 track에 붙이면 된다.
만약 원격의 화면을 받고 싶다면? remoteTrack에 뭐가 왔나 잘 감시하면 된다.
그리고 그 track을 peerConnection에 추가하면, sdp, candidate를 잘 주고 받았을 때 알아서 p2p연결이 시작된다.

둘 다 구현한다면 양방향이 되는것이며, 둘 중 하나만 (local-remote)구현한다면 단방향이 된다.

A(localTrack) - B(remoteTrack)으로 구현했다면 비디오, 오디오를 A가 보내고 B가 받는 쪽이 된다는 뜻이며 그 반대도 가능하다는 뜻이다.



코드설명

(오디오는 생략한다)

WebRTCClient.class

  • 먼저 PeerConnectionFactory, PeerConnection을 만든다.

PeerConnectionFactory를 만든다.

PeerConnection에 사용할 config를 만든다.

peerConnection(pc)을 만든다.
mediaConstraints는 [kRTCMediaConstraintsOfferToReceiveVideo: kRTCMediaConstraintsValueTrue] 이런 값임.


  • 비디오 캡처를 설정한다.
    RTCVideoCapturer를 사용하여 비디오 트랙을 설정한다.
    factory에서 videoSource를 만들고 RTCVideoCapturer에 담으면 된다.

    비디오 소스를 생성, 이걸 capturer로 등록 후 videoSource를 self에 저장하고 videoTrack을 생성 후 생성한 videoTrack을 localVideoTrack으로 저장. (중간에 스트림이 있는데 없어도 무관함)

  • WebRTC의 디바이스(카메라)를 설정.

    위에서 생성한 videoCapturer가 nil이 아니라면, targetFormat, targetDevice를 찾는다.
    그 후
    캡처를 시작한다.
    여기서 주목해야할 점은, DispatchQueue의 qos값, startCapture의 with, format, fps이다.
    내가 실험을 해봤는데, CameraSession에서 생성한 device, format이 있다면 그것을 targetDevice, targetFormat대신 여기다가 넣어도 된다. (그러니까 CameraSession을 사용하면서 WebRTC를 사용한다면 만들어 놓은 것을 써도 되고, WebRTC만 사용한다면 최소한의 CameraSession설정을 해놓은 뒤에 디바이스는 WebRTCClient에서 설정해도 된다는 뜻이다.)

    유의사항: CameraSession과 WebRTC의 capturer는 동시에 하나만 사용 가능하다. 만약 CameraSession에서 화면하나, WebRTC에서 화면 하나 해서 따로 돌아가게 해야지~ 라고 생각한다면 불가능하다. '단, 카메라 세션을 두개 생성하고, 번갈아 키고끄고 하면서 두개가 동시에 돌아가는 것 처럼 구현하면 되기는 함. 근데 이걸 누가 할까...'

  • localVideoTrack을 설정한다

self.peerConnection!
    .add(localVideoTrack, streamIds: ["stream-0"])
  • remoteVideoTrack을 설정한다.
    remoteVideoTrack은 peerConnect가 서로 연결되기 전(sdp 교환완료 하기 전)에 peerConnect에 붙여야 한다.
        remoteVideoTrack = peerConnection!.transceivers
        .first { $0.mediaType == .video }?.receiver.track as? RTCVideoTrack

connect 버튼을 누르면 동작하게 끔 한다면, 그 connect함수 안에 넣어도 되고, 양방향이면 localTrack설정할 때 아예 미리 넣어도 된다.
나 같은 경우, 단방향이기 때문에 connect에 넣었다.(sdp가 소켓으로 오면, 그때 peerConnect를 생성하게 코드를 구현했음. 따라서, 한 쪽이 미리 peerConnect를 생성하지 않고 기다리기 때문에 connect쪽에 remoteVideoTrack을 구현할 필요가 있었음.)



이 정도면 단방향 교환은 완료한거나 마찬가지이다.
이제 sdp편으로 넘어가보겠다.


sdp부터는 github나 구글링에서 쉽게 찾을 수 있다. 따라서 지금은 방법만 설명하고 생략하겠음.

  1. A(비디오보냄), B(비디오받음)둘다 웹소켓에서 접속해서 기다림.
  2. B가 비디오 달라고 요청할려고 함
    2.1 peerConnect를 생성하고 remoteTrack을 만듦.
    2.2 offer를 만듦
    2.3 offer를 웹소켓에 전송함.
  3. A는 offer를 받고 sdp를 저장함.
    3.1 peerConnect를 생성하고 localTrack을 만듦.
    3.2 answer를 만듦.
    3.3 answer를 웹소켓에 전송함.
  4. B는 answer를 받고 answer sdp를 저장함.
    SDP 교환완료.
  5. B는 candidate를 생성한 뒤 웹소켓에 보냄 (이건 RTCPeerConnectionDelegate의 peerConnection( peerConnection:, didGenerate candidate:))에 정의되어 있음.
    5.1 A는 B의 candidate를 저장한 뒤, B에게 자신의 candidate를 보냄
    5.2 B는 A의 candidate를 저장함.

이러면 WebRTC는 끝임.

필요에 따라서 localRenderView, remoteRenderView를 만들면 된다.

  • renderView설정하기.(local만 설명하겠음.)
localRenderView = RTCEAGLVideoView(frame: localView.frame)
localRenderView!.delegate = self
localView.addSubview(localRenderView!)
localVideoTrack.add(localRenderView!)

localView는 UIView이며 거기에 사이즈를 딱 맞추려고 renderView의 frame을 localView의 frame으로 하였다.
자신의 로컬 트랙의 비디오를 previewLayer처럼 미리보기 화면을 구현하고 싶다면 localVideoTrack.add(renderView)를 하면 된다.
원격또한 마찬가지다. remoteVideoTrack을 renderView에 표시되게 끔 설정해 놓는다면, remoteVideoTrack에 비디오 패킷이 도착했을 때, 원격 화면이 표시된다.

양방향이지만 내 화면 말고 상대 화면만 보고싶으면 remoteRenderView만 설정해놓고, 로컬화면 까지 둘 다 보고 싶으면 localRenderView를 추가하면 된다.




지금 포스팅은 좀 불친절하다. 왜냐하면 깃허브올리고 싶은데 개인 프로젝트가 아니어서 아직 private이다. 때가 된다면 public으로 바꿔서 공개하고 여기다가 추가해놓겠다.

profile
귀차니즘을 이기기 위한 게으른 개인용 블로그

0개의 댓글