[Voice AI] WebRTC: PeerConnection, ICE, STUN/TURN, SFU
정의
WebRTC = 브라우저 / 모바일 P2P 실시간 미디어 + 데이터 표준. 음성, 비디오, 데이터 채널. 2026 시점 voice agent + 영상 통화 + 화면 공유 의 표준.
동작 직관
핵심 컴포넌트
flowchart TB
JS["JavaScript API"]
JS --> PC[RTCPeerConnection]
JS --> GU[getUserMedia]
JS --> DC[RTCDataChannel]
PC --> Media["미디어 트랙 (Audio/Video)"]
PC --> Sig["Signaling (별도 채널)"]
PC --> ICE[ICE candidates]
ICE --> STUN[STUN server]
ICE --> TURN["TURN server (relay)"]
PC --> DTLS[DTLS handshake]
PC --> SRTP["SRTP (암호화 미디어)"]
| API | 역할 |
|---|---|
getUserMedia | 마이크 / 카메라 접근 |
RTCPeerConnection | P2P 연결 |
RTCDataChannel | 임의 데이터 |
RTCRtpSender/Receiver | 미디어 송수신 제어 |
Signaling (WebRTC 가 직접 다루지 않음)
sequenceDiagram
autonumber
participant A as Browser A
participant Sig as Signaling Server<br/>(WebSocket)
participant B as Browser B
A->>A: createOffer (SDP)
A->>Sig: send offer
Sig->>B: relay offer
B->>B: setRemoteDescription
B->>B: createAnswer (SDP)
B->>Sig: send answer
Sig->>A: relay answer
A->>A: setRemoteDescription
par 양쪽 ICE candidates
A->>Sig: ICE candidate
Sig->>B: relay
B->>Sig: ICE candidate
Sig->>A: relay
end
A->>B: P2P 직접 연결 (DTLS + SRTP)
Signaling 은 WebRTC 표준 외부. WebSocket / HTTP 폴링 등 자유.
SDP (Session Description Protocol)
v=0
o=- 1719318060 2 IN IP4 127.0.0.1
s=-
t=0 0
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104
a=rtpmap:111 opus/48000/2
a=rtpmap:103 ISAC/16000
a=fmtp:111 minptime=10;useinbandfec=1
a=setup:actpass
a=fingerprint:sha-256 AC:8D:...
...
코덱 / 포맷 / 보안 / ICE 후보 등. offer/answer 모델.
ICE / STUN / TURN
flowchart LR
A[Browser A] -->|"behind NAT"| Net[(Internet)]
B[Browser B] -->|"behind NAT"| Net
A -.STUN query.-> STUN[STUN server]
STUN -.public IP/port.-> A
A -.candidate exchange.-> B
A -->|direct| B
A -.failed?.-> TURN["TURN server (relay)"]
TURN -->|relay| B
| STUN | TURN | |
|---|---|---|
| 역할 | 내 공용 IP/Port 알려줌 | 미디어 중계 (P2P 실패 시) |
| 부하 | 낮음 | 높음 (모든 미디어 흐름) |
| 비용 | 거의 0 | 비쌈 (대역폭) |
| 예 | stun:stun.l.google.com:19302 | coturn (self-host) |
약 20% 의 사용자가 symmetric NAT / 방화벽 때문에 TURN 필요. coturn 운영 필수.
ICE Candidate 유형
ICE 는 여러 후보를 수집해 우선순위 순으로 연결 시도합니다.
| 유형 | 설명 | 예 |
|---|---|---|
| host | 로컬 NIC IP/port | 192.168.1.5:50001 |
| srflx (Server Reflexive) | STUN 으로 알아낸 공용 IP/port | 203.0.113.7:50001 |
| prflx (Peer Reflexive) | 상대방이 받은 내 패킷에서 알아낸 주소 | NAT 연결 중 발견 |
| relay | TURN 서버 주소 | 198.51.100.2:3478 |
우선순위: host > srflx > prflx > relay. Symmetric NAT 이면 host/srflx 모두 실패하고 relay 로 fallback.
ICE Restart
네트워크 변경 (wifi → LTE 전환 등) 시 기존 ICE 연결이 끊깁니다. pc.restartIce() 로 새 offer 를 만들어 ICE 를 재수집하면 연결을 유지할 수 있습니다.
async function handleNetworkChange() {
pc.restartIce();
const offer = await pc.createOffer({ iceRestart: true });
await pc.setLocalDescription(offer);
// signaling 으로 offer 전송
}
DataChannel (임의 데이터)
const dc = pc.createDataChannel('chat', { ordered: true });
dc.onopen = () => dc.send('hello');
dc.onmessage = (e) => console.log(e.data);
| 모드 | 의미 |
|---|---|
| ordered + reliable | TCP 같은 (기본) |
| unordered + unreliable | UDP 같은 (게임) |
| reliable + maxPacketLifeTime | 시간 한도 |
SFU (Selective Forwarding Unit)
P2P 는 N 명 → N×(N-1)/2 연결. 4명 = 6 연결, 10명 = 45 연결. 폭발.
flowchart TB
subgraph P2P["P2P (mesh)"]
A1[A] --- B1[B]
A1 --- C1[C]
A1 --- D1[D]
B1 --- C1
B1 --- D1
C1 --- D1
end
subgraph SFU["SFU (server forwards)"]
SFUS[SFU Server]
A2[A] --> SFUS
SFUS --> A2
B2[B] --> SFUS
SFUS --> B2
C2[C] --> SFUS
SFUS --> C2
D2[D] --> SFUS
SFUS --> D2
end
| P2P (mesh) | SFU | MCU | |
|---|---|---|---|
| 적합 | 1:1 / 작은 그룹 | 5-100 명 | TV/대규모 |
| 클라이언트 부하 | N-1 stream | 1 publish + 멀티 download | 1 stream |
| 서버 비용 | 0 | 중간 (forwarding) | 높음 (transcoding) |
| 예 | 일반 WebRTC | LiveKit, mediasoup, Janus | Jitsi Videobridge |
DTLS-SRTP (미디어 암호화)
WebRTC 는 모든 미디어를 DTLS-SRTP 로 암호화합니다. DTLS 로 키 교환 후 SRTP 로 미디어 패킷을 암호화.
- DTLS: UDP 위의 TLS. 핸드셰이크에서
a=fingerprint:sha-256 ...(SDP 에서 교환된 인증서 지문) 검증 - SRTP: 대칭키로 RTP 패킷의 payload 암호화 + HMAC-SHA1 무결성
브라우저에서 평문 WebRTC 는 표준상 불가. MITM 차단.
Simulcast / SVC (다중 해상도 스트림)
SFU 에서 화면 크기가 다른 구독자에게 서로 다른 품질을 주기 위한 기술.
- Simulcast: 송신자가 같은 영상을 여러 해상도 (720p, 360p, 180p) 로 동시 인코딩해 전송. SFU 가 선택.
- SVC (Scalable Video Coding): 레이어 구조로 인코딩, 하위 레이어 위에 상위 레이어를 쌓는 방식. 대역폭 효율 좋음 (예: AV1-SVC, H264-SVC).
flowchart TB
Sender["발신자"]
Sender -->|"720p stream"| SFU
Sender -->|"360p stream"| SFU
Sender -->|"180p stream"| SFU
SFU -->|"720p"| ClientHD["고대역폭 수신자"]
SFU -->|"360p"| ClientMD["중대역폭 수신자"]
SFU -->|"180p"| ClientLD["저대역폭 수신자"]
수신자 대역폭/CPU 에 따라 SFU 가 실시간으로 레이어를 전환해줍니다.
Bandwidth Estimation (REMB / TWCC)
WebRTC 는 수신자의 네트워크 상태를 추정해 송신자가 비트레이트를 자동 조절합니다.
| 알고리즘 | 방향 | 방식 |
|---|---|---|
| REMB (Receiver Estimated Max Bitrate) | 수신 → 송신 | 수신자가 추정 최대 속도를 RTCP 로 전송 |
| TWCC (Transport-Wide Congestion Control) | 수신 → 송신 | 수신 타임스탬프를 모두 보고, 송신측에서 추정 |
TWCC 가 더 정확하고 최신 표준. GCC (Google Congestion Control) 알고리즘이 TWCC 피드백을 사용합니다.
음성 에이전트 + WebRTC
flowchart LR
User[Browser/Mobile] -->|WebRTC| SFU[LiveKit SFU]
SFU -->|미디어 stream| Agent["Voice Agent (Pipecat / LiveKit Agents)"]
Agent -->|STT| LLM
LLM -->|TTS audio| Agent
Agent -->|미디어 stream| SFU
SFU -->|WebRTC| User
자세한 건 pipecat-livekit.
WebRTC vs WebSocket vs HTTP/3 (voice)
| 항목 | WebRTC | WebSocket | HTTP/3 |
|---|---|---|---|
| Latency | 최저 (UDP) | 중간 (TCP) | 중간 |
| Packet loss | FEC + loss concealment | TCP 재전송 (HoL) | QUIC stream 별 |
| Echo cancel | 내장 (browser) | 직접 | 직접 |
| NAT 통과 | ICE | TCP/HTTP 기본 OK | UDP 차단 위험 |
| 코덱 | Opus, VP8/9, H264 | 직접 | 직접 |
| 사용 | voice / video | 일반 | 차세대 |
voice agent = WebRTC 가 거의 정답. 단 방화벽 UDP 차단 환경 은 WebSocket fallback.
getStats() 로 품질 모니터링
setInterval(async () => {
const stats = await pc.getStats();
stats.forEach(stat => {
if (stat.type === 'inbound-rtp' && stat.kind === 'audio') {
console.log('jitter', stat.jitter);
console.log('packetsLost', stat.packetsLost);
console.log('roundTripTime', stat.roundTripTime);
}
});
}, 2000);
주요 지표:
| 지표 | 기준 (음성 품질) |
|---|---|
jitter | < 30ms 이면 양호 |
packetsLost (비율) | < 1% 이면 양호, > 5% 이면 품질 저하 |
roundTripTime | < 150ms 이면 양방향 통화에 OK |
framesDropped | 낮을수록 좋음 (영상) |
흔한 함정
WARNING
- STUN 만 + TURN 없음 = 20% 사용자 연결 실패. coturn 운영 필수.
- 에코 (사용자 음성 마이크로 다시) =
getUserMedia({ audio: { echoCancellation: true } }). AEC 필수. - Signaling = WebSocket 권장 = HTTP long-polling 은 느리고 깨짐 잦음.
- mesh 4명+ 운영 = N² 폭발. SFU 로 반드시 전환.
- SDP 직접 편집 = browser 버전마다 다름. libwebrtc / SFU lib 활용.
관련 위키
- voice-agent-architecture
- pipecat-livekit
- WebSocket
- UDP
- TLS (DTLS 기반)
이 글의 용어 (5개)
- [Django] Channels: WebSocket, Consumer, Groupdjango
- 정의 Django Channels는 Django에 WebSocket, 비동기 프로토콜, 백그라운드 작업을 추가하는 공식 확장. 기본 Django는 HTTP만, Channels는 …
- [Voice AI] 음성 에이전트 아키텍처: Cascading / Streaming / S2S + Latency Budgetai
- 정의 실시간 음성 AI 의 3가지 패턴. 각자 지연 vs 유연성 vs 비용 트레이드오프. 3 패턴 비교 | | Cascading | Streaming | S2S | |---|--…
- [Voice AI] Pipecat vs LiveKit Agents: 프레임워크 비교ai
- 정의 2026 시점 voice agent 프레임워크 4가지 선택지: | | Pipecat | LiveKit Agents | Vapi / Retell | 자체 구축 | |---|-…
- TLS / SSLnetwork
- 정의 TLS (Transport Layer Security) 는 (또는 위 ) 위에서 동작하는 암호화·인증 프로토콜이다. SSL 은 TLS 의 전신 (Netscape 1995, …
- UDPnetwork
- 정의 UDP (User Datagram Protocol)는 비연결·비신뢰 전송 계층 프로토콜이다. RFC 768 (1980) 로 정의되었다. TCP 와 대비된다. "보내고 잊는다…
이 개념을 다룬 위키 페이지 (9)
- wiki[Voice AI] Pipecat vs LiveKit Agents: 프레임워크 비교
- wikiDTLS
- wiki[Network] NAT: Network Address Translation
- wikiQUIC
- wikiUDP
- wiki[Network] WebSocket: HTTP upgrade, frame, ping/pong
- wiki[Voice AI] WebSocket 미디어 스트림: 양방향 풀듀플렉스, 프레임 버퍼링
- wiki[Voice AI] Speech-to-Speech: OpenAI Realtime, Gemini Live, Moshi
- wiki[Voice AI] VAD: Silero, 에너지 기반, endpointing 임계값
💬 댓글