Opus 오디오 코덱 차세대 표준 | WebRTC·저지연·FFmpeg 실전 가이드
이 글의 핵심
화상회의나 보이스챗에서는 음질만큼 지연 시간이 중요한데, Opus는 로열티 없이 저지연과 높은 압축 효율을 함께 얻을 수 있는 IETF 표준입니다. 다만 일부 구형 기기와 브라우저에서 재생되지 않는 호환성 문제가 있어, 어떤 상황에서 AAC나 MP3를 폴백으로 남겨야 하는지까지 판단할 수 있게 구성했습니다.
들어가며
Opus는 IETF RFC 6716 으로 표준화된 오디오 코덱으로, 음성(SILK 계열) 과 음악(CELT 계열) 을 하나의 코덱에서 다루도록 설계되었습니다. 6~510 kbps 같은 넓은 비트레이트 범위와 프레임 길이 조절 덕분에 실시간 화상·음성·게임 보이스처럼 지연 예산(ms) 이 빡빡한 환경에서 강합니다. Opus가 실시간 통신에서 사실상 기본값이 된 이유는 음질보다 설계 목표 쪽에 있습니다. MP3와 AAC는 “파일을 저장했다가 재생하는” 용도를 전제로 만들어져, 앞뒤 프레임을 넉넉히 보고 압축 효율을 높이는 대신 수십~수백 ms의 지연을 받아들였습니다. 반면 Opus는 처음부터 “말하는 사람과 듣는 사람 사이의 왕복 지연”을 줄이는 것을 목표로, 프레임 길이를 2.5ms까지 줄일 수 있고 비트레이트·대역폭·모드를 패킷 단위로 바꿀 수 있게 만들어졌습니다. 이 글은 그 구조를 먼저 설명한 뒤, FFmpeg와 WebRTC에서 실제로 어떤 값을 골라야 하는지와 호환성 문제를 다룹니다.
코덱 개요
역사 및 개발 배경
Opus는 Xiph.org 가 주도한 CELT 와 Skype가 발전시킨 SILK 를 결합한 하이브리드 코덱으로, IETF codec working group에서 표준화되었습니다.
주요 마일스톤:
- 2007: CELT 프로젝트 시작 (Xiph.org)
- 2009: SILK 개발 (Skype)
- 2010: IETF에서 CELT + SILK 통합 결정
- 2012: RFC 6716 표준화
- 2016: RFC 7874에서 G.711과 함께 WebRTC 필수 구현(MTI) 오디오 코덱으로 지정
- 현재: 모든 주요 브라우저의 WebRTC 구현, Discord 등 음성 채팅 서비스, WhatsApp 같은 메신저 통화에 널리 쓰임
기술적 특징
| 항목 | 설명 |
|---|---|
| 압축 방식 | SILK (음성) + CELT (음악) 하이브리드 |
| 샘플레이트 | 8/12/16/24/48 kHz (내부 자동 처리) |
| 비트레이트 | 6~510 kbps |
| 지연 | 5~66.5ms (프레임 길이 조절 가능) |
| 채널 | 모노, 스테레오 (최대 255 채널 이론상 가능) |
| 패킷 손실 복구 | FEC (Forward Error Correction) 내장 |
주요 모드
| 모드 | 비트레이트 | 용도 | 특징 |
|---|---|---|---|
| SILK | 6-40 kbps | 음성 통화 | LPC 기반, 음성 최적화 |
| Hybrid | 12-64 kbps | 음성+음악 혼합 | 자동 전환 |
| CELT | 48-510 kbps | 음악, 고품질 | MDCT 기반 |
표의 비트레이트 구간은 대략적인 경향이고 경계가 딱 나뉘지는 않습니다. libopus 인코더는 목표 비트레이트, application 설정, 입력 신호의 성격(음성인지 음악인지 판별한 결과)을 보고 패킷마다 모드와 오디오 대역폭(협대역 4kHz부터 전대역 20kHz까지)을 스스로 고릅니다. 그래서 같은 32kbps라도 voip로 두면 SILK나 Hybrid 쪽으로, audio로 두면 CELT 쪽으로 기울어집니다. 한 스트림 안에서도 모드가 바뀔 수 있고, 디코더는 패킷 첫 바이트(TOC)의 정보만 보고 알아서 따라가므로 수신 측에서 따로 설정할 것은 없습니다.
압축 원리
SILK (Speech) 모드
특징:
- LPC (Linear Predictive Coding) 기반
- 음성 포만트 보존
- 저비트레이트 최적화 처리 과정:
PCM 입력
↓
음성 분석 (피치, 포만트)
↓
LPC 계수 추출
↓
잔차 신호 양자화
↓
비트스트림
CELT (Music) 모드
특징:
- MDCT 기반 주파수 변환
- 저지연 설계 (짧은 프레임)
- 음악 품질 우선 처리 과정:
PCM 입력
↓
MDCT 변환
↓
심리음향 모델
↓
양자화·비트 할당
↓
비트스트림
하이브리드 모드
자동 전환:
-
입력 신호 분석
-
음성 비율 높음 → SILK 비중 증가
-
음악 비율 높음 → CELT 비중 증가 장점:
-
혼합 콘텐츠 (음성+배경음악) 최적화
-
비트레이트 효율 향상
정확히 말하면 Hybrid 모드는 “비중을 조절”하는 방식이라기보다 주파수 대역을 나눠 맡는 방식입니다. 8kHz 아래 저대역은 음성에 강한 SILK가 부호화하고, 그 위 고대역은 CELT가 부호화해 두 결과를 합칩니다. 사람 목소리의 핵심 정보는 저대역에 몰려 있으므로 이 대역은 음성 모델로 효율적으로 압축하고, 치찰음이나 배경음 같은 고대역 성분은 범용 변환 코덱에 맡기는 구조입니다. 이 설계 덕분에 20~32kbps 정도의 낮은 비트레이트에서도 전대역(fullband) 음성을 보낼 수 있습니다.
실전 인코딩
FFmpeg 기본 예제
음성 인코딩 (VoIP)
# 모노, 32 kbps (음성 최적화)
ffmpeg -i voice.wav \
-c:a libopus \
-b:a 32k \
-ac 1 \
-ar 48000 \
-application voip \
voice.opus
파라미터 설명:
-b:a 32k: 비트레이트 32 kbps-ac 1: 모노-ar 48000: 48kHz 샘플레이트-application voip: 음성 최적화 모드
-ar 48000을 명시하는 이유는 libopus가 받아들이는 입력 샘플레이트가 8/12/16/24/48kHz뿐이기 때문입니다. 44.1kHz WAV를 넣으면 FFmpeg가 자동으로 48kHz로 리샘플링하는데, 이 동작을 명시해 두면 파이프라인을 읽는 사람이 의도를 알 수 있습니다. 또 Ogg Opus 파일의 헤더에는 “원본 샘플레이트” 필드가 따로 있어 디코더가 44.1kHz로 되돌려 출력할 수도 있지만, Opus 내부는 항상 48kHz 기준으로 동작합니다.
음악 인코딩
# 스테레오, 128 kbps (음악)
ffmpeg -i music.wav \
-c:a libopus \
-b:a 128k \
-ac 2 \
-ar 48000 \
-application audio \
music.opus
저지연 인코딩
# 저지연 모드 (5ms 프레임)
ffmpeg -i input.wav \
-c:a libopus \
-b:a 64k \
-frame_duration 5 \
-application lowdelay \
lowdelay.opus
프레임 길이 옵션:
2.5: 2.5ms (최저 지연)5: 5ms10: 10ms (기본)20: 20ms40: 40ms60: 60ms (최고 효율)
주의할 점은 10ms보다 짧은 프레임(2.5ms, 5ms)에서는 SILK를 쓸 수 없다는 것입니다. SILK는 10ms 단위로 동작하므로, 이 프레임 길이에서는 CELT만 쓰이고 저비트레이트 음성 효율이 떨어집니다. 위 예제에서 -application lowdelay를 함께 준 것도 이 때문으로, lowdelay는 SILK를 아예 끄고 CELT 전용으로 동작시켜 알고리즘 지연을 최소화합니다. 기본값은 20ms이며, WebRTC도 대부분 20ms 패킷(ptime=20)을 씁니다. 네트워크 패킷 수와 헤더 오버헤드를 생각하면 20ms가 지연과 효율 사이의 무난한 균형점입니다.
고급 옵션
VBR vs CBR
# VBR (기본, 권장)
ffmpeg -i input.wav -c:a libopus -b:a 128k -vbr on output.opus
# CBR (스트리밍 대역폭 예측)
ffmpeg -i input.wav -c:a libopus -b:a 128k -vbr off output.opus
# Constrained VBR (중간)
ffmpeg -i input.wav -c:a libopus -b:a 128k -vbr constrained output.opus
패킷 손실 대응 (FEC)
# FEC 활성화 (음성 신호 + voip 모드에서 효과적)
ffmpeg -i input.wav \
-c:a libopus \
-b:a 32k \
-application voip \
-packet_loss 10 \
-fec 1 \
output.opus
Opus의 인밴드 FEC는 SILK 계층의 기능이라, 인코더가 CELT 모드를 고르는 설정(높은 비트레이트의 audio, lowdelay)에서는 켜 두어도 효과가 없습니다. 또 FEC는 패킷 N에 패킷 N-1의 저품질 사본을 덧붙여, 수신 측이 N-1을 잃었을 때 N을 받아 복원하는 방식입니다. 따라서 파일로 저장해 재생하는 .opus에는 의미가 없고, RTP처럼 패킷을 잃을 수 있는 실시간 전송에서만 가치가 있습니다. 파일 인코딩 예제에서 이 옵션을 쓰는 것은 설정을 미리 들어 보는 테스트 용도라고 보면 됩니다.
WebM 컨테이너
# 비디오 + Opus 오디오
ffmpeg -i input.mp4 \
-c:v libvpx-vp9 \
-c:a libopus \
-b:a 128k \
output.webm
배치 처리
Bash 스크립트
#!/bin/bash
# 모든 WAV 파일을 Opus로 변환
for f in *.wav; do
echo "Processing: $f"
ffmpeg -y -i "$f" \
-c:a libopus \
-b:a 128k \
-application audio \
"${f%.wav}.opus"
done
echo "완료!"
Python 스크립트
import subprocess
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor
def convert_to_opus(input_file, output_file, bitrate='128k', application='audio'):
"""
WAV → Opus 변환
"""
cmd = [
'ffmpeg', '-y',
'-i', str(input_file),
'-c:a', 'libopus',
'-b:a', bitrate,
'-application', application,
str(output_file)
]
subprocess.run(cmd, check=True)
return output_file
def batch_convert(input_dir, output_dir, max_workers=4):
"""
병렬 배치 변환
"""
Path(output_dir).mkdir(parents=True, exist_ok=True)
wav_files = list(Path(input_dir).glob('*.wav'))
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = []
for wav_file in wav_files:
output_file = Path(output_dir) / f"{wav_file.stem}.opus"
future = executor.submit(convert_to_opus, wav_file, output_file)
futures.append((wav_file.name, future))
for name, future in futures:
try:
result = future.result()
print(f"✓ {name} → {result.name}")
except Exception as e:
print(f"✗ {name}: {e}")
# 사용
batch_convert('input_wavs', 'output_opus', max_workers=4)
성능 비교
비트레이트별 음질 비교
아래 표는 공개된 청취 테스트들(예: HydrogenAudio 커뮤니티의 다중 포맷 청취 테스트)과 코덱 설계에서 나오는 일반적인 경향을 정리한 것으로, 특정 측정 결과가 아닙니다. 실제 체감 품질은 원본 소스, 인코더 버전·설정, 청취 환경에 따라 달라지므로 서비스에 적용할 비트레이트는 자기 콘텐츠로 ABX 청취 테스트를 해 보고 정하는 것이 좋습니다.
음성 (Speech)
대상: 모노, 음성 전용
| 비트레이트 | MP3 | AAC-HE | Opus | 평가 |
|---|---|---|---|---|
| 12 kbps | 사용 불가 | 나쁨 | 보통 | Opus만 가능 |
| 16 kbps | 사용 불가 | 나쁨 | 좋음 | Opus 실용 |
| 24 kbps | 나쁨 | 보통 | 매우 좋음 | Opus 최적 |
| 32 kbps | 보통 | 좋음 | 우수 | Opus 유리 |
| 64 kbps | 좋음 | 매우 좋음 | 우수 | 모두 충분 |
음악 (Music)
대상: 스테레오 음악
| 비트레이트 | MP3 | AAC-LC | Opus | 평가 |
|---|---|---|---|---|
| 64 kbps | 나쁨 | 보통 | 좋음 | Opus 유리 |
| 96 kbps | 보통 | 좋음 | 매우 좋음 | Opus/AAC 우수 |
| 128 kbps | 좋음 | 매우 좋음 | 매우 좋음 | 일반 청취 충분 |
| 192 kbps | 매우 좋음 | 우수 | 우수 | 구분 어려움 |
지연 시간 비교
| 코덱 | 프레임 크기 | 알고리즘 지연 | 총 지연 | 용도 |
|---|---|---|---|---|
| Opus (2.5ms, lowdelay) | 120 샘플 | 5ms | 매우 낮음 | 실시간 악기 합주·게임 |
| Opus (10ms) | 480 샘플 | 12.5~16.5ms | 낮음 | VoIP |
| Opus (20ms, 기본) | 960 샘플 | 22.5~26.5ms | 낮음 | 일반 통화 |
| AAC-LC | 1024 샘플 | 수십 ms | 중간 | 스트리밍 |
| MP3 | 1152 샘플 | 수십~100ms 이상 | 높음 | 파일 재생 |
결론: Opus는 알고리즘 지연을 수 ms~수십 ms 범위에서 고를 수 있습니다.
Opus의 알고리즘 지연은 “프레임 길이 + 미리 보기(look-ahead)“입니다. CELT 전용(lowdelay)은 미리 보기가 2.5ms라 2.5ms 프레임에서 5ms가 되고, SILK·Hybrid를 쓰는 기본 설정은 미리 보기가 6.5ms라 20ms 프레임에서 26.5ms가 됩니다. 사용자가 느끼는 입에서 귀까지의 지연은 여기에 오디오 장치 버퍼, 네트워크 전송, 수신 측 지터 버퍼가 더해진 값이라, 실제 통화에서는 코덱 지연보다 지터 버퍼와 네트워크가 더 큰 비중을 차지하는 경우가 많습니다. 코덱 프레임을 2.5ms로 줄여도 지터 버퍼가 60ms라면 체감 차이는 크지 않다는 뜻입니다.
인코딩 속도는 크게 걱정할 부분이 아닙니다. libopus는 현대 CPU에서 음성 한 채널을 실시간보다 훨씬 빠르게 인코딩하므로, 서버 한 대에서 많은 채널을 동시에 처리하는 경우가 아니면 병목이 되지 않습니다. 대량 인코딩 비용이 걱정된다면 -compression_level(0~10, 기본 10)을 낮춰 CPU 사용량과 품질을 맞바꿀 수 있습니다.
실무 활용 사례
사례 1: WebRTC 화상회의
요구사항:
- 초저지연 (<50ms)
- 음성 명료도
- 패킷 손실 대응
JavaScript WebRTC 설정
class OpusWebRTCConfig {
static getAudioConfig(mode = 'voip') {
const configs = {
voip: {
codec: 'opus',
bitrate: 32000,
sampleRate: 48000,
channels: 1,
fec: true,
dtx: true,
maxptime: 60
},
music: {
codec: 'opus',
bitrate: 128000,
sampleRate: 48000,
channels: 2,
fec: false,
dtx: false,
maxptime: 20
}
};
return configs[mode];
}
static createSDP(config) {
return `
m=audio 9 UDP/TLS/RTP/SAVPF 111
a=rtpmap:111 opus/48000/2
a=fmtp:111 minptime=10;useinbandfec=${config.fec ? 1 : 0};usedtx=${config.dtx ? 1 : 0}
a=maxptime:${config.maxptime}
a=ptime:20
`.trim();
}
}
// 사용
const voipConfig = OpusWebRTCConfig.getAudioConfig('voip');
const sdp = OpusWebRTCConfig.createSDP(voipConfig);
console.log(sdp);
이 코드는 SDP의 어느 줄이 어떤 설정에 대응하는지 보여 주기 위한 예시이고, 브라우저에 codec, bitrate 같은 필드를 가진 설정 객체를 넘기는 API가 있는 것은 아닙니다. 실제 브라우저 WebRTC에서는 대부분 SDP의 a=fmtp 줄을 협상 전에 고쳐 쓰거나(SDP munging), 비트레이트는 RTCRtpSender.setParameters()의 encodings[0].maxBitrate로 조정합니다. 몇 가지 헷갈리는 점이 있습니다. a=rtpmap:111 opus/48000/2의 /2는 모노로 보낼 때도 항상 2로 적는 것이 RFC 7587의 규칙이고, 실제 스테레오 수신 여부는 fmtp의 stereo=1로 정합니다. useinbandfec=1은 “나는 FEC가 담긴 패킷을 받을 수 있다”는 수신 측 선언이라, 상대방 인코더가 FEC를 켜게 만드는 것이지 내 인코더 설정이 아닙니다. usedtx=1(무음 구간 전송 중단)은 대역폭을 크게 아끼지만, 일부 서버 측 믹서나 녹음 파이프라인이 끊긴 패킷을 손실로 오인해 잡음을 넣는 경우가 있어 켜기 전에 전체 경로를 확인해야 합니다.
FFmpeg 테스트 인코딩
# 음성 최적화 (16 kbps)
ffmpeg -i voice.wav \
-c:a libopus \
-b:a 16k \
-ac 1 \
-application voip \
-frame_duration 20 \
voice_16k.opus
# 음성 최적화 (32 kbps)
ffmpeg -i voice.wav \
-c:a libopus \
-b:a 32k \
-ac 1 \
-application voip \
voice_32k.opus
사례 2: 게임 보이스챗
요구사항:
- 저지연 (<30ms)
- 대역폭 절약
- 다중 플레이어 동시 송수신
Unity C# 예제
using System;
using System.Runtime.InteropServices;
public class OpusVoiceChat
{
[DllImport("opus")]
private static extern IntPtr opus_encoder_create(
int Fs,
int channels,
int application,
out int error
);
[DllImport("opus")]
private static extern int opus_encode(
IntPtr st,
short[] pcm,
int frame_size,
byte[] data,
int max_data_bytes
);
private IntPtr encoder;
private const int SAMPLE_RATE = 48000;
private const int CHANNELS = 1;
private const int BITRATE = 24000;
private const int FRAME_SIZE = 960; // 20ms at 48kHz
public void Initialize()
{
int error;
encoder = opus_encoder_create(
SAMPLE_RATE,
CHANNELS,
2, // OPUS_APPLICATION_VOIP
out error
);
if (error != 0)
{
throw new Exception($"Opus encoder error: {error}");
}
}
public byte[] Encode(short[] pcm)
{
byte[] output = new byte[4000];
int encoded_bytes = opus_encode(
encoder,
pcm,
FRAME_SIZE,
output,
output.Length
);
if (encoded_bytes < 0)
{
throw new Exception($"Encode error: {encoded_bytes}");
}
byte[] result = new byte[encoded_bytes];
Array.Copy(output, result, encoded_bytes);
return result;
}
}
이 예제는 인코더 생성과 인코딩만 보여 주는 최소 골격이라 실제로 쓰려면 몇 가지를 채워야 합니다. BITRATE 상수를 선언해 두고도 적용하지 않았는데, 비트레이트는 opus_encoder_ctl(enc, OPUS_SET_BITRATE(24000))으로 설정합니다. 이 함수는 C의 가변 인자 함수라 C#의 DllImport로 직접 부르면 플랫폼(특히 ARM64 iOS·Android)에 따라 인자가 잘못 전달될 수 있으므로, 고정 인자를 받는 작은 C 래퍼 함수를 만들어 노출하는 방식이 흔히 쓰입니다. 또 인코더를 해제하는 opus_encoder_destroy를 호출하지 않으면 씬을 오갈 때마다 네이티브 메모리가 새므로 IDisposable로 감싸야 합니다. frame_size는 채널당 샘플 수라서, 48kHz에서 20ms면 960이고 입력 pcm 배열은 정확히 그만큼의 샘플을 담고 있어야 합니다. Unity 마이크 입력은 보통 다른 크기의 버퍼로 들어오므로 링 버퍼에 모아 960개씩 잘라 넘기는 처리가 필요합니다. 출력 버퍼 4000바이트는 libopus 문서가 권장하는 안전한 크기입니다.
FFmpeg 테스트
# 게임 보이스 (24 kbps, 저지연)
ffmpeg -i game_voice.wav \
-c:a libopus \
-b:a 24k \
-ac 1 \
-application voip \
-frame_duration 10 \
game_voice.opus
사례 3: 팟캐스트 배포
요구사항:
- 파일 크기 최소화
- 음성 명료도
- RSS 피드 호환
Opus 설정 (최소 크기)
# 모노, 48 kbps (팟캐스트)
ffmpeg -i podcast.wav \
-c:a libopus \
-b:a 48k \
-ac 1 \
-ar 48000 \
-application voip \
-metadata title="에피소드 제목" \
-metadata artist="팟캐스트 이름" \
podcast.opus
파일 크기 비교:
- MP3 64k: 1시간 = 28MB
- Opus 48k: 1시간 = 21MB
Python 자동화
import subprocess
from pathlib import Path
def create_podcast_opus(input_file, metadata):
"""
팟캐스트 Opus 생성
"""
output_file = Path(metadata['filename'])
cmd = [
'ffmpeg', '-y',
'-i', str(input_file),
'-c:a', 'libopus',
'-b:a', '48k',
'-ac', '1',
'-ar', '48000',
'-application', 'voip',
'-metadata', f"title={metadata['title']}",
'-metadata', f"artist={metadata['artist']}",
str(output_file)
]
subprocess.run(cmd, check=True)
size_mb = output_file.stat().st_size / (1024 * 1024)
print(f"생성 완료: {output_file.name} ({size_mb:.2f} MB)")
# 사용
metadata = {
'filename': 'episode_01.opus',
'title': '첫 번째 에피소드',
'artist': '내 팟캐스트'
}
create_podcast_opus('recording.wav', metadata)
사례 4: 웹 오디오 플레이어
요구사항:
- 브라우저 호환성
- 적응형 품질
- 폴백 지원
HTML5 Audio
<audio controls>
<source src="audio.opus" type="audio/ogg; codecs=opus">
<source src="audio.webm" type="audio/webm; codecs=opus">
<source src="audio.m4a" type="audio/mp4">
<source src="audio.mp3" type="audio/mpeg">
브라우저가 audio 태그를 지원하지 않습니다.
</audio>
JavaScript 적응형 로딩
class AdaptiveOpusPlayer {
constructor() {
this.audio = new Audio();
this.formats = [
{ src: 'audio.opus', type: 'audio/ogg; codecs=opus' },
{ src: 'audio.webm', type: 'audio/webm; codecs=opus' },
{ src: 'audio.m4a', type: 'audio/mp4' },
{ src: 'audio.mp3', type: 'audio/mpeg' }
];
}
selectFormat() {
for (const format of this.formats) {
if (this.audio.canPlayType(format.type)) {
return format.src;
}
}
return this.formats[this.formats.length - 1].src;
}
play() {
this.audio.src = this.selectFormat();
this.audio.play();
}
}
// 사용
const player = new AdaptiveOpusPlayer();
player.play();
canPlayType()은 "probably", "maybe", "" 중 하나를 돌려주는데, "maybe"도 참으로 평가되므로 이 코드는 “재생할 수도 있다”는 형식을 선택합니다. 대부분의 경우 문제없지만, 실제 재생 실패에 대비하려면 audio의 error 이벤트에서 다음 형식으로 넘어가는 처리를 더하는 것이 안전합니다. 또 play()는 프로미스를 돌려주고, 사용자 클릭 같은 제스처 없이 호출하면 브라우저의 자동 재생 정책 때문에 NotAllowedError로 거부될 수 있으므로 버튼 클릭 핸들러 안에서 부르고 반환된 프로미스의 실패를 처리해야 합니다.
최적화 팁
비트레이트 최적화
음성 비트레이트 가이드
| 용도 | 비트레이트 | 품질 |
|---|---|---|
| 저품질 통화 | 12-16 kbps | 이해 가능 |
| 일반 통화 | 24-32 kbps | 좋음 |
| 고품질 통화 | 40-64 kbps | 매우 좋음 |
음악 비트레이트 가이드
| 용도 | 비트레이트 | 품질 |
|---|---|---|
| 저품질 | 64-80 kbps | 보통 |
| 일반 | 96-128 kbps | 좋음 |
| 고품질 | 160-192 kbps | 매우 좋음 |
| 최고 | 256+ kbps | 원본과 거의 동일 |
프레임 길이 최적화
# 저지연 우선 (5ms)
ffmpeg -i input.wav -c:a libopus -b:a 64k -frame_duration 5 lowdelay.opus
# 효율 우선 (60ms)
ffmpeg -i input.wav -c:a libopus -b:a 64k -frame_duration 60 efficient.opus
트레이드오프:
- 짧은 프레임: 낮은 지연, 높은 오버헤드
- 긴 프레임: 높은 효율, 높은 지연
패킷 손실 대응
# FEC 활성화 (10% 손실 예상, 음성 모드)
ffmpeg -i input.wav \
-c:a libopus \
-b:a 32k \
-application voip \
-packet_loss 10 \
-fec 1 \
output.opus
트러블슈팅
문제 1: 브라우저 재생 안 됨
증상: 일부 브라우저(특히 Safari·iOS)에서 .opus 파일 재생 불가
<audio src="audio.opus" controls></audio>
<!-- 브라우저에 따라 재생 안 됨 -->
원인: FFmpeg로 만든 .opus 파일은 이미 Ogg 컨테이너이므로 Chrome·Firefox에서는 보통 재생됩니다. 문제는 대개 두 가지입니다. 첫째, Safari는 오랫동안 Ogg 컨테이너를 지원하지 않았고 Opus를 담을 수 있는 컨테이너 지원도 버전에 따라 다릅니다. 둘째, 웹 서버가 .opus 확장자에 audio/ogg 대신 application/octet-stream 같은 MIME 타입을 붙여 보내면 브라우저가 재생을 거부하기도 합니다. 서버 MIME 설정을 먼저 확인하고, 폭넓은 호환이 필요하면 WebM과 AAC(m4a) 폴백을 함께 제공합니다.
해결:
# Ogg 컨테이너
ffmpeg -i input.wav -c:a libopus -b:a 128k output.ogg
# WebM 컨테이너
ffmpeg -i input.wav -c:a libopus -b:a 128k output.webm
<audio controls>
<source src="audio.ogg" type="audio/ogg; codecs=opus">
<source src="audio.webm" type="audio/webm; codecs=opus">
</audio>
문제 2: 구형 기기 미지원
증상: 차량 USB, 구형 스마트폰에서 재생 불가 해결: MP3/AAC 폴백 제공
# 다중 포맷 생성
ffmpeg -i input.wav -c:a libopus -b:a 96k output.opus
ffmpeg -i input.wav -c:a aac -b:a 128k output.m4a
ffmpeg -i input.wav -c:a libmp3lame -b:a 192k output.mp3
문제 3: 음악 품질 저하
증상: 64 kbps Opus에서 음악이 뭉개짐 원인: 비트레이트 부족 해결: 음악은 최소 96 kbps 이상
# 음악 최소 권장 (96 kbps)
ffmpeg -i music.wav \
-c:a libopus \
-b:a 96k \
-application audio \
music.opus
# 고품질 (128 kbps)
ffmpeg -i music.wav \
-c:a libopus \
-b:a 128k \
-application audio \
music_hq.opus
문제 4: 지연 시간 높음
증상: 실시간 통화에서 지연 체감 원인: 프레임 길이가 너무 김 해결: 짧은 프레임 사용
# 저지연 (10ms 프레임)
ffmpeg -i input.wav \
-c:a libopus \
-b:a 64k \
-frame_duration 10 \
-application lowdelay \
lowdelay.opus
문제 5: 파일 크기 예측 불가 (VBR)
증상: VBR 모드에서 파일 크기를 미리 알 수 없음 해결: CBR 모드 사용
# CBR (크기 예측 가능)
ffmpeg -i input.wav \
-c:a libopus \
-b:a 128k \
-vbr off \
output.opus
# 파일 크기 계산
# 크기 = (비트레이트 * 시간) / 8
# 128 kbps * 300s / 8 = 4.8 MB
마무리
Opus는 음성+음악 하이브리드와 프레임 길이 조절로 저지연 실시간에 강합니다.
핵심 요약
- 하이브리드 구조
- SILK: 음성 최적화 (6-40 kbps)
- CELT: 음악 최적화 (48-510 kbps)
- 자동 모드 전환
- 저지연
- 프레임 길이: 2.5~60ms
- 총 지연: 5~66ms
- WebRTC 표준
- 로열티 프리
- 참조 구현 libopus는 BSD 라이선스
- 상용 배포 자유
- 관련 특허 보유사가 로열티 없는 라이선스를 제공
선택 가이드
| 상황 | 비트레이트 | 설정 |
|---|---|---|
| VoIP/화상회의 | 16-32 kbps | -application voip |
| 게임 보이스챗 | 24-48 kbps | -frame_duration 10 |
| 팟캐스트 | 48-64 kbps | 모노, -application voip |
| 음악 스트리밍 | 96-128 kbps | 스테레오, -application audio |
| 고품질 음악 | 160-192 kbps | 스테레오, VBR |
FFmpeg 명령 치트시트
# 음성 (32 kbps)
ffmpeg -i voice.wav -c:a libopus -b:a 32k -ac 1 -application voip voice.opus
# 음악 (128 kbps)
ffmpeg -i music.wav -c:a libopus -b:a 128k -ac 2 -application audio music.opus
# 저지연 (10ms)
ffmpeg -i input.wav -c:a libopus -b:a 64k -frame_duration 10 lowdelay.opus
# WebM 컨테이너
ffmpeg -i input.mp4 -c:v copy -c:a libopus -b:a 128k output.webm
# 배치 변환
for f in *.wav; do
ffmpeg -y -i "$f" -c:a libopus -b:a 128k "${f%.wav}.opus"
done
추천 사용 시나리오
- 실시간 음성·화상·게임: Opus 우선
- 음악 스트리밍 (신규): Opus 검토
- 팟캐스트 (파일 크기 중요): Opus 모노 48k
- 레거시 호환 필요: MP3/AAC 병행
다음 단계
- AAC 비교: AAC 오디오 코덱
- MP3 비교: MP3 실전 가이드
- 코덱 비교: AAC vs MP3 vs Opus
참고 자료
- Opus 공식: https://opus-codec.org/
- RFC 6716: https://datatracker.ietf.org/doc/html/rfc6716
- FFmpeg libopus:
ffmpeg -h encoder=libopus - WebRTC: https://webrtc.org/ 한 줄 정리: 실시간 음성·저지연이 필요하면 Opus가 최선이며, 레거시 호환이 필요하면 MP3/AAC를 병행합니다.
자주 묻는 질문 (FAQ)
Q. 네트워크 패킷 손실이 있는 환경에서 Opus 음질을 지키려면 어떻게 설정하나요?
A. Opus는 인밴드 FEC(Forward Error Correction)를 지원하므로, FFmpeg libopus에서 -fec 1과 예상 손실률을 뜻하는 -packet_loss 값을 함께 지정하면(FEC는 SILK·Hybrid 모드에서만 동작하므로 -application voip처럼 음성 모드가 쓰이는 설정이어야 합니다) 손실된 프레임을 일부 복원할 수 있습니다. FEC는 여분 정보를 싣는 만큼 같은 비트레이트에서 원래 음성에 쓸 비트가 줄어들기 때문에, 손실률을 실제 환경보다 과하게 잡지 않는 것이 좋습니다. 손실 대응이 중요하다면 짧은 프레임 길이와 함께 조정해 보는 것도 방법입니다.
같이 보면 좋은 글
- AAC vs MP3 vs Opus 오디오 코덱 비교 | 음질·비트레이트·호환성 가이드
- MP3 오디오 코덱 실전 활용 | LAME·CBR·VBR·FFmpeg 인코딩 가이드
- AAC 오디오 코덱