AAC 오디오 코덱: LC-AAC·HE-AAC 압축 원리와 FFmpeg 인코딩 설정

이 글의 핵심

AAC 코덱의 LC-AAC·HE-AAC 프로파일, MPEG-4 컨테이너 연동, FFmpeg 인코딩 옵션까지. 스트리밍·모바일 실무에서 품질과 용량을 맞추는 방법을 정리합니다.

들어가며

AAC(Advanced Audio Coding)는 MP3의 후속으로 설계된 MPEG 계열 손실 압축 코덱으로, 같은 비트레이트에서 MP3보다 나은 음질을 목표로 만들어졌습니다. MP4 컨테이너와 HLS·DASH 같은 적응형 스트리밍에서 기본 오디오 코덱으로 널리 쓰이고, 거의 모든 스마트폰·브라우저·TV가 디코딩을 지원합니다.

실제로 AAC를 쓰려면 어떤 프로파일을 쓸지, 비트레이트를 얼마로 잡을지, FFmpeg에서 어떤 인코더를 쓸지를 함께 정해야 합니다. 이 글은 코덱 구조를 먼저 설명하고, 재현 가능한 FFmpeg 예제와 품질·용량 트레이드오프, 갭리스 재생과 라이선스처럼 운영에서 부딪히는 문제로 이어갑니다.


코덱 개요

역사 및 개발 배경

AAC는 1997년 ISO/IEC 13818-7(MPEG-2 Part 7)로 표준화되었고, 이후 ISO/IEC 14496-3(MPEG-4 Audio)에 포함되어 SBR, PS 같은 도구가 추가되었습니다.

MP3(MPEG-1/2 Layer III)와 비교하면 몇 가지가 개선되었습니다. MP3는 다상 필터뱅크 뒤에 MDCT를 붙인 하이브리드 구조였지만, AAC는 순수 MDCT 필터뱅크를 쓰고 긴 블록의 주파수 분해능을 높였습니다. 순간적인 소리에 대응하는 블록 전환이 더 유연해졌고, 시간 영역 노이즈 분포를 조절하는 TNS(Temporal Noise Shaping), 스테레오 코딩 개선, 더 효율적인 허프만 코드북이 추가되었습니다.

대중화에는 Apple의 역할이 컸습니다. 2003년 iTunes Music Store가 AAC로 음원을 판매하고 iPod이 이를 재생하면서 널리 퍼졌고, 이후 MP4 기반 영상 스트리밍에서 오디오 트랙의 기본 선택이 되었습니다.

기술적 특징

항목설명
압축 방식지각 코딩(perceptual coding) 기반 손실 압축. MDCT 변환, 양자화, 허프만 부호화
샘플레이트8 kHz~96 kHz (프로파일·구현에 따라 다름). 실제로는 44.1/48 kHz가 대부분
채널모노부터 5.1, 7.1 등 멀티채널까지
프레임 크기AAC-LC는 프레임당 1024샘플 (48 kHz에서 약 21ms)
컨테이너MP4(.m4a), ADTS(.aac), 3GP, MPEG-TS 등

주요 프로파일

프로파일기술대략적인 사용 구간 (스테레오)주요 용도
AAC-LC기본 MDCT 코딩96 kbps 이상, 보통 128~256 kbps음악, 팟캐스트, VOD, 범용
HE-AAC v1LC + SBR(고역 복원)약 48~80 kbps모바일 스트리밍, 인터넷 라디오
HE-AAC v2v1 + PS(파라메트릭 스테레오)약 48 kbps 이하초저대역폭, 음성 중심

비트레이트 구간은 인코더 구현과 콘텐츠에 따라 달라지는 대략적인 기준입니다. 공통된 원칙은 비트레이트가 충분하면 LC가 가장 좋고, 비트레이트가 낮아질수록 SBR과 PS가 이득을 준다는 것입니다.

AAC-LC (Low Complexity)

가장 널리 지원되는 기본 프로파일입니다. 스마트폰, PC, 자동차, 스마트 TV 등 AAC를 재생하는 거의 모든 기기가 LC를 지원하고, 디코딩 연산량도 적습니다. 다만 비트레이트를 64 kbps 아래로 내리면 고주파를 표현할 비트가 부족해져서, 인코더가 대역폭을 크게 잘라 내거나 고역에서 거친 아티팩트가 생깁니다. 음악 스트리밍, 팟캐스트, 영상의 오디오 트랙처럼 비트레이트를 충분히 줄 수 있는 대부분의 용도에 맞습니다.

HE-AAC v1 (High-Efficiency AAC)

HE-AAC v1은 LC 코어에 SBR(Spectral Band Replication)을 더한 프로파일입니다. 코어 LC 인코더는 절반의 샘플레이트로 동작해 저역만 부호화하고, 고역은 “저역의 스펙트럼을 복사해 올린 뒤 포락선을 맞추는 데 필요한 파라미터”만 적은 비트로 보냅니다. 디코더는 저역을 복원한 뒤 이 파라미터로 고역을 재구성합니다. 코어가 담당하는 대역과 SBR 대역의 경계는 비트레이트에 따라 달라지며 보통 수 kHz~10 kHz 근처입니다.

