PPI · FEATURES · VOICE OBSERVABILITY

PPI-1374 AI 발화 무음 구간 판별 — 송출·수신·재생 진단 로그

마지막 업데이트 2026-10-14

작성 2026-10-14 코드 커밋 fb28c536 PR #1169 실기기 검증 미실행
한 줄 요약. AI 음성이 생성됐는데 아동에게 안 들리는 경우, 무음이 ① Agent 송출 → ② LiveKit 전달·브라우저 수신 → ③ 브라우저 재생 중 어디서 시작됐는지 로그만으로 가릅니다. 세 구간이 모두 정상이면 남는 것은 로그 밖의 ④ 단말 출력입니다. 측정하지 못한 구간은 0이 아니라 사유(measurement)로 기록합니다.

1. 코드 경로

파일역할
apps/livekit-agent/agent.py:1686 _sample_tts_audio_outbound첫 TTS 프레임부터 1초 동안 로컬 오디오 트랙의 RTP 송출 증가량을 측정합니다. 호출 위치는 tts_node 첫 프레임 :2110
apps/livekit-agent/agent.py:1618 _is_audio_publicationpublication kind를 rtc.TrackKind.KIND_AUDIO(int)로 비교합니다. 기존 문자열 비교 버그도 이 함수로 함께 고쳤습니다
apps/web/lib/voice-agent/livekit-ai-speech-delivery-probe.ts (신규)Agent가 speaking일 때마다 1초 동안의 수신량·에너지·재생 진행량을 측정합니다. 수신이 0이면 한 번 더 확인합니다
apps/web/lib/voice-agent/livekit-playout-drift-monitor.ts:110inbound-rtp 통계에 packetsReceived, packetsLost, bytesReceived, totalAudioEnergy를 추가했습니다. drift 판정 로직은 그대로입니다
apps/web/lib/voice-agent/livekit-client-session.ts:2302, :3456probe를 생성하고 lk.agent.state 속성 변경에 연결합니다. play 결과는 :3376~3399에서 전달하고, 종료·disconnect 시 :3003, :3261에서 probe를 멈춥니다

2. 목적과 범위

3. 입출력 흐름 (한 발화 기준)

Agent tts_node 첫 프레임 yield
  └─ agent.py:2110  _start_outbound_sample(agent_response_id)  ← 백그라운드 task, 파이프라인 비차단
       get_stats() → 1초 sleep → get_stats()
       → tts_audio_outbound_sample  packets_sent_delta / bytes_sent_delta / track_sid
                 │
                 ▼  (RTP)  LiveKit SFU
                 │
브라우저: Agent 참가자 속성 lk.agent.state = "speaking"
  └─ livekit-client-session.ts:3456  onAgentSpeakingChanged(true)
       기준값: inbound-rtp + audio element 상태
       1초 뒤 재측정
         ├─ 수신 증가 있음          → AI speech delivery sample (info)
         ├─ 수신 0 + 아직 speaking → AI speech delivery inbound stalled (warn) → 1초 뒤 재확인
         │                             → 최종 sample (회복=info / 계속 0=warn, attempt=2)
         └─ 1초 전에 speaking 종료  → 종료 시점까지로 정리 (trigger=speech_ended)

4. 로그 필드

Agent — tts_audio_outbound_sample

필드의미
agent_response_id, track_sid발화와 송출 트랙. 브라우저 쪽 trackSid, responseId와 맞춰 봅니다
packets_sent_delta, bytes_sent_delta1초(window_ms) 동안의 outbound-rtp 증가량. 0이면 WARN
measurementok / no_audio_track / stats_unavailable(+error_class). ok가 아니면 delta는 null입니다

브라우저 — AI speech delivery sample (logger LIVEKIT_AI_SPEECH_DELIVERY)

필드의미
packetsReceivedDelta, bytesReceivedDelta, packetsLostDelta소리 데이터가 도착했는가
totalAudioEnergyDelta, concealedSamplesDelta도착한 데이터가 실제 소리인가, 아니면 무음 샘플이거나 은닉(손실 보정)인가
currentTimeDeltaSec, paused, muted, volumeaudio element 재생이 진행됐는가
outputBlocked, lastPlayResult앱이 출력을 막았는가, play()가 resolved / blocked / rejected(+errorName) 중 무엇이었나. 이전에는 dev에서만 기록됐는데 이제 prod에도 남습니다
measurement, trigger, attempt, windowMs, wallClockMs측정 가능 여부(ok / no_agent_track / stats_unavailable), 측정 계기, 재확인 차수, 실제 측정 길이, 벽시계 시각

5. 판정표 — 의심 구간은 로그로 3군데, 로그 밖에 1군데

구간판정 조건다음에 볼 곳
① Agent → LiveKit 송출생성 프레임은 있는데 packets_sent_delta = 0Agent 출력 큐, AudioSource, 송출 트랙
② LiveKit → 브라우저 수신송신은 늘었는데 packetsReceivedDelta = 0LiveKit 서버 전달, 구독 상태, 네트워크 경로
②-a 무음 샘플패킷은 늘었는데 totalAudioEnergyDelta ≈ 0Agent가 무음을 보냈는지, 디코딩 쪽 문제인지 먼저 구분
③ 브라우저 재생수신·에너지는 정상인데 play 거부, muted/paused, outputBlocked, 재생시간 정지 중 하나브라우저 재생 정책·상태
④ 단말 출력 (로그 밖)위 조건이 모두 정상기기 볼륨, 블루투스·이어폰 등 출력 장치
한 단계 앞 구간을 먼저 정상으로 확인하고 다음 구간으로 넘어갑니다. 예를 들어 Agent 송신량이 0이면 브라우저 수신 0은 그 결과일 뿐입니다.

6. 주의사항

검증

7. 관련 문서