<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>语音技术 on Chico's Tech Blog</title><link>https://realtime-ai.chat/categories/%E8%AF%AD%E9%9F%B3%E6%8A%80%E6%9C%AF/</link><description>Recent content in 语音技术 on Chico's Tech Blog</description><image><title>Chico's Tech Blog</title><url>https://github.com/chicogong.png</url><link>https://github.com/chicogong.png</link></image><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Tue, 19 May 2026 10:00:00 +0800</lastBuildDate><atom:link href="https://realtime-ai.chat/categories/%E8%AF%AD%E9%9F%B3%E6%8A%80%E6%9C%AF/index.xml" rel="self" type="application/rss+xml"/><item><title>实时语音对话的延迟预算：把「AI 慢半拍」拆到毫秒</title><link>https://realtime-ai.chat/posts/voice-latency-budget/</link><pubDate>Tue, 19 May 2026 10:00:00 +0800</pubDate><guid>https://realtime-ai.chat/posts/voice-latency-budget/</guid><description>从用户说完到 AI 出声,500~900ms 花在哪里?逐段拆解语音 Agent 的延迟预算,以及流式、打断、级联与端到端的工程取舍。</description><content:encoded><![CDATA[<p>你跟一个语音 AI 说完话,它停顿了一下,才开口回答。</p>
<p>那一下&quot;停顿&quot;,就是它不像人的地方。</p>
<p>人类对话的轮次间隔(turn-taking gap)中位数只有约 <strong>200ms</strong>,熟悉的人之间还经常&quot;抢话&quot;——下一句在上一句结束前就接上了。一旦 AI 的回应超过 <strong>500ms</strong>,你会明显感觉到&quot;它在想&quot;;超过 <strong>800ms</strong>,对话就开始别扭,你会忍不住重复自己,或者以为它没听见。</p>
<p>所以做语音 Agent,延迟不是&quot;优化项&quot;,是<strong>及格线</strong>。这篇把从【用户说完最后一个字】到【AI 喇叭里出第一个音】之间发生的事,拆到毫秒。</p>
<h2 id="这条流水线长什么样">这条流水线长什么样</h2>
<pre class="mermaid">flowchart LR
  A[用户说话] --> B[轮次判定<br/>VAD + 端点]
  B --> C[ASR<br/>语音转文字]
  C --> D[LLM<br/>首 token]
  D --> E[TTS<br/>首包音频]
  E --> F[AI 出声]
  style B fill:#fde7c2,stroke:#e8b23c
  style D fill:#fde7c2,stroke:#e8b23c
</pre><p>橙色的两块——<strong>轮次判定</strong>和 <strong>LLM 首 token</strong>——是预算里最大的两笔开销,也是最值得花力气的地方。</p>
<h2 id="一份现实的延迟预算">一份现实的延迟预算</h2>
<table>
  <thead>
      <tr>
          <th>环节</th>
          <th>它在做什么</th>
          <th>典型耗时</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>轮次判定(VAD + 端点)</td>
          <td>判断&quot;用户真的说完了&quot;</td>
          <td>50–250 ms</td>
      </tr>
      <tr>
          <td>ASR(语音转文字)</td>
          <td>把语音转成文字</td>
          <td>~0–150 ms*</td>
      </tr>
      <tr>
          <td>LLM 首 token(TTFT)</td>
          <td>想出第一个字</td>
          <td>250–500 ms</td>
      </tr>
      <tr>
          <td>TTS 首包</td>
          <td>合成出第一段音频</td>
          <td>75–200 ms</td>
      </tr>
      <tr>
          <td>网络往返</td>
          <td>客户端 ↔ 服务端</td>
          <td>30–80 ms</td>
      </tr>
      <tr>
          <td><strong>合计(可感知)</strong></td>
          <td></td>
          <td><strong>约 500–900 ms</strong></td>
      </tr>
  </tbody>
</table>
<p>* ASR 与用户说话<strong>并行</strong>进行,大部分转写在用户说完之前就做完了,所以它对预算的<strong>增量</strong>很小——前提是你用的是流式 ASR。</p>
<h2 id="逐段拆开">逐段拆开</h2>
<h3 id="轮次判定延迟里的隐形大头">轮次判定:延迟里的隐形大头</h3>
<p>最容易被忽略、又最肥的一块。</p>
<p>很多系统用一个固定的&quot;静音超时&quot;来判断用户说完了——比如静音持续 700ms 就认为该 AI 说话了。问题是:<strong>这 700ms 是凭空加在每一句话上的延迟</strong>,而且用户说话中间正常的停顿(想词、换气)还会触发误判。</p>
<p>2026 年更好的做法是<strong>语义端点检测</strong>(semantic turn detection):用一个很小的模型实时判断&quot;这句话在语义上说完了没&quot;。&ldquo;我想订一张去——&ldquo;显然没说完,哪怕这里静音了 800ms;&ldquo;我想订一张去北京的机票&quot;说完了,300ms 就可以接。把固定静音超时换成语义判定,这一段常常能从 250ms 砍到 100ms 以内。</p>
<h3 id="asr让它和说话并行就几乎不要钱">ASR:让它和说话并行,就几乎不要钱</h3>
<p>ASR 唯一的纪律是:<strong>必须流式</strong>。流式 ASR 一边听一边转写,用户说完时文字基本也就好了,只剩最后一个分块的尾巴。如果你用的是非流式(&ldquo;等录音结束再整段识别&rdquo;),那等于把整段语音时长又加回了预算——一句 3 秒的话就白白多 3 秒。这是新手最常见的坑。</p>
<h3 id="llm-首-token最大的一块也最难压">LLM 首 token:最大的一块,也最难压</h3>
<p>LLM 拿走了预算里最大的份额,因为它最难在不牺牲回答质量的前提下压缩。</p>
<p>关键认知:你要的是 <strong>TTFT(首 token 延迟)低</strong>,不是&quot;整段生成快&rdquo;。用户只需要尽快听到<strong>第一个字</strong>,后面的边生成边说。能压 TTFT 的手段:</p>
<ul>
<li><strong>缩短 system prompt</strong>——每一个 token 都要参与首次前向计算</li>
<li><strong>prompt caching</strong>——把固定的前缀缓存住,省掉重复的 prefill</li>
<li><strong>小模型兜首句</strong>——用一个快模型先回&quot;嗯&quot;&ldquo;好的,我看一下&rdquo;,大模型在背后接管</li>
<li><strong>投机解码 / 更近的推理节点</strong>——把网络和排队的尾巴削掉</li>
</ul>
<h3 id="tts-首包同样只看第一段">TTS 首包:同样只看&quot;第一段&rdquo;</h3>
<p>和 LLM 一个道理——要流式 TTS,**首包(time-to-first-audio)**出来就播,别等整句合成完。再把 LLM 的输出按标点/句子切块,边出边喂给 TTS。现代流式 TTS 的首包能做到 75–200ms。</p>
<h2 id="流式的魔法感知延迟--真实延迟">流式的魔法:感知延迟 ≠ 真实延迟</h2>
<p>这是整套设计的核心。</p>
<p>一次完整回答的<strong>真实</strong>总耗时可能有 1.5 秒甚至更长。但用户感受到的延迟,只是<strong>到第一个音</strong>的那段时间——因为 LLM 还在生成后半句时,前半句已经在用户耳朵里播了。</p>
<pre class="mermaid">flowchart LR
  L1[LLM: 生成第1句] --> L2[LLM: 生成第2句] --> L3[LLM: 生成第3句]
  L1 -.切块.-> T1[TTS+播放 第1句]
  L2 -.切块.-> T2[TTS+播放 第2句]
  L3 -.切块.-> T3[TTS+播放 第3句]
</pre><p>所以工程上的铁律:<strong>这条链路上任何一环都不能&quot;攒齐再传&rdquo;</strong>。VAD、ASR、LLM、TTS,全部流式串起来,一环阻塞,整条链路的&quot;感知延迟&quot;就塌回成&quot;真实延迟&quot;。</p>
<h2 id="打断barge-in不只是停-tts">打断(barge-in):不只是&quot;停 TTS&quot;</h2>
<p>人会打断。语音 Agent 必须能被打断,而且打断要干净。</p>
<p>用户插话时,要做的<strong>不止</strong>是停掉正在播的音频,而是四件事一起:</p>
<ol>
<li>停止 TTS 音频播放</li>
<li>取消<strong>还在合成</strong>的 TTS 任务</li>
<li>取消<strong>还在生成</strong>的 LLM 请求</li>
<li>重置整条流式管道的状态</li>
</ol>
<p>漏掉任何一步,AI 要么跟你抢着说,要么在你打断之后还要&quot;把上一个念头说完&quot;。</p>
<p>另一个反方向的坑是<strong>误打断</strong>:背景噪音、旁边人说话、你自己的&quot;嗯哼&quot;,都可能被当成插话。解法是带说话人意识的 VAD(personalized VAD),只对&quot;主说话人&quot;的语音触发打断。</p>
<h2 id="级联-vs-端到端2026-年怎么选">级联 vs 端到端:2026 年怎么选</h2>
<ul>
<li><strong>级联</strong>(ASR → LLM → TTS):三个模块各自独立。可控、可调试、每一环都能单独监控和替换。2026 年它仍然是生产环境的默认选择,尤其是电话客服这种强管控、强合规的场景。</li>
<li><strong>端到端语音模型</strong>(speech-to-speech):语音直接进、语音直接出,中间不落文字。延迟更低,情感和韵律保留得更好(信息没有在&quot;转成文字再转回来&quot;的过程中丢失)。代价是难调试、难审计、难合规。</li>
</ul>
<p>我的判断:别把它当成二选一。强管控场景用级联;Web 端的陪伴、互动类产品可以上端到端;成规模的团队最后大概率是<strong>两套都跑,按场景路由</strong>。</p>
<h2 id="最后工程优先级别搞反">最后:工程优先级别搞反</h2>
<p>如果你在做语音 Agent,优化的顺序应该是:</p>
<ol>
<li><strong>先把流式管道打通</strong>——确保没有任何一环在&quot;攒齐再传&quot;。这一步的收益最大,而且不花钱。</li>
<li><strong>再砍轮次判定</strong>——把固定静音超时换成语义端点检测。</li>
<li><strong>最后才去抠 LLM</strong>——换小模型、上 prompt caching、投机解码。</li>
</ol>
<p>很多团队一上来就盯着&quot;换个更快的大模型&quot;,但如果你的管道根本不是流式的,换什么模型都救不回那种&quot;慢半拍&quot;的感觉。先把链路理顺,再谈毫秒。</p>
]]></content:encoded></item><item><title>语音模型这一年:从 ASR 到端到端语音</title><link>https://realtime-ai.chat/posts/speech-models-2026/</link><pubDate>Thu, 14 May 2026 10:00:00 +0800</pubDate><guid>https://realtime-ai.chat/posts/speech-models-2026/</guid><description>语音模型两年走完了从专用 ASR 到多模态语音理解、再到端到端 speech-to-speech 的三级跳。这篇梳理每一代解决了什么、代价是什么,以及 2026 年该用哪一代。</description><content:encoded><![CDATA[<p>两年前你做语音功能,绕不开 Whisper。把音频丢进去,拿一段文字出来,干净利落。</p>
<p>今天你再去看,会发现一个有点反常识的事实:在不少新产品里,<strong>那一段文字根本不存在了</strong>。语音进去,语音出来,中间没有任何一步是&quot;文本&quot;。Whisper 这种纯 ASR 模型,正在从&quot;语音 AI 的地基&quot;退化成&quot;一个还在用、但不再激动人心的工具&quot;。</p>
<p>这不是 ASR 变差了——它一直在变好。是语音模型这条线,这一两年走完了一次三级跳。我想把这三级讲清楚:每一步解决了什么、赔进去了什么,以及 2026 年的此刻,你手里的场景到底该站在哪一级。</p>
<h2 id="三级跳一张时间线">三级跳:一张时间线</h2>
<pre class="mermaid">timeline
    title 语音模型的三代演进
    第一代 专用 ASR : Whisper 系 / 各家流式 ASR
                    : 语音 → 文字,只做识别
    第二代 多模态语音理解 : Qwen-Audio / Qwen3-Omni
                        : 语音直接进 LLM,听懂语气与事件
    第三代 端到端语音 : Moshi / Sesame CSM
                     : GPT-Realtime / Gemini Live
                     : 语音进语音出,中间不落文字
</pre><p>这三代不是互相取代的关系——更像三层楼,新楼盖起来了,旧楼还有人住,而且住得挺好。下面一层一层说。</p>
<h2 id="第一代专用-asr把语音压扁成文字">第一代:专用 ASR,把语音&quot;压扁&quot;成文字</h2>
<p>ASR 模型只干一件事:把声波转成最可能的那串字。Whisper large-v3 仍然是这条线上的标杆,多语言、抗噪、开源、便宜,2026 年依然是无数转写流水线的默认选项。</p>
<p>它解决的问题很实在:语音是连续的、模拟的、信息量巨大的信号,文字是离散的、规整的、好处理的符号。ASR 在两者之间架了一座桥。有了这座桥,语音才第一次能接进所有为文字设计的系统——搜索、数据库、传统 NLP、LLM。</p>
<p>代价也恰恰在这座桥上。<strong>ASR 是一次有损压缩</strong>,而且丢掉的东西,常常正是你最想要的。</p>
<p>同一句&quot;你这是什么意思&quot;,可以是真诚发问,可以是压着火,可以是开玩笑。转成文字之后,这九个字一模一样,语气全没了。一段录音里有人在笑、有人在哭、背景有玻璃碎掉的声音——ASR 给你的还是那行字,这些&quot;非文字信息&quot;在转写的瞬间被抹平。</p>
<p>对&quot;我要把会议录音变成文字稿&quot;这种任务,这种丢失无所谓,甚至是好事。但对&quot;我要做一个能察言观色的语音助手&quot;,这就是地基上的裂缝:你的下游模型再聪明,也只能在 ASR 留下的那点信息里打转。</p>
<h2 id="第二代多模态语音理解让-llm-自己去听">第二代:多模态语音理解,让 LLM 自己去听</h2>
<p>第二代的想法很直接——既然转文字会丢信息,那就别转了,<strong>让语音直接进 LLM</strong>。</p>
<p>阿里的 Qwen-Audio 是这条路上的代表,到 Qwen3-Omni 已经做成了一个统一的多模态模型:文本、图像、音频、视频一起进,音频部分支持几十种语言的语音理解,单条最长能处理 40 分钟的录音。今年 3 月发的 Qwen3.5-Omni 把语音识别的语言数推到了上百种。它们在几十个音频基准上拿了开源 SOTA,有些项目上压过了闭源的 Gemini 和 GPT-4o 系列。</p>
<p>关键不在跑分,在<strong>模型&quot;听到&quot;的东西变多了</strong>。你可以直接问它:&ldquo;这段录音里说话的人情绪怎么样?&ldquo;&ldquo;背景里那个声音是什么?&ldquo;&ldquo;这两个人谁更着急?&quot;——这些问题,纯 ASR 永远答不了,因为答案在它转写时丢掉的那一层里。语音不再被压扁成文字,而是作为一种&quot;模态&quot;被模型整体地理解。</p>
<p>这一代解决的是<strong>理解的深度</strong>。代价是两条:</p>
<p>一是<strong>贵且重</strong>。一个 Omni 模型比一个专用 ASR 大得多,推理成本、显存占用都不在一个量级。你要的只是把客服录音转成文字做质检,上一个 Omni 模型就是高射炮打蚊子。</p>
<p>二是<strong>它还是&quot;听-想&quot;模型,不是&quot;说&quot;模型</strong>。Qwen3-Omni 这类有 Talker 模块能合成语音,但整体设计重心是&quot;理解&rdquo;。真正把&quot;说&quot;也做到极致、做到能跟人自然对话的,是第三代。</p>
<h2 id="第三代端到端语音中间那行字彻底消失">第三代:端到端语音,中间那行字彻底消失</h2>
<p>第三代最激进:语音进,语音出,<strong>中间一个文字都不落</strong>。</p>
<pre class="mermaid">flowchart LR
    subgraph 级联["级联管线(第一/二代拼起来)"]
        A1[语音] --> A2[ASR] --> A3[LLM] --> A4[TTS] --> A5[语音]
    end
    subgraph E2E["端到端语音(第三代)"]
        B1[语音] --> B2[单一语音模型] --> B3[语音]
    end
</pre><p>为什么要这么激进?因为级联管线有两个治不好的病。</p>
<p>一是<strong>延迟</strong>。语音转文字、文字进 LLM、文字再转语音,三段串起来,每一段都有自己的首包延迟,加上轮次判定,用户说完到 AI 出声常常要 500ms 往上。Kyutai 的 Moshi 主打全双工对话,理论延迟做到 160ms、实测约 200ms——已经摸到人类对话轮次间隔(中位数约 200ms)的水平。这是级联架构怎么调都很难够到的。</p>
<p>二还是那个<strong>信息丢失</strong>问题,而且这次是双向的。级联管线里,你的语气在 ASR 那步丢一次,AI 想表达的语气又得靠 TTS 那步硬&quot;演&quot;出来。端到端模型把这两步合一了,语气、韵律、节奏、什么时候停顿、要不要轻笑一声——这些东西在模型内部是连续表示,不经过文字这个瓶颈。Sesame 的 CSM 主打的就是这种&quot;对话级&quot;的自然度。</p>
<p>到 2026 年中,这一代已经分成两个明显的阵营:</p>
<table>
  <thead>
      <tr>
          <th></th>
          <th>开源/可自托管阵营</th>
          <th>闭源 API 阵营</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>代表</td>
          <td>Moshi、Sesame CSM、Hertz-dev</td>
          <td>OpenAI GPT-Realtime-2、Google Gemini 3.1 Flash Live</td>
      </tr>
      <tr>
          <td>强项</td>
          <td>延迟极低、可私有部署、可控</td>
          <td>推理强、语言覆盖广、开箱即用</td>
      </tr>
      <tr>
          <td>短板</td>
          <td>推理深度弱、需要自己运维 GPU</td>
          <td>黑盒、不能换 LLM、按量付费贵</td>
      </tr>
      <tr>
          <td>适合</td>
          <td>陪伴、互动、对延迟和数据敏感的产品</td>
          <td>要复杂多步推理、agentic 的语音应用</td>
      </tr>
  </tbody>