사람의 청각이 고역의 세부 구조보다는 대략적인 에너지 분포에 민감하다는 점을 이용한 방식이라, 저비트레이트에서는 LC보다 훨씬 자연스럽게 들립니다. 반면 고역이 원본의 파형이 아니라 재구성된 신호이므로, 심벌·하이햇처럼 고역이 두드러진 음악에서는 금속성 아티팩트가 들릴 수 있고, 비트레이트를 충분히 줄 수 있는 구간에서는 LC보다 오히려 불리합니다. 디코딩 연산량도 LC보다 늘어납니다.

HE-AAC v2 (HE-AAC + PS)

HE-AAC v2는 v1에 Parametric Stereo(PS)를 더합니다. 스테레오 신호를 모노 다운믹스 하나로 부호화하고, 좌우 채널 간 세기 차이·위상 차이·상관도 같은 파라미터만 따로 보내 디코더에서 스테레오 이미지를 재구성합니다. 아주 낮은 비트레이트에서도 스테레오 느낌을 유지할 수 있지만, 실제 좌우 신호를 보내는 것이 아니므로 악기가 넓게 배치된 음악에서는 스테레오 이미지가 뭉개지거나 공간감이 부자연스러워집니다. 음성 위주의 토크 콘텐츠나 대역폭이 극히 제한된 환경에 맞습니다.

flowchart TD
    START[비트레이트 결정]
    START --> Q1{96 kbps 이상?}
    Q1 -->|Yes| LC[AAC-LC]
    Q1 -->|No| Q2{48~80 kbps?}
    Q2 -->|Yes| HE1[HE-AAC v1]
    Q2 -->|No| Q3{실시간 통화나 브라우저 WebRTC?}
    Q3 -->|Yes| OPUS[Opus]
    Q3 -->|No| HE2[HE-AAC v2]

압축 원리

심리음향 모델(Psychoacoustic Model)

AAC의 핵심은 사람이 들을 수 없거나 알아채기 어려운 성분에 비트를 쓰지 않는다는 원칙입니다. 이를 위해 인코더는 청각의 몇 가지 특성을 이용합니다.

동시 마스킹(simultaneous masking)은 큰 소리가 있을 때 주파수가 가까운 작은 소리가 들리지 않는 현상입니다. 시간적 마스킹(temporal masking)은 큰 소리의 앞뒤로 작은 소리가 묻히는 현상으로, 큰 소리 직후의 후행 마스킹은 수십백여 ms까지 이어지고 직전의 선행 마스킹은 수 ms 정도로 짧습니다. 또한 청각은 대략 25 kHz 대역에 가장 민감하고 저역과 고역으로 갈수록 둔감해집니다.

인코더는 이런 특성으로 대역별 마스킹 임계값을 계산하고, 양자화 잡음이 이 임계값 아래에 머무르도록 대역마다 양자화 정밀도를 다르게 정합니다. 데이터를 일률적으로 줄이는 것이 아니라, 들리지 않을 가능성이 높은 성분부터 거칠게 표현하는 전략입니다.

MDCT(Modified Discrete Cosine Transform)

AAC는 MDCT로 시간 영역 신호를 주파수 계수로 바꿉니다. MDCT는 블록을 50%씩 겹쳐 변환하고, 겹친 부분에서 생기는 시간 영역 앨리어싱이 인접 블록과 더해질 때 상쇄되도록(TDAC) 설계되어 있습니다. 덕분에 블록 경계에서 불연속이 생기지 않으면서도, 겹침이 있는데 계수 수는 늘어나지 않습니다.

블록 길이는 신호에 따라 바뀝니다. 정상 상태의 신호에는 긴 블록(1024 계수)을 써서 주파수 분해능을 높이고, 타악기의 어택처럼 순간적으로 에너지가 치솟는 구간에는 짧은 블록(128 계수 × 8)으로 바꿔 시간 분해능을 높입니다. 긴 블록을 계속 쓰면 양자화 잡음이 블록 전체로 퍼져서, 어택 직전에 잡음이 먼저 들리는 프리에코(pre-echo)가 생기기 때문입니다. 변환된 계수는 청각의 임계 대역에 맞춘 스케일 팩터 밴드로 묶여 함께 양자화됩니다.

비트 할당

인코더는 심리음향 분석으로 얻은 마스킹 임계값을 기준으로 스케일 팩터 밴드별 양자화 단계를 정하고, 목표 비트레이트를 맞추면서 잡음이 임계값을 넘지 않도록 반복 조정합니다. 프레임마다 필요한 비트 수가 다르므로, 쉬운 프레임에서 남은 비트를 비트 리저버(bit reservoir)에 모아 두었다가 어려운 프레임에 씁니다.

몇 가지 보조 도구도 함께 쓰입니다. TNS는 주파수 계수에 예측 필터를 적용해 양자화 잡음의 시간 분포를 신호의 포락선에 맞춰 줍니다. 짧은 블록만으로 부족한 순간음 구간에서 프리에코를 줄이는 역할입니다. MPEG-4의 PNS(Perceptual Noise Substitution)는 잡음처럼 들리는 대역을 실제 계수 대신 “이 대역은 이 세기의 잡음”이라는 정보로 대체합니다. 비트레이트가 낮을 때는 인코더가 고역을 아예 잘라 내 대역폭을 제한하기도 합니다.

처리 흐름

아래는 HE-AAC를 포함한 인코딩 흐름입니다. LC만 쓸 때는 SBR/PS 단계 없이 PCM이 바로 코어 인코더로 들어갑니다.

