GEMINI LABEN
ROBOTICS — 8月31日に停止した ER 1.6 preview には後継があります。Gemini Robotics ER 2 が公開プレビュー中で、通常版とストリーミング版の2種類が提供されていますVIDEO — ER 2 の成功・失敗判定は静止画ではなく生の映像フィード上で動きます。こぼれ・滑り・位置ずれのような、実行の途中で起きる失敗を捉えられる設計ですDEADLINE — 次の期限は9月30日、gemini-omni-flash-preview の廃止です。移行先は8月27日に GA になった gemini-omni-1.1-flash で、残り4週間を切りましたAPIKEY — 残りの標準 API キーは、制限付きのものも含めて9月中に全面停止します。移行先は Google Cloud サービスアカウントに紐付く auth キー形式ですPRICE — Gemini 3.7 Flash の導入価格 $0.75/$3.75 per 1M は12月31日までです。2027年1月1日から $1.50/$7.50 になるため、年を跨ぐ見積もりは2本立てが要りますAUDIO — Gemini 3.5 Transcribe は85言語以上の言語検出、話者ダイアライゼーション、単語単位タイムスタンプ、最大1,000語のカスタム語彙バイアスに対応していますROBOTICS — 8月31日に停止した ER 1.6 preview には後継があります。Gemini Robotics ER 2 が公開プレビュー中で、通常版とストリーミング版の2種類が提供されていますVIDEO — ER 2 の成功・失敗判定は静止画ではなく生の映像フィード上で動きます。こぼれ・滑り・位置ずれのような、実行の途中で起きる失敗を捉えられる設計ですDEADLINE — 次の期限は9月30日、gemini-omni-flash-preview の廃止です。移行先は8月27日に GA になった gemini-omni-1.1-flash で、残り4週間を切りましたAPIKEY — 残りの標準 API キーは、制限付きのものも含めて9月中に全面停止します。移行先は Google Cloud サービスアカウントに紐付く auth キー形式ですPRICE — Gemini 3.7 Flash の導入価格 $0.75/$3.75 per 1M は12月31日までです。2027年1月1日から $1.50/$7.50 になるため、年を跨ぐ見積もりは2本立てが要りますAUDIO — Gemini 3.5 Transcribe は85言語以上の言語検出、話者ダイアライゼーション、単語単位タイムスタンプ、最大1,000語のカスタム語彙バイアスに対応しています
記事一覧/API / SDK
API / SDK/2026-06-03初級

Gemini Live API の応答音声が速回しに聞こえる — サンプルレート取り違えの直し方

Gemini Live API の応答音声が甲高く速回しに聞こえる、あるいはノイズ混じりになる症状は、ほとんどが 24kHz の出力を別のサンプルレートで再生していることが原因です。ブラウザと iOS の両方で、取り違えを直す具体的なコードを記録します。

Gemini API228Live API音声サンプルレート個人開発111トラブルシューティング31

2014 年から個人開発で癒し系・壁紙系のアプリを作り続け、累計 5,000 万ダウンロードほどになりました。そのひとつに音声で話しかけられる機能を試していたとき、Gemini Live API から返ってきた音声が、まるで早送りの再生のように甲高く、3 割ほど速く聞こえました。テキストの応答は正しいのに、音だけがおかしい。最初はモデルの問題かと疑って voiceConfig をいくつも切り替えてみましたが、何も変わりませんでした。原因は Gemini ではなく、私が音声を再生する側のサンプルレートを取り違えていたことでした。

同じ症状で時間を溶かしている方のために、再現条件と直し方を残しておきます。

どんな症状か

  • 応答音声が甲高く、本来より速く聞こえる(いわゆるチップマンクボイス)
  • 逆に低く間延びして聞こえる場合もある
  • ザーッというノイズが乗る、あるいはプチプチと途切れる
  • テキスト出力やトランスクリプトは正常

音が「速い・高い」なら再生レートが本来より高い、「遅い・低い」なら低い、と考えると原因の切り分けが速くなります。

なぜ起きるのか

