1. 为什么我要把录音转文字这件事从云端搬回本地
做内容这行的人都有个共同的痛点:采访录音、会议记录、灵感速记,攒了一堆音频文件,真要整理成文字的时候,要么手动听打累到崩溃,要么用在线转写服务,按分钟计费不说,还得把录音上传到别人的服务器。我做深度访谈那阵子,每个月产生的录音素材少说十几个小时,用在线工具转写,一个月下来成本不低,更关键的是,有些采访对象聊的内容比较私密,上传到云端总让我心里不踏实。
后来我开始找本地离线转写方案。试过几个开源项目,要么识别准确率感人,要么速度慢得让人想砸键盘。直到用上faster-whisper,才算真正把“录音一键变文字”这件事跑通了。它本质上是 OpenAI 的 Whisper 模型的加速重实现,底层用 CTranslate2 做推理引擎,相比原版 Whisper 在同等精度下速度能快好几倍,显存占用也更低。最让我满意的是,它完全跑在本地,断网也能用,音频文件不出本机,隐私和成本两个问题一次性解决。
这篇文章我打算把 faster-whisper 本地离线转写这套方案从头到尾拆一遍。从环境搭建、模型选型、参数调优,到批量处理、时间戳对齐、常见报错排查,都会给到可直接复现的步骤和命令。如果你手头有大量录音需要整理,或者对数据隐私有要求,又或者单纯想省下在线转写的订阅费,这套方案值得你花一个下午折腾一下。哪怕你之前没怎么碰过 Python 环境,跟着走也能跑起来。
2. 方案整体设计与核心思路拆解
2.1 为什么是 faster-whisper 而不是原版 Whisper
先说清楚选型逻辑。OpenAI 开源的 Whisper 模型本身识别能力很强,多语言、抗噪、带标点,但原版实现有个硬伤:推理速度慢,尤其是 large 模型,在普通显卡上跑一段一小时的录音,可能要等上十几二十分钟。faster-whisper 做的事情,是把 Whisper 的模型权重转换成 CTranslate2 格式,用这个专门为 Transformer 推理优化的引擎来跑,同时支持 FP16 和 INT8 量化。
实测下来,同样的 large-v3 模型,faster-whisper 在 GPU 上比原版快 4 倍左右,显存占用从原来的 10GB 级别降到 5GB 以内。如果用量化后的 INT8 模型,甚至能在没有独立显卡的机器上跑,只是速度会慢一些。这个差距在批量处理场景下非常明显——我手头三十多个小时的访谈录音,用 faster-whisper 一个晚上就跑完了,换成原版估计得挂机两天。
另一个关键点是它自带VAD(语音活动检测)过滤。录音里难免有长时间静音、环境噪音,VAD 会先把这些片段切掉,只把真正有人声的部分送进模型,既省时间又减少幻觉输出。原版 Whisper 在处理静音段时偶尔会“编造”出一些莫名其妙的文字,VAD 能有效缓解这个问题。
2.2 本地离线转写的整体流程
整套方案的流程其实不复杂,拆开看就是四步:音频预处理、模型加载、推理转写、结果输出。但每一步都有值得说道的细节。
音频预处理这块,faster-whisper 内部会用 PyAV 或 ffmpeg 解码音频,所以你不用提前把文件转成特定格式,mp3、wav、m4a、flac 基本都能直接喂进去。不过如果源文件采样率特别高或者声道数多,提前用 ffmpeg 统一转成 16kHz 单声道 wav,能减少解码环节的意外。
模型加载是性能差异的关键。faster-whisper 支持 tiny、base、small、medium、large-v1、large-v2、large-v3 这几档,还有对应的 distil 蒸馏版本。模型越大越准,但速度和显存要求也越高。我的经验是,中文内容用 medium 起步,追求准确率直接上 large-v3;英文内容 small 或 medium 就够用。首次运行会自动从模型仓库下载权重,之后缓存在本地,后续离线使用不再联网。
推理转写阶段,核心参数是language、beam_size、vad_filter、compute_type。语言指定能避免模型自动检测出错,beam_size 影响解码质量和速度的平衡,compute_type 决定用 FP16 还是 INT8。这些参数怎么调,后面会专门讲。
结果输出支持多种格式,纯文本、带时间戳的 SRT、VTT 字幕,还能拿到每个词级别的时间戳。做字幕或者需要精确定位的时候,词级时间戳特别有用。
2.3 硬件门槛与适用场景
很多人关心的是:我这台机器能不能跑?给个大致参考。有 NVIDIA 独立显卡(显存 6GB 以上)的,large-v3 用 FP16 跑起来很舒服;显存 4GB 左右的,用 medium 或者 large 的 INT8 量化版;只有 CPU 的机器,用 small 或 base 的 INT8 版本,速度慢但能用,适合不赶时间的场景。苹果 M 系列芯片的话,faster-whisper 对 Metal 的支持不如 CUDA 成熟,实际体验一般,可以考虑用 whisper.cpp 那套方案,不过那是另一个话题了。
适用场景我列几个典型的:播客和视频创作者整理口播稿、记者和研究者处理访谈录音、企业做会议纪要、学生整理课堂录音、法律和医疗等对隐私敏感的行业做语音归档。共同点都是音频量大、对隐私有要求、或者想长期省下转写成本。
3. 环境搭建与模型选型实操
3.1 从零搭建运行环境
我假设你用的是 Windows 或者 Linux,macOS 也能参考。第一步是装 Python,建议 3.9 到 3.11 之间的版本,太新或太旧都可能遇到依赖问题。装完 Python 后,强烈建议用虚拟环境隔离,避免和系统里其他项目的包打架。
python -m venv whisper-env # Windows whisper-env\Scripts\activate # Linux / macOS source whisper-env/bin/activate激活虚拟环境后,安装 faster-whisper 本体。如果你有 NVIDIA 显卡并且想用 GPU 加速,还需要确保 CUDA 和 cuDNN 装好。faster-whisper 依赖 CTranslate2,而 CTranslate2 对 CUDA 版本有要求,目前比较稳的是 CUDA 12.x 配 cuDNN 8.x 或 9.x。这一步是新手最容易卡住的地方,我后面会专门讲排查。
pip install faster-whisper如果你需要处理各种格式的音频,再装个 ffmpeg。Windows 上可以去官网下静态包,解压后把 bin 目录加到 PATH;Linux 用包管理器装就行。
# Ubuntu / Debian sudo apt install ffmpeg装完之后验证一下,跑个最简单的转写测试,确认环境没问题再往下走。
3.2 模型档位怎么选才不踩坑
模型选型是这套方案里最需要权衡的一步。我把各档位的特点整理成一张表,方便你对照自己的需求。
| 模型档位 | 参数量级 | 中文准确率 | 速度(相对) | 显存占用(FP16) | 推荐场景 |
|---|---|---|---|---|---|
| tiny | 最小 | 较低 | 极快 | 1GB 以内 | 快速预览、英文简单内容 |
| base | 小 | 一般 | 很快 | 1GB 左右 | 英文日常转写 |
| small | 中 | 尚可 | 快 | 2GB 左右 | 英文为主、中文要求不高 |
| medium | 中大 | 良好 | 中等 | 5GB 左右 | 中文主力档位 |
| large-v3 | 大 | 优秀 | 较慢 | 10GB 左右 | 中文高精度、专业内容 |
| distil-large-v3 | 大(蒸馏) | 良好 | 快 | 6GB 左右 | 英文高精度、追求速度 |
我的实际经验是,中文内容别在 tiny 和 base 上浪费时间,识别出来的错字能让你改到怀疑人生。medium 是性价比拐点,large-v3 是准确率天花板。如果显存不够又想用 large,可以选 INT8 量化版本,精度损失很小,显存直接砍半。
模型名称在代码里这样指定:"large-v3"、"medium"、"small"等。首次加载会自动下载,下载源在国外,网速慢的话可以提前用镜像或者手动下载放到缓存目录。缓存路径一般在~/.cache/huggingface/hub下面。
3.3 首次运行的最小可用示例
先给一个能跑通的最小代码,让你建立信心。新建一个transcribe.py:
from faster_whisper import WhisperModel model_size = "medium" model = WhisperModel(model_size, device="cuda", compute_type="float16") segments, info = model.transcribe("test.mp3", language="zh", beam_size=5) print(f"检测语言: {info.language}, 置信度: {info.language_probability:.2f}") for segment in segments: print(f"[{segment.start:.2f}s -> {segment.end:.2f}s] {segment.text}")几个关键点解释一下。device="cuda"表示用 GPU,没有 GPU 就改成"cpu"。compute_type="float16"是 GPU 上的推荐精度,CPU 上要改成"int8"。language="zh"明确指定中文,避免自动检测在混合语言场景下判断失误。beam_size=5是解码束宽,值越大越准但越慢,5 是个常用平衡点。
跑起来之后你会看到它先加载模型,然后逐段输出识别结果。第一次跑会下载模型,耐心等一会儿。如果这一步顺利,恭喜你,核心链路已经通了。
4. 核心参数调优与批量处理实战
4.1 影响识别质量的几个关键参数
模型选对之后,参数调优是提升识别质量的下一个杠杆。我把最值得调的参数列出来,逐个说清楚它们的作用和推荐值。
beam_size控制解码时的搜索宽度。默认是 5,调到 10 会更准但明显变慢,调到 1 就是贪心解码,最快但容易出错。我的建议是 medium 及以上模型用 5,small 及以下用 3 到 5 之间。
vad_filter是布尔值,开启后会先用语音活动检测切掉静音段。这个参数我强烈建议开启,尤其是处理会议录音、访谈这种有大量停顿的素材。开启后还能配合vad_parameters微调,比如min_silence_duration_ms控制多长的静音算一段,默认 2000 毫秒。
condition_on_previous_text这个参数比较微妙。开启时模型会参考前文来提升连贯性,但在长音频里可能导致错误累积,一旦前面识别错了,后面会跟着错下去。处理长录音时我一般设成False,牺牲一点连贯性换取稳定性。
temperature是采样温度,默认是一组递增值。识别置信度低的时候,模型会尝试用更高的温度重新解码。一般不用动,但如果发现输出特别不稳定,可以固定成 0 强制走确定性解码。
initial_prompt是个被低估的参数。你可以给它一段领域相关的文本,引导模型往那个方向识别。比如转写医学内容时,把常见医学术语塞进去,专有名词的识别率会明显提升。这个技巧在处理专业领域录音时特别好用。
4.2 批量处理大量录音的脚本设计
单文件转写跑通之后,真正的生产力来自批量处理。我手头经常是几十个文件一起处理,手动一个个跑不现实。下面这个脚本是我实际在用的简化版,支持遍历目录、跳过已处理文件、输出多种格式。
import os import json from faster_whisper import WhisperModel AUDIO_DIR = "./audios" OUTPUT_DIR = "./transcripts" MODEL_SIZE = "large-v3" os.makedirs(OUTPUT_DIR, exist_ok=True) model = WhisperModel(MODEL_SIZE, device="cuda", compute_type="float16") def format_timestamp(seconds): h = int(seconds // 3600) m = int((seconds % 3600) // 60) s = int(seconds % 60) ms = int((seconds - int(seconds)) * 1000) return f"{h:02d}:{m:02d}:{s:02d},{ms:03d}" for filename in sorted(os.listdir(AUDIO_DIR)): if not filename.lower().endswith((".mp3", ".wav", ".m4a", ".flac")): continue base = os.path.splitext(filename)[0] txt_path = os.path.join(OUTPUT_DIR, base + ".txt") if os.path.exists(txt_path): print(f"跳过已处理: {filename}") continue print(f"开始处理: {filename}") segments, info = model.transcribe( os.path.join(AUDIO_DIR, filename), language="zh", beam_size=5, vad_filter=True, condition_on_previous_text=False, ) txt_lines = [] srt_lines = [] for i, seg in enumerate(segments, 1): txt_lines.append(seg.text.strip()) srt_lines.append(str(i)) srt_lines.append(f"{format_timestamp(seg.start)} --> {format_timestamp(seg.end)}") srt_lines.append(seg.text.strip()) srt_lines.append("") with open(txt_path, "w", encoding="utf-8") as f: f.write("\n".join(txt_lines)) with open(os.path.join(OUTPUT_DIR, base + ".srt"), "w", encoding="utf-8") as f: f.write("\n".join(srt_lines)) print(f"完成: {filename}")这个脚本有几个设计考量。跳过已处理文件是为了断点续跑,批量任务跑到一半中断了,重新跑不会重复劳动。同时输出 txt 和 srt 是为了兼顾阅读和字幕需求。condition_on_previous_text=False是我处理长录音的固定选择,前面解释过原因。
4.3 时间戳对齐与字幕输出
做视频的人对时间戳要求高。faster-whisper 默认给的是段落级时间戳,也就是每句话的起止时间。如果你需要词级时间戳,在transcribe里加word_timestamps=True,返回的 segment 里会多一个words列表,每个词都有精确的起止时间。
词级时间戳的用途主要是做卡拉OK式字幕,或者需要精确定位某个词出现在哪一秒的场景。不过开启后速度会慢一些,不需要的话别开。
字幕输出这块,SRT 格式最通用,剪辑软件基本都认。VTT 格式适合网页播放器。如果你用的是剪映、Premiere 这类工具,直接导入 SRT 就能自动对齐。我一般会检查一下生成的字幕,把过长或过短的段落手动合并拆分一下,观感会好很多。
提示:faster-whisper 输出的时间戳偶尔会有轻微漂移,尤其是音频开头有较长静音时。如果发现字幕整体偏移,可以在转写前用 ffmpeg 把开头的静音裁掉,或者手动在 SRT 里做整体平移。
5. 常见报错与性能问题排查实录
5.1 环境类报错的排查思路
新手最容易卡在环境上,我把遇到过的典型报错和解决思路整理成速查表。
| 报错信息关键词 | 可能原因 | 解决方向 |
|---|---|---|
Could not load library cudnn | cuDNN 未装或版本不匹配 | 装对应 CUDA 版本的 cuDNN,确认在 PATH 里 |
CUDA out of memory | 显存不足 | 换小模型、用 INT8、减小 beam_size |
No module named 'av' | PyAV 未装 | pip install av |
ffmpeg not found | ffmpeg 未装或不在 PATH | 安装 ffmpeg 并配置环境变量 |
| 模型下载卡住 | 网络问题 | 手动下载模型放到缓存目录 |
Library cublas not found | CUDA 运行库缺失 | 装完整 CUDA Toolkit 或对应运行库 |
CUDA 相关的报错占了新手问题的一大半。核心原则是:CTranslate2 编译时用的 CUDA 版本,要和你系统里装的 CUDA 运行库版本对得上。如果实在搞不定 GPU 环境,先用 CPU 加 INT8 跑起来,虽然慢但至少能用,等熟悉了再折腾 GPU。
5.2 识别质量问题的定位方法
识别质量不达预期,原因可能有很多。我的排查顺序是这样的:先确认模型档位够不够,中文内容 medium 以下基本不用考虑;再检查音频质量,采样率太低、背景噪音太大、多人重叠说话,这些都会拉低准确率;然后看参数,language有没有指定对,beam_size是不是太小,vad_filter有没有开。
如果识别结果里出现大量重复文字,通常是condition_on_previous_text导致的错误累积,关掉它。如果出现莫名其妙的幻觉文字,多半是静音段没被 VAD 过滤掉,检查vad_filter是否开启。如果专有名词老是识别错,试试initial_prompt引导。
音频预处理这块也值得投入。我习惯先用 ffmpeg 把音频统一转成 16kHz 单声道,顺便做一下响度归一化。命令如下:
ffmpeg -i input.mp3 -ar 16000 -ac 1 -af "loudnorm" output.wav-ar 16000指定采样率 16kHz,这是 Whisper 模型的原生采样率,避免内部重采样。-ac 1转单声道,减少计算量。loudnorm做响度归一化,让音量更均匀,对识别稳定性有帮助。
5.3 速度优化的几个实用技巧
速度慢是另一个常见抱怨。除了换小模型和用量化,还有几个技巧。一是确保真的在用 GPU,代码里device="cuda",跑的时候看显卡占用,如果 GPU 没动静说明还在用 CPU。二是compute_type用float16而不是float32,速度差一倍。三是批量处理时模型只加载一次,别每个文件都重新加载,我上面的脚本就是全局加载一次。
还有一个容易被忽略的点:音频解码本身也耗时。如果源文件是高质量 mp3 或 m4a,解码开销不小。提前用 ffmpeg 转成 wav,虽然多一步,但整体可能更快。另外,如果机器内存够大,可以把多个短音频拼成一个长音频一次性转写,减少模型调用次数,不过要注意拼接处的静音处理。
注意:INT8 量化在 CPU 上提速明显,但在 GPU 上收益有限,甚至可能因为反量化开销而变慢。GPU 场景优先用 float16,CPU 场景用 int8。
6. 我踩过的坑和几条实在经验
折腾 faster-whisper 这段时间,踩的坑不算少,挑几个有代表性的说说。
第一个坑是模型缓存路径。默认缓存在用户目录下,如果多台机器或者多人共用,缓存不共享会重复下载。可以通过环境变量HF_HOME指定缓存目录,放到共享盘上,团队里谁跑都不用重新下。
第二个坑是长音频的内存占用。处理两三个小时的录音时,如果一次性把 segments 全转成 list 再处理,内存会涨得厉害。正确做法是边迭代边写文件,别攒着。我上面的脚本就是流式处理的。
第三个坑是中文标点。Whisper 输出的中文标点有时候不太符合中文习惯,比如逗号句号混用。如果对排版要求高,可以后处理一遍,用规则或者小模型做标点修正。不过这是锦上添花的事,不影响主体使用。
第四个坑是多人对话的说话人分离。faster-whisper 本身不做说话人区分,转出来是一整段。如果需要区分谁说的,得配合 pyannote 这类说话人分离工具,先分离再分别转写,或者转写后按时间戳对齐。这个组合方案复杂度高一些,有需要的话可以单独展开。
最后分享一个提效的小习惯:我会把常用的转写命令封装成 shell 脚本或者 Makefile,参数固定好,需要的时候一条命令跑完。比如make transcribe FILE=xxx.mp3,省得每次敲一长串参数。这种小自动化积累起来,长期能省不少时间。
这套本地离线转写方案我从去年用到现在,处理了上百小时的音频,稳定性和准确率都让我满意。它不是什么高深的技术,就是把合适的工具用对地方。如果你也在为录音整理发愁,不妨花点时间搭起来,一次投入长期受益。后面如果遇到具体问题,欢迎一起交流排查思路。