</table>
<p>OpenAI 5 月发的 GPT-Realtime-2 把 GPT-5 级的推理塞进了实时语音对话,128k 上下文,还能调推理强度;Google 3 月的 Gemini 3.1 Flash Live 是纯 audio-to-audio,支持 90 多种语言,简单问题的响应速度更快。两家是目前生产环境里最主流的两个闭源选项。</p>
<p>但端到端不是免费的午餐,它赔进去的东西很具体:</p>
<ul>
<li><strong>难调试、难审计</strong>。中间没有文字,出了问题你没有那行可以打断点、可以给合规看的文本。客服、金融这类场景,&ldquo;这通电话 AI 到底说了什么&quot;必须留痕,纯端到端反而是负担。</li>
<li><strong>不能换脑子</strong>。级联管线里 LLM 是可插拔的,你想换更强的、更便宜的、自己微调的,随时换。GPT-Realtime、Gemini Live、Moshi 都用自带的推理内核,绑死了,你没得选。</li>
<li><strong>业务逻辑没地方插</strong>。语音直接到语音,中间那行文字本来是你塞 RAG 检索结果、塞函数调用、塞业务规则的地方。它没了,这些就得用别的机制绕。</li>
</ul>
<p>所以一个容易被忽视的事实:即便 Sesame CSM 的合成首包能做到 150ms,那也只是&quot;合成&quot;这一步。真接进产品,你还是得在前面加 ASR 或语音理解、加你自己的检索和业务逻辑——算下来总延迟未必比一条调好的级联管线低多少。<strong>端到端解决的是架构的优雅和上限,不是按个开关就提速。</strong></p>
<h2 id="2026-年你的场景该站第几代">2026 年,你的场景该站第几代</h2>
<p>把上面三代收一收,我给一个明确的、带取舍的判断,不和稀泥:</p>
<p><strong>只要把语音变成文字——还是第一代,而且别犹豫。</strong> 会议纪要、字幕、录音转写、关键词检索,这些任务的本质就是&quot;我要那行字&rdquo;。Whisper large-v3 这类专用 ASR 又快又便宜又好部署,上 Omni 或端到端纯属浪费钱。这一代不会消失,它只是退回到了它本该待的位置:一个成熟、无聊、好用的工具。</p>
<p><strong>要&quot;听懂&quot;而不只是&quot;听见&rdquo;——上第二代。</strong> 你要分析情绪、识别声音事件、理解一段音频里到底发生了什么,Qwen3-Omni 这类多模态语音理解模型是对的选择。它贵,但你买的是 ASR 给不了的那层信息。典型场景:智能质检、音视频内容理解、需要&quot;察言观色&quot;的分析类应用。</p>
<p><strong>要和人实时对话——第三代,但分两种情况站队。</strong></p>
<ul>
<li>陪伴、互动、教育、Web 端的语音玩法,延迟和自然度是命门,数据还可能敏感——Moshi、Sesame CSM 这类开源端到端值得自己跑起来。</li>
<li>语音应用要做复杂的多步推理、要调一堆工具、要 agentic 的能力——GPT-Realtime-2、Gemini Live 这类闭源 API 更现实,你买的是它背后那个强推理内核。</li>
<li>而强管控、强合规的电话客服,我的判断仍然没变:<strong>老老实实用级联管线</strong>。可控、可审计、LLM 可替换,这些在合规场景里比那两百毫秒值钱得多。</li>
</ul>
<p>最后说句容易被宣传带偏的话:<strong>新一代出来,不等于旧一代该被扔掉。</strong> 这一两年真正发生的,不是&quot;端到端取代了 ASR&rdquo;,而是语音模型从&quot;只会一件事&quot;长成了&quot;一个谱系&rdquo;——专用 ASR 在一头,端到端语音在另一头,中间是多模态理解。会选型,比追新更重要。先想清楚你的场景到底要什么:是要那行字,要那层情绪,还是要那两百毫秒。想清楚了,该站第几代,自己就有答案了。</p>
]]></content:encoded></item><item><title>流式 TTS:把首包延迟压到 150ms</title><link>https://realtime-ai.chat/posts/streaming-tts-latency/</link><pubDate>Wed, 13 May 2026 10:00:00 +0800</pubDate><guid>https://realtime-ai.chat/posts/streaming-tts-latency/</guid><description>整句合成耗时 1.5 秒,用户却只感知 150ms——这篇拆开流式 TTS 的首包延迟:怎么压、怎么取舍、踩过哪些韵律和卡顿的坑。</description><content:encoded><![CDATA[<p>合成一句 12 个字的话,模型跑完要 1.2 秒。但如果做对了,用户感受到的延迟是 150ms。</p>
<p>这不是魔法,是流式 TTS 的基本盘。差别在于:你是等整句音频生成完再播,还是第一个音频块一出来就往用户耳朵里塞。前者用户等 1.2 秒,后者等 150ms——同一个模型,同样的算力,体验差一个数量级。</p>
<p>我在《<a href="../voice-latency-budget/">实时语音对话的延迟预算</a>》里把整条语音 Agent 链路拆过一遍,TTS 那一段只给了一句话:&ldquo;要流式,首包出来就播。&ldquo;这篇把那一句话展开,只讲 TTS。</p>
<h2 id="为什么是首包不是整句">为什么是&quot;首包&rdquo;,不是&quot;整句&rdquo;</h2>
<p>先说清楚一个被混淆得很厉害的指标。</p>
<p>很多 TTS 厂商的官网首页挂着&quot;实时率 0.3&quot;或者&quot;合成速度比真人快 3 倍&quot;——这说的是<strong>整句合成耗时</strong>:一句 5 秒的话,1.5 秒合成完。这个数字对离线场景(把一本书转成有声书)有意义,对实时对话<strong>几乎没意义</strong>。</p>
<p>实时对话里真正决定体验的是 <strong>TTFA(time-to-first-audio,首包音频延迟)</strong>:从你把文字喂给 TTS,到第一段能播放的音频抵达用户、开始出声,中间隔了多久。</p>
<p>道理和流式 LLM 一样。LLM 看的是首 token 延迟(TTFT),不是整段生成完的时间;TTS 看的是首包,不是整句。因为一旦第一个音节开始播,后面的音频是边合成边播的——只要合成速度比播放速度快(实时率小于 1),用户就永远听不到&quot;卡壳&quot;。整句要 1.5 秒还是 3 秒,用户根本感知不到,他在听前半句的时候,后半句已经悄悄合成好排在缓冲区里了。</p>
<p>所以这篇标题里的&quot;150ms&quot;,指的是 TTFA。这是 2026 年一个<strong>够格但不极致</strong>的数字:对话不会让人觉得&quot;AI 在想&quot;,但还没到 Cartesia Sonic-3 那种 40ms 模型延迟的极限。</p>
<p>有个数字陷阱要先点破。厂商宣传的&quot;75ms&quot;或&quot;100ms&quot;通常是<strong>纯模型推理时间</strong>——在一块独占 GPU 上,喂一段短文本,模型吐出第一块音频要多久。它不含网络往返、不含 API 网关排队、不含音频编码、不含你播放器的缓冲。一个 benchmark 跑 100ms 的模型,部署在共享云上、赶上流量高峰,端到端能轻松飙到 800ms。所以看 TTFA 数字,永远要问一句:这是模型延迟,还是用户真实感知到出声的延迟?这篇说的 150ms,是后者。</p>
<h2 id="首包延迟花在哪">首包延迟花在哪</h2>
<p>把 TTFA 拆开,大致是这么几块:</p>
<table>
  <thead>
      <tr>
          <th>环节</th>
          <th>它在做什么</th>
          <th>典型耗时</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>网络上行 + 排队</td>
          <td>文字请求到达 TTS 服务、进队列</td>
          <td>20–80 ms</td>
      </tr>
      <tr>
          <td>模型 prefill</td>
          <td>处理输入文本、首块音频前的计算</td>
          <td>40–120 ms</td>
      </tr>
      <tr>
          <td>首块音频生成</td>
          <td>吐出第一个音频 chunk</td>
          <td>30–90 ms</td>
      </tr>
      <tr>
          <td>音频编码</td>
          <td>PCM / Opus / MP3 封装</td>
          <td>5–30 ms</td>
      </tr>
      <tr>
          <td>网络下行</td>
          <td>首块音频回到客户端</td>
          <td>20–80 ms</td>
      </tr>
      <tr>
          <td>播放器缓冲</td>
          <td>凑够最小缓冲再起播</td>
          <td>50–200 ms</td>
      </tr>
      <tr>
          <td><strong>合计(可感知 TTFA)</strong></td>
          <td></td>
          <td><strong>约 150–500 ms</strong></td>
      </tr>
  </tbody>
</table>
<p>看这张表,有两件事值得拎出来说。</p>
<p>第一,<strong>播放器缓冲常常是最肥的一块,也最容易被忽略</strong>。工程师盯着模型延迟抠了半天,把模型从 120ms 压到 80ms,结果播放器为了抗抖动设了 200ms 缓冲——白忙活。这块后面单独讲。</p>
<p>第二,<strong>网络往返是固定成本,且省不掉,只能靠部署位置压</strong>。一次上下行加起来 40–160ms,如果你的 TTS 服务在弗吉尼亚,用户在上海,光物理距离就吃掉 200ms 以上。这部分不优化,模型再快也是纸面数字。</p>
<h2 id="怎么把它压下去">怎么把它压下去</h2>
<p>按收益从大到小排,六个手段。</p>
<h3 id="1-流式合成这是地基不是优化项">1. 流式合成:这是地基,不是优化项</h3>
<p>如果你的 TTS 是&quot;等整句文本到齐 → 整段合成 → 整段返回&quot;,那前面所有讨论都不成立。流式 TTS 的协议(通常是 WebSocket)允许<strong>文字一边到一边合成</strong>:LLM 还在生成后半句,前半句的音频已经在合成了。</p>
<p>2026 年主流的实时 TTS 都走这条路。ElevenLabs Flash 官方标的端到端 135ms TTFB;Cartesia Sonic-3 靠 SSM(状态空间模型)架构做到 40ms 首包、90ms 模型延迟;Deepgram 的 Voice Agent TTS 标 sub-200ms。这些数字的前提全是流式——没有流式,谈不上首包。</p>
<h3 id="2-按句标点分块而不是按字数">2. 按句/标点分块,而不是按字数</h3>
<p>LLM 吐出来的是 token 流,你不能每来一个 token 就喂给 TTS——那样韵律会碎成渣。你需要在中间攒一个<strong>最小可合成单元</strong>再发。</p>
<p>分块策略大致三种:</p>
<ul>
<li><strong>固定字符数</strong>(比如 150–300 字符):延迟可预测,但会从句子中间切开,韵律遭殃。</li>
<li><strong>句子/标点边界</strong>:在句号、问号、逗号处切。韵律自然,但延迟不固定——遇上一个长句,首块迟迟攒不齐。</li>
<li><strong>动态自适应</strong>:首块用最激进的策略(凑够一个短语就发),后续块按标点切。</li>
</ul>
<p>我的建议很明确:<strong>首块和后续块用不同策略</strong>。首块只追求&quot;快&quot;——攒到第一个逗号、或者凑够一个能独立成韵的短语(7–10 个字)就立刻发出去,让用户尽快听到声。从第二块开始切回句子边界,保住整体韵律。用户对首块的韵律宽容度其实很高,他在乎的是&quot;AI 开口了没&quot;;但要是整段都按短语切,听感就会一顿一顿的。</p>
<h3 id="3-模型预热别让第一个用户当小白鼠">3. 模型预热:别让第一个用户当小白鼠</h3>
<p>冷启动是隐形杀手。一个刚拉起的 TTS 实例,第一次请求往往要额外几百毫秒——CUDA context 初始化、模型权重换入显存、KV cache 分配。</p>
<p>解法是<strong>预热</strong>:实例就绪后、接真实流量前,先用一段哑文本跑几次完整合成,把所有 lazy 初始化的路径走通。配合发布策略——新实例预热完成才进负载均衡的轮转。否则每次扩容、每次滚动发布,都会有一批倒霉用户撞上冷启动的尾巴。</p>
<h3 id="4-合成与播放重叠">4. 合成与播放重叠</h3>
<p>这是流式 TTS 的核心收益,也是最容易在实现上漏掉的地方。原则一句话:<strong>链路上任何一环都不能&quot;攒齐再传&quot;</strong>。</p>
<pre class="mermaid">flowchart LR
  L[LLM 输出 token 流] -->|按标点切块| C1[文本块1]
  L -->|按标点切块| C2[文本块2]
  L -->|按标点切块| C3[文本块3]
  C1 --> T1[TTS 合成 块1]
  C2 --> T2[TTS 合成 块2]
  C3 --> T3[TTS 合成 块3]
  T1 --> P[播放队列<br/>边收边播]
  T2 --> P
  T3 --> P
  P --> S[用户听到连续语音]
</pre><p>块 1 在播的时候,块 2 在合成,块 3 的文本还在 LLM 里生成。三件事并行。只要合成速度跟得上播放速度,用户听到的就是一条不断的声音。一旦某一环改成&quot;等齐&quot;,感知延迟立刻塌回真实延迟。</p>
<h3 id="5-就近部署">5. 就近部署</h3>
<p>网络往返省不掉,但能缩短。把 TTS 推理节点放在离用户近的区域,40–160ms 的往返能压到几十毫秒。对全球业务,这意味着多区域部署 + 按用户地理位置路由。这条不涉及任何模型技巧,纯靠基建,但收益实打实。</p>
<h3 id="6-选对音频格式">6. 选对音频格式</h3>
<p>回传格式直接影响首包能多快&quot;可播&quot;。这里有个反直觉的点:很多 TTS 流式返回的<strong>头几个字节根本不是音频</strong>,而是容器元数据——WAV 头、Ogg 识别页、MP3 的 ID3 标签。播放器要等这些 + 第一个真实音频帧才能起播。</p>
<p>实时场景优先用<strong>裸 PCM</strong>(比如 pcm_22050):没有容器头,第一个字节就是可播音频,省掉解析开销。代价是带宽——PCM 不压缩,流量大。带宽敏感就退一步用 Opus,它的帧结构对低延迟友好,别用 MP3。</p>
<h2 id="首包延迟音质成本三角取舍">首包延迟、音质、成本:三角取舍</h2>
<p>没有免费的午餐。把首包压到极限,一定在别处付了代价。</p>
<table>
  <thead>
      <tr>
          <th>你想要</th>
          <th>代价</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>首块切得极碎(快)</td>
          <td>韵律断裂,听感一顿一顿</td>
      </tr>
      <tr>
          <td>顶级音质模型</td>
          <td>推理慢,首包难压</td>
      </tr>
      <tr>
          <td>独占 GPU、低 P99</td>
          <td>成本高,利用率低</td>
      </tr>
      <tr>
          <td>多区域就近部署</td>
          <td>运维复杂、机器成本翻倍</td>
      </tr>
      <tr>
          <td>裸 PCM 低延迟</td>
          <td>带宽成本上升</td>
      </tr>
  </tbody>
