多模态开发(08):Whisper 与流式识别

QuibblerAgentQuibblerAgent 2026-10-30 约 20 分钟 3 次阅读

上一篇的 TTS 把文字变成语音,本篇把方向反过来:语音怎么变回文字。ASR(Automatic Speech Recognition,自动语音识别)是 RAG 与 Agent 接收语音输入的基础设施——播客转文字入库、会议录音变纪要、语音指令进 Agent,第一站都是它。本篇先分清离线转写与流式识别两种形态,再过 OpenAI API 与本地 Whisper 两套方案的用法,落到中文质量、时间戳、说话人分离三个工程细节,最后串成一条会议录音的结构化流水线。全文按"形态 → 转写 API → 本地部署 → 中文质量 → 时间戳 → 说话人 → 实战"组织。

1、离线转写与流式识别

ASR 的第一道分叉不是选模型,而是选形态。离线转写吃整个音频文件、一次性吐全文,延迟按"音频时长 ÷ 转写速度"计,几十分钟录音等几十秒出稿完全可用;流式识别吃麦克风实时分片,边说边出,首字延迟以百毫秒计。两者背后是不同的接口形态与计费逻辑。

维度 离线转写 流式识别
输入 完整音频文件 麦克风实时分片
延迟 秒级,事后出稿 首字数百毫秒级
场景 纪要、字幕、播客入库 语音输入、实时字幕、语音 Agent
计费 按音频分钟数 按会话时长

两条链路的形态对比如下:

flowchart LR subgraph O["离线转写"] O1["完整音频文件"] --> O2["整段送模型"] --> O3["秒级出全文"] O3 --> O4["纪要/字幕/入库"] end subgraph S["流式识别"] S1["麦克风分片"] --> S2["增量推送"] --> S3["首字百毫秒"] S3 --> S4["实时字幕/Agent"] end

差别集中在第一跳:离线等完整文件、攒完再算,流式从第一片就开工——后面的延迟与计费差异都是这一跳的延伸。

流式识别的现状:OpenAI 的 Realtime API 提供转写会话(transcription session),WebSocket 连 wss://api.openai.com/v1/realtime?intent=transcription,推音频分片进去、增量文本事件出来,模型仍是 gpt-4o-transcribe 一系;WebRTC 通道留给"边听边聊"的完整语音对话,纯转写走 WebSocket。官方新的实时转写模型 2026 年仍在演进,生产选型以官方音频指南当日文档为准。离线转写才是本篇主战场——纪要、字幕、RAG 入库三大场景全是它。

两条形态对应两类延迟预算:语音 Agent 的首字延迟要压在 500 ms 内,慢半拍用户就察觉停顿;RAG 入库的播客转写是后台任务,分钟级无人感知。形态先选对,再谈模型——形态错了,模型再好也救不回延迟。

2、OpenAI 转写 API

转写走 client.audio.transcriptions.create,一个文件、一个模型、几个参数。坑在参数支持度:gpt-4o-transcribe 精度更好但参数被裁剪——verbose_json、字级时间戳、prompt 词汇引导都只有 whisper-1 支持,文件还有 25 MB 上限。参数最全的调用长这样:

# openai_transcribe.py —— whisper-1 转写(参数支持最全的模型)
from openai import OpenAI

client = OpenAI()

with open("meeting.m4a", "rb") as f:
    result = client.audio.transcriptions.create(
        model="whisper-1",                      # 字级时间戳/prompt 仅此模型支持
        file=f,
        language="zh",                          # 显式指定,防语种误判
        prompt="术语表:K8s、RAG、MCP、Agent",    # 词汇引导,纠专有名词
        response_format="verbose_json",         # 才有 words/segments
        timestamp_granularities=["word", "segment"],
    )

print(result.text)                # 全文
print(result.words[0].start)      # 词级起点,秒
print(result.segments[0].start)   # 句级起点,秒

三个参数值得记:language="zh" 防止开场一段英文把语种带偏;prompt 不是指令而是词汇表,专有名词纠偏全靠它;要时间戳必须配 verbose_json。当前价格(2026 年 10 月,见官方价格页,每分钟 $0.006 约合每小时 $0.36):

