MP3 오디오 코덱 실전 활용 | LAME·CBR·VBR·FFmpeg 인코딩 가이드
이 글의 핵심
MP3는 오래된 코덱이지만 거의 모든 기기에서 재생된다는 이유로 팟캐스트와 웹 오디오에서 여전히 기본값으로 쓰입니다. MPEG-1 Layer III가 심리음향 모델과 MDCT로 비트를 배분하는 원리를 이해하면 비트레이트를 무작정 올리지 않고도 용량과 음질의 균형을 잡을 수 있고, 재압축할수록 음질이 떨어지는 이유도 알 수 있습니다.
들어가며
MP3는 MPEG-1 Audio Layer III의 통칭으로, 1990년대 이후 디지털 음악을 대중화한 포맷입니다. 기술적으로는 오래된 코덱이지만 거의 모든 기기, OS, 카 오디오가 재생을 지원한다는 이유로 여전히 실무에서 쓰입니다.
새 프로젝트에서 MP3를 고를 이유는 대부분 호환성 하나입니다. 같은 음질을 AAC는 더 낮은 비트레이트로, Opus는 그보다도 낮은 비트레이트로 낼 수 있지만, 오래된 카 오디오, MP3 플레이어, 일부 팟캐스트 앱, 사내 방송 장비처럼 “MP3만 확실히 되는” 재생 환경이 여전히 많습니다. 그래서 MP3 작업의 핵심은 코덱의 한계를 인정하고, 그 안에서 LAME의 모드와 비트레이트를 목적에 맞게 고르는 것입니다.
코덱 개요
역사 및 개발 배경
MP3는 ISO/IEC 11172-3(MPEG-1 Audio)과 13818-3(MPEG-2 Audio)의 Layer III로 정의됩니다. Fraunhofer IIS 등의 연구를 바탕으로 상용화되었고, 1999년 Napster를 거치며 파일 공유와 음악 산업의 형태를 바꿨습니다.
- 1991~1993년: MPEG-1 Audio 표준 확정 및 ISO/IEC 11172-3 발행
- 1995년: Fraunhofer가 파일 확장자로
.mp3를 채택 - 1999년: Napster 출시로 MP3 파일 공유 확산
- 2017년: 주요 특허 만료, Technicolor·Fraunhofer의 라이선스 프로그램 종료
기술적 특징
| 항목 | 설명 |
|---|---|
| 압축 방식 | 지각 코딩 기반 손실 압축, 폴리페이즈 필터뱅크 + MDCT 하이브리드 변환 |
| 샘플레이트 | MPEG-1: 32/44.1/48kHz, MPEG-2 LSF: 16/22.05/24kHz. 실무에서는 44.1·48kHz가 대부분 |
| 비트레이트 | MPEG-1 Layer III 기준 32 |
| 채널 | 모노, 스테레오, Joint Stereo, Dual Channel |
| 메타데이터 | ID3v2 태그가 사실상 표준(커버 아트·챕터 등). 포맷 자체에는 태그 규격이 없음 |
Joint Stereo는 이름 때문에 “음질을 깎는 모드”로 오해받곤 하지만, 실제로는 프레임마다 좌우 채널을 그대로 저장할지 합(Mid)과 차(Side)로 바꿔 저장할지를 고르는 방식입니다. 대부분의 음악은 좌우가 비슷해 차 신호가 작으므로 Mid/Side로 저장하면 같은 비트로 더 정확하게 담을 수 있습니다. LAME의 기본값도 Joint Stereo이며, 특별한 이유 없이 -joint_stereo 0으로 끄면 같은 비트레이트에서 오히려 음질이 떨어지는 경우가 많습니다. 또 MP3가 지원하는 최대 샘플레이트는 48kHz라, 96kHz 원본은 인코딩 전에 반드시 리샘플링됩니다.
주요 모드: CBR·VBR·ABR
| 모드 | 설명 | 장점 | 단점 |
|---|---|---|---|
| CBR | Constant Bitrate (고정 비트레이트) | 대역폭과 파일 크기 예측이 쉬움 | 복잡한 구간에서 비트가 모자랄 수 있음 |
| VBR | Variable Bitrate (가변 비트레이트) | 같은 평균 비트레이트에서 체감 품질이 나은 경우가 많음 | 크기 예측이 어렵고, 헤더 없는 파일은 길이 표시 오류 |
| ABR | Average Bitrate (평균 비트레이트) | 목표 평균에 맞추면서 프레임별로 비트를 조절 | 같은 크기의 VBR보다 품질이 약간 낮을 수 있음 |
압축 원리
그림: MP3 인코더는 PCM을 필터뱅크와 MDCT로 주파수 라인으로 바꾸는 동시에, 입력에 별도 FFT를 돌린 심리음향 모델로 마스킹 임계값을 구해 대역별 양자화 비트를 정한다. 한 프레임은 1152샘플로 44.1kHz에서 약 26ms다.
심리음향 모델(Psychoacoustic Model)
MP3는 인간 청각의 마스킹 효과를 이용합니다. 강한 소리 근처 주파수의 약한 소리는 들리지 않는 주파수 마스킹과, 강한 소리 직전·직후의 약한 소리가 들리지 않는 시간 마스킹이 대표적입니다. 인코더는 심리음향 모델로 대역별 마스킹 임계값을 계산하고, 양자화 잡음이 그 임계값 아래에 머물도록 대역마다 양자화 스텝을 정합니다. 잡음이 숨을 수 있는 대역에는 거칠게, 귀가 민감한 대역(대략 2~5kHz)이나 마스킹이 약한 대역에는 정밀하게 비트를 씁니다.
이 원리 때문에 MP3의 손실은 “소리가 작아지는” 형태가 아니라 특정 조건에서만 드러나는 형태로 나타납니다. 대표적인 것이 프리에코(pre-echo)입니다. 캐스터네츠나 하이햇처럼 소리가 갑자기 시작하는 순간, 변환 블록 안에서 양자화 잡음이 소리가 시작하기 전까지 번져 “스” 하는 잔향이 앞에 붙습니다. MP3는 이를 줄이려고 긴 블록과 짧은 블록을 전환하지만 완전히 막지는 못합니다.
또 비트레이트가 낮으면 LAME은 고역을 저역 통과 필터로 잘라 버리고(128kbps 부근에서 대략 16~17kHz 이상), 남은 비트를 들리는 대역에 집중시킵니다. 스펙트럼 분석기로 MP3를 보면 특정 주파수 위가 칼로 자른 듯 비어 있는 이유가 이것이며, 파일이 “진짜 320kbps에서 인코딩됐는지” 추정할 때도 이 차단 주파수를 봅니다.
하이브리드 필터뱅크와 MDCT
PCM 입력 ─┬─> 폴리페이즈 필터뱅크 (32 서브밴드)
│ ↓
│ MDCT (서브밴드당 18개 → 576개 주파수 라인, 짧은 블록은 3×6)
│ ↓
└─> 심리음향 모델 (FFT로 마스킹 임계값 계산) ─> 양자화·비트 할당
↓
허프만 코딩
↓
MP3 프레임
심리음향 모델은 필터뱅크 출력이 아니라 입력 PCM에 별도 FFT를 돌려 병렬로 계산되고, 그 결과가 양자화 단계에 전달됩니다. 한 프레임은 1152샘플(그래뉼 2개 × 576)이며, 44.1kHz에서 약 26ms에 해당합니다.
비트레이트 할당 전략
CBR은 모든 프레임에 같은 크기를 배정하므로 조용한 구간에서는 비트가 남고 복잡한 구간에서는 모자랍니다. VBR은 프레임마다 목표 품질을 맞추는 데 필요한 만큼만 비트를 써서, 복잡한 구간에 많이, 단순한 구간에 적게 배분합니다.
CBR이라도 MP3는 비트 저장소(bit reservoir)를 써서 프레임 사이에 약간의 비트를 빌려주고 받을 수 있습니다. 조용한 프레임에서 남긴 비트를 바로 뒤의 복잡한 프레임이 쓰는 식이라, CBR의 “모든 프레임이 똑같다”는 말은 프레임 크기 기준으로만 맞습니다. 다만 저장소 크기가 작아(최대 511바이트) 몇 프레임 범위 안에서만 효과가 있으므로, 곡 전체에 걸쳐 복잡도가 크게 달라지는 음악에서는 VBR이 여전히 유리합니다.
실전 인코딩
FFmpeg 설치 확인
# FFmpeg 버전 확인
ffmpeg -version
# libmp3lame 인코더 확인
ffmpeg -encoders | grep -i lame
기본 인코딩 예제
고품질 VBR (권장)
# VBR 품질 2 (평균 약 190 kbps)
ffmpeg -i input.wav -c:a libmp3lame -q:a 2 output.mp3
-q:a | 평균 비트레이트 | 용도 |
|---|---|---|
| 0 | 약 220-260 kbps (평균 245 전후) | 최고 품질 VBR |
| 2 | 약 170-210 kbps (평균 190 전후) | 고품질 (일반 배포) |
| 4 | 약 140-185 kbps (평균 165 전후) | 중간 품질 |
| 6 | 약 100-130 kbps (평균 115 전후) | 낮은 품질 |
표의 비트레이트는 LAME 문서에 실린 대략적인 범위이며, VBR은 이름 그대로 콘텐츠에 따라 결과가 달라집니다. 조용한 피아노 독주는 V2로도 150kbps가 안 나오고, 왜곡된 기타가 가득한 록은 230kbps를 넘기기도 합니다. FFmpeg의 -q:a 숫자는 LAME CLI의 -V 숫자와 같은 의미이며, 숫자가 작을수록 고품질입니다.
마스터 보관용으로 V0이나 320kbps를 쓰는 경우를 종종 보는데, 보관이 목적이라면 손실 코덱 대신 FLAC을 쓰는 것이 맞습니다. 나중에 AAC나 Opus로 다시 인코딩할 일이 생기면 MP3 마스터는 재압축 손실을 피할 수 없기 때문입니다.
CBR 인코딩
# CBR 192 kbps
ffmpeg -i input.wav -c:a libmp3lame -b:a 192k output.mp3
# CBR 320 kbps (MP3 규격상 최대)
ffmpeg -i input.wav -c:a libmp3lame -b:a 320k output.mp3
# CBR 128 kbps (스트리밍)
ffmpeg -i input.wav -c:a libmp3lame -b:a 128k output.mp3
ABR 인코딩
# ABR 190 kbps 평균
ffmpeg -i input.wav -c:a libmp3lame -abr 1 -b:a 190k output.mp3
고급 옵션
샘플레이트 변환
# 48 kHz → 44.1 kHz
ffmpeg -i input.wav -ar 44100 -c:a libmp3lame -q:a 2 output.mp3
모노 변환
# 스테레오 → 모노 (강의, 팟캐스트)
ffmpeg -i input.wav -ac 1 -c:a libmp3lame -q:a 3 output.mp3
메타데이터 추가
# ID3 태그 추가
ffmpeg -i input.wav -c:a libmp3lame -q:a 2 \
-metadata title="노래 제목" \
-metadata artist="아티스트" \
-metadata album="앨범" \
-metadata date="2026" \
output.mp3
커버 아트 추가
# 앨범 커버 임베드 (구형 기기 호환을 위해 ID3v2.3으로 기록)
ffmpeg -i input.wav -i cover.jpg \
-map 0:a -map 1:v \
-c:a libmp3lame -q:a 2 \
-c:v copy \
-id3v2_version 3 \
-metadata:s:v title="Album cover" \
-metadata:s:v comment="Cover (front)" \
output.mp3
FFmpeg의 기본 ID3 버전은 v2.4인데, 일부 카 오디오와 윈도 탐색기 구버전은 v2.4 태그를 제대로 읽지 못합니다. 커버 아트가 안 보인다는 문의가 오면 -id3v2_version 3부터 확인합니다.
배치 처리
Bash 스크립트
#!/bin/bash
# 모든 WAV 파일을 MP3로 변환
for f in *.wav; do
echo "Processing: $f"
ffmpeg -y -i "$f" -c:a libmp3lame -q:a 2 "${f%.wav}.mp3"
done
Python 스크립트
import subprocess
from pathlib import Path
def batch_convert_to_mp3(input_dir, output_dir, quality=2):
"""WAV 파일을 MP3로 일괄 변환"""
Path(output_dir).mkdir(parents=True, exist_ok=True)
for wav_file in Path(input_dir).glob('*.wav'):
output_file = Path(output_dir) / f"{wav_file.stem}.mp3"
cmd = [
'ffmpeg', '-y',
'-i', str(wav_file),
'-c:a', 'libmp3lame',
'-q:a', str(quality),
str(output_file)
]
print(f"Converting: {wav_file.name}")
subprocess.run(cmd, check=True)
batch_convert_to_mp3('input_wavs', 'output_mp3s', quality=2)
다른 코덱과의 비교
코덱 비교표
| 코덱 | 흔히 쓰는 비트레이트 | 같은 비트레이트에서의 음질 | 호환성 | 용도 |
|---|---|---|---|---|
| MP3 | 128-320 kbps | 기준 | 가장 넓음 | 범용 배포 |
| AAC-LC | 96-256 kbps | MP3보다 나음 | 넓음 | 스트리밍, 모바일 |
| Opus | 32-128 kbps | 저비트레이트에서 가장 나음 | 브라우저·앱 중심 | VoIP, 웹 |
| FLAC | 무손실 (대략 원본의 50~70%) | 원본과 동일 | 중간 | 아카이브 |
비트레이트별 음질 경향
아래 표는 공개 청취 테스트에서 반복적으로 보고되는 대략적인 경향이며, 곡·청취자·재생 장비에 따라 달라집니다.
| 비트레이트 | MP3 (LAME) | AAC-LC | Opus | 비고 |
|---|---|---|---|---|
| 96 kbps | 고역 손실이 들림 | 무난 | 좋음 | MP3에는 낮은 비트레이트 |
| 128 kbps | 무난 | 좋음 | 매우 좋음 | 일반 청취에는 충분한 경우가 많음 |
| 192 kbps | 좋음 | 매우 좋음 | 매우 좋음 | 대부분 원본과 구분 어려움 |
| 320 kbps | 매우 좋음 | — | — | MP3 최대값 |
파일 크기 계산
CBR 파일 크기는 비트레이트 × 재생 시간 ÷ 8로 거의 정확하게 예측됩니다. 5분(300초) 곡이라면 다음과 같습니다.
| 설정 | 계산 | 대략적인 크기 |
|---|---|---|
| CBR 128k | 128,000 × 300 ÷ 8 | 약 4.8MB |
| CBR 192k | 192,000 × 300 ÷ 8 | 약 7.2MB |
| CBR 320k | 320,000 × 300 ÷ 8 | 약 12MB |
| VBR -V 2 | 평균 약 190kbps로 가정 | 약 7MB (곡에 따라 달라짐) |
LAME은 단일 스레드로 동작하지만 인코딩 자체가 가벼워 현대 CPU에서는 실시간보다 훨씬 빠르게 끝나므로, 대부분의 작업에서 병목이 되지 않습니다. 대량 변환에서 속도가 문제라면 뒤에서 설명하는 파일 단위 병렬 처리가 가장 효과적입니다.
실무 활용 사례
팟캐스트 배포
팟캐스트는 호환성이 가장 중요하고, 음성 명료도만 유지하면 되므로 용량을 크게 줄일 수 있습니다.
# 모노, 64 kbps CBR (음성용)
ffmpeg -i podcast.wav \
-ac 1 \
-c:a libmp3lame \
-b:a 64k \
-ar 44100 \
output.mp3
1시간 분량이면 64,000 × 3,600 ÷ 8로 약 28.8MB입니다. 팟캐스트 앱 중 일부는 VBR 파일의 탐색(seek) 위치를 부정확하게 계산하므로, 챕터 이동이 중요한 프로그램이라면 CBR이 무난합니다.
다중 품질 배포
대역폭 예측이 중요한 다운로드·스트리밍 서비스는 CBR로 여러 품질을 미리 만들어 둡니다.
# 저품질 (모바일 데이터)
ffmpeg -i master.wav -c:a libmp3lame -b:a 128k low.mp3
# 중품질 (Wi-Fi)
ffmpeg -i master.wav -c:a libmp3lame -b:a 192k medium.mp3
# 고품질
ffmpeg -i master.wav -c:a libmp3lame -b:a 320k high.mp3
import subprocess
from pathlib import Path
def create_multi_quality(input_file, output_dir):
"""다중 품질 MP3 생성"""
qualities = [('low', '128k'), ('medium', '192k'), ('high', '320k')]
Path(output_dir).mkdir(parents=True, exist_ok=True)
stem = Path(input_file).stem
for name, bitrate in qualities:
output_file = Path(output_dir) / f"{stem}_{name}.mp3"
cmd = [
'ffmpeg', '-y',
'-i', input_file,
'-c:a', 'libmp3lame',
'-b:a', bitrate,
str(output_file)
]
print(f"Creating {name} quality: {bitrate}")
subprocess.run(cmd, check=True)
create_multi_quality('master.wav', 'output')
게임 배경음악
# VBR 품질 4 (중간 품질, 작은 크기)
ffmpeg -i bgm.wav \
-c:a libmp3lame \
-q:a 4 \
-ar 44100 \
bgm.mp3
게임 배경음악에서 MP3가 까다로운 이유는 인코더 지연과 패딩 때문입니다. MP3는 1152샘플 단위 프레임으로 인코딩하고 필터뱅크 구조상 앞쪽에 수백 샘플의 지연이 생기므로, 디코딩한 결과의 앞뒤에 원본에 없던 짧은 무음이 붙습니다. 이 상태로 반복 재생하면 루프 지점마다 “뚝” 하고 끊기는 틈이 들립니다. LAME은 이 지연·패딩 길이를 파일 앞쪽의 LAME 태그에 기록해 두고, 이를 읽는 gapless 지원 플레이어는 정확히 잘라 내지만, 게임 엔진이나 브라우저의 오디오 API가 이를 존중하는지는 제각각입니다. 끊김 없는 루프가 필요하면 Ogg Vorbis·Opus처럼 컨테이너가 샘플 단위 위치를 기록하는 포맷을 쓰거나, 엔진 쪽에서 루프 시작·끝 샘플 위치를 직접 지정하는 방식이 안전합니다. 편집용 원본은 WAV로 보관하고 MP3는 최종 배포용으로만 만듭니다.
웹 오디오 플레이어
<audio controls>
<source src="audio.mp3" type="audio/mpeg">
브라우저가 audio 태그를 지원하지 않습니다.
</audio>
네트워크 상태에 따라 품질을 고르는 간단한 예입니다. navigator.connection(Network Information API)은 Chromium 계열 브라우저에서만 제공되므로, 없을 때의 기본값을 반드시 둡니다.
class AdaptiveAudioPlayer {
constructor() {
this.audio = new Audio();
this.qualities = {
low: 'audio_128k.mp3',
medium: 'audio_192k.mp3',
high: 'audio_320k.mp3'
};
}
selectQuality() {
const connection = navigator.connection;
if (!connection) {
return this.qualities.medium;
}
if (connection.saveData) {
return this.qualities.low;
}
switch (connection.effectiveType) {
case '4g':
return this.qualities.high;
case '3g':
return this.qualities.medium;
default:
return this.qualities.low;
}
}
async play() {
this.audio.src = this.selectQuality();
// 자동 재생 정책 때문에 사용자 동작 없이 호출하면 거부될 수 있음
await this.audio.play();
}
}
// 버튼 클릭 같은 사용자 동작 안에서 호출
document.querySelector('#play').addEventListener('click', () => {
new AdaptiveAudioPlayer().play().catch(console.error);
});
최적화
같은 크기에서 음질 높이기: VBR
# CBR 192k (5분 곡 기준 약 7.2MB)
ffmpeg -i input.wav -c:a libmp3lame -b:a 192k cbr.mp3
# VBR -V 2 (평균 약 190k, 곡에 따라 약 7MB 전후)
ffmpeg -i input.wav -c:a libmp3lame -q:a 2 vbr.mp3
두 파일의 평균 비트레이트가 비슷하다면(V2는 대개 190kbps 전후), VBR 쪽이 복잡한 구간에 비트를 몰아줄 수 있어 같은 크기에서 체감 음질이 더 나은 경우가 많습니다. 반대로 VBR이 CBR보다 파일이 커졌다면 콘텐츠가 복잡하다는 뜻이므로, 비교는 항상 비슷한 크기끼리 해야 공정합니다.
음성은 모노로
# 스테레오 → 모노
ffmpeg -i stereo.wav -ac 1 -c:a libmp3lame -q:a 3 mono.mp3
강의나 대화처럼 좌우 정보가 의미 없는 음성은 모노로 바꾸면 같은 품질 설정에서 파일이 상당히 작아집니다. Joint Stereo가 좌우가 같은 신호를 이미 효율적으로 처리하므로 정확히 절반이 되지는 않습니다.
인코딩 속도: 파일 단위 병렬 처리
# -threads를 줘도 libmp3lame 인코딩 자체는 단일 스레드로 동작
ffmpeg -i input.wav -threads 4 -c:a libmp3lame -q:a 2 output.mp3
LAME 인코더는 멀티스레드를 지원하지 않으므로 -threads로는 한 파일의 인코딩이 빨라지지 않습니다. 여러 파일을 변환한다면 파일 단위로 병렬 실행하는 것이 코어를 제대로 쓰는 방법입니다.
#!/bin/bash
# GNU Parallel 사용 (파일명에 공백이 있어도 안전)
parallel -j 4 'ffmpeg -y -i {} -c:a libmp3lame -q:a 2 {.}.mp3' ::: *.wav
메타데이터 정리
# 모든 태그와 커버 아트 제거 (오디오 스트림만 복사)
ffmpeg -i input.mp3 -map 0:a -c:a copy -map_metadata -1 output.mp3
# 기존 태그를 모두 지우고 지정한 태그만 새로 기록
ffmpeg -i input.mp3 -map 0:a -c:a copy \
-map_metadata -1 \
-metadata title="제목" \
-metadata artist="아티스트" \
output.mp3
-map_metadata -1은 태그만 지우고, 커버 아트는 별도의 비디오 스트림으로 남습니다. -map 0:a로 오디오 스트림만 고르면 커버 아트도 함께 빠집니다.
트러블슈팅
VBR 재생 시간 오류
구형 플레이어나 일부 웹 플레이어에서 VBR 파일의 재생 시간이 실제와 다르게 표시되거나, 탐색 위치가 어긋나는 경우가 있습니다.
# 표시되는 길이 확인
ffprobe vbr.mp3
VBR 파일은 첫 프레임에 Xing(또는 VBRI) 헤더를 두고, 여기에 전체 프레임 수와 탐색용 목차를 기록합니다. 이 헤더가 없으면 플레이어는 첫 프레임의 비트레이트로 전체 길이를 추정하므로 틀린 값이 나옵니다. FFmpeg의 MP3 먹서는 기본으로(-write_xing 1) 이 헤더를 쓰지만, 인코딩이 끝난 뒤 파일 앞으로 돌아가 값을 채우는 방식이라 파이프(pipe:1)처럼 되감을 수 없는 출력에서는 헤더가 비게 됩니다.
# 일반 파일로 다시 먹싱해 Xing 헤더 기록 (재인코딩 없음)
ffmpeg -i broken.mp3 -c:a copy fixed.mp3
재압축으로 인한 음질 저하
MP3를 다시 MP3로 인코딩하면 음질이 눈에 띄게 떨어집니다. MP3는 들리지 않는다고 판단한 정보를 버리는 손실 압축이라, 이미 손실된 파일을 다시 인코딩하면 새 손실이 이전 손실 위에 쌓입니다. 비트레이트를 올려서 재인코딩해도 사라진 정보는 돌아오지 않습니다.
# 피해야 할 예: 손실 압축 → 손실 압축
ffmpeg -i input.mp3 -c:a libmp3lame -b:a 128k output.mp3
# 올바른 흐름: WAV/FLAC 원본을 보관하고 필요한 품질로 한 번만 인코딩
ffmpeg -i master.wav -c:a libmp3lame -q:a 2 final.mp3
클리핑
# 디코딩 후 최대 음량 확인
ffmpeg -i input.mp3 -af "volumedetect" -f null -
# max_volume이 0 dB 이상이면 클리핑 위험
MP3에서 클리핑은 원본이 멀쩡해도 생길 수 있다는 점이 함정입니다. 원본 WAV의 피크가 0dBFS에 딱 붙어 있으면, 인코딩 과정에서 고역을 자르고 양자화한 결과를 디코딩할 때 파형이 원본보다 살짝 튀어나와 0dBFS를 넘는 경우가 많습니다. 음압을 최대로 올린 최근 마스터링 음원일수록 이 현상이 잦습니다. 그래서 인코딩 전에 트루 피크를 -1dBTP 정도로 여유를 두고 맞추는 것이 권장되며, loudnorm 필터는 라우드니스와 트루 피크를 함께 맞춰 줍니다.
# 단순 감쇠: 전체 음량을 3dB 낮춤 (원본 상태와 무관하게 일괄 적용)
ffmpeg -i input.wav -af "volume=-3dB" -c:a libmp3lame -q:a 2 output.mp3
# 라우드니스 정규화 (EBU R128)
# loudnorm은 내부적으로 192kHz로 업샘플링하므로 -ar로 출력 샘플레이트를 지정
ffmpeg -i input.wav -af "loudnorm=I=-16:TP=-1.5:LRA=11" \
-ar 44100 -c:a libmp3lame -q:a 2 output.mp3
volume=-3dB는 정규화가 아니라 단순 감쇠라서, 이미 작은 파일에 일괄 적용하면 불필요하게 더 작아집니다. loudnorm을 한 번만 돌리면 동적 모드로 동작해 음량이 미세하게 출렁일 수 있으므로, 품질이 중요하면 첫 번째 패스로 측정값을 얻고 두 번째 패스에 measured_* 값을 넘기는 2패스 방식을 씁니다.
샘플레이트 호환
MP3 헤더에는 샘플레이트가 기록되므로 정상적인 플레이어라면 48kHz 파일도 올바른 속도로 재생합니다. 문제는 일부 구형 하드웨어 디코더나 방송 장비가 44.1kHz만 받는 경우로, 재생이 거부되거나 장비가 잘못 리샘플링해 음질이 떨어집니다. 이런 환경에 배포한다면 인코딩할 때 44.1kHz로 맞춰 둡니다.
# 고품질 리샘플링 (soxr 지원 빌드 필요)
ffmpeg -i input_48k.wav \
-af "aresample=44100:resampler=soxr" \
-c:a libmp3lame -q:a 2 \
output_44k.mp3
VBR 파일 크기를 미리 알아야 할 때
저장 용량이나 다운로드 크기에 상한이 있다면 ABR로 평균을 고정하는 방법이 가장 단순합니다.
# 평균 192 kbps 목표
ffmpeg -i input.wav -c:a libmp3lame -abr 1 -b:a 192k output.mp3
VBR을 유지하고 싶다면 앞부분 일부만 인코딩해 평균 비트레이트를 확인한 뒤 전체 크기를 추정할 수 있습니다. 다만 곡의 앞부분이 전체를 대표하지 않으면 추정이 크게 빗나갑니다.
# 처음 30초만 인코딩해 크기 확인
ffmpeg -i input.wav -t 30 -c:a libmp3lame -q:a 2 test.mp3
ls -lh test.mp3
실행해서 확인한 결과
이 글의 ffmpeg 명령 30개를 macOS(FFmpeg 9.0.2)에서 testsrc2 640x360 영상과 사인파 음성으로 만든 합성 입력(mp4·mkv·webm·wav 등)으로 실행했습니다. 29개가 오류 없이 끝났습니다. aresample=44100:resampler=soxr 예제는 Conversion failed!로 실패했는데, 이 빌드에 libsoxr가 없었기 때문이고 글의 “soxr 지원 빌드 필요”라는 주의와 같은 결과입니다. 음질(청감), 인코더 비교 수치, 플레이어별 호환성은 확인하지 않았습니다.
참고 자료
- LAME 프로젝트: https://lame.sourceforge.io/
- FFmpeg 문서: https://ffmpeg.org/ffmpeg-codecs.html#libmp3lame
- MPEG Audio 표준: ISO/IEC 11172-3