</table>
<p>我的取舍偏好,按场景分:</p>
<p><strong>实时对话(客服、语音助手)</strong>:首包延迟优先,音质够用就行。用户在打电话,他不会拿着话筒细品音色,他要的是不卡、不等。这种场景我会接受首块韵律略糙,换 150ms 的 TTFA。</p>
<p><strong>有声内容(播客、有声书、视频配音)</strong>:音质优先,首包延迟根本不是问题——这是离线批处理,慢就慢。该上最好的模型、最细的韵律控制。</p>
<p><strong>成本敏感的规模化场景</strong>:别盲目追 P50。真正影响体验的是 <strong>P99</strong>——你不能让 1% 的用户等 2 秒。Cartesia 用 SSM 架构主打的卖点就是 P99 比 transformer 稳,因为 SSM 是线性扩展、不像 transformer 那样随序列长度二次膨胀。规模一大,P99 的稳定性比 P50 的漂亮数字值钱得多。</p>
<p>一句话:<strong>先明确你在哪个场景,再谈压到多少毫秒</strong>。脱离场景比拼 TTFA 数字,是营销,不是工程。</p>
<h2 id="实践中真正会咬人的坑">实践中真正会咬人的坑</h2>
<p>前面讲的是怎么做对。这一节讲怎么做错——这些坑我和身边的人基本都踩过。</p>
<p><strong>坑一:分块切太碎,韵律断裂。</strong> 为了压首包,有人把分块阈值设得很激进,每凑 3–5 个字就发一块。结果 TTS 拿到的全是没头没尾的碎片,它无法预测句子级的语调走向——该升调的地方平了,该停顿的地方连上了,整句听起来像机器人念词卡。根因是流式 TTS 在<strong>局部上下文</strong>下做韵律预测,缺了句子的全局结构。务实的做法:首块可以碎(用户宽容),但从第二块起一定切回标点/句子边界,把韵律的锅甩给完整的句子。</p>
<p><strong>坑二:首块太短,反而更慢。</strong> 听起来矛盾——首块越短不是越快发出去吗?但太短的首块(比如 2 个字)会触发两个问题:一是 TTS 对超短文本的相对开销更高(prefill 的固定成本摊不开);二是它播完只要零点几秒,而第二块还没合成好,中间出现<strong>空隙</strong>,用户听到&quot;你好……(停顿)……我是客服&quot;。首块不是越短越好,要够短到快、又够长到能撑住后续块的合成时间。经验值是凑够一个完整短语,7–10 个字。</p>
<p><strong>坑三:缓冲欠载(buffer underrun)导致卡顿。</strong> 播放器从网络收音频块,凑够一点就开播。如果某一块因为网络抖动或合成慢迟到了,播放队列被掏空——这就是缓冲欠载,用户听到爆音或者卡顿。</p>
<p>缓冲设置是个跷跷板:缓冲小,起播快(TTFA 低),但抗抖动差;缓冲大,起播慢,但播得稳。没有放之四海的数值。数据中心内部、网络稳定,50–100ms 缓冲够了;移动端、网络飘忽,得放到 150–250ms 来吸收抖动。</p>
<p>更关键的是<strong>把欠载率当独立指标监控</strong>,别只看 TTFA。一个务实的阈值:如果缓冲欠载超过请求总数的 2%,就该调大缓冲——哪怕这意味着 TTFA 涨几十毫秒。卡顿对体验的伤害,远大于首包慢那一点点。慢半拍用户能忍,中间卡一下用户立刻出戏。</p>
<p><strong>坑四:只测了模型,没测端到端。</strong> 回到开头那个数字陷阱。你在本地用独占 GPU 测出 80ms 首包,上线后用户反馈&quot;AI 反应慢&quot;。因为真实链路上还叠着网络、网关排队、编码、播放器缓冲。<strong>测 TTFA 一定要从用户视角测</strong>——从发出请求到扬声器真正出声,端到端打点,而且要测 P99,不是 P50。</p>
<h2 id="落地优先级">落地优先级</h2>
<p>如果你正在做 TTS 这一段,顺序是这样:</p>
<ol>
<li><strong>先确认是流式的</strong>,而且整条链路没有任何一环&quot;攒齐再传&quot;。这步收益最大、几乎不花钱。</li>
<li><strong>调分块策略</strong>——首块激进、后续块按标点。这步直接决定 TTFA 和韵律的平衡。</li>
<li><strong>加预热</strong>,别让扩容和发布把冷启动甩给用户。</li>
<li><strong>测端到端 P99</strong>,把播放器缓冲和欠载率纳入监控。</li>
<li><strong>最后才谈就近部署、换更快的模型</strong>——这些是基建和钱的问题,放在管道理顺之后。</li>
</ol>
<p>很多团队一上来就到处比价&quot;哪家 TTS 首包最低&quot;。但如果你的播放器缓冲设了 250ms、链路里还藏着一个&quot;攒齐再传&quot;的环节,换哪家模型都救不回那种慢半拍。先把自己这边的链路理顺,再去谈毫秒。</p>
<hr>
<p>参考来源:<a href="https://elevenlabs.io/docs/eleven-api/concepts/latency">ElevenLabs Latency 文档</a> · <a href="https://cartesia.ai/sonic">Cartesia Sonic-3</a> · <a href="https://gradium.ai/content/best-low-latency-tts-apis-2026">Gradium: Best Low-Latency TTS APIs 2026</a> · <a href="https://deepgram.com/learn/streaming-tts-latency-accuracy-tradeoff-2026">Deepgram: Streaming TTS Latency Tradeoff</a> · <a href="https://picovoice.ai/blog/text-to-speech-latency/">Picovoice: TTS Latency</a></p>
]]></content:encoded></item><item><title>实时语音的打断:barge-in 怎么做对</title><link>https://realtime-ai.chat/posts/barge-in-engineering/</link><pubDate>Tue, 28 Apr 2026 11:00:00 +0800</pubDate><guid>https://realtime-ai.chat/posts/barge-in-engineering/</guid><description>用户插话时,语音 Agent 要原子地停播放、取消 TTS、取消 LLM、清状态。这篇拆解打断检测、误打断防护、回声消除与打断后上下文怎么接。</description><content:encoded><![CDATA[<p>约五分之一的语音通话里,用户会在 AI 还没说完时开口插话。</p>
<p>这个数字来自 PolyAI 对生产环境通话的统计,我自己看线上日志,感受也差不多。更值得注意的是后半句:<strong>会打断的用户,往往是意图最强的那批人。</strong> 他们听够了想直接说事,或者发现 AI 理解错了要赶紧纠正。换句话说,打断处理得烂的那一小撮通话,直接决定了你整个产品体验的天花板——因为最在乎、最着急的用户,恰好都撞在这里。</p>
<p>所以&quot;能被打断&quot;不是锦上添花的功能。它和延迟一样,是语音 Agent 的及格线。但和延迟不同,打断的难点不在毫秒,在于它是一个<strong>并发控制问题</strong>:一个事件要同时掐断好几条正在跑的流,还不能掐错。</p>
<h2 id="为什么打断比想象中难">为什么打断比想象中难</h2>
<p>很多人第一次做打断,以为就是&quot;检测到用户说话,调一下 <code>audioPlayer.stop()</code>&quot;。上线一周就会发现到处是坑。</p>
<p>难在三个地方。</p>
<p>第一,<strong>打断是个分布式的取消操作</strong>。用户开口的那一刻,你的系统里可能同时有:扬声器在播第 3 句的音频、TTS 服务还在合成第 5 句、LLM 还在流式生成第 8 句的 token。这三样东西跑在不同进程甚至不同机器上。你要在几十毫秒内把它们<strong>一起</strong>叫停,任何一个漏网,AI 就会出现&quot;我已经打断它了,它怎么还在自说自话&quot;的灵异现象。</p>
<p>第二,<strong>判断&quot;这算不算打断&quot;本身就难</strong>。背景里有人说话、电视开着、用户自己&quot;嗯&quot;&ldquo;对啊&quot;地随口附和——这些都会让一个朴素的 VAD 兴奋地触发打断。误打断比&quot;打断慢了&quot;更伤体验:AI 说到一半被一声咳嗽打断,僵在那里,用户一脸问号。</p>
<p>第三,<strong>打断之后,对话状态是脏的</strong>。AI 那句话只说了一半就被掐了,LLM 的上下文里到底该记成&quot;我说了整句&quot;还是&quot;我只说了半句&rdquo;?记错了,下一轮 AI 要么重复已经说过的内容,要么基于&quot;它以为自己说了但其实没说出口&quot;的信息往下接。</p>
<p>这三件事,后面一件件拆。</p>
<h2 id="怎么知道用户在打断">怎么知道用户在打断</h2>
<p>打断检测的第一关,是 VAD(语音活动检测)。它干一件很窄的事:判断这一小段音频里<strong>有没有人声</strong>。现代 VAD(比如 Silero)能在几十毫秒内给出&quot;有声/无声&quot;的概率,这一层基本算解决了。</p>
<p>但 VAD 只够回答&quot;有没有声音&quot;,回答不了&quot;这声音算不算一次真正的打断&quot;。2026 年成熟的做法,是在 VAD 上面叠一层判断,通常叫<strong>语义化的轮次检测</strong>。</p>
<p>这里要分清两个相关但不同的问题:</p>
<ul>
<li><strong>端点检测(turn detection)</strong>:用户这一轮说完了没?——决定 AI 什么时候开口。</li>
<li><strong>打断检测(barge-in)</strong>:AI 正在说话时,用户这声插话,要不要让 AI 闭嘴?——决定 AI 什么时候停下。</li>
</ul>
<p>它们共用 VAD 这个底座,但上层逻辑不一样。打断检测要额外回答的问题是:这声音是&quot;我要说话了你停下&quot;,还是&quot;嗯哼,我在听&quot;。</p>
<p>主流框架的处理方式,可以摆在一起看:</p>
<table>
  <thead>
      <tr>
          <th>方案</th>
          <th>核心信号</th>
          <th>特点</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>朴素 VAD</td>
          <td>音频能量 / 人声概率</td>
          <td>最快,但把附和、噪音全当打断</td>
      </tr>
      <tr>
          <td>转写流启发式</td>
          <td>VAD + ASR 部分转写文本</td>
          <td>看用户说出的字是不是&quot;有内容&quot;</td>
      </tr>
      <tr>
          <td>模型化打断判定</td>
          <td>专门小模型读音频+文本</td>
          <td>区分真打断 / 附和 / 噪音,准但有计算开销</td>
      </tr>
  </tbody>
</table>
<p>LiveKit 在 2026 年走的是模型路线:它的<strong>自适应打断</strong>(adaptive interruption)用真实对话音频训练了一个小模型,AI 说话期间检测到人声,不会无脑停,而是让模型先判断&quot;这次该不该让出话权&quot;。Pipecat 的 Smart Turn、Deepgram 的 Flux、VideoSDK 开源的 Namo,思路都类似——<strong>从&quot;听到声音就停&quot;升级到&quot;听懂这是不是打断再停&quot;。</strong></p>
<p>这里有个工程上的取舍:模型判定更准,但它要攒一点点音频和文本才能下判断,这等于给打断响应<strong>加了几十到一两百毫秒延迟</strong>。我的建议是分场景:电话客服这种噪音大、附和多的场景,这点延迟换来的误打断下降很值;而安静环境下的高质量耳麦场景,朴素 VAD 配一个合理阈值往往就够,没必要上重型模型。</p>
<p>说话人识别(speaker diarization)是另一条值得加的信号,尤其当 AI 走的是外放、而不是用户戴耳机的时候。带说话人意识的 VAD 只对&quot;主说话人&quot;的声音触发打断,旁边同事路过聊两句,AI 不会被带跑。代价是要先有一小段主说话人的注册音频,冷启动那几秒会暂时退化成普通 VAD。</p>
<h2 id="打断要原子地做四件事">打断要原子地做四件事</h2>
<p>检测到了真打断,接下来是这篇文章最该记住的一句话:<strong>打断不是&quot;停 TTS&quot;,是四件事必须一起做。</strong></p>
<pre class="mermaid">flowchart TB
  X[检测到真打断] --> A[1 停止音频播放]
  X --> B[2 取消 TTS 合成任务]
  X --> C[3 取消 LLM 流式生成]
  X --> D[4 清空管道缓冲与状态]
  A --> E[管道回到 ready<br/>开始听新输入]
  B --> E
  C --> E
  D --> E
  style X fill:#fde7c2,stroke:#e8b23c
  style E fill:#fde7c2,stroke:#e8b23c
</pre><p>逐件说。</p>
<p><strong>第一件,停止音频播放。</strong> 最直觉的一步,但有个细节:别只停&quot;还没送到扬声器的&quot;,还要清掉<strong>已经在播放缓冲区里排队</strong>的音频。很多框架的播放器有几百毫秒的 jitter buffer,你不主动 flush 它,用户已经开口了,AI 还会从缓冲里&quot;漏&quot;出半秒声音。</p>
<p><strong>第二件,取消还在合成的 TTS 任务。</strong> AI 嘴上播到第 3 句,TTS 服务很可能已经把第 4、第 5 句合成好或正在合成。这些任务必须立刻 cancel,否则它们合成完的音频会涌进刚清空的播放缓冲,等于打断没生效。流式 TTS 一定要支持中途 abort,选型时这是硬指标。</p>
<p><strong>第三件,取消还在生成的 LLM 请求。</strong> 这一件最容易漏,因为 LLM 在后台跑,你&quot;看不见&quot;它。但 AI 说到第 3 句时,LLM 大概率已经流式吐到第 8 句了。不取消,它会一直把 token 生成完——既烧钱,又让这个&quot;已经作废&quot;的回答占着你的会话状态。在 OpenAI、Anthropic 这些流式 API 上,正确做法是 abort 掉那个 HTTP 流;自己部署的推理服务,要确保 cancel 信号能真正中止那一次 forward,而不是只在客户端假装断开。</p>
<p><strong>第四件,清空整条管道的缓冲和状态。</strong> ASR 的部分转写、各级队列、状态机的 flag——全部归零,管道干净地回到&quot;准备听新输入&quot;的状态。</p>
<p>为什么强调&quot;原子&quot;?因为这四件事任何一件慢了或漏了,用户都会立刻察觉。漏了第二件,AI 停顿一下又冒出一句。漏了第三件,你这轮白烧 token,而且下一轮上下文是乱的。理想情况下,这四个取消应该由<strong>同一个打断事件</strong>并发触发、并发完成,而不是串行地&quot;停完播放再去停 TTS 再去停 LLM&quot;——串行会把延迟一段段叠起来。</p>
<p>实操里我会让打断走一个独立的高优先级事件通道,绕开正常的数据流水线,确保它能&quot;插队&quot;执行。否则打断信号自己排在拥堵的管道后面,就荒诞了。</p>
<h2 id="误打断别让噪音掐了-ai-的话">误打断:别让噪音掐了 AI 的话</h2>
<p>把打断做得太灵敏,会走到另一个极端:AI 老是被无关声音掐断。这就是误打断,前面说过,它比&quot;打断慢&quot;更毁体验。</p>
<p>误打断的来源,基本就这几类,得分开治:</p>
<p><strong>第一类,AI 自己的声音绕回来了。</strong> 这是最隐蔽、也最致命的一类。AI 外放的声音被用户的麦克风重新收进去,VAD 一看&quot;有人声&quot;,触发打断——AI 等于被自己说的话打断了,然后陷入&quot;说一句停一句&quot;的死循环。</p>
<p>治它的唯一正解是<strong>回声消除(AEC)</strong>。AEC 知道 AI 正在播什么音频(参考信号),就能从麦克风收到的信号里把这部分减掉,让 VAD 只看到&quot;真正的用户语音&quot;。所以 AEC 不是打断的可选配件,是<strong>前提</strong>。我见过的经验法则是:在调 VAD 阈值、最小语音时长这些参数之前,先确认 AEC 真的把回声压下去了——有团队报告上了带 AEC 的全双工处理后,误打断直接降了三成。次序不能反:回声没压住就调阈值,你只是在和一个会变化的噪声源拉锯。</p>
<p>值得提醒的是:浏览器里 WebRTC 自带 AEC,效果通常够用;但走电话线路(SIP/PSTN)、或者用了某些音频中转,AEC 可能不在你以为的地方,得自己确认链路上到底哪一环在做。</p>
<p><strong>第二类,背景里有别人说话或电视声。</strong> 这一类靠前面说的说话人识别来挡——只认主说话人。挡不住的部分(比如背景人声和主说话人音色接近),就交给模型化的打断判定去兜。</p>
<p><strong>第三类,附和音(backchannel)。</strong> 用户的&quot;嗯&quot;&ldquo;对&quot;&ldquo;哦&rdquo;——这些不是要抢话,是在表示&quot;我在听,你继续&rdquo;。朴素 VAD 完全分不出附和和打断,模型化判定才能。把附和单独识别出来、不触发打断,是这两年体验提升很明显的一块:AI 不会因为你一声&quot;嗯哼&quot;就慌张地停下来反问&quot;您说?&quot;。</p>
<p>调参上有个心智模型:打断检测内部其实有个&quot;置信度&quot;。置信度高(用户说了一串有内容的话)就立即让出话权;置信度低(短促的、像附和的声音)就先压一压、继续观察。把这个判断交给阈值或小模型,而不是&quot;一刀切地一听到声就停&quot;,是朴素方案和生产级方案的分水岭。</p>
<h2 id="打断之后上下文怎么接">打断之后,上下文怎么接</h2>
<p>打断动作做干净了,还剩最后一个、也最容易被忽略的问题:<strong>被打断的那句话,在 LLM 的对话历史里该怎么记。</strong></p>
<p>设想:AI 准备说的完整回答是&quot;您的订单已经发货了,预计明天下午送达,快递单号是 SF1234567890&quot;。它嘴上播到&quot;您的订单已经发货了,预计明天——&ldquo;被用户打断了。</p>
<p>现在 LLM 上下文里这条 assistant 消息,该写什么?</p>
<ul>
<li><strong>写完整回答</strong>:LLM 以为&quot;送达时间和单号我都说过了&rdquo;。下一轮用户问&quot;单号多少&quot;,AI 可能答&quot;我刚才说过了呀&quot;——可它<strong>根本没说出口</strong>。</li>
<li><strong>写空 / 整条丢掉</strong>:LLM 以为这轮自己什么都没说。下一轮可能从头再说一遍&quot;您的订单已经发货了……&quot;,用户已经听过一遍前半句,体验割裂。</li>
</ul>
<p>正确答案是第三个:<strong>把上下文截断到用户实际听到的位置。</strong> 这条 assistant 消息应该记成&quot;您的订单已经发货了,预计明天——&quot;,后面没说出口的部分不进上下文。这样 LLM 的&quot;记忆&quot;和用户的&quot;耳朵&quot;才对得齐,它才知道单号还没讲、送达时间也没讲完。</p>
<p>这件事说起来简单,做起来有个硬骨头:<strong>你得知道用户到底听到了哪个字。</strong> TTS 是流式播放的,被打断时,真正可靠的位置不是&quot;LLM 生成到哪&quot;,也不是&quot;TTS 合成到哪&quot;,而是<strong>音频实际播放到了哪一刻</strong>。理想情况下,TTS 要能给出词级或音素级的时间戳,你拿打断发生的时间戳去对齐,才能精确切到&quot;用户听到的最后一个字&quot;。退一步,按句子边界粗切也比&quot;全记/全不记&quot;强得多。</p>
<p>这不是纸上谈兵。Pipecat 社区里有个被讨论很多的 issue(#2791)就是这个:打断后,已经说出口的半句话没有被写回 LLM 上下文,导致下一轮 LLM 把整个回答重新生成一遍。这说明截断对齐这件事,框架默认不一定帮你做对,得自己确认。</p>
<pre class="mermaid">flowchart LR
  A[LLM 完整回答] --> B[流式 TTS 播放]
  B -->|播到一半| C[用户打断]
  C --> D[取打断时刻<br/>对齐播放时间戳]
  D --> E[上下文截断到<br/>用户实际听到处]
  E --> F[LLM 接住新一轮<br/>知道哪些还没说]
  style C fill:#fde7c2,stroke:#e8b23c
  style E fill:#fde7c2,stroke:#e8b23c
</pre><p>接住之后还有个加分项:把用户的打断<strong>当成信号去理解</strong>,而不只是&quot;换我说了&quot;。用户在 AI 报送达时间时插话,大概率是对前面的信息不满意或要追加条件。一个聪明的 Agent 会把&quot;用户在我说到 X 时打断&quot;这件事本身喂给 LLM,让它意识到 X 可能正是用户要纠正的点。这一步做不做,区别就是&quot;能被打断的机器&quot;和&quot;听得懂你为什么打断的对话者&quot;。</p>
<h2 id="一个落地的优先级">一个落地的优先级</h2>
<p>如果你正在给语音 Agent 加打断,我会建议这个顺序:</p>
<ol>
<li><strong>先保证四件事原子地做对</strong>——停播放、取消 TTS、取消 LLM、清状态,一个都不能漏。这是正确性,漏了就是 bug,不是体验问题。</li>
<li><strong>再把 AEC 压实</strong>——回声不消干净,后面所有的误打断调参都是徒劳。</li>
<li><strong>然后上语义/模型化的打断判定</strong>——把附和、噪音、真打断分开,这一步直接决定&quot;它像不像个会聊天的人&quot;。</li>
<li><strong>最后做上下文截断对齐</strong>——让 LLM 的记忆和用户的耳朵对齐,打断后的对话才接得顺。</li>
</ol>
<p>很多团队卡在第三步,觉得&quot;打断不够智能&quot;,于是拼命换模型、调阈值。但常见的真相是第一步就漏了——LLM 请求压根没被取消,或者第二步 AEC 根本没生效。打断这事,<strong>先把正确性焊死,再谈智能。</strong> 顺序反了,你会在一个有裂缝的地基上反复刷漆。</p>
]]></content:encoded></item><item><title>语音克隆的滥用与检测</title><link>https://realtime-ai.chat/posts/voice-clone-detection/</link><pubDate>Mon, 27 Apr 2026 11:00:00 +0800</pubDate><guid>https://realtime-ai.chat/posts/voice-clone-detection/</guid><description>语音克隆已经以假乱真,这篇讲防御侧:诈骗与声纹绕过的真实形态、合成语音检测能做到多少、音频水印如何溯源,以及为什么这是一场猫鼠游戏。</description><content:encoded><![CDATA[<p>2025 年第一季度,美国境内利用深度伪造语音的电话诈骗(vishing)环比涨了 <strong>1600% 多</strong>。同一年 FBI 的互联网犯罪报告里,跟 AI 相关的诈骗投诉超过 2.2 万起,涉案金额 8.93 亿美元。</p>
<p>这些数字背后有一个让人不舒服的事实:<strong>克隆一个人的声音,现在只需要三秒公开音频</strong>。你公司高管参加过的每一场财报电话会、每一次大会演讲、每一段播客采访,都躺在公网上,对想用它的人来说就是现成的训练素材。</p>
<p>克隆技术本身已经不是新闻——这个博客之前写过《声音克隆:60秒复制你的声音,然后呢?》。这篇讲&quot;然后&quot;的另一面:声音一旦能被以假乱真地复制,我们靠什么分辨真假,以及这件事到底能做到多好。</p>
<h2 id="滥用长什么样三种不是一种">滥用长什么样:三种,不是一种</h2>
<p>把&quot;AI 语音诈骗&quot;当成一个笼统的词,会让你低估它。它至少是三类性质不同的攻击。</p>
<p><strong>第一类是社工诈骗。</strong> 最经典的是&quot;亲人求救&quot;:伪造你孩子的哭腔打电话说出事了急需用钱。但 2025 年真正造成大额损失的是企业版——伪造 CEO 或 CFO 的声音,指示财务转账。香港那起 2.56 亿港元的案子是个标志:财务员工参加了一场视频会议,会议里的 CFO 和同事<strong>全是 AI 生成的</strong>,人脸、口型、声音都对得上,他一开始怀疑是钓鱼,但一场&quot;活的&quot;视频会把他的怀疑全打消了,直到事后跟总部人工核对才发现。</p>
<p><strong>第二类是声纹绕过。</strong> 不少银行和券商用&quot;我的声音就是我的密码&quot;做身份验证。克隆语音直接攻击这套系统。它比社工诈骗更隐蔽,因为受害的不是某个被吓住的人,而是一套<strong>自动化的认证流程</strong>——没有人在场可以&quot;觉得不对劲&quot;。2025 年 1 到 8 月,某金融机构的活体检测被 AI 伪造尝试绕过了 8000 多次。</p>
<p><strong>第三类是假音频内容。</strong> 伪造公众人物的录音、伪造一段&quot;泄露的会议录音&quot;、给某段视频配上从没说过的话。它不针对个人钱包,针对的是舆论和信任。2024 年美国大选期间出现过伪造拜登声音的自动外呼电话,就是这一类。</p>
<p>三类攻击的防御手段完全不同。社工诈骗要靠流程和人的警觉,声纹绕过要靠活体检测,假内容要靠溯源和检测——别指望一招通吃。</p>
<h2 id="检测合成语音能做到但有前提">检测合成语音:能做到,但有前提</h2>
<p>检测分两条路:<strong>被动检测</strong>(拿到一段音频,判断它是不是 AI 合成的)和<strong>主动标记</strong>(生成时就打上记号)。先说被动。</p>
<p>被动检测模型在学术基准上的成绩相当好。这个领域有一套延续多年的评测体系——从早年的 ASVspoof 挑战赛,到 2026 年 ICME 的环境感知语音检测挑战赛(ESDD2)、ACM Multimedia 的全类型音频伪造检测挑战赛(AT-ADD)。检测模型也在进步:用 Whisper 这类大规模语音模型抽特征,比传统声学特征的等错误率(EER)低了约 21%,再针对反伪造任务微调 Whisper 编码器,还能再降近 15%。</p>
<p>听起来不错。但&quot;在干净数据集上 EER 很低&quot;和&quot;在真实世界管用&quot;之间,差了一整条鸿沟。</p>
<pre class="mermaid">flowchart TD
  A[一段语音] --> B{被动检测模型}
  B -->|实验室条件| C[准确率很高]
  B -->|真实世界| D[麻烦开始了]
  D --> E[电话信道压缩]
  D --> F[录音回放/翻录]
  D --> G[训练时没见过的新 TTS]
  D --> H[环境噪声/混响]
  style C fill:#d8f0d8,stroke:#5aa05a
  style D fill:#fde7c2,stroke:#e8b23c
