GEMINI LABEN
NANOLITE — Nano Banana 2 Liteが登場しました。Googleで最も速く、最もコスト効率の高いGemini Imageモデルで、軽量な画像生成を安く回したい用途に向いていますOMNIFLASH — Gemini Omni Flashがpublic previewになりました。ネイティブにマルチモーダルなモデルで、企業や開発者が独自の動的な動画ワークフローを構築できますAGENTS — Managed Agentsが拡張されました。background: trueでサーバー側の非同期実行とポーリング、リモートMCPサーバー連携、対話をまたぐ認証情報のリフレッシュに対応しますMEMORY — Memory BankのIngestEvents APIが一般提供になりました。イベントの取り込みとメモリ生成を分離し、コンテンツを継続的にストリームできますTHROUGHPUT — Provisioned Throughputで、同一モデル・同一リージョンに対して最大7件の保留オーダーを提出できるようになりましたDEPRECATE — 画像生成モデルは8月17日に、Gemini Enterprise Agent PlatformのGrok 4.1系は8月20日に停止される予定ですNANOLITE — Nano Banana 2 Liteが登場しました。Googleで最も速く、最もコスト効率の高いGemini Imageモデルで、軽量な画像生成を安く回したい用途に向いていますOMNIFLASH — Gemini Omni Flashがpublic previewになりました。ネイティブにマルチモーダルなモデルで、企業や開発者が独自の動的な動画ワークフローを構築できますAGENTS — Managed Agentsが拡張されました。background: trueでサーバー側の非同期実行とポーリング、リモートMCPサーバー連携、対話をまたぐ認証情報のリフレッシュに対応しますMEMORY — Memory BankのIngestEvents APIが一般提供になりました。イベントの取り込みとメモリ生成を分離し、コンテンツを継続的にストリームできますTHROUGHPUT — Provisioned Throughputで、同一モデル・同一リージョンに対して最大7件の保留オーダーを提出できるようになりましたDEPRECATE — 画像生成モデルは8月17日に、Gemini Enterprise Agent PlatformのGrok 4.1系は8月20日に停止される予定です
記事一覧/API / SDK
API / SDK/2026-06-03初級

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

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

Gemini API191Live API音声サンプルレート個人開発91トラブルシューティング30

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/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

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

関連記事

API / SDK2026-05-15
Gemini API Embedding で踏んだ3つの地雷 — 壁紙アプリのカテゴリ自動分類で遭遇したエラーと対処法
Gemini API Embedding を壁紙アプリのカテゴリ自動分類に組み込んだ際に踏んだ3つのエラー(INVALID_ARGUMENT・RESOURCE_EXHAUSTED 429・RAG精度低下)と実際の修正コードを紹介します。
API / SDK2026-07-19
静止画か短い動画か — Nano Banana 2 Lite と Omni Flash のコスト桁差で機能の置き所を決める
壁紙アプリの目玉アセットを静止画にするか短い動画にするかで手が止まったとき、決め手になったのは好みではなくコストの桁でした。Nano Banana 2 Lite と Omni Flash の単価を同じ土俵に並べ、動く意思決定関数まで落とし込みます。
API / SDK2026-07-16
Gemini にスクリーンショットを採点させたら、全部78〜85点だった — 絶対スコアを捨ててペア比較に切り替えるまで
Gemini Vision に App Store スクリーンショットを100点満点で採点させると、良い案も崩した案も78〜85点に潰れます。弁別能を実測して絶対スコアを捨て、位置バイアスを除いたペア比較でランキングを作るまでの実装記録です。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →