G.711은 음성 에이전트가 대부분의 전화 통화에서 듣는 코덱입니다. 음성을 초당 8,000번 샘플링하고, 각 샘플을 μ-law나 A-law라는 두 로그 곡선 중 하나로 8비트에 담아 64kbit/s로 보냅니다. ITU가 1972년에 G.711 권고로 처음 발표했고, 지금도 공중 전화망, 대부분의 SIP 트렁크, Twilio Media Streams에서 기본 형식입니다.
전화 음성을 다루는 무언가를 만든다면 언젠가는 G.711 바이트를 만지게 됩니다. 이 가이드는 코덱의 작동 방식, μ-law와 A-law를 구분하는 법, 몇 줄의 코드로 디코딩과 인코딩하는 법, 발신자의 목소리를 잡음으로 바꾸는 버그, 그리고 불필요한 변환 없이 8kHz 음성을 음성 모델과 주고받는 법을 다룹니다. 전화 회선을 음성 인식, LLM, 음성 합성에 연결하는 엔지니어를 위한 글입니다.
G.711 한눈에 보기
| 항목 | 값 |
|---|---|
| 샘플링 레이트 | 8,000Hz |
| 샘플당 비트 | 8 (선형이 아닌 로그) |
| 비트레이트 | 단방향 64kbit/s |
| 오디오 대역 | 약 300~3,400Hz |
| 종류 | μ-law(PCMU)와 A-law(PCMA) |
| RTP 페이로드 | PCMU = 0, PCMA = 8, 둘 다 8,000Hz 클럭 |
| 20ms 패킷 | 160샘플, 페이로드 160바이트 |
| 코덱 지연 | 1샘플(0.125ms), 선행 탐색 없음 |
| 무음 바이트 | μ-law 0xFF, A-law 0xD5 |
20ms 패킷 크기는 RFC 3551에서 나오며, PCMU와 PCMA의 기본 패킷화 간격을 20ms로 정합니다. 대부분의 통신사와 PBX가 이 값을 쓰기 때문에 패킷 캡처에서 보게 되는 숫자는 패킷당 160바이트입니다.
μ-law와 A-law의 작동 방식
16비트 선형 샘플은 65,536단계를 표현할 수 있습니다. 음성에는 그렇게 많은 균등한 단계가 필요하지 않습니다. 필요한 것은 목소리 정보가 대부분 모여 있는 작은 음량에서의 세밀한 단계와, 작은 오차가 귀에 들리지 않는 큰 음량에서의 굵은 단계입니다.
G.711은 정확히 그렇게 합니다. 각 샘플을 로그 곡선에 대응시키는데, 이를 압신(압축한 뒤 디코딩할 때 신장)이라고 합니다. 8비트 코드는 세 부분으로 되어 있습니다. 부호 비트 1개, 8개 세그먼트 중 하나를 고르는 3비트, 세그먼트 안의 단계를 고르는 4비트입니다. 각 세그먼트는 바로 아래 세그먼트의 두 배 범위를 덮으므로, 신호가 커질수록 단계 폭이 두 배가 됩니다.
두 방식은 단계를 두는 위치가 다릅니다.
- μ-law는 14비트 선형 샘플을 입력으로 받고 북미와 일본에서 씁니다. Twilio Media Streams가 주고받는 유일한 형식이기도 합니다.
- A-law는 13비트 선형 샘플을 입력으로 받고 유럽, 인도, 그리고 대부분의 다른 나라에서 씁니다. 작은 신호에 약간 더 높은 해상도를 줍니다.
둘 다 13~14비트의 다이내믹 레인지를 8비트에 담기 때문에 G.711은 무전기가 아니라 평범한 전화처럼 들립니다. μ-law 국가에서 A-law 국가로 통화가 넘어가면 네트워크가 변환하고, 표준은 두 나라 사이 구간에서 A-law를 쓰도록 정합니다.
어느 방식인지 알아내는 법
보통 바이트만 봐서는 구분할 수 없습니다. 둘 다 헤더 없는 부호 없는 8비트 값의 나열입니다. 대신 주변 메타데이터를 보세요.
- SDP.
a=rtpmap:0 PCMU/8000이면 μ-law,a=rtpmap:8 PCMA/8000이면 A-law입니다. - Twilio Media Streams.
start메시지는 항상"encoding": "audio/x-mulaw"와"sampleRate": 8000입니다. Twilio의 WebSocket 메시지 문서는 이 값이 바뀌지 않는다고 명시합니다. - WAV 파일.
fmt청크의 포맷 태그는 μ-law가 7, A-law가 6입니다.ffprobe는pcm_mulaw또는pcm_alaw로 표시합니다. - 무음. 조용한 회선에서 μ-law 바이트는
0xFF와0x7E근처에 머물고, A-law 바이트는0xD5와0x55를 오갑니다.0xFF로 가득 찬 버퍼는 손상된 스트림이 아니라 μ-law 무음일 가능성이 큽니다.
잘못 고르면 귀로 들립니다. 440Hz 톤을 μ-law로 인코딩하고 A-law로 디코딩해 보니, 시험한 모든 음량에서 원본과의 상관이 절반 정도만 남았고, 음량은 입력 레벨에 따라 절반에서 두 배까지 흔들렸습니다. 방식을 잘못 고른 음성은 문제가 다른 곳에 있다고 착각할 만큼은 알아들을 수 있고, 음성 인식을 망칠 만큼은 거칩니다.
코드로 G.711 디코딩·인코딩하기
가장 빠른 방법은 FFmpeg입니다. 두 방식 모두 원시 스트림으로 읽고 쓸 수 있습니다.
# 통화의 원시 μ-law를 들을 수 있는 16비트 WAV로
ffmpeg -f mulaw -ar 8000 -ac 1 -i call.ulaw call.wav
# 아무 오디오 파일을 Twilio로 보낼 8kHz 원시 μ-law로
ffmpeg -i prompt.wav -ar 8000 -ac 1 -f mulaw prompt.ulaw
# A-law: 포맷 이름만 바꾸면 됩니다
ffmpeg -f alaw -ar 8000 -ac 1 -i call.alaw call.wav
Python에서는 예전에 표준 라이브러리의 audioop으로 했지만, audioop은 Python 3.13에서 제거되었습니다. audioop-lts 패키지로 되살릴 수 있습니다. 의존성을 늘리고 싶지 않다면 μ-law는 NumPy 몇 줄이면 됩니다.
import numpy as np
def linear_to_mulaw(pcm: np.ndarray) -> bytes:
"""16-bit signed PCM samples -> G.711 mu-law bytes."""
x = pcm.astype(np.int32) >> 2 # 14-bit magnitude range
mask = np.where(x < 0, 0x7F, 0xFF)
x = np.minimum(np.abs(x), 8158) + 33 # clip, then add the bias
segment = np.floor(np.log2(x)).astype(np.int32) - 5
code = (segment << 4) | ((x >> (segment + 1)) & 0x0F)
return (code ^ mask).astype(np.uint8).tobytes()
def mulaw_to_linear(data: bytes) -> np.ndarray:
"""G.711 mu-law bytes -> 16-bit signed PCM samples."""
u = ~np.frombuffer(data, dtype=np.uint8).astype(np.int32) & 0xFF
segment, mantissa = (u >> 4) & 0x07, u & 0x0F
magnitude = (((mantissa << 3) + 0x84) << segment) - 0x84
return np.where(u & 0x80, -magnitude, magnitude).astype(np.int16)
두 함수를 16비트의 모든 입력값과 256개 μ-law 코드 전부에 대해 audioop과 대조했고, 완전히 일치했습니다. 인코더끼리는 세그먼트 경계에서 결과가 다를 수 있습니다(FFmpeg와 audioop은 65,536개 입력 중 512개에서 다릅니다). 차이는 곡선에서 한 단계이고 귀로는 구분되지 않으니 괜찮습니다.
발신자의 목소리를 잡음으로 바꾸는 버그
G.711 문제의 대부분은 바이트의 실제 내용과 코드가 생각하는 내용이 어긋나서 생깁니다.
- 방식 혼동. μ-law를 A-law로 디코딩하거나 그 반대입니다. 소리가 크고 거칠며 반쯤 알아들을 수 있습니다. 통신사에 대한 짐작이 아니라 SDP나 스트림의
start메시지를 확인하세요. - G.711을 8비트 선형 PCM으로 취급. 8비트 샘플을 선형이라고 가정하는 라이브러리는 목소리가 묻힌 잡음을 재생합니다. 리샘플링, 믹싱, 음성 활동 감지 전에 16비트 선형 PCM으로 디코딩하세요.
- 스트림에 섞인 파일 헤더. Twilio는 보내는
media.payload에 오디오 파일 헤더 바이트를 넣지 말라고 합니다. WAV의 44바이트 헤더를 보내면 매 프롬프트 시작에 딸깍 소리나 잡음이 들어갑니다. 헤더를 빼거나 음성 모델에 원시 출력을 요청하세요. - 샘플링 레이트 표기 오류. 8kHz 음성을 16kHz로 표기하면 두 배 빠르고 한 옥타브 높게 재생됩니다. 반대면 느리게 돌린 테이프처럼 들립니다. 둘 다 오류 없이 디코딩되기 때문에 이런 버그는 코드 리뷰를 통과합니다.
- 여러 번 리샘플링. 8kHz μ-law를 받아 한 서비스용으로 16kHz, 다른 서비스용으로 24kHz로 바꾼 뒤 다시 8kHz μ-law로 돌리는 파이프라인은 변환할 때마다 지연과 필터 잡음을 더합니다. 각 오디오 경로가 한 번만, 가능하면 한 번도 변환하지 않도록 형식을 고르세요.
- “음질 개선”을 위한 업샘플링. 8kHz 음성을 16kHz로 바꿔도 4kHz 위는 돌아오지 않습니다. 늘어난 샘플은 보간된 값입니다. 16kHz만 받는 모델에는 도움이 되지만, 발신자의 “f”가 덜 “s”처럼 들리게 해 주지는 않습니다.
8kHz가 음성 에이전트에 의미하는 것
300~3,400Hz 대역은 대부분의 사람이 말을 알아듣는 데 필요한 부분이고, 그래서 전화가 통합니다. 하지만 “f”와 “s”, “m”과 “n”을 가르는 정보의 상당 부분이 이 대역 밖에 있습니다. 깨끗한 16kHz 브라우저 음성으로만 시험한 음성 인식 모델은 같은 말이라도 전화 회선에서는 눈에 띄게 나쁜 결과를 낼 수 있습니다. 통신사 쪽 이야기는 SIP 트렁킹 가이드에 있습니다.
도움이 되는 습관이 세 가지 있습니다.
- 전화 음성으로 평가하세요. 모델을 비교하기 전에 실제 통신사를 통해 테스트 통화를 녹음하거나, 적어도 테스트 세트를 8kHz μ-law로 한 번 왕복 변환하세요. 결과를 잘못 읽지 않는 법은 WER 가이드에서 설명합니다.
- 중요한 정보는 확인하세요. 이름, 이메일, 계좌 번호는 다시 읽어 주고, 긴 숫자열은 키패드 입력을 안내하세요. DTMF 키 입력은 코덱이 자음을 제대로 전달하는지와 상관이 없습니다.
- 회선이 원하는 형식으로 음성을 보내세요. 44.1kHz로 생성한 합성 음성을 8kHz μ-law로 줄이면 청크마다 변환이 한 번씩 듭니다. 모델이 8kHz μ-law를 바로 만들 수 있다면 그렇게 요청하세요.
광대역 음성을 싣는 통화도 있습니다. G.722(“HD 보이스”)는 16kHz로 샘플링하고, WebRTC 통화는 보통 Opus를 씁니다. 하지만 통화가 공중 전화망을 거치는 순간부터는 G.711을 전제로 하세요.
Cartesia에서 G.711 쓰기
Cartesia의 음성 모델은 G.711을 기본으로 받고 출력하므로, 전화 파이프라인에 별도의 코덱 단계가 필요 없습니다.
음성 합성. Sonic은 요청하면 8kHz 원시 μ-law나 A-law를 돌려줍니다. 어떤 TTS 요청이든 출력 형식을 지정하세요.
{
"container": "raw",
"encoding": "pcm_mulaw",
"sample_rate": 8000
}
A-law 회선에는 pcm_alaw를 쓰세요. 모든 인코딩과 레이트는 출력 형식 문서에 있습니다. TTS WebSocket의 원시 청크는 base64로 오는데, 이는 Twilio가 media.payload에 기대하는 형태와 같아서 Twilio 연동 가이드는 디코딩 없이 그대로 전달합니다.
음성 인식. Ink는 스트리밍 API로 μ-law와 A-law를 받습니다. 음성 에이전트용 턴 감지 엔드포인트도 마찬가지입니다. 연결할 때 형식을 선언하세요.
wss://api.cartesia.ai/stt/turns/websocket?model=ink-2&encoding=pcm_mulaw&sample_rate=8000&cartesia_version=2026-08-14
서버는 선언한 그대로 바이트를 디코딩하며, 선언이 틀려도 오류를 돌려주지 않을 수 있습니다. 오디오 입력 문서는 스트림을 저장해 ffplay로 확인하는 방법을 보여 주며, 잘못된 방식이나 레이트를 몇 초 만에 찾아냅니다.
Managed Agents. Managed Agents는 Cartesia 번호, Twilio, 또는 자체 SIP 트렁크로 전화를 받고 통화 음성을 대신 처리합니다. 자체 전화 시스템을 에이전트에 직접 연결한다면 에이전트 WebSocket이 mulaw_8000 입력을 바로 받습니다. 에이전트 통화는 분당 0.06달러이며, 플레이그라운드에서 만들어 내 전화로 걸어 볼 수 있습니다.
녹음 몇 개만 변환하면 된다면 FFmpeg로 충분하고, 음성 API는 필요 없습니다.