Gemini Live API の音声まわりには、混同しやすい数字が 2 つあります。

  • 入力(マイクからモデルへ送る音声)は 16,000 Hz(16kHz)の 16bit PCM
  • 出力(モデルが返す音声)は 24,000 Hz(24kHz)の 16bit PCM

送信用に 16kHz を使うコードはサンプルが多く、つい再生側もその流れで 16kHz の AudioContext に流し込んでしまいます。すると 24kHz のデータを 16kHz として読むことになり、再生が 24000 / 16000 = 1.5 倍速、ピッチも 1.5 倍に上がります。これが「速回しで甲高い」の正体です。

ブラウザにはもう一段の落とし穴があります。new AudioContext() を引数なしで作ると、多くの環境で 48kHz が既定になります。ここに 24kHz の生 PCM を「48kHz のつもり」で書き込むと、今度は 2 倍速になります。再生レートは「データが本来持つレート」と「再生する側が解釈するレート」を一致させない限り合いません。

直し方その1:ブラウザ(Web Audio API)

受信した 24kHz の PCM を、必ず 24,000 を指定した AudioBuffer に載せます。Live API は int16 の PCM を base64 で返すので、Float32(-1.0〜1.0)へ正しく変換する点も忘れがちです。

// 出力は 24kHz と決め打ちで AudioContext を作る
const playbackCtx = new AudioContext({ sampleRate: 24000 });
 
// base64 の 16bit PCM(little-endian) → Float32 に変換して再生する
function playPcm24k(base64Pcm) {
  const bytes = Uint8Array.from(atob(base64Pcm), (c) => c.charCodeAt(0));
  const int16 = new Int16Array(bytes.buffer);
 
  const float32 = new Float32Array(int16.length);
  for (let i = 0; i < int16.length; i++) {
    float32[i] = int16[i] / 32768; // 16bit を -1.0〜1.0 に正規化
  }
 
  // チャンネル数1・サンプルレート24000 で AudioBuffer を作る
  const buffer = playbackCtx.createBuffer(1, float32.length, 24000);
  buffer.copyToChannel(float32, 0);
 
  const source = playbackCtx.createBufferSource();
  source.buffer = buffer;
  source.connect(playbackCtx.destination);
  source.start();
}

AudioContext({ sampleRate: 24000 })createBuffer(1, length, 24000) の両方を 24000 で揃えるのがポイントです。片方だけ直しても、ブラウザが内部で勝手にリサンプルして微妙にズレることがあります。

ただし連続再生では source.start() を呼びっぱなしにすると音が重なります。実際のアプリでは、次のチャンクの開始時刻を積み上げて隙間なくつなぐキューにします。

let nextStartTime = 0;
 
function enqueuePcm24k(base64Pcm) {
  const bytes = Uint8Array.from(atob(base64Pcm), (c) => c.charCodeAt(0));
  const int16 = new Int16Array(bytes.buffer);
  const float32 = new Float32Array(int16.length);
  for (let i = 0; i < int16.length; i++) float32[i] = int16[i] / 32768;
 
  const buffer = playbackCtx.createBuffer(1, float32.length, 24000);
  buffer.copyToChannel(float32, 0);
 
  const source = playbackCtx.createBufferSource();
  source.buffer = buffer;
  source.connect(playbackCtx.destination);
 
  const now = playbackCtx.currentTime;
  const startAt = Math.max(now, nextStartTime);
  source.start(startAt);
  nextStartTime = startAt + buffer.duration; // 次チャンクは現チャンクの直後から
}

これでプチプチ途切れる症状も同時に解消します。途切れは多くの場合、チャンクごとに start() をその場で呼んでいて再生の隙間が空くことが原因だからです。

直し方その2:iOS(AVAudioEngine)

ネイティブでも考え方は同じです。AVAudioPlayerNode に流すフォーマットを 24,000 Hz で宣言します。ここを録音側の 16,000 Hz のまま使い回すと、やはり速回しになります。

import AVFoundation
 
let engine = AVAudioEngine()
let player = AVAudioPlayerNode()
 
