Zettelkasten

Agora Server Gateway SDK의 encoded frame observer는 원본이 아닌 재인코딩된 Opus를 전달한다

·수정 2026.04.27·수정 1

요약

  • audio_recv_encoded_frame=1 + encoded frame observer를 등록하면 Opus 프레임을 콜백으로 받을 수 있지만, 이것은 네트워크 원본이 아닌 SDK 내부에서 디코딩 후 재인코딩된 Opus다
  • 바이트 단위 비교, 암호화 확인, 프레임 듀레이션 불일치(10ms→20ms)로 확정
  • Opus 패스스루가 필요하면 Agora Cloud Recording의 비트랜스코딩 모드(streamMode: "original")가 유일한 경로

본문

검증 과정

1단계: ctypes 바인딩으로 encoded frame 수신 성공

Python SDK의 register_audio_encoded_frame_observer가 미구현(#todo)이어서, C 네이티브 함수를 직접 ctypes로 바인딩했다.

agora_audio_encoded_frame_rev_observer_create
agora_remote_audio_track_register_encoded_frame_rev_observer

등록 성공(ret=0), 콜백 호출 확인. 수신 프레임은 유효한 Opus로 ffmpeg 디코딩 가능.

2단계: 패스스루 vs 재인코딩 검증

검증 결과 의미
바이트 비교 (콜백 프레임 vs 네트워크 패킷) 6바이트 서브스트링 매치 0건 동일 데이터 아님
네트워크 payload 엔트로피 ~6.5 bits/byte 암호화됨 — 원본 추출 불가
프레임 듀레이션 네트워크 10ms, 콜백 20ms SDK가 2개를 합쳐서 재인코딩
프레임 크기 네트워크 평균 262B, 콜백 평균 70B 크기 불일치

3단계: C++ 헤더로 API 동작 확인

NGIAgoraAudioTrack.hAudioEncFrameRecvParams에 ORIGINAL 모드가 있지만, C API wrapper는 이 파라미터를 받지 않는다. 또한 SDK 내부에 AudioPktConverter 파이프라인이 항상 작동하여 복호화→디코딩→재인코딩을 수행한다.

SDK 내부 파이프라인

Network(Opus, 암호화, 10ms)
    ↓ SDK 복호화
    ↓ 지터 버퍼 (NetEq) + FEC 복구
    ↓ Opus 디코딩 → PCM
    ↓ 오디오 프로세싱 (AEC, 노이즈 제거 등)
    ↓ Opus 재인코딩 (20ms)
    ↓ 콜백 전달

콜백 시그니처 (C 헤더 기준 정확한 정의)

// receive용 struct (send용과 다름!)
typedef struct _encoded_audio_frame_rev_info {
    uint64_t sendTs;  // 송신 타임스탬프
    uint8_t codec;    // 코덱 (1=Opus)
} encoded_audio_frame_rev_info;

// observer struct (콜백 1개)
typedef struct _audio_encoded_frame_rev_observer {
    int (*on_encoded_audio_frame_received)(
        AGORA_HANDLE observer_handle,
        const uint8_t* packet,
        size_t length,
        const encoded_audio_frame_rev_info* info
    );
} audio_encoded_frame_rev_observer;

대안

솔루션 Opus 원본 보존
Server Gateway SDK (우리) 불가 — 재인코딩
Cloud Recording (비트랜스코딩, streamMode: "original") 가능성 높음
On-Premise Recording SDK 불가 — AAC/PCM만

참고

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