背景
前段时间接到一个任务,需要将老板的会议录音转为文本,方便助理整理会议纪要。由于是内部会议,对内容保密性要求较高,因此不能使用第三方平台的商业化语音识别API,只能通过本地或公司内网部署的模型来实现。
经过调研,公司内部已经部署了ASR(语音识别)模型,并提供了现成的API。但该API仅支持60秒以内的短音频,而我们的会议录音通常长达2小时左右。如果采用分段切割音频、循环调用API、最后合并结果的方案,虽然可行,但实现起来较为复杂且不够高效。
于是,我开始寻找本地化的语音识别方案。在咨询纳米AI上的DeepSeek后,他们推荐了Whisper.cpp,这是一个能在本地高效运行的语音转文本方案,完美符合我们的需求。
本地部署Whisper
1. 简介
Whisper
是 OpenAI 推出的高性能语音识别模型,支持多语言转写。whisper.cpp 是其 C++ 实现版本,优化了推理效率,尤其适合本地部署和低资源环境。
它具有以下优势:
- 高性能:基于 C/C++ 的高效实现,显著提升语音转文本处理速度。跨平台支持:兼容 macOS、Linux、Windows 等多种操作系统。离线运行:无需依赖第三方平台,适合对数据隐私有要求的场景。低资源占用:适用于资源受限的设备,如嵌入式系统。
2.克隆Whisper项目
从 GitHub 克隆 whisper.cpp 仓库。终端运行以下命令:
git clone https://github.com/ggerganov/whisper.cpp.git
3.**安装必要依赖 **
安装CMake : whisper.cpp 项目使用 CMake 进行构建(编译)。你需要先安装一些必要的工具,比如 CMake 和 Make,它们可以通过 Homebrew 安装:
brew install cmake make
安装ffmpeg:进行语音识别,免不了要对多媒体文件进行处理,安装ffmpeg
brew install ffmpeg
4.构建&编译whisper.cpp
在 whisper.cpp 目录中,运行以下命令来构建项目:
cmake -B buildcmake --build build --config Release
完成后会在/build/bin/
目录下面 生成一个whisper-cli
可执行文件
pic
5.下载Whisper模型
model
whisper.cpp 需要使用预训练的 Whisper 模型进行语音识别。你可以从 OpenAI 提供的链接下载模型文件:
下载后,将模型文件放到其子目录models中。当然你也可以通过Whisper项目自带的脚本来下载模型
cd whisper.cppbash ./models/download-ggml-model.sh medium
Whisper 提供多个模型版本,主要差异在于参数数量、所需内存、处理速度和识别准确率。以下是各模型的简要对比:
模型版本 | 参数量 | 所需内存 | 相对速度 | 特点 |
---|---|---|---|---|
tiny | 39M | ~1GB | ~10x | 速度快,准确率较低 |
base | 74M | ~1GB | ~7x | 平衡速度与准确率 |
small | 244M | ~2GB | ~4x | 准确率提升,速度适中 |
medium | 769M | ~5GB | ~2x | 高准确率,速度较慢 |
large | 1550M | ~10GB | 1x | 最高准确率,速度最慢 |
turbo | 809M | ~6GB | ~8x | 与 large 相比,速度提升约 5.4 倍,准确率略有下降 |
此外,.en 版本的模型专为英语优化,适用于仅处理英语的场景。以-q5 或是 -q8结尾的模型是进行了量化的版本。(量化是通过降低模型参数的位数来减小模型体积和加快处理速度的技术)
具体选择什么模块可以根据自己的电脑配置来选择,我这里选择的是medium版本的模型。
实现语音识别
格式转换
接下来利用可执行文件whisper-cli
进行语音识别,whisper-cli
目前仅适用于 16 位 WAV 文件。如果不知道音频信息,可以通过ffmpeg来进行音频资源信息的查看:
ffmpeg -i ***.wav
可以看到类似下面的输出信息
Input #0, wav, from '会议记录.wav': Duration: 00:00:05.00, bitrate: 705 kb/s Stream #0:0: Audio: pcm_s16le, 44100 Hz, mono, s16, 705 kb/s
简单解释一下:
• 编码格式:pcm_s16le 表示音频使用的是 16 位有符号小端 PCM 编码。
• 采样率:44100 Hz 表示音频的采样率为 44.1 kHz。
• 声道数:mono 表示单声道音频。
• 采样格式:s16 表示每个样本使用 16 位。
如果您看到的编码格式是 pcm_s16le
,那么该文件确实是 16 位的 WAV 文件。如果文件格式不符合类型要求,就需要使用ffmpeg 进行格式转换,方式如下:
ffmpeg -i input.mp3 -ar 16000 -ac 1 -c:a pcm_s16le output.wav
当然如果是视频格式的文件,例如mov类型,使用下面命令提取音频文件:
ffmpeg -i input.mov -vn -acodec pcm_s16le -ar 16000 -ac 1 output.wav
语音识别
使用whisper-cli
进行语音识别:
./whisper-cli -f <audio-file> -m <model-path> -osrt -l zh -t 4
• **-f **:要转换的音频文件路径
• -m:模型文件路径
• -osrt -otxt -ovtt:需要输出字幕文件(如 SRT、TXT 或 VTT 格式)
• -l : 指定识别的语言(默认为自动检测)
• -t:指定使用的线程数(根据你的 CPU 核心数量调整)
完成语音识别。
存在问题
1. 识别后输出的是繁体中文的内容
截屏2025-05-23 14.41.20
这个问题的解决方案,可以通过在命令行后面加prompt参数搞定:
./whisper-cli -m models/ggml-whisper-large-zh-cv11-Q5_0.bin -f output.wav -l zh -t 4 --output-txt --prompt "以下是普通话的句子。"
2. 提升中文内容的识别准确度,使用专门的中文模型
可以通过链接huggingface.co/second-stat…
截屏2025-05-23 14.44.45
3. 长音频文件识别错误
有时候,处理较长的音频文件(> 20 分钟)时,可能会遇到识别错误,导致输出重复的句子。特别是当音频的声音不清晰,存在较长空白、杂音等内容时,就容易出现文本重复出现的问题。例如:
你好,今天天气不错我们一起去海边旅游吧我们一起去海边旅游吧我们一起去海边旅游吧...
解决方案一:
添加参数:–condition_on_previous_text False:
--condition_on_previous_text 控制是否在处理一个新音频段(chunk)时,将前一段的识别结果作为上下文传递给当前段落。
True(默认) :使用前一段的文字作为上下文,有助于维持上下文一致性,尤其是处理完整对话、语境相关内容时,会更自然、连贯。
False:每个段落独立识别,不会依赖之前的识别内容。这有助于避免:
- 重复句子问题上下文“污染”造成的识别错误更适合批量短音频或较长音频中的独立片段识别
解决方案二:
利用ffmpeg将音频资源进行分割,分割成小于20分钟的小片段,针对每个小片段进行语音识别,最后将识别的内容进行拼接。
假设你知道音频总长是 120 秒,你可以执行下面的命令将音频分割为两段60S的分段:
ffmpeg -i input.wav -ss 0 -t 60 output_part1.wavffmpeg -i input.wav -ss 60 -t 60 output_part2.wav
解释:
- -ss:起始时间(单位:秒)-t:持续时长(单位:秒)
脚本自动化实现
针对上面长音频文件识别错误的问题,我利用ffmpeg对音频资源进行了识别前的预处理。主要包括下面几个方面:
音频格式转换
转为 Whisper 可识别的 16kHz 单声道 16位 WAV 格式
ffmpeg -y -i "$INPUT_AUDIO" -ar 16000 -ac 1 -sample_fmt s16 whisper_input.wav
提升音频资源的音量
如果音频资源本身质量不佳,音量过小,可以利用ffmpeg提升音频的音量:
ffmpeg -i output_denoised.wav -af "volume=3dB" output_enhanced.wav
针对音频资源进行降噪
如果音频质量不佳,可以利用 sox
、 noiseprof
对音频资源进行降噪处理:
Step 1: 提取背景噪声样本(纯背景噪音段,5秒等)
ffmpeg -i input.wav -ss 0 -t 5 noise_sample.wav
Step 2: 生成噪声配置文件(需安装 sox, 可直接通过homebrew安装brew install sox
)
sox noise_sample.wav -n noiseprof noise.prof
Step 3: 用配置文件进行降噪
sox input.wav output_denoised.wav noisered noise.prof 0.21
🔹 参数 0.21 是降噪强度,越高越激进(建议 0.2~0.5 之间试试)
音频资源切割
按照用户的设置,将音频资源分割成固定的片段
ffmpeg -y -i ***.wav -f segment -segment_time "$SPLIT_DURATION_SECONDS" -c copy part%03d.wav
将分割的片段按照顺序进行语音识别
i=0for part in part*.wav; do PART_INDEX=$(printf "%03d" "$i") echo "--> 转换 $part 为文本..." /Users/chenxiaoming/Desktop/MIng/whisper/whisper.cpp/build/bin/whisper-cli \ -f "$part" -m "$WHISPER_MODEL" -l zh -t 4 --output-txt \ --prompt "以下是普通话的句子,使用简体中文输出。" mv "${part%.wav}.txt" "part_${PART_INDEX}.txt" ((i++))done
最后,合并所有识别文本为总文件
FINAL_TXT="$WORKDIR/${BASENAME}_whisper.txt"cat part_*.txt > "$FINAL_TXT"
最后的脚本文件:
#!/bin/bashSCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"WHISPER_CLI="$SCRIPT_DIR/whisper-cli"# 参数检查if [ $# -lt 2 ]; then echo "用法: $0 <音频文件路径> <whisper模型路径> [--volume <dB>] [--no-denoise] [--split-duration <分钟>]" exit 1fiINPUT_AUDIO="$1"WHISPER_MODEL="$2"shift 2# 默认参数值VOLUME_GAIN=3DENOISE_ENABLED=trueSPLIT_DURATION_MINUTES=20# 解析可选参数while [[ $# -gt 0 ]]; do case "$1" in --volume) VOLUME_GAIN="$2" shift 2 ;; --no-denoise) DENOISE_ENABLED=false shift ;; --split-duration) SPLIT_DURATION_MINUTES="$2" shift 2 ;; *) echo "❌ 未知参数: $1" exit 1 ;; esacdone# 设置变量WORKDIR=$(dirname "$INPUT_AUDIO")BASENAME=$(basename "$INPUT_AUDIO" | sed 's/.[^.]*$//')TMP_DIR="$WORKDIR/whisper_temp"mkdir -p "$TMP_DIR"cd "$TMP_DIR" || exit 1echo "==> 步骤1:转为 Whisper 可识别的 16kHz 单声道 16位 WAV 格式"ffmpeg -y -i "$INPUT_AUDIO" -ar 16000 -ac 1 -sample_fmt s16 whisper_input.wavecho "==> 步骤2:增强音量(+${VOLUME_GAIN}dB)"ffmpeg -y -i whisper_input.wav -af "volume=${VOLUME_GAIN}dB" output_enhanced.wavif [ "$DENOISE_ENABLED" = true ]; then echo "==> 步骤3:提取前5秒背景噪声样本" ffmpeg -y -i output_enhanced.wav -ss 0 -t 5 noise_sample.wav echo "==> 步骤4:生成噪声配置文件" sox noise_sample.wav -n noiseprof noise.prof echo "==> 步骤5:使用配置文件降噪" sox output_enhanced.wav output_denoised.wav noisered noise.prof 0.1else echo "==> 跳过降噪处理,直接使用增强后的音频" cp output_enhanced.wav output_denoised.wavfiSPLIT_DURATION_SECONDS=$((SPLIT_DURATION_MINUTES * 60))echo "==> 步骤6:将音频按每 ${SPLIT_DURATION_MINUTES} 分钟分割"ffmpeg -y -i output_denoised.wav -f segment -segment_time "$SPLIT_DURATION_SECONDS" -c copy part%03d.wavecho "==> 步骤7:对分割片段逐个识别"i=0for part in part*.wav; do PART_INDEX=$(printf "%03d" "$i") echo "--> 转换 $part 为文本..." "$WHISPER_CLI" -f "$part" -m "$WHISPER_MODEL" -l zh -t 4 --output-txt \ --prompt "以下是普通话的句子,使用简体中文输出。" ORIGINAL_TXT="${part}.txt" TARGET_TXT="part_${PART_INDEX}.txt" if [ -f "$ORIGINAL_TXT" ]; then mv "$ORIGINAL_TXT" "$TARGET_TXT" else echo "⚠️ 警告:没有找到识别结果 $ORIGINAL_TXT" fi ((i++))doneecho "==> 步骤8:合并所有识别文本为总文件"FINAL_TXT="$WORKDIR/${BASENAME}_whisper.txt"cat part_*.txt > "$FINAL_TXT"echo "✅ 所有步骤完成,最终输出: $FINAL_TXT"
使用脚本:
参数名 | 说明 | 默认值 |
---|---|---|
--volume | 音量增强的分贝数 | 3 |
--no-denoise | 是否关闭降噪处理 | 开启(不传表示开启) |
--split-duration <分钟> | 每个音频分段的时长,单位为分钟 | 20 |
./voice_whisper.sh 20250419_***.wav ggml-medium-q8_0.bin --volume 5 --no-denoise --split-duration 20
#### 总结以上就是使用Whisper进行语音识别的完整流程,当然除了使用`whisper-cli`进行本地语音识别之外,你可以利用`whisper-server`将其部署为对外的服务。会更多内容可以参考Whisper的项目介绍:https://github.com/openai/whisper