바지인·재생 제어¶
Gateway는 사용자 발화 후보와 TTS playout을 같은 세션 상태기계에서 조정합니다. 핵심은
speech_candidate는 아직 확정 발화가 아니므로 내용을 즉시 버리지 않고 출력을 낮추거나
일시정지하고, 확정 뒤에만 현재 응답을 취소하는 것입니다.
stateDiagram-v2
[*] --> Normal
Normal --> Attack: speech_candidate
Attack --> Ducked: target gain 도달
Ducked --> HoldRelease: candidate_cancelled
HoldRelease --> Release: 최소 hold 소진
Release --> Normal: 150 ms ramp 완료
Attack --> Cancelled: 발화 확정
Ducked --> Cancelled: 발화 확정
Cancelled --> Normal: generation 증가·queue 무효화
Normal --> Paused: 실험군 + pause 창 안 candidate
Paused --> Release: candidate_cancelled
Paused --> Cancelled: 발화 확정
Paused --> Ducked: pause 상한 초과
duck 계약¶
재생 중 speech_candidate가 오면 40 ms attack으로 TTS_DUCK_CANDIDATE_GAIN까지 낮춥니다.
목표 gain이 실제 outbound frame에 적용된 뒤 /sohri로 다음 ACK를 보냅니다.
이 ACK는 단순 관측 이벤트가 아니라 EPD와의 제어 계약입니다. EPD는 후보와 activityId를 연결하고
ACK 시점으로 판정을 진행하므로 불필요한 대기로 늦춰서는 안 됩니다. 연속 후보는 같은 duck
덩어리로 병합하고, 취소 시 최소 hold 후 150 ms release ramp로 원래 음량을 복원합니다. 발화가
확정되면 release하지 않고 응답·재생을 취소합니다.
첫 가청 TTS 직후 INITIAL_PLAYBACK_GRACE 동안의 후보는 AEC 안정화용으로 의도적으로 무시합니다.
기본값은 2초이며, 이 구간의 후보가 duck ACK를 만들지 않는 것은 결함이 아닙니다.
선택적 pause A/B¶
현재 통합 브랜치는 초기 TTS 구간의 후보에서 duck 대신 재생 위치를 멈추는 기능을 포함합니다.
다만 표준 기본값 TTS_PAUSE_WINDOW=0s에서는 비활성입니다.
- B군은 설정한 가청 누계 window 안의 후보에서 gain 0으로 정지하며 PCM offset을 전진시키지 않습니다.
- 오탐 취소 시 정지 위치부터 150 ms ramp로 이어 재생하므로 duck 중 흘러가 버리는 내용을 보존합니다.
- A군은 기존 duck 동작을 유지합니다.
- 군은 Gateway session ID hash와
TTS_PAUSE_AB_PERCENT로 결정적으로 배정합니다. TTS_PAUSE_MAX를 넘으면 갇힘 방지를 위해 pause를 풀고 기존 duck으로 강등합니다.- pause 중에도
tts_playback_state.active는 true입니다. “소리는 없지만 출력 세션은 진행 중”이라는 EPD 상태를 보존하기 위해서입니다.
pause가 EPD의 후보 검출 지연을 없애지는 않습니다. 실험은 내용 보존과 attack 구간을 개선하는 레버이며, 전체 barge-in latency 개선으로 과대 해석해서는 안 됩니다.
playout generation¶
모든 TTS job에는 enqueue 당시 generation이 붙습니다. response.cancel,
output_audio_buffer.clear, 확정 바지인, provider interrupt에서 generation을 증가시킵니다.
이전에 큐에 들어온 job은 나중에 dequeue되더라도 트랙에 쓰기 직전 stale_generation으로
폐기됩니다.
이 설계는 채널을 비우는 작업과 동시에 backend read가 새 frame을 넣는 race에서 취소된 음성이
다시 재생되는 것을 막습니다. stream sentinel도 같은 큐를 지나가므로 정상 완료와 중단
(sohri.tts_stream_end/aborted)을 실제 playout 결과와 맞춥니다.
tts_playback_state¶
첫 가청 frame을 실제 트랙에 쓸 때 /sohri로 tts_playback_state{active:true}를 한 번 보내고,
스트림을 소진·중단할 때 active:false를 한 번 보냅니다. raw PCM 청크 경계마다 true/false를
반복하지 않습니다. 완전 무음 스트림은 가청 시작 신호를 만들지 않습니다.
스트리밍 TTS가 completed sentinel까지 정상 소진되면 tts_playback_completed ACK를 보냅니다.
큐 포화로 sentinel이 의도적으로 폐기되면 유휴 시 state는 false로 복구되지만 완료 ACK는 없을 수
있으며, 메트릭과 경고로 드러납니다.
half-duplex 입력 게이트¶
BARGE_IN_ENABLED=false이면 TTS 출력 동안 마이크 PCM을 /sohri로 보내지 않습니다. 이것은
codec 오류가 아니라 의도된 half-duplex drop입니다.
MIC_PREROLL_MS=0: TTS 중 입력을 모두 폐기- 양수: 최신 PCM ring buffer만 보존하고 TTS 종료 후 먼저 전송
- 범위: 0..5000 ms, 100 ms 단위
- preroll은 barge-in이 비활성일 때만 설정 가능
의도적 drop을 오류와 구분하기¶
다음은 상태 안전성을 위한 정상 폐기 사유가 될 수 있습니다.
- 취소 전 generation의 TTS 또는 OpenAI PCM
- provider interrupt 뒤 늦게 도착한 PCM
- playout queue 포화 시 새 청크 또는 종료 sentinel
- 활성 response 없이 도착한 SOHRI TTS
- half-duplex TTS 구간의 mic PCM
- 초기 보호 구간의 speech candidate
반복 비율이 높으면 운영 문제이므로 운영·메트릭의 reason label과 테스트·문제 해결을 함께 확인하세요.