之前我实现了在本地部署FunASR并调用,还是比较成功。当我想在OpenClaw中用FunASR识别收到的音频文件时,发现调用还是不顺,所以就记录下来。
2种调用方式
调用FunASR服务有2种方式,
- funasr_client_api.py
- funasr_wss_client.py
没有深入看代码,我直觉先用funasr_wss_client.py调用,解决基本问题后,就可以轻松识别。问题解决见我之前的文章:
识别模式mode
我先用funasr_wss_client.py跑了一下,感觉效果还可以。
就写了一段代码,实现在Android手机上进行语音识别。
但用着就发现,还有mode的区别:
- online:只用online模式,精准度较低,实时返回给客户端。
- offline:用offline模式识别,精准度较高,且有标点符号,延迟相对高一点。在funasr_wss_client.py有BUG,直接会识别不了。
- 2pass:2阶段模式,先用online模式快速识别,并返回客户端。当VAD识别中断后,调用offline模式,进行精准识别。返回给客户端是先返回online,几个字一返回,速度快,实时。然后会返回一段offline的识别结果。
这就导致程序必须要做特殊处理:
-
实时解析online的结果,并显示,但不作为最终的结果
-
如果收到offline的结果,就将最近收到的online结果替换成offline的结果,可以作为最终结果
-
这样循环往复处理
-
最后,如果没有收到offline就结束了,最后一部分的online结果要追加到最终识别结果中。
这对实时语音识别还好。实时识别结果
[2026-03-14 20:42:35.756] online, {'key': 'rand_key_dckfdL0z5uIAy', 'text': '嗯'}
[2026-03-14 20:42:36.568] online, {'key': 'rand_key_urlorDGzrgpk3', 'text': '是一'}
[2026-03-14 20:42:37.385] online, {'key': 'rand_key_dpM7tpTqiDexO', 'text': '测试的音'}
[2026-03-14 20:42:38.195] online, {'key': 'rand_key_q6xD6tpBm9BYs', 'text': '频收'}
[2026-03-14 20:42:38.978] online, {'key': 'rand_key_FcINSJXFHGrvM', 'text': '到之后将'}
[2026-03-14 20:42:39.853] online, {'key': 'rand_key_tMMVyu8TmB0DH', 'text': '文字转'}
[2026-03-14 20:42:40.701] online, {'key': 'rand_key_1IUqHgBE2NwYt', 'text': ''}
[2026-03-14 20:42:41.485] online, {'key': 'rand_key_KEkjm94BRNriX', 'text': '将音频转'}
[2026-03-14 20:42:42.295] online, {'key': 'rand_key_BOKlofaf5Vvc9', 'text': '成文字'}
[2026-03-14 20:42:43.203] online, {'key': 'rand_key_9l4CcBt08p9Hp', 'text': '并且发给'}
[2026-03-14 20:42:44.103] online, {'key': 'rand_key_Kd840v2DL8WJF', 'text': '我'}
[2026-03-14 20:42:45.260] offline_asr, raw: {'key': 'rand_key_Jc1g3yNq14ScG', 'text': '这 是 一 条 测 试 的 音 频 收 到 之 后 将 文 字 转 将 音 频 转 成 文 字 并 且 发 给 我', 'timestamp': [[250, 390], [390, 510], [510, 590], [590, 730], [730, 870], [870, 1050], [1050, 1170], [1170, 1330], [1330, 1570], [1730, 1870], [1870, 2010], [2010, 2110], [2110, 2310], [2310, 2530], [2530, 2690], [2690, 2830], [2830, 3070], [3590, 3830], [3850, 3950], [3950, 4150], [4150, 4250], [4250, 4390], [4390, 4530], [4530, 4770], [4790, 4890], [4890, 5050], [5050, 5190], [5190, 5430], [5430, 6055]]}
[2026-03-14 20:42:45.260] offline_asr, keys: dict_keys(['key', 'text', 'timestamp'])
[2026-03-14 20:42:46.237] offline, after punc {'key': 'rand_key_amHImDZF510iK', 'text': '这是一条测试的音频,收到之后将文字转将音频转成文字并且发给我', 'punc_array': tensor([1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 3], device='cuda:0')}offline的结果,看起来更准确,且有标点符号
[2026-03-14 20:42:46.238] ======offline final text: 这是一条测试的音频,收到之后将文字转将音频转成文字并且发给我
FunASR识别一个文件
当我想用FunASR识别一个文件的时候,就遇到无法完整识别的问题。
按理应该是用offline模式,但是会失败。所以还是用2pass模式。
- 按照流程,会先用online识别,然后静音后用offline识别
- 最后一段没有用offline,只有online结果,导致识别准确率不高。
就如上面的日志显示那样。
funasr_client_api调用方式
最开始没有注意到这个,当我注意它的时候,以为它能用来识别一个文件。但看了代码还是和funasr_wss_client类似。
想要正确调用,需要修改funasr_client_api.py,将文件路径直接填进源码,其他信息如服务器、端口都要进行调整。
python ./funasr_client_api.py
提示缺少:websocket。安装
pip install websocket
# 如果不安会有新的报错
pip install websocket-client
提示新的错误,需要安装websocket-client:
ImportError: cannot import name 'ABNF' from 'websocket' (/home/band/anaconda3/envs/faenv/lib/python3.12/site-packages/websocket/__init__.py)
这是直接调用会报新的错误:
connect to url wss://127.0.0.1:10095
Exception: Handshake status 400 Bad Request -+-+- {'date': 'Sat, 14 Mar 2026 13:37:22 GMT', 'connection': 'close', 'content-length': '60', 'content-type': 'text/plain; charset=utf-8', 'server': 'Python/3.12 websockets/16.0'} -+-+- b'Failed to open a WebSocket connection: missing subprotocol.\n'
解决方案是改funasr_client_api.py
# 修改前
self.websocket = create_connection(uri, ssl=ssl_context, sslopt=ssl_opt)
# 修改后
self.websocket = create_connection(uri, ssl=ssl_context, sslopt=ssl_opt, subprotocols=["binary"])
单文件调用的正确方式
上述的websocket调用方式,对于单文件都有不好的地方。最终找到最佳的使用方式是使用代码调用:
#
from funasr.auto.auto_model import AutoModel
model_dir = "FunAudioLLM/Fun-ASR-Nano-2512"
model = AutoModel(
model=model_dir,
vad_model="fsmn-vad",
vad_kwargs={"max_single_segment_time": 30000},
device="cuda:0",
)
wav_path = f"filepath.wav"
res = model.generate(input=[wav_path], cache={}, batch_size_s=0)
text = res[0]["text"]
print(text)
(faenv) PS D:\workspace\FunASR\tests> python .\test_single_wav.py
funasr version: 1.3.1.
Check update of funasr, and it would cost few times. You may disable it by set `disable_update=True` in AutoModel
You are using the latest version of funasr-1.3.1
Downloading Model from https://www.modelscope.cn to directory: D:\ProgramData\modelscope\models\FunAudioLLM\Fun-ASR-Nano-2512
WARNING:root:trust_remote_code: False
Downloading Model from https://www.modelscope.cn to directory: D:\ProgramData\modelscope\models\iic\speech_fsmn_vad_zh-cn-16k-common-pytorch
WARNING:root:trust_remote_code: False
rtf_avg: 0.022: 100%| 略
Okay。 收到这条语音,这是一条测试音频,收到之后将这个音频 转成文字,并且发给我,把文字的内容发给我。
总结及最终选择
第一次使用FunASR,对于它的原理、用法还不熟,借用AI,只是提高效率,对于我不熟、AI也不熟的地方,实在没有好的方法,只能一步一个脚印、走一步踩一坑地走下去。
对于单文件语音识别,使用WebSocket服务器和本地直接调用的对比,在同一模型、同一台机器上:
| 维度 | WebSocket服务器 | 本地直接调用 |
|---|---|---|
| 正确率 | 大部分一样,最后一段差 | 优 |
| 加载速度 | 优,免加载 | 差,耗时 |
| 识别速度 | 同 | 同 |
| 性能消耗 | 差,不用也会消耗 | 优,按需调用 |
| 内存占用 | 差,持续占用内存 | 优,按需调用 |
由于我是要在OpenClaw中使用飞书调用FunASR识别我发的语音,两种方案都差不多。我就暂时先按照WebSocket服务器来用。