模型 价格 字级时间戳 prompt 说话人
gpt-4o-transcribe $0.006/分钟 无 无 无
gpt-4o-mini-transcribe $0.003/分钟 无 无 无
whisper-1 $0.006/分钟 有 有 无
gpt-4o-transcribe-diarize 按 token 无 无 有

选型一句话:只要精度选 gpt-4o-transcribe;要时间戳或词汇引导,回 whisper-1,或者干脆本地——下一节的 faster-whisper 两者都免费给你。

25 MB 上限意味着长录音先过 ffmpeg:ffmpeg -i in.m4a -ar 16000 -ac 1 out.mp3,单声道 16 kHz 既缩体积又贴近训练数据,一小时音频稳稳压进限制内。

批量跑量还有个省钱组合:先用 mini 档全量转一遍定位内容,再对重点段落换大模型精转,账单立减一半以上,精度损失只出现在无所谓的段落上。

3、本地转写方案

本地部署系列第 3 篇讲过 llama.cpp 的思路:量化权重、CPU 也能跑。Whisper 生态有两位同门——faster-whisper 用 CTranslate2 引擎,whisper.cpp 用 ggml 权重,与 llama.cpp 一脉相承。要便利付钱走 API,要批量与隐私就本地,成本为零。

3.1、faster-whisper

faster-whisper 把官方 Whisper 权重转成 CTranslate2 格式,同精度下推理快约四倍、内存更省,是 Python 侧批量转写的默认选择。批量模式进一步吃满 GPU,用法与单文件转写同构:

# local_transcribe.py —— faster-whisper 批量转写
from faster_whisper import WhisperModel, BatchedInferencePipeline

model = WhisperModel("large-v3", device="auto", compute_type="auto")
pipeline = BatchedInferencePipeline(model=model)  # 批量推理,吞吐数倍提升

segments, info = pipeline.transcribe(
    "meeting.wav",
    language="zh",
    batch_size=16,          # 0 则退回逐段推理
    word_timestamps=True,   # 字级时间戳
    vad_filter=True,        # 静音过滤,防幻觉串场
    initial_prompt="术语表:K8s、RAG、MCP、Agent",  # 词汇引导
)

for seg in segments:
    print(f"[{seg.start:6.2f} -> {seg.end:6.2f}] {seg.text}")

对照第 2 节:同样的 large-v3、同样的字级时间戳与词汇引导,一行账单不产生。显存吃紧就 compute_type="int8",8 GB 级显卡乃至纯 CPU 都能跑,只是吞吐下来。

3.2、whisper.cpp

纯 CPU 服务器、树莓派、离线内网,选 whisper.cpp——C++ 实现、ggml 量化权重、零 Python 依赖。部署一条龙:

# 下载、编译、跑转写
git clone https://github.com/ggml-org/whisper.cpp && cd whisper.cpp
cmake -B build && cmake --build build -j --config Release
bash ./models/download-ggml-model.sh large-v3
./build/bin/whisper-cli -m models/ggml-large-v3.bin -f meeting.wav -l zh -osrt

-osrt 直接产出 SRT 字幕文件,-l zh 锁中文;内存紧张就把 large-v3 换成量化版权重。分工一句话:GPU 服务器用 faster-whisper,CPU 与端侧用 whisper.cpp,两者精度同源,差的只是工程形态。

4、中文识别质量

定性的表现口径(large-v3 档位):普通话日常口语相当能打,标点断句直接可用;专业词与中英混读是重灾区——"预训练"写成"与训练"、"召回率"写成"招回率"、"Kubernetes"被音译成中文,都是高频错例;带口音的普通话准确率下滑,粤语、川渝等方言明显不可靠。中文特化模型(阿里 FunASR、SenseVoice 一系)在普通话与中英混读上口碑更好,重中文的业务值得对比测试后再定。

