들어가며
안녕하세요. LINE 앱에서 VoIP(voice over Internet protocol, 인터넷 기반 음성 통화) Android 서비스 개발을 담당하고 있는 안덕용입니 다.
멀리 있는 가족과 안부를 나누거나, 연인과 소소한 이야기로 한참 웃다 보면 통화를 끝내기 아쉬운 순간이 있습니다. 그때의 목소리를 다시 듣고, 표정을 생생하게 보고 싶을 때가 누구에게나 있다고 생각합니다. 프리미엄콜은 이렇게 간직하고 싶은 통화의 한순간을 소리와 영상으로 기록하는 기능입니다.
물론 소중한 추억만을 위한 기능은 아닙니다. 통화 중에 정한 약속이나 길게 설명받은 내용을 나중에 다시 확인하고 싶을 때도 유용합니다. 대화를 놓칠까 봐 서둘러 메모하는 대신, 통화할 때는 상대방의 이야기에 집중하고 필요한 내용은 나중에 꺼내 볼 수 있으면 좋겠다고 생각했습니다.
프리미엄콜이 어떤 기능인지 먼저 소개하고, Android에서 녹음/녹화를 개발하며 고민한 내용을 이야기해 보려고 합니다.
음성 통화가 영상 통화로 바뀌어도 하나의 파일에 기록을 이어 가는 기능과, 자체 온디바이스 모델로 음성을 인식하면서 통화 품질과 기기 성능을 함께 고려한 과정도 소개하겠습니다.
프리미엄콜은 어떤 기능인가요
프리미엄콜은 LINE Labs로 출시했으며, 일본과 대만, 태국에서는 녹음/녹화 기능을 제공하는 등 국가마다 이용 가능한 서비스가 다를 수 있습니다. LINE의 1:1 음성 통화나 영상 통화 중 원하는 구간을 기록할 수 있습니다. 통화 화면에서 기록을 시작하면 나와 상대방의 목소리가 함께 남고, 영상 통화에서는 서로의 모습도 함께 기록합니다. 통화를 마친 뒤 Keep Memo에 저장된 기록이 있다면 나중에 다시 확인할 수 있습니다.
예를 들어 전화로 물건의 사용법을 설명받는 상황을 생각해 보겠습니다. 처음에는 말로 설명을 듣다가, 잘 이해되지 않는 부분이 있어 카메라를 켜고 직접 보여 달라고 할 수 있겠죠. 이 통화를 기록해 두면 나중에 음성으로 들었던 설명과 영상으로 보았던 동작을 함께 확인할 수 있습니다. 통화 중에 모든 내용을 외우거나 메모하지 않아도 되는 것입니다.
음성 통화의 기록을 재생할 때는 상대방의 프로필과 이름, 통화 시작 시각을 함께 보여 줍니다. 목소리만 들리는 빈 화면 대신 어떤 통화인지 알아볼 수 있는 화면을 제공하는데요. 영상 통화에서는 서로의 표정과 반응도 함께 남겨, 나중에 그 대화를 다시 보고 들을 수 있습니다.
통화 상대에게도 녹음/녹화 중이라는 사실을 안내합니다. 대화가 기록되고 있다는 것을 참여자가 알 수 있도록 하는 것도 중요했습니다.
음성 통화에서는 설정에 따라 STT(speech-to-text, 음성의 텍스트 변환)를 함께 사용할 수 있습니다. 현재 이 기능은 일본어를 지원합니다.
녹음을 다시 듣기 어려운 상황에서도 대화 내용을 글로 확인하고, 필요한 내용을 훑어볼 수 있도록 돕는 기능입니다. 음성 인식은 서버 대신 기기 안의 자체 모델로 처리합니다. 프리미엄콜의 중심은 다시 보고 들을 수 있는 통화 기록이고, STT는 그 기록을 더 편하게 활용하도록 보조합니다.
이렇게 사용자가 보는 흐름은 단순하지만, 개발하는 입장에서는 챙길 일이 꽤 많았습니다. 앞서 예로 든 통화에서 카메라를 켜는 순간부터 살펴보겠습니다.
음성 통화가 영상 통화로 바뀌면 기록은 어떻게 될까요
“말로만 설명하기 어려운데, 카메라를 켜서 화면으로 알려 드릴게요.”
사용자에게는 음성 통화에서 영상 통화로 전환되는 시점입니다. 이때 카메라를 켰다는 이유로 기록이 멈추거나 녹화 버튼을 다시 눌러야 한다면, 대화보다 녹화가 중단되지는 않을지, 현재 상태를 먼저 신경 쓰게 됩니다. 그래서 카메라를 켜더라도 진행 중인 기록은 멈추지 않고 이어지도록 해야 했습니다.
개발하는 입장에서는 음성만 받다가 카메라 영상도 함께 받게 되는 변화입니다. 앞서 나눈 음성 대화에 영상까지 이어서 남기려면, 기록 도중 새로운 영상이 들어와도 같은 파일에 담을 수 있어야 했습니다.
음성 구간과 영상 구간을 따로 저장하는 방법도 생각해 볼 수 있습니다. 녹음과 녹화를 각각 구성할 수 있지만, 결과 파일이 나뉘고 전환 사이의 데이터를 다루는 일이 남습니다. 통화가 끝난 뒤 파일을 이어 붙인다면 임시 파일과 후처리도 관리해야 합니다.
저희는 한 번 시작한 기록이 통화 방식이 바뀌어도 같은 파일에 이어지도록 구성했습니다. 이를 위해 처음부터 음성과 영상을 함께 담을 수 있는 구조를 준비하고, 통화 상태에 따라 그 안에 넣을 화면을 바꿨습니다.
음성 통화에도 화면을 담은 이유
음성 통화라면 목소리만 저장할 수도 있는데, 왜 화면까지 함께 담았을까요? 먼저 통화가 기록된 파일을 열었을 때 누구와 나눈 통화인지 쉽게 알아볼 수 있도록 하고 싶었습니다. 상대방의 프로필과 이름, 통화 시작 시각을 보여 주면 목소리를 한참 듣지 않아도 어떤 통화의 기록인지 확인할 수 있습니다.
또 하나는 통화 도중 음성에서 영상으로 바뀌는 순간을 같은 기록 안에 담기 위해서입니다. 음성 통 화 중에는 상대방의 프로필을 보여 주다가, 영상 통화로 전환되면 같은 파일 안에서 상대방의 실제 영상으로 이어지도록 했습니다. 나중에 재생할 때도 다른 파일을 열 필요 없이, 프로필 화면에서 통화 영상으로 자연스럽게 이어지는 대화를 볼 수 있습니다.
이 두 가지를 구현하기 위해 음성 통화를 기록할 때부터 오디오 트랙과 비디오 트랙을 함께 준비했습니다. 트랙은 하나의 파일 안에서 음성과 화면을 각각 담는 경로입니다. 오디오에는 양쪽 목소리를 담고, 비디오에는 프로필과 통화 정보로 만든 화면을 담습니다. 실제 카메라 영상이 없어도 이미지를 영상의 한 장면인 프레임으로 만들어 기록할 수 있습니다. 두 트랙이 준비되면 파일 기록을 시작합니다.
영상 통화로 바뀌면 비디오 트랙에 전달하던 프로필 화면을 실제 통화 영상으로 교체합니다. 오디오는 계속 기록하고, 파일과 트랙도 유지합니다. 이때 하나의 녹화를 관리하는 작업 단위를 녹화 세션이라고 부르는데요. 세션을 새로 시작하지 않고, 그 안에서 사용할 화면만 바꾸는 방식입니다. 실제 영상을 언제부터 사용할 수 있는지는 조금 더 살펴보겠습니다.
음성 통화에서 영상 통화로 이어지는 과정
그렇다면 영상 통화로 바뀌었다는 신호를 받는 즉시 프로필 화면을 실제 영상으로 교체하면 될까요? 여기서 한 가지 더 살펴볼 것이 있습니다. 통화 상태가 바뀌는 시점과 실제로 그릴 수 있는 영상이 도착하는 시점은 같지 않을 수 있습니다.
VoIP 엔진은 같은 통화 세션의 미디어 상태가 바뀌었다는 이벤트를 전달합니다. 반면 내 카메라 영상과 상대방에게서 받은 영상은 각각의 입력 경로에서 들어옵니다. 내 영상만 먼저 준비될 수도 있 고, 상대방 영상이 먼저 도착할 수도 있습니다.
구체적으로 이야기해 보면, 먼저 음성 통화인지 영상 통화인지에 따라 녹화할 화면을 정합니다. 영상 통화로 바뀌었다면 내 영상과 상대방 영상을 따로 살펴보면서 실제로 도착한 프레임이 있는지, 화면에 그릴 수 있는 상태인지 확인합니다. ‘영상 통화를 시작했다’는 것과 ‘기록할 영상이 도착했다’는 것을 나눠 보는 것입니다.
앞서 예로 든 통화에서 내 카메라 영상이 먼저 준비됐다고 해 보겠습니다. 영상 통화용 화면으로 바꾼 뒤 내 영상이 들어갈 영역에는 실제 영상을 넣고, 아직 영상이 도착하지 않은 상대방 영역에는 프로필을 유지합니다. 이후 상대방 영상도 준비되면 그 영역을 실제 영상으로 바꿉니다. 이런 순서로 입력이 준비되는 흐름은 다음과 같습니다.
각 영상이 준비되기 전에는 프로필을 보여 주며, 오디오와 파일 기록은 계속 이어집니다. 영상의 도착 순서는 달라질 수 있습니다.
양쪽 영상이 동시에 준비될 때까지 녹화 전체를 멈추지 않아도 됩니다. 아직 사용할 수 없는 입력에는 대체 화면을 두고, 사용할 수 있는 입력부터 반영합니다. 이 구분은 카메라를 끄는 등 영상 통화 중 입력 상태가 달라지는 경우에도 필요합니다.
다시 보고 싶은 표정을 잘 담기 위해
통화를 다시 볼 때는 상대방이 어떤 표정으로 이야기했는지도 보고 싶습니다. 반가운 소식에 함께 웃던 표정처럼, 목소리만으로는 다 남기기 어려운 모습도 있으니까요. 상대방의 얼굴이 잘 보이면서 그 순간 내가 어떻게 반응했는지도 함께 남아 있으면, 나중에 그 대화를 떠올리기 좋겠죠.
프리미엄콜은 이런 순간을 남길 수 있도록 상대방 영상과 내 영상을 함께 기록합니다.
이 화면은 휴대폰 화면을 그대로 캡처해서 만들지 않습니다. 저희는 VoIP 엔진이 전달하는 내 영상과 상대방 영상으로 녹화용 화면을 따로 만들었습니다. 이렇게 하면 알림이나 통화와 관계없는 화면 요소를 기록에 넣지 않고, 통화 영상만으로 녹화할 화면을 구성할 수 있습니다. 사용자가 보는 통화 UI(user interface, 사용자 인터페이스)와 파일에 남길 화면을 나눠 다룬 것입니다.
그런데 실제 영상이 도착한 뒤에도 입력 조건은 계속 달라질 수 있습니다. 내 영상과 상대방 영상의 크기나 비율이 다를 수 있고, 통화 중 해상도가 바뀌기도 합니다. 이 변화를 그대로 녹화 파일의 화면 크기에 반영하면 입력 하나가 달라질 때마다 그 뒤의 기록 경로까지 영향을 받습니다.
그래서 영상이 들어오는 쪽과 파일에 저장하는 쪽을 나눠 다뤘습니다. 영상 소스나 해상도가 바뀌면, 필요한 경우 화면을 그리는 렌더러(renderer)에 해당 입력을 다시 연결합니다. 입력 영상의 해상도가 바뀌더라도, 파일에 기록하는 최종 프레임의 크기는 유지합니다. 그 안에서 상대방 영상과 내 영상을 화면 영역에 맞춰 렌더링합니다. 이처럼 파일에 담을 최종 화면을 만드는 동안에도 녹화 세션은 유지됩니다.
STT는 서버가 아닌 온디바이스로 처리합니다
지금까지는 통화의 모습이 바뀌어도 녹음/녹화를 진행하는 방법을 살펴봤습니다. 이번에는 대화 내용을 텍스트로 확인하도록 돕는 STT 이야기로 넘어가 보겠습니다. 소리를 켜고 녹음을 듣기 어려운 상황에서도 텍스트로 내용을 살펴볼 수 있는데요. 현재 STT는 음성 통화 구간에서만 동작합니다.
프리미엄콜의 STT에는 자체 온디바이스(on-device) 음성 인식 모델을 사용합니다. 음성 인식을 위해 통화 음성을 서버에 보내지 않고, 기기 안에서 모델이 음성을 받아 텍스트로 바꿉니다.
개발하면서 기술적으로 가장 어려웠던 부분도 이 STT였습니다. 음성 인식을 서버를 거치지 않고 처리하므로, 그 연산까지 통화 중인 디바이스가 감당해야 했습니다. 상대방 목소리를 재생하고 내 음성을 보내면서 녹음도 계속하는 기기에서 음성 인식 모델까지 함께 실행해야 하는데요. 인식이 잘되는 것만으로는 부족했고, 통화 중 기기에 더해지는 퍼포먼스 이슈를 해결하기 위해 신경을 많이 썼습니다.
온디바이스 모델에 적합한 음성을 전달합니다
먼저 살펴본 것은 모델에 넣을 음성 데이터였습니다. 상대방의 목소리는 통화 중에도 깨끗하게 들려야 하고, 녹음도 나중에 다시 들을 수 있도록 충분한 음성 정보를 담아야 합니다. 그렇다고 사람이 듣는 음성과 같은 형식의 음성을 STT 모델에도 그대로 넣어야 하는 것은 아닙니다.
저희가 사용하는 온디바이스 모델은 다운샘플링한 음성으로도 대화 내용을 충분히 텍스트로 바꿀 수 있었습니다. 다운샘플링(downsampling)은 음성 신호의 샘플레이트를 낮춰, 같은 길이의 음성을 더 적은 샘플로 표현하는 처리입니다. 이를 통해 모델에 전달하는 오디오 데이터 양을 줄일 수 있습니다.
그래서 통화 중 재생하는 음성은 고음질로 유지하고, STT에는 모델의 입력 조건에 맞춰 준비한 음성을 사용했습니다. 통화할 때 듣는 음질을 유지하면서, 모델에 전달하는 오디오 데이터 양을 줄인 것입니다.
음성이 들어올 때 엔진에서 두 스트림을 만듭니다
통화 중에는 상대방의 음성 데이터가 계속 들어옵니다. 이렇게 이어서 전달되는 데이터의 흐름을 스트림이라고 하는데요. 저희는 VoIP 엔진 내부의 오디오 처리 과정에서 고음질 스트림과 모델의 입력 조건에 맞춘 최적화 스트림을 함께 준비하도록 했습니다.
내 목소리와 상대방 목소리는 엔진 안에서 각각 필요한 오디오 처리를 거칩니다. 고음질 스트림은 통화 재생에 사용하고, 최적화 스트림은 앱으로 전달해 녹음과 STT에 함께 사용합니다.
음성을 각 용도에 맞게 준비하는 작업은 VoIP 엔진 안에서 처리합니다. 앱은 엔진이 준비한 최적화 스트림을 녹음과 온디바이스 STT에 함께 사용하므로, STT용 음성을 다시 다운샘플링할 필요가 없습니다.
녹음 모듈은 파일을 만드는 일에, STT 모듈은 모델 실행과 인식 결과를 다루는 일에 집중하도록 역할을 정했습니다.
음성 입력과 인식 작업을 나눠 처리합니다
모델에 맞는 음성을 준비한 뒤에도, 실제로 음성을 인식하는 데는 별도의 연산이 필요합니다. 모델의 연산은 여전히 같은 기기에서 수행되므로 음성을 받는 작업과 인식 작업을 비동기로 연결했습니다. 새 음성을 받는 쪽이 인식 완료를 기다리지 않도록 한 것입니다. 두 작업은 동일한 디바이스의 자원을 사용하기 때문에 서로 영향을 줄 수 있고, 인식이 늦어지면 처리할 작업이 쌓일 수 있습니다.
인식할 음성의 구간도 따로 골랐습니다. STT에서는 나와 상대방의 음성을 구분해 인식 작업에 전달하고, VAD(voice activity detection, 음성 활동 감지)를 활용해 말소리가 없는 구간의 불필요한 처리를 줄입니다.
그런데 이 조건을 녹음에도 그대로 적용하면 어떨까요? 앞서 예로 든 통화에서 설명을 들은 사람이 물건을 찾아보느라 잠시 말하지 않는 구간을 생각해 보겠습니다. 말소리가 있는 부분만 이어 붙이면 실제로는 잠시 기다렸다가 이어진 대화가 바로 이어진 것처럼 들릴 수 있습니다.
그래서 STT에서는 말소리가 있는 구간을 골라 처리하더라도, 녹음에는 대화 사이의 침묵도 함께 남깁니다. 같은 음성을 사용하지만, 사람이 다시 들을 기록과 모델이 인식할 입력은 목적에 맞게 다룬 것입니다.
통화 종료와 기록 완성은 다릅니다
통화를 마친 뒤에는 기록한 내용을 다시 확인할 수 있어야 합니다. 하지만 통화가 끝났더 라도 마지막으로 받은 음성과 영상의 처리가 남아 있을 수 있습니다. 새 데이터를 받는 일이 끝났다고 해서, 이미 받은 데이터까지 모두 파일에 반영된 것은 아니기 때문입니다.
녹음/녹화에서는 먼저 새 음성과 영상을 받는 일을 멈춥니다. 이후 남은 미디어 처리를 마친 뒤 파일을 닫고 필요한 자원을 해제합니다. 파일부터 닫으면 아직 처리 중인 데이터를 기록하지 못할 수 있기 때문입니다.
음성 통화 구간에서 모델에 전달한 인식 작업은 통화가 끝난 뒤에도 남아 있을 수 있습니다. STT는 녹화 파일과 별도로 텍스트를 만들기 때문에 남은 작업과 결과를 따로 정리합니다. 자원을 해제할 때는 기다리는 시간에 한도를 두고, 인식이 끝나지 못한 경우도 결과 상태에 반영합니다.
사용자가 보는 화면에서는 파일과 STT 결과의 상태를 모아 보여 줍니다. 통화가 끝난 뒤에는 녹음/녹화 파일과 STT 결과를 각각 마무리해 저장합니다. 통화 종료와 기록 완성을 구분해 표현하는 것입니다.
이처럼 통화 상태와 영상 입력의 준비 여부, 기록 결과의 완성 여부를 따로 관리했습니다.
마치며
프리미엄콜을 개발하면서 ‘통화를 기록한다’는 짧은 말 안에 생각보다 많은 일이 들어 있다는 것을 느꼈습니다. 양쪽 목소리를 담는 것뿐 아니라, 통화 도중 영상이 들어오는 순간과 마지막 데이터를 정리하는 과정까지 이어져야 다시 재생할 수 있는 기록이 되 기 때문입니다.
통화 중 카메라를 켜도 같은 파일에 대화를 이어서 남기고, STT는 자체 온디바이스 모델을 사용해 기기 안에서 처리했습니다. 이때 통화 재생에는 고음질 스트림을 사용하고, 녹음과 STT에는 엔진에서 준비한 최적화 스트림을 함께 사용했습니다. 실시간 미디어와 온디바이스 모델을 함께 다루는 개발자분들께도 이 과정이 작은 참고가 되었으면 좋겠습니다.
통화가 끝나고 다시 듣고 싶은 목소리나 보고 싶은 표정이 남을 때가 있습니다. 프리미엄콜이 그런 순간을 간직하는 데 도움이 되었으면 좋겠습니다. 중요한 내용을 다시 확인할 때도, 소중한 사람과 나눈 대화가 문득 생각날 때도 언제든지 편하게 꺼내 볼 수 있는 프리미엄콜을 앞으로도 많은 관심 부탁드립니다.

