AI 오디오 노이즈 가드 (버징/정적 탐지) — 코드레벨 동작 흐름 P1코드레벨

마지막 업데이트 2026-07-22

작성일: 2026-06-14 갱신일: 2026-06-15 (하드 클리핑 고속 경로 추가) 대상: 개발자 — 핑퐁이 출력 기계음 탐지 로직 파악 핵심 파일: hooks/use-ai-audio-noise-guard.ts

개요 · 범위

핑퐁이(AI) 출력 음성에 섞이는 기계음/버징/정적을 실시간 탐지하는 가드. OpenAI 출력 스트림을 Web Audio analyser로 분석하며, 같은 50ms 루프(analyzeFrame)·같은 buffer 위에서 서로 독립된 두 경로로 판정한다.

경로판정 기준확정 지연
① CV(변동계수) 경로 — 주음량(RMS) 변동이 작고 균일(CV)+ZCR. 1차 링버퍼+2차 재검증, PLC 억제 적용~900ms
② 하드 클리핑 고속 경로 — 보조crest(peak/RMS)·풀스케일 샘플 비율. 재검증 없이 즉시 확정, PLC 억제 미적용~150ms

어느 경로든 통과하면 onNoiseDetected() 콜백만 호출한다.

탐지만, 억제는 안 함: 이 훅은 노이즈를 감지해 알리는 진단/대응 트리거일 뿐, 오디오를 직접 고치거나 음소거하지 않는다. 후속 동작(세션 재시작·알림 등)은 콜백 소비자가 결정. gpt-realtime 정적 이슈·PLC 오탐와 직접 연계.

① CV 경로 판정 지표 (왜 이 3개인가)

지표역할임계
CV (변동계수, std/mean)주 기준. 정상 음성은 진폭 변동 큼(CV~0.85), 기계음은 균일(CV~0.27)cv < 0.35
ZCR (영교차율)보조. 너무 매끈한 저주파/안정 TTS 구간 오탐 방지zcr ≥ 0.04
RMS (dBFS)무음 필터 전용(상한 없음 — 기기별 레벨 차 무관)> -50dB만 판정

CV는 최근 8프레임(400ms) linear RMS 슬라이딩 윈도우로 계산. 한 프레임이 "기계음 프레임"이 되려면 윈도우 충분 + CV<0.35 + ZCR≥0.04 모두 충족.

① CV 경로 — 다단계 판정 파이프라인 analyzeFrame L321–494 (50ms 주기)

매 50ms: 유예(발화 후 500ms)·무음(-50dB)·PLC active 면 → 후보 제외 CV/ZCR로 isNoiseFrame 판정 → PLC면 무효(isEffectiveNoiseFrame) ┌ 1차: 링버퍼 14프레임(700ms) 중 10프레임(71%) 이상 노이즈 │ → "후보 감지" → 재검증 모드 진입(링버퍼 초기화) └ 2차: 재검증 4프레임(200ms) 중 3프레임 이상 노이즈 → 쿨다운(10s) 통과 시 확정 → onNoiseDetected()
  1. 1유예(grace) 500ms: AI 발화 시작 직후 과도기는 판정 건너뜀(발화 시작 시 모든 상태 초기화).
  2. 2무음 필터: RMS < -50dB면 제외.
  3. 3프레임 판정: CV<0.35 ∧ ZCR≥0.04 ∧ 윈도우 충분 → noiseFrame. PLC active면 무효화.
  4. 41차(링버퍼): 14프레임 중 ≥10 노이즈 → 즉시 확정하지 않고 재검증 모드 진입.
  5. 52차(재검증): 4프레임 중 ≥3 노이즈면 통과 → 쿨다운(10s) 확인 후 확정·콜백. 실패하면 정상 분석 복귀.
왜 2단계인가: 1차 즉시 중단 대신 200ms 재확인을 두어 순간적 오탐을 거른다(리뷰 반영). 확정 후 10초 쿨다운으로 연속 발동 방지.

② 하드 클리핑(지지직) 고속 감지 경로 analyzeFrame L385–424 · calculateClipFeatures L214–238

OpenAI Realtime 디코더가 간헐적으로 출력을 풀스케일로 깨뜨리는(하드 클리핑) 경우, CV 경로(~900ms)로는 대응이 느리다. 그래서 같은 analyzeFrame 루프 안에서 CV와 완전히 독립으로 도는 고속 경로를 둔다. 같은 buffer를 crest factor·풀스케일 샘플 비율로 직접 판정한다.

지표역할임계
crest (peak/RMS, dB)정상 음성 12–18dB vs 클리핑 3–6dB. 파형이 천장에 눌리면 crest 급감crest < 6dB
풀스케일 샘플 비율|sample| ≥ 0.98 인 샘플 비중. 천장에 붙은 샘플이 얼마나 많은지ratio ≥ 0.01 (1%)
매 50ms (CV 판정 직후, 같은 buffer): calculateClipFeatures(buffer, rmsDb) → crestDb, clipRatio isClipFrame = clipRatio≥0.01 ∧ crestDb<6 clipRing(4프레임) 중 ≥3 → 즉시 확정 (재검증 없음) → 쿨다운(10s) 통과 시 onNoiseDetected() · return
  • 재검증 없음: 클리핑은 신호가 명확해 CV 경로의 200ms 재검증을 생략하고 4프레임(≈200ms)만에 확정. 지연 ~900ms → ~150ms.
  • PLC 억제 대상 아님: 클리핑은 네트워크 패킷손실 보정이 아니라 디코더 왜곡이므로 PLC active 구간에도 억제하지 않는다(raw 판정).
  • CV 경로보다 우선: 클리핑 확정 시 즉시 return하며, 진행 중이던 CV 재검증 상태(isReverifyingRef)도 강제 초기화한다.
두 경로 = 하나의 루프, 같은 buffer: CV(변동계수)·하드클리핑(crest)은 별도 루프가 아니라 동일 analyzeFrame이 같은 2048 샘플 buffer를 각자 다른 방식으로 계산한다. 누적 버퍼만 분리(CV: noiseFrameRing 14 + 재검증 4 / 클리핑: clipRing 4).

PLC 완화 — 가장 중요한 오탐 방지 samplePlcState L199–277

WebRTC/Chrome이 패킷 손실을 보정(PLC)하면서 만드는 균일 반복 파형은 CV 기준으로 기계음처럼 보인다. 즉 "진짜 OpenAI 기계음"과 "네트워크 손실 보정 파형"이 헷갈린다. 이를 막으려고 inbound 통계로 PLC 활성 구간을 추정해 그 구간의 노이즈 후보를 무효화한다.

getAudioInboundStats()(주입)를 500ms마다 읽어 직전 대비 델타로 판정. 아래 중 하나라도 충족하면 PLC active(TTL 1.5s):

지표임계
concealedSampleRatio (보정 샘플 비율)≥ 0.03
concealmentEvents 델타≥ 2
packetsLost 델타≥ 3
jitter≥ 80ms
실패 시 active 유지: getStats()가 일시 실패(ICE 재연결 등)해도 active를 false로 덮지 않는다 — 그 순간이 바로 네트워크 불안정 구간이라, 끄면 PLC 보호가 필요한 때 풀리는 역설이 생긴다.

라이프사이클 · 입출력

  • 입력: aiAudioStream(OpenAI/릴레이 음성), isPeerTalking, enabled, onNoiseDetected, getAudioInboundStats?.
  • AudioContext/Analyser(FFT 2048, smoothing 0.3)는 enabled+stream일 때 생성, source.connect(analyser)만(재생 경로와 무관한 read-only 탭).
  • 분석 루프isPeerTalking일 때만 50ms setInterval. 발화 종료 시 정지.
  • PLC 샘플러는 isPeerTalking + getAudioInboundStats 있을 때 500ms 주기.
  • 반환: { isNoiseDetected, detectionCount }.

함정 · 주의

  • 탐지 전용: 음질을 고치지 않는다. 콜백 소비자의 대응이 없으면 사용자 체감 변화 없음.
  • PLC stats 주입 필수: getAudioInboundStats 미제공 시 PLC 완화가 꺼져 네트워크 불안정 구간에서 오탐 급증(PLC 오탐 분석).
  • 임계값은 파형 분석 기반: CV 0.35/ZCR 0.04/링버퍼 10/14, 클리핑 crest 6dB/풀스케일 1%/링버퍼 3/4 등은 실측 튜닝값. 변경 시 오탐/미탐 트레이드오프 직결.
  • 클리핑 경로는 PLC 무방비: 고속 경로는 PLC 억제를 받지 않으므로, 만약 패킷손실 보정 파형이 풀스케일까지 치솟는 환경이 생기면 오탐 가능성 있음(현재까진 디코더 클리핑에 한정 관찰).
  • analyser는 read-only 탭이지만 같은 라이브 원격 스트림을 Web Audio로 끌어들이는 행위 자체의 영향은 별도 검토 필요(#1/RMS 립싱크 정적 이슈 참고).
  • 발화 경계 의존: isPeerTalking 토글 정확도에 분석 시작/리셋이 묶여 있다. 발화 상태가 부정확하면 유예/리셋이 어긋난다.

파일 · 라인 레퍼런스

파일/심볼역할
use-ai-audio-noise-guard.ts가드 본체
analyzeFrame (L321–494)50ms 루프 — CV 1차/2차 + 하드 클리핑 고속 경로 동시 처리
하드 클리핑 판정 (L385–424)clipRing 누적·즉시 확정(재검증·PLC 억제 없음)
calculateClipFeatures (L214–238)crest(dB)·풀스케일 샘플 비율 계산
samplePlcState (L240–318)inbound stats로 PLC 활성 추정(CV 경로 전용)
calculateCV/ZCR/RmsDb (L167–211)CV 경로 지표 계산
상단 상수 (L9–65)임계값 일람(CV·클리핑 튜닝 지점)