音频转文字怎么操作:FunASR 中文识别、区分说话人和批量转写的 Python 代码

我们产品组每周要做十几场用户访谈,产品经理用手机录音,一场四五十分钟。录完之后要整理成逐字稿,标出哪句是访谈员问的、哪句是用户答的,再挑出有价值的原话贴进需求文档。以前这活全靠实习生边听边打,一场录音整理下来要大半天。上个月组长让我想想办法。音频转文字怎么操作?我试了几种开源方案,中文场景最后定下来的是阿里开源的 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 批量脚本,带热词、分说话人、导出表格;组里同事临时转一段会议录音或者一条语音,就用提词匠的音频转文字,转完自己改几处错字再导出;一小时以上、想看章节结构的会议,丢给通义听悟。实习生现在不用再逐字敲稿,省下的时间都拿去做访谈分析了。

0
0
0
0
评论
未登录
暂无评论