flowchart TB
    PCM["PCM 입력\n44.1/48 kHz"]
    subgraph HE["HE-AAC 전용"]
        QMF["QMF 분석"]
        SBR["SBR 파라미터 추출"]
        PS["PS 파라미터 추출 (v2)"]
        DOWN["절반 샘플레이트로 다운샘플"]
    end
    subgraph CORE["AAC-LC 코어"]
        PSY["심리음향 분석\n마스킹 임계값"]
        MDCT["MDCT\n블록 전환"]
        TNS["TNS"]
        QUANT["양자화\n비트 할당"]
        HUFF["허프만 부호화"]
    end
    BS["비트스트림\nADTS 또는 MP4"]

    PCM --> QMF
    QMF --> SBR
    QMF --> PS
    PCM --> DOWN
    DOWN --> PSY
    DOWN --> MDCT
    MDCT --> TNS
    TNS --> QUANT
    PSY --> QUANT
    QUANT --> HUFF
    HUFF --> BS
    SBR --> BS
    PS --> BS

실전 인코딩

FFmpeg 인코더 선택

FFmpeg에서 실제로 고를 수 있는 AAC 인코더는 세 가지입니다.

인코더특징사용 시기
aac네이티브 인코더, 모든 빌드에 기본 포함. HE-AAC 미지원96~128 kbps 이상 LC, 배포 가능한 바이너리가 필요할 때
libfdk_aacFraunhofer 인코더. LC, HE-AAC v1/v2, LD/ELD 지원. 저비트레이트에서 특히 우수96 kbps 이하, HE-AAC가 필요할 때
aac_atmacOS/iOS AudioToolbox(Apple 인코더) 래퍼. LC, HE 지원macOS에서 인코딩할 때

128 kbps 이상 스테레오 LC라면 네이티브 aac로도 대부분의 청취자가 원본과 구분하기 어렵고, 배포·라이선스 제약도 없어서 기본값으로 두기 좋습니다. 비트레이트를 96 kbps 아래로 내려야 하거나 HE-AAC가 필요하면 libfdk_aac(macOS라면 aac_at)가 낫습니다. 네이티브 인코더는 저비트레이트에서 약점이 뚜렷하기 때문입니다.

libfdk_aac는 라이선스가 GPL과 호환되지 않아서, 이를 포함한 FFmpeg는 --enable-nonfree로만 빌드되고 바이너리를 재배포할 수 없습니다. 그래서 배포판 패키지나 공개 정적 빌드에는 빠져 있고, 직접 빌드해 내부에서만 써야 합니다. 도커 이미지에 넣어 고객에게 배포하는 제품이라면 처음부터 네이티브 aac나 OS 인코더를 전제로 설계하는 편이 안전합니다.

# 사용 가능한 AAC 인코더 확인
ffmpeg -hide_banner -encoders | grep aac

기본 인코딩 예제

AAC-LC 128 kbps 스테레오

ffmpeg -i input.wav \
  -c:a aac \
  -b:a 128k \
  -ar 48000 \
  -ac 2 \
  output.m4a

-b:a는 목표 평균 비트레이트이고, -ar 48000은 영상과 합칠 때 흔히 쓰는 48 kHz로 맞추며, -ac 2는 스테레오 출력을 지정합니다. 네이티브 인코더의 알고리즘 옵션 -aac_coder는 기본값이 이미 품질 위주의 twoloop이라 따로 지정할 필요가 없습니다. fast로 바꾸면 빨라지는 대신 품질이 떨어집니다.

네이티브 aac의 VBR(-q:a)은 피하기

네이티브 aac 인코더도 -q:a를 받지만 FFmpeg 문서에서 실험적 기능으로 분류되어 있고, 값과 실제 비트레이트의 관계도 일정하지 않습니다. 범위는 대략 0.1~2이며 값이 클수록 고품질입니다. MP3(LAME)의 “-q:a 0이 최고 품질” 관례와 방향이 반대라 헷갈리기 쉽습니다. 품질을 예측 가능하게 관리하려면 네이티브 인코더는 -b:a로 비트레이트를 지정하고, VBR이 필요하면 libfdk_aac의 -vbr을 쓰는 편이 낫습니다.

libfdk_aac VBR

ffmpeg -i input.wav \
  -c:a libfdk_aac \
  -profile:a aac_low \
  -vbr 4 \
  output.m4a

FFmpeg 위키가 안내하는 -vbr 값별 비트레이트는 채널당 대략값입니다.

-vbr채널당 비트레이트스테레오 환산
120~32 kbps40~64 kbps
232~40 kbps64~80 kbps
348~56 kbps96~112 kbps
464~72 kbps128~144 kbps
596~112 kbps192~224 kbps

채널당 값이라는 점을 놓치면 스테레오 -vbr 4를 64 kbps짜리로 착각해 용량 계산이 두 배 가까이 틀어집니다. HE-AAC는 -b:a로 비트레이트를 직접 지정하는 편이 단순합니다.

HE-AAC v1 / v2

# HE-AAC v1, 64 kbps
ffmpeg -i input.wav -c:a libfdk_aac -profile:a aac_he -b:a 64k output_he.m4a

# HE-AAC v2, 32 kbps (스테레오 입력 필요)
ffmpeg -i input.wav -c:a libfdk_aac -profile:a aac_he_v2 -b:a 32k -ac 2 output_hev2.m4a

네이티브 aac 인코더는 HE-AAC를 지원하지 않으므로 libfdk_aac나 aac_at이 필요합니다. PS는 스테레오 정보를 파라미터로 바꾸는 도구이므로 HE-AAC v2에는 스테레오 입력을 넣어야 합니다.