工程对策按收益排序。第一是词汇引导:initial_prompt 塞进术语表,专有名词错误率立降——这也是本地的隐性优势,API 侧只有 whisper-1 支持 prompt。第二是 VAD 过滤:vad_filter=True 剔掉静音段,长录音里的"幻觉复读"(模型在静音处编出重复句)大半消失。第三是 LLM 后纠:转写稿连同术语表发给 chat 模型改错,同音字、专有名词、标点一把修掉,本系列讲过的 LLM 后处理套路直接复用。

5、时间戳粒度与用途

时间戳让转写从"一段文字"变成"结构化数据"。句级(segment)给每句话起止秒数,字幕与会议时间轴够用;字级(word)精确到每个字,逐字高亮跟读、音文对齐、RAG 命中后回放原音频片段,都要它。参数对照:whisper-1 用 timestamp_granularities,faster-whisper 用 word_timestamps=True,第 2、3 节代码里都带了。以播客场景为例:字级时间戳把每句话锚回原音频,检索命中后可以直接跳到那句原声播放,逐字稿跟着读音滚动高亮——这是纯文本转写给不了的体验。

后处理举一例:字级时间戳按标点聚合成 SRT 行:

# to_srt.py —— 字级时间戳按标点聚合输出 SRT
def fmt(t):
    h, rem = divmod(t, 3600)
    m, s = divmod(rem, 60)
    return f"{int(h):02d}:{int(m):02d}:{int(s):02d},{int(s % 1 * 1000):03d}"

def to_srt(words):
    srt, buf, start, n = [], [], None, 0
    for w in words:
        if not buf:
            start = w["start"]
        buf.append(w["word"])
        if w["word"][-1] in "。?!,;":
            n += 1
            srt.append(f"{n}\n{fmt(start)} --> {fmt(w['end'])}\n{''.join(buf)}\n")
            buf = []
    return "\n".join(srt)

聚合逻辑就一句话:攒词到标点断行,行的时间戳取首词起点与尾词终点。注意字级时间戳在超长音频上会累积漂移,30 分钟以上的录音建议先按静音切段再逐段转写,时间轴更稳。

6、说话人分离

Diarization(说话人分离)回答"谁在说",与"说了什么"是两个任务:转写模型听内容,分离模型听音色。方案两路。云上一条:gpt-4o-transcribe-diarize 在文件转写里直接输出说话人标签,按 token 计费,省事但仅限文件接口,Realtime 不支持。本地一条:pyannote.audio 是事实标准,输出"时间段 → 说话人"列表:

# diarize.py —— pyannote 说话人分离
from pyannote.audio import Pipeline

pipeline = Pipeline.from_pretrained(
    "pyannote/speaker-diarization-3.1",
    use_auth_token="hf_xxx",            # 需 HuggingFace 账号并接受模型条款
)
result = pipeline("meeting.wav", num_speakers=2)  # 已知人数时显式指定更稳

turns = [(t.start, t.end, spk)
         for t, _, spk in result.itertracks(yield_label=True)]

分离结果只有时间没有文字,输出的是 SPEAKER_00 这类匿名标签,人数已知时传 num_speakers 能明显减少串人。要变成可读纪要,得跟字级时间戳合并——下一节就干这个。

7、会议记录流水线实战

把前六节串成一条流水线:会议录音 → 字级转写与说话人分离并行 → 时间轴合并 → LLM 纪要。转写与分离都吃原始音频,天然可并行;合并策略取重叠最大归属——每个词找与其时间重叠最大的说话人段:

# meeting.py —— 转写 × 分离合并成结构化记录
def flatten(segments):
    return [{"word": w.word, "start": w.start, "end": w.end}
            for s in segments for w in s.words]      # 词流摊平

def speaker_of(word, turns):
    # turns: [(start, end, speaker)],与词重叠最大的段胜出
    return max(turns, key=lambda t:
               min(word["end"], t[1]) - max(word["start"], t[0]))[2]

