실시간 오디오 처리

배치 파이프라인(Batch pipeline)은 파일을 처리합니다. 실시간 파이프라인(Real-time pipeline)은 다음 20밀리초가 도착하기 전에 현재 20밀리초를 처리합니다. 대화형 인공지능(Conversational AI), 방송 스튜디오(broadcast studio), 텔레포니 봇(telephony bot)은 모두 이 지연 시간 예산(latency budget) 위에서 생사가 갈립니다.

유형: Build 언어: Python, Rust 선수 강의: Phase 6 · 02 (Spectrograms), Phase 6 · 04 (ASR), Phase 6 · 07 (TTS) 예상 시간: 약 75분

학습 목표

  • 대화형 인공지능(Conversational AI)의 종단 간(end-to-end) 지연 시간 예산을 단계별로 나누어 봅니다.
  • 링 버퍼(Ring buffer), 음성 활동 감지(VAD), 스트리밍 자동 음성 인식(streaming ASR), 인터럽션(interruption), 지터 버퍼(jitter buffer)를 설명할 수 있습니다.
  • Python 오디오 콜백(audio callback)에서 전역 인터프리터 락(GIL)과 메모리 할당(allocation)을 조심해야 하는 이유를 이해합니다.
  • 실시간 음성 앱(real-time voice app)에 필요한 전송(transport)과 관측 가능성(observability) 도구를 고를 수 있습니다.

문제

살아 있는 것처럼 느껴지는 음성 비서(voice assistant)를 만들고 싶다고 가정합니다. 사람의 대화 차례 전환(turn-taking) 지연 시간은 침묵에서 응답까지(silence-to-response) 기준 약 230밀리초입니다. 500밀리초를 넘어가면 로봇처럼 어색하게 느껴지고, 1500밀리초를 넘어가면 망가진 시스템처럼 느껴집니다. 2026년 기준 전체 듣기 → 이해 → 응답 → 말하기(hear → understand → respond → speak) 루프의 예산은 다음과 같습니다.

단계(Stage)예산(Budget)
Mic → buffer20 ms
VAD10 ms
ASR (streaming)150 ms
LLM (first token)100 ms
TTS (first chunk)100 ms
Render → speaker20 ms
**Total약 400 ms**

Moshi(Kyutai, 2024)는 전이중(full-duplex) 200밀리초를 기록했습니다. GPT-4o-realtime(2024)은 약 320밀리초입니다. 2022년의 캐스케이드 파이프라인(cascaded pipeline)은 2500밀리초로 배포되곤 했습니다. 10배 개선은 세 가지 기법에서 비롯되었습니다. (1) 모든 단계에서의 스트리밍(streaming), (2) 부분 결과(partial result) 기반의 비동기 파이프라이닝(asynchronous pipelining), (3) 중단 가능한 생성(interruptible generation)입니다.

사전 테스트

2문제 · 이 강의를 시작하기 전에 얼마나 알고 있는지 확인해보세요

1.대화형 인공지능(Conversational AI) 음성 비서에서, 사용자가 말을 끝낸 시점부터 첫 오디오 응답 바이트까지의 총 지연 시간 예산은 약 400 ms입니다. 이 예산을 지키는 것이 왜 중요한가요?

2.실시간 오디오 처리 파이프라인에서 링 버퍼(ring buffer)의 목적은 무엇인가요?

0/2 답변 완료

개념

conversational voice pipeline — every 20 ms frame mic → ring 20 ms frames 16 kHz mono VAD Silero 4.0 < 1 ms streaming ASR Parakeet-CTC 0.6B ~150 ms LLM first-token ~100 ms streaming TTS Kokoro / EL v2.5 ~100 ms speaker opus ~20 ms barge-in: VAD cancels LLM + TTS mid-stream 2026 end-to-end latency leaderboard 200 Moshi 320 GPT-4o realtime 500 LiveKit cascade 1500 batch ASR + chat + TTS 2500+ 2022 cascaded rules of thumb 20 ms frames 320 samples @ 16 kHz; everything downstream keeps the cadence ring buffer 2 s = 32 000 samples; absorbs jitter without adding floor latency AEC mandatory without it, TTS output loops back into ASR and self-triggers warm the TTS prime vocoder before first turn or users feel the 150 ms warm-up