HLS 세그먼트로 출력

ffmpeg -i input.wav \
  -c:a aac -b:a 128k -ar 48000 -ac 2 \
  -f hls -hls_time 6 -hls_playlist_type vod \
  -hls_segment_type fmp4 \
  audio.m3u8

HLS 오디오는 MPEG-TS, fMP4, 또는 ID3 타임스탬프를 붙인 ADTS(packed audio) 세그먼트로 전달할 수 있습니다. 단순히 -f adts로 만든 .aac 파일 하나는 HLS 세그먼트가 아니므로, 위처럼 hls 먹서로 플레이리스트와 세그먼트를 함께 만들어야 합니다. ADTS는 프레임마다 헤더가 있어 중간부터 읽기 쉽지만 전체 길이나 탐색 인덱스가 없고, MP4는 moov 박스에 인덱스가 있어 다운로드 재생과 탐색에 유리합니다.

영상의 오디오만 재인코딩

ffmpeg -i input.mp4 \
  -c:v copy \
  -c:a aac -b:a 192k -ar 48000 \
  output.mp4

-c:v copy는 비디오 스트림을 재인코딩하지 않고 그대로 복사하므로 빠르고 화질 손실이 없습니다.

샘플레이트 선택

음악 원본이 44.1 kHz(CD)라면 그대로 유지하고, 영상과 합칠 오디오는 영상 제작 표준인 48 kHz로 맞추는 것이 일반적입니다. 48 kHz가 MP4 영상에 필수는 아니지만, 편집 도구와 방송 규격이 48 kHz를 기준으로 하므로 맞춰 두면 중간에 리샘플링할 일이 줄어듭니다. 32 kHz나 22.05 kHz는 대역폭이 극히 제한된 음성 용도에만 의미가 있고, 고역이 잘려 음악에는 맞지 않습니다. HE-AAC에서는 인코더가 코어를 절반 샘플레이트로 돌리므로 입력은 44.1/48 kHz 그대로 두면 됩니다.

비트레이트와 파일 크기

파일 크기는 비트레이트 × 재생 시간 ÷ 8로 계산할 수 있습니다. 3분(180초) 스테레오 음악 기준입니다.

비트레이트파일 크기
256 kbps약 5.8 MB
192 kbps약 4.3 MB
128 kbps약 2.9 MB
96 kbps약 2.2 MB
64 kbps (HE-AAC v1)약 1.4 MB

1시간짜리 팟캐스트라면 64 kbps 모노가 약 29 MB, 96 kbps 스테레오가 약 43 MB, 128 kbps 스테레오가 약 58 MB입니다.

공개된 청취 테스트들이 공통으로 보여 주는 경향은 다음과 같습니다. 128 kbps 이상 스테레오에서는 좋은 LC 인코더와 원본을 구분하기 어려운 청취자가 대부분이고, 64 kbps 전후에서는 HE-AAC가 LC보다 확실히 낫고, 같은 비트레이트라면 MP3보다 AAC가 앞섭니다. 아주 낮은 비트레이트에서는 Opus가 AAC보다 좋은 결과를 내는 경우가 많습니다. 다만 결과는 콘텐츠와 인코더 버전에 따라 크게 달라지므로, 서비스의 기준 비트레이트는 실제 콘텐츠로 직접 정하는 것이 좋습니다.

# 같은 소스를 여러 비트레이트로 인코딩해 블라인드 비교
ffmpeg -i source.wav -c:a aac -b:a 128k test_128.m4a
ffmpeg -i source.wav -c:a aac -b:a 192k test_192.m4a
ffmpeg -i source.wav -c:a aac -b:a 256k test_256.m4a

비교는 ABX 방식(어느 쪽인지 모르는 상태에서 원본과 같은 것을 고르는 방식)으로 하고, 체계적인 평가가 필요하면 ITU-R BS.1534(MUSHRA) 절차를 참고합니다. 결국 대상 사용자가 쓰는 기기와 이어폰으로 직접 들어 봐야 판단할 수 있습니다.

디코딩과 인코딩 부하

iOS와 Android는 OS 미디어 프레임워크(AVFoundation, MediaCodec)를 통해 최적화된 AAC 디코더나 오디오 DSP를 씁니다. 화면을 끈 백그라운드 재생에서 저전력 오디오 경로를 탈 수 있다는 점이 모바일에서 AAC의 실질적인 장점이며, 앱이 자체 소프트웨어 디코더를 번들하면 이 이점을 잃을 수 있습니다. PC에서는 AAC 디코딩 부하 자체가 작아서 소프트웨어 디코딩으로 충분합니다.

오디오 인코딩은 영상 인코딩에 비하면 부담이 매우 작아서, 어느 인코더든 일반 CPU에서 실시간보다 훨씬 빠르게 처리됩니다. 대량 배치에서 병목은 대개 원본 디코딩이나 디스크 I/O이므로, 인코더는 속도보다 품질과 라이선스로 고르면 됩니다. AAC 인코딩용 하드웨어 가속은 일반적이지 않습니다.


실무 활용 사례

스트리밍 서비스

