# Gemini 3.5 Transcribe API 指南：预录转写、实时字幕与验证方法

> 已有录音用 gemini-3.5-transcribe，实时音频用 gemini-3.5-transcribe-live。预录的词表与说话人、逐词时间戳不能同时开启；实时字幕要分别处理临时和最终结果，并在音频结束后限时收尾。

- URL: https://blog.laozhang.ai/zh/posts/gemini-3-5-transcribe-api
- Published: 2026-08-27
- Updated: 2026-10-07
- Author: LaoZhang AI Team (https://blog.laozhang.ai/zh/about)
- Topic: API 接入指南
- Tags: Gemini 3.5 Transcribe, Gemini API, 语音转文字, Live API, 音频转录

---
**已有录音文件用 `gemini-3.5-transcribe`，实时字幕用 `gemini-3.5-transcribe-live`。** 预录路线先上传文件，再通过 Interactions API 转写；实时路线通过 Live API 发送原始 PCM 音频，持续接收文字。两者的输入格式、参数写法和输出结构不同，不能只替换模型名。

接入时最容易踩的坑是把所有增强功能一起打开：预录的 `custom_vocabulary` 不能与说话人标注或逐词时间戳同时使用，`smart` 也不支持这两类结构化标注。需要可回跳、可区分说话人的录音记录，就选择不带词表的 `verbatim` 结构化分支；只需要更易读的文稿，则可以选择 smart 加精简词表。这些组合以当前 [模型说明](https://ai.google.dev/gemini-api/docs/models/gemini-3.5-transcribe)和[转写接口指南](https://ai.google.dev/gemini-api/docs/transcribe)为准。

## 先按录音文件或实时字幕选接口

| 读者要完成的任务 | 模型与接口 | 直接影响接入的条件 |
|---|---|---|
| 把采访、播客或客服录音转成文本 | `gemini-3.5-transcribe`；Files + Interactions API | 普通预录最长 1 小时 |
| 给录音添加说话人和逐词时间戳 | 同一预录模型，选择 verbatim 结构化配置 | 最长 30 分钟；不能同时加自定义词表 |
| 边说边显示字幕或语音输入 | `gemini-3.5-transcribe-live`；Live API | 单 session 最长 10 分钟；没有说话人标注和逐词时间戳 |
| 转写后生成纪要或调用业务工具 | 先完成转写，再交给合适的文本模型 | Transcribe 本身不支持 function calling、thinking 或 Search grounding |

时长、能力与参数限制来自 [Gemini 3.5 Transcribe 模型页](https://ai.google.dev/gemini-api/docs/models/gemini-3.5-transcribe)。实时字幕与语音助手是不同任务：这里的 Live Transcribe 返回文字，不承担与用户对话并生成语音的完整流程。

![预录 Interactions 与实时 Live 的接口分工、字幕状态和合法配置分支示意。](https://blog.laozhang.ai/posts/zh/gemini-3-5-transcribe-api/img/route-decision-map.webp)

不要为状态标签改写模型 ID。核对至 2026 年 10 月 6 日，Google 的[更新日志](https://ai.google.dev/gemini-api/docs/changelog#august-26-2026)把 8 月 26 日的两个模型标为 GA，而同日的[发布文章](https://blog.google/innovation-and-ai/models-and-research/gemini-models/gemini-3-5-transcribe/)仍保留 public preview 表述。这两处官方措辞尚不一致，也不能据此保证每个账户、项目和地区都已开放。本文采用当前 Developer API 文档中的精确模型名和请求契约。

## 先跑通预录音频转写

以下是面向读者的 Python 接入示例，前提是运行环境已提供 `google-genai` SDK、获准使用的后端 API 凭据，以及你有权提交的本地音频。本次没有执行 SDK 导入、文件上传或模型调用；示例依据当前 [Audio transcription 指南](https://ai.google.dev/gemini-api/docs/transcribe)编写，不能当作真实调用成功记录。

把文件名作为参数传入，先使用最少字段建立基线。保存为 `recorded.py` 后，例如以 `python recorded.py sample.mp3 plain` 调用；另外两个 profile 见下一节。

```python
# recorded.py
import argparse
from google import genai

PROFILES = {
    "plain": {},
    "readable": {
        "mode": "smart",
        "language_codes": ["cmn-Hans-CN"],
        "custom_vocabulary": ["Gemini", "BigQuery", "LaoZhang AI"],
    },
    "annotated": {
        "mode": {
            "type": "verbatim",
            "diarization_mode": "speaker",
            "timestamp_granularities": ["word"],
        },
    },
}


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("audio_path")
    parser.add_argument("profile", choices=PROFILES, default="plain", nargs="?")
    args = parser.parse_args()
    client = genai.Client()
    audio = client.files.upload(file=args.audio_path)
    options = {}
    if PROFILES[args.profile]:
        options["generation_config"] = {
            "transcription_config": PROFILES[args.profile]
        }
    result = client.interactions.create(
        model="gemini-3.5-transcribe",
        input=[{"type": "audio", "uri": audio.uri,
                "mime_type": audio.mime_type}],
        **options,
    )
    # 保留完整响应，随后可解析 steps 中的词级标注。
    with open("interaction.json", "w", encoding="utf-8") as output:
        output.write(result.model_dump_json(exclude_none=True))
    print("status:", result.status)
    print(result.output_text)


if __name__ == "__main__":
    main()
```

这里使用 Files API 返回的 `uri` 和实际 `mime_type`，输入是平铺的 audio 对象，不套通用聊天的 role/content 格式，也不改为普通 `generateContent`。用 REST 实现时，对应的是 `POST https://generativelanguage.googleapis.com/v1beta/interactions`；认证与字段要按该接口文档处理，不能照搬一个“OpenAI 兼容音频接口”。

成功判断要分开看：上传返回文件信息、interaction 状态完成、输出文本存在，以及听录音后确认文本对应输入。只有前三项通过，仍不能证明姓名、数字或说话人归属正确。保留原音频、完整响应和应用记录，便于复查。

### smart 与 verbatim 怎样组合才合法

| profile | 用途 | 当前配置 |
|---|---|---|
| `plain` | 先验证基本转写，再决定要加什么 | 不传可选配置，使用默认 verbatim |
| `readable` | 文本清洗、去口头填充词、整理口误和数字格式 | `mode: "smart"`，可加词表；不加 speaker/word |
| `annotated` | 保留说话人和逐词时间，便于定位录音 | `mode: {"type": "verbatim", ...}`；不加词表 |

预录 **smart 是字符串 `"smart"`，不是 `{"type": "smart"}`**。需要说话人或时间戳时才采用上面 verbatim 的对象形式。`custom_vocabulary` 与 `diarization_mode`、`timestamp_granularities` 不兼容；把词表塞进 annotated 分支会违反当前契约，不能靠提示词解除。[配置说明](https://ai.google.dev/gemini-api/docs/transcribe)

简体普通话的语言提示是 `cmn-Hans-CN`。中英混说或语言不确定时，可以不传 `language_codes`，或传空数组自动识别。词表最多 1,000 项，模型页建议通常控制在 100 项以内；应放可能被误听的专有名词，而不是整本行业词典。Smart 的文本更便于阅读，但对填充词、重复和自我纠正的整理不能替代逐字录音记录。[语言与词表限制](https://ai.google.dev/gemini-api/docs/models/gemini-3.5-transcribe)

## 不只保存 output_text：解析说话人和逐词时间戳

`output_text` 是合并文本。结构化信息位于 `steps[].content[].annotations[]`，其中 `type == "word_info"` 的标注包含 `text`、`speaker`、`start_offset` 和 `end_offset`。时间偏移是类似 `"0.100s"` 的 Duration 字符串，不是可以直接相加的浮点秒数。[响应结构与示例](https://ai.google.dev/gemini-api/docs/transcribe)

下面的独立脚本读取上一节保存的 `interaction.json`，输出每个词及相对当前输入文件的秒数。它保留缺失字段，也保留合法的零秒偏移；没有标注时输出空数组，不从纯文本编造说话人或时间。

```python
# word_rows.py
import json
import re
import sys
from decimal import Decimal


def seconds(value):
    if value is None:
        return None
    if not isinstance(value, str) or not re.fullmatch(r"\d+(?:\.\d+)?s", value):
        raise ValueError(f"非法时间偏移: {value!r}")
    return Decimal(value[:-1])


def word_rows(response):
    rows = []
    for step in response.get("steps") or []:
        for content in step.get("content") or []:
            for item in content.get("annotations") or []:
                if item.get("type") != "word_info":
                    continue
                start = seconds(item.get("start_offset"))
                end = seconds(item.get("end_offset"))
                if start is not None and end is not None and end < start:
                    raise ValueError("词级结束时间早于开始时间")
                rows.append({
                    "text": item.get("text"),
                    "speaker": item.get("speaker"),
                    "start_seconds": str(start) if start is not None else None,
                    "end_seconds": str(end) if end is not None else None,
                })
    return rows


if __name__ == "__main__":
    with open(sys.argv[1], encoding="utf-8") as source:
        print(json.dumps(word_rows(json.load(source)), ensure_ascii=False, indent=2))
```

以 `python word_rows.py interaction.json` 查看结果后，再决定字幕断句、每屏字数和重叠显示规则；逐词标注本身不是现成 SRT。长录音如果先切片，每片时间偏移属于该片，拼接时要加上应用保存的片段起点。说话人标签也不能仅凭相同名称跨片合并成同一个人。

说话人能力页面列出最多 8 人，但明确 **3 位及以上属于 experimental**，不是稳定的八人会议识别承诺；逐词时间戳还可能降低转写准确率。开启任一种结构标注后，输入最长 30 分钟。先用本业务样本检查归属和回跳位置，再决定是否满足上线要求。[当前模型边界](https://ai.google.dev/gemini-api/docs/models/gemini-3.5-transcribe)

## 实时字幕要处理的是流，不是文件

Live Transcribe 需要 raw signed 16-bit、16 kHz、单声道、little-endian PCM。100 ms 对应 1,600 个采样、3,200 字节。WAV 带容器头，WebM/Opus 是另一种编码，8 kHz 电话音频也需要转换；只把 MIME 改成 `audio/pcm;rate=16000` 不会完成解码和重采样。[实时转写指南](https://ai.google.dev/gemini-api/docs/live-api/live-transcribe)

下例读取已经转换好的 `.pcm` 文件，按约 100 ms 节奏发送，用于解释真实媒体管线接入前的客户端流程。它不是麦克风采集器，也不含转码步骤。若使用 `mode`，Live 配置的枚举是 `"SMART"` 或 `"VERBATIM"`，与预录字符串写法不同；本例选 `VERBATIM`，并使用默认自动 VAD。

收到 `interim_input_transcription` 时覆盖当前预览，收到 `input_transcription` 时追加最终片段并清空预览。一个事件可能同时带两者，也可能没有 `server_content`。不要对最终文本做全局去重：连续两次“好的”可能是两段真实发言。

```python
# live_pcm.py
import argparse
import asyncio
from pathlib import Path
from google import genai
from google.genai import types


class Captions:
    def __init__(self):
        self.preview = ""
        self.final = []

    def apply(self, interim=None, final=None):
        if interim is not None:
            self.preview = interim
        if final is not None:
            self.final.append(final)
            self.preview = ""


async def receive(session, captions):
    # receive() 可按轮次结束；继续接收后续轮次。
    while True:
        received = False
        async for event in session.receive():
            received = True
            content = event.server_content
            if content is None:
                continue
            interim = content.interim_input_transcription
            final = content.input_transcription
            captions.apply(
                interim.text if interim is not None else None,
                final.text if final is not None else None,
            )
            print({"final": captions.final, "preview": captions.preview})
        if not received:
            return "receive_ended"


async def main(pcm_path):
    pcm = Path(pcm_path).read_bytes()
    if not pcm or len(pcm) % 2:
        raise ValueError("PCM 不能为空，字节数必须是 16 位采样的整数倍")
    if pcm[:4] in (b"RIFF", b"OggS") or pcm[:4] == b"\x1aE\xdf\xa3":
        raise ValueError("输入含容器头；先解码为 raw PCM")
    # 9 分钟是本例的应用上限，给发送和收尾留余量。
    if len(pcm) > 16000 * 2 * 540:
        raise ValueError("本例只处理不超过 9 分钟的输入")
    captions = Captions()
    status = "partial"
    client = genai.Client()
    config = types.LiveConnectConfig(
        response_modalities=["TEXT"],
        input_audio_transcription=types.AudioTranscriptionConfig(
            mode="VERBATIM", language_codes=[],
        ),
    )
    async with client.aio.live.connect(
        model="gemini-3.5-transcribe-live", config=config,
    ) as session:
        receiver = asyncio.create_task(receive(session, captions))
        try:
            loop = asyncio.get_running_loop()
            started = loop.time()
            for offset in range(0, len(pcm), 3200):
                if receiver.done():
                    status = "receiver_stopped: " + str(await receiver)
                    break
                await session.send_realtime_input(audio=types.Blob(
                    data=pcm[offset:offset + 3200],
                    mime_type="audio/pcm;rate=16000",
                ))
                due = started + (offset + 3200) / 32000
                await asyncio.sleep(max(0, due - loop.time()))
            else:
                await session.send_realtime_input(audio_stream_end=True)
                try:
                    status = await asyncio.wait_for(receiver, timeout=5)
                except asyncio.TimeoutError:
                    status = "drain_timeout_partial"
        except Exception as exc:
            status = "error_partial: " + type(exc).__name__
        finally:
            if not receiver.done():
                receiver.cancel()
            try:
                await receiver
            except (asyncio.CancelledError, Exception):
                pass
            # 连接退出后仍可检查已提交片段、未定稿预览和终止原因。
            print({"status": status, "final": captions.final,
                   "pending_preview": captions.preview})


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("pcm_path")
    asyncio.run(main(parser.parse_args().pcm_path))
```

以 `python live_pcm.py sample.pcm` 运行时，发送的内容必须确实满足 PCM 格式；头部和字节数检查只会拦截部分错误，不能证明任意文件都是 16 kHz 单声道。示例在音频 EOF 后发送 `audio_stream_end=True`，最多等待 5 秒，然后取消接收任务并关闭连接。这个应用超时不代表服务端承诺的 final 延迟；若仍有预览或等待超时，应把记录标为部分结果，保留已完成片段，不能宣告整段转写完成。

默认自动 VAD 与手动活动边界是不同配置。需要手动方案时，按官方指南关闭自动检测再发送 activity start/end，不能把所有结束信号随意叠加。本例只展示有限长度文件的流式发送与收尾；生产产品还要记录应用自己的 session 编号、片段顺序和断线位置。它们是应用字段，不是服务端返回的词级时间戳，也不保证跨重连恰好处理一次。超过模型的 10 分钟 session 边界时，需要明确轮换和回放策略。[VAD 与连接流程](https://ai.google.dev/gemini-api/docs/live-api/live-transcribe)

### 浏览器直连用短期令牌，不能公开长期 API key

上例是后端 SDK 流程。浏览器直连应由可信后端验证客户端，再发放约束模型和配置的短期令牌。当前 [ephemeral token 文档](https://ai.google.dev/gemini-api/docs/live-api/ephemeral-tokens)限定为 Live API 的 `v1beta`：默认 1 分钟内启动新 session、30 分钟连接消息有效期、使用次数为 1。短期令牌仍可被提取，只是缩短暴露范围。

**令牌的 30 分钟有效期不会把 Transcribe 的单 session 上限从 10 分钟延长到 30 分钟。** 后端还需约束令牌用途与允许的配置，避免把长期 key 放进前端 bundle 或长期可复用的连接地址。这里没有发放令牌，也没有验证浏览器、麦克风或重连链路。

## 成本很低，但不要忽略输出和重试

截至 2026 年 10 月 6 日，以下是 [Gemini Developer API 定价页](https://ai.google.dev/gemini-api/docs/pricing#gemini-3.5-transcribe)的付费标准价，单位为美元／100 万 token，分别计算音频输入与文本输出：

| 路线 | 音频输入 | 文本输出 | 官方舍入后的每分钟估算 |
|---|---:|---:|---:|
| 预录 Transcribe | $2 | $12 | 约 $0.005 |
| Live Transcribe | $3.50 | $21 | 约 $0.009 |

每分钟数字采用约每秒 25 个音频 token、每分钟 175 个文本 token 的假设，**不是固定分钟套餐**。预录每分钟未舍入的计算为 `1500 × 2 / 1000000 + 175 × 12 / 1000000 = $0.0051`；实时为 `$0.008925`。在这组假设下，100 小时即 6,000 分钟分别约 `$30.60` 和 `$53.55`。若直接乘官方舍入数字，则会得到约 `$30` 和 `$54`；差异来自舍入，不是另一档费率。

真实调用费用应按实际用量计算：`音频输入 token × 输入单价 / 1000000 + 文本输出 token × 输出单价 / 1000000`。再单列重试、存储、转码、媒体服务器、下游纪要模型和人工抽检成本。这里没有真实账单或付费调用，也不把通用 Gemini 音频的 token 速率用于这个专用模型的估算。

### 免费调用与录音数据怎样处理

定价页列出免费层，但免费额度、可用地区和录音可否提交是不同问题。[Gemini API 条款](https://ai.google.dev/gemini-api/terms)通常允许将未付费服务的输入、输出用于改进产品，并可能有人审阅，要求不要提交敏感、机密或个人信息。对 API，启用有效 Cloud Billing 的项目适用付费服务数据规则；输入、输出不用于改进产品，仍有有限的滥用、安全和法律处理及日志范围。

条款对 EEA、瑞士和英国另有数据使用例外，并要求面向这些地区用户的 API 客户端使用付费服务。不能根据文章语言判断地区资格，也不能把定价表的 Yes/No 简化成“免费必训练、付费零保留”或合规保证。客户通话、内部会议等录音需要先确认组织政策、授权和所用项目的实际条款。

Files API 的存储生命周期也要单独处理：单文件最多 2 GB、项目最多 20 GB，上传文件自动在 48 小时后删除。Files 服务本身免费不等于转写推理免费；48 小时文件删除也不代表 interaction、输出和服务日志都只保留 48 小时。上传文件不能从该 API 下载回来，应自行保留获准存储的原始音频。[Files API 说明](https://ai.google.dev/gemini-api/docs/files)

## 用自己的金标音频验收，而不是只看 WER 新闻

应用上线需要分别验收基础调用、录音质量和字幕状态。先取得人工参考文本，覆盖实际会遇到的口音、中英混说、噪声、电话编码、多人交叠与专有名词。对姓名、订单号、金额等高代价字段单独统计错误，不能让整体准确率掩盖它们。

- 文稿清洗：比较 plain 与 readable，检查口误修正有没有改变原意。
- 结构化录音：检查说话人归属，点击词级时间能否回到对应位置，并保留原片段起点。
- 实时字幕：测最终字幕延迟、预览替换、真实重复发言、断线和 EOF 后的部分结果。
- 费用：记录实际输入、输出用量及重试，与预算假设对照。

本次离线检查只覆盖公开示例的 Python 语法、三个配置分支、合成 `word_info` 数据的 Duration 解析、字幕临时／最终状态、合成 PCM 字节数和费用算术。它没有执行 SDK 或模型、听取私人录音、测麦克风、评估 WER、延迟或生产并发，不能证明服务接受了请求或识别质量达标。

![合法参数、结构化响应、PCM 音频规格和实时字幕收尾的接入核对示意。](https://blog.laozhang.ai/posts/zh/gemini-3-5-transcribe-api/img/integration-checklist.webp)

## 常见接入错误与停止判断

### 请求成功，但没有说话人或时间戳

先确认使用预录模型以及 annotated 分支，不带 `custom_vocabulary`；再查看完整响应的 `steps[].content[].annotations[]`，而不是只读 `output_text`。若字段确实缺失，应保存缺失状态并检查服务响应，不能从文本补造 speaker 或时间。Live 模型本身不提供这两类结构。[预录指南](https://ai.google.dev/gemini-api/docs/transcribe)

### 开启 smart 后结构化标注消失

Smart 与说话人、逐词时间戳不兼容。需要可定位的录音记录，就用 verbatim 结构化分支；若还要一份易读纪要，在下游另做受控整理，并保留原记录。预录的 smart 参数用小写字符串，Live 的 mode 用大写枚举。[预录配置](https://ai.google.dev/gemini-api/docs/transcribe)、[Live 配置](https://ai.google.dev/gemini-api/docs/live-api/live-transcribe)

### 实时字幕重复、跳字或结束后一直等待

先确认 interim 覆盖预览、final 才追加，并保留合法重复发言；再检查 PCM 解码、采样率、声道、字节序和发送节奏。EOF 要结束音频并限时等待接收，超时保存部分结果。不要用提示词修补音频格式，也不要无限等待一个并不等于关闭连接的 `audio_stream_end`。[实时流程](https://ai.google.dev/gemini-api/docs/live-api/live-transcribe)

### 模型能转写，却不能总结或调用工具

当前原生 Transcribe 不支持 function calling、thinking、Search grounding、file search 或 code execution。先完成并保存 transcript，再交给处理纪要或业务动作的模型。消费端应用的演示与发布时的未来计划不能直接变成这个 API 的能力。[模型功能表](https://ai.google.dev/gemini-api/docs/models/gemini-3.5-transcribe)

如果产品要求实时说话人分离、实时逐词时间戳、不能轮换的超长实时 session，或不能切片的 30 分钟以上结构化录音，应先调整架构或另选 STT 路线。其余场景从 plain 短录音基线开始，选对合法功能分支，再完成真实音频、字幕收尾与实际用量验收，才能判断是否适合生产。

## 参考来源

本文引用的外部页面，按正文出现顺序排列。最后更新于 2026-10-07。

- [模型说明](https://ai.google.dev/gemini-api/docs/models/gemini-3.5-transcribe) (ai.google.dev)
- [转写接口指南](https://ai.google.dev/gemini-api/docs/transcribe) (ai.google.dev)
- [更新日志](https://ai.google.dev/gemini-api/docs/changelog) (ai.google.dev)
- [发布文章](https://blog.google/innovation-and-ai/models-and-research/gemini-models/gemini-3-5-transcribe/) (blog.google)
- [实时转写指南](https://ai.google.dev/gemini-api/docs/live-api/live-transcribe) (ai.google.dev)
- [ephemeral token 文档](https://ai.google.dev/gemini-api/docs/live-api/ephemeral-tokens) (ai.google.dev)
- [Gemini Developer API 定价页](https://ai.google.dev/gemini-api/docs/pricing) (ai.google.dev)
- [Gemini API 条款](https://ai.google.dev/gemini-api/terms) (ai.google.dev)
- [Files API 说明](https://ai.google.dev/gemini-api/docs/files) (ai.google.dev)
