Whisper语音识别实战完全教程
从零掌握OpenAI Whisper语音识别模型,涵盖模型原理、多语言转录、实时流式处理、微调优化及企业级部署方案。
目录
- Whisper模型概述与架构解析
- 模型版本对比与选型指南
- 安装与环境配置
- 基础语音转录实战
- 多语言识别与翻译
- 实时流式转录
- Fine-tune微调实战
- 与主流商业语音API对比
- 字幕生成应用
- 会议记录系统构建
- 大规模部署方案
- 最佳实践与常见问题
1. Whisper模型概述与架构解析
Whisper是OpenAI于2022年开源的通用语音识别模型,其核心创新在于大规模弱监督预训练——使用从互联网收集的68万小时多语言音频数据进行训练,覆盖了真实世界中极其丰富的语音场景。
1.1 架构设计
Whisper采用经典的Encoder-Decoder Transformer架构:
音频输入 → Mel频谱特征提取 → Transformer Encoder → Transformer Decoder → 文本输出
编码器(Encoder) 负责将音频信号转化为高维特征表示:
- 输入:80维Log-Mel频谱图,每帧25ms,步长10ms
- 通过两层1D卷积进行下采样
- 多层Transformer块处理时序特征
解码器(Decoder) 基于编码器输出自回归生成文本:
- 使用BPE(Byte Pair Encoding)分词器,词表大小为51865
- 支持特殊token控制任务类型(转录/翻译/时间戳等)
- 交叉注意力机制连接编码器与解码器
1.2 训练策略
Whisper的训练数据来自互联网上68万小时的音频-文本配对数据,涵盖99种语言。这种弱监督方式意味着数据质量参差不齐,但海量数据带来的泛化能力弥补了这一缺陷。训练目标包括:
- 语音识别(转录)
- 语音翻译(英译)
- 语言识别
- 时间戳预测
- 静音检测(VAD)
这种多任务联合训练使得Whisper成为一个"全能型"语音处理模型。
2. 模型版本对比与选型指南
Whisper提供了多个不同规模的模型版本,适用于不同场景:
| 模型名称 | 参数量 | 编码器层 | 解码器层 | 显存占用 | 英文WER | 推理速度 |
|---|---|---|---|---|---|---|
| tiny | 39M | 4 | 4 | ~1GB | 7.6% | 极快 |
| base | 74M | 6 | 6 | ~1GB | 5.4% | 很快 |
| small | 244M | 12 | 12 | ~2GB | 4.3% | 快 |
| medium | 769M | 24 | 24 | ~5GB | 3.5% | 中等 |
| large-v3 | 1550M | 32 | 32 | ~10GB | 2.7% | 较慢 |
WER:Word Error Rate(词错误率),越低越好。
2.1 选型建议
开发与原型验证:使用 tiny 或 base,推理速度快,适合快速迭代。
生产环境 - 英文为主:small 模型性价比最高,WER仅比large高1.6%,但速度快3-4倍。
多语言场景:必须使用 medium 或 large-v3,小模型在非英语语言上表现显著下降。
资源受限设备(边缘部署):tiny 或 base,配合量化可进一步压缩。
追求极致准确率:large-v3,特别是噪声音频或专业领域场景。
2.2 Whisper V3 Turbo
2024年发布的 large-v3-turbo 是一个重要改进版本:
- 参数量与large-v3相同(1550M)
- 解码器层从32层减少到4层
- 推理速度提升约8倍
- 准确率与large-v3基本持平
# 加载turbo版本
import whisper
model = whisper.load_model("large-v3-turbo")
result = model.transcribe("audio.mp3")
3. 安装与环境配置
3.1 系统要求
- Python:3.8 - 3.11(推荐3.10)
- PyTorch:1.10+
- FFmpeg:音频解码必需
- GPU(可选但强烈推荐):NVIDIA GPU + CUDA 11.7+
3.2 安装步骤
# 安装系统依赖
sudo apt update && sudo apt install -y ffmpeg
# 安装Whisper
pip install -U openai-whisper
# 验证安装
whisper --help
# 安装GPU版本PyTorch(如未安装)
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
3.3 Python环境配置
import whisper
import torch
# 检查GPU可用性
print(f"CUDA available: {torch.cuda.is_available()}")
print(f"Device: {torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'CPU'}")
# 加载模型(首次运行会自动下载)
model = whisper.load_model("base")
print("Model loaded successfully!")
3.4 常见安装问题
问题1:FFmpeg未找到
# Ubuntu/Debian
sudo apt install ffmpeg
# macOS
brew install ffmpeg
# 验证
ffmpeg -version
问题2:CUDA内存不足
# 使用较小模型或强制CPU推理
model = whisper.load_model("base", device="cpu")
问题3:下载超时
# 手动下载模型文件到缓存目录
# 缓存路径:~/.cache/whisper/
4. 基础语音转录实战
4.1 最简转录示例
import whisper
# 加载模型
model = whisper.load_model("base")
# 转录音频文件
result = model.transcribe("meeting_recording.mp3")
# 输出结果
print("识别文本:")
print(result["text"])
print("\n检测语言:")
print(result["language"])
print("\n带时间戳的分段:")
for segment in result["segments"]:
start = segment["start"]
end = segment["end"]
text = segment["text"]
print(f"[{start:.1f}s - {end:.1f}s] {text}")
4.2 高级转录参数
result = model.transcribe(
"audio.mp3",
language="zh", # 指定语言(提高准确率)
task="transcribe", # "transcribe" 或 "translate"
beam_size=5, # beam search宽度
best_of=5, # 采样次数取最优
temperature=0, # 温度(0=贪心解码)
compression_ratio_threshold=2.4, # 压缩率阈值
logprob_threshold=-1.0, # 对数概率阈值
no_speech_threshold=0.6, # 静音检测阈值
word_timestamps=True, # 词级时间戳
initial_prompt="以下是普通话的句子。", # 提示词引导
)
4.3 音频预处理
对于非标准音频格式,建议预处理:
import subprocess
def preprocess_audio(input_path, output_path="processed.wav"):
"""统一音频格式:16kHz单声道WAV"""
cmd = [
"ffmpeg", "-i", input_path,
"-ar", "16000", # 采样率16kHz
"-ac", "1", # 单声道
"-c:a", "pcm_s16le", # 16位PCM
"-y", # 覆盖输出
output_path
]
subprocess.run(cmd, check=True, capture_output=True)
return output_path
# 使用
audio_path = preprocess_audio("input_video.mkv")
result = model.transcribe(audio_path)
4.4 长音频分段处理
Whisper内部会对长音频自动分段,但你也可以手动控制:
import whisper
import numpy as np
def transcribe_long_audio(audio_path, model, chunk_duration=30):
"""分段转录长音频"""
audio = whisper.load_audio(audio_path)
sample_rate = 16000
chunk_samples = chunk_duration * sample_rate
all_segments = []
for i in range(0, len(audio), chunk_samples):
chunk = audio[i:i + chunk_samples]
# 填充到30秒(Whisper要求)
if len(chunk) < chunk_samples:
chunk = np.pad(chunk, (0, chunk_samples - len(chunk)))
chunk = whisper.pad_or_trim(chunk)
mel = whisper.log_mel_spectrogram(chunk).to(model.device)
result = model.decode(mel, whisper.DecodingOptions(
language="zh",
without_timestamps=False,
))
offset = i / sample_rate
all_segments.append({
"start": offset,
"text": result.text
})
return all_segments
5. 多语言识别与翻译
5.1 语言检测
import whisper
model = whisper.load_model("large-v3")
audio = whisper.load_audio("multilingual_audio.mp3")
audio = whisper.pad_or_trim(audio)
mel = whisper.log_mel_spectrogram(audio).to(model.device)
# 检测语言(前30秒)
_, probs = model.detect_language(mel)
detected_lang = max(probs, key=probs.get)
print(f"检测语言: {detected_lang}")
print("Top 5语言概率:")
for lang, prob in sorted(probs.items(), key=lambda x: -x[1])[:5]:
print(f" {lang}: {prob:.2%}")
5.2 支持的语言
Whisper支持99种语言,主要包括:
| 语言 | 代码 | 语言 | 代码 | 语言 | 代码 |
|---|---|---|---|---|---|
| 中文 | zh | 英语 | en | 日语 | ja |
| 韩语 | ko | 法语 | fr | 德语 | de |
| 西班牙语 | es | 俄语 | ru | 阿拉伯语 | ar |
| 葡萄牙语 | pt | 意大利语 | it | 泰语 | th |
| 越南语 | vi | 印地语 | hi | 土耳其语 | tr |
5.3 语音翻译(转英文)
# 将任意语言音频翻译为英文文本
result = model.transcribe(
"chinese_speech.mp3",
task="translate", # 关键参数:翻译模式
language="zh"
)
print("英文翻译:", result["text"])
5.4 中英混合识别优化
中英混合语音是常见场景,Whisper原生支持但可通过提示词优化:
result = model.transcribe(
"code_switching_audio.mp3",
language="zh",
initial_prompt="以下是中英文混合的技术讨论,包含Python、API、机器学习等术语。",
word_timestamps=True,
)
# 输出结果
for seg in result["segments"]:
print(f"[{seg['start']:.1f}-{seg['end']:.1f}] {seg['text']}")
6. 实时流式转录
Whisper本身是批量处理模型,但可以通过以下方案实现近实时转录。
6.1 基于VAD的流式方案
import whisper
import pyaudio
import numpy as np
import webrtcvad
from collections import deque
class StreamingTranscriber:
def __init__(self, model_name="base", language="zh"):
self.model = whisper.load_model(model_name)
self.language = language
self.vad = webrtcvad.Vad(2) # 灵敏度0-3
self.sample_rate = 16000
self.frame_duration = 30 # ms
self.frame_size = int(self.sample_rate * self.frame_duration / 1000)
self.buffer = deque(maxlen=100) # 约3秒缓冲
self.speech_buffer = []
self.is_speaking = False
self.silence_frames = 0
self.silence_threshold = 30 # 约1秒静音后处理
def start(self):
"""启动实时转录"""
pa = pyaudio.PyAudio()
stream = pa.open(
format=pyaudio.paInt16,
channels=1,
rate=self.sample_rate,
input=True,
frames_per_buffer=self.frame_size,
)
print("🎤 开始录音,按Ctrl+C停止...")
try:
while True:
data = stream.read(self.frame_size, exception_on_overflow=False)
audio_frame = np.frombuffer(data, dtype=np.int16)
# VAD检测
is_speech = self.vad.is_speech(data, self.sample_rate)
if is_speech:
self.speech_buffer.extend(audio_frame)
self.silence_frames = 0
self.is_speaking = True
elif self.is_speaking:
self.silence_frames += 1
if self.silence_frames >= self.silence_threshold:
self._process_buffer()
self.is_speaking = False
except KeyboardInterrupt:
print("\n停止录音")
finally:
stream.stop_stream()
stream.close()
pa.terminate()
def _process_buffer(self):
"""处理语音缓冲区"""
if len(self.speech_buffer) < self.sample_rate: # 至少1秒
return
audio = np.array(self.speech_buffer, dtype=np.float32) / 32768.0
self.speech_buffer = []
# Whisper转录
audio = whisper.pad_or_trim(audio)
mel = whisper.log_mel_spectrogram(audio).to(self.model.device)
result = self.model.decode(mel, whisper.DecodingOptions(
language=self.language,
without_timestamps=True,
))
if result.text.strip():
print(f"📝 {result.text}")
# 使用
transcriber = StreamingTranscriber(model_name="base", language="zh")
transcriber.start()
6.2 使用faster-whisper加速
faster-whisper是基于CTranslate2的Whisper实现,推理速度提升4-8倍:
from faster_whisper import WhisperModel
# 加载模型(支持int8量化)
model = WhisperModel(
"large-v3",
device="cuda",
compute_type="float16", # 或 "int8" 进一步加速
)
# 转录
segments, info = model.transcribe(
"audio.mp3",
beam_size=5,
language="zh",
vad_filter=True, # 内置VAD过滤静音
vad_parameters=dict(
min_silence_duration_ms=500,
),
)
print(f"检测语言: {info.language} (概率: {info.language_probability:.2%})")
for segment in segments:
print(f"[{segment.start:.2f}s -> {segment.end:.2f}s] {segment.text}")
7. Fine-tune微调实战
7.1 何时需要微调
以下场景考虑微调:
- 特定领域术语(医疗、法律、金融)
- 特定口音或方言
- 嘈杂环境录音
- 需要极低错误率的专业应用
7.2 数据准备
# 训练数据格式(JSON Lines)
# {"audio": "path/to/audio1.wav", "text": "转录文本1", "language": "zh"}
# {"audio": "path/to/audio2.wav", "text": "转录文本2", "language": "zh"}
import json
import os
def prepare_dataset(audio_dir, transcript_file, output_path):
"""准备训练数据"""
samples = []
with open(transcript_file, "r", encoding="utf-8") as f:
for line in f:
parts = line.strip().split("\t")
if len(parts) == 2:
audio_name, text = parts
audio_path = os.path.join(audio_dir, audio_name)
if os.path.exists(audio_path):
samples.append({
"audio": audio_path,
"text": text,
"language": "zh"
})
with open(output_path, "w", encoding="utf-8") as f:
for sample in samples:
f.write(json.dumps(sample, ensure_ascii=False) + "\n")
print(f"准备了 {len(samples)} 条训练数据")
prepare_dataset("./audio/", "./transcripts.tsv", "./train.jsonl")
7.3 使用whisper-finetune训练
# 安装微调工具
pip install whisper-finetune
# 开始微调
whisper_finetune \
--model base \
--train-data train.jsonl \
--val-data val.jsonl \
--output-dir ./output \
--epochs 10 \
--batch-size 16 \
--learning-rate 1e-5 \
--language zh \
--warmup-steps 100 \
--gradient-accumulation-steps 4 \
--fp16
7.4 使用Hugging Face Transformers微调
from transformers import WhisperForConditionalGeneration, WhisperProcessor
from datasets import Audio, load_dataset
import torch
# 加载预训练模型和处理器
model_name = "openai/whisper-base"
processor = WhisperProcessor.from_pretrained(model_name)
model = WhisperForConditionalGeneration.from_pretrained(model_name)
# 准备数据集
dataset = load_dataset("json", data_files={"train": "train.jsonl"})
def prepare_dataset(batch):
audio = batch["audio"]
# 处理音频
input_features = processor(
audio["array"],
sampling_rate=audio["sampling_rate"],
return_tensors="pt"
).input_features[0]
# 处理文本
labels = processor.tokenizer(batch["text"]).input_ids
return {
"input_features": input_features,
"labels": labels
}
# 映射数据集
dataset = dataset.cast_column("audio", Audio(sampling_rate=16000))
processed_dataset = dataset.map(
prepare_dataset,
remove_columns=dataset["train"].column_names,
)
# 训练配置
from transformers import Seq2SeqTrainer, Seq2SeqTrainingArguments
training_args = Seq2SeqTrainingArguments(
output_dir="./whisper-finetuned",
per_device_train_batch_size=16,
gradient_accumulation_steps=4,
learning_rate=1e-5,
warmup_steps=100,
max_steps=2000,
fp16=True,
evaluation_strategy="steps",
save_steps=500,
logging_steps=50,
predict_with_generate=True,
generation_max_length=225,
)
trainer = Seq2SeqTrainer(
model=model,
args=training_args,
train_dataset=processed_dataset["train"],
tokenizer=processor.feature_extractor,
)
trainer.train()
trainer.save_model("./whisper-finetuned-final")
7.5 微调效果评估
import jiwer
def evaluate_wer(model, test_data):
"""评估词错误率"""
predictions = []
references = []
for sample in test_data:
result = model.transcribe(sample["audio"])
predictions.append(result["text"])
references.append(sample["text"])
wer = jiwer.wer(references, predictions)
cer = jiwer.cer(references, predictions)
print(f"WER: {wer:.2%}")
print(f"CER: {cer:.2%}")
return wer, cer
8. 与主流商业语音API对比
| 维度 | Whisper (本地) | Azure Speech | Google Speech-to-Text | Amazon Transcribe |
|---|---|---|---|---|
| 费用 | 开源免费(自付算力) | $1/小时 | $0.006-0.024/15秒 | $0.024/分钟 |
| 语言支持 | 99种 | 100+种 | 125+种 | 100+种 |
| 实时能力 | 需自行实现 | 原生支持 | 原生支持 | 原生支持 |
| 准确率(英文) | ~2.7% WER | ~3.5% WER | ~3.2% WER | ~4.0% WER |
| 中文准确率 | 优秀 | 优秀 | 良好 | 良好 |
| 说话人分离 | 需第三方 | 内置 | 内置 | 内置 |
| 自定义词汇 | 微调 | 支持 | 支持 | 支持 |
| 数据隐私 | 完全本地 | 云端处理 | 云端处理 | 云端处理 |
| 部署复杂度 | 较高 | 低 | 低 | 低 |
8.1 选型决策流程
需要数据隐私?
├── 是 → Whisper本地部署
└── 否 →
├── 预算充足 + 需要企业级SLA → Azure Speech
├── 需要最广泛语言支持 → Google Speech-to-Text
└── 已在AWS生态 → Amazon Transcribe
8.2 混合方案
实际项目中常采用混合策略:
class HybridTranscriber:
"""混合转录器:本地Whisper + 云端API作为fallback"""
def __init__(self):
self.whisper_model = whisper.load_model("large-v3")
self.confidence_threshold = 0.8
def transcribe(self, audio_path, language="zh"):
# 先用本地Whisper
result = self.whisper_model.transcribe(audio_path, language=language)
# 检查置信度
avg_logprob = sum(
seg["avg_logprob"] for seg in result["segments"]
) / max(len(result["segments"]), 1)
if avg_logprob > -0.5: # 置信度足够
return {"source": "whisper", "text": result["text"]}
# 低置信度时调用云端API
return self._fallback_to_cloud(audio_path, language)
def _fallback_to_cloud(self, audio_path, language):
# 调用Azure/Google API
# ... 实现省略
pass
9. 字幕生成应用
9.1 SRT字幕生成
import whisper
from datetime import timedelta
def generate_srt(audio_path, output_path="output.srt", language="zh"):
"""生成SRT格式字幕文件"""
model = whisper.load_model("large-v3")
result = model.transcribe(
audio_path,
language=language,
word_timestamps=True,
)
def format_timestamp(seconds):
td = timedelta(seconds=seconds)
hours = int(td.total_seconds() // 3600)
minutes = int((td.total_seconds() % 3600) // 60)
secs = int(td.total_seconds() % 60)
millis = int((td.total_seconds() % 1) * 1000)
return f"{hours:02d}:{minutes:02d}:{secs:02d},{millis:03d}"
with open(output_path, "w", encoding="utf-8") as f:
for i, segment in enumerate(result["segments"], 1):
start = format_timestamp(segment["start"])
end = format_timestamp(segment["end"])
text = segment["text"].strip()
f.write(f"{i}\n")
f.write(f"{start} --> {end}\n")
f.write(f"{text}\n\n")
print(f"字幕已保存到 {output_path}")
return output_path
generate_srt("video.mp4")
9.2 ASS字幕(带样式)
def generate_ass(audio_path, output_path="output.ass", language="zh"):
"""生成ASS格式字幕(支持样式)"""
model = whisper.load_model("large-v3")
result = model.transcribe(audio_path, language=language)
ass_header = """[Script Info]
Title: Whisper Generated Subtitles
ScriptType: v4.00+
PlayResX: 1920
PlayResY: 1080
[V4+ Styles]
Format: Name,Fontname,Fontsize,PrimaryColour,OutlineColour,Bold,Italic,BorderStyle,Outline,Shadow,Alignment,MarginL,MarginR,MarginV,Encoding
Style: Default,Noto Sans CJK SC,48,&H00FFFFFF,&H00000000,-1,0,1,2,1,2,20,20,30,1
[Events]
Format: Layer,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text
"""
def format_ass_time(seconds):
hours = int(seconds // 3600)
minutes = int((seconds % 3600) // 60)
secs = seconds % 60
return f"{hours}:{minutes:02d}:{secs:05.2f}"
with open(output_path, "w", encoding="utf-8") as f:
f.write(ass_header)
for seg in result["segments"]:
start = format_ass_time(seg["start"])
end = format_ass_time(seg["end"])
text = seg["text"].strip()
f.write(f"Dialogue: 0,{start},{end},Default,,0,0,0,,{text}\n")
print(f"ASS字幕已保存到 {output_path}")
9.3 WebVTT字幕(Web播放器用)
def generate_vtt(audio_path, output_path="output.vtt"):
"""生成WebVTT格式字幕"""
model = whisper.load_model("base")
result = model.transcribe(audio_path)
def format_vtt_time(seconds):
hours = int(seconds // 3600)
minutes = int((seconds % 3600) // 60)
secs = int(seconds % 60)
millis = int((seconds % 1) * 1000)
return f"{hours:02d}:{minutes:02d}:{secs:02d}.{millis:03d}"
with open(output_path, "w", encoding="utf-8") as f:
f.write("WEBVTT\n\n")
for i, seg in enumerate(result["segments"], 1):
start = format_vtt_time(seg["start"])
end = format_vtt_time(seg["end"])
f.write(f"{i}\n")
f.write(f"{start} --> {end}\n")
f.write(f"{seg['text'].strip()}\n\n")
10. 会议记录系统构建
10.1 系统架构
麦克风/录音 → 音频预处理 → VAD分段 → Whisper转录 → 说话人分离 → 会议纪要生成
↓
降噪/增强 → 标点恢复 → 关键词提取 → 结构化输出
10.2 完整会议记录系统
import whisper
import torch
import numpy as np
from datetime import datetime
from pyannote.audio import Pipeline
class MeetingRecorder:
def __init__(self, whisper_model="large-v3"):
# Whisper模型
self.whisper = whisper.load_model(whisper_model)
# 说话人分离模型(需要HuggingFace token)
self.diarization = Pipeline.from_pretrained(
"pyannote/speaker-diarization-3.1",
use_auth_token="YOUR_HF_TOKEN"
)
def process_meeting(self, audio_path, output_dir="./meeting_output"):
"""处理会议录音"""
import os
os.makedirs(output_dir, exist_ok=True)
print("📝 步骤1/3:语音转录...")
result = self.whisper.transcribe(
audio_path,
language="zh",
word_timestamps=True,
)
print("👥 步骤2/3:说话人分离...")
diarization_result = self.diarization(audio_path)
print("📋 步骤3/3:生成会议记录...")
meeting_notes = self._merge_results(result, diarization_result)
# 保存结果
output_path = os.path.join(output_dir, "meeting_notes.md")
self._save_meeting_notes(meeting_notes, output_path)
print(f"✅ 会议记录已保存到 {output_path}")
return meeting_notes
def _merge_results(self, whisper_result, diarization_result):
"""合并转录和说话人分离结果"""
segments = []
for turn, _, speaker in diarization_result.itertracks(yield_label=True):
# 找到该时间段内的Whisper转录
text_parts = []
for seg in whisper_result["segments"]:
if seg["start"] >= turn.start and seg["end"] <= turn.end:
text_parts.append(seg["text"].strip())
if text_parts:
segments.append({
"speaker": speaker,
"start": turn.start,
"end": turn.end,
"text": " ".join(text_parts)
})
return segments
def _save_meeting_notes(self, notes, output_path):
"""保存为Markdown格式"""
with open(output_path, "w", encoding="utf-8") as f:
f.write(f"# 会议记录\n\n")
f.write(f"**日期**: {datetime.now().strftime('%Y-%m-%d %H:%M')}\n\n")
f.write("---\n\n")
current_speaker = None
for note in notes:
if note["speaker"] != current_speaker:
current_speaker = note["speaker"]
f.write(f"\n### {current_speaker}\n\n")
start_time = self._format_time(note["start"])
f.write(f"**[{start_time}]** {note['text']}\n\n")
def _format_time(self, seconds):
minutes = int(seconds // 60)
secs = int(seconds % 60)
return f"{minutes:02d}:{secs:02d}"
# 使用
recorder = MeetingRecorder()
notes = recorder.process_meeting("meeting_2024.mp3")
10.3 实时会议转录服务
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse
import whisper
import numpy as np
import asyncio
import json
app = FastAPI()
model = whisper.load_model("base")
@app.websocket("/ws/transcribe")
async def websocket_transcribe(websocket: WebSocket):
"""WebSocket实时转录端点"""
await websocket.accept()
audio_buffer = np.array([], dtype=np.float32)
try:
while True:
# 接收音频数据
data = await websocket.receive_bytes()
audio_chunk = np.frombuffer(data, dtype=np.float32)
audio_buffer = np.append(audio_buffer, audio_chunk)
# 每积累3秒处理一次
if len(audio_buffer) >= 16000 * 3:
audio_segment = whisper.pad_or_trim(audio_buffer)
mel = whisper.log_mel_spectrogram(audio_segment).to(model.device)
result = model.decode(mel, whisper.DecodingOptions(
language="zh",
without_timestamps=True,
))
await websocket.send_json({
"text": result.text,
"timestamp": asyncio.get_event_loop().time()
})
audio_buffer = np.array([], dtype=np.float32)
except WebSocketDisconnect:
print("客户端断开连接")
# 前端页面
@app.get("/")
async def get():
return HTMLResponse("""
<!DOCTYPE html>
<html>
<body>
<h1>实时语音转录</h1>
<button id="startBtn">开始录音</button>
<div id="output" style="margin-top:20px;font-size:18px;"></div>
<script>
const startBtn = document.getElementById('startBtn');
const output = document.getElementById('output');
let ws, mediaRecorder;
startBtn.onclick = async () => {
const stream = await navigator.mediaDevices.getUserMedia({audio: true});
ws = new WebSocket('ws://localhost:8000/ws/transcribe');
ws.onmessage = (e) => {
const data = JSON.parse(e.data);
output.innerHTML += `<p>${data.text}</p>`;
};
const audioContext = new AudioContext({sampleRate: 16000});
const source = audioContext.createMediaStreamSource(stream);
const processor = audioContext.createScriptProcessor(4096, 1, 1);
processor.onaudioprocess = (e) => {
const data = e.inputBuffer.getChannelData(0);
ws.send(data.buffer);
};
source.connect(processor);
processor.connect(audioContext.destination);
};
</script>
</body>
</html>
""")
11. 大规模部署方案
11.1 架构设计
┌──────────────┐
│ 负载均衡 │
│ (Nginx/K8s) │
└──────┬───────┘
│
┌────────────┼────────────┐
│ │ │
┌─────┴─────┐ ┌───┴─────┐ ┌────┴────┐
│ Worker 1 │ │ Worker 2│ │ Worker 3│
│ GPU: T4 │ │ GPU: T4 │ │ GPU: T4 │
└─────┬─────┘ └────┬────┘ └────┬────┘
│ │ │
└────────────┼────────────┘
│
┌──────┴───────┐
│ 消息队列 │
│ (Redis/RMQ) │
└──────┬───────┘
│
┌──────┴───────┐
│ 对象存储 │
│ (MinIO/S3) │
└──────────────┘
11.2 Docker化部署
# Dockerfile
FROM nvidia/cuda:11.8.0-runtime-ubuntu22.04
RUN apt-get update && apt-get install -y \
python3.10 \
python3-pip \
ffmpeg \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip3 install --no-cache-dir -r requirements.txt
COPY . .
# 预下载模型
RUN python3 -c "import whisper; whisper.load_model('large-v3')"
CMD ["python3", "worker.py"]
# docker-compose.yml
version: '3.8'
services:
redis:
image: redis:7-alpine
ports:
- "6379:6379"
minio:
image: minio/minio
ports:
- "9000:9000"
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin
command: server /data
whisper-worker-1:
build: .
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
environment:
- REDIS_URL=redis://redis:6379
- MODEL_NAME=large-v3-turbo
depends_on:
- redis
- minio
whisper-worker-2:
build: .
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
environment:
- REDIS_URL=redis://redis:6379
- MODEL_NAME=large-v3-turbo
depends_on:
- redis
- minio
api:
build: .
command: python3 api_server.py
ports:
- "8000:8000"
environment:
- REDIS_URL=redis://redis:6379
depends_on:
- redis
11.3 异步任务队列
# worker.py
import redis
import json
import whisper
import time
class WhisperWorker:
def __init__(self):
self.redis = redis.from_url("redis://localhost:6379")
self.model = whisper.load_model("large-v3-turbo")
self.queue_name = "whisper:tasks"
self.result_prefix = "whisper:result:"
def run(self):
print("Worker启动,等待任务...")
while True:
# 阻塞等待任务
_, task_data = self.redis.brpop(self.queue_name)
task = json.loads(task_data)
task_id = task["id"]
audio_path = task["audio_path"]
options = task.get("options", {})
print(f"处理任务: {task_id}")
start_time = time.time()
try:
result = self.model.transcribe(audio_path, **options)
elapsed = time.time() - start_time
self.redis.setex(
f"{self.result_prefix}{task_id}",
3600, # 1小时过期
json.dumps({
"status": "completed",
"text": result["text"],
"segments": result["segments"],
"language": result["language"],
"processing_time": elapsed,
}, ensure_ascii=False)
)
print(f"任务 {task_id} 完成,耗时 {elapsed:.1f}s")
except Exception as e:
self.redis.setex(
f"{self.result_prefix}{task_id}",
3600,
json.dumps({"status": "error", "error": str(e)})
)
print(f"任务 {task_id} 失败: {e}")
if __name__ == "__main__":
worker = WhisperWorker()
worker.run()
# api_server.py
from fastapi import FastAPI, UploadFile, File
import redis
import uuid
import json
import os
app = FastAPI()
r = redis.from_url("redis://localhost:6379")
@app.post("/transcribe")
async def transcribe(
file: UploadFile = File(...),
language: str = "zh",
model: str = "large-v3-turbo",
):
"""提交转录任务"""
# 保存上传文件
task_id = str(uuid.uuid4())
audio_path = f"/tmp/{task_id}_{file.filename}"
with open(audio_path, "wb") as f:
f.write(await file.read())
# 提交到队列
task = {
"id": task_id,
"audio_path": audio_path,
"options": {"language": language}
}
r.lpush("whisper:tasks", json.dumps(task))
return {"task_id": task_id, "status": "queued"}
@app.get("/result/{task_id}")
async def get_result(task_id: str):
"""获取转录结果"""
result = r.get(f"whisper:result:{task_id}")
if result is None:
return {"status": "pending"}
return json.loads(result)
11.4 性能优化策略
1. 模型量化
# 使用CTranslate2量化模型
import ctranslate2
import transformers
# 转换模型
converter = ctranslate2.converters.TransformersConverter(
"openai/whisper-large-v3",
quantization="int8", # int8量化,显存减少50%
)
converter.save_model("./whisper-large-v3-int8")
# 加载量化模型
model = ctranslate2.Translator("./whisper-large-v3-int8", device="cuda")
2. 批量处理
def batch_transcribe(model, audio_paths, batch_size=8):
"""批量转录提高GPU利用率"""
results = []
for i in range(0, len(audio_paths), batch_size):
batch = audio_paths[i:i + batch_size]
# 并行处理
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor(max_workers=batch_size) as executor:
futures = {
executor.submit(model.transcribe, path): path
for path in batch
}
for future in concurrent.futures.as_completed(futures):
results.append(future.result())
return results
3. 缓存策略
import hashlib
import json
import os
class TranscriptionCache:
def __init__(self, cache_dir="./cache"):
self.cache_dir = cache_dir
os.makedirs(cache_dir, exist_ok=True)
def get_cache_key(self, audio_path, options):
"""基于文件内容和参数生成缓存key"""
with open(audio_path, "rb") as f:
file_hash = hashlib.md5(f.read()).hexdigest()
options_hash = hashlib.md5(json.dumps(options, sort_keys=True).encode()).hexdigest()
return f"{file_hash}_{options_hash}"
def get(self, audio_path, options):
key = self.get_cache_key(audio_path, options)
cache_path = os.path.join(self.cache_dir, f"{key}.json")
if os.path.exists(cache_path):
with open(cache_path, "r") as f:
return json.load(f)
return None
def set(self, audio_path, options, result):
key = self.get_cache_key(audio_path, options)
cache_path = os.path.join(self.cache_dir, f"{key}.json")
with open(cache_path, "w", encoding="utf-8") as f:
json.dump(result, f, ensure_ascii=False, indent=2)
12. 最佳实践与常见问题
12.1 最佳实践总结
音频质量
- 采样率16kHz足够,无需更高
- 单声道优于立体声(减少处理量)
- 预处理降噪可显著提升准确率
- 避免音频压缩过度(MP3 128kbps以上为佳)
模型选择
- 开发阶段用
base,生产用large-v3-turbo - 中文场景加
initial_prompt引导 - 长音频确保每段不超过30秒
性能优化
- GPU推理比CPU快10-50倍
- int8量化可在几乎不损失精度的情况下减少50%显存
- faster-whisper比原版快4-8倍
- 批量处理提高GPU利用率
生产部署
- 使用Redis/RabbitMQ做任务队列
- 实现缓存避免重复转录
- 监控GPU利用率和队列深度
- 准备CPU fallback方案
12.2 常见问题
Q: 识别结果出现幻觉(重复文本/无关内容)?
# 调整参数抑制幻觉
result = model.transcribe(
"audio.mp3",
no_speech_threshold=0.6, # 提高静音检测阈值
compression_ratio_threshold=2.4, # 过滤异常压缩比
logprob_threshold=-1.0, # 过滤低置信度
condition_on_previous_text=False, # 禁止依赖前文(减少幻觉传播)
)
Q: 中文标点符号缺失?
# 使用initial_prompt引导标点
result = model.transcribe(
"audio.mp3",
language="zh",
initial_prompt="以下是一段带有标点符号的中文语音内容。",
)
Q: 专业术语识别不准?
# 方法1:initial_prompt中列出关键词
result = model.transcribe(
"medical_audio.mp3",
initial_prompt="这是一段关于心电图、CT扫描、核磁共振的医疗讨论。",
)
# 方法2:后处理替换
corrections = {
"心店图": "心电图",
"核磁工正": "核磁共振",
}
text = result["text"]
for wrong, correct in corrections.items():
text = text.replace(wrong, correct)
Q: 如何处理多人同时说话?
- Whisper不支持多人分离,需配合 pyannote-audio 做说话人分离
- 先分离再转录,或先转录再对齐时间戳
Q: GPU内存不足怎么办?
# 方案1:使用更小模型
model = whisper.load_model("small")
# 方案2:使用faster-whisper + int8量化
from faster_whisper import WhisperModel
model = WhisperModel("large-v3", device="cuda", compute_type="int8")
# 方案3:强制CPU(速度慢但无显存限制)
model = whisper.load_model("large-v3", device="cpu")
总结
Whisper为语音识别领域带来了一个真正通用的开源解决方案。从个人字幕生成到企业级会议系统,从单语言转录到多语言翻译,Whisper都能胜任。关键要点:
- 选对模型:根据场景选择合适大小,
large-v3-turbo是生产环境首选 - 优化输入:好的音频预处理 = 更好的识别结果
- 善用提示词:
initial_prompt是提升特定场景准确率的利器 - 工程化思维:缓存、队列、量化,让Whisper在生产环境稳定运行
- 持续迭代:关注社区更新,Whisper生态在快速发展
语音是人机交互最自然的方式,掌握Whisper,你就拥有了将声音转化为价值的能力。
📚 扩展资源