본문으로 건너뛰기
김신건의 로그

[Voice AI] VAD: Silero, 에너지 기반, endpointing 임계값

· 수정 · 📖 약 1분 · 464자/단어 #vad #silero #voice-activity #endpointing #audio
VAD, Voice Activity Detection, Silero VAD, WebRTC VAD, 음성 활동 감지, endpointing, energy-based VAD, neural VAD

정의

VAD (Voice Activity Detection) = 오디오 스트림에서 음성 vs 비음성 (침묵, 노이즈) 구분. 음성 에이전트의 발화 시작 / 종료 감지에 필수.

종류

flowchart TB
    VAD[VAD 종류]
    VAD --> Energy["Energy-based<br/>(RMS, ZCR)"]
    VAD --> Spectral[Spectral feature]
    VAD --> Neural["Neural network<br/>(Silero, Marble)"]
    VAD --> Webrtc["WebRTC VAD<br/>(GMM)"]
    Energy --> Simple["단순, 빠름, 노이즈에 약함"]
    Neural --> Robust["robust, 정확, 작은 모델"]
VAD정확도속도노이즈 견고성
Energy (RMS)낮음매우 빠름X
WebRTC VAD (GMM)보통빠름보통
Silero VAD높음빠름 (2MB)우수
Marble VAD매우 높음보통우수
Whisper VAD매우 높음느림우수

Silero VAD (사실상 표준)

import torch
torch.set_num_threads(1)

model, utils = torch.hub.load(
    repo_or_dir='snakers4/silero-vad',
    model='silero_vad',
    onnx=False,
)

# 32ms 청크 (16kHz, 512 sample) 단위로 처리
def is_speech(audio_chunk: torch.Tensor) -> float:
    """0.0 ~ 1.0 (음성 확률)"""
    return model(audio_chunk, 16000).item()

# 실시간 처리
chunk_size = 512   # 32ms at 16kHz
threshold = 0.5    # 0.5 이상 → 음성
for chunk in audio_stream:
    prob = is_speech(chunk)
    if prob > threshold:
        on_voice_active()
    else:
        on_silence()

Silero VAD 특징

속성
모델 크기2MB (ONNX)
입력32ms 청크 (512 samples at 16kHz)
추론 시간< 1ms on CPU
정확도96%+ on LibriSpeech
라이센스MIT

IMPORTANT

2026 시점 voice agent VAD 의 사실상 표준. Silero VAD 가 Pipecat, LiveKit Agents, faster-whisper-streaming 등의 기본.

Endpointing (발화 종료 결정)

flowchart LR
    Audio[오디오 stream] --> VAD
    VAD --> State{현재 상태}
    State -->|음성 중| Speak[Speaking]
    State -->|침묵 N ms 누적| End[Speech ended → final transcript trigger]
    Speak -->|침묵 시작| Track[Silence tracker]
    Track -->|N ms 경과| End
    Track -->|다시 음성| Speak
class EndpointDetector:
    def __init__(self, silence_threshold_ms=700):
        self.silence_ms = 0
        self.threshold = silence_threshold_ms
        self.in_speech = False

    def feed(self, vad_prob: float, chunk_ms: int = 32):
        is_voice = vad_prob > 0.5
        if is_voice:
            self.silence_ms = 0
            self.in_speech = True
        else:
            if self.in_speech:
                self.silence_ms += chunk_ms
                if self.silence_ms >= self.threshold:
                    self.in_speech = False
                    return "speech_ended"
        return None

Endpointing 임계값 튜닝

시나리오silence ms
빠른 대화 (FAQ 봇)300-500ms
일반 대화500-800ms
사용자가 천천히 말함 (고연령)1000-1500ms
전화번호 / 주소 dictation시맨틱 endpointing (순수 VAD 부적합)

CAUTION

순수 VAD endpointing 의 한계: “내 번호는 010…” [짧은 침묵] → final → 사용자 끊김. 시맨틱 turn detection 으로 보완. 자세한 건 turn-detection-barge-in.

노이즈 감쇠 전처리

flowchart LR
    Mic --> NS["Noise Suppression<br/>(RNNoise, Krisp, getUserMedia)"]
    NS --> VAD
    VAD --> STT
도구의미
getUserMedia({ noiseSuppression: true })브라우저 내장
RNNoiseOSS, 신경망 기반
Krisp SDK상용, 고성능
DeepFilterNetOSS, real-time

VAD 실패 시나리오

flowchart TD
    Fail[VAD 실패 케이스]
    Fail --> Bg["배경 음성 (TV, 다른 사람)"]
    Fail --> Whisper["속삭임"]
    Fail --> Music[배경 음악]
    Fail --> NonSpeech["기침, 한숨, 침묵 호흡"]
    Fail --> Bg --> Sol1["방향성 마이크 + 화자 분리"]
    Fail --> Whisper --> Sol2["임계값 낮춤"]
    Fail --> Music --> Sol3["음악 vs 음성 분류기"]

getUserMedia 옵션

const stream = await navigator.mediaDevices.getUserMedia({
  audio: {
    echoCancellation: true,
    noiseSuppression: true,
    autoGainControl: true,
    sampleRate: 16000,
    channelCount: 1,
  },
});

브라우저 내장 신호처리 가 기본. 일부 OS 는 덮어쓰기 안 됨 (Chrome 의 hardware AEC).

VAD + STT 통합 패턴

flowchart LR
    Mic --> VAD
    VAD -->|음성 시작| Open[STT WebSocket 연결]
    VAD -->|음성 청크| Forward[STT 로 전달]
    VAD -->|침묵 N ms| Close[STT close → final]
    Close --> LLM
async def voice_pipeline(mic_stream):
    vad = SileroVAD()
    endpoint = EndpointDetector(silence_threshold_ms=700)
    stt_ws = None

    async for chunk in mic_stream:
        prob = vad.predict(chunk)

        if prob > 0.5 and not stt_ws:
            stt_ws = await connect_stt()

        if stt_ws:
            await stt_ws.send(chunk)

            transcript = await stt_ws.recv()
            if transcript.is_final:
                yield transcript.text

        result = endpoint.feed(prob)
        if result == "speech_ended":
            await stt_ws.close()
            stt_ws = None

흔한 함정

WARNING

  1. 에너지 기반 VAD 의 한계 = 카페 / 키보드 소리에 활성. Silero 권장.
  2. 임계값 0.5 그대로 = 환경에 따라 조정. 조용한 곳 은 0.3, 시끄러운 곳 은 0.7.
  3. VAD 없이 STT = 침묵 동안 전사 못 끝남 + 비용 폭증.
  4. chunk size 가 16/32ms 가 아님 = Silero 가 512 samples (32ms at 16kHz) 만 받음. resampling 필요.

관련 위키

이 글의 용어 (4개)
[Voice AI] 음성 에이전트 아키텍처: Cascading / Streaming / S2S + Latency Budgetai
정의 실시간 음성 AI 의 3가지 패턴. 각자 지연 vs 유연성 vs 비용 트레이드오프. 3 패턴 비교 | | Cascading | Streaming | S2S | |---|--…
[Voice AI] STT 스트리밍: partial vs final, endpointing, gRPC/WSvoice
정의 스트리밍 STT = 오디오 청크 가 들어올 때마다 부분 결과 (partial) 를 즉시 반환 + 발화 종료 시 최종 결과 (final) 확정. [!IMPORTANT] 대화형…
[Voice AI] Turn Detection + Barge-in + AEC: 자연스러운 대화voice
정의 자연스러운 음성 대화의 3 요소: 1. Turn Detection - 사용자가 말끝났는지 정확히 판단 2. Barge-in - 에이전트가 말하는 중에 사용자가 끼어들 수 있…
[Voice AI] WebRTC: PeerConnection, ICE, STUN/TURN, SFUnetwork
정의 WebRTC = 브라우저 / 모바일 P2P 실시간 미디어 + 데이터 표준. 음성, 비디오, 데이터 채널. 2026 시점 voice agent + 영상 통화 + 화면 공유 의…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기