마지막 업데이트 2026-08-20
livekit-agent 서버다. Agent가 합성 음성을 LiveKit Room에 올리면 게스트가 직접 듣고, 게스트는 그 AI 오디오를 Mediasoup에 다시 publish하여 모니터에게 전달한다.AudioEmitter.push()| 구분 | LiveKit Room | Socket.IO / Mediasoup Room |
|---|---|---|
| 목적 | 게스트와 AI Agent의 실시간 음성 대화 | 게스트 수업 미디어를 모니터에 공유 |
| 참가자 | LiveKit Agent + 게스트 | 게스트 + 모니터 |
| AI 오디오 | Agent가 publish, 게스트가 직접 subscribe | 게스트가 재발행, 모니터가 consume |
| 모니터 입장 | 입장하지 않음 | 입장함 |
| 관리 서버 | LiveKit 서버 | apps/socket + Mediasoup |
client.stream("POST", ...)로 Typecast에 합성을 요청한다.
resp.aiter_bytes(4096)가 열린 HTTP 응답에서 최대 4KB씩 읽는다. 4KB마다 새 POST를 보내는 것이 아니다.
WavStreamParser.feed()가 WAV 컨테이너에서 재생 가능한 PCM payload를 추출한다.
output_emitter.push(pcm)는 스피커 재생이 아니라 LiveKit Agents 내부 비동기 채널에 PCM을 넣는다.
LiveKit이 PCM을 AudioFrame으로 만들고 RoomIO의 프레임 큐와 rtc.AudioSource에 전달한다.
Agent의 LocalAudioTrack을 LiveKit Room에 publish하고 게스트가 RemoteAudioTrack으로 구독한다.
Typecast WAV bytes → WavStreamParser.feed(chunk) → output_emitter.push(pcm) → SynthesizedAudio / rtc.AudioFrame → RoomIO.capture_frame(frame) → rtc.AudioSource(queue_size_ms=200) → LocalAudioTrack.publish_track(...) → Guest RemoteAudioTrack
근거: apps/livekit-agent/typecast_tts.py · LiveKit Agents tts/tts.py · voice/generation.py · voice/room_io/_output.py
queue_size_ms=200은 최대 출력 큐 용량이며, 항상 200ms를 먼저 채우고 시작한다는 뜻은 아니다.| 관측 | 우선 조사 범위 | 판정 강도 |
|---|---|---|
| 게스트·모니터 모두 같은 끊김/이상음 | Typecast → Agent → LiveKit → 게스트 수신까지의 공통 경로 | 모니터 단독 재생 문제 가능성 낮음 |
| 게스트 정상, 모니터만 이상 | 게스트 AI 오디오 브리지 → Mediasoup producer/consumer | LiveKit Agent 원인 가능성 낮음 |
| 중간 무음 반복 | Typecast 청크 간격, Agent 이벤트 루프, AudioSource 큐 잔량 | 언더런 후보지만 청크 로그 없으면 미확정 |
| 실제 드르륵 파형 출력 | ①~③ 원본 오디오 단계별 저장·비교 | 단순 언더런만으로 확정 불가 |
AI_SPEAKING_START/STOP과 오디오 레벨은 끊김을 보여주지만 Typecast 청크 도착 시각이나 실제 출력 큐 잔량을 직접 증명하지 않는다. 근본 위치를 확정하려면 ① Typecast WAV, ② Agent PCM, ③ 게스트 수신 오디오를 같은 응답 ID로 저장해야 한다.