AAC 오디오 트랜스코딩 파라미터별 실측 비교
개요
영상 트랜스코딩을 다룰 때는 해상도, 비트레이트, GOP, Profile 같은 비디오 파라미터에 관심이 쏠리기 쉽습니다. 하지만 실제 재생 품질 문의의 상당수는 오디오 트랙에서 발생합니다. 화면은 멀쩡한데 소리가 먹먹하다거나, 특정 기기에서만 오디오가 한쪽으로 쏠려 들린다거나, 저용량 프로파일에서 소리가 유독 거칠게 들린다는 식입니다.
이 글은 AAC 오디오 트랜스코딩에서 자주 건드리는 세 가지 파라미터, sampling rate, audio bitrate, channel 설정을 FFmpeg와 Tencent Cloud MPS 양쪽에서 실제로 바꿔가며 결과물을 ffprobe로 검증합니다.
소스는 720p H.264 비디오에 32000Hz 스테레오 AAC 오디오가 붙은 10초 클립이고, AI로 생성한 합성 콘텐츠입니다.
실무 시나리오: 저용량 스트림에서 소리가 이상하다는 문의를 받았다
모바일 저용량 프로파일(오디오 32kbps 근처)에서 소리가 깨진다는 문의가 들어왔다고 가정합니다. 이때 확인해야 할 질문은 크게 세 가지로 나뉩니다.
첫째, 32kbps처럼 낮은 비트레이트를 요청했을 때 인코더가 그 값을 그대로 쓰는지, 아니면 재생 가능한 결과물을 만들기 위해 sample rate나 channel 수를 몰래 낮추는지입니다.
둘째, 같은 32kbps라도 mono와 stereo 중 어느 쪽이 더 안전한 선택인지입니다. 셋째, 프리셋 기반 매니지드 서비스(MPS)에서는 이 값들을 어디까지 세밀하게 통제할 수 있는지입니다.
이 세 질문에 감이 아니라 실측으로 답하기 위해, 같은 소스로 FFmpeg 네이티브 AAC 인코더와 MPS 양쪽에서 동일한 파라미터 조합을 여러 번 돌렸습니다.
FFmpeg 실측: sampling rate
-ar 값만 바꾸고 비트레이트(128k)와 채널(스테레오)은 고정했습니다.
ffmpeg -y -i src.mp4 -vn -c:a aac -b:a 128k -ar 48000 -ac 2 ar_48000.m4a
ffmpeg -y -i src.mp4 -vn -c:a aac -b:a 128k -ar 44100 -ac 2 ar_44100.m4a
ffmpeg -y -i src.mp4 -vn -c:a aac -b:a 128k -ar 22050 -ac 2 ar_22050.m4affprobe로 확인한 결과입니다.
| 요청 sample rate | 실제 sample_rate | channels | 실측 bit_rate | 파일 크기 |
|---|---|---|---|---|
| 48000 | 48000 | 2 | 128,133 bps | 162,418 bytes |
| 44100 | 44100 | 2 | 128,228 bps | 162,415 bytes |
| 22050 | 22050 | 2 | 124,178 bps | 156,868 bytes |
요청한 sample rate는 세 경우 모두 정확히 그대로 반영됐습니다. 22050Hz로 낮췄을 때 파일 크기가 살짝 줄어든 건 sample rate 자체보다는 인코더가 낮은 sample rate 조건에서 목표 비트레이트에 완전히 도달하지 못했기 때문으로 보입니다(124,178 vs 요청 128,000). 이 정도 편차는 정상 범위입니다.
FFmpeg 실측: audio bitrate
이번엔 sample rate(44100)와 채널(스테레오)을 고정하고 -b:a만 바꿨습니다.
ffmpeg -y -i src.mp4 -vn -c:a aac -b:a 320k -ar 44100 -ac 2 br_320k.m4a
ffmpeg -y -i src.mp4 -vn -c:a aac -b:a 128k -ar 44100 -ac 2 br_128k.m4a
ffmpeg -y -i src.mp4 -vn -c:a aac -b:a 64k -ar 44100 -ac 2 br_64k.m4a
ffmpeg -y -i src.mp4 -vn -c:a aac -b:a 32k -ar 44100 -ac 2 br_32k.m4a| 요청 bitrate | 실측 bit_rate | 파일 크기 |
|---|---|---|
| 320k | 261,755 bps | 328,910 bytes |
| 128k | 128,228 bps | 162,415 bytes |
| 64k | 64,252 bps | 82,643 bytes |
| 32k | 32,205 bps | 42,683 bytes |
128k, 64k, 32k는 요청값과 실측값이 거의 정확히 일치했습니다. 그런데 320k만 유독 실측값이 261,755bps로, 요청한 값의 약 82%에 그쳤습니다.
이건 우연이 아니라 FFmpeg 네이티브 AAC 인코더 내부의 하드 캡과 관련이 있습니다. 아래에서 이 부분을 따로 파봤습니다.
왜 320k가 그대로 나오지 않는가: 6144비트 프레임 캡
FFmpeg 네이티브 AAC 인코더(aac)는 AAC 스펙이 정한 채널당 프레임 비트 상한(1024 샘플 프레임당 채널당 최대 6144비트)을 그대로 지킵니다. 요청한 비트레이트가 이 상한을 넘으면 콘솔에 경고를 남기고 값을 강제로 낮춥니다.
이 상한을 넘는지 여부는 요청 비트레이트 × 1024 / sample_rate가 6144 × channel 수를 넘는지로 결정됩니다.
이 공식이 실제로 맞는지 네 가지 경계 조건으로 검증했습니다.
# 모노 44100Hz에 320k 요청 → 7430 > 6144(모노 캡), 클램프 발동
ffmpeg -y -loglevel warning -i src.mp4 -vn -c:a aac -b:a 320k -ar 44100 -ac 1 mono_320k.m4a
# [aac @ ...] Too many bits 7430.385488 > 6144 per frame requested, clamping to max
# 실측 bit_rate=168,336
# 스테레오 44100Hz에 320k 요청 → 7430 < 12288(스테레오 캡), 클램프 미발동
ffmpeg -y -loglevel warning -i src.mp4 -vn -c:a aac -b:a 320k -ar 44100 -ac 2 stereo_320k.m4a
# 경고 없음, 실측 bit_rate=261,755
# 스테레오 22050Hz에 320k 요청 → 14860 > 12288(스테레오 캡), 클램프 발동
ffmpeg -y -loglevel warning -i src.mp4 -vn -c:a aac -b:a 320k -ar 22050 -ac 2 s22_320k.m4a
# [aac @ ...] Too many bits 14860.770975 > 12288 per frame requested, clamping to max
# 실측 bit_rate=147,440
# 모노 22050Hz에 128k 요청 → 5942 < 6144(모노 캡), 클램프 미발동
ffmpeg -y -loglevel warning -i src.mp4 -vn -c:a aac -b:a 128k -ar 22050 -ac 1 s22_mono_128k.m4a
# 경고 없음, 실측 bit_rate=99,785네 조건 모두 공식과 정확히 맞아떨어졌습니다. 여기서 중요한 실무 포인트가 하나 나옵니다. 스테레오 44100Hz에 320k를 요청한 경우는 클램프 경고가 뜨지 않았는데도 실측값(261,755bps)이 요청값보다 18% 가까이 낮았습니다.
즉 하드 캡에 걸리지 않아도 네이티브 AAC 인코더가 요청한 비트레이트를 콘텐츠 특성에 따라 완전히 채우지 못하는 경우가 있다는 뜻입니다. 320kbps처럼 AAC치고 상당히 높은 값을 요청할 때는 결과물을 반드시 ffprobe로 재확인해야 한다는 걸 이번 실측으로 확인했습니다.
반대로 128k 이하 구간에서는 요청값과 실측값이 거의 정확히 일치했으므로, 실무에서 흔히 쓰는 64k~128k 구간은 신뢰하고 써도 됩니다.
FFmpeg 실측: channel 설정
sample rate(44100)와 bitrate(128k)를 고정하고 채널만 바꿨습니다.
ffmpeg -y -i src.mp4 -vn -c:a aac -b:a 128k -ar 44100 ch_original.m4a # -ac 생략, 원본 채널 유지
ffmpeg -y -i src.mp4 -vn -c:a aac -b:a 128k -ar 44100 -ac 1 ch_mono.m4a
ffmpeg -y -i src.mp4 -vn -c:a aac -b:a 128k -ar 44100 -ac 2 ch_stereo.m4a| 설정 | channels | channel_layout | 실측 bit_rate | 파일 크기 |
|---|---|---|---|---|
원본 유지(-ac 생략) | 2 | stereo | 128,228 bps | 162,415 bytes |
-ac 1(mono) | 1 | mono | 128,992 bps | 163,367 bytes |
-ac 2(stereo) | 2 | stereo | 128,228 bps | 162,415 bytes |
원본이 이미 스테레오라 -ac 생략과 -ac 2는 완전히 동일한 결과가 나왔습니다. 흥미로운 건 mono 결과의 파일 크기가 stereo보다 오히려 살짝 더 크다는 점입니다(163,367 vs 162,415 bytes).
같은 128kbps를 요청했을 때 mono는 채널 하나에 전체 비트를 몰아줘서 채널당 비트 밀도가 stereo의 두 배가 되고, 인코더가 그 여유를 실제로 다 채워 썼기 때문입니다. "mono면 무조건 파일이 작아진다"는 통념은 같은 비트레이트를 유지하는 조건에서는 성립하지 않는다는 걸 보여주는 사례입니다.
극저비트레이트(32kbps)에서 mono와 stereo 비교
실무 시나리오에서 제기한 질문, 32kbps에서 mono와 stereo 중 뭐가 더 안전한지를 직접 확인했습니다.
ffmpeg -y -i src.mp4 -vn -c:a aac -b:a 32k -ar 44100 -ac 1 extreme_32k_mono.m4a
ffmpeg -y -i src.mp4 -vn -c:a aac -b:a 32k -ar 44100 -ac 2 extreme_32k_stereo.m4a| 설정 | channels | 실측 bit_rate | 파일 크기 |
|---|---|---|---|
| 32k, mono | 1 | 32,390 bps | 42,914 bytes |
| 32k, stereo | 2 | 32,205 bps | 42,683 bytes |
둘 다 요청한 sample rate와 channel 수를 그대로 유지했고, 인코더가 몰래 mono로 다운믹스하거나 sample rate를 낮추는 동작은 없었습니다. 다만 32kbps를 스테레오 두 채널에 나눠 쓰면 채널당 실질 비트 밀도는 mono의 절반 수준이 됩니다.
문의로 들어온 "저용량에서 소리가 거칠다"는 증상은 인코더가 값을 왜곡해서가 아니라, 애초에 스테레오로 32kbps를 나눠 쓰는 구성 자체가 채널당 정보량 부족을 만들기 때문일 가능성이 높습니다. 같은 32kbps 예산이라면 mono로 인코딩하는 편이 채널당 음질 여유가 더 큽니다.
말로만 설명하기보다 직접 들어보는 게 빠릅니다. 같은 소스를 32kbps로, 채널만 바꿔 인코딩한 결과입니다.
32kbps, mono
32kbps, stereo
들어보면 스테레오 쪽이 더 먹먹하고 디테일이 뭉개진 느낌이 나는 반면, mono 쪽은 같은 32kbps인데도 상대적으로 또렷합니다. 채널당 실질 비트 밀도가 두 배 차이 나는 게 숫자로만이 아니라 실제로 체감되는 차이라는 걸 확인할 수 있습니다.
참고: 소스가 스테레오일 때 5.1(6채널)로 업믹스 요청
-ac 6으로 스테레오 소스를 6채널로 강제 지정하면 FFmpeg는 별다른 경고 없이 인코딩을 완료합니다.
ffmpeg -y -i src.mp4 -vn -c:a aac -b:a 320k -ar 48000 -ac 6 upmix_6ch.m4a결과는 channels=6, channel_layout=5.1로 찍혔습니다. 다만 원본이 진짜 5.1 믹스가 아니라 스테레오이기 때문에, 이건 서라운드 채널에 실제 공간감 있는 콘텐츠가 채워지는 게 아니라 채널 레이아웃 태그만 5.1로 바뀌는 기계적 업믹스입니다. 채널 수를 늘리는 것과 실제 서라운드 믹스를 만드는 것은 다른 작업이라는 점을 실측으로 확인한 셈입니다.
MPS 실측: RawParameter로 오디오 파라미터 직접 지정하기
MPS에서 등록된 프리셋 템플릿이 아니라 즉석에서 파라미터를 지정하려면 Definition: 0과 함께 RawParameter를 씁니다. 여기서 첫 번째로 확인한 건, 오디오만 바꾸고 싶어도 RawParameter.Container와 RawParameter.VideoTemplate.Codec을 비워두면 요청이 거부된다는 점입니다.
# AudioTemplate만 채우고 VideoTemplate을 생략한 첫 시도
task["RawParameter"] = {
"Container": "mp4",
"AudioTemplate": {"Codec": "aac", "Bitrate": 128, "SampleRate": 44100, "AudioChannel": 2},
}
# 결과: InvalidParameterValue.VideoCodec is null비디오 트랙을 아예 없애고 오디오만 다루고 싶다면 RemoveVideo: 1을 지정하면 됩니다. 이렇게 하면 VideoTemplate 없이도 요청이 통과합니다.
task["RawParameter"] = {
"Container": "mp4",
"RemoveVideo": 1,
"AudioTemplate": {"Codec": "aac", "Bitrate": 128, "SampleRate": 44100, "AudioChannel": 2},
}MPS 실측: sampling rate
SampleRate에 48000/44100/32000/22050을 각각 넣어 테스트했습니다.
| 요청 SampleRate | 실제 sample_rate | channels | 실측 bit_rate | 파일 크기 |
|---|---|---|---|---|
| 48000 | 48000 | 2 | 129,086 bps | 163,264 bytes |
| 44100 | 44100 | 2 | 129,049 bps | 163,066 bytes |
| 32000 | 32000 | 2 | 129,194 bps | 162,766 bytes |
| 22050 | 22050 | 2 | 129,614 bps | 162,912 bytes |
네 값 모두 그대로 반영됐고, 실측 bit_rate도 요청한 128kbps에 거의 정확히 맞춰졌습니다. FFmpeg의 22050Hz 결과(124,178bps)보다 오히려 목표치에 더 가깝게 나왔는데, 이건 MPS 트랜스코딩 파이프라인이 목표 비트레이트를 채우는 레이트 컨트롤 튜닝이 FFmpeg 네이티브 AAC 인코더와 다르기 때문으로 보입니다.
MPS 실측: audio bitrate 256kbps 하드컷
Bitrate 값을 256, 288, 320(kbps)으로 올려가며 테스트했습니다.
# Bitrate: 256 → 성공
# Bitrate: 288 → InvalidParameterValue.AudioBitrate(288) for Container(mp4)
# Bitrate: 320 → InvalidParameterValue.AudioBitrate(320) for Container(mp4)256kbps까지는 정상 처리됐지만 288kbps부터는 요청 자체가 거부됐습니다. 이 지점이 FFmpeg와 가장 뚜렷하게 갈리는 대목입니다.
FFmpeg 네이티브 AAC 인코더는 320kbps를 요청하면 일단 받아들인 뒤 내부 캡에 걸려 조용히(또는 경고와 함께) 값을 낮춰서 처리합니다. 반면 MPS는 mp4 컨테이너의 AAC 오디오에 대해 유효 비트레이트 상한을 256kbps로 명확히 그어두고, 그 값을 넘는 요청은 아예 InvalidParameterValue로 튕겨냅니다.
값을 조용히 바꾸는 대신 요청 단계에서 실패시키는 쪽을 선택한 셈인데, 어느 쪽이 안전한지는 관점에 따라 다르지만 최소한 "왜 결과물이 요청과 다른가"를 디버깅할 필요가 없다는 점에서는 MPS 쪽이 더 명확합니다.
저비트레이트 구간도 함께 확인했습니다.
| 설정 | channels | 실측 bit_rate | 파일 크기 |
|---|---|---|---|
| 32kbps, mono | 1 | 32,532 bps | 42,998 bytes |
| 32kbps, stereo | 2 | 32,814 bps | 43,349 bytes |
FFmpeg와 마찬가지로 32kbps 극저비트레이트에서도 sample rate나 channel 수를 몰래 낮추는 동작은 없었습니다.
MPS 실측: channel 설정과 5.1 업믹스
AudioChannel 필드를 비웠을 때, 1로 지정했을 때, 2로 지정했을 때를 비교했습니다.
| 설정 | 실제 channels | channel_layout | 비고 |
|---|---|---|---|
AudioChannel 생략 | 2 | stereo | 원본(스테레오) 채널 그대로 유지 |
AudioChannel: 1 | 1 | mono | |
AudioChannel: 2 | 2 | stereo | 생략했을 때와 MD5까지 완전히 동일 |
AudioChannel을 생략하면 원본 채널 수(이 소스는 스테레오)를 그대로 유지하고, 원본이 이미 스테레오인 이 케이스에서는 생략과 AudioChannel: 2 명시가 바이트 단위로 완전히 동일한 결과물을 만들었습니다(같은 MD5).
5.1(6채널) 옵션은 실제로 넣어봤습니다.
audio_template = {"Codec": "aac", "Bitrate": 128, "SampleRate": 48000, "AudioChannel": 6}이 요청은 에러 없이 성공했고, 결과물의 실제 채널 정보는 channels=6, channel_layout=5.1로 정확히 찍혔습니다. 즉 MPS의 RawParameter 커스텀 인코딩 경로는 AudioChannel: 6을 정식으로 지원하고, 스테레오 소스도 6채널로 업믹스해 처리합니다.
AudioChannel에 3, 4, 8처럼 1/2/6이 아닌 값을 넣으면 각각 InvalidParameterValue.AudioChannel(3), (4), (8)로 명확히 거부됐으므로, 이 필드가 받는 유효값은 사실상 미지정(원본 유지)/1(mono)/2(stereo)/6(5.1) 네 가지로 좁혀집니다.
다만 콘솔에 등록된 표준 프리셋 템플릿에서는 이 옵션이 노출되지 않습니다. DescribeTranscodeTemplates로 프리셋 타입 템플릿 290개를 전부 훑어본 결과, AudioChannel 값은 예외 없이 전부 2(스테레오)였습니다.
정리하면 mono와 5.1 채널 지정은 표준 프리셋 카탈로그에는 없지만, RawParameter 커스텀 인코딩 경로에서는 API 레벨로 이미 정식 지원되는 기능입니다. 프리셋 목록에서 mono나 5.1 옵션을 찾을 수 없어서 지원이 안 되는 줄 알았다면, RawParameter로 직접 지정해보는 걸 권합니다.
MPS 실측에서 발견한 부수적 함정: 배치 제출과 실패 전파
이번 실측 중에 파라미터 자체와는 별개로 유의할 만한 운영상의 함정을 하나 발견했습니다. ProcessMedia 호출 하나에 TranscodeTaskSet 배열로 여러 개의 트랜스코딩 작업을 한꺼번에 담아 제출할 수 있는데, 이 배열 안에 파라미터가 유효하지 않은 작업이 하나라도 섞여 있으면 같은 배치에 속한 다른 작업들까지 전부 실패 처리됩니다.
실제로 Bitrate 320, 128, 64, 32(kbps)를 각각 지정한 작업 4개를 한 번에 제출했더니, 320만 유효 범위를 벗어났는데도 128/64/32 작업까지 전부 FAIL 상태로 끝났고, 네 작업 모두 동일한 에러 메시지(InvalidParameterValue.AudioBitrate(320) for Container(mp4))를 그대로 돌려받았습니다. 128/64/32는 단독으로 제출했을 때는 문제없이 성공했던 값입니다.
즉 MPS는 배치 안의 모든 작업을 먼저 검증하고, 하나라도 걸리면 배치 전체를 무효화하는 것으로 보입니다. 여러 파라미터 조합을 한 번에 테스트하거나 운영에서 배치로 묶어 제출할 때는, 조합 하나하나를 개별 요청으로 나누거나 사전에 값 검증을 거치는 편이 안전합니다.
정리
세 파라미터를 FFmpeg와 MPS 양쪽에서 실측한 결과를 종합하면 이렇습니다.
| 파라미터 | FFmpeg 네이티브 AAC | MPS RawParameter |
|---|---|---|
| Sampling rate | 요청값 그대로 반영. 저sample rate에서 실측 bit_rate가 목표에 소폭 못 미칠 수 있음 | 요청값 그대로 반영. 128kbps 목표에 더 정확히 수렴 |
| Audio bitrate | 채널당 6144비트/프레임 캡을 넘으면 자동 클램프(경고 로그 남김). 캡 안에서도 고비트레이트는 목표에 못 미칠 수 있음 | mp4 컨테이너 기준 256kbps 초과 요청은 InvalidParameterValue로 즉시 거부. 256 이하는 목표치에 정확히 수렴 |
| Channel | 원본 유지/mono/stereo 모두 정확히 반영. 채널 수와 무관하게 요청 비트레이트는 유지(밀도만 달라짐). -ac 6으로 기계적 업믹스도 가능 | AudioChannel 생략 시 원본 채널 유지. 1/2/6만 유효값이고 그 외 값은 명확히 거부. 6(5.1)도 API 레벨에서 정식 지원하지만 표준 프리셋 290개에는 노출되지 않음 |
두 경로 모두 극저비트레이트(32kbps)에서 sample rate나 channel 수를 몰래 낮추는 동작은 하지 않았습니다. 다만 값이 유효 범위를 벗어났을 때 대응하는 방식은 뚜렷하게 갈립니다.
FFmpeg는 인코더 내부 스펙 한도까지는 값을 받아들이되 넘으면 조용히 낮추고, MPS는 서비스가 정한 유효 범위를 벗어나는 순간 요청 자체를 거부합니다. 저용량 스트림에서 오디오 문제를 진단할 때는 이 차이를 염두에 두고, 요청한 값이 아니라 ffprobe로 확인한 실제 값을 기준으로 판단하는 게 안전합니다.
이번 실측에 쓴 COS 오브젝트(audio-transcode-test/ 아래 소스 파일과 트랜스코딩 결과물)는 검증이 끝난 뒤 전부 삭제했습니다.