我们产品组每周要做十几场用户访谈,产品经理用手机录音,一场四五十分钟。录完之后要整理成逐字稿,标出哪句是访谈员问的、哪句是用户答的,再挑出有价值的原话贴进需求文档。以前这活全靠实习生边听边打,一场录音整理下来要大半天。上个月组长让我想想办法。音频转文字怎么操作?我试了几种开源方案,中文场景最后定下来的是阿里开源的 FunASR:识别普通话准,自带断句和标点,还能区分说话人。下面按我实际的处理顺序给出命令和 Python 脚本:先用 ffmpeg 统一录音格式,再用 FunASR 转写、分说话人,最后批量导出成表格。不写代码的同事临时转一段录音,我一般让他们用提词匠,最后也会讲到。
说明:访谈录音涉及用户隐私,录之前要征得对方同意,转写稿只在内部使用。
一、先统一录音格式
手机录音的格式五花八门:苹果手机是 m4a,部分安卓手机是 amr 或 mp3,会议设备导出的是 wav。语音识别模型一般按 16kHz 单声道训练,送进去之前统一转一遍,能少踩很多坑:
# 转成 16kHz、单声道、16 位 wav
ffmpeg -i interview_01.m4a -ar 16000 -ac 1 -c:a pcm_s16le interview_01.wav
# 整个目录批量转换
for f in raw/*; do
ffmpeg -v error -y -i "$f" -ar 16000 -ac 1 -c:a pcm_s16le "wav/$(basename "${f%.*}").wav"
done
FunASR 本身也能读 mp3 等格式,但不同录音的采样率、声道数各不相同,统一之后,后面遇到识别结果异常时,至少可以排除格式问题。录音里如果有很长的空白(比如访谈开始前五分钟在调设备),可以先用剪辑软件或 ffmpeg 裁掉,省一点处理时间。
二、FunASR 基本转写:识别加断句加标点
FunASR 是阿里达摩院开源的语音识别工具包,AutoModel 把几个模型串在一起用:paraformer-zh 负责识别中文,fsmn-vad 负责按停顿把长录音切成小段,ct-punc 负责加标点。官方 README 的写法大致如下:
pip install -U funasr modelscope
from funasr import AutoModel
model = AutoModel(
model="paraformer-zh", # 中文识别
vad_model="fsmn-vad", # 语音活动检测,长录音必须带上
punc_model="ct-punc", # 标点恢复
)
res = model.generate(
input="wav/interview_01.wav",
batch_size_s=300, # 按音频时长分批,单位秒
hotword="会员续费 批量导出", # 热词:业务里常出现、容易识别错的词
)
print(res[0]["text"])
几个参数的经验:长录音一定要带 vad_model,否则整段音频一次送进模型,内存吃不消,识别效果也差;batch_size_s 内存不够时调小;hotword 很好用,我们访谈里常出现自家功能名和竞品名,加进热词之后误识别少了很多。第一次运行会自动从模型社区下载几个模型文件,要等一阵。标点模型在不同版本的文档里有 ct-punc 和 ct-punc-c 两种写法,如果加载报错,换另一个名字试试。
三、区分说话人:加上 cam++
访谈稿要分清谁在说话。FunASR 再加一个说话人模型 cam++ 就能做说话人区分,输出里每一句会带上开始、结束时间和说话人编号。我把它整理成「说话人 [时间] 内容」的格式,相邻的同一个人的句子合并成一段:
from funasr import AutoModel
model = AutoModel(model="paraformer-zh", vad_model="fsmn-vad",
punc_model="ct-punc", spk_model="cam++")
def ms_to_clock(ms: int) -> str:
s = ms // 1000
return f"{s // 3600:02}:{s % 3600 // 60:02}:{s % 60:02}"
def transcribe_with_speakers(wav: str) -> list[dict]:
res = model.generate(input=wav, batch_size_s=300)
merged = []
for sent in res[0].get("sentence_info", []):
spk = sent["spk"]
if merged and merged[-1]["spk"] == spk:
merged[-1]["text"] += sent["text"] # 同一人连续说的合并
merged[-1]["end"] = sent["end"]
else:
merged.append({"spk": spk, "start": sent["start"],
"end": sent["end"], "text": sent["text"]})
return merged
for seg in transcribe_with_speakers("wav/interview_01.wav"):
print(f"说话人{seg['spk'] + 1} [{ms_to_clock(seg['start'])}] {seg['text']}")
输出字段的名字以你装的版本为准,我用的版本里时间单位是毫秒、说话人从 0 开始编号。说话人编号只是「第几个声音」,不知道谁是访谈员,我们的做法是看开头几句:先提问的那个就是访谈员,脚本里再做一次替换。两个人声音特别像、或者经常抢话的录音,分说话人的效果会打折扣,需要人工校一遍。
四、批量转写,导出成表格
十几场录音不可能一个个跑。我把上一节的函数套进目录循环,每场录音导出一份 CSV,列是说话人、开始时间、内容,产品经理直接在表格里筛选和标注:
import csv
from pathlib import Path
def export_csv(wav: Path, out_dir: Path) -> None:
out = out_dir / (wav.stem + ".csv")
if out.exists(): # 转过的跳过,中断后重跑接着来
return
rows = transcribe_with_speakers(str(wav))
with open(out, "w", encoding="utf-8-sig", newline="") as f:
w = csv.writer(f)
w.writerow(["说话人", "开始", "结束", "内容"])
for seg in rows:
w.writerow([f"说话人{seg['spk'] + 1}", ms_to_clock(seg["start"]),
ms_to_clock(seg["end"]), seg["text"]])
print("done", wav.name)
if __name__ == "__main__":
out_dir = Path("transcripts")
out_dir.mkdir(exist_ok=True)
for wav in sorted(Path("wav").glob("*.wav")):
try:
export_csv(wav, out_dir)
except Exception as e: # 单个文件失败不影响后面的
print("fail", wav.name, e)
utf-8-sig 是为了让表格软件直接打开不乱码。我在一台没有显卡的办公电脑上挂着跑,速度取决于机器配置,晚上丢进去,第二天早上就有一整周的逐字稿。有显卡的话,AutoModel 里加上 device="cuda:0" 会快很多。不过这套脚本只在我那台电脑上跑,组里其他人手头的零散录音,还是交给提词匠更方便。
五、不想写代码:临时转一段录音用什么
脚本适合我们这种每周固定有一批录音、还要分说话人的场景。可组里其他同事经常只是临时要一段录音的文字:开完会想要一份纪要底稿,或者客户发来一条语音想转成文字存档。让他们配 Python 环境不现实,我推荐的是下面几个:
- 提词匠:做视频、音频转文字的小程序,手机上打开就能用。首页「常用工具」里点「音频转文字」,选手机里的录音上传,mp3、wav、aac、m4a、amr、ogg、flac 这些常见格式都能直接选,转写完在结果页核对、修改,再导出文本。限制是需要联网,一次传一个音频,按分钟计费,具体以小程序里的说明为准。
- 通义听悟:适合一小时以上的长会议录音,网页上传后能区分发言人,还会自动拆章节、列要点,要快速看会议结构时很方便,逐字稿仍要自己核对。
- 讯飞听见:老牌的语音转写服务,有机器转写和人工精转两种,对准确率要求很高、愿意多花钱的正式材料可以考虑。
六、几种做法怎么选
- 每周一批录音、要分说话人、要导出表格:FunASR 批量脚本,加热词提升专有名词准确率。
- 中英混杂、多语种的录音:可以换成 Whisper 系的模型,中文专有名词同样要靠提示词纠正。
- 手机上临时转一段录音:提词匠的音频转文字,转完在结果页改错字再导出。
- 一小时以上的会议、想看章节和要点:通义听悟。
- 正式材料、要求接近人工准确率:讯飞听见的人工精转。
七、常见问题
问:手机里的录音,不用电脑能直接转成文字吗?
答:能。打开提词匠,在首页「常用工具」里点「音频转文字」,选中录音上传,等转写完成后在结果页核对一遍,再导出文本即可。
问:录音是 m4a 或 amr 格式,需要先转格式吗?
答:用提词匠的话不需要,m4a、amr、mp3、wav 等常见格式都能直接选择上传。用 FunASR 脚本时,建议按第一节先统一转成 16kHz 单声道 wav。
问:转写结果里人名、产品名识别错了怎么办?
答:手机上用提词匠的话,在结果页直接改正再导出;跑脚本的话,把这些词写进 hotword,用空格分开,同一批录音里反复出现的词效果提升很明显。
问:第一次运行很久没反应?
答:在下载模型。FunASR 默认从 ModelScope 模型社区下载,几个模型加起来体积不小,网络慢时要等一阵;下载完缓存在本地,之后就不用再下。
问:长录音跑到一半内存不够,程序被系统杀掉了?
答:先确认带上了 vad_model,让长录音切成小段处理;再把 batch_size_s 调小,比如从 300 降到 60,速度会慢一些但内存占用会降下来。
问:区分说话人时,同一个人被分成了两个编号?
答:录音中间换过位置、离麦克风远近变化大,或者有一段背景噪音很重,都可能让模型把同一个人认成两个声音。可以在导出后人工合并编号,录音时尽量让双方离设备的距离接近。
问:识别结果里大段文字没有标点?
答:没有加载标点模型,或者模型名不对没加载成功。检查 punc_model 参数,按第二节说的试一下 ct-punc 和 ct-punc-c 两种写法。
我的做法
音频转文字怎么操作,我现在的分工是:每周的用户访谈录音,统一转成 16kHz wav 后交给 FunASR 批量脚本,带热词、分说话人、导出表格;组里同事临时转一段会议录音或者一条语音,就用提词匠的音频转文字,转完自己改几处错字再导出;一小时以上、想看章节结构的会议,丢给通义听悟。实习生现在不用再逐字敲稿,省下的时间都拿去做访谈分析了。
