
Terraform으로 전화망에 통화 분석 AI의 토대를 구축하기 ― Amazon Connect+KVS와 「CodecID 사칭 사건」
요약
Amazon Connect와 Kinesis Video Streams를 활용하여 실시간 통화 분석 AI 인프라를 Terraform으로 구축하는 과정을 다룹니다. 화자 분리 문제를 해결하는 방법과 미디어 스트리밍 설정 시 발생하는 기술적 함정, 그리고 잘못된 CodecID로 인한 디코딩 오류 해결 방법을 공유합니다.
핵심 포인트
- Amazon Connect를 사용하면 화자 분리(Diarization) 없이 별도 트랙으로 음성 수집 가능
- Terraform 사용 시 API 레퍼런스와 프로바이더 간의 열거형 이름 차이 주의
- 콜 플로우 JSON은 공식 문서보다 실제 작동하는 샘플을 역산하는 것이 효율적
- KVS 스트림의 CodecID 사칭 문제(AAC로 표시되나 실제는 L16 PCM) 해결법 제시
통화 상대방의 심리 상태를 AI가 실시간으로 읽는 프로덕트(crossbar_telepath)를 만들기 시작했다. 설계 사상으로서, 접속 시 녹음·해석을 안내(announcement)로 선언하는 것을 처음부터 요건으로 하고 있다(스텔스형으로 귀띔하는 AI가 논란이 된 사례의 전철을 밟지 않는다).
PH1의 목표는 "전화를 걸면 동의 안내가 흐르고, 통화 음성이 Kinesis Video Streams(KVS)에 쌓인다"까지. 그 과정에서 빠졌던 함정이 흥미로웠기에 공유한다.
화자 분리는 "사는 것"
Amazon Connect를 선택한 가장 큰 이유는, 화자 분리(Speaker Diarization)라는 난제가 사라진다는 점이다. 통화 음성에서 "누가 언제 말했는가"를 추정하는 화자 분리는 확률적 처리로, 오분리가 반드시 섞인다. 하지만 Connect의 미디어 스트리밍(Media Streaming)은, 자신 측과 상대 측의 음성을 처음부터 별도의 트랙으로 KVS에 흘려보내 준다. 교환기가 별도 선으로 전달해 주는 것을, 굳이 섞었다가 다시 추정할 이유가 없다.
Terraform은 실질적으로 4개의 리소스
| 리소스 | 역할 |
|---|---|
| aws_connect_instance | 국사 |
| ... |
에이전트(인간)는 제로. 착신은 IVR이 자동 응답하므로, 수신 요원이 필요 없는 콜센터가 성립한다.
함정: 미디어 스트리밍 설정의 열거형 이름이 API 레퍼런스(LIVE_MEDIA_STREAMS)와 Terraform 프로바이더(MEDIA_STREAMS)에서 다르다. 전화번호는 재고의 레이스 컨디션(Race Condition) 때문에 첫 번째 apply가 실패한다(재시도하면 다른 번호를 잡을 수 있다).
콜 플로우(Call Flow) JSON은 "작동하는 실물"에서 역산한다
가장 시간을 많이 잡아먹은 것이 이 부분이다. Terraform은 새로운 플로우 언어(Version: 2019-10-30)를 요구하지만, 공식 문서는 리다이렉트 지옥이고, 웹 검색으로 얻을 수 있는 샘플의 대부분은 API에 넣을 수 없는 구식 형식이다. 결국 확실했던 것은, AWS 공식 샘플과 Deepgram사의 연동 리포지토리에 놓여 있는 작동하는 JSON으로부터 역산하는 것이었다.
또 다른 정석은 "무음 SSML 루프"다: 스트리밍 시작 후 플로우가 종단에 도달하면 호출이 끊기므로, <break time="5000ms"/>를 12개 나열한 무음 프롬프트를 Loop로 돌려 통화를 유지한다. 바보 같지만 효과적이다.
CodecID 사칭 사건
녹음된 음성을 들으려 했더니, ffmpeg가 모든 프레임에서 디코딩 에러를 냈다. ffprobe는 Audio: aac라고 보고하지만, KVS의 스트림 정보는 audio/L16(생 PCM)이라고 한다. 둘 중 하나는 거짓말을 하고 있다.
MKV를 바이너리로 열어보니, 범인은 컨테이너였다. 트랙의 CodecID가 A_AAC라고 주장하고 있는데, 페이로드(Payload)는 생 L16 PCM(8kHz/16bit/mono)이었다. ffmpeg는 성실하게 CodecID를 믿고 AAC 디코더에 넣었다가 전멸했다. 사칭하고 있는 것은 Connect 측이며, ffmpeg는 억울한 상황이다.
대책은 EBML(MKV의 하위 구조)을 직접 탐색하는 것이다. Python의 ebmlite로 파싱하고, SimpleBlock의 앞 4바이트를 떼어내면 나머지가 그대로 PCM이다. 트랙 번호로 분류하여 WAV 헤더를 붙이는 작업은 60줄 정도로 작성할 수 있었다.
트랙 대응은 MKV의 Tracks 요소에서 확정: 트랙 1 = AUDIO_TO_CUSTOMER, 트랙 2 = AUDIO_FROM_CUSTOMER. 추출한 자신의 목소리가 8kHz의 전화 품질로 돌아왔고, "화자 분리는 살 수 있다"라는 근간 가설이 실제 데이터로 뒷받침되었다.
요약
- 콜 플로우 JSON은 문서보다 "작동하는 실물"에서 역산할 것
- KVS의 MKV는 CodecID를 믿지 말 것 (
A_AAC의 내용은 생 L16) - 검증 후에는
terraform destroy로 모두 삭제 (전화번호는 일일 과금)
상세 버전(구성도, 무음 검출을 통한 대응 조사 등)은 원문 블로그에서 확인하세요:
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기