RSS듀오랩스

WebRTC 기반 AI 상담원: 딥페이크 영상 응답을 앱에서 재생한 방법

모바일 앱글: , Duolabs7분 읽기2022blogdeepfake-videoiosportfoliotechnical-notewebrtc

2022년 말 국내 시중은행의 AI 상담원 프로젝트에 참여했습니다. 사용자가 앱에 말을 하면 서버가 질문을 처리하고, 화면 속 가상 은행원이 딥페이크 영상과 합성 음성으로 답하는 서비스였습니다. 제가 맡은 범위는 사용자의 음성을 실시간으로 보내고, 돌아온 영상 응답을 앱에서 재생하는 구간이었습니다.

이 글에서는 그중 영상 응답 재생에 집중합니다. 딥페이크 모델을 학습하거나 답변을 생성하는 서버를 제가 만들었다는 이야기가 아닙니다. 서버가 만든 결과를 사용자가 대화처럼 느끼도록 전달하는 일이 제 역할이었습니다.

WebRTC는 얼굴을 만드는 기술이 아니었습니다

딥페이크와 WebRTC가 한 문장에 들어가면 WebRTC가 얼굴이나 목소리를 생성하는 기술처럼 읽히기 쉽습니다. 두 기술의 역할은 분명히 달랐습니다.

사용자 발화
  -> iOS 앱에서 음성 수집
  -> 압축한 음성을 소켓으로 전송
  -> 서버에서 질문 처리와 응답 생성
  -> 딥페이크 영상과 합성 음성 생성
  -> WebRTC로 응답 미디어 전달
  -> 앱 화면에서 재생

서버가 화면 속 상담원의 얼굴과 목소리를 만들었다면, WebRTC는 그 결과가 앱에 도착하는 길을 맡았습니다. W3C의 WebRTC 명세도 WebRTC를 실시간 프로토콜을 통해 미디어와 데이터를 주고받기 위한 API로 정의합니다. 생성 모델과 전송 계층을 섞어서 설명하면 제가 만든 범위도, 장애가 생겼을 때 확인할 지점도 흐려집니다.

대화에서는 첫 프레임이 중요했습니다

일반 동영상은 어느 정도 내려받은 뒤 재생해도 큰 문제가 없습니다. AI 상담은 다릅니다. 사용자가 말을 마친 뒤 빈 화면이 오래 이어지면, 시스템이 질문을 듣지 못했는지 답을 만드는 중인지 알 수 없습니다.

사용자가 느끼는 대기 시간은 한 구간에서만 생기지 않았습니다.

구간 기다리는 일
음성 입력 마이크 버퍼가 채워지고 인코딩됩니다
음성 전송 압축된 음성이 서버까지 이동합니다
서버 처리 질문을 인식하고 답변과 미디어를 만듭니다
미디어 연결 응답 스트림을 받을 연결이 준비됩니다
첫 화면 패킷을 받고 디코딩한 첫 프레임을 그립니다

당시의 구간별 측정값은 지금 남아 있지 않습니다. 기억에 기대어 몇백 밀리초였다고 쓰지는 않겠습니다. 분명히 남아 있는 목표는 하나입니다. 목소리를 보낸 순간부터 영상이 돌아오기까지의 공백을 줄이는 일이었습니다.

WKWebView 안에서는 재생 방식도 설계해야 했습니다

이 프로젝트는 네이티브 앱과 웹 화면이 섞인 구조였습니다. 영상이 도착하는 것만으로 끝나지 않고, WKWebView 안에서 상담 화면의 일부로 재생되어야 했습니다.

당시 개발 노트에는 다음 WKWebViewConfiguration 항목을 검토한 기록이 남아 있습니다.

  • allowsInlineMediaPlayback
  • mediaTypesRequiringUserActionForPlayback
  • allowsAirPlayForMediaPlayback
  • allowsPictureInPictureMediaPlayback

