从零部署 FunASR 语音识别服务

从零部署 FunASR 语音识别服务

本文是一篇通用教程,教你在一台无 GPU 的低配云服务器 上,从零部署一套可对外提供 HTTP 接口的中文语音识别(ASR)服务

技术栈:FunASR + SenseVoiceSmall 模型 + Python 服务端 + systemd 守护进程

复制本文全部内容给Agent后可自动部署。


目录

  1. 这是什么、能做什么
  2. 前置准备
  3. 服务器环境准备
  4. [安装 FunASR 与依赖](#安装 FunASR 与依赖)
  5. 编写识别服务端代码
  6. [配置 API 鉴权(多密钥)](#配置 API 鉴权(多密钥))
  7. [配置 systemd 守护进程](#配置 systemd 守护进程)
  8. 放行防火墙/安全组端口
  9. 测试与验证
  10. [常见问题 FAQ](#常见问题 FAQ)

1. 这是什么、能做什么

FunASR 是阿里巴巴达摩院开源的工业级语音识别工具包,支持:

  • 离线/流式语音识别(ASR)
  • 语音活动检测(VAD)
  • 标点恢复、说话人分离等

本教程选用 SenseVoiceSmall 模型:它专为 CPU 也能流畅跑 设计,中文识别准确率高,天然带标点,非常适合在没有显卡的低配服务器上部署。

部署完成后,你会得到一个类似这样的 HTTP 接口,供你的应用(桌面宠物、App、网页等)通过网络调用:

复制代码
POST http://<你的服务器IP>:<端口>/api/asr
Header: Authorization: Bearer <你的密钥>
Body: { "audio": "<音频的base64或url>" }

→ 返回识别出的文字

2. 前置准备

项目 说明
一台云服务器 Ubuntu 20.04/22.04 均可,无需 GPU
最低配置建议 2 核 + 4G 内存(本文示例为 4 核 3.6G)
磁盘 至少预留 5G(Python 依赖 + 模型约 1G+)
网络 公网 IP 或内网可达
一个 SSH 客户端 用于登录服务器

提示:SenseVoiceSmall 依赖 torch CPU,安装体积较大,请确保磁盘与带宽充足。


3. 服务器环境准备

登录服务器后,先更新系统并安装 Python 与相关工具:

bash 复制代码
sudo apt update && sudo apt upgrade -y

# 安装 Python3、pip、venv 等
sudo apt install -y python3 python3-pip python3-venv git curl

# 确认版本(建议 Python >= 3.8)
python3 --version

创建独立工作目录,并建立 Python 虚拟环境(强烈建议用 venv 隔离依赖,避免污染系统环境):

bash 复制代码
mkdir -p ~/funasr-asr && cd ~/funasr-asr
python3 -m venv venv
source venv/bin/activate

# 升级 pip
pip install --upgrade pip

4. 安装 FunASR 与依赖

在虚拟环境里安装(CPU 版 torch):

bash 复制代码
# CPU 版 PyTorch(体积大,耐心等待)
pip install torch torchaudio --index-url https://download.pytorch.org/whl/cpu

# 安装 FunASR 本体
pip install funasr

# 服务端 Web 框架(用于提供 HTTP 接口)
pip install flask

说明:

  • 如果服务器有 GPU,可改为安装对应 CUDA 版本的 torch,并在代码里把 device 改成 "cuda"
  • torch 安装较慢/较大,若网络慢可考虑换 pip 镜像源(如清华源)。

验证一下模块能正常 import:

bash 复制代码
python -c "import funasr; print('funasr ok')"
python -c "import torch; print(torch.__version__)"

5. 编写识别服务端代码

创建服务端主文件 server.py(路径首行注释已标注):

python 复制代码
# ~/funasr-asr/server.py
import os
import base64
import tempfile
from flask import Flask, request, jsonify
from funasr import AutoModel
from funasr.utils.postprocess_utils import rich_transcription_postprocess

app = Flask(__name__)

# 模型只加载一次,常驻内存,避免每次请求重复加载
print("[init] loading SenseVoiceSmall model ...")
model = AutoModel(
    model="iic/SenseVoiceSmall",
    vad_model="fsmn-vad",
    device="cpu",          # 有 GPU 改成 "cuda"
    disable_update=True,   # 禁止自动更新模型,避免启动时联网
)
print("[init] model loaded.")

def check_auth():
    """校验 API 密钥(支持逗号分隔的多密钥)"""
    keys = os.environ.get("ASR_API_KEYS", "") or os.environ.get("ASR_API_KEY", "")
    valid = [k.strip() for k in keys.split(",") if k.strip()]
    # 若未配置任何密钥,则允许本地调试(生产环境务必配置)
    if not valid:
        return True
    auth = request.headers.get("Authorization", "")
    token = auth.replace("Bearer ", "").strip()
    return token in valid

@app.route("/health", methods=["GET"])
def health():
    return jsonify({"status": "ok"})

@app.route("/api/asr", methods=["POST"])
def asr():
    if not check_auth():
        return jsonify({"error": "unauthorized"}), 401

    data = request.get_json(silent=True) or {}
    audio_b64 = data.get("audio")
    audio_url = data.get("url")

    if not audio_b64 and not audio_url:
        return jsonify({"error": "missing audio or url"}), 400

    # 将输入统一转成临时音频文件
    tmp = tempfile.NamedTemporaryFile(suffix=".wav", delete=False)
    tmp_path = tmp.name
    tmp.close()
    try:
        if audio_url:
            import urllib.request
            urllib.request.urlretrieve(audio_url, tmp_path)
        else:
            with open(tmp_path, "wb") as f:
                f.write(base64.b64decode(audio_b64))

        result = model.generate(input=tmp_path)
        text = ""
        if result and result[0].get("text"):
            text = result[0]["text"]
        return jsonify({"text": text})
    except Exception as e:
        return jsonify({"error": str(e)}), 500
    finally:
        if os.path.exists(tmp_path):
            os.remove(tmp_path)

if __name__ == "__main__":
    port = int(os.environ.get("PORT", "10095"))
    app.run(host="0.0.0.0", port=port, threaded=True)

关键点:

  • model = AutoModel(...) 放在模块顶层只加载一次,避免每次请求都重新加载(那可是秒级甚至几十秒的开销)。
  • vad_model="fsmn-vad" 会自动做语音活动检测,只切出有人声的片段。
  • 正式环境务必通过环境变量配置密钥,见下一节。

6. 配置 API 鉴权(多密钥)

为避免陌生人滥用你的识别服务,务必开启密钥鉴权。本方案支持多个密钥(逗号分隔)。

生成随机密钥(示例):

bash 复制代码
python -c "import secrets; print(secrets.token_urlsafe(32))"

记录生成的密钥,例如 'AbCdEf123...'(下面用占位符表示)。

方式一:临时运行(调试用)

bash 复制代码
export ASR_API_KEYS="<你的密钥1>,<你的密钥2>"
export PORT=10095
python server.py

方式二:写入 systemd(推荐,见下一节)

在 systemd 服务文件里通过 Environment= 注入密钥,重启不丢失。


7. 配置 systemd 守护进程

用 systemd 让服务开机自启、崩溃自动拉起

创建服务文件:

bash 复制代码
sudo tee /etc/systemd/system/funasr-asr.service > /dev/null <<'EOF'
[Unit]
Description=FunASR ASR Service
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=/root/funasr-asr
Environment="PORT=10095"
Environment="ASR_API_KEYS=<你的密钥1>,<你的密钥2>"
ExecStart=/root/funasr-asr/venv/bin/python /root/funasr-asr/server.py
Restart=always
RestartSec=5
# 低配机器可限制内存,避免 OOM(按需调整)
MemoryMax=2.5G

[Install]
WantedBy=multi-user.target
EOF

⚠️ 请把 <你的密钥1>,<你的密钥2> 替换成你上一步生成的真实密钥。

启用并启动:

bash 复制代码
sudo systemctl daemon-reload
sudo systemctl enable funasr-asr
sudo systemctl start funasr-asr

# 查看运行状态
sudo systemctl status funasr-asr

# 查看实时日志(首次启动会看到模型加载过程)
sudo journalctl -u funasr-asr -f

看到日志里出现模型加载完成、服务监听端口,即表示成功。


8. 放行防火墙/安全组端口

光启动服务还不够,还需要开放端口。

本机防火墙(若启用了 ufw)

bash 复制代码
sudo ufw allow 10095/tcp
sudo ufw status

云服务器安全组

在云服务商控制台(如腾讯云/阿里云)的安全组中,添加一条入站规则:

  • 协议:TCP
  • 端口:10095
  • 来源:0.0.0.0/0(或仅限你自己的 IP,更安全)

安全提示:建议来源限制为你自己的固定 IP,或至少配合密钥鉴权使用。


9. 测试与验证

本机测试

bash 复制代码
curl http://127.0.0.1:10095/health
# 期望返回 {"status":"ok"}

带密钥的识别测试

用一段音频(本地 wav 文件)base64 编码后调用:

bash 复制代码
# 将本地音频转 base64
B64=$(base64 -w 0 /path/to/your_audio.wav)

curl -X POST http://127.0.0.1:10095/api/asr \
  -H "Authorization: Bearer <你的密钥>" \
  -H "Content-Type: application/json" \
  -d "{\"audio\": \"$B64\"}"

期望返回类似:

json 复制代码
{"text": "欢迎大家来体验语音识别模型"}

客户端调用示例(Python)

python 复制代码
# 客户端示例 client_example.py
import base64
import requests

def asr(audio_path: str, server: str, key: str) -> str:
    with open(audio_path, "rb") as f:
        b64 = base64.b64encode(f.read()).decode()
    resp = requests.post(
        f"{server}/api/asr",
        headers={"Authorization": f"Bearer {key}"},
        json={"audio": b64},
        timeout=60,
    )
    if resp.status_code != 200:
        raise RuntimeError(f"ASR failed: {resp.status_code} {resp.text}")
    return resp.json()["text"]

if __name__ == "__main__":
    SERVER = "http://<你的服务器IP>:10095"
    KEY = "<你的密钥>"
    print(asr("/path/to/audio.wav", SERVER, KEY))

10. 常见问题 FAQ

Q1:启动很慢,日志卡在加载模型?

首次启动需要下载 iic/SenseVoiceSmall 模型(约几百 MB)。若网络慢,可设置 HuggingFace/ModelScope 镜像,或离线下载后指定本地路径。耐心等模型下载缓存完成即可,之后启动会快很多。

Q2:返回 401 Unauthorized?

检查客户端 Authorization 里的密钥,是否与服务器 ASR_API_KEYS 中配置的某一项完全一致(注意前后空格)。

Q3:内存不够被 OOM 杀掉?

低配机器建议:① 在 systemd 里设置 MemoryMax;② 关闭其他占用内存的服务;③ 只保留必要的进程。

Q4:CPU 识别慢怎么办?

  • 确认使用了 device="cpu" 且模型只加载一次;
  • 短音频通常 1~3 秒内返回,可接受;
  • 如需更快,换 GPU 服务器,或改用官方 funasr-server 并配合 GPU。

Q5:想支持英文/多语言?

SenseVoiceSmall 本身支持中英日韩粤等,model.generate 可加 language="auto" 自动检测。需要更多语种可参考 FunASR 官方文档选择其他模型。

Q6:如何更省心的一键部署?

FunASR 官方也提供 funasr-server(OpenAI 兼容接口)。本教程的裸机方案胜在体积小、可定制(自定义鉴权、管理页),适合需要深度控制的场景。


附:本教程的关键安全清单

  • 已通过 ASR_API_KEYS 配置至少一个强随机密钥
  • 未在代码/日志中硬编码真实密钥
  • 安全组端口来源已尽量收窄
  • 服务由 systemd 托管,崩溃自动拉起
  • 定期查看 journalctl -u funasr-asr 日志

本文基于 FunASR(modelscope/FunASR)官方文档整理。模型与工具版本迭代较快,若遇到版本差异,请以官方仓库最新 README 为准:https://github.com/modelscope/FunASR

相关推荐
Fxkj88821 分钟前
传统企业布局新媒体:自行摸索与IP陪跑模式深度对比
人工智能·网络协议·tcp/ip·媒体
Wang's Blog25 分钟前
Vibe Coding一人即团队系列31:基于Claude Code实现登录注册与数据库联动
数据库·人工智能
geneculture25 分钟前
从融智学到人机互助:技术成果交易的理论重构与战略前瞻——基于“九五至尊模型”与融智学三大判断的分析
大数据·人工智能·融智学的重要应用·哲学与科学统一性·融智时代(杂志)·序位逻辑的实例化·中国企业知识产权战略专栏
lvts_cs27 分钟前
如何判断化工产业规划的定位布局是否合理
大数据·人工智能
断眉的派大星28 分钟前
TensorRT 基础与核心流程完整学习笔记
人工智能·深度学习
运营小白30 分钟前
SEONIB AI 内容自动化流水线深度评测
人工智能·自动化·内容运营·跨境电商·社交媒体·ai skill
樱振宇gly32 分钟前
Vm机器视觉 26-8-28 学习笔记
人工智能
动物园猫33 分钟前
草莓成熟度目标检测数据集:3类别、2,000张图像 | 目标检测
人工智能·目标检测·计算机视觉
努力的小雨34 分钟前
聊天、自动化、Agent 怎么选?看任务,不看名字
人工智能