마이크에 대고 말하면 상대방 스피커에서 소리가 나옵니다. 당연한 것 같지만, 그 사이에는 ADC, 오디오 전처리, 코덱 인코딩, RTP 패킷화, UDP 전송, Jitter Buffer, 디코딩, DAC까지 수많은 단계가 있습니다. 각 단계에서 왜 그 선택을 했는지, 시니어 엔지니어 시선으로 정리합니다.
1단계: 소리가 컴퓨터로 들어오는 과정
마이크 → ADC → PCM
마이크 (아날로그)
↓
ADC (Analog to Digital Converter)
↓
PCM (디지털 오디오 데이터)
마이크: 공기 진동 → 전기 신호 (연속적, 아날로그)
ADC: 전기 신호 → 숫자 (이산적, 디지털)
PCM: 그 숫자들의 나열
ADC는 OS/하드웨어 레벨에서 이미 처리됨.
우리가 제어하는 건 PCM 이후.
그래서 시스템 설계에서는 보통 이렇게 표현합니다:
Mic → PCM → processing → encoding
ADC는 "있다"는 것만 알면 충분합니다.
2단계: 오디오 전처리 — AEC, AGC, NS
PCM이 나왔다고 바로 보내지 않습니다. 전처리가 먼저입니다.
PCM (원본)
↓
AEC (Acoustic Echo Cancellation)
→ 스피커에서 나온 소리가 마이크에 다시 들어가는 것 방지
→ 화상회의에서 "에코" 현상 제거
↓
AGC (Automatic Gain Control)
→ 볼륨 자동 조절
→ 작게 말해도 적당히, 크게 말해도 적당히
↓
NS (Noise Suppression)
→ 배경 노이즈 제거
→ 키보드 소리, 에어컨 소리 등
↓
전처리된 PCM
WebRTC 구현체와 RTC SDK는 AEC·AGC·NS를 제공할 수 있지만, 지원 여부·기본값·조절 범위는 플랫폼과 SDK 버전에 따라 다릅니다. 웹에서는 getSupportedConstraints()와 트랙의 settings를 확인하고, Agora는 사용 중인 버전의 오디오 처리 옵션을 확인해야 합니다.
3단계: 코덱 인코딩 — 왜 PCM 그대로 보내지 않나?
PCM 그대로 보내면:
PCM (16bit, 48kHz, mono) ≈ 768 kbps
Opus 인코딩 후 설정에 따라 크게 달라짐
→ RFC 6716의 지원 범위는 6~510 kbps이며 음성은 더 낮은 구간을 흔히 사용
PCM은:
✅ 용량 큼
✅ 품질 좋음
✅ 계산/처리는 쉬움
❌ 네트워크에 올리면 비쌈 + 느림
→ 그래서 압축(인코딩) 필요
코덱 선택 — 상황에 따라 다르다
┌─────────────────────┬─────────┬──────────────────────────────────┐
│ 상황 │ 코덱 │ 이유 │
├─────────────────────┼─────────┼──────────────────────────────────┤
│ WebRTC / 라이브 통신 │ Opus 등 │ 협상된 코덱 사용. Opus·G.711은 MTI │
├─────────────────────┼─────────┼──────────────────────────────────┤
│ 전화 / SIP / PSTN │ µ-law │ 초경량, CPU 거의 안 씀, 레거시 │
├─────────────────────┼─────────┼──────────────────────────────────┤
│ VOD / 저장 / HLS │ AAC 등 │ 컨테이너·재생기·배포 규격에 맞춤 │
├─────────────────────┼─────────┼──────────────────────────────────┤
│ 간단한 웹 다운로드 │ MP3 │ 호환성 최고, 구형 시스템 포함 │
└─────────────────────┴─────────┴──────────────────────────────────┘
각 코덱의 특징
Opus
- 6~510kbps, 2.5~60ms 프레임을 지원
- 인밴드 FEC·PLC 등 실시간 통신용 기능 제공
→ WebRTC 필수 구현 코덱이며, 실제 사용 코덱은 SDP 협상 결과로 결정.
µ-law
- 매우 가벼움 (lookup table 수준)
- CPU 거의 안 씀
- 음질 낮음 (8bit, 8kHz)
→ 레거시 전화용. WebRTC에서는 거의 안 씀.
AAC
- 고음질
- 같은 bitrate에서 MP3보다 더 좋은 음질
- MP4·MPEG-TS 등 여러 컨테이너에서 널리 사용
→ 콘텐츠 배포용으로 흔하지만 HLS가 AAC만 허용하는 것은 아님.
MP3
- 호환성 최고
- 널리 지원되지만 배포 전 대상 브라우저·앱의 codec support matrix와 재생 시험으로 범위를 확정
→ 파일 배포 선택지 중 하나.
코덱 선택 의사결정 트리
1. 실시간인가?
→ YES → 양 끝이 협상 가능한 코덱 중 지연·품질·상호운용성 기준으로 선택
2. 전화망인가?
→ YES → µ-law
3. 저장/스트리밍인가?
→ YES → AAC
4. 호환성이 가장 중요한가?
→ YES → MP3
4단계: 네트워크 전송 — RTP, UDP, Jitter Buffer
왜 UDP를 우선 사용하는가?
TCP:
신뢰성·순서 보장을 제공하지만 손실 시 head-of-line blocking이 생길 수 있음
UDP:
빠름
일부 손실 허용
실시간 통신은:
정확성보다 "지연"이 더 중요
0.5초 전 소리를 정확히 듣는 것보다
지금 소리를 대충이라도 듣는 게 나음
→ WebRTC는 UDP를 우선하되, UDP가 차단되면 TURN/TCP 또는 TURN/TLS 같은 경로도 사용할 수 있음
RTP (Real-time Transport Protocol)
오디오/영상 실시간 전송 프로토콜.
UDP 위에서 동작하면서 부족한 부분을 보완합니다:
sequence number → 패킷 순서 관리
timestamp → 타이밍 동기화
UDP는 순서/타이밍 보장이 없으므로
RTP가 이 정보를 헤더에 추가합니다.
인코딩된 오디오 데이터:
↓
RTP 패킷으로 포장 (헤더 + payload)
↓
UDP로 전송
Jitter Buffer — 패킷 도착 시간을 재생 타이밍으로 바꾸는 장치
먼저 지터(Jitter)가 뭔지 정확히 잡아야 합니다.
지터 = 패킷 도착 시간의 흔들림
정상 도착 시각: 10ms 20ms 30ms 40ms (일정한 간격)
가변 도착 시각: 10ms 25ms 35ms 55ms (간격이 15, 10, 20ms)
순서 문제가 아니라 "시간 간격이 불규칙"한 것.
그대로 재생하면 → 끊김, 찢어짐, 로봇소리
지터 버퍼가 하는 일:
1. 패킷 도착 (들쭉날쭉)
2. 버퍼에 잠깐 쌓음
3. 일정 간격으로 꺼내서 재생
도착: 2 1 3
버퍼: [1 2 3] ← 순서 정렬
재생: → → → ← 일정 간격으로 출력
핵심:
"도착 기준"이 아니라
"재생 타이밍 기준"으로 바꾼다
TCP식 사고 vs Jitter Buffer — 중요한 차이
TCP식 사고 (정확성 우선):
순서 틀리면 기다림
빠진 거 재요청
→ 결과: 느림
Jitter Buffer (부드러움 우선):
조금만 기다림
늦으면 그냥 포기
대신 끊김 방지
한 줄: "완벽한 데이터"보다 "부드러운 재생"이 목표
Jitter Buffer 크기 = Latency vs 안정성 Trade-off
버퍼 작으면:
장점: 지연 낮음 (빠름)
단점: 끊김 많음
버퍼 크면:
장점: 안정적
단점: 지연 증가 (느림)
┌──────────────────┬───────────┐
│ 상황 │ 선택 │
├──────────────────┼───────────┤
│ 게임 / 통화 │ 작은 버퍼 │
│ 방송 / 스트리밍 │ 큰 버퍼 │
└──────────────────┴───────────┘
Adaptive Jitter Buffer — 요즘은 고정이 아님
네트워크 상태를 보고 자동 조절:
네트워크 안정 → 버퍼 줄임 → 지연 낮아짐
네트워크 불안 → 버퍼 늘림 → 안정성 확보
WebRTC 구현체는 수신 통계와 재생 지연을 바탕으로 적응형 지터 버퍼를 사용할 수 있습니다. Chromium의 오디오 수신기는 NetEq를 사용합니다. Agora의 구체적인 알고리즘과 제어 범위는 공개된 SDK 문서와 버전별 동작을 기준으로 확인해야 합니다.
Packet Loss 대응 — SFU와 클라이언트의 협력
손실 대응은 송신기·수신기·SFU가 나눠 맡습니다. 구현에 따라 SFU가 최근 패킷을 보관해 재전송하거나 RTCP feedback을 전달하고, 송신기는 FEC/RTX를 만들며, 수신 디코더는 PLC를 수행합니다.
1. NACK (재전송 요청)
클라이언트: "sequence 123 없어!"
SFU 또는 원 송신기: 보관된 패킷이 있으면 재전송
2. FEC (Forward Error Correction)
미리 여분 데이터를 추가해서 전송
데이터 + 복구용 패킷
→ 일부 loss는 재전송 없이 클라이언트에서 복구
3. PLC (Packet Loss Concealment)
클라이언트에서:
이전 프레임 복사
보간(interpolation)
→ "티 안 나게 숨김"
4. Bitrate Adaptation
네트워크 안 좋으면
→ 송신 bitrate를 낮추거나 SFU가 낮은 simulcast/SVC 계층을 선택
→ 끊기는 것보다 화질 낮추는 게 나음
SFU = 전달·계층 선택·feedback 중계·선택적 재전송을 수행할 수 있음
복구 = 송신기·SFU·수신기의 구현과 협상된 RTP 확장에 따라 분담
클라이언트 수신 전체 흐름:
UDP packet 도착
→ jitter buffer 저장
→ 순서 정렬
→ missing 있으면 NACK 요청
→ 복구(FEC) or 숨김(PLC)
→ 디코딩
→ 재생
5단계: 수신 — 디코딩 → DAC → 스피커
UDP 수신
↓
RTP depacketize (헤더 제거, 오디오 데이터 추출)
↓
Jitter Buffer (흔들림 보정)
↓
Opus decode → PCM (압축 해제)
↓
DAC (Digital to Analog Converter)
↓
스피커 (전기 → 공기 진동 → 소리)
WebRTC / Agora 실제 흐름
아래 그림은 표준 WebRTC 오디오 경로를 단순화한 것입니다. Agora의 내부 프로토콜과 노드 구성은 공개된 제품 설명 이상으로 동일하다고 단정할 수 없습니다.
📡 송신 (보내는 쪽)
Mic
↓
ADC
↓
PCM
↓
Audio Processing (AEC, AGC, NS)
↓
Opus 인코딩 (압축)
↓
RTP packetize
↓
UDP 전송
📡 수신 (받는 쪽)
UDP 수신
↓
RTP depacketize
↓
Jitter Buffer
↓
Opus decode → PCM
↓
DAC
↓
Speaker
Agora의 차이점 — SD-RTN
일반 WebRTC:
Client A → ICE로 선택한 direct 또는 TURN relay 경로 → Client B/SFU
Agora SD-RTN:
Client A → 가까운 Edge PoP → 최적 경로 선택 → Edge PoP → Client B
Edge ↔ Edge 사이:
전 세계에 배치된 edge node와 소프트웨어 정의 경로 최적화를 사용한다고 Agora가 설명
사용자 패킷 수신
→ 가까운 Edge에 들어감
→ 공개 문서 범위에서는 품질·가용성을 고려해 경로를 최적화
→ 수신 측 Edge에서 클라이언트로 전달
녹화/스트리밍 — 인코딩, 트랜스코딩, 패키징
실시간 전송과 별개로, 녹화/VOD 배포에는 다른 파이프라인이 필요합니다.
인코딩 (Encoding)
원본을 압축 포맷으로 바꾸는 것.
raw video → H.264
raw audio (PCM) → AAC
트랜스코딩 (Transcoding)
이미 인코딩된 스트림을 다른 해상도/비트레이트/포맷으로 바꾸는 것.
1080p 6Mbps → 720p 3Mbps
1080p 6Mbps → 480p 1.2Mbps
→ 다양한 네트워크 환경에 대응 (Adaptive Bitrate)
패키징 (Packaging)
인코딩된 비디오/오디오를 HLS용 조각 파일로 만드는 것.
H.264 + AAC
→ segment-0001.ts 또는 fMP4 fragment
→ segment-0002.ts 또는 fMP4 fragment
→ index.m3u8
→ CDN에서 배포 가능한 형태
RTMP Push — CDN으로 스트림 밀어넣기
RTMP push는 인코딩된 H.264/AAC 스트림을
실시간으로 CDN ingest endpoint에 전달하는 과정입니다.
OBS / 인코더
↓ RTMP push (H.264 + AAC, TCP 기반)
CDN Ingest Endpoint
↓ 수신
HLS 패키징 (TS 세그먼트 + m3u8)
↓
Edge 캐싱
↓
사용자 재생
현대의 브라우저 라이브 서비스에서는 RTMP를 ingest로 쓰고 HLS/DASH 등으로 배포하는 구성이 흔합니다. 다만 이는 서비스 설계이지 RTMP 규격의 불변 조건은 아니며, CDN은 단순 패키징뿐 아니라 트랜스코딩과 ABR ladder 생성도 수행할 수 있습니다.
RTMP → WebRTC 변환 — Media Gateway가 하는 일
RTMP 스트림을 WebRTC로 연결할 때 서버는 RTMP 메시지에서 미디어를 추출해 RTP payload 형식으로 packetize합니다. 코덱·프로파일·parameter set·packetization·타임스탬프 조건이 맞으면 압축 payload를 유지할 수 있지만, 호환되지 않으면 decode/encode가 필요합니다.
전체 흐름:
OBS (RTMP push)
↓
Agora Media Gateway (RTMP ingest)
↓
디코딩 또는 트랜스코딩
↓
WebRTC용으로 재패키징 (RTP/UDP)
↓
클라이언트 (WebRTC)
단계별로 보면
1) OBS → RTMP (업로드)
Video (H.264) + Audio (AAC)
→ RTMP (TCP 기반)
→ Agora ingest 서버
이건 그냥 "방송 업로드". 여기까진 단순함.
2) Media Gateway (핵심)
여기서 진짜 일이 일어남. 서버가 두 가지를 합니다:
(A) 디코딩 (필요한 경우)
H.264 → raw frame
AAC → PCM
왜?
bitrate 조절이 필요할 때
해상도 변경이 필요할 때
여러 시청자 대응 (Adaptive Bitrate)
코덱이 같으면 디코딩 없이 passthrough도 가능.
(B) 재패키징 (항상 필요)
WebRTC는 RTMP를 못 먹습니다.
RTMP = TCP 기반 스트림
WebRTC = RTP/UDP 기반 패킷
그래서:
RTMP stream (TCP)
→ 미디어 데이터 추출
→ RTP packet으로 재포장 (WebRTC format)
→ UDP로 전송
"RTMP → WebRTC 프로토콜 변환" (X)
"미디어를 꺼내서 → WebRTC 방식으로 다시 싸서 보냄" (O)
3) 클라이언트 (WebRTC 수신)
RTP depacketize
→ Jitter Buffer
→ 디코딩 (H.264 → frame, Opus → PCM)
→ 렌더링/재생
여기서부터는 일반 WebRTC 수신과 동일.
RTMP → FLV tag 파싱이란?
RTMP 안에 들어있는 비디오/오디오 데이터 구조를 꺼내는 것입니다.
RTMP 스트림 내부 구조:
RTMP
→ FLV tag와 유사한 구조의 RTMP audio/video message payload
각 FLV tag:
┌──────────────┐
│ type │ ← video / audio
│ timestamp │ ← 시점
│ data │ ← H.264 NAL units 또는 AAC frames
└──────────────┘
파싱 = RTMP message type과 codec-specific payload를 읽어 미디어 access unit을 복원하는 것
MTU — 왜 1200 바이트씩 쪼개냐?
MTU (Maximum Transmission Unit)
= 네트워크에서 한 번에 보낼 수 있는 최대 패킷 크기
Ethernet MTU는 흔히 1500바이트지만, 경로 MTU와 IP 버전·RTP 확장·SRTP 오버헤드는 경로마다 다릅니다.
WebRTC에서 1200바이트 안팎을 목표로 삼는 구현은 여러 경로에서 IP fragmentation을 피하려는
보수적인 선택이지, 1500에서 고정 헤더를 뺀 산술 결과가 아닙니다.
왜 중요하냐?
TCP: 애플리케이션에 바이트 스트림을 제공
UDP: datagram 경계를 유지하므로 애플리케이션/RTP payload 규격이 packetization을 담당
MTU를 넘기면?
→ IPv4에서는 fragmentation이 생길 수 있고, IPv6 라우터는 중간 fragmentation을 하지 않음
→ 하나라도 조각이 유실되면 전체 패킷 손실
→ 실시간에서는 치명적
그래서 path MTU보다 작도록 payload 규격에 맞춰 packetize함.
내부 동작 — 의사 코드로 이해하기
실제 구현은 훨씬 복잡하지만, 개념을 잡기 위한 추상적인 의사 코드입니다.
# 전체 파이프라인 (개념 이해용 의사 코드)whileTrue:
rtmp_packet = read_rtmp() # 1. RTMP 읽기
media = extract_payload(rtmp_packet) # 2. payload 추출
rtp_packets = packetize_to_rtp(media) # 3. RTP로 쪼개기
send_to_sfu(rtp_packets) # 4. SFU로 전달
# 1단계: RTMP 읽기defread_rtmp():
# TCP 소켓에서 데이터 읽음
data = socket.recv()
# RTMP -> FLV tag 파싱return parse_rtmp(data)
# 3단계: 코덱별 RTP payload 규격으로 packetizedefpacketize_to_rtp(access_unit, codec, rtp_timestamp):
# H.264는 RFC 6184의 Single NAL/STAP/FU 규칙,# Opus는 RFC 7587의 payload 규칙을 적용한다.# 한 영상 access unit의 fragment는 같은 RTP timestamp를 사용하고# sequence number, marker bit, payload header를 규격대로 설정한다.return codec_payload_packetizer(codec).packetize(access_unit, rtp_timestamp)
# 4단계: SFU로 전달defsend_to_sfu(rtp_packets):
for p in rtp_packets:
sfu_input_queue.put(p)
SFU — 압축 미디어를 선택적으로 전달
SFU(Selective Forwarding Unit)는 Media Gateway와 역할이 다릅니다.
Media Gateway = 포맷 변환 (RTMP → RTP)
SFU = 분배 (RTP → 여러 클라이언트)
일반적인 SFU는 압축 미디어를 디코딩·합성하지 않고 전달하지만,
SRTP termination, RTP header rewrite, retransmission, simulcast/SVC 계층 선택을 수행할 수 있습니다.
제품에 따라 별도 트랜스코딩 경로를 함께 제공하기도 합니다.
# SFU 내부 (개념 이해용 의사 코드)defsfu_loop():
whileTrue:
packet = input_queue.get()
for subscriber in subscribers:
send_udp(packet, subscriber)
# 확장: 실제 SFU는 네트워크 상태에 따라 선택적 전달defsfu_loop_adaptive():
whileTrue:
packet = input_queue.get()
for subscriber in subscribers:
if subscriber.network_bad:
send_low_bitrate_stream(packet, subscriber)
else:
send_high_bitrate_stream(packet, subscriber)
RTMP ingest 단계에서는 Media Gateway가 H.264/AAC payload를 추출해
RTP packet으로 재패키징하고, 이후 SFU가 이 RTP 스트림을 복제하여
다수의 WebRTC 클라이언트에 전달합니다.
포맷 변환과 분배는 별도의 컴포넌트로 분리되어 동작합니다.
┌──────────────┬──────────────────────┐
│ 단계 │ 역할 │
├──────────────┼──────────────────────┤
│ Gateway │ 포맷 변환 (RTMP→RTP) │
│ SFU │ 분배 (복제+선택 전달) │
└──────────────┴──────────────────────┘
오버레이 네트워크 — 경로를 직접 선택한다
Agora는 SD-RTN을 인터넷 위의 오버레이 네트워크로 설명합니다. 관리되는 edge 사이에서는 경로를 최적화할 수 있지만, 단말에서 첫 edge까지의 access network와 물리 구간은 여전히 ISP 라우팅의 영향을 받습니다.
일반 인터넷 (Underlay):
서울 → 미국
= ISP가 아무 경로로 보냄
= 우리는 제어 불가
오버레이 네트워크:
서울 Edge → 도쿄 Edge → 미국 Edge
= 관리되는 PoP 사이의 중계 경로를 선택
= 경로 성능을 계속 측정하고 최적 선택
┌──────────────┬──────────────────┬──────────────────┐
│ │ Underlay (인터넷) │ Overlay (SD-RTN) │
├──────────────┼──────────────────┼──────────────────┤
│ 경로 결정 │ ISP │ 애플리케이션 │
│ 제어 │ 없음 │ 있음 │
│ 최적화 │ 제한적 │ 실시간 가능 │
└──────────────┴──────────────────┴──────────────────┘
왜 필요하냐?
latency 줄이려고
packet loss 피하려고
jitter 줄이려고
핵심 포인트:
1. depacketization
RTP 패킷에서 미디어 데이터 추출 (RTP 헤더 제거)
2. 디코딩 + 재인코딩 (트랜스코딩)
WebRTC 코덱(Opus, VP8)과 RTMP 코덱(AAC, H.264)이
다르기 때문에 디코딩 후 재인코딩 필요
Opus → PCM → AAC
VP8 → raw frame → H.264
3. 코덱이 이미 일치하면?
WebRTC에서 H.264를 쓰고 있다면
→ 디코딩/재인코딩 없이 리패키징만 수행 가능
→ CPU 부담 대폭 감소
H.264(RTP) → H.264(RTMP) 리패키징만
Opus(RTP) → AAC(RTMP) 트랜스코딩 필요
Voice AI 파이프라인에서 VAD는 "언제 말하는지 감지"하는 역할입니다. 없으면 어떻게 되는지 보면 왜 필요한지 바로 이해됩니다.
먼저 구분: 에코 vs VAD
마이크가 TTS 소리를 픽업하는 것
|
| 이건 물리적 현상
| (스피커 -> 공기 -> 마이크)
|
v
AEC가 처리해야 할 문제
주된 해결 수단은 AEC. 잔여 에코는 VAD 오검출에도 영향을 줄 수 있음
AEC(Acoustic Echo Cancellation)는 스피커 출력이 마이크로 다시 들어오는 물리적 에코를 제거합니다. VAD는 에코가 아니라 "사람이 말하고 있는지"를 판단합니다. 역할이 다릅니다.
VAD가 없으면 생기는 4가지 문제
1. 침묵 구간도 STT로 전송될 수 있음
상황 VAD 있음 VAD 없음
-------------------------------------------------
침묵 구간 무시 STT로 계속 전송
말하는 구간 감지 후 전달 구분 불가
발화 시작/끝 정확히 자름 잘림 없이 흘러감
오디오 스트림이 2분 동안 연결되어 있으면 침묵 구간도 오디오 데이터입니다. 애플리케이션이 VAD로 전송을 중단하거나 요청을 분리하면 사용량을 줄일 수 있지만, 과금 방식은 STT 제공자의 현재 정책을 확인해야 합니다.
타임라인:
00:00 ---------------------------------------- 02:00
| |
Mic 연결됨 Mic 여전히 연결
| |
v v
오디오 스트림이 계속 STT로 흘러들어감
(침묵 = 무음 PCM 데이터도 "데이터"임)
2. 발화 종료 시점 모름 -- LLM 호출 타이밍 불명확
Mic (연속) --> STT --> 끝없는 텍스트 조각들
--> LLM이 언제 응답해야 할지 모름
--> 중복/쓸모없는 추론 폭발
Mic ---------------------------------------------------------->
[침묵][말][침묵][말][침묵]
^-- VAD 없으면 이 모든 것이 STT로 전달됨
VAD가 있으면 발화 시작을 감지해 STT 전송이나 turn 상태를 제어할 수 있습니다. VAD가 없어도 STT 자체 endpointing이나 다른 turn detector를 사용할 수 있으므로 항상 켜 두어야 한다고 단정할 수는 없습니다.
4. Barge-in 감지 불가 -- TTS 재생 중 유저가 말해도 모름
VAD 없음:
TTS가 재생되는 동안 유저가 말하기 시작
-> 시스템이 유저 발화 시작을 감지 못함
-> TTS를 끊지 못함 (interruption 불가)
-> 대화가 아니라 일방적 출력이 됨
VAD가 있으면 유저가 말하기 시작할 때 TTS 재생을 즉시 중단(barge-in)할 수 있습니다. "유저가 다시 말하기 시작했다"는 판단이 VAD의 역할입니다.
VAD 없음 전체 흐름:
Mic -> 침묵/잡음/말 구분 불가
-> STT <-- 쓰레기 입력 (Garbage In)
-> LLM <-- 언제 응답할지 모름
-> TTS <-- 엉뚱한 타이밍에 말함
-> User <-- 혼란, 끊김, barge-in 불가
STT Endpointing vs VAD — 타이밍 문제
Streaming STT의 endpointing 방식과 timeout은 제품·모델·설정에 따라 다릅니다. 일부 API는 음성 활동 이벤트와 configurable timeout을 제공하므로, 아래 타임라인은 고정 동작이 아니라 애플리케이션 설계 예시입니다.
STT 2가지 모드:
1. Streaming STT (실시간) <-- Voice AI에서 사용
2. Batch STT (파일 업로드)
문제 1: 타이밍이 늦다
유저: "주문하고 싶어요" [말 끝]
STT endpointing만 사용 (VAD 없음):
---------------------------------------------------
t=0.0s "주문하고 싶어요" 말하기 완료
t=0.0s 유저 입장: "이제 답해줘야지"
<-- 여기서 바로 LLM 호출했으면 좋겠음
t=0.5s STT 내부: "아직 말 더 할 수도 있으니 대기"
t=1.0s STT 내부: "아직 대기..."
t=1.5s STT 내부: "침묵 1.5초 지났네 -> final 출력!"
t=1.5s final: "주문하고 싶어요" -> LLM 호출
t=2.5s LLM 응답 생성
t=3.0s TTS 출력
---------------------------------------------------
유저 체감: 말 끝내고 -> 3초 후에 응답
VAD가 있으면?
---------------------------------------------------
t=0.0s "주문하고 싶어요" 말하기 완료
t=0.3s VAD: "발화 종료 감지!" -> 즉시 STT final 트리거
t=0.3s LLM 호출
t=1.3s LLM 응답
t=1.6s TTS 출력
---------------------------------------------------
유저 체감: 말 끝내고 -> 1.6초 후 응답
end-of-speech 지연은 VAD의 hangover, 민감도, 후처리와 STT 설정에 따라 달라집니다. 고정된 200500ms 대 12초 규칙 대신 실제 트래픽으로 false cut과 응답 지연을 함께 측정해야 합니다.
문제 2: 부정확하다 — 문장이 쪼개진다
케이스 A — 문장 중간 침묵
유저: "저는... [2초 침묵] ...치킨 주문할게요"
STT endpointing (VAD 없음):
---------------------------------------------------
t=0.5s interim: "저는"
t=2.5s 침묵 2초 경과 -> STT가 final 판단!
final: "저는" -> LLM 호출
LLM: "네, 무엇을 도와드릴까요?" (엉뚱한 응답)
t=3.0s 유저: "치킨 주문할게요" (계속 말하는 중)
STT: 또 final -> LLM 또 호출
---------------------------------------------------
결과:
-> 문장이 쪼개져서 LLM이 맥락 없이 응답
-> 유저는 말 중간에 끊김 경험
케이스 B — 생각하면서 말하기
유저: "음... 그러니까... 제가 원하는 건..."
STT endpointing (VAD 없음):
-> "음" -> final -> LLM 호출
-> "그러니까" -> final -> LLM 호출
-> "제가 원하는 건" -> final -> LLM 호출
LLM은 계속 엉뚱한 응답 생성 중...
VAD는 음성/비음성 가능성을 판단할 뿐 발화의 의미나 turn completion을 이해하지는 않습니다. filler와 긴 생각 구간을 안전하게 처리하려면 STT 결과, timeout, semantic turn detector를 함께 사용해야 합니다.
VAD의 위치 — 파이프라인에서 어디에?
Mic -> PCM -> 전처리(AEC/AGC/NS)
|
v
+-----+-----+
| VAD | <-- 여기서 발화 감지
+-----+-----+
|
+---------+---------+
| |
[말하는 중] [침묵]
| |
STT로 전송 전송 안 함
|
STT interim/final
|
LLM 호출 (발화 종료 시)
|
TTS 응답
VAD는 보통 전처리된 오디오에서 동작합니다. 말할 때만 STT에 보내는 gate로 쓸 수도 있고, 오디오는 계속 보내되 turn detector의 신호로만 쓸 수도 있습니다. 전송을 잘라 쓸 때는 발화 첫 음절이 잘리지 않도록 pre-roll과 hangover를 둡니다.
1. PCM은 오디오 처리에서 널리 쓰는 샘플 표현
→ 캡처·디코딩 뒤 처리 단계에서 흔히 사용
2. 코덱은 양 끝의 지원과 목적에 맞춰 선택
→ WebRTC는 Opus·PCMA·PCMU를 필수 구현하며 실제 코덱은 협상
3. PCM은 실시간 네트워크에서 대역폭이 커서 보통 압축
→ 필요한 품질·지연·복원력을 기준으로 비트레이트를 측정
4. WebRTC 미디어는 RTP/SRTP를 사용하고 UDP를 우선
→ 방화벽 환경에서는 TURN/TCP·TURN/TLS 경로도 가능
5. Jitter Buffer와 PLC가 품질을 지킨다
→ 네트워크 흔들림 흡수
→ 패킷 유실 보정
6. 인코딩 ≠ 트랜스코딩 ≠ 패키징
→ 인코딩: raw → 압축
→ 트랜스코딩: 압축 → 다른 압축
→ 패키징: HLS 조각으로 분할
7. SFU는 보통 압축 미디어를 선택적으로 전달
→ RTP rewrite·계층 선택·재전송·feedback 중계는 구현에 따라 수행
→ Media Gateway = 포맷 변환, SFU = 분배
8. RTMP → WebRTC는 "프로토콜 변환"이 아니라
→ FLV tag에서 H.264/AAC 추출
→ MTU 기준으로 RTP fragmenting
→ SFU 통해 WebRTC 클라이언트로 분배
9. VAD는 Voice AI의 입력 신호 중 하나
→ 음성 활동 감지, 전송 gate, barge-in 신호에 활용
→ 의미상 발화 종료는 STT endpointing·timeout·turn detector와 함께 결정
전체 파이프라인 한 줄:
RTMP → FLV parsing → H.264/AAC 추출 → RTP packetize (MTU 기준)
→ UDP 전송 → SFU 전달 → Client jitter buffer
→ 복구 (NACK/FEC) → 디코딩 → 재생