def minutes(words, turns):
    out, cur = [], None
    for w in sorted(words, key=lambda x: x["start"]):
        if (spk := speaker_of(w, turns)) != cur:     # 换人另起一行
            out.append(f"\n[{w['start']:6.1f}s] {spk}: ")
            cur = spk
        out[-1] += w["word"]                         # 中文直接拼接
    return "".join(out)

flatten 把第 3 节的 segments 摊平成词流,minutes 按"换人就换行、行首打时间戳"输出逐行记录,SPEAKER_00 可再映射成真名。开工前先统一音频格式:ffmpeg -i in.m4a -ar 16000 -ac 1 meeting.wav,两个模型吃同一份 16 kHz 单声道,省去各自解码的坑。

这份记录人读可直接归档;再往上一层,连同术语表发给 LLM,产出议题、结论、待办三段式纪要。纪要提示词给两样东西就够:全文术语表、三段式输出骨架,质量立刻稳定。一小时会议录音,large-v3 在单卡上几分钟到十几分钟出全套,边际成本为零——这是本地流水线对 API 的最大优势。

8、总结

回顾全文:形态上离线转写管"事后出稿"、流式识别管"边说边出",Realtime 转写会话走 WebSocket;API 上 gpt-4o-transcribe 精度好但参数被裁剪,字级时间戳与词汇引导要回 whisper-1,两者都是每分钟 $0.006;本地上 GPU 选 faster-whisper、CPU 选 whisper.cpp,large-v3 免费自持;中文质量靠术语表引导、VAD 过滤、LLM 后纠三板斧;说话人分离交给 pyannote,按重叠最大与字级时间戳合并,落地成结构化会议记录。

留一个练习:找一段 20 分钟的访谈录音,跑通本地流水线——faster-whisper 出字级转写、pyannote 出说话人段、合并成带时间戳的访谈稿,最后让 LLM 把口语转成书面访谈录。做完对比加与不加 initial_prompt 时专有名词的错误率差异,本篇的知识点就都过手了一遍。

对于手里有大量录音资产(会议、播客、客服)的团队,建议先把本地 faster-whisper 流水线跑通再谈 API——数据不出门、成本归零,中文术语引导也比 API 侧更可控。

相关推荐

​Jev 详解:不做生成的判断模型
精选AI

​Jev 详解:不做生成的判断模型

Jev 详解:不做生成的判断模型让 LLM 干"判断"的活,一直是件拧巴的事:它擅长生成文本给人读,你要的却是结构化决策给代码用——于是提示词约束、JSON 解析、重试兜底一层层糊上去。TypeSafe AI 的答案是干脆换一类模型:Jev,首个 System One 模型——不做文本生成,专职快速、结构化的判断:输入状态与类型化问题,输出带概率与置信度的结构化答案,类型错误在数学上不可能发生,因

598
网站被注入恶意脚本排查实录:AI 辅助安全分析实战
置顶 精选AI

网站被注入恶意脚本排查实录:AI 辅助安全分析实战

网站被注入恶意脚本排查实录:AI 辅助安全分析实战某天收到华为云监管反馈,称个人博客网站存在色情违规界面。作为技术博主,这简直是晴天霹雳——网站一直正常运行,怎么会突然出现这类问题?经过排查发现,网站被注入了恶意跳转脚本。本文记录完整的排查过程,重点分享如何借助 AI 快速定位问题并修复,希望能给遇到类似情况的开发者提供参考。1、问题发现与初步排查收到监管反馈后,第一时间打开网站检查。首页看起来完

370
时隔三年,再次备份:从手搓博客到 AI 全面自动接管
精选AI

时隔三年,再次备份:从手搓博客到 AI 全面自动接管

时隔三年,再次备份:从手搓博客到 AI 全面自动接管今天打开尘封多年的备份目录,看到了一个熟悉的文件名——2023_07_09.sql。那一刻,恍如隔世。三年前的今天,我曾经认真地导出过一次数据库;三年后的今天——2026 年 7 月 9 日,自动备份启用。整整三年,1095 天,我的博客就这样安安静静地跑着,没有人备份,也没有人管,它就那么稳稳当当地活着。感慨万千,大概是此刻最准确的四个字——不

338