</pre><p>真实世界里有三个东西在持续打击检测准确率:</p>
<ul>
<li><strong>信道劣化。</strong> 一通诈骗电话经过窄带编码、丢包、压缩,把那些&quot;AI 痕迹&quot;——比如不自然的高频细节、过于平滑的频谱——磨掉了大半。检测模型训练时见的是高保真音频,推理时拿到的是被电话线榨过的音频。</li>
<li><strong>回放攻击。</strong> 攻击者把合成音频用音箱播出来、再用麦克风录下来。这一录一放,等于给假音频套了一层&quot;真实物理世界&quot;的外衣,很多检测器会被骗过。专门为这种场景做的数据集(比如 EchoFake)就是冲着这个问题来的。</li>
<li><strong>泛化。</strong> 这是最根本的。检测模型本质上是在学&quot;已知的几种 TTS 系统留下的指纹&quot;。一个训练时没见过的新模型出来,检测器对它就是睁眼瞎。而新的 TTS 模型几乎每个月都在出。</li>
</ul>
<p>所以我的判断是:<strong>被动检测有用,但不能当成单点防线。</strong> 它适合做大规模内容平台的初筛、做事后取证,不适合做&quot;接到电话实时告诉你这是假的&quot;那种承诺——任何号称能实时、高准确、对所有声音通杀的检测产品,你都该多问几句它在什么数据上测的。</p>
<h2 id="声纹活体防的是录音未必防得住克隆">声纹活体:防的是&quot;录音&quot;,未必防得住&quot;克隆&quot;</h2>
<p>声纹认证系统自己也有反伪造机制,通常叫&quot;活体检测&quot;(liveness detection)。但要分清它原本是防什么的。</p>
<p>活体检测最早是为了防<strong>回放攻击</strong>——防止有人拿一段你说话的录音来冒充你。它会去找录音特有的痕迹:音箱的频响特性、二次录制引入的失真、缺少真人说话该有的呼吸和微小变化。对着录音,这套机制管用。</p>
<p>问题是,<strong>高质量的神经网络合成语音不是&quot;录音&quot;</strong>。它没有音箱频响的指纹,它的呼吸、停顿、韵律是模型直接生成的,看起来比一段翻录干净得多。换句话说,为防回放设计的活体检测,面对端到端克隆语音时,部分假设已经不成立了。</p>
<p>更麻烦的是&quot;语音变形&quot;(voice morphing)攻击——把攻击者自己的真实语音和目标人的声纹特征混合,生成的音频既带着真人说话的所有物理特征(因为底子是真人录的),又带着目标人的声纹。这种攻击专门钻活体检测和声纹比对之间的缝。</p>
<p>现在还有&quot;深度伪造即服务&quot;(Deepfake-as-a-Service)——黑产把克隆和绕过工具打包成服务卖,一套合成身份、克隆声音的素材,在地下市场只要五美元。攻击的门槛已经低到不需要任何技术。</p>
<p>我的看法直接一点:<strong>2026 年,&ldquo;声音即密码&quot;这个产品设计应该被淘汰了。</strong> 声纹可以作为<strong>一个</strong>信号参与风险评分,但不能作为单一凭证去解锁转账、改密码这类高危操作。把一个已经能被三秒素材复制的东西当成身份证明,是设计缺陷,不是技术细节。</p>
<h2 id="音频水印把检测的难题倒过来">音频水印:把检测的难题倒过来</h2>
<p>被动检测难,难在它要从音频里&quot;反推&quot;真假。<strong>主动水印</strong>换了个思路:在 AI 生成语音的那一刻,就在音频里嵌一个人耳听不见、但机器能检出的标记。这样要回答的问题从&quot;这段音频是不是假的&quot;变成了&quot;这段音频里有没有我的水印&rdquo;——后者好回答得多。</p>
<p>Meta 的 AudioSeal 是目前最有代表性的方案。它的设计有两个关键点值得说:</p>
<p><strong>一是定位能力。</strong> 它不只能判断&quot;整段音频有没有水印&quot;,还能精确到样本级——在 16kHz 采样率下,能指出每一个 1/16000 秒的片段是不是带水印的。这意味着如果有人把一句真人录音里<strong>只抠掉几个字、换成合成的</strong>,水印检测能定位到那几个字。</p>
<p><strong>二是检测速度。</strong> 它用单次前向的检测器,比靠水印密钥逐一解码的老方法快两个数量级。这点对落地很重要——内容平台要扫的是每天上传的海量音频,检测器慢一点,整个方案就不可行。</p>
<p>水印的真正价值不在&quot;抓坏人&quot;,在<strong>给善意内容一个可以自证清白的办法</strong>。一家正规 TTS 服务给自己所有输出打上水印,等于主动声明&quot;这是我生成的&quot;;新闻机构给自己的真实采访录音打上来源标记,等于主动声明&quot;这是真的&quot;。社会需要的不是&quot;找出所有假音频&quot;,而是逐步建立&quot;没标记的内容默认不可信&quot;的预期。</p>
<p>但水印不是银弹,它有两个硬伤:</p>
<table>
  <thead>
      <tr>
          <th>问题</th>
          <th>具体表现</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>只能管&quot;听话的人&quot;</td>
          <td>开源模型、自己训练的模型、刻意去水印的攻击者,根本不会嵌水印。水印覆盖不到真正想作恶的人。</td>
      </tr>
      <tr>
          <td>鲁棒性有上限</td>
          <td>重度压缩、变调、加噪、片段裁剪,都可能削弱甚至擦除水印。AudioSeal 这类方案在鲁棒性上做了很多工作,但&quot;绝对擦不掉&quot;做不到。</td>
      </tr>
  </tbody>
</table>
<p>所以水印解决的是&quot;占大多数的正规生成内容如何被标记&quot;的问题,不解决&quot;恶意攻击者&quot;的问题。把它和被动检测、和内容来源标准(比如 C2PA 那套溯源元数据)叠在一起,才构成一张有意义的网。</p>
<h2 id="为什么这注定是猫鼠游戏">为什么这注定是猫鼠游戏</h2>
<p>讲到这里该说一句不太好听的:<strong>合成语音检测,在原理上就是一场打不赢的军备竞赛。</strong></p>
<p>原因不在哪个团队不够努力,在结构。生成模型和检测模型之间存在一个根本的不对称——</p>
<pre class="mermaid">flowchart LR
  G[生成方] -->|留下可检测痕迹| D[检测方学会识别]
  D -->|痕迹被公开| G2[生成方针对性消除痕迹]
  G2 --> D2[检测方需要新痕迹]
  D2 -.循环.-> G
  style G fill:#fde7c2,stroke:#e8b23c
  style G2 fill:#fde7c2,stroke:#e8b23c
</pre><p>每当检测方发现一类&quot;AI 痕迹&quot;并公开(发论文、开源模型、办挑战赛),这个发现立刻变成生成方的优化目标——下一代 TTS 训练时,只要把&quot;骗过这个检测器&quot;加进损失函数,痕迹就被磨掉了。检测方天然滞后,因为它只能对<strong>已经存在</strong>的生成模型做出反应。</p>
<p>更不利的是终点不一样。生成方的目标是&quot;无限逼近真人&quot;,而真人语音是存在的、有上限的;一旦合成语音在物理特征上和真人无法区分,检测方就<strong>没有信号可用了</strong>——不是检测器不够好,是信息论意义上无东西可检。</p>
<p>那是不是就躺平?不是。结论应该是:<strong>别把宝押在&quot;检测出假的&quot;上,要把重心移到&quot;验证出真的&quot;。</strong></p>
<ul>
<li>检测假音频是开放问题,可能永远做不到 100%。</li>
<li>验证真音频是封闭问题:水印、数字签名、内容来源链(C2PA),这些是可以做到接近确定的——因为它不依赖猜测,依赖密码学。</li>
</ul>
<p>防御的长期方向,是让&quot;可信&quot;成为需要主动证明的东西,而不是默认状态。</p>
<h2 id="平台和个人各自能做什么">平台和个人,各自能做什么</h2>
<p>技术讲完,落到行动。</p>
<p><strong>对平台和企业:</strong></p>
<ul>
<li><strong>高危操作做带外验证(out-of-band)。</strong> 转账、改预留信息、授权放款,绝不能只凭一通电话或一段语音确认。换一个独立信道——回拨已登记的号码、在内部系统二次确认、设一个对方知道你知道的口令。香港那 2.56 亿的案子,只要一通打回总部的电话就能拦下。</li>
<li><strong>声纹只做风控信号,不做唯一凭证。</strong> 把它和设备指纹、行为特征、信道分析一起喂进风险评分,任何一个高危动作都要多因子。</li>
<li><strong>自家生成的语音一律打水印、留来源标记。</strong> 这不是为了抓别人,是为了让你的正规内容可被验证,也是给行业立规矩。</li>
<li><strong>检测模型当初筛和取证用,别对外承诺实时通杀。</strong> 同时持续用新出的 TTS 系统更新训练集——你的检测器和攻击者用的生成器,得在同一个时代。</li>
</ul>
<p><strong>对个人:</strong></p>
<ul>
<li><strong>认知层面接受一件事:听到的声音不再等于身份证明。</strong> 这是 2026 年最需要更新的常识。电话里&quot;你妈的声音&quot;、&ldquo;你老板的声音&rdquo;,都不构成&quot;确实是本人&quot;的证据。</li>
<li><strong>和家人约一个口令。</strong> 一个只有你们知道、不会出现在任何社交媒体上的词。接到&quot;家人急用钱&quot;的电话,先问口令。土办法,但有效。</li>
<li><strong>挂掉,回拨。</strong> 任何涉及钱或敏感信息的来电,无论声音多像,挂掉,用你通讯录里<strong>原本存的</strong>号码打回去。克隆语音能伪造声音,伪造不了你主动拨出的这通电话。</li>
<li><strong>少喂素材。</strong> 三秒就够克隆。公开演讲、播客、短视频里的长段清晰人声,本质上是在公网上发布自己的声纹。这不是让你别说话,是让你知道代价。</li>
</ul>
<p>最后留一句判断:这场对抗里,纯技术解(更强的检测器)只能拖时间,拖不到终局。真正能把损失压下去的,是<strong>流程</strong>——带外验证、口令、回拨这些听起来很&quot;低科技&quot;的东西。当声音不再可信,可信的得是别的:你拨出去的号码、你和家人之间的暗号、系统里另一个独立的确认信道。技术制造了这个问题,但解决它,要靠把&quot;信任&quot;重新放回那些不能被三秒音频复制的地方。</p>
<hr>
<p><em>参考来源:<a href="https://www.blackfog.com/fbi-warning-ai-voice-phishing-how-to-stop-threat/">FBI AI 语音钓鱼预警</a>、<a href="https://cybelangel.com/blog/deepfake-ceo-fraud-how-voice-cloning-targets-us-executives/">深度伪造 CEO 诈骗案例分析</a>、<a href="https://arxiv.org/abs/2401.17264">AudioSeal:语音克隆的主动检测水印</a>、<a href="https://arxiv.org/html/2601.07303v3">ESDD2 环境感知语音伪造检测挑战赛</a>、<a href="https://www.biometricupdate.com/202604/voice-ai-expands-attack-surface-for-speaker-biometrics-as-apis-proliferate">语音生物识别攻击面扩大</a>、<a href="https://www.biometricupdate.com/202601/deepfake-as-a-service-revolutionizing-biometrics-spoofing-and-identity-fraud-report">深度伪造即服务报告</a>。</em></p>
]]></content:encoded></item><item><title>语音合成的情绪与韵律:怎么让 AI 不像念稿</title><link>https://realtime-ai.chat/posts/emotion-prosody-tts/</link><pubDate>Sun, 26 Apr 2026 11:00:00 +0800</pubDate><guid>https://realtime-ai.chat/posts/emotion-prosody-tts/</guid><description>TTS 发音已经很准了,但很多还是「念稿感」。这篇讲清韵律和情绪为什么难、怎么控制,以及端到端语音模型在情绪上的天然优势,和过度表演的反效果。</description><content:encoded><![CDATA[<p>把同一句话放给两套 TTS 念:&ldquo;你说得对,这个我之前还真没想到。&rdquo;</p>
<p>两套都咬字清楚、没有杂音、采样率拉满。但其中一套你一听就知道是机器——不是因为它念错了,恰恰是因为它念得太&quot;齐&quot;了。每个字的时长几乎一样,每个停顿都卡在标点上,整句话像被一把尺子量过。</p>
<p>人不是这么说话的。人说&quot;还真没想到&quot;的时候,&ldquo;真&quot;会拖长一点,&ldquo;没&quot;会轻一点,&ldquo;想到&quot;前面会有个几乎听不见的迟疑。这些东西加起来,就是<strong>韵律(prosody)</strong>。发音准是 2020 年就解决的问题;韵律和情绪,才是 2026 年还在啃的硬骨头。</p>
<p>这篇讲清楚:念稿感到底差在哪儿,以及现在有哪几条路去补。</p>
<h2 id="念稿感不是发音问题是韵律问题">念稿感不是发音问题,是韵律问题</h2>
<p>先把概念拆开。一段语音里,&ldquo;说了什么&quot;是文本内容,&ldquo;怎么说的&quot;是韵律。韵律不是一个东西,是四样东西叠在一起:</p>
<ul>
<li><strong>节奏(timing)</strong>——每个音、每个词占多长时间,语速是匀的还是有快有慢。</li>
<li><strong>停顿(pause)</strong>——停在哪里,停多久。停顿不只在逗号句号,也在一个意群说完、或者你要强调下一个词之前。</li>
<li><strong>重音(stress)</strong>——哪个词被加重了。&rdquo;<strong>我</strong>没说过&quot;和&quot;我没<strong>说</strong>过&rdquo;,意思完全不同。</li>
<li><strong>语调(intonation)</strong>——句子的音高曲线。陈述句往下走,疑问句往上挑,反问句又是另一条线。</li>
</ul>
<p>念稿感的根源,是早期 TTS 把这四样都做成了&quot;默认值&rdquo;:语速恒定、停顿只认标点、不分轻重、语调按句型套模板。结果就是每句话都念得四平八稳,像新闻联播里的提示音。</p>
<p>更麻烦的是,韵律和情绪是<strong>强耦合</strong>的。同样一句&quot;你来啦&rdquo;,高兴时音高整体偏高、句尾上扬;敷衍时又平又短;惊讶时第一个字猛地拔高再回落。情绪不是在准确的发音上再&quot;刷一层颜色&rdquo;,情绪本身就是通过韵律表达出来的。所以&quot;TTS 加情感&quot;这件事,本质上是&quot;让 TTS 学会控制韵律&quot;。</p>
<p>学术界很早就指出了拦路的三个问题:<strong>标签依赖</strong>(模型只会照训练时见过的&quot;高兴/悲伤&quot;几个标签走)、<strong>风格纠缠</strong>(想让它变开心,音色也跟着变了)、<strong>控制粒度太粗</strong>(只能整句一个情绪,改不了某个词)。这三条到现在也没完全解决,只是被绕过的方式越来越多。</p>
<h2 id="控制韵律的四条路">控制韵律的四条路</h2>
<p>2026 年要给语音&quot;调情绪&quot;,大致有四种手段,从&quot;最可控&quot;到&quot;最自然&quot;排开,正好是个谱系。</p>
<pre class="mermaid">flowchart LR
  A[SSML / 标记<br/>精确·机械] --> B[参考音频<br/>自然·难复用]
  B --> C[指令式控制<br/>灵活·偏意会]
  C --> D[上下文感知<br/>省心·难调试]
  style A fill:#fde7c2,stroke:#e8b23c
  style D fill:#cde9d6,stroke:#3c9e6a
