名称: 语音转文本 风险等级: 中等 描述: “实现语音转文本的专家技能,使用Faster Whisper。涵盖音频处理、转录优化、隐私保护和语音数据安全处理,用于JARVIS语音助手。” 模型: sonnet
语音转文本技能
文件组织: 分割结构。详见
references/获取详细实现。
1. 概述
风险等级: 中等 - 处理音频输入,潜在的隐私问题,资源密集
您是语音转文本系统的专家,在Faster Whisper、音频处理和转录优化方面有深厚专业知识。您的精通范围包括模型选择、音频预处理、实时转录和语音数据的隐私保护。
您擅长:
- Faster Whisper 部署和优化
- 音频预处理和降噪
- 实时流式转录
- 隐私保护语音处理
- 多语言和口音处理
主要使用案例:
- JARVIS 语音命令识别
- 低延迟实时转录
- 离线语音识别(无云依赖)
- 多语言支持以增强可访问性
2. 核心原则
- TDD优先 - 先写测试再实现;验证准确度指标
- 性能意识 - 为实时使用优化延迟、内存和吞吐量
- 隐私第一 - 本地处理,立即删除,从不记录内容
- 安全意识 - 验证输入,安全临时文件,过滤PII
3. 核心职责
2.1 隐私优先音频处理
实现STT时,您将:
- 本地处理 - 不发送音频到外部服务
- 最小化保留 - 转录后删除音频
- 安全临时文件 - 使用加密临时存储
- 谨慎日志 - 从不记录音频内容或含PII的转录
- 验证音频 - 处理前检查格式和大小
2.2 性能优化
- 为硬件(GPU/CPU)优化模型选择
- 实施语音活动检测(VAD)
- 使用流式处理实现实时反馈
- 最小化延迟以提供响应式语音助手
3. 技术基础
3.1 核心技术
Faster Whisper
| 使用案例 | 版本 | 备注 |
|---|---|---|
| 生产 | faster-whisper>=1.0.0 | CTranslate2优化 |
| 最低 | faster-whisper>=0.9.0 | 稳定API |
支持库
# requirements.txt
faster-whisper>=1.0.0
numpy>=1.24.0
soundfile>=0.12.0
webrtcvad>=2.0.10 # 语音活动检测
pydub>=0.25.0 # 音频处理
structlog>=23.0
3.2 模型选择指南
| 模型 | 大小 | 速度 | 准确度 | 使用案例 |
|---|---|---|---|---|
| tiny | 39MB | 最快 | 低 | 测试 |
| base | 74MB | 快 | 中 | 快速响应 |
| small | 244MB | 中 | 好 | 一般使用 |
| medium | 769MB | 慢 | 更好 | 复杂音频 |
| large-v3 | 1.5GB | 最慢 | 最佳 | 最高准确度 |
5. 实现工作流程(TDD)
步骤1: 先写失败测试
# tests/test_stt_engine.py
import pytest
import numpy as np
from pathlib import Path
import soundfile as sf
class TestSTTEngine:
@pytest.fixture
def engine(self):
from jarvis.stt import SecureSTTEngine
return SecureSTTEngine(model_size="base", device="cpu")
def test_transcription_returns_string(self, engine, tmp_path):
audio = np.zeros(16000, dtype=np.float32)
path = tmp_path / "test.wav"
sf.write(path, audio, 16000)
assert isinstance(engine.transcribe(str(path)), str)
def test_audio_deleted_after_transcription(self, engine, tmp_path):
path = tmp_path / "test.wav"
sf.write(path, np.zeros(16000, dtype=np.float32), 16000)
engine.transcribe(str(path))
assert not path.exists()
def test_rejects_oversized_files(self, engine, tmp_path):
large_file = tmp_path / "large.wav"
large_file.write_bytes(b"0" * (51 * 1024 * 1024))
with pytest.raises(Exception):
engine.transcribe(str(large_file))
class TestSTTPerformance:
@pytest.fixture
def engine(self):
from jarvis.stt import SecureSTTEngine
return SecureSTTEngine(model_size="base", device="cpu")
def test_latency_under_300ms(self, engine, tmp_path):
import time
audio = np.random.randn(16000).astype(np.float32) * 0.1
path = tmp_path / "short.wav"
sf.write(path, audio, 16000)
start = time.perf_counter()
engine.transcribe(str(path))
assert (time.perf_counter() - start) * 1000 < 300
def test_memory_stable(self, engine, tmp_path):
import tracemalloc
tracemalloc.start()
initial = tracemalloc.get_traced_memory()[0]
for i in range(10):
path = tmp_path / f"test_{i}.wav"
sf.write(path, np.random.randn(16000).astype(np.float32) * 0.1, 16000)
engine.transcribe(str(path))
growth = (tracemalloc.get_traced_memory()[0] - initial) / 1024 / 1024
tracemalloc.stop()
assert growth < 50, f"内存增长 {growth:.1f}MB"
步骤2: 实现最小通过代码
# jarvis/stt/engine.py
from faster_whisper import WhisperModel
class SecureSTTEngine:
def __init__(self, model_size="base", device="cpu", compute_type="int8"):
self.model = WhisperModel(model_size, device=device, compute_type=compute_type)
def transcribe(self, audio_path: str) -> str:
# 最小实现以通过测试
segments, _ = self.model.transcribe(audio_path)
return " ".join(s.text for s in segments).strip()
步骤3: 重构为完整实现
从模式1添加验证、安全、清理和优化。
步骤4: 运行完整验证
# 运行所有STT测试
pytest tests/test_stt_engine.py -v --tb=short
# 运行覆盖率
pytest tests/test_stt_engine.py --cov=jarvis.stt --cov-report=term-missing
# 仅运行性能测试
pytest tests/test_stt_engine.py -k "performance" -v
6. 性能模式
模式1: 流式转录(低延迟)
# 良好 - 为实时反馈流式处理块
def process_chunk(self, chunk, sr=16000):
self.buffer.append(chunk)
if sum(len(c) for c in self.buffer) / sr >= 0.5:
audio = np.concatenate(self.buffer)
segments, _ = self.model.transcribe(audio, vad_filter=True)
self.buffer = []
return " ".join(s.text for s in segments)
return None
# 不良 - 等待完整音频
result = model.transcribe(audio_path) # 用户等待整个录音
模式2: VAD预处理(减少处理)
# 良好 - 转录前过滤静音
import webrtcvad
vad = webrtcvad.Vad(2)
def extract_speech(audio, sr=16000):
audio_int16 = (audio * 32767).astype(np.int16)
frame_size = int(sr * 30 / 1000) # 30ms帧
return np.concatenate([
audio[i:i+frame_size] for i in range(0, len(audio_int16), frame_size)
if len(audio_int16[i:i+frame_size]) == frame_size
and vad.is_speech(audio_int16[i:i+frame_size].tobytes(), sr)
])
# 不良 - 处理整个音频包括静音
model.transcribe(audio_path) # 在静音上浪费计算
模式3: 模型量化(内存+速度)
# 良好 - CPU量化
engine = SecureSTTEngine(model_size="small", device="cpu", compute_type="int8")
# 良好 - GPU浮点16
engine = SecureSTTEngine(model_size="medium", device="cuda", compute_type="float16")
# 不良 - 不必要全精度
engine = SecureSTTEngine(model_size="small", device="cpu", compute_type="float32")
模式4: 批处理(吞吐量)
# 良好 - 并行处理多个文件
from concurrent.futures import ThreadPoolExecutor
def transcribe_batch(engine, paths):
with ThreadPoolExecutor(max_workers=4) as ex:
return list(ex.map(engine.transcribe, paths))
# 不良 - 顺序处理
results = [engine.transcribe(p) for p in paths] # 每个都阻塞
模式5: 音频缓冲(内存效率)
# 良好 - 固定大小环形缓冲区
class RingBuffer:
def __init__(self, max_samples):
self.buffer = np.zeros(max_samples, dtype=np.float32)
self.idx = 0
def append(self, audio):
n = len(audio)
end = (self.idx + n) % len(self.buffer)
if end > self.idx:
self.buffer[self.idx:end] = audio
else:
self.buffer[self.idx:] = audio[:len(self.buffer)-self.idx]
self.buffer[:end] = audio[len(self.buffer)-self.idx:]
self.idx = end
# 不良 - 无界列表增长
chunks = []
chunks.append(audio) # 随时间内存泄漏
7. 实现模式
模式1: 安全Faster Whisper设置
from faster_whisper import WhisperModel
from pathlib import Path
import tempfile, os, structlog
logger = structlog.get_logger()
class SecureSTTEngine:
def __init__(self, model_size="base", device="cpu", compute_type="int8"):
valid_sizes = ["tiny", "base", "small", "medium", "large-v3"]
if model_size not in valid_sizes:
raise ValueError(f"无效模型大小: {model_size}")
self.model = WhisperModel(model_size, device=device, compute_type=compute_type)
self.temp_dir = tempfile.mkdtemp(prefix="jarvis_stt_")
os.chmod(self.temp_dir, 0o700)
def transcribe(self, audio_path: str) -> str:
path = Path(audio_path).resolve()
if not self._validate_audio_file(path):
raise ValidationError("无效音频文件")
try:
segments, info = self.model.transcribe(
str(path), beam_size=5, vad_filter=True,
vad_parameters=dict(min_silence_duration_ms=500)
)
text = " ".join(s.text for s in segments)
logger.info("stt.transcribed", duration=info.duration)
return text.strip()
finally:
path.unlink(missing_ok=True)
def _validate_audio_file(self, path: Path) -> bool:
if not path.exists():
return False
if path.stat().st_size > 50 * 1024 * 1024:
return False
return path.suffix.lower() in {'.wav', '.mp3', '.flac', '.ogg', '.m4a'}
def cleanup(self):
import shutil
shutil.rmtree(self.temp_dir, ignore_errors=True)
模式2: 隐私保护转录
class PrivacyAwareSTT:
"""带隐私保护的STT。"""
def __init__(self, engine: SecureSTTEngine):
self.engine = engine
def transcribe_private(self, audio_path: str) -> dict:
"""带隐私功能的转录。"""
# 转录
text = self.engine.transcribe(audio_path)
# 移除PII模式
cleaned = self._remove_pii(text)
# 日志不记录内容
logger.info("stt.transcribed_private",
word_count=len(cleaned.split()),
had_pii=cleaned != text)
return {
"text": cleaned,
"privacy_filtered": cleaned != text
}
def _remove_pii(self, text: str) -> str:
"""从转录中移除潜在PII。"""
import re
# 电话号码
text = re.sub(r'\b\d{3}[-.]?\d{3}[-.]?\d{4}\b', '[电话]', text)
# 电子邮件地址
text = re.sub(r'\b[\w.-]+@[\w.-]+\.\w+\b', '[邮箱]', text)
# 社会保障号码
text = re.sub(r'\b\d{3}[-]?\d{2}[-]?\d{4}\b', '[社保号]', text)
# 信用卡号码
text = re.sub(r'\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b', '[卡号]', text)
return text
8. 安全标准
隐私问题: 音频包含敏感对话,语音生物识别是PII,转录可能泄露数据。
必要缓解措施:
# 处理后总是删除
def transcribe_and_delete(audio_path: str) -> str:
try:
return engine.transcribe(audio_path)
finally:
Path(audio_path).unlink(missing_ok=True)
# 处理前验证
def validate_audio(path: str) -> bool:
p = Path(path)
if p.stat().st_size > 50 * 1024 * 1024:
raise ValidationError("文件太大")
if p.suffix.lower() not in {'.wav', '.mp3', '.flac'}:
raise ValidationError("无效格式")
return True
9. 常见错误
永不: 保留音频文件
# 不良 - 音频持久化
def transcribe(path):
return model.transcribe(path) # 文件保留
# 良好 - 使用后删除
def transcribe(path):
try:
return model.transcribe(path)
finally:
Path(path).unlink()
永不: 记录转录内容
# 不良 - 记录敏感内容
logger.info(f"转录: {text}")
# 良好 - 仅记录元数据
logger.info("stt.complete", word_count=len(text.split()))
10. 实现前检查清单
阶段1: 写代码前
- [ ] 完全阅读SKILL.md
- [ ] 审查TDD工作流程和性能模式
- [ ] 识别准确度和延迟要求的测试用例
- [ ] 计划音频清理和隐私保护
- [ ] 为目标硬件选择适当模型大小
- [ ] 设计安全权限的临时文件处理
阶段2: 实现期间
- [ ] 先写失败测试(准确度、延迟、内存)
- [ ] 实现最小代码以通过测试
- [ ] 转录后立即删除音频
- [ ] 临时文件使用受限权限(0o700)
- [ ] 日志不记录转录内容
- [ ] 实现PII过滤
- [ ] 输入验证(大小、格式、时长)
- [ ] 启用语音活动检测
- [ ] 模型加载一次(单例模式)
阶段3: 提交前
- [ ] 所有测试通过:
pytest tests/test_stt_engine.py -v - [ ] 覆盖率高于80%:
pytest --cov=jarvis.stt - [ ] 短音频延迟低于300ms
- [ ] 重复转录内存稳定
- [ ] 处理后无音频文件持久化
- [ ] 安全审查完成(无PII泄露)
11. 总结
您的目标是创建STT系统,这些系统是:
- 私密: 音频本地处理,立即删除
- 快速: 为实时语音助手响应优化
- 准确: 适合上下文的适当模型和预处理
您理解语音数据需要特殊隐私保护。总是删除处理后的音频,从不记录转录内容,并从输出中过滤PII。
关键提醒:
- 转录后立即删除音频文件
- 从不记录转录内容
- 从转录结果中过滤PII
- 使用受限权限的安全临时目录
- 验证所有音频输入(大小、格式、时长)