주요 서비스가 공개한 바로는 다음과 같습니다. Apple Music은 기본 스트리밍에 AAC 256 kbps, 무손실에 ALAC를 씁니다. YouTube는 MP4 계열 재생에서 AAC-LC(약 128 kbps), WebM 계열에서 Opus를 쓰며 같은 영상에 두 트랙이 모두 있습니다. Netflix는 스테레오에 AAC 계열, 5.1 이상 서라운드에 주로 Dolby Digital Plus(E-AC-3)를 쓰고, Android에서 음량 제어를 위해 xHE-AAC를 도입했다고 밝힌 바 있습니다. Spotify는 앱에서 주로 Ogg Vorbis, 웹 플레이어에서 AAC를 씁니다. 각 서비스의 구성은 바뀔 수 있으므로 최신 정보는 공식 자료로 확인해야 합니다.

적응형 스트리밍에서는 여러 비트레이트 버전을 미리 만들어 두고 플레이어가 네트워크 상황에 따라 고르게 합니다.

ffmpeg -i source.wav -c:a aac -b:a 256k -ar 48000 audio_256k.m4a
ffmpeg -i source.wav -c:a aac -b:a 192k -ar 48000 audio_192k.m4a
ffmpeg -i source.wav -c:a aac -b:a 128k -ar 48000 audio_128k.m4a
ffmpeg -i source.wav -c:a libfdk_aac -profile:a aac_he -b:a 64k -ar 48000 audio_64k.m4a

같은 오디오 그룹 안에서 LC와 HE-AAC 변형을 오가면 일부 플레이어에서 전환 지점에 잡음이 들릴 수 있으므로, 사다리를 구성할 때는 대상 플레이어에서 전환을 직접 확인하는 것이 좋습니다.

팟캐스트

음성이 중심이면 낮은 비트레이트로도 충분합니다.

# 음성 전용 (모노)
ffmpeg -i podcast.wav -c:a aac -b:a 64k -ac 1 podcast_mono.m4a

# 음성 + 인트로/아웃트로 음악 (스테레오)
ffmpeg -i podcast.wav -c:a aac -b:a 96k -ac 2 podcast_stereo.m4a

VoIP 및 WebRTC

실시간 음성 통신에서는 AAC보다 Opus가 적합합니다. AAC-LC는 프레임이 1024샘플(48 kHz에서 약 21ms)이고 MDCT 겹침과 인코더 룩어헤드가 더해져 알고리즘 지연이 크며, HE-AAC는 SBR 때문에 더 큽니다. AAC-LD/ELD는 통화용 저지연 변형으로 Apple FaceTime이 AAC-ELD를 쓰지만, 브라우저 WebRTC에서는 쓸 수 없습니다. Opus는 프레임 길이를 2.5~60ms로 바꿀 수 있고, 기본 20ms 프레임에서 알고리즘 지연이 약 26.5ms이며 WebRTC 필수 코덱입니다. AAC는 녹음된 미디어 파일과 적응형 스트리밍 쪽에 맞는 코덱입니다.

브라우저 지원

<audio controls>
  <source src="audio.m4a" type="audio/mp4">
  <source src="audio.mp3" type="audio/mpeg">
  브라우저가 오디오를 지원하지 않습니다.
</audio>
브라우저AAC in MP4ADTS(.aac)
Chrome/Edge지원재생은 되나 길이·탐색 부정확
Firefox지원 (OS 디코더 사용)재생은 되나 길이·탐색 부정확
Safari지원지원

웹에서는 MP4 컨테이너(.m4a)를 쓰는 것이 좋습니다. ADTS는 인덱스가 없어 길이 표시와 탐색이 불안정하고, 일부 리눅스 배포판의 Chromium·Firefox는 특허 코덱을 빼고 빌드되거나 OS 코덱 패키지가 없어 AAC 자체가 재생되지 않을 수 있습니다. MSE(Media Source Extensions) 기반 플레이어에서는 fMP4(fragmented MP4)에 담은 AAC가 널리 지원되며, HLS·DASH 웹 플레이어가 이 방식을 씁니다.

xHE-AAC(USAC)

xHE-AAC는 USAC(Unified Speech and Audio Coding) 기반 프로파일로, 음성과 음악을 모두 저비트레이트에서 다루도록 설계되었습니다. 매우 낮은 비트레이트부터 고음질 구간까지 하나의 코덱으로 적응형 스트리밍할 수 있고, MPEG-D DRC(음량·다이내믹 레인지 제어)를 필수로 지원합니다. Android 9 이상과 iOS 13/macOS 10.15 이상이 디코딩을 지원합니다. 다만 오픈소스 인코더 선택지가 거의 없고 브라우저·구형 기기 지원이 고르지 않아서, 일반 서비스라면 LC/HE-AAC 사다리를 유지하고 xHE-AAC는 대상 기기가 확실한 앱에서 검토하는 정도가 현실적입니다.


처리 자동화와 최적화

손실 코덱을 두 번 거치지 않기

# 나쁜 예: AAC를 디코딩해 다시 AAC로 인코딩 (세대 손실)
ffmpeg -i input_44100.wav -ar 48000 -c:a aac -b:a 128k temp.m4a
ffmpeg -i temp.m4a -ar 44100 output.m4a

# 좋은 예: 원본에서 한 번만 인코딩
ffmpeg -i input_44100.wav -c:a aac -b:a 128k output.m4a

위 나쁜 예에서 품질을 떨어뜨리는 주된 원인은 리샘플링보다 두 번째 명령이 AAC를 다시 디코딩해 재인코딩한다는 점입니다. 손실 코덱을 거칠 때마다 양자화 잡음이 누적되므로, 형식을 바꿔야 할 때는 항상 무손실 원본에서 다시 인코딩합니다. 컨테이너만 바꿀 때는 -c:a copy로 재인코딩 없이 옮깁니다.

