블로그 목록
Fundamentals20분 읽기

오디오 파이프라인 해부 — 마이크부터 스피커까지, 코덱·RTP·Jitter Buffer

마이크 입력이 ADC, audio processing, codec, RTP packetization, network, jitter buffer, decoder, DAC를 거쳐 재생되는 경로를 설명합니다. Opus·G.711·AAC의 사용 맥락과 packet loss·jitter 대응, RTMP-WebRTC 변환, Voice AI의 VAD·STT 경계를 구분합니다.

OpusRTPUDPJitter BufferAACWebRTCAgoraVADSTT오디오기초
목차(41개 항목)
  1. 1단계: 소리가 컴퓨터로 들어오는 과정
    1. 마이크 → ADC → PCM
  2. 2단계: 오디오 전처리 — AEC, AGC, NS
  3. 3단계: 코덱 인코딩 — 왜 PCM 그대로 보내지 않나?
    1. 코덱 선택 — 상황에 따라 다르다
    2. 각 코덱의 특징
    3. 코덱 선택 의사결정 트리
  4. 4단계: 네트워크 전송 — RTP, UDP, Jitter Buffer
    1. 왜 UDP를 우선 사용하는가?
    2. RTP (Real-time Transport Protocol)
    3. Jitter Buffer — 패킷 도착 시간을 재생 타이밍으로 바꾸는 장치
    4. TCP식 사고 vs Jitter Buffer — 중요한 차이
    5. Jitter Buffer 크기 = Latency vs 안정성 Trade-off
    6. Adaptive Jitter Buffer — 요즘은 고정이 아님
    7. Packet Loss 대응 — SFU와 클라이언트의 협력
  5. 5단계: 수신 — 디코딩 → DAC → 스피커
  6. WebRTC / Agora 실제 흐름
    1. Agora의 차이점 — SD-RTN
  7. 녹화/스트리밍 — 인코딩, 트랜스코딩, 패키징
    1. 인코딩 (Encoding)
    2. 트랜스코딩 (Transcoding)
    3. 패키징 (Packaging)
    4. RTMP Push — CDN으로 스트림 밀어넣기
    5. RTMP → WebRTC 변환 — Media Gateway가 하는 일
    6. 단계별로 보면
    7. RTMP → FLV tag 파싱이란?
    8. MTU — 왜 1200 바이트씩 쪼개냐?
    9. 내부 동작 — 의사 코드로 이해하기
    10. SFU — 압축 미디어를 선택적으로 전달
    11. 오버레이 네트워크 — 경로를 직접 선택한다
    12. WebRTC → RTMP 변환 — 역방향도 있다
    13. 실제 처리 단계
  8. VAD (Voice Activity Detection) — 왜 필요한가?
    1. 먼저 구분: 에코 vs VAD
    2. VAD가 없으면 생기는 4가지 문제
    3. STT Endpointing vs VAD — 타이밍 문제
    4. VAD의 위치 — 파이프라인에서 어디에?
  9. 전체 그림 — 상황별 오디오 흐름
  10. 핵심 정리
  11. 관련 글
  12. 참고 자료

마이크에 대고 말하면 상대방 스피커에서 소리가 나옵니다. 당연한 것 같지만, 그 사이에는 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가 필요합니다.

비유:
  RTMP = 택배 박스
  WebRTC = 오토바이 퀵배송

  물건은 같고 (영상/음성)
  포장 방식만 바뀐다
전체 흐름:

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함.

내부 동작 — 의사 코드로 이해하기

실제 구현은 훨씬 복잡하지만, 개념을 잡기 위한 추상적인 의사 코드입니다.

# 전체 파이프라인 (개념 이해용 의사 코드)

while True:
    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 읽기

def read_rtmp():
    # TCP 소켓에서 데이터 읽음
    data = socket.recv()
    # RTMP -> FLV tag 파싱
    return parse_rtmp(data)
# 2단계: payload 추출 — "포장 뜯기"

def extract_payload(rtmp_packet):
    if rtmp_packet.type == "video":
        return rtmp_packet.h264_nal_units  # H.264 데이터
    elif rtmp_packet.type == "audio":
        return rtmp_packet.aac_frames      # AAC 데이터
# 3단계: 코덱별 RTP payload 규격으로 packetize

def packetize_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로 전달

def send_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 내부 (개념 이해용 의사 코드)

def sfu_loop():
    while True:
        packet = input_queue.get()

        for subscriber in subscribers:
            send_udp(packet, subscriber)

# 확장: 실제 SFU는 네트워크 상태에 따라 선택적 전달

def sfu_loop_adaptive():
    while True:
        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 줄이려고
전체 RTMP → WebRTC 흐름 (최종):

RTMP (TCP)
   ↓
Media Gateway
   - H.264/AAC payload 추출
   - RTP packetize (sequence + timestamp 부여)
   ↓
SFU
   - packet 복제
   - 사용자별 선택적 분배
   ↓
오버레이 네트워크 (Edge → Edge, 최적 경로)
   ↓
WebRTC 클라이언트 (UDP)
RTMP vs WebRTC 비교:

┌──────────────┬─────────────────┬─────────────────┐
│              │ RTMP            │ WebRTC          │
├──────────────┼─────────────────┼─────────────────┤
│ 전송 프로토콜 │ TCP             │ UDP             │
├──────────────┼─────────────────┼─────────────────┤
│ 지연         │ 1~3초           │ 200ms 이하      │
├──────────────┼─────────────────┼─────────────────┤
│ 용도         │ 방송 업로드용    │ 실시간 시청용    │
│              │ (ingest)        │ (playback)      │
├──────────────┼─────────────────┼─────────────────┤
│ 오디오 코덱  │ AAC             │ Opus            │
├──────────────┼─────────────────┼─────────────────┤
│ 비디오 코덱  │ H.264           │ H.264/VP8/VP9   │
└──────────────┴─────────────────┴─────────────────┘

RTMP는 "올리는 용" (방송자 → 서버)
WebRTC는 "보는 용" (서버 → 시청자, 초저지연)

WebRTC → RTMP 변환 — 역방향도 있다

RTMP→WebRTC만 있는 게 아닙니다. WebRTC 스트림을 외부 CDN으로 내보내려면 역방향 변환이 필요합니다.

WebRTC 스트림은 RTP 기반 (Opus/VP8 등)
RTMP는 TCP 기반 (H.264/AAC)

→ 프로토콜도 다르고 코덱도 다를 수 있음
→ 서버에서 변환 과정이 필요
전체 흐름:

WebRTC Client
  ↓ RTP/UDP (Opus + VP8/H.264)
서버 (Media Gateway)
  ↓ RTP depacketization
  ↓ 디코딩 (Opus → PCM, VP8 → raw frame)
  ↓ 재인코딩 (PCM → AAC, raw frame → H.264)
  ↓ RTMP 패킷으로 재패키징
  ↓ RTMP push (TCP)
외부 CDN Ingest Endpoint
  ↓
HLS 패키징 → Edge → 시청자
핵심 포인트:

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)    트랜스코딩 필요
정리:

┌──────────────────┬──────────────────────────────────┐
│ 방향             │ 처리                             │
├──────────────────┼──────────────────────────────────┤
│ RTMP → WebRTC    │ FLV 파싱 → RTP 재패키징         │
│                  │ (코덱 같으면 디코딩 불필요)       │
├──────────────────┼──────────────────────────────────┤
│ WebRTC → RTMP    │ RTP depacketize → 트랜스코딩     │
│                  │ → RTMP 재패키징 → CDN push       │
│                  │ (코덱 같으면 리패키징만)          │
└──────────────────┴──────────────────────────────────┘

양방향 모두 핵심은 동일:
  코덱 일치 → 리패키징만 (가벼움)
  코덱 불일치 → 트랜스코딩 필요 (무거움)

실제 처리 단계

RTC tracks (실시간 스트림)
  ↓
decode / compose (필요 시)
  ↓
encode H.264 / AAC
  ↓
package as HLS segments
  ↓
CDN cache / distribute
  ↓
브라우저에서 재생

RTMP 경유:
OBS → RTMP push → CDN ingest → HLS packaging → Edge → 재생

RTMP → WebRTC (초저지연):
OBS → RTMP push → Media Gateway → 재패키징(RTP/UDP) → WebRTC 시청

WebRTC → RTMP (CDN 배포):
WebRTC → RTP depacketize → 트랜스코딩 → RTMP push → CDN → HLS → 재생

VAD (Voice Activity Detection) — 왜 필요한가?

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로 전달됨
  • STT가 침묵, 잡음, 배경음도 계속 처리할 수 있음
  • LLM 호출 시점은 STT endpointing이나 애플리케이션 turn detector가 별도로 결정해야 함

3. 발화 시작 시점 모름 -- 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를 둡니다.


전체 그림 — 상황별 오디오 흐름

[Capture]
  Mic → ADC → PCM

[Real-time 통신]
  PCM → Opus → Network → Opus → PCM → Speaker

[전화]
  PCM → µ-law → Network → µ-law → PCM → Speaker

[저장/배포]
  PCM → AAC/MP3 → 저장 → 재생

[녹화 → VOD]
  RTC → decode → encode(H.264+AAC) → package(HLS) → CDN → 재생

[RTMP → HLS 배포]
  OBS → RTMP push → CDN ingest → HLS packaging → Edge → 재생

[RTMP → WebRTC 초저지연]
  OBS → RTMP push → Media Gateway → 재패키징(RTP/UDP) → WebRTC 시청

[Voice AI]
  PCM → 전처리 → VAD → STT → LLM → TTS → Speaker

핵심 정리

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) → 디코딩 → 재생

관련 글

참고 자료

© 2026 Frank Kim. All rights reserved.