특히 인라인 재생은 중요했습니다. 영상이 iPhone의 기본 전체 화면 플레이어로 넘어가면 입력 화면과 상담 상태가 가려지고, 한 화면에서 이어져야 할 대화 흐름이 끊깁니다. Apple 문서에 따르면 allowsInlineMediaPlayback을 켜는 것과 함께 HTML 비디오의 playsinline 속성도 맞춰야 합니다.

현재 남은 기록만으로 당시 각 옵션의 최종 값을 모두 복원할 수는 없습니다. 대신 무엇을 확인했는지는 분명합니다. 미디어 재생 정책은 플레이어 구현의 부가 설정이 아니라 상담 화면 자체의 동작이었습니다.

연결 상태는 사용자에게 보이는 상태였습니다

WebRTC 연결에는 연결 중, 연결됨, 일시 단절, 실패처럼 서로 다른 상태가 있습니다. 개발자에게는 네트워크 상태지만 사용자에게는 화면이 멈춘 이유입니다.

제가 담당한 범위에도 연결이 불안정할 때의 화면 처리가 포함됐습니다. 응답이 아직 생성되지 않은 것과 생성됐지만 재생 연결이 끊긴 것은 다른 상황입니다. 둘을 같은 로딩 화면으로 처리하면 사용자는 기다려야 하는지 다시 말해야 하는지 판단할 수 없습니다.

그래서 실시간 미디어를 붙일 때는 플레이어보다 먼저 다음 질문을 정해야 합니다.

  • 연결을 준비하는 동안 무엇을 보여줄 것인가
  • 첫 영상 프레임이 오기 전까지 입력을 막을 것인가
  • 일시 단절과 완전한 실패를 어떻게 구분할 것인가
  • 이전 응답이 늦게 도착하면 재생할 것인가 버릴 것인가
  • 재연결에 실패했을 때 사용자가 다시 시작할 방법은 무엇인가

정확한 당시 화면 문구와 재시도 횟수는 공개 기록에 남아 있지 않아 여기서 새로 만들지 않았습니다. 다만 포트폴리오에 남긴 세 가지 담당 항목 중 하나가 바로 불안정한 연결에서의 화면 처리였습니다.

영상과 합성 음성은 함께 도착해야 했습니다

딥페이크 응답은 얼굴만 움직이는 영상이 아니었습니다. 사용자가 듣는 목소리도 AI가 만든 합성 음성이었습니다. 입 모양과 목소리가 따로 도착하거나 서로 다른 시점에 시작하면 사람은 작은 어긋남도 금방 알아챕니다.

응답을 WebRTC 미디어로 받으면 오디오와 비디오의 시간 정보를 같은 재생 파이프라인에서 다룰 수 있습니다. 앱이 별도의 음성 파일과 영상 파일을 각각 내려받아 시작 시점을 손으로 맞추는 것보다 실시간 응답에 어울리는 구조였습니다.

여기서도 제 담당 범위는 생성이 아니라 재생이었습니다. 합성 음성이 어떤 모델로 만들어졌는지, 특정 인물의 목소리를 복제했는지는 현재 공개 가능한 기록만으로 확인되지 않습니다. 그래서 이 글에서는 이를 음성 복제라고 단정하지 않고 합성 음성이라고 적었습니다.

제가 만든 것은 모델과 사용자 사이의 마지막 구간이었습니다

AI 상담원에서 가장 눈에 띄는 것은 화면 속 얼굴입니다. 실제 사용감은 그 앞뒤의 연결에서 갈렸습니다. 사용자의 목소리가 끊기지 않고 올라가야 했고, 서버가 만든 영상은 대화가 끝나기 전에 앱 화면에 자리를 잡아야 했습니다.

2022년의 코드를 지금의 생성형 AI 서비스처럼 포장하고 싶지는 않습니다. 제가 맡았던 것은 딥페이크 모델 전체가 아니라 실시간 음성 전송과 영상 응답 재생 구간이었습니다. 범위는 좁지만, 이 구간이 제대로 동작해야 생성된 얼굴과 목소리가 파일 한 편이 아니라 상담원의 답변으로 느껴졌습니다.

마지막 수정:

공유하실 때는 출처(Duolabs)와 원문 주소를 표시해 주세요.