무음 구간 제거

강의나 인터뷰처럼 중간에 긴 무음이 있는 콘텐츠는 무음을 잘라 파일 크기와 재생 시간을 줄일 수 있습니다.

# 파일 중간의 1초 이상 무음 구간을 모두 제거
ffmpeg -i input.wav \
  -af "silenceremove=stop_periods=-1:stop_duration=1:stop_threshold=-50dB" \
  -c:a aac -b:a 96k \
  output.m4a

start_periods만 지정하면 파일 앞부분의 무음만 잘리고, 중간 무음까지 지우려면 위처럼 stop_periods=-1을 씁니다. 임계값을 너무 높게 잡으면 조용한 말소리까지 잘리므로 콘텐츠에 맞게 조정해야 합니다.

병렬 배치 인코딩

# GNU parallel로 4개씩 동시에 처리
find ./wav -name '*.wav' -print0 | \
  parallel -0 -j 4 \
  ffmpeg -nostdin -y -i {} -c:a aac -b:a 160k {.}.m4a

-nostdin을 빼면 여러 ffmpeg 프로세스가 표준 입력을 두고 경쟁해 멈추거나 입력이 섞일 수 있습니다. -j는 CPU 코어 수에 맞게 조정합니다.

#!/bin/bash
# batch_encode.sh
INPUT_DIR="./source"
OUTPUT_DIR="./encoded"
mkdir -p "$OUTPUT_DIR"

for file in "$INPUT_DIR"/*.wav; do
  filename=$(basename "$file" .wav)
  echo "Encoding: $filename"
  ffmpeg -nostdin -y -i "$file" \
    -c:a aac -b:a 128k -ar 48000 -ac 2 \
    "$OUTPUT_DIR/${filename}.m4a"

  # 검증용 스펙트로그램 (고역 차단 주파수를 눈으로 확인)
  ffmpeg -nostdin -y -i "$OUTPUT_DIR/${filename}.m4a" \
    -lavfi showspectrumpic=s=1920x1080 \
    "$OUTPUT_DIR/${filename}_spectrum.png"
done

CI에서 인코딩한다면 다음처럼 구성할 수 있습니다.

# .github/workflows/encode-audio.yml
name: Encode Audio Files
on:
  push:
    paths:
      - 'audio/source/**/*.wav'
jobs:
  encode:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install FFmpeg
        run: sudo apt-get update && sudo apt-get install -y ffmpeg

      - name: Encode AAC
        run: |
          for file in audio/source/*.wav; do
            ffmpeg -nostdin -y -i "$file" -c:a aac -b:a 192k "${file%.wav}.m4a"
          done

      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: encoded-audio
          path: audio/source/*.m4a

입력 파일의 체크섬과 출력 스펙트로그램을 함께 보관해 두면, FFmpeg 버전을 올렸을 때 인코딩 결과가 달라졌는지 비교하기 쉽습니다.


파일 형식과 재생 환경 자주 묻는 질문

ADTS 원시 스트림(.aac)과 MP4 컨테이너(.m4a)의 구조 비교

그림: .aac는 프레임마다 헤더가 붙은 연속 스트림이고, .m4a는 길이·탐색 표·태그를 담은 moov와 프레임 데이터 mdat로 구성된다. 담긴 AAC 프레임은 같다.

.aac, .m4a, .mp4는 무엇이 다른가

세 확장자 모두 안에 든 오디오는 AAC일 수 있고, 차이는 포장 방식(컨테이너)에 있습니다.

확장자구조특징
.aacADTS 프레임을 이어 붙인 원시 스트림프레임마다 헤더가 있어 헤더 없이 중간부터 재생할 수 있지만, 전체 길이·탐색 정보와 태그가 없어 길이 표시가 부정확할 수 있음
.m4aMP4 컨테이너에 오디오만 담음길이·탐색 정보와 메타데이터(제목, 앨범 아트)를 저장. 음악 보관과 배포에 일반적
.mp4MP4 컨테이너에 영상과 오디오영상 파일의 오디오 트랙으로 AAC가 가장 흔함

이미 ADTS .aac 파일을 가지고 있다면 재인코딩 없이 컨테이너만 바꿀 수 있어서 음질 손실이 없습니다.

ffmpeg -i input.aac -c:a copy output.m4a

AAC 재생에 별도 코덱을 설치해야 하나

최신 Windows, macOS, Android, iOS와 주요 브라우저는 AAC 디코더를 기본으로 갖고 있어서 따로 설치할 필요가 없습니다. 재생이 안 되는 경우는 대개 코덱이 없어서가 아니라, 프로파일(예: HE-AAC v2나 xHE-AAC를 지원하지 않는 오래된 플레이어)이나 컨테이너가 맞지 않아서입니다. 확인은 ffprobe로 프로파일과 컨테이너를 보는 것이 빠릅니다.

ffprobe -hide_banner input.m4a

출력의 Audio: aac (LC) 또는 (HE-AAC) 부분이 프로파일이고, 윗줄의 Input #0, mov,mp4,m4a...가 컨테이너입니다.

MP3를 AAC로 바꾸면 음질이 좋아지나

좋아지지 않습니다. 손실 압축된 파일을 다시 손실 압축하면 이미 버려진 정보는 돌아오지 않고, 오히려 한 번 더 열화합니다. 용량을 줄이려는 목적이 아니라면 원본(WAV, FLAC)에서 바로 AAC로 인코딩하는 것이 맞습니다. 이 주제는 앞의 “손실 코덱을 두 번 거치지 않기”에서 더 자세히 다룹니다.


흔한 문제와 해결

HE-AAC가 재생되지 않음

HE-AAC 디코더를 지원하지 않는 오래된 기기나 일부 하드웨어 플레이어에서는 소리가 나지 않거나, SBR을 무시하고 코어 LC만 재생해 절반 샘플레이트의 먹먹한 소리가 납니다. 이런 기기가 대상에 있다면 LC 버전을 함께 제공합니다.

ffmpeg -i input.wav -c:a aac -b:a 128k fallback_lc.m4a
ffmpeg -i input.wav -c:a libfdk_aac -profile:a aac_he -b:a 64k modern_he.m4a
<audio controls>
  <source src="audio_he.m4a" type='audio/mp4; codecs="mp4a.40.5"'>
  <source src="audio_lc.m4a" type='audio/mp4; codecs="mp4a.40.2"'>
</audio>

type을 audio/mp4로만 적으면 브라우저는 두 소스를 구분할 수 없어 첫 번째를 그대로 고릅니다. codecs 파라미터(mp4a.40.2=LC, mp4a.40.5=HE-AAC v1, mp4a.40.29=HE-AAC v2)를 붙여야 해당 프로파일을 재생할 수 있는지로 판단합니다. HLS 마스터 플레이리스트의 CODECS 속성도 같은 문자열을 쓰는데, 실제 스트림과 다르게 적으면 일부 플레이어가 변형을 건너뛰므로 인코딩 설정과 함께 관리해야 합니다.

ADTS 파일의 길이·탐색 문제

.aac 파일이 일부 환경에서 재생되지 않거나, 재생은 되지만 전체 길이가 틀리게 표시되고 탐색이 엉뚱한 위치로 튀는 경우가 있습니다. ADTS는 프레임마다 헤더만 있고 전체 길이나 탐색 인덱스가 없는 원시 스트림이라 플레이어가 길이를 추정해야 하기 때문입니다. 서버가 올바른 MIME 타입(audio/aac)을 보내지 않는 경우도 흔합니다. 재인코딩 없이 MP4로 옮기면 해결됩니다.

ffmpeg -i input.aac -c:a copy -movflags +faststart output.m4a

-movflags +faststart는 인덱스가 담긴 moov 박스를 파일 앞으로 옮겨, 전체를 내려받기 전에 재생을 시작할 수 있게 합니다.

영상과 오디오 싱크가 맞지 않음

ffmpeg -i video.mp4 -i audio.wav \
  -map 0:v -map 1:a \
  -c:v copy \
  -c:a aac -b:a 192k -ar 48000 \
  -af aresample=async=1 \
  output.mp4

aresample=async=1은 오디오 타임스탬프의 틈이나 겹침을 샘플을 채우거나 버려서 보정합니다(예전 -async 1 옵션은 deprecated). 싱크가 점점 어긋난다면 녹음 장비의 실제 샘플레이트가 명목값과 미세하게 다른 문제일 가능성이 크고, 처음부터 일정하게 어긋나 있다면 아래 인코더 지연(priming) 정보가 사라진 경우일 가능성이 큽니다.

소리가 깨지거나 고역이 거침

원본이 0 dBFS 가까이 꽉 차 있으면, 인코딩 후 디코딩된 파형의 피크가 원본보다 높아져(샘플 사이 피크, true peak) 재생 시 클리핑이 생길 수 있습니다. 손실 코덱은 파형을 정확히 보존하지 않기 때문입니다. 인코딩 전에 1~3 dB 정도 헤드룸을 확보하면 피할 수 있습니다.

ffmpeg -i input.wav -af "volume=-3dB" -c:a aac -b:a 192k output.m4a
# 또는 음량 정규화와 true peak 제한을 함께
ffmpeg -i input.wav -af "loudnorm=I=-16:TP=-1.5:LRA=11" -c:a aac -b:a 192k output.m4a

하이햇이나 심벌이 쉭쉭거리는 소리로 뭉개진다면 대개 비트레이트가 부족해 고역 양자화가 거칠어진 경우입니다. LC라면 비트레이트를 올리고, 네이티브 aac로 96 kbps 이하를 쓰고 있었다면 libfdk_aac로 바꾸는 것이 효과적입니다.

스테레오 이미지 손실

AAC 인코더는 Mid/Side 스테레오 코딩을 대역·프레임 단위로 자동 선택하므로 따로 켜고 끌 옵션이 있는 것은 아닙니다. 저비트레이트에서 좌우 분리감이 무너지는 것은 비트가 부족해 인코더가 사이드 채널을 희생하기 때문이라, 비트레이트를 올리거나 libfdk_aac로 바꾸는 것이 실질적인 해결책입니다. HE-AAC v2는 스테레오를 모노와 파라미터로 표현하는 방식이므로 음악의 스테레오 이미지가 약해지는 것이 정상이며, 음성 위주 콘텐츠에만 쓰는 것이 맞습니다.

인코더 지연(priming)과 갭리스 재생

MDCT는 블록을 겹쳐 처리하므로 디코더가 첫 블록을 온전히 복원하려면 그 앞의 데이터가 필요합니다. 그래서 인코더는 파일 앞에 priming 샘플을 붙입니다. LC에서는 보통 1024샘플이고 Apple 인코더는 2112샘플을 쓰며, HE-AAC는 SBR 처리 때문에 더 깁니다. MP4 컨테이너는 편집 리스트(edit list)나 iTunSMPB 메타데이터로 “앞의 N샘플은 버려라”를 기록해 두고, 이를 해석하는 플레이어는 정확한 위치부터 재생합니다.

문제는 이 정보가 사라지는 경로입니다. ADTS(.aac)에는 이 정보를 담을 곳이 없고, 일부 도구로 MP4를 다시 먹싱하면 편집 리스트가 빠집니다. 그러면 트랙 앞에 수십 ms의 무음이 생겨, 라이브 앨범처럼 곡이 끊김 없이 이어지는 음반에서는 곡 사이에 틈이 들리고 영상에서는 오디오가 일정하게 늦는 싱크 오차가 생깁니다. 갭리스가 중요한 콘텐츠라면 중간 산출물을 ADTS로 두지 말고 처음부터 MP4(.m4a)로 유지하며, 먹싱 후 MP4 박스 구조에 편집 리스트(elst)가 남아 있는지 확인하는 것이 좋습니다.

라이선스

AAC 특허 풀은 Via Licensing이 관리해 왔고, 2023년 MPEG LA와 합병해 현재는 Via LA가 창구입니다. 초기 AAC 관련 특허는 상당수 만료되었지만 HE-AAC·xHE-AAC 쪽에는 유효한 특허가 남아 있습니다.

사용 형태일반적인 상황
OS 코덱을 호출하는 앱 (iOS/Android/Windows 기본 디코더·인코더)대개 OS 공급자의 라이선스로 커버
인코더·디코더를 직접 번들한 앱·서버 제품유료·무료와 무관하게 검토 필요
하드웨어 제품제조사가 라이선스를 받는 것이 일반적

라이선스 판단 기준은 “돈을 받느냐”보다 AAC 구현체를 누가 배포하느냐에 가깝습니다. 브라우저·OS가 제공하는 디코더로 재생만 하는 웹 서비스와, FFmpeg를 서버 제품에 넣어 고객사에 설치해 주는 경우는 전혀 다른 상황입니다. FFmpeg 네이티브 aac는 FFmpeg 라이선스(LGPL/GPL)를 따르고, libfdk_aac는 Fraunhofer FDK AAC 라이선스를 따르는데 이 라이선스는 특허 사용권을 부여하지 않는다고 명시합니다. 상용 제품이나 대규모 배포라면 법무 검토를 거쳐야 하며, 이 표는 법률 자문이 아닙니다.

메타데이터와 앨범 아트

ffmpeg -i input.wav \
  -c:a aac -b:a 192k \
  -metadata title="노래 제목" \
  -metadata artist="아티스트" \
  -metadata album="앨범명" \
  -metadata date="2026" \
  output.m4a

# 앨범 아트 추가
ffmpeg -i input.wav -i cover.jpg \
  -map 0:a -map 1:v \
  -c:a aac -b:a 192k \
  -c:v copy \
  -disposition:v:0 attached_pic \
  output.m4a

스트림 정보 확인

# 코덱, 프로파일, 샘플레이트, 채널, 비트레이트 확인
ffprobe -v error -select_streams a:0 -show_streams audio.m4a
# codec_name=aac
# profile=LC  (HE-AAC면 HE-AAC, v2면 HE-AACv2)
# sample_rate=48000
# channels=2

# 프레임 단위 정보
ffprobe -v error -select_streams a:0 -show_frames audio.m4a | head -n 50

# ADTS 헤더 덤프
xxd audio.aac | head -n 2

ADTS 헤더는 7바이트(CRC가 있으면 9바이트)입니다. 첫 바이트 둘이 FF F1이면 동기 워드 12비트(FFF), MPEG 버전 1비트(0=MPEG-4), 레이어 2비트(항상 00), protection_absent 1비트(1=CRC 없음)입니다. 다음 바이트가 50이면 상위 2비트 01이 프로파일(오디오 객체 타입에서 1을 뺀 값, 즉 LC), 다음 4비트 0100이 샘플레이트 인덱스 4(44.1 kHz)를 뜻합니다. HE-AAC를 ADTS로 담으면 헤더에는 코어인 LC와 절반 샘플레이트가 기록되고 SBR은 비트스트림 안에서 암묵적으로 신호되는 경우가 많아서, 헤더만 보고 HE-AAC 여부를 판단하면 틀릴 수 있습니다.


실행해서 확인한 결과

이 글의 ffmpeg 명령 28개를 macOS(FFmpeg 9.0.2)에서 testsrc2 640x360 영상과 사인파 음성으로 만든 합성 입력(mp4·mkv·webm·wav 등)으로 실행했습니다. 23개가 오류 없이 끝났습니다. libfdk_aac를 쓰는 5개는 이 빌드에 해당 인코더가 없어(Encoder not found) 실행하지 못했고, libfdk_aac 빌드 여부에 따라 달라지므로 HE-AAC·VBR 모드 동작은 확인하지 못했습니다. 내장 aac 인코더 예제와 표지 이미지 삽입 명령은 정상 동작했습니다. 청감 품질과 비트레이트 비교는 확인하지 않았습니다.

참고 자료



같이 보면 좋은 글