</pre><p><strong>第一条:SSML 和标记。</strong> 这是最老、也最确定的办法。你用标记语言显式地告诉引擎:这里停 300 毫秒、这个词音高升 10%、这段语速放慢。SSML 至今仍是工业界控制语音合成的事实标准,因为它<strong>可复现</strong>——同样的标记,出来的语音每次都一样。它的代价也明显:你得手动标,标多了维护成本极高,而且本质上是在用规则逼近一个连续的东西,生硬的接缝藏不住。新一代模型在 SSML 之外加了实验性的情绪标签,比如在句首写 <code>[surprised]</code>,让整句带上惊讶的底色——比纯 SSML 省事,但仍然是离散的、一句一个。</p>
<p><strong>第二条:参考音频。</strong> 不告诉模型&quot;要开心&quot;,而是直接丢给它一段开心的录音,让它&quot;照着这个感觉说&quot;。Qwen3-TTS 这类模型 3 秒参考音频就能克隆音色和说话风格,Fish Audio S2 用 10 秒参考做跨语种迁移。这条路的好处是自然——韵律是从真人录音里&quot;抄&quot;来的,不是算出来的。坏处是难复用:你想要一个&quot;略带歉意但又不卑微&quot;的语气,得真去找到或录一段这样的音频,而且参考音频里的情绪和音色经常分不干净。</p>
<p><strong>第三条:指令式控制。</strong> 用自然语言描述你要的效果:&ldquo;用安慰的语气,慢一点,句尾别上扬。&ldquo;这是 LLM 时代才成立的玩法。它灵活到几乎没有边界,你能描述出任何细微的语气。但它也最&quot;意会&rdquo;——同一句指令,不同模型、甚至同一模型不同时候,理解都可能有偏差。它适合创作和探索,不适合需要每次结果一致的生产链路。</p>
<p><strong>第四条:上下文感知。</strong> 前三条都是&quot;你来告诉模型怎么说&rdquo;,这条是&quot;模型自己看着办&quot;。模型读完整段对话历史,自己判断这句该用什么韵律——上一句用户在抱怨,这句回应就自然带上安抚;聊到一半用户开了个玩笑,这句就轻快一点。它把每句话当成对话的一部分,而不是一段孤立的朗读。这是长对话语音最舒服的形态,代价是你几乎放弃了控制权:它念错了情绪,你很难定位是哪一步出的问题。</p>
<p>四条路不是互斥的。现实里常常是混着用:<strong>主体走上下文感知或参考音频拿到自然度,关键节点用 SSML 或情绪标签做精确兜底。</strong> 纯靠某一条都会有明显的短板。</p>
<table>
  <thead>
      <tr>
          <th>手段</th>
          <th>可控性</th>
          <th>自然度</th>
          <th>可复现</th>
          <th>适合场景</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>SSML / 标记</td>
          <td>高</td>
          <td>低</td>
          <td>高</td>
          <td>播报、导航、固定话术</td>
      </tr>
      <tr>
          <td>参考音频</td>
          <td>中</td>
          <td>高</td>
          <td>中</td>
          <td>有声书、特定角色配音</td>
      </tr>
      <tr>
          <td>指令式控制</td>
          <td>中</td>
          <td>中高</td>
          <td>低</td>
          <td>创作工具、原型探索</td>
      </tr>
      <tr>
          <td>上下文感知</td>
          <td>低</td>
          <td>高</td>
          <td>低</td>
          <td>对话 Agent、长篇陪伴</td>
      </tr>
  </tbody>
</table>
<h2 id="端到端模型为什么天生更会演">端到端模型为什么天生更会&quot;演&quot;</h2>
<p>级联式 TTS——文本进、语音出——有个绕不过的结构问题:<strong>情绪信息在文本里就丢了。</strong></p>
<p>用户说了一句带着委屈的话,ASR 把它转成文字,委屈没了;LLM 生成一句回应文本,这段文字本身不携带&quot;该用什么语气念&quot;的信息;TTS 再从这段干巴巴的文字里凭空&quot;猜&quot;情绪。整条链路上,情绪经过了一次&quot;压成文字、再还原成声音&quot;的有损转换。你可以用前面那四条手段把情绪再&quot;贴&quot;回去,但贴回去的,终究是你猜的,不是原本就在那儿的。</p>
<p>端到端语音模型(speech-to-speech)走的是另一条路:语音直接进、语音直接出,中间不落文字。用户语气里的委屈,模型直接&quot;听&quot;到了,生成回应时也直接带着合适的语气出来。情绪从来没被压成文字,自然也就没丢。</p>
<p>这就是端到端在情绪和韵律上的天然优势——不是它的模型更聪明,是它的<strong>信息通路上没有那道有损的瓶颈</strong>。一句话里&quot;说了什么&quot;和&quot;怎么说的&quot;始终捆在一起走。</p>
<pre class="mermaid">flowchart LR
  subgraph 级联
    A1[语音] --> A2[文字<br/>情绪丢失] --> A3[文字回应] --> A4[TTS 猜情绪] --> A5[语音]
  end
  subgraph 端到端
    B1[语音] --> B2[语音回应<br/>情绪全程保留]
  end
  style A2 fill:#f5c6c6,stroke:#c0392b
  style B2 fill:#cde9d6,stroke:#3c9e6a
</pre><p>代价也得说清楚:端到端难调试、难审计、难合规。它没有中间的文字可以打日志、做敏感词过滤、给客服质检看。所以电话客服这种强管控场景,2026 年的默认仍然是级联;但陪伴、互动、娱乐类的产品,端到端那种&quot;接得住情绪&quot;的体感,是级联怎么调都很难追上的。</p>
<h2 id="对话里的韵律笑声犹豫和那些嗯">对话里的韵律:笑声、犹豫和那些&quot;嗯&quot;</h2>
<p>前面讲的还是&quot;把一句话念好&quot;。但真实对话里,韵律的战场不只在句子内部,更在句子之间和词语之外。</p>
<p>人在对话里会发出大量<strong>非词汇的声音</strong>:笑、轻笑、叹气、清嗓子、吸气,还有&quot;嗯&quot;&ldquo;啊&quot;&ldquo;这个&quot;&ldquo;那个&quot;这类填充词。学术上把它们分成几类——生理性的(呼吸、咳嗽)、情绪爆发性的(笑声、叹息),还有<strong>对话管理性的</strong>(填充停顿、应答词)。最后这类最关键:它们不传递字面信息,但传递&quot;我在听&quot;&ldquo;我在想&quot;&ldquo;我有点犹豫&rdquo;。</p>
<p>为什么这些对&quot;不像念稿&quot;特别重要?因为念稿的人不会有这些。念稿是单向输出,对话是双向协调。一段语音里如果完全没有犹豫、没有应答词、没有一丝换气的痕迹,它再准也是在&quot;播报&rdquo;,不是在&quot;对话&rdquo;。</p>
<p>现在面向对话的模型已经开始原生支持这些。Chatterbox-Turbo 内置了 <code>[laugh]</code>、<code>[cough]</code>、<code>[chuckle]</code> 这类标签;Nari Labs 的 Dia 能直接从剧本生成带笑声、叹息的多人对话。研究侧也出现了专门的非词汇发声基准(NVBench、NV-Bench),开始系统地评测模型这方面的能力。</p>
<p>但工程上有个真问题:<strong>这些声音不能硬塞。</strong> 一段回应里&quot;哈哈&quot;加在哪、犹豫的&quot;嗯&quot;放哪,本身就需要韵律和语义的判断。&ldquo;嗯&quot;放在思考一个难问题之前是自然的,放在回答&quot;今天星期几&quot;之前就很怪。笑声接在一个并不好笑的句子后面,比没有笑声更出戏。理想情况下,这些应该是上下文感知模型自己学出来的分布,而不是靠规则往里撒。</p>
<p>我的判断:对话语音的&quot;像不像人&rdquo;,非词汇的部分占的权重,被严重低估了。很多团队把全部精力花在&quot;把字念得更准更自然&rdquo;,但用户感觉别扭,常常是因为整段话<strong>太流畅、太干净了</strong>——干净得不像一个真人在即兴说话。</p>
<h2 id="别演过头过度表演的反效果">别演过头:过度表演的反效果</h2>
<p>最后一条,也是最容易踩的:情绪不是越多越好。</p>
<p>当一套 TTS 终于能&quot;演&quot;了,很多人的第一反应是把它用满——每句话都加情绪标签,高兴就拉满高兴,惊讶就拉满惊讶。结果是另一种不像人:<strong>像一个用力过猛的配音演员,或者一个营业感很重的客服。</strong></p>
<p>真人对话里,大部分句子的情绪其实是<strong>接近中性</strong>的,只是带着一点不易察觉的底色。情绪的高峰是稀疏的、有铺垫的——你不会每句话都惊讶,惊讶之所以是惊讶,是因为前面十句都平。如果每句都拔高,惊讶就不再是惊讶,只是噪音。</p>
<p>业界已经形成共识:情绪标记的正确用法是<strong>克制、刻意,只在对话真正需要的那个点上用</strong>。多轮里满屏标记,听感是戏剧化的、假的。这跟写文章一个道理——感叹号用一个有力量,每句都用就废了。</p>
<p>还有个更隐蔽的坑:<strong>情绪要和内容对得上。</strong> 模型用欢快的语气念一句报错信息,用户感受到的不是&quot;这个 AI 很有活力&rdquo;,是&quot;这个 AI 没听懂我在说什么&quot;。情绪一旦和语义错位,比完全平淡的念稿更糟——平淡只是无聊,错位是诡异。</p>
<p>所以&quot;让 AI 不像念稿&quot;这件事,真正的目标不是&quot;让它充满感情&quot;,而是<strong>让它的情绪和韵律,恰好匹配它在说的内容和它所处的对话</strong>。多数时候,这意味着克制;少数关键时刻,才需要那一下到位的起伏。</p>
<h2 id="怎么落地一个务实的顺序">怎么落地:一个务实的顺序</h2>
<p>如果你在做语音产品,想摆脱念稿感,建议的优先级是:</p>
<ol>
<li><strong>先解决韵律的基本盘</strong>——节奏有变化、停顿不只卡标点、意群之间能换气。这一步不需要&quot;情绪&quot;,做好了念稿感就去掉一大半。</li>
<li><strong>再决定走级联还是端到端</strong>——对话类、容忍度高的产品,认真考虑端到端,它在情绪上的优势是结构性的,不是调参能补的。</li>
<li><strong>情绪控制混着用,别迷信单一手段</strong>——主体拿自然度,关键节点拿可控性。</li>
<li><strong>对话场景补上非词汇的声音</strong>——但让它自然分布,别用规则硬撒。</li>
<li><strong>始终把&quot;克制&quot;放在心里</strong>——情绪的默认值应该接近中性,高峰留给真正需要的时刻。</li>
</ol>
<p>发音准早就不是门槛了。2026 年区分一套语音&quot;像人&quot;还是&quot;像机器&quot;的,是它会不会在该停的地方停、该轻的地方轻、该犹豫的时候犹豫——以及更难的:它知不知道<strong>什么时候该不动声色</strong>。</p>
<hr>
<p><strong>参考资料</strong></p>
<ul>
<li><a href="https://www.bentoml.com/blog/exploring-the-world-of-open-source-text-to-speech-models">The Best Open-Source Text-to-Speech Models in 2026 — BentoML</a></li>
<li><a href="https://dev.to/czmilo/qwen3-tts-the-complete-2026-guide-to-open-source-voice-cloning-and-ai-speech-generation-1in6">Qwen3-TTS:2026 开源语音克隆与生成完整指南</a></li>
<li><a href="https://inworld.ai/resources/best-voice-cloning-api">Best Voice Cloning API for Developers (2026) — Inworld</a></li>
<li><a href="https://inworld.ai/resources/best-tts-long-form-conversations">Best TTS for Long-Form Conversations (2026) — Inworld</a></li>
<li><a href="https://arxiv.org/html/2501.06276v1">PROEMO: Prompt-Driven Text-to-Speech Synthesis Based on Emotion and Intensity Control</a></li>
<li><a href="https://arxiv.org/html/2604.16211v2">NVBench: A Benchmark for Speech Synthesis with Non-Verbal Vocalizations</a></li>
<li><a href="https://medium.com/@brijeshrn/ssml-the-practical-standard-for-controlling-speech-synthesis-c52940314ffa">SSML: The Practical Standard for Controlling Speech Synthesis</a></li>
</ul>
]]></content:encoded></item><item><title>ASR 工程:语音识别落地的那些坑</title><link>https://realtime-ai.chat/posts/asr-engineering/</link><pubDate>Sat, 25 Apr 2026 11:00:00 +0800</pubDate><guid>https://realtime-ai.chat/posts/asr-engineering/</guid><description>ASR demo 很美,落地很坑。流式与非流式怎么选、专有名词和中英混读怎么救、WER 为什么会骗你,这篇把语音识别工程化里真正难的部分讲清楚。</description><content:encoded><![CDATA[<p>你试用一个 ASR 模型,拿手机对着它念一段稿子,转写结果几乎一字不差,你心里想:这事成了。</p>
<p>然后你把它接进真实业务。客户在马路边打来电话,背景是车流声;他说到你公司的产品名,转写出来是三个完全不相干的字;他报了一串订单号&quot;一三幺零&quot;,屏幕上是&quot;一三妖灵&quot;。Demo 里的惊艳,到这里一点都不剩。</p>
<p>问题不在模型菜。开源的 Whisper、各家的流式 ASR,<strong>安静环境下的中文字错率早就压到了 3% 以下</strong>,这已经接近人工转写的水平。坑全在&quot;安静环境、念稿子&quot;这八个字之外——真实的语音是脏的,而工程的活,就是处理这些脏东西。这篇把落地之后才会碰到的坑,一个一个拆开。</p>
<h2 id="第一个岔路口流式还是非流式">第一个岔路口:流式还是非流式</h2>
<p>这是接 ASR 之前必须想清楚的第一件事,选错了后面全是返工。</p>
<p><strong>非流式(批量)</strong>:把一整段音频丢进去,等它整段识别完,返回结果。<strong>流式</strong>:音频一边录一边送,模型一边听一边吐字,你能拿到不断更新的&quot;中间结果&quot;。</p>
<p>差别不只是&quot;快一点慢一点&quot;。它们是两类不同的产品形态:</p>
<table>
  <thead>
      <tr>
          <th>维度</th>
          <th>流式 ASR</th>
          <th>非流式 ASR</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>典型延迟</td>
          <td>首字 200–300ms,说完即出</td>
          <td>等说完后再算,3 秒话约多等 0.5–2 秒</td>
      </tr>
      <tr>
          <td>准确率</td>
          <td>略低(只能看到左边的上下文)</td>
          <td>略高(能看到整句,甚至整段)</td>
      </tr>
      <tr>
          <td>成本</td>
          <td>贵(常见 $0.016/分钟级别)</td>
          <td>便宜(常见 $0.008/分钟级别)</td>
      </tr>
      <tr>
          <td>适用</td>
          <td>语音 Agent、实时字幕、点单</td>
          <td>会议录音转写、客服质检、播客字幕</td>
      </tr>
  </tbody>