프레임 / 청크 / 윈도우(Frame / chunk / window). 실시간 오디오는 고정 크기 블록 단위로 흐릅니다. 흔한 선택은 20밀리초입니다. 16 kHz에서는 한 프레임이 320 샘플입니다. 다운스트림(downstream)의 모든 단계는 이 박자(cadence)를 따라잡아야 합니다.

링 버퍼(Ring buffer). 고정 크기의 원형 버퍼(circular buffer)입니다. 생산자 스레드(producer thread)가 새 프레임을 쓰고, 소비자 스레드(consumer thread)가 읽어 갑니다. 핫 패스(hot path)에서 메모리 할당이 일어나지 않도록 막아 줍니다. 크기는 대략 최대 지연 시간 × 샘플레이트(sample-rate)로 잡습니다. 2초 분량의 16 kHz 링 버퍼는 32,000 샘플입니다.

음성 활동 감지(Voice Activity Detection; VAD). 아무도 말하지 않을 때 다운스트림 처리를 차단(gate)합니다. Silero VAD 4.0(2024)은 CPU에서 30밀리초 프레임당 1밀리초 미만으로 실행됩니다. webrtcvad는 더 오래된 대안입니다.

스트리밍 자동 음성 인식(Streaming ASR). 오디오가 도착하는 동안 부분 전사(partial transcript)를 내보내는 모델입니다. 스트리밍 모드의 Parakeet-CTC-0.6B(NeMo, 2024)는 320밀리초 지연에서 2~5% 단어 오류율(WER)을 냅니다. Whisper-Streaming(Macháček et al., 2023)은 Whisper를 청크 단위로 잘라 약 2초 지연의 준스트리밍(near-streaming)을 만듭니다.

인터럽션(Interruption). 비서가 말하는 도중에 사용자가 말을 시작하면 (a) 끼어들기(barge-in)를 감지하고, (b) 음성 합성(TTS)을 멈추고, (c) 남은 대규모 언어 모델(LLM) 출력을 버려야 합니다. 이 모든 일이 100밀리초 안에 일어나야 합니다. 그렇지 않으면 사용자는 비서가 자신의 말을 듣지 못한다고 느낍니다.

WebRTC Opus 전송(transport). 20밀리초 프레임, 48 kHz, 8~128 kbps의 적응형 비트레이트(adaptive bitrate)를 사용합니다. 브라우저와 모바일의 표준입니다. LiveKit, Daily.co, Pion이 2026년 음성 앱 스택입니다.

지터 버퍼(Jitter buffer). 네트워크 패킷은 순서가 뒤바뀌거나 늦게 도착할 수 있습니다. 지터 버퍼는 이를 재정렬하고 부드럽게 만들어 줍니다. 너무 작으면 들리는 공백(audible gap)이 생기고, 너무 크면 지연이 커집니다. 일반적으로 60~80밀리초를 둡니다.

흔한 함정(gotcha)

  • 스레드 경합(Thread contention). Python의 전역 인터프리터 락(GIL)에 무거운 모델까지 더해지면 오디오 스레드가 굶주릴 수 있습니다. C 콜백(C-callback) 기반 오디오 라이브러리(sounddevice, PortAudio)를 사용하고, 핫 패스에서는 Python을 최소화합니다.
  • 샘플레이트 변환 지연(Sample-rate conversion latency). 파이프라인 안에서 리샘플링(resampling)을 하면 5~20밀리초가 추가됩니다. 미리(upfront) 리샘플링을 하거나, 무지연 리샘플러(zero-latency resampler, 예: PolyPhase, soxr_hq)를 사용합니다.
  • TTS 프라이밍(TTS priming). Kokoro처럼 빠른 TTS도 첫 요청에는 100~200밀리초의 워밍업(warm-up)이 발생합니다. 실제 첫 차례가 시작되기 전에 더미 호출(dummy run)로 모델을 데우고 캐시(cache)에 올려 둡니다.
  • 에코 캔슬링(Echo cancellation). 음향 반향 제거(AEC)가 없으면 TTS 출력이 마이크로 다시 들어가서 봇 자기 목소리로 음성 인식(ASR)이 트리거됩니다. WebRTC AEC3가 오픈 소스 표준입니다.

직접 만들기

Step 1: 링 버퍼

import collections

class RingBuffer:
    def __init__(self, capacity):
        self.buf = collections.deque(maxlen=capacity)
    def write(self, frame):
        self.buf.extend(frame)
    def read(self, n):
        return [self.buf.popleft() for _ in range(min(n, len(self.buf)))]
    def level(self):
        return len(self.buf)

용량(capacity)이 최대 버퍼링 지연을 결정합니다. 16 kHz에서 32,000 샘플은 2초에 해당합니다.

Step 2: VAD 게이트

def simple_energy_vad(frame, threshold=0.01):
    return sum(x * x for x in frame) / len(frame) > threshold ** 2

프로덕션(production)에서는 Silero VAD로 교체합니다.

import torch
vad, _ = torch.hub.load("snakers4/silero-vad", "silero_vad")
is_speech = vad(torch.tensor(frame), 16000).item() > 0.5

Step 3: 스트리밍 ASR

# Parakeet-CTC-0.6B streaming via NeMo
from nemo.collections.asr.models import EncDecCTCModelBPE
asr = EncDecCTCModelBPE.from_pretrained("nvidia/parakeet-ctc-0.6b")
# chunk_ms=320 ms, look_ahead_ms=80 ms
for chunk in audio_stream():
    partial_text = asr.transcribe_streaming(chunk)
    print(partial_text, end="\r")

Step 4: 인터럽션 핸들러

class Dialog:
    def __init__(self):
        self.tts_task = None

    def on_user_speech(self, frame):
        if self.tts_task and not self.tts_task.done():
            self.tts_task.cancel()   # barge-in
        # then feed to streaming ASR

    def on_final_user_utterance(self, text):
        self.tts_task = asyncio.create_task(self.reply(text))

    async def reply(self, text):
        async for tts_chunk in llm_then_tts(text):
            speaker.write(tts_chunk)

핵심은 비동기 입출력(async I/O)과 취소 가능한(cancellable) TTS 스트리밍입니다. 오디오 트랙에 대해 WebRTC peerconnection.stop()을 호출하는 방식이 정석(canonical)입니다.

사용하기

2026년 기준 스택은 다음처럼 고릅니다.

계층(Layer)선택(Pick)
TransportLiveKit (WebRTC) 또는 Pion (Go)
VADSilero VAD 4.0
Streaming ASRParakeet-CTC-0.6B 또는 Whisper-Streaming
LLM first-tokenGroq, Cerebras, vLLM-streaming
Streaming TTSKokoro 또는 ElevenLabs Turbo v2.5
Echo cancelWebRTC AEC3
End-to-end nativeOpenAI Realtime API 또는 Moshi

흔한 함정

  • 안전을 이유로 500밀리초 버퍼링. 버퍼 자체가 지연의 하한(latency floor)이 됩니다. 가능한 한 줄여야 합니다.
  • 스레드를 고정(pinning)하지 않음. 오디오 콜백이 UI 스레드보다 우선순위가 낮은 스레드에서 돌면, 부하 상황에서 끊김(glitch)이 발생합니다.
  • TTS 청크가 너무 작음. 200밀리초 미만의 청크는 보코더 아티팩트(vocoder artifact)가 귀에 들리게 만듭니다. 320밀리초 청크가 스위트 스폿(sweet spot)입니다.
  • 지터 버퍼 없음. 실제 네트워크는 지터(jitter)가 심합니다. 부드럽게 다듬는 단계가 없으면 팝 노이즈(pop noise)가 납니다.
  • 단발성(single-shot) 오류 처리. 오디오 파이프라인은 크래시에 강해야(crash-proof) 합니다. 예외 하나가 세션 전체를 죽이면 안 됩니다.

