MediaSoup 을 사용해서 SFU방식으로 VideoChat 구현하기(2/2) - 구현 과정
by Lizzie Oh2023. 1. 23.
다수의 사람이 화상으로 대화를 하기 위해서는 미디어 스트림은 audio track과 video track 이 둘 다 존재해야 하고, 각 사람이 producer 이면서 동시에 consumer 여야 한다. 하지만 결국 가장 기본 흐름은 지난 포스팅에서 다룬 아래 형식에서 시작하게 된다!
이를 기반으로, 어떻게 video chat 코드를 짤 수 있고 어떤 흐름으로 다중 화상 통화 기능을 구현할 수 있는지 정리해보려고 한다. 코드에서 서 중요한 부분만 발췌하였기 때문에 실제로 돌아가는 코드를 보고 참고하시려는 분들은 gitHub 레포지토리를 참고하시길 바란다! (frontend Repository, socket Repository)
아래는 간단한 흐름을 내 식대로 다시 정리한 표이다!
그렇다면 순서대로 흐름과 간략한 코드를 확인해보자!
📌 서버: Worker를 생성
worker가 있어야 Router를 만들 수 있고, Router가 있어야 Transport를 만들 수 있다. 가장 먼저 worker를 생성한다. 이때 옵션으로 입력하는 rtcMinPort와 rtcMaxPort 는 통신이 이루어지는 포트의 범위를 결정한다. 따라서 이 범위가 통신의 주체가 되는 사람의 수를 커버할 수 있어야 하고 (이전 글 참고) ec2에 올릴 때는 이 범위의 포트에 대해 보안그룹이 열려있어야 한다. (이거 문제 찾느라 진짜 일주일 넘게 헤맸다.. 다시한번 문제 해결하는데 큰 도움 준 쫑구 너무 고마워 ㅠ )
constcreateWorker=async()=>{
worker=awaitmediasoup.createWorker({
rtcMinPort:2000,
rtcMaxPort:2100,
})
returnworker
}
worker=createWorker()
📌 클라이언트: 유저의 미디어 장비에 접근해서 오디오, 비디오 stream을 받고 서버에 Router rtpCapabilities 요청
: 서버에 Router rtpCapabilities를 요청한다는 것은 클라이언트 입장에서는 어떤 채팅 '방'에 들어가겠다는 의미!
📌 서버: 서버측 Transport(RP)를 생성하고, 생성한 transport의 정보들을 클라이언트로 전송
router의 createWebRtcTransport 메서드를 실행하여 transport를 생성한다. 이때 ip주소와 udp, tcp 설정을 옵션으로 전달한다. 이때 로컬에서 테스트 할 때라면 아래 코드와 같이 ip는 로컬 ip를, announcedIp는 null로 설정하지만 ec2에 올려서 테스트하는 경우라면 ip에는 private ip주소를, announcedIp는 public ip 주소를 넣는다.
connectSendTransport();// audio producer와 video producer가 생성된다
});
};
📌 클라이언트: Transport(LP)의 produce()를 호출해서 transport가 Router로 미디어(video/audio track)를 보내게 한다. 이때 produce 메서드 호출은 transport의 connect, produce 이벤트를 발생시킨다!
produce는 audio track, video track 별로 호출하게 되므로 영상과 음성을 모두 보내려면 음성,영상 각각 produce()를 실행해야 한다. (device와는 음성과 영상을 구분하지 않지만 producer와 consumer의 경우 음성과 영상 각각에 대해 존재한다! transport 의 경우 produce를 위한 transport는 음성과 영상을 함께 보내더라도 1개만 있으면 되지만, consumer transport는 음성과 영상 각각에 대해 존재한다.)
videoProducer.on("transportclose", () => {console.log("video transport ended"); });
};
📌 클라이언트: Transport(LP) 의 produce로 발생한 connect, produce이 서버의 connect, produce 메서드를 호출 (audio, video 각각에 대해 connect, produce가 발생한다!)
connect 이벤트는 transport가 ICE+DTLS connection 을 맺기 위해 서버측 transport와 정보를 주고받아야 할 때 emit 된다. 이때 ㅣlocal dtls parameter 정보인 dtlsParameters를 받게 되는데, 이를 서버로 보내서 server-side transport(RP)가 connect 메서드를 호출할 때 인자로 사용할 수 있게 한다.
produce 이벤트는 transport가 새 producer에 대한 정보를 서버 측 trasport에 전송해야 할 때 emit 된다. 이때 서버 측 producer를 생성하는 데 사용하는 parameter 정보인 parameters값을 받게 되는데, 이를 서버로 보내서 server-side transport(RP)의 produce 메서드를 호출할 때 인자로 사용할 수 있게 한다.
📌 서버: transport(RP)의 produce({ kind, rtpParameters }) 실행 → 서버측 producer 생성(audio, video 각각 실행) 이후 콜백함수에 클라이언트에 생성한 producer id와 현재 producer가 존재하는지 정보를 담아 전송하게 되며, 동시에 기존에 존재하던 다른 consumer들에게 새로운 producer가 있다는 것을 알림 (informConsumer 함수)
📌 클라이언트: produce 이벤트의 콜백함수를 실행함으로서, transport(LP)에 서버측 producer id를 전달한다. 이 단계까지 오면 영상을 송출할 수 있게 되고, 기존에 이미 영상을 송출하고 있던 producer가 있다면 이를 consume 하기 위해 서버에 정보를 요청
📌 서버 : 클라이언트에서 받은 정보로 router의 consume 가능 여부를 체크하고 (canConsume 메서드) 가능하다면 서버측 consumer transport(RC) 의 consume 함수를 실행. 단 공식 문서에서 권고하는 방식과 같이 paused:true 를 인자로 넣어 우선 pause 한 후 추후 resume 으로 시작할 수 있도록 한다.
When creating a consumer it's recommended to setpausedtotrue, then transmit the consumer parameters to the consuming endpoint and, once the consuming endpoint has created its local side consumer, unpause the server side consumer using theresume()method.
Reasons for create the server side consumer inpausedmode: - If the remote endpoint is a WebRTC browser or application and it receives a RTP packet of the new consumerbeforethe remoteRTCPeerConnectionis ready to process it (this is, before the remote consumer is created in the remote endpoint) it may happen that theRTCPeerConnectionwill wrongly associate the SSRC of the received packet to an already existing SDPm=section, so the imminent creation of the new consumer and its associatedm=section will fail. - Also, when creating a video consumer, this is an optimization to make it possible for the consuming endpoint to render the video as far as possible. If the server side consumer was created withpaused: false, mediasoup will immediately request a key frame to the producer and that key frame may reach the consuming endpoint even before it's ready to consume it, generating “black” video until the device requests a keyframe by itself.
📌 클라이언트: transport(LC) 의 consume 메서드를 실행하여 consumer 생성. 이때 consume 메서드는 LC에 connect이벤트를 발생 시켜 서버에 transport-recv-connect 이벤트를 emit 하게 되고, 이때 dtlsParameter를 담아 서버로 보낸다. consumer를 생성한 이후에는 html audio, video 태그를 만들고 srcObject로 미디어 스트림 트랙을 담아 음성과 영상이 재생될 수 있도록 한다.
댓글