Whisper 是 OpenAI 开源的语音识别模型,识别准确率在开源方案里名列前茅,但它天生是为整段音频设计的,并不是流式模型。想在麦克风输入场景下做到实时转录,需要自己在采集和推理之间做一层切分与调度的设计。本文围绕这个问题,把环境搭建、模型选择、分块录音、滚动识别到实际落地中的坑完整讲一遍。

一、Whisper 的模型选择与环境准备
Whisper 官方提供了 tiny、base、small、medium、large 等多个尺寸的模型。模型越大识别越准,但速度越慢。做实时转录时,推理耗时必须明显小于音频时长,否则转录会越积越多,延迟不断增大。一般来说,CPU 环境下建议用 tiny 或 base,带 GPU 的机器可以上 small 甚至 medium。可以先做个简单测试:录一段 30 秒的音频,分别统计不同模型的推理时间,找到在你的硬件上单次推理耗时低于音频时长一半的那个模型作为候选。
环境方面,推荐用 faster-whisper 代替官方 openai-whisper 库。faster-whisper 基于 CTranslate2 重写了推理逻辑,同样模型下速度快 3 到 4 倍,显存占用也低很多,是实时场景的主流选择。安装依赖只需要几行命令:
pip install faster-whisper pip install pyaudio numpy # 如果用官方版本,则执行 # pip install openai-whisper pyaudio numpy
另外提醒一点:PyAudio 在 Windows 上安装通常很顺利,但在 Linux 上需要先装 portaudio 的系统库,例如 Debian/Ubuntu 下执行 sudo apt install portaudio19-dev 之后再 pip 安装,否则会编译失败。
二、用 PyAudio 采集麦克风音频
实时转录的第一步是把麦克风的模拟信号变成模型能处理的数字音频。Whisper 要求输入为 16kHz 采样的浮点数组,而声卡的采样率通常是 44.1kHz 或 48kHz,所以采集后必须做一次重采样。多数声卡其实支持直接以 16000Hz 打开,系统会自动完成重采样,这样最省事。如果设备强制返回 44100Hz,就自己用 numpy 做一次插值降采样。下面的代码演示了打开麦克风流并按块读取音频的过程:
import pyaudio
import numpy as np
CHUNK = 1024 # 每次读取的帧数
FORMAT = pyaudio.paInt16
CHANNELS = 1 # Whisper 只需要单声道
RATE = 16000 # 直接以 16kHz 打开设备
p = pyaudio.PyAudio()
stream = p.open(format=FORMAT, channels=CHANNELS,
rate=RATE, input=True,
frames_per_buffer=CHUNK)
def read_block(duration_sec=0.5):
"""读取指定时长的音频块,返回 float32 数组"""
frames = []
total = int(RATE * duration_sec / CHUNK)
for _ in range(total):
data = stream.read(CHUNK, exception_on_overflow=False)
frames.append(np.frombuffer(data, dtype=np.int16))
audio = np.concatenate(frames).astype(np.float32) / 32768.0
return audio注意 exception_on_overflow=False 这个参数,它避免缓冲区溢出时程序抛异常崩溃,在实时采集里很关键。设备选择也很重要,笔记本往往有多个输入设备,插上耳机后默认设备还会切换。可以用 p.get_device_info_by_index(i) 遍历所有设备,按 maxInputChannels 大于零筛选出输入设备,让用户手动指定,比依赖系统默认设备可靠得多。如果要转录的是电脑本身播放的声音(比如网课或会议对方的声音),就需要安装 VB-Cable 之类的虚拟声卡,把系统输出回环成输入设备。
三、分块策略与滚动窗口识别
拿到音频块之后就是核心的分块策略。Whisper 处理短音频时有一个特性:它依赖音频开头的时间戳机制,输入太短会导致输出不稳定,甚至重复输出相同文字。实践中有两种主流做法。第一种是固定分块:每凑够 4 到 5 秒音频就送一次识别,识别完立刻输出。优点是实现简单、延迟固定;缺点是会把词语从中间切断,比如“人工智能”被切成“人工”和“智能”两次识别,准确性受损。第二种是滚动窗口:维护一个最近 10 秒的音频缓冲区,每次都识别整个窗口,但只输出新增部分,同时用上一段识别结果的结尾作为初始 prompt 传给下一次调用,让模型保持上下文连贯。这种方式准确率明显更高,代价是计算量增大。
下面是一个基于 faster-whisper 的完整可运行示例,采用带静音检测的分割方案——用能量阈值判断说话开始和结束,只在检测到完整语句时才触发识别:
import time
from faster_whisper import WhisperModel
model = WhisperModel("small", device="cpu", compute_type="int8")
# GPU 环境可改为 device="cuda", compute_type="float16"
SAMPLE_RATE = 16000
SILENCE_TH = 0.01 # 静音能量阈值,需按麦克风实际调节
MAX_BUFFER = 10 # 缓冲区最大秒数
buffer = np.zeros(0, dtype=np.float32)
prompt = ""
print("开始说话(Ctrl+C 退出)...")
try:
while True:
block = read_block(0.5)
buffer = np.concatenate([buffer, block])
# 检测当前块能量,判断是否静音
energy = np.abs(block).mean()
if energy < SILENCE_TH and len(buffer) > SAMPLE_RATE * 1.0:
# 静音且缓冲区超过1秒,认为是完整语句,触发识别
segments, _ = model.transcribe(
buffer,
language="zh",
beam_size=5,
initial_prompt=prompt,
vad_filter=True
)
text = "".join(seg.text for seg in segments)
if text.strip():
print(f"[{time.strftime('%H:%M:%S')}] {text}")
prompt = (prompt + text)[-200:] # 保留最近的上下文
buffer = np.zeros(0, dtype=np.float32)
elif len(buffer) > SAMPLE_RATE * MAX_BUFFER:
# 超过最大缓冲,只保留最近的音频,防止延迟堆积
buffer = buffer[-SAMPLE_RATE * MAX_BUFFER:]
except KeyboardInterrupt:
stream.stop_stream()
stream.close()
p.terminate()这段代码有几个值得注意的细节。vad_filter=True 会先在模型内部跑一遍 Silero VAD,把纯静音片段过滤掉,能大幅减少幻听式的重复输出;initial_prompt 传递上文,让模型在分块场景下保持用词一致,比如人名、专业术语不会前后写法不一;强制截断缓冲区那一步是兜底逻辑,防止有人连续说很长的话不停顿导致内存和延迟一起爆炸。
四、准确率与实时性的平衡调优
实际部署中最常遇到的是三类问题。第一是延迟偏高,可以从几个方向优化:换 int8 量化的 compute_type、减小 beam_size 到 1、换更小的模型。第二是静音时输出乱码或重复同一句话,这是 Whisper 的典型幻觉,解决办法是开启 VAD 过滤、调高静音阈值,并在能量低于阈值时不触发识别,双管齐下基本可以根治。
第三是说话人切换或中英混杂场景。建议显式指定 language="zh" 而不是让模型自动检测——自动检测在短音频上经常误判,一旦判错整段输出就全乱了。如果确实需要多语言混说,可以不对 language 赋值,但要把触发识别的音频块加长到 5 秒以上,给模型足够上下文做语言判断。
最后给一组实测参考数据:普通 CPU 加 tiny 模型,5 秒音频推理约 0.8 秒,延迟可控;small 模型在 CPU 上约 4 秒,明显跟不上;而在一块入门级显卡上,small 模型只需 0.4 秒左右,medium 也能勉强实时。所以选型结论很直接:纯 CPU 用 tiny 或 base,有显卡就 small 或 medium。按这个思路搭起来的方案,识别延迟基本能稳定在 1 秒出头,足够支撑会议纪要、直播字幕这类对实时性要求较高的应用了。
Whisper实时转录流式语音识别麦克风语音识别修改时间:2026-09-11 02:06:57