GEMINI LABEN
API — Gemini APIのスループットが毎分160億トークン超となり、OpenAIとほぼ肩を並べる規模になりましたENTERPRISE — Gemini Enterpriseの有料シートが800万を超え、導入企業は2,800社超に拡大していますAGENT — Claude Opus 4.8がGemini Enterprise Agent Platformで利用可能になり、マルチベンダー化が進んでいますSPEECH — gemini-3.1-flash-tts-previewがstreamGenerateContentでストリーミング音声生成に対応しましたDATA — CrossbeamデータストアをGemini Enterpriseへ接続できる公開プレビューが始まりましたMODEL — Gemini 3.5 FlashのGAとGemma 4の提供で、エージェント用途と軽量用途の選択肢が揃いましたAPI — Gemini APIのスループットが毎分160億トークン超となり、OpenAIとほぼ肩を並べる規模になりましたENTERPRISE — Gemini Enterpriseの有料シートが800万を超え、導入企業は2,800社超に拡大していますAGENT — Claude Opus 4.8がGemini Enterprise Agent Platformで利用可能になり、マルチベンダー化が進んでいますSPEECH — gemini-3.1-flash-tts-previewがstreamGenerateContentでストリーミング音声生成に対応しましたDATA — CrossbeamデータストアをGemini Enterpriseへ接続できる公開プレビューが始まりましたMODEL — Gemini 3.5 FlashのGAとGemma 4の提供で、エージェント用途と軽量用途の選択肢が揃いました
記事一覧/開発ツール
開発ツール/2026-07-03上級

音声が全部できるまで待たせない — Gemini TTS ストリーミング生成を配信経路に組み込む設計

gemini-3.1-flash-tts-preview が streamGenerateContent に対応しました。最初の音が出るまで実測1.8秒の配信経路を、PCM境界処理・文単位の再開・preview停止フォールバックまで含めて設計します。

Gemini API163TTS2ストリーミング9音声生成3FastAPI

プレミアム記事

個人開発で運営している Dolice Labs のサイト群で、記事を音声でも聴けるようにする実験を続けております。

これまでのボトルネックは生成そのものではなく「待ち時間」でした。3,800字ほどの原稿をバッチの TTS に渡すと、音声ファイルが完成するまで平均41秒。ポッドキャストのように作り置きする用途なら問題になりませんが、ページ上の「読み上げボタン」を押した読者を41秒待たせるのは現実的ではありません。

2026年7月の変更で、この前提が変わりました。gemini-3.1-flash-tts-preview が streamGenerateContent 経由のストリーミング音声生成に対応し、音声を「作り終えてから配る」のではなく「作りながら配る」経路を組めるようになりました(Gemini API changelog)。

実際に配信経路を作り直してみると、SDK の呼び出し自体は簡単な一方で、その先の「配り方」にいくつも設計判断が必要でした。ここからは、その過程で確定した構成をコードと実測値でまとめておきます。

「作ってから配る」と「作りながら配る」で何が変わるか

同じ TTS でも、バッチとストリーミングでは設計の重心が別物です。最初にここを整理しておくと、後の判断がぶれません。

観点バッチ生成(作ってから配る)ストリーミング生成(作りながら配る)
最初の音までの時間原稿全体の生成完了まで(実測41秒/3,800字)先頭チャンク到着まで(実測1.8秒)
成果物完成したファイル(WAV/MP3)PCM チャンクの列。ファイルは後から組み立てる
失敗時の意味論最初から再生成すればよい(冪等)途中まで再生済み。どこから再開するかの設計が必要
向く用途ポッドキャスト・動画ナレーション・作り置き読み上げボタン・対話 UI・その場で聴かせる導線

私の結論を先に書くと、アーカイブ用の音声はバッチのまま残し、「その場で聴かせる」導線だけをストリーミングに切り替えました。両方をストリーミングに寄せる必要はありません。

受け口 — streamGenerateContent から PCM チャンクを取り出す

サーバー側の受け口はこれだけです。ポイントは、チャンクから取り出せるのが 24kHz・16bit・モノラルの生 PCM だという点です。

# tts_stream.py — ストリーミング TTS の受け口
from google import genai
from google.genai import types
 
client = genai.Client()  # GEMINI_API_KEY を環境変数から読み込みます
 
TTS_MODEL = "gemini-3.1-flash-tts-preview"  # 後述の理由で必ず設定に外出しします
 
def stream_tts(text: str):
    """テキストを音声チャンク(24kHz 16bit mono PCM)のジェネレータに変換します。"""
    stream = client.models.generate_content_stream(
        model=TTS_MODEL,
        contents=text,
        config=types.GenerateContentConfig(
            response_modalities=["AUDIO"],
            speech_config=types.SpeechConfig(
                voice_config=types.VoiceConfig(
                    prebuilt_voice_config=types.PrebuiltVoiceConfig(
                        voice_name="Kore"
                    )
                )
            ),
        ),
    )
    for chunk in stream:
        if not chunk.candidates:
            continue
        part = chunk.candidates[0].content.parts[0]
        if part.inline_data and part.inline_data.data:
            yield part.inline_data.data

これを HTTP に載せます。FastAPI ならチャンク転送そのままです。

# server.py — チャンク転送で配る
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from tts_stream import stream_tts
 
app = FastAPI()
 
@app.get("/tts")
def tts(text: str):
    return StreamingResponse(
        stream_tts(text),
        media_type="audio/L16;rate=24000;channels=1",
        headers={"Cache-Control": "no-store"},
    )

Content-Type を audio/L16 にしているのは意図的です。ここで「なぜ普通に WAV で返さないのか」という問題に突き当たります。

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

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
streamGenerateContent の音声チャンクを FastAPI からチャンク転送で配る最小構成と、最初の音が出るまで実測1.8秒(バッチ比 約23分の1)のレイテンシ設計
長さ未確定のまま WAV を配れない問題への3つの選択肢の比較と、生PCM+クライアント再生に落ち着いた判断根拠(Int16境界の持ち越しバッファ実装つき)
途中切断をバイト位置ではなく文境界で再開する chunk map 設計と、preview モデル停止に備えたバッチへの自動フォールバック
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

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

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

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

関連記事

開発ツール2026-07-02
元データを消しても生成物は残る — Gemini 派生データに削除を伝播させる台帳設計
ユーザーが元データを削除しても、Gemini で生成した埋め込みやキャッシュにはテキストが残り続けます。生成時に由来を記録する provenance 台帳、シンク別の削除伝播ワーカー、取りこぼしを数える検証スイープまで、削除を派生データに追随させる設計をまとめました。
開発ツール2026-07-02
url_context が取得に失敗しても答えは返ってくる — 取得ステータスを検証してから通すゲート設計
url_context ツールは対象URLの取得に失敗しても、それらしい回答を返します。url_context_metadata の取得ステータスを読み、根拠URLが実際に読めた時だけ答えを確定する検証ゲートと、失敗時のフォールバックを実装します。
開発ツール2026-06-20
Geminiの工程別モデル割り当て — 下書きはFlash、仕上げは上位ティアで回す自動運用の組み方
Gemini 3.5 Flash の一般提供と 3.1 Flash-Lite の展開を機に、自動運用パイプラインの工程ごとにモデルを割り当て直した記録です。下書き・分類・仕上げの3段に分けるルーターの実装と、コストの見え方の変化、運用で決めた歯止めのルールを紹介します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →