G.711は、音声エージェントがほとんどの電話で耳にするコーデックです。音声を1秒間に8,000回サンプリングし、各サンプルをμ-lawかA-lawという2種類の対数カーブのどちらかで8ビットに収め、64kbit/sで送ります。ITUがG.711勧告として最初に公開したのは1972年ですが、今も公衆電話網、多くのSIPトランク、TwilioのMedia Streamsで標準の形式です。
電話の音声に触れるものを作るなら、遅かれ早かれG.711のバイト列を扱うことになります。このガイドでは、コーデックの仕組み、μ-lawとA-lawの見分け方、数行のコードでのデコードとエンコード、発信者の声をノイズに変えてしまうバグ、そして余計な変換なしに8kHzの音声を音声モデルとやり取りする方法を説明します。電話回線を音声認識、LLM、音声合成につなぐエンジニア向けの内容です。
G.711の概要
| 項目 | 値 |
|---|---|
| サンプリングレート | 8,000Hz |
| 1サンプルのビット | 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がこれを使うので、パケットキャプチャで見る数字は1パケット160バイトです。
μ-lawとA-lawの仕組み
16ビットのリニアサンプルは65,536段階を表せます。音声にはそこまで均等な刻みは要りません。必要なのは、声の情報の大半がある小さな音量での細かい刻みと、小さな誤差が耳に届かない大きな音量での粗い刻みです。
G.711はまさにそれをします。各サンプルを対数カーブに写す処理で、圧伸(圧縮してからデコード時に伸張する)と呼ばれます。8ビットのコードは3つの部分からなります。符号ビット1つ、8つのセグメントから1つを選ぶ3ビット、セグメント内の段を選ぶ4ビットです。各セグメントは1つ下のセグメントの2倍の範囲をカバーするので、信号が大きくなるほど刻みの幅が倍になります。
2つの方式は刻みの置き方が違います。
- μ-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でデコードしたところ、試したすべての音量で元の音との相関は半分ほどしか残らず、音量は入力レベルによって半分から2倍まで振れました。方式を間違えた音声は、問題が別の場所にあると思い込ませる程度には聞き取れ、音声認識を台無しにする程度には荒れています。
コードで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)
2つの関数は、16ビットのすべての入力値と256個すべてのμ-lawコードについてaudioopと照合し、完全に一致しました。エンコーダー同士はセグメントの境目で一致しないことがあります(FFmpegとaudioopは65,536個の入力のうち512個で異なります)が、差はカーブ上の1段で、耳ではわかりません。
発信者の声をノイズに変えるバグ
G.711のトラブルの多くは、バイトの実際の中身と、コードがそうだと思っている中身のずれから起きます。
- 方式の取り違え。 μ-lawをA-lawとしてデコードする、またはその逆です。音が大きく、ざらつき、半分くらい聞き取れます。通信事業者についての思い込みではなく、SDPかストリームの
startメッセージを確認してください。 - G.711を8ビットのリニアPCMとして扱う。 8ビットのサンプルをリニアだと思い込むライブラリは、声が埋もれたノイズを再生します。リサンプリング、ミキシング、音声区間検出の前に、16ビットのリニアPCMへデコードしてください。
- ストリームにファイルヘッダーが混ざる。 Twilioは、送信する
media.payloadに音声ファイルのヘッダーバイトを含めてはいけないとしています。WAVの44バイトのヘッダーを送ると、毎回プロンプトの頭にクリック音やノイズが入ります。ヘッダーを取り除くか、音声モデルに生の出力を頼んでください。 - サンプリングレートの表記違い。 8kHzの音声を16kHzと表記すると、2倍速で1オクターブ高く鳴ります。逆はテープを遅回ししたように聞こえます。どちらもエラーなくデコードできるので、こうしたバグはコードレビューをすり抜けます。
- 何度もリサンプリングする。 8kHzのμ-lawを受け取り、あるサービス用に16kHz、別のサービス用に24kHzへ変換し、最後に8kHzのμ-lawへ戻すパイプラインは、変換のたびに遅延とフィルタのアーティファクトを足します。各音声経路で変換が1回、できればゼロになるように形式を選んでください。
- 「品質向上」のためのアップサンプリング。 8kHzの音声を16kHzにしても、4kHzより上は戻りません。増えたサンプルは補間です。16kHzしか受け付けないモデルには役立ちますが、発信者の「f」が「s」に聞こえにくくなるわけではありません。
8kHzが音声エージェントにとって意味すること
300〜3,400Hzの帯域は、多くの人が言葉を聞き取るのに必要な部分で、だから電話は成り立っています。一方で「f」と「s」、「m」と「n」を分ける情報の多くがこの帯域の外にあります。16kHzのきれいなブラウザ音声だけで試した音声認識モデルは、同じ言葉でも電話回線ではかなり悪い結果を出すことがあります。通信事業者側からの話はSIPトランクのガイドにあります。
役立つ習慣が3つあります。
- 電話の音声で評価する。 モデルを比べる前に、実際の通信事業者経由でテスト通話を録音するか、少なくともテストセットを8kHzのμ-lawで往復変換してください。結果を読み違えない方法はWERのガイドで説明しています。
- 大事な情報は確認する。 名前、メールアドレス、口座番号は復唱し、長い数字列にはキー入力を案内します。DTMFのキー入力は、コーデックが子音を正しく運べるかどうかに左右されません。
- 回線が求める形式で音声を送る。 44.1kHzで生成した合成音声を8kHzのμ-lawに落とすと、チャンクのたびに変換が1回かかります。モデルが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の入力を直接受け付けます。エージェント通話は1分あたり0.06ドルで、Playgroundで作って自分の電話からかけられます。
数本の録音を変換したいだけなら、FFmpegで十分で、音声APIは要りません。