// 出力は 24kHz・モノラル・Float32 で宣言する
let outputFormat = AVAudioFormat(
    commonFormat: .pcmFormatFloat32,
    sampleRate: 24000,
    channels: 1,
    interleaved: false
)!
 
func setupPlayer() {
    engine.attach(player)
    engine.connect(player, to: engine.mainMixerNode, format: outputFormat)
    try? engine.start()
    player.play()
}
 
// Live API から受け取った 16bit PCM(Data) を再生
func play(pcm16: Data) {
    let sampleCount = pcm16.count / 2
    guard let pcmBuffer = AVAudioPCMBuffer(
        pcmFormat: outputFormat, frameCapacity: AVAudioFrameCount(sampleCount)
    ) else { return }
    pcmBuffer.frameLength = AVAudioFrameCount(sampleCount)
 
    pcm16.withUnsafeBytes { raw in
        let int16Ptr = raw.bindMemory(to: Int16.self)
        let dst = pcmBuffer.floatChannelData![0]
        for i in 0..<sampleCount {
            dst[i] = Float(int16Ptr[i]) / 32768.0 // Int16 → Float32
        }
    }
    player.scheduleBuffer(pcmBuffer)
}

scheduleBuffer は内部で順番に再生してくれるので、ブラウザのような開始時刻の管理は不要です。再生フォーマットを 24,000 で宣言しさえすれば、速回しは止まります。

それでも直らないときの切り分け

レートを揃えても直らない場合は、次の 2 つを順に疑います。

ひとつは PCM の解釈です。int16float32 に変換せず、バイト列をそのまま Float32Array として読むと、まったく別の数値になりノイズだらけになります。バイト数(2 バイト = 1 サンプル)とエンディアン(リトルエンディアン)を確認してください。

もうひとつはチャンネル数です。Live API の出力はモノラル(1ch)です。ステレオ(2ch)として読むと、左右に半分ずつ振り分けられて速さもピッチも崩れます。createBufferAVAudioFormatchannels を 1 にしているか確認します。

同じ轍を踏まないために

AdMob で収益を立てる無料アプリを長く運用してきましたが、音声まわりは「数字を 1 つ取り違えるだけで全部おかしくなる」領域だと改めて感じました。広告と違ってユーザーの耳に直接届くので、速回しの音は一発で「壊れている」と伝わってしまいます。再発を防ぐために、コードのコメントではなく定数名に意味を込めるようにしています。

const GEMINI_INPUT_RATE = 16000;  // マイク → モデル
const GEMINI_OUTPUT_RATE = 24000; // モデル → スピーカー

1600024000 を直接書かず、この 2 つの定数を入口で 1 度だけ定義しておくと、送信用のレートを再生に流用する事故がほぼ起きなくなります。

まず手元のコードで、再生に使っているサンプルレートが 24000 になっているかどうかを 1 箇所確認してみてください。多くの場合、直すのはその 1 行です。

シェア

お読みいただきありがとうございます

Gemini Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥2,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

API / SDK2026-05-15
Gemini API Embedding で詰まった3か所 — 壁紙アプリのカテゴリ自動分類で踏んだエラーと対処
Gemini API の Embedding をカテゴリ自動分類に組み込んで踏んだ INVALID_ARGUMENT・429・精度不足の3点を、次元の固定・切り詰め後の正規化・チェックポイント付きバッチ・recall@5 の計測台という形で整理した記録です。
API / SDK2026-08-27
temperature が効いているかを測るより渡した値を記録する方が先です
非推奨になったサンプリングパラメータは 200 が返るまま無視されます。grep と AST が取りこぼした箇所を実行時に拾い、設定の組み立て元まで記録して CI の合否に変えるまでを、実際に動かした結果とともにまとめます。
API / SDK2026-08-16
Gemini 3.7 Flash の値上げ分は、バッチ層へ移すとちょうど相殺されます
Gemini 3.7 Flash の導入価格は2026年12月31日で終わり、翌日から単価が倍になります。値上げの対象になるモデルとならないモデルを価格表で切り分け、自分の使用量から年明けの請求を先に出すところまでを整理しました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →