Zettelkasten

Agora SDK는 Opus 패킷을 수신하여 내부에서 PCM으로 디코딩한다

·수정 2026.04.26·수정 1

요약

  • Agora SD-RTN에서 ECS/로컬로 들어오는 UDP 패킷은 Opus 인코딩 상태(평균 130B/pkt)이며, SDK 내부에서 PCM으로 디코딩하여 콜백에 전달한다
  • RTCConnConfig.audio_recv_encoded_frame=0이 이 동작을 결정하며, tcpdump 캡처로 네트워크 레벨에서 실증했다
  • VPC ingress ~23kbps/user 역산은 Opus 기준이 맞다
    • kbps 는 killo bit per second

본문

배경

connecting-call-recording-service에서 코드의 audio_recv_encoded_frame=0 필드명을 보고 "SDK 내부에서 Opus→PCM 디코딩한다"고 추론했지만, 문서에서 정확한 동작을 확인하지 못해 네트워크 레벨에서 직접 검증했다.

검증 방법: tcpdump + test-web

  1. sudo tcpdump -i en0 udp -c 1000 -w /tmp/agora_capture.pcap로 UDP 패킷 캡처
  2. test_web.py로 브라우저 마이크 → Agora 채널 → 서버 녹음 파이프라인 구동
  3. 캡처된 패킷에서 Agora 서버(NAT64 64:ff9b:: 프리픽스) → 로컬 방향의 수신 패킷만 필터
  4. payload 크기 분포로 코덱 판별

수신 패킷 크기 분포 (Agora SD-RTN → SDK)

구간 패킷 수 비율 의미
0-50B 63 16% 시그널링/제어
51-200B 301 76% Opus 인코딩 오디오
201-500B 21 5% 번들된 Opus / 제어
501B+ 12 3% FEC / 핸드셰이크

평균 130B/packet. PCM이었다면 ~960B 이상이 대다수여야 하나, 1001B 이상은 0.7%뿐.

판별 기준

Opus 프레임 크기 (20ms 기준, VBR)

  • 48kbps: 48000 × 0.02 / 8 = 120 bytes (payload)
  • 24kbps: 24000 × 0.02 / 8 = 60 bytes (payload)
    • RTP 헤더(12B) + Agora 프로토콜 헤더

PCM 프레임 크기 (코드에서 도출)

  • 48000Hz × 2B × 1ch × 0.01s = 960 bytes (10ms)
  • 48000Hz × 2B × 1ch × 0.02s = 1,920 bytes (20ms)

SDK 콜백 로그로 PCM 디코딩 확인

AudioFrame info for uid=2526: bytes_per_sample=2, samples_per_sec=48000,
channels=1, samples_per_channel=480, buffer_len=960

samples_per_channel=480 = 48000Hz × 0.01s → SDK가 10ms PCM 프레임으로 디코딩하여 전달.

결정하는 코드

# agora_base.py - RTCConnConfig
audio_recv_encoded_frame: int = 0  # 0=SDK가 디코딩, 1=인코딩된 채로 전달

# recording_service.py - 이 필드를 설정하지 않으므로 기본값 0 사용
self._conn_config = RTCConnConfig(
    auto_subscribe_audio=1,
    client_role_type=ClientRoleType.CLIENT_ROLE_BROADCASTER
)

agoraapi.log에서도 recvType:(audio: 0, video: 0)으로 확인.

확정된 파이프라인

모바일(Opus) → Agora SD-RTN → UDP(Opus, ~130B/pkt)
                                    ↓
                              SDK 내부 Opus→PCM 디코딩
                              (audio_recv_encoded_frame=0)
                                    ↓
                              PCM s16le, 48kHz, mono (960B/10ms frame)
                                    ↓
                              FFmpeg → AAC 48k → HLS (.ts) → S3

참고

Opus VBR은 오디오 복잡도에 따라 bitrate를 실시간으로 조절한다 Opus DTX(Discontinuous Transmission)은 무음 구간을 더 낮의 비트레이트로 인코딩해 대역폭을 절감한다.