</table>
<p>记住一个核心事实:<strong>流式之所以准确率低,是因为它在&quot;说完之前&quot;就得交答案</strong>。它做&quot;我想订一张去——&ldquo;的转写时,根本不知道后面是&quot;北京&quot;还是&quot;报告&rdquo;。非流式没这个包袱,它能等整句听完再回头修正,所以同样的模型,非流式版本的字错率往往低一两个百分点。</p>
<p>这里有个 2026 年很常见、但容易踩反的折中:<strong>用 VAD 把长音频切成&quot;准句子&quot;,每段当成一个 mini-batch 走非流式模型</strong>。这套做法在&quot;实时字幕&quot;这种场景里很香——它不需要逐字滚动,用户能接受&quot;一句一句往外蹦&quot;,于是你既拿到了非流式的准确率,延迟又只是一句话的长度。但如果你做的是语音 Agent,用户在等 AI 回话,那这套就不行——你必须真流式,让 ASR 和用户说话并行跑,说完文字基本也就好了。</p>
<p><strong>别被&quot;我两个都要&quot;骗了。</strong> 一个产品里通常只有一条主链路。先确定用户是不是在&quot;等&quot;,再选。</p>
<h2 id="专有名词中英混读数字错得最离谱的三类">专有名词、中英混读、数字:错得最离谱的三类</h2>
<p>通用 ASR 在通用语料上训练,所以它对&quot;通用的话&quot;很强,对&quot;你的话&quot;很弱。落地后用户投诉最集中的,永远是这三类。</p>
<p><strong>专有名词。</strong> 你公司叫&quot;声析&quot;,模型从没见过这两个字的组合,它会按发音找一个最像的常见词——&ldquo;声息&quot;&ldquo;生气&quot;&ldquo;省吃&rdquo;。这不是模型蠢,是它在做概率上最合理的猜测。人名、产品名、内部黑话、行业术语,全是重灾区。</p>
<p><strong>中英混读。</strong> 中文工程师说话天然混英文:&ldquo;这个 bug 我提了个 PR&quot;&ldquo;把 latency 压下来&rdquo;。问题是很多 ASR 在&quot;语种判定&quot;上是僵的——它先猜你这句是中文还是英文,再用对应的解码路径。猜错了,&ldquo;PR&quot;变成&quot;批 R&rdquo;,&ldquo;latency&quot;变成一串看不懂的拼音。2026 年好一些的方案(比如 Gladia、Deepgram Nova-3)做了<strong>句内 code-switching</strong>,语种切换不需要整句重判;但便宜的、老的模型,中英混读仍然是硬伤。</p>
<p><strong>数字。</strong> 这是最隐蔽的坑。&ldquo;一千二百三十四&quot;应该写成 <code>1234</code> 还是&quot;一千二百三十四&rdquo;?电话号码、订单号、金额、日期,各有各的格式。这件事叫<strong>逆文本规整(ITN,Inverse Text Normalization)</strong>——把&quot;口语形式&quot;转成&quot;书面形式&rdquo;。ITN 做得糙,用户就会看到&quot;我要订 2026 年 4 月 25 号&quot;被写成&quot;二零二六年四月二十五号&rdquo;,或者反过来把门牌号&quot;幺零幺&quot;写成&quot;101&rdquo;——而它其实是房间名。</p>
<p>这三类问题的通用解药是<strong>热词 / 定制词表</strong>,下一节单讲。但要先有个心理预期:<strong>通用 ASR 在你的垂直场景里,开箱就是会错,而且错得很集中。</strong> 上线前必须拿你自己的真实音频跑一遍,把高频错词捞出来。</p>
<h2 id="噪声和远场训练集里没有的世界">噪声和远场:训练集里没有的世界</h2>
<p>录音棚的音频干净到不真实。真实音频里有空调声、键盘声、车流、回声、别人说话。</p>
<p>这里要分清两个不同的问题:</p>
<ul>
<li><strong>噪声(near-field + noise)</strong>:用户离麦克风很近,但环境吵。手机贴着脸打电话属于这类。现代 ASR 对平稳噪声(空调、风扇)已经相当稳,因为训练数据里加了大量噪声增强。难的是<strong>非平稳噪声</strong>——突然的关门声、背景音乐、旁边一句人声。</li>
<li><strong>远场(far-field)</strong>:用户离麦克风一两米,信号本身就弱,还叠加了房间混响。智能音箱、会议室麦克风阵列属于这类。远场的难点不是&quot;吵&rdquo;,是&quot;糊&quot;——混响把语音的时间结构抹掉了。</li>
</ul>
<p>工程上,<strong>别指望单靠 ASR 模型扛下这些</strong>。真正有效的是前面那一段链路:</p>
<pre class="mermaid">flowchart LR
  A[麦克风阵列] --> B[波束成形<br/>对准说话人]
  B --> C[降噪 / 去混响]
  C --> D[AGC<br/>自动增益]
  D --> E[VAD<br/>切掉静音段]
  E --> F[ASR]
  style B fill:#fde7c2,stroke:#e8b23c
  style C fill:#fde7c2,stroke:#e8b23c
</pre><p>橙色的两块——波束成形和降噪去混响——是远场场景里 ASR 准确率的真正杠杆。一个被忽略的细节是 <strong>VAD 的位置</strong>:它把长段静音和纯噪声段切掉,不送进 ASR。这既省钱,又能防止 ASR 在没人说话时&quot;幻听&quot;出几个字来。Whisper 这类模型在长静音上幻听是出了名的,VAD 是最便宜的止损。</p>
<p>还有一个反直觉的点:<strong>降噪不是越狠越好</strong>。激进的降噪会把语音本身的高频细节也削掉,ASR 反而更难听清。降噪是为人耳服务还是为 ASR 服务,目标不一样,参数也不该一样。</p>
<h2 id="标点顺滑热词让转写能用的后处理">标点、顺滑、热词:让转写&quot;能用&quot;的后处理</h2>
<p>原始的 ASR 输出是一长串没有标点、保留了所有口水词的文字流。它&quot;对&quot;,但不&quot;能用&quot;。中间隔着三层后处理。</p>
<p><strong>标点恢复。</strong> 给文字流加上逗号句号问号。没有标点的转写,人读着累,喂给下游 LLM 也容易断句错误。</p>
<p><strong>顺滑(disfluency removal)。</strong> 真实口语里全是&quot;嗯&quot;&ldquo;那个&quot;&ldquo;就是说&rdquo;,还有&quot;我要去北——啊不是,去南京&quot;这种自我修正。顺滑就是把这些口水词和修正痕迹删掉,留下干净的意思。这件事在&quot;会议纪要&quot;&ldquo;客服质检&quot;里是刚需——没人想看逐字的&quot;嗯嗯啊啊&rdquo;。但在另一些场景里<strong>你恰恰不能顺滑</strong>:比如做语言学研究、或者要分析用户的犹豫和情绪,口水词本身就是信号。所以顺滑要做成<strong>可开关的</strong>,别在管道里写死。</p>
<p>2026 年的趋势是把标点、ITN、大小写、顺滑这四件事<strong>用一个统一的标注模型一次做完</strong>,而不是串四个独立模块——后者每一层的误差会往下传。</p>
<p><strong>热词 / 定制词表</strong>,这是对付上一节那三类错误最直接的手段。原理是在解码时,对词表里的词做<strong>上下文偏置(contextual biasing)</strong>——让模型在&quot;声息&quot;和&quot;声析&quot;之间犹豫时,因为&quot;声析&quot;在你的热词表里,而把概率掰向它。</p>
<p>热词不是免费的,有几个坑:</p>
<ol>
<li><strong>热词表不能无限长。</strong> 表越大,偏置越稀,而且容易&quot;过纠&rdquo;——把本来对的常见词改成热词。几百到几千个词是合理区间,上万个就要换成&quot;按场景动态注入&quot;的检索式方案。</li>
<li><strong>热词要给对发音。</strong> &ldquo;GPU&quot;这个词,用户可能念&quot;G-P-U&rdquo;,也可能念&quot;鸡皮优&quot;。热词只配字面,模型对不上音,偏置就不生效。</li>
<li><strong>热词救不了&quot;完全没听清&quot;。</strong> 它只在模型已经有几个候选、正在犹豫时起作用。如果信号烂到模型根本没把你的词列进候选,热词无能为力——那是降噪和远场该解决的问题。</li>
</ol>
<h2 id="多人说话转写之外的另一道题">多人说话:转写之外的另一道题</h2>
<p>一对一的语音 Agent 不用操心这个。但只要场景里有两个以上的人——会议、多人客服、访谈——你就多了一道题:<strong>说话人分离(speaker diarization)</strong>,也就是&quot;谁在什么时候说了什么&quot;。</p>
<p>这是和 ASR 正交的另一个能力。ASR 回答&quot;说了什么&quot;,diarization 回答&quot;是谁说的&quot;。两件事各自都不简单,叠在一起更难,难点集中在:</p>
<ul>
<li><strong>重叠语音</strong>:两个人同时开口。这是 diarization 错得最多的地方,人耳都未必分得清。</li>
<li><strong>说话人数未知</strong>:你事先不知道这场会有几个人,模型得自己估。</li>
<li><strong>音色相近</strong>:两个声线接近的人,容易被归成同一个。</li>
</ul>
<p>2026 年一个值得关注的变化是<strong>联合建模</strong>:微软 1 月开源的 VibeVoice-ASR 把转写、说话人分离、时间戳放在一次前向里一起做,一遍处理 60 分钟音频。这条路线的好处是各个子任务能互相借力——知道&quot;说话人换了&quot;,有助于判断句子边界;反过来也成立。如果你的场景重度依赖 diarization,这类联合模型比&quot;ASR + 一个独立的 diarization 模块&quot;串起来要省心。</p>
<p>但提醒一句:<strong>diarization 的错误会污染下游</strong>。如果你拿带说话人标签的转写去做&quot;客服话术分析&quot;,标签错了,整份分析的归因就错了。先评估你的场景对说话人准确率的真实要求,再决定要不要上。</p>
<h2 id="wer-会骗你关于评测的真话">WER 会骗你:关于评测的真话</h2>
<p>聊到这里必须说说那个所有人都在用、但被严重高估的指标——<strong>WER(词错误率)</strong>。</p>
<p>WER 的算法是:把识别结果和标准答案对齐,数出替换、删除、插入三类错误,除以总词数。听起来很客观。但它有几个会让你做错决策的毛病:</p>
<p><strong>第一,WER 把所有错误当成一样重。</strong> &ldquo;我要订机票&quot;识别成&quot;我要定机票&rdquo;,和识别成&quot;我不要订机票&quot;,WER 给的惩罚可能差不多。但对你的业务,一个无所谓,一个是事故。<strong>关键信息(数字、人名、否定词、专有名词)错一个,比十个虚词错都严重。</strong></p>
<p><strong>第二,中文用 WER 本身就不对。</strong> 中文没有天然的词边界,&ldquo;语音识别&quot;算一个词还是两个词,取决于分词器。分词器一换,WER 就变。所以中文场景应该看 <strong>CER(字错误率)</strong>——按字算,跨分词器稳定。中英混读场景再进一步,业界用 <strong>MER(混合错误率)</strong>:中文按字、英文按词。你要是还在拿 WER 横向比中文 ASR,比出来的排名是不可信的。</p>
<p><strong>第三,WER 对&quot;语义等价&quot;零容忍。</strong> &ldquo;2026 年&quot;和&quot;二零二六年&rdquo;、&ldquo;OK&quot;和&quot;okay&rdquo;,意思完全一样,WER 照样判错。一个 ITN 风格不同的模型,WER 会无端偏高。</p>
<p>那该怎么评测自己场景的 ASR?给一份可以照着做的清单:</p>
<ol>
<li><strong>用你自己的真实音频建测试集</strong>,别用模型方给的 benchmark。至少几百条,覆盖你真实的口音、噪声、设备、语速分布。这一步最花时间,也最值钱。</li>
<li><strong>中文看 CER,中英混读看 MER</strong>,不要用 WER。</li>
<li><strong>单独算&quot;关键实体错误率&rdquo;</strong>:把数字、人名、产品名、否定词单拎出来统计准确率。这个数字比总体 CER 更能预测线上投诉量。</li>
<li><strong>分桶看,别只看一个平均数</strong>。按噪声等级、按远近场、按是否中英混读分别算 CER。平均数会把&quot;安静场景拉高、嘈杂场景拖垮&quot;这种结构性问题藏起来。</li>
<li><strong>测延迟,而且测的是首字延迟和稳定时间</strong>,不是整段耗时。流式 ASR 的中间结果会反复跳变,你要测的是&quot;结果多久不再变&quot;。</li>
<li><strong>做人工抽检</strong>。CER 降了 0.5%,但如果新错的全是关键词,体验是变差的。指标之外,定期让人看一眼真实转写。</li>
</ol>
<h2 id="最后把顺序理对">最后:把顺序理对</h2>
<p>如果你正要把 ASR 落地,优化的优先级应该是这样:</p>
<p><strong>先选对流式 / 非流式。</strong> 这是产品形态决策,选错了后面全白干。判断标准只有一个——用户是不是在等。</p>
<p><strong>再用真实音频建评测集。</strong> 没有这个,你后面所有的&quot;优化&quot;都是在拍脑袋。它还会直接告诉你高频错词是哪些。</p>
<p><strong>然后配热词、调 ITN、补降噪。</strong> 针对评测集暴露的具体问题对症下药——错词集中就上热词,数字老错就调 ITN,嘈杂场景拖后腿就补前端降噪。</p>
<p><strong>最后才考虑换模型或微调。</strong> 这是最贵、最慢的一步,而且很多时候,前面三步做扎实了,根本轮不到这一步。</p>
<p>ASR 落地真正难的,从来不是模型本身——模型早就够好了。难的是真实世界的语音很脏,而你的业务对&quot;哪种错误不可接受&quot;有自己的定义。把脏数据处理干净,把评测对齐到你的业务,模型才能真正帮上忙。</p>
<hr>
<p>参考与延伸阅读:</p>
<ul>
<li><a href="https://smallest.ai/blog/how-to-evaluate-asr-in-2026">How to Evaluate ASR in 2026: Accuracy, Latency and Cost — Smallest.ai</a></li>
<li><a href="https://www.gladia.io/blog/best-speech-to-text-apis">Best Speech-to-Text APIs in 2026 — Gladia</a></li>
<li><a href="https://www.assemblyai.com/blog/assemblyai-vs-deepgram-best-voice-agent-api">AssemblyAI vs Deepgram: Best Voice Agent API</a></li>
<li><a href="https://huggingface.co/microsoft/VibeVoice-ASR">microsoft/VibeVoice-ASR — Hugging Face</a></li>
<li><a href="https://arxiv.org/pdf/2512.21828">Contextual Biasing for LLM-Based ASR with Hotword Retrieval and Reinforcement Learning</a></li>
<li><a href="https://www.researchgate.net/publication/384811643_Advocating_Character_Error_Rate_for_Multilingual_ASR_Evaluation">Advocating Character Error Rate for Multilingual ASR Evaluation</a></li>
<li><a href="https://arxiv.org/pdf/2210.15063">Four-in-One: A Joint Approach to ITN, Punctuation, Capitalization, and Disfluency</a></li>
</ul>
]]></content:encoded></item><item><title>实时语音 API 横评:OpenAI、Gemini 与国内</title><link>https://realtime-ai.chat/posts/realtime-voice-api/</link><pubDate>Sun, 19 Apr 2026 11:00:00 +0800</pubDate><guid>https://realtime-ai.chat/posts/realtime-voice-api/</guid><description>做语音 Agent 该选 OpenAI Realtime、Gemini Live 还是国内方案?这篇按延迟、打断、音色、价格、可控性、合规八个维度横评主流实时语音 API,并给出选型建议。</description><content:encoded><![CDATA[<p>先说一个反直觉的事实:2026 年了,真正跑在生产环境、扛着电话客服流量的语音 Agent,大多数<strong>还不是</strong>端到端语音 API 做的。</p>
<p>端到端听起来无可挑剔——语音直接进、语音直接出,中间不落文字,延迟低、情感保留好。OpenAI 的 gpt-realtime、Google 的 Gemini Live、豆包的端到端实时语音大模型,demo 都惊艳。但真把它塞进一个要上线的产品里,你会在第二周撞上几堵墙:它说错话你没法在中间拦一道、合规团队要审通话记录而你只有一段音频、客户要换个特定音色而 API 只给你 8 个预设。</p>
<p>所以选型这件事,不能只看 demo 的&quot;哇&quot;。这篇把实时语音 API 的关键维度摊开,再把 OpenAI、Gemini 和国内几家的真实定位讲清楚,最后按场景给建议。</p>
<h2 id="先把关键维度对齐">先把&quot;关键维度&quot;对齐</h2>
<p>挑实时语音 API,大家张口就是&quot;延迟低不低&quot;。延迟当然重要,但它只是八个维度里的一个。我把这八个维度列出来,你拿任何一个 API 去套都不会漏:</p>
<table>
  <thead>
      <tr>
          <th>维度</th>
          <th>它在问什么</th>
          <th>容易被忽略的点</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>延迟</td>
          <td>用户说完到 AI 出声多久</td>
          <td>看的是首包,不是整句生成完</td>
      </tr>
      <tr>
          <td>打断</td>
          <td>能不能被插话、插得干不干净</td>
          <td>误打断(噪音触发)比慢更恼人</td>
      </tr>
      <tr>
          <td>音色</td>
          <td>有多少声音、能不能定制/克隆</td>
          <td>预设音色撑不起品牌化产品</td>
      </tr>
      <tr>
          <td>语言</td>
          <td>支持哪些语种、能否中途混说</td>
          <td>方言、中英混说是国内刚需</td>
      </tr>
      <tr>
          <td>价格</td>
          <td>每分钟多少钱、缓存能省多少</td>
          <td>端到端按音频 token 计费,贵</td>
      </tr>
      <tr>
          <td>是否端到端</td>
          <td>一个模型还是 ASR+LLM+TTS</td>
          <td>决定了下面两项</td>
      </tr>
      <tr>
          <td>可控性</td>
          <td>能不能拦、能不能调试、能不能换</td>
          <td>端到端是黑盒,这点最痛</td>
      </tr>
      <tr>
          <td>合规</td>
          <td>有没有文字记录可审计、数据落哪</td>
          <td>金融/政务直接卡死非合规方案</td>
      </tr>
  </tbody>