산출물 만들기

원문은 outputs/skill-realtime-designer.md로 저장하라고 안내합니다. 현재 이 강의의 실제 산출물 파일은 outputs/skill-realtime-pipeline.md입니다. 종단 간 지연 목표에 맞춰 각 단계별 지연 예산을 가진 실시간 오디오 파이프라인을 설계하는 스킬(skill)입니다.

연습문제

  1. 쉬움. code/main.py를 실행해 봅니다. 링 버퍼와 에너지 기반 VAD를 시뮬레이션하고, 가짜(fake) 10초 스트림에 대한 각 단계의 지연 시간을 출력합니다.
  2. 중간. sounddevice를 사용해 마이크 입력을 20밀리초 프레임 단위로 처리하는 패스스루(passthrough) 루프를 만들고, 각 프레임의 VAD 상태를 출력합니다.
  3. 어려움. aiortc로 전이중(full duplex) 에코 테스트를 만듭니다. browser → WebRTC → Python → WebRTC → browser 경로를 연결하고, 1 kHz 펄스(pulse)로 화면-대-화면(glass-to-glass) 지연 시간을 측정합니다.

핵심 용어

용어흔한 설명실제 의미
링 버퍼(Ring buffer)원형 큐(circular queue)오디오 프레임용 고정 크기, 락 프리(lock-free) 또는 단일 생산자-단일 소비자(SPSC-locked) 선입선출(FIFO) 큐.
음성 활동 감지(VAD)침묵 게이트(silence gate)음성(speech)과 비음성(non-speech)을 표시하는 모델 또는 휴리스틱(heuristic).
스트리밍 ASR실시간 음성-텍스트 변환(STT)오디오가 도착하는 동안 부분 텍스트를 내보내며 제한된 룩어헤드(lookahead)를 가짐.
지터 버퍼(Jitter buffer)네트워크 평활화(smoother)순서가 뒤바뀐 패킷을 재정렬하는 큐. 일반적으로 60~80밀리초.
음향 반향 제거(AEC)에코 캔슬링(echo cancellation)스피커에서 마이크로 되돌아오는 피드백 경로를 빼냄(subtract).
끼어들기(Barge-in)사용자 인터럽트(user interrupt)TTS 재생 중간에 사용자 음성을 감지하고 재생을 취소해야 하는 상황.
전이중(Full duplex)양방향 동시 통신사용자와 봇이 동시에 말할 수 있음. Moshi가 전이중 방식.

더 읽을거리

실습 코드

이 강의의 실습 코드 1개

main
Code

산출물

이 강의에서 생성된 프롬프트, 스킬, 코드 산출물 1개

realtime-voice-pipeline

Pick transport, VAD, streaming STT, LLM, streaming TTS, and orchestration for a target end-to-end latency.

Skill

확인 문제

3문제 · 모두 맞추면 완료 표시가 가능합니다

1.음성 비서가 자신의 TTS 출력을 ASR 파이프라인으로 다시 되먹이며, 자기 음성을 전사하고 피드백 루프에 빠집니다. 어떤 구성요소가 빠져 있나요?

2.Python으로 실시간 음성 앱을 만들고 있는데, CPU 부하 상태에서 간헐적인 오디오 끊김(glitch)이 발생합니다. 오디오 콜백이 무거운 모델 추론과 같은 Python 스레드에서 실행됩니다. 가장 가능성 높은 원인은 무엇인가요?

3.비서가 아직 응답 중인데 사용자가 말을 시작합니다. 비서가 멈추기까지 2초 동안 사용자 위에 겹쳐 말합니다. 어떤 파이프라인 메커니즘이 실패한 건가요?

0/3 답변 완료

추가 문제 풀기

AI가 강의 내용을 바탕으로 새로운 문제를 생성합니다