1. 👊 시작하기
Github 에서 프로젝트 설명을 자세하게 보실 수 있습니다.
북쪽행성 방문하기
Web RTC (Web Real Time Communication) 프로젝트로 온라인 발표 시스템을 개발했습니다.

발표자의 화상 영상 위에 발표자료를 띄운 서비스인데요. 이 서비스를 이용해 청취자는 발표자의 비언어적 표현, 언어적 표현을 한 번에 볼 수 있어요.
WebRTC 프로젝트를 진행하면서 삽질했던 경험과 느꼈던 점을 얘기해볼게요.
2. 삽질 기록
3. 여러 명 통신 상황에서 발표자료 띄우기
3.1. N:N 상황에서 ImageOverlayFilter 띄우기
3.1.1. 문제를 발견하게 된 과정

Kurento 미디어 서버는 웹에서 실시간으로 통신하는 기능을 제공합니다. (WebRTC)
그룹콜 상황에서 Kurento 미디어 서버에 ImageOverlayFilter를 적용했지만 송신자(sender), 수신자(receiver) 영상에 Image가 안보였습니다.
<java />
//image Overlay Filter
log.trace("[UserSession] receiveVideoFrom image 필터 씌우기");
imageOverlayFilter=new ImageOverlayFilter.Builder(pipeline).build();
String imageId = "testImage";
String imageUri = "/home/ubuntu/image/flower.jpg";
log.trace("image start imageId: "+imageId+" imageUri: "+imageUri+" pipeline: "+pipeline);
imageOverlayFilter.addImage(imageId, imageUri, 0.4f, 0.4f, 0.4f, 0.4f, true, true);
this.getEndpointForUser(sender).connect(imageOverlayFilter);
imageOverlayFilter.connect(this.getEndpointForUser(sender));
3.1.2. 문제의 원인
ImageOverlayFilter의 Pipeline 연결이 잘못됐습니다.
3.1.3. 문제 해결을 위해 시도해 본 방법들
1) 송신자(sender)의 WebRtcEndpoint와 pipeline 연결 -> 실패
2) 수신자(receiver)의 WebRtcEndpoint와 pipeline 연결 -> 실패
3) 송신자(sender)의 WebRtcEndpoint, 수신자(receiver)의 WebRtcEndpoint와 연결 -> 성공
3.1.4. 문제를 해결한 방법
3번 방법으로 해결했습니다.
sender의 WebRtcEndpoint > ImageOverlayFilter > receiver의 WebRtcEndpoint 순서로 연결했습니다.
<java />
// 송신자의 end pointer 에 이미지 연결
sender.getOutgoingWebRtcPeer().connect(imageOverlayFilter);
// 이미지에 수신자의 end pointer 연결
imageOverlayFilter.connect(incoming);
3.2.
4. 웹페이지에 노트북 카메라로 찍히는 영상과 Kurento 미디어 서버를 통신해서 응답받은 영상 띄우기
4.1. 사용자의 웹페이지에 local video와 remote video 모두 띄우기
4.1.1. 문제를 발견하게 된 과정
사용자의 영상 위에 발표 자료를 띄우기 위해서는 본인의 Remote video가 필요했습니다. 하지만 웹 페이지에 Local video만 뜨는 상황이었습니다.
4.1.2. 문제의 원인
Group Call에서는 사용자의 기능이 미디어 서버에서 나가는 outgoingMedia와 미디어 서버로 들어오는 incomingMedia로 나뉘어 있습니다. incomingMedia에 본인을 제외한 다른 참여자들의 영상만 들어있기 때문에 사용자 본인의 영상은 보이지 않았습니다.
4.1.3. 문제 해결을 위해 시도해 본 방법들
javascript코드에서 비디오 속성을 송신 목적으로만 쓰이는 sendonly에서 송신, 수신 모두 활용하는 sendrecv로 변경했지만 실패했습니다.
4.1.4. 문제를 해결한 방법
비디오 속성을 sendrecv로 변경한 후, 본인의 outgoingMedia도 incomingMedia에 포함시켜 줘서 본인을 본인에게 연결했습니다.
UserSession.java에서 수정했습니다.
<java />
//생성자에서 outgoingMedia를 incomingMedia에 포함
this.incomingMedia.put(name,outgoingMedia);
public void receiveVideoFrom(UserSession sender, String sdpOffer) throws IOException {
log.info("USER {}: connecting with {} in room {}", this.name, sender.getName(), this.roomName);
log.trace("USER {}: SdpOffer for {} is {}", this.name, sender.getName(), sdpOffer);
// My code starts
if (sender.getName().equals(name)) {
final String ipSdpAnswer = this.getOutgoingWebRtcPeer().processOffer(sdpOffer);
final JsonObject scParams = new JsonObject();
scParams.addProperty("id", "receiveVideoAnswer");
scParams.addProperty("name", sender.getName());
scParams.addProperty("sdpAnswer", ipSdpAnswer);
log.trace("USER {}: SdpAnswer for {} is {}", this.name, sender.getName(), ipSdpAnswer);
this.sendMessage(scParams);
log.debug("gather candidates");
this.getOutgoingWebRtcPeer().gatherCandidates();
//본인의 outgoingMedia와 incomingMedia 연결
WebRtcEndpoint incoming = incomingMedia.get(sender.getName());
sender.getOutgoingWebRtcPeer().connect(incoming);
} else {
final String ipSdpAnswer = this.getEndpointForUser(sender).processOffer(sdpOffer);
final JsonObject scParams = new JsonObject();
scParams.addProperty("id", "receiveVideoAnswer");
scParams.addProperty("name", sender.getName());
scParams.addProperty("sdpAnswer", ipSdpAnswer);
log.trace("USER {}: SdpAnswer for {} is {}", this.name, sender.getName(), ipSdpAnswer);
this.sendMessage(scParams);
log.debug("gather candidates");
this.getEndpointForUser(sender).gatherCandidates();
}
}
private WebRtcEndpoint getEndpointForUser(final UserSession sender) {
log.debug("PARTICIPANT {}: receiving video from {}", this.name, sender.getName());
WebRtcEndpoint incoming = incomingMedia.get(sender.getName());
if (incoming == null) {
log.debug("PARTICIPANT {}: creating new endpoint for {}", this.name, sender.getName());
incoming = new WebRtcEndpoint.Builder(pipeline).build();
incoming.addIceCandidateFoundListener(new EventListener<IceCandidateFoundEvent>() {
@Override
public void onEvent(IceCandidateFoundEvent event) {
JsonObject response = new JsonObject();
response.addProperty("id", "iceCandidate");
response.addProperty("name", sender.getName());
response.add("candidate", JsonUtils.toJsonObject(event.getCandidate()));
try {
synchronized (session) {
session.sendMessage(new TextMessage(response.toString()));
}
} catch (IOException e) {
log.debug(e.getMessage());
}
}
});
incomingMedia.put(sender.getName(), incoming);
}
log.debug("PARTICIPANT {}: obtained endpoint for {}", this.name, sender.getName());
sender.getOutgoingWebRtcPeer().connect(incoming);
return incoming;
}
4.2.
5. 화상영상에 이미지 넣는 기능의 아키텍처 변경
처음에는 Kurento 서버의 ImageOverlayFilter를 이용해서 화상 영상에 이미지를 넣었습니다.
하지만 N:N 통신 상황에서 응답시간이 약 2초 정도 걸렸어요.
문제를 개선하기 위해 이미지를 보여주는 기능을 프론트엔드에 만들게 됐습니다.
5.0.1. 변경과정
1) 발표자 비디오에서 발표자료를 띄울 좌표를 계산한 뒤 웹소켓을 통해 다른 사용자에게 좌표를 전송합니다.
2) 전송받은 좌표정보를 이용해서 다른 사용자 화면에 보이는 발표자 비디오 위에 발표자료를 띄웁니다.
6. 마치며
서로를 잘 아는 팀이 좋은 결과를 만들어낸다.
저는 이 말에 공감하게 됐는데요. 서로를 잘 알아야 의사소통 할 때에도 오해 없이 서로의 의미를 이해할 수 있습니다.
개발 아이디어를 내고 의견을 수립하는 과정에서 많은 고민 끝에 나온 의견임을 알기 때문에 선정되지 못한 의견들도 소중히 다룰 수 있었습니다.
이는 개발 시너지 향상에도 도움이 되는데요. 서로가 맡은 기능을 책임감 있게 개발할 것이라고 믿고, 본인이 맡은 일을 수행하면서 어려운 점을 공유할 수 있었습니다.
이번 프로젝트로 팀원들의 장점을 많이 알게 되어 다음에 또 프로젝트를 하게 된다면 더 좋은 결과를 만들 수 있을 것 같습니다.
'Coding > project' 카테고리의 다른 글
WebRTC (6) | OpenVidu 튜토리얼 시작하기 (0) | 2021.08.03 |
---|---|
WebRTC (4) | Windows10 에서 Kurento의 GroupCall 예제 실행하기 (2) | 2021.07.23 |
WebRTC (3) | Windows10 에서 Kurento의 HelloWorld 예제 실행하기 (0) | 2021.07.23 |
WebRTC (1) | WebRTC 알아보기 (0) | 2021.07.23 |