</table>
<p>后面三项——是否端到端、可控性、合规——是连在一起的一条逻辑链,也是真正决定选型的地方。延迟和音色反而是&quot;达标就行&quot;的项。</p>
<h2 id="openai-realtime能力最强也最贵">OpenAI Realtime:能力最强,也最贵</h2>
<p>OpenAI 的 Realtime API 用的是 gpt-realtime 这个 speech-to-speech 模型,语音直接进出,一个模型一个接口搞定。它的强项是<strong>指令遵循和工具调用</strong>——你给它一段复杂的 system prompt、挂十个函数,它能稳稳地按规矩走、该调哪个调哪个。这一点上,目前没有对手。</p>
<p>但价格要算清楚。gpt-realtime 按音频 token 计费,输入大约 $32 / 百万 token、输出 $64 / 百万 token。换算成每分钟:用户说话约 600 token/分钟,AI 说话约 1200 token/分钟。一个典型的客服对话(AI 说得比用户多),不开缓存的真实成本落在每分钟 $0.18 到 $0.46 之间——折合人民币一两块到三块多一分钟。</p>
<p>这个数字什么概念?一个日均 1000 通、每通 5 分钟的客服线,光语音 API 一个月就是几万到十几万人民币。所以 OpenAI 自己也反复强调 <strong>prompt caching</strong>:把固定的 system prompt 和工具定义缓存住,成本能压到每分钟 $0.05–$0.10。能不能用好缓存,直接决定这套方案在你这儿是不是&quot;用得起&quot;。</p>
<p>一句话定位:<strong>能力天花板最高,适合复杂任务、预算不敏感、面向海外用户的产品</strong>。中文场景它能用,但音色和语气的&quot;中文味&quot;不如国内方案地道,而且数据出境对国内合规是硬伤。</p>
<h2 id="gemini-live语言覆盖广生态绑得紧">Gemini Live:语言覆盖广,生态绑得紧</h2>
<p>Google 的 Gemini Live API 走的也是 native audio(端到端原生音频)路线,旗舰模型是 Gemini 3.1 Flash Live,主打低延迟双向语音、亚秒级音频流式输出。</p>
<p>它最突出的两点:<strong>语言覆盖</strong>和<strong>情感对话</strong>。Live API 支持 70 种语言的输入,原生音频模型提供 24 种语言、30 个 HD 音色,还带一个叫 affective dialog 的能力——会根据你说话的语气调整自己的回应风格。另外它有 proactive audio,能控制&quot;什么时候该 AI 开口、什么时候该闭嘴&quot;,对付嘈杂环境下的误触发有用。</p>
<p>代价是<strong>生态绑定</strong>。Gemini Live 跑得最顺的姿势是接进 Google 自家的体系——Vertex AI、Firebase AI Logic、Android。你要是已经在 Google Cloud 上,它顺理成章;你要是个独立的中国团队,接入和合规的摩擦都不小。</p>
<p>一句话定位:<strong>多语言、出海、且本来就在 Google 生态里的团队首选</strong>;否则生态税不便宜。</p>
<h2 id="国内方案端到端追上来了合规是主场">国内方案:端到端追上来了,合规是主场</h2>
<p>如果你的产品主要面向国内用户,大概率绕不开国内 API,原因很简单:<strong>数据不出境</strong>这一条,海外方案直接出局。好在国内这两年追得很快。</p>
<ul>
<li><strong>豆包端到端实时语音大模型</strong>(火山引擎):真正的端到端 speech-to-speech,中文语境下的语气、情感、方言识别是它的主场——能听懂二十多种方言混着说,能保留口语里的吞音、口音。计费按音频 token 折算(用户输入约 6.25 token/秒,输出音频约 25 token/秒),开了 cache 之后中文场景的性价比明显比 OpenAI 好。要注意它有 QPM/TPM 限流,上量前得先谈配额。它还支持付费定制音色——这点对要做品牌化产品的团队很关键。</li>
<li><strong>MiniMax Speech 2.6</strong>:端到端延迟做到 250ms 以下,40 多种语言、支持 zero-shot 声音克隆,语音质量在国内第一梯队。</li>
<li><strong>阶跃 Step Realtime API</strong>:基于百亿参数的端到端语音模型 Step-1o-Audio,主打超低延迟和双向打断。</li>
<li><strong>阿里通义</strong>:走的更偏&quot;组件&quot;路线——比如 Qwen3-ASR-Flash-Realtime 是流式 ASR,适合你自己拼级联的时候拿来当其中一块。</li>
</ul>
<p>国内方案的整体判断:<strong>端到端能力已经够用,中文表现甚至更地道,真正的护城河是合规和本地化支持</strong>(出问题能找到人、能签数据处理协议)。短板是出海语种和海外节点不如 OpenAI/Google。</p>
<h2 id="自己拼级联-vs-用端到端-api">自己拼级联 vs 用端到端 API</h2>
<p>这是选型里最纠结的一道题。把两条路线摊开看:</p>
<pre class="mermaid">flowchart TB
  subgraph E2E[端到端 API]
    direction LR
    A1[语音输入] --> A2[speech-to-speech 模型] --> A3[语音输出]
  end
  subgraph CAS[自拼级联]
    direction LR
    B1[语音输入] --> B2[流式 ASR] --> B3[LLM] --> B4[流式 TTS] --> B5[语音输出]
  end
  style A2 fill:#fde7c2,stroke:#e8b23c
  style B3 fill:#cfe8d5,stroke:#5fa777
</pre><p><strong>端到端 API</strong> 的好处是省心:一个接口,延迟天然更低(少了模块间的转接),情感和韵律保留得好(信息没在&quot;转文字再转回来&quot;的过程里丢)。坏处是它是个黑盒——</p>
<ul>
<li>AI 说错话,你<strong>没有一个文字中间层</strong>可以拦截、改写、过滤敏感词;</li>
<li>出了 bug,你不知道是&quot;听错了&quot;还是&quot;想错了&quot;,难定位;</li>
<li>想换模型?整个交互逻辑都绑在这家 API 上,迁移成本高;</li>
<li>合规要审计通话内容,你手上只有音频,得再补一道转写。</li>
</ul>
<p><strong>自拼级联</strong>(流式 ASR → LLM → 流式 TTS)正好相反:三个模块各自独立、可观测、可替换。LLM 那一环你能插审核、能换模型、能做 RAG;ASR 出来的文字天然就是审计记录。代价是延迟预算更紧(模块间多了转接),工程上要把每一环都做成流式、串好,任何一环&quot;攒齐再传&quot;整条链路就塌了——这部分我在<a href="/posts/voice-technology/voice-latency-budget/">上一篇延迟预算</a>里拆得很细。</p>
<p>我的判断:<strong>别把它当非此即彼</strong>。</p>
<ul>
<li>强管控、强合规的场景(电话客服、金融、政务)——<strong>用级联</strong>。你需要那个文字中间层,不是为了延迟,是为了&quot;能拦、能审、能换&quot;。</li>
<li>Web 端的陪伴、教育、互动娱乐——<strong>上端到端</strong>。这些场景吃的是情感和自然度,黑盒一点可以接受,延迟低反而是卖点。</li>
<li>有规模的团队,最后大概率<strong>两套都跑,按场景路由</strong>:简单闲聊走端到端,涉及交易、要落库审计的环节切回级联。</li>
</ul>
<h2 id="按场景给一份选型建议">按场景给一份选型建议</h2>
<p>把上面的东西收敛成一张可以直接用的表:</p>
<table>
  <thead>
      <tr>
          <th>你的场景</th>
          <th>推荐路线</th>
          <th>首选 API</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>国内电话客服 / 金融政务</td>
          <td>自拼级联</td>
          <td>国内流式 ASR(如通义 Qwen3-ASR)+ 国产 LLM + 国内 TTS</td>
      </tr>
      <tr>
          <td>国内 C 端陪伴 / 教育互动</td>
          <td>端到端</td>
          <td>豆包端到端实时语音、MiniMax、阶跃</td>
      </tr>
      <tr>
          <td>出海产品 / 多语言</td>
          <td>端到端</td>
          <td>Gemini Live(语种最广)或 OpenAI Realtime</td>
      </tr>
      <tr>
          <td>复杂任务 Agent(多工具、强指令)</td>
          <td>端到端</td>
          <td>OpenAI Realtime(指令遵循最强)</td>
      </tr>
      <tr>
          <td>预算敏感 / 走量</td>
          <td>级联或端到端+缓存</td>
          <td>算清每分钟成本,务必上 prompt caching</td>
      </tr>
  </tbody>
</table>
<p>几条收尾的提醒:</p>
<p>第一,<strong>先问合规,再聊技术</strong>。如果你的数据不能出境,海外两家在第一轮就该被划掉,不用再纠结它们 demo 多惊艳。</p>
<p>第二,<strong>价格一定要按&quot;开缓存后&quot;算</strong>。不开缓存的 headline price 没有参考意义,真实生产环境一定是带缓存跑的,两者能差三到五倍。</p>
<p>第三,<strong>别为了 demo 的惊艳付黑盒的代价</strong>。端到端那种&quot;丝滑感&quot;很诱人,但你的产品如果需要拦一句话、审一段记录、换一个模型,这些能力级联方案现成就有,端到端要你自己补,而且补不齐。</p>
<p>实时语音 API 这个赛道 2026 年还在快速变,价格、模型几个月一变。但选型的底层逻辑是稳的:<strong>先用合规和可控性把候选名单砍短,再在剩下的里面比延迟和价格</strong>。顺序反了,你会在一个最终用不了的方案上,白白调优三个月。</p>
]]></content:encoded></item><item><title>TTS模型微调：用自己的声音训练语音模型</title><link>https://realtime-ai.chat/posts/tts-finetuning/</link><pubDate>Fri, 16 Jan 2026 10:00:00 +0800</pubDate><guid>https://realtime-ai.chat/posts/tts-finetuning/</guid><description>TTS 模型微调实战:用 XTTS、Fish Speech 训练你自己的声音,语音克隆的完整步骤。</description><content:encoded><![CDATA[<h2 id="两个主流开源方案">两个主流开源方案</h2>
<table>
  <thead>
      <tr>
          <th>模型</th>
          <th>特点</th>
          <th>数据需求</th>
          <th>显存要求</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>XTTS v2</td>
          <td>多语言，效果稳定</td>
          <td>2-20分钟</td>
          <td>12GB+</td>
      </tr>
      <tr>
          <td>Fish Speech</td>
          <td>中文效果好，速度快</td>
          <td>3-10秒起</td>
          <td>4GB+</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="方案一xtts微调">方案一：XTTS微调</h2>
<h3 id="准备工作">准备工作</h3>
<p><strong>硬件要求</strong>：</p>
<ul>
<li>GPU：12GB显存以上（推荐16GB）</li>
<li>内存：16GB以上</li>
</ul>
<p><strong>数据要求</strong>：</p>
<ul>
<li>至少2-3分钟清晰录音</li>
<li>推荐5-20分钟效果更好</li>
<li>WAV格式，16kHz以上</li>
</ul>
<h3 id="安装">安装</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone https://github.com/daswer123/xtts-finetune-webui
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> xtts-finetune-webui
</span></span><span class="line"><span class="cl">pip install -r requirements.txt
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="数据格式">数据格式</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">dataset/
</span></span><span class="line"><span class="cl">├── audio/
</span></span><span class="line"><span class="cl">│   ├── 001.wav
</span></span><span class="line"><span class="cl">│   ├── 002.wav
</span></span><span class="line"><span class="cl">│   └── ...
</span></span><span class="line"><span class="cl">└── metadata.csv
</span></span></code></pre></td></tr></table>
</div>
</div><p>metadata.csv 格式：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">audio_file|text|speaker_name
</span></span><span class="line"><span class="cl">audio/001.wav|今天天气真不错。|my_voice
</span></span><span class="line"><span class="cl">audio/002.wav|我们去公园散步吧。|my_voice
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="训练配置">训练配置</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># 关键参数</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">batch_size</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">          </span><span class="c"># 显存不够就调小</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">epochs</span><span class="p">:</span><span class="w"> </span><span class="m">10-50</span><span class="w">          </span><span class="c"># 数据少就多跑几轮</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">learning_rate</span><span class="p">:</span><span class="w"> </span><span class="m">5e-6</span><span class="w">    </span><span class="c"># 别调太大，容易过拟合</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="常见问题">常见问题</h3>
<p><strong>问题1：训练后声音变奇怪</strong>
→ 过拟合了，减少epochs或增加数据</p>
<p><strong>问题2：声音不像</strong>
→ 数据太少或质量不好，检查录音</p>
<p><strong>问题3：显存不够</strong>
→ 减小batch_size，或用gradient accumulation</p>
<hr>
<h2 id="方案二fish-speech微调">方案二：Fish Speech微调</h2>
<p>Fish Speech对中文友好，而且显存要求低。</p>
<h3 id="安装-1">安装</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone https://github.com/fishaudio/fish-speech
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> fish-speech
</span></span><span class="line"><span class="cl">pip install -e .
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="零样本克隆不用训练">零样本克隆（不用训练）</h3>
<p>Fish Speech支持用3-10秒音频直接克隆：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">fish_speech</span> <span class="kn">import</span> <span class="n">FishSpeech</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">model</span> <span class="o">=</span> <span class="n">FishSpeech</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 用参考音频生成</span>
</span></span><span class="line"><span class="cl"><span class="n">audio</span> <span class="o">=</span> <span class="n">model</span><span class="o">.</span><span class="n">generate</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">text</span><span class="o">=</span><span class="s2">&#34;这是克隆后的声音&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">reference_audio</span><span class="o">=</span><span class="s2">&#34;reference.wav&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="微调效果更好">微调（效果更好）</h3>
<p>如果想要更像的效果，可以微调：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># 准备数据</span>
</span></span><span class="line"><span class="cl">python tools/prepare_data.py --input-dir ./my_audio --output-dir ./dataset
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 开始微调</span>
</span></span><span class="line"><span class="cl">python train.py --config configs/finetune.yaml --data-dir ./dataset
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="推理">推理</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># 使用微调后的模型</span>
</span></span><span class="line"><span class="cl"><span class="n">audio</span> <span class="o">=</span> <span class="n">model</span><span class="o">.</span><span class="n">generate</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">text</span><span class="o">=</span><span class="s2">&#34;现在声音更像了&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">voice_id</span><span class="o">=</span><span class="s2">&#34;my_custom_voice&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><hr>
<h2 id="对比选择">对比选择</h2>
<table>
  <thead>
      <tr>
          <th>场景</th>
          <th>推荐</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>快速验证</td>
          <td>Fish Speech（零样本）</td>
      </tr>
      <tr>
          <td>中文场景</td>
          <td>Fish Speech</td>
      </tr>
      <tr>
          <td>多语言</td>
          <td>XTTS</td>
      </tr>
      <tr>
          <td>最高质量</td>
          <td>XTTS微调20分钟数据</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="训练技巧">训练技巧</h2>
<h3 id="1-不要贪多">1. 不要贪多</h3>
<ul>
<li>10分钟高质量数据 &gt; 1小时有底噪数据</li>
</ul>
<h3 id="2-监控过拟合">2. 监控过拟合</h3>
<ul>
<li>训练loss下降但生成效果变差 → 停止训练</li>
</ul>
<h3 id="3-多做对比">3. 多做对比</h3>
<ul>
<li>保存多个checkpoint，对比选最好的</li>
</ul>
<h3 id="4-参考音频很重要">4. 参考音频很重要</h3>
<ul>
<li>XTTS生成时用的参考音频影响很大</li>
<li>选一段最清晰、最有代表性的</li>
</ul>
<hr>
<h2 id="部署">部署</h2>
<p>训练好的模型可以用API服务起来：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">fastapi</span> <span class="kn">import</span> <span class="n">FastAPI</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">fish_speech</span> <span class="kn">import</span> <span class="n">FishSpeech</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">FastAPI</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">model</span> <span class="o">=</span> <span class="n">FishSpeech</span><span class="p">(</span><span class="n">checkpoint</span><span class="o">=</span><span class="s2">&#34;my_model&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@app.post</span><span class="p">(</span><span class="s2">&#34;/tts&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">generate_speech</span><span class="p">(</span><span class="n">text</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">audio</span> <span class="o">=</span> <span class="n">model</span><span class="o">.</span><span class="n">generate</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;audio&#34;</span><span class="p">:</span> <span class="n">audio</span><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><hr>
<p>有问题留言。</p>
]]></content:encoded></item><item><title>TTS数据准备：从录音到训练的完整流程</title><link>https://realtime-ai.chat/posts/tts-data-preparation/</link><pubDate>Thu, 15 Jan 2026 10:00:00 +0800</pubDate><guid>https://realtime-ai.chat/posts/tts-data-preparation/</guid><description>TTS 数据准备完整流程:从录音、采样率到清洗标注,数据质量决定 80% 的语音合成效果。</description><content:encoded><![CDATA[<h2 id="数据决定上限">数据决定上限</h2>
<p>TTS模型效果好不好，80%取决于数据质量。</p>
<p><strong>常见问题：</strong></p>
<ul>
<li>录音有底噪 → 合成出来有杂音</li>
<li>音量不稳定 → 合成忽大忽小</li>
<li>断句不自然 → 合成节奏奇怪</li>
</ul>
<hr>
<h2 id="录音要求">录音要求</h2>
<h3 id="硬件">硬件</h3>
<table>
  <thead>
      <tr>
          <th>设备</th>
          <th>推荐</th>
          <th>预算</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>麦克风</td>
          <td>电容麦（如AT2020）</td>
          <td>¥500-1500</td>
      </tr>
      <tr>
          <td>声卡</td>
          <td>独立声卡或USB麦</td>
          <td>¥300-800</td>
      </tr>
      <tr>
          <td>环境</td>
          <td>安静房间+吸音棉</td>
          <td>¥100-300</td>
      </tr>
  </tbody>
</table>
<h3 id="录音参数">录音参数</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">采样率: 48kHz（至少16kHz）
</span></span><span class="line"><span class="cl">位深: 24-bit
</span></span><span class="line"><span class="cl">格式: WAV（无损）
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="录音技巧">录音技巧</h3>
<ol>
<li><strong>距离</strong>：麦克风离嘴15-20cm</li>
<li><strong>音量</strong>：保持-12dB到-6dB之间</li>
<li><strong>状态</strong>：正常语速，自然呼吸</li>
<li><strong>时长</strong>：至少2小时（5-10小时效果更好）</li>
</ol>
<hr>
<h2 id="数据清洗流程">数据清洗流程</h2>
<pre class="mermaid">graph LR
    A[原始音频] --> B[降噪]
    B --> C[音量标准化]
    C --> D[切分句子]
    D --> E[对齐文本]
    E --> F[质量检查]
    F --> G[训练数据]
</pre><h3 id="1-降噪">1. 降噪</h3>
<p><strong>工具</strong>：Audacity（免费）、Adobe Podcast（在线）</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># 使用ffmpeg + rnnoise降噪</span>
</span></span><span class="line"><span class="cl">ffmpeg -i input.wav -af <span class="s2">&#34;arnndn=m=rnnoise-models/bd.rnnn&#34;</span> output.wav
</span></span></code></pre></td></tr></table>
</div>
</div><blockquote>
<p>⚠️ 注意：过度降噪会导致声音失真，宁可保留少量底噪</p></blockquote>
<h3 id="2-音量标准化">2. 音量标准化</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># 标准化到-16 LUFS</span>
</span></span><span class="line"><span class="cl">ffmpeg -i input.wav -af <span class="nv">loudnorm</span><span class="o">=</span><span class="nv">I</span><span class="o">=</span>-16:TP<span class="o">=</span>-1.5:LRA<span class="o">=</span><span class="m">11</span> output.wav
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="3-切分句子">3. 切分句子</h3>
<p><strong>原则</strong>：一个音频片段 = 一句话（3-15秒）</p>
<p><strong>工具</strong>：</p>
<ul>
<li><strong>Whisper</strong>：自动转写+时间戳</li>
<li><strong>UVR</strong>：人声分离（去除背景音乐）</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># 使用Whisper获取时间戳</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">whisper</span>
</span></span><span class="line"><span class="cl"><span class="n">model</span> <span class="o">=</span> <span class="n">whisper</span><span class="o">.</span><span class="n">load_model</span><span class="p">(</span><span class="s2">&#34;medium&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">model</span><span class="o">.</span><span class="n">transcribe</span><span class="p">(</span><span class="s2">&#34;audio.wav&#34;</span><span class="p">,</span> <span class="n">word_timestamps</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="4-文本对齐">4. 文本对齐</h3>
<p>每个音频片段需要对应的文本标注：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">audio_001.wav | 今天天气真不错。
</span></span><span class="line"><span class="cl">audio_002.wav | 我们去公园散步吧。
</span></span><span class="line"><span class="cl">audio_003.wav | 好的，等我换个衣服。
</span></span></code></pre></td></tr></table>
</div>
</div><p><strong>常见格式</strong>：</p>
<ul>
<li>LJSpeech格式：<code>文件名|文本</code></li>
<li>CSV格式：带时间戳</li>
</ul>
<hr>
<h2 id="常见坑">常见坑</h2>
<h3 id="坑1录音环境不一致">坑1：录音环境不一致</h3>
<p>同一批数据，有的在卧室录，有的在办公室录 → 合成效果不稳定</p>
<p><strong>解决</strong>：固定一个录音环境</p>
<h3 id="坑2情绪变化大">坑2：情绪变化大</h3>
<p>开始精神饱满，后面疲惫无力 → 合成声音不稳定</p>
<p><strong>解决</strong>：分多次录，每次1小时以内</p>
<h3 id="坑3文本标注错误">坑3：文本标注错误</h3>
<p>音频说&quot;今天&quot;，标注写&quot;昨天&quot; → 模型学混了</p>
<p><strong>解决</strong>：用Whisper自动转写，人工校验</p>
<h3 id="坑4切分太碎">坑4：切分太碎</h3>
<p>3个字一个片段 → 模型学不到完整语调</p>
<p><strong>解决</strong>：保持5-15秒一个片段</p>
<hr>
<h2 id="数据量参考">数据量参考</h2>
<table>
  <thead>
      <tr>
          <th>目标</th>
          <th>数据量</th>
          <th>效果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>快速验证</td>
          <td>10分钟</td>
          <td>勉强能用</td>
      </tr>
      <tr>
          <td>基本可用</td>
          <td>1-2小时</td>
          <td>像那么回事</td>
      </tr>
      <tr>
          <td>效果不错</td>
          <td>5-10小时</td>
          <td>接近真人</td>
      </tr>
      <tr>
          <td>专业级</td>
          <td>20小时+</td>
          <td>几乎分不出</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="推荐工具">推荐工具</h2>
<table>
  <thead>
      <tr>
          <th>用途</th>
          <th>工具</th>
          <th>备注</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>录音</td>
          <td>Audacity</td>
          <td>免费</td>
      </tr>
      <tr>
          <td>降噪</td>
          <td>Adobe Podcast</td>
          <td>在线免费</td>
      </tr>
      <tr>
          <td>人声分离</td>
          <td>UVR5</td>
          <td>开源</td>
      </tr>
      <tr>
          <td>转写</td>
          <td>Whisper</td>
          <td>开源</td>
      </tr>
      <tr>
          <td>批处理</td>
          <td>FFmpeg</td>
          <td>命令行工具</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="下一步">下一步</h2>
<p>数据准备好后，就可以开始训练了。下篇讲TTS模型微调。</p>
<p>有问题留言。</p>
]]></content:encoded></item><item><title>Voice Agent架构：从语音输入到智能响应</title><link>https://realtime-ai.chat/posts/voice-agent-architecture/</link><pubDate>Wed, 14 Jan 2026 10:00:00 +0800</pubDate><guid>https://realtime-ai.chat/posts/voice-agent-architecture/</guid><description>Voice Agent 架构详解:ASR→LLM→TTS 的完整链路,如何构建能听会说的实时语音助手。</description><content:encoded><![CDATA[<h2 id="voice-agent-是什么">Voice Agent 是什么</h2>
<p>一句话：<strong>能听会说的AI助手</strong>。</p>
<pre class="mermaid">graph LR
    A[用户说话] --> B[ASR语音识别]
    B --> C[LLM理解+生成]
    C --> D[TTS语音合成]
    D --> E[播放给用户]
</pre><p>看起来简单，但要做好有三个核心挑战：</p>
<ol>
<li><strong>延迟</strong> - 用户说完到AI回复，要控制在1-2秒内</li>
<li><strong>打断</strong> - 用户随时可以打断AI说话</li>
<li><strong>自然度</strong> - 不能像机器人一样僵硬</li>
</ol>
<hr>
<h2 id="核心架构">核心架构</h2>
<h3 id="方案一串行流水线">方案一：串行流水线</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">用户说话 → [等说完] → ASR → LLM → TTS → 播放
</span></span></code></pre></td></tr></table>
</div>
</div><p><strong>优点</strong>：实现简单
<strong>缺点</strong>：延迟高（3-5秒）</p>
<p><strong>适合</strong>：对延迟不敏感的场景（如语音留言）</p>
<h3 id="方案二流式处理">方案二：流式处理</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">用户说话 → [边说边识别] → [边生成边合成] → [边合成边播放]
</span></span></code></pre></td></tr></table>
</div>
</div><p><strong>优点</strong>：延迟低（1-2秒）
<strong>缺点</strong>：实现复杂，需要处理中间状态</p>
<p><strong>适合</strong>：实时对话场景</p>
<hr>
<h2 id="关键组件">关键组件</h2>
<h3 id="1-asr语音识别">1. ASR（语音识别）</h3>
<table>
  <thead>
      <tr>
          <th>方案</th>
          <th>延迟</th>
          <th>准确率</th>
          <th>成本</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Whisper API</td>
          <td>1-2s</td>
          <td>95%+</td>
          <td>按时长计费</td>
      </tr>
      <tr>
          <td>Deepgram</td>
          <td>200ms</td>
          <td>90%+</td>
          <td>按时长计费</td>
      </tr>
      <tr>
          <td>本地Whisper</td>
          <td>500ms-2s</td>
          <td>95%+</td>
          <td>需要GPU</td>
      </tr>
  </tbody>
</table>
<p><strong>实时识别关键</strong>：</p>
<ul>
<li>使用流式API，边说边识别</li>
<li>VAD（语音活动检测）判断用户是否说完</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># Deepgram 流式识别示例</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">deepgram</span> <span class="kn">import</span> <span class="n">Deepgram</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">dg</span> <span class="o">=</span> <span class="n">Deepgram</span><span class="p">(</span><span class="n">api_key</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">connection</span> <span class="o">=</span> <span class="n">dg</span><span class="o">.</span><span class="n">transcription</span><span class="o">.</span><span class="n">live</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;punctuate&#34;</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;interim_results&#34;</span><span class="p">:</span> <span class="kc">True</span>  <span class="c1"># 获取中间结果</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="2-llm理解生成">2. LLM（理解+生成）</h3>
<p><strong>流式输出是关键</strong>：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># OpenAI 流式生成</span>
</span></span><span class="line"><span class="cl"><span class="n">response</span> <span class="o">=</span> <span class="n">openai</span><span class="o">.</span><span class="n">chat</span><span class="o">.</span><span class="n">completions</span><span class="o">.</span><span class="n">create</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">model</span><span class="o">=</span><span class="s2">&#34;gpt-4o&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">messages</span><span class="o">=</span><span class="p">[</span><span class="o">...</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="n">stream</span><span class="o">=</span><span class="kc">True</span>  <span class="c1"># 流式输出</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">chunk</span> <span class="ow">in</span> <span class="n">response</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">text</span> <span class="o">=</span> <span class="n">chunk</span><span class="o">.</span><span class="n">choices</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">delta</span><span class="o">.</span><span class="n">content</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># 立即发给TTS，不用等完整回复</span>
</span></span><span class="line"><span class="cl">    <span class="n">tts_queue</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="3-tts语音合成">3. TTS（语音合成）</h3>
<table>
  <thead>
      <tr>
          <th>方案</th>
          <th>延迟</th>
          <th>音质</th>
          <th>成本</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>ElevenLabs</td>
          <td>100ms</td>
          <td>最好</td>
          <td>$5起/月</td>
      </tr>
      <tr>
          <td>OpenAI TTS</td>
          <td>200ms</td>
          <td>好</td>
          <td>按字符计费</td>
      </tr>
      <tr>
          <td>Edge TTS</td>
          <td>50ms</td>
          <td>一般</td>
          <td>免费</td>
      </tr>
  </tbody>
</table>
<p><strong>流式合成</strong>：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># ElevenLabs 流式合成</span>
</span></span><span class="line"><span class="cl"><span class="n">audio_stream</span> <span class="o">=</span> <span class="n">elevenlabs</span><span class="o">.</span><span class="n">generate</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">text</span><span class="o">=</span><span class="n">text</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">voice</span><span class="o">=</span><span class="s2">&#34;Bella&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">stream</span><span class="o">=</span><span class="kc">True</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 边生成边播放</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">chunk</span> <span class="ow">in</span> <span class="n">audio_stream</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">audio_player</span><span class="o">.</span><span class="n">play</span><span class="p">(</span><span class="n">chunk</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><hr>
<h2 id="延迟优化">延迟优化</h2>
<h3 id="总延迟--asr延迟--llm延迟--tts延迟">总延迟 = ASR延迟 + LLM延迟 + TTS延迟</h3>
<table>
  <thead>
      <tr>
          <th>环节</th>
          <th>优化前</th>
          <th>优化后</th>
          <th>优化方法</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>ASR</td>
          <td>2000ms</td>
          <td>200ms</td>
          <td>流式识别 + VAD</td>
      </tr>
      <tr>
          <td>LLM</td>
          <td>1500ms</td>
          <td>300ms</td>
          <td>流式输出 + 首token优化</td>
      </tr>
      <tr>
          <td>TTS</td>
          <td>500ms</td>
          <td>100ms</td>
          <td>流式合成 + 预热</td>
      </tr>
      <tr>
          <td><strong>总计</strong></td>
          <td><strong>4000ms</strong></td>
          <td><strong>600ms</strong></td>
          <td>-</td>
      </tr>
  </tbody>
</table>
<h3 id="关键技术">关键技术</h3>
<p><strong>1. 句子级流水线</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">LLM生成第1句 → TTS合成第1句 → 播放第1句
</span></span><span class="line"><span class="cl">    ↓ 同时进行
</span></span><span class="line"><span class="cl">LLM生成第2句 → TTS合成第2句 → 等待播放
</span></span></code></pre></td></tr></table>
</div>
</div><p><strong>2. 首字节优化</strong></p>
<ul>
<li>LLM：选择首token延迟低的模型</li>
<li>TTS：预建立WebSocket连接</li>
</ul>
<p><strong>3. 预测性合成</strong></p>
<ul>
<li>对常见回复（&ldquo;好的&rdquo;、&ldquo;没问题&rdquo;）预先合成</li>
</ul>
<hr>
<h2 id="打断处理">打断处理</h2>
<p>用户随时可能打断AI说话：</p>
<pre class="mermaid">graph TD
    A[AI在说话] --> B{检测到用户说话?}
    B -->|是| C[立即停止播放]
    C --> D[取消待播放音频]
    D --> E[开始新一轮识别]
    B -->|否| A
</pre><p><strong>实现要点</strong>：</p>
<ol>
<li>持续监听麦克风，即使AI在说话</li>
<li>VAD检测到用户开口，立即停止</li>
<li>清空TTS队列，避免残留</li>
</ol>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">on_user_speech_detected</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># 立即停止</span>
</span></span><span class="line"><span class="cl">    <span class="n">audio_player</span><span class="o">.</span><span class="n">stop</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">tts_queue</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># 开始新识别</span>
</span></span><span class="line"><span class="cl">    <span class="n">start_listening</span><span class="p">()</span>
</span></span></code></pre></td></tr></table>
</div>
</div><hr>
<h2 id="推荐技术栈">推荐技术栈</h2>
<h3 id="快速原型">快速原型</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">ASR: Deepgram
</span></span><span class="line"><span class="cl">LLM: GPT-4o
</span></span><span class="line"><span class="cl">TTS: ElevenLabs
</span></span><span class="line"><span class="cl">通信: WebSocket
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="生产部署">生产部署</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">ASR: Whisper (本地) + VAD
</span></span><span class="line"><span class="cl">LLM: Claude / GPT-4o
</span></span><span class="line"><span class="cl">TTS: Fish Speech (本地)
</span></span><span class="line"><span class="cl">通信: WebRTC
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="开源方案">开源方案</h3>
<ul>
<li><strong>Pipecat</strong>: 专门的Voice Agent框架</li>
<li><strong>LiveKit</strong>: 实时音视频基础设施</li>
<li><strong>Vocode</strong>: Voice Agent开发库</li>
</ul>
<hr>
<h2 id="常见坑">常见坑</h2>
<h3 id="坑1回声问题">坑1：回声问题</h3>
<p>AI说话时麦克风录到AI的声音 → 无限循环</p>
<p><strong>解决</strong>：回声消除（AEC），或AI说话时静音麦克风</p>
<h3 id="坑2网络抖动">坑2：网络抖动</h3>
<p>网络不稳定导致音频断断续续</p>
<p><strong>解决</strong>：音频缓冲 + 自适应码率</p>
<h3 id="坑3并发处理">坑3：并发处理</h3>
<p>用户连续说多句，处理乱序</p>
<p><strong>解决</strong>：请求排队 + 取消机制</p>
<hr>
<h2 id="总结">总结</h2>
<p>Voice Agent的核心是<strong>流式处理</strong>：</p>
<ul>
<li>边听边识别</li>
<li>边想边说</li>
<li>边合成边播放</li>
</ul>
<p>做好这三点，延迟就能控制在1秒以内。</p>
<p>有问题留言。</p>
]]></content:encoded></item><item><title>声音克隆：60秒复制你的声音，然后呢？</title><link>https://realtime-ai.chat/posts/voice-cloning/</link><pubDate>Sat, 10 Jan 2026 14:00:00 +0800</pubDate><guid>https://realtime-ai.chat/posts/voice-cloning/</guid><description>声音克隆技术现状:60 秒复制一个人的声音有多容易,以及随之而来的诈骗风险与防范。</description><content:encoded><![CDATA[<h2 id="先说个真事">先说个真事</h2>
<p>朋友公司有人收到&quot;老板&quot;的语音消息，让转账50万。声音、语气都对，差点就转了。后来发现是AI克隆的——骗子从老板的抖音视频里扒了几十秒素材。</p>
<p>这就是现在声音克隆的水平：<strong>以假乱真</strong>。</p>
<hr>
<h2 id="60秒能干什么">60秒能干什么</h2>
<p>用ElevenLabs举例：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">elevenlabs</span> <span class="kn">import</span> <span class="n">clone</span><span class="p">,</span> <span class="n">generate</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 上传60秒录音</span>
</span></span><span class="line"><span class="cl"><span class="n">voice</span> <span class="o">=</span> <span class="n">clone</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">name</span><span class="o">=</span><span class="s2">&#34;我的声音&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">files</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;sample.mp3&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 让它说任何话</span>
</span></span><span class="line"><span class="cl"><span class="n">audio</span> <span class="o">=</span> <span class="n">generate</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">text</span><span class="o">=</span><span class="s2">&#34;这话我从没说过&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">voice</span><span class="o">=</span><span class="n">voice</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>就这么简单。效果好到专业人士都分辨不出。</p>
<hr>
<h2 id="能用来干什么">能用来干什么</h2>
<p><strong>正经用途：</strong></p>
<ul>
<li>有声书制作（成本从10万降到1千）</li>
<li>虚拟主播（24小时不下播）</li>
<li>游戏NPC配音（1000个NPC，1000种声音）</li>
<li>帮失声的人&quot;说话&quot;</li>
</ul>
<p><strong>不正经用途：</strong></p>
<ul>
<li>诈骗（前面说的那种）</li>
<li>伪造录音</li>
<li>未经授权用别人的声音</li>
</ul>
<hr>
<h2 id="怎么防骗">怎么防骗</h2>
<ol>
<li><strong>涉及转账，打电话确认</strong>。语音消息不算数。</li>
<li><strong>设暗号</strong>。家人之间约定一个只有你们知道的词。</li>
<li><strong>听细节</strong>。AI声音太&quot;完美&quot;——没有呼吸声、没有口水音、没有犹豫。</li>
</ol>
<hr>
<h2 id="怎么玩">怎么玩</h2>
<p><strong>免费方案：</strong> Coqui TTS（开源），需要自己部署</p>
<p><strong>付费方案：</strong> ElevenLabs，$11/月起，效果最好</p>
<p><strong>录音技巧：</strong></p>
<ul>
<li>安静环境</li>
<li>正常语速</li>
<li>至少60秒，内容越丰富越好</li>
</ul>
<hr>
<h2 id="配音演员会失业吗">配音演员会失业吗</h2>
<p>低端活会被抢：有声书旁白、广告配音、游戏NPC。</p>
<p>高端活抢不走：需要情感演绎的角色、艺术创作。</p>
<p><strong>新机会：</strong> 授权自己的声音收版权费、做AI配音指导。</p>
<hr>
<h2 id="最后">最后</h2>
<p>技术没有善恶，看人怎么用。</p>
<p>玩声音克隆记得：<strong>用自己的声音玩，别克隆别人的</strong>。</p>
<p>有问题留言。</p>
<hr>
<p><em>相关链接：<a href="https://elevenlabs.io/">ElevenLabs</a> | <a href="https://github.com/coqui-ai/TTS">Coqui TTS</a></em></p>
]]></content:encoded></item></channel></rss>