跳转至

FunASR 部署 API 接口

概述

本节将 FunASR 语音识别能力封装为 REST API 服务,支持通过 HTTP 接口提交音频文件进行语音转文字。

特性 说明
框架 FastAPI + Uvicorn
单模型加载 服务启动时加载,后续请求复用
临时文件清理 自动清理上传的临时音频文件
健康检查 /health 接口
热词定制 支持传入热词提升识别率

安装依赖

Bash
source ~/funasr-env/bin/activate
pip install fastapi uvicorn python-multipart

API 服务代码

创建文件 /root/asr_server.py

Python
#!/usr/bin/env python3
"""
FunASR 语音转写 API 服务
用法: python asr_server.py
依赖: pip install fastapi uvicorn python-multipart
"""

import os
import time
import tempfile
from pathlib import Path
from contextlib import asynccontextmanager
from fastapi import FastAPI, File, UploadFile, Form, HTTPException
from fastapi.responses import JSONResponse
from funasr import AutoModel

# 支持的文件扩展名
ALLOWED_EXTENSIONS = {".wav", ".mp3", ".ogg", ".flac", ".m4a"}

# 全局模型实例
model_instance = None


def load_asr_model():
    """加载 FunASR 模型"""
    global model_instance
    if model_instance is None:
        print("\u23f3 正在加载 FunASR 模型 (首次运行约需 10-20s)...")
        model_instance = AutoModel(
            model="paraformer-zh",
            vad_model="fsmn-vad",
            punc_model="ct-punc",
        )
        print("\u2705 模型加载完成")
    return model_instance


@asynccontextmanager
async def lifespan(app: FastAPI):
    """服务启动时初始化模型"""
    load_asr_model()
    yield


app = FastAPI(
    title="FunASR Service",
    description="基于 FunASR 的语音转文字 API 服务",
    lifespan=lifespan,
)


@app.get("/health")
async def health_check():
    """健康检查接口"""
    return {
        "status": "healthy",
        "model_loaded": model_instance is not None,
        "uptime": time.time(),
    }


@app.post("/asr")
async def transcribe_audio(
    file: UploadFile = File(...),
    hotword: str = Form(None),
):
    """
    语音转文字接口
    - file: 音频文件(wav/mp3/ogg/flac/m4a)
    - hotword: 热词(可选),多个词用空格分隔
    """
    global model_instance
    model_instance = load_asr_model()

    # 1. 校验文件格式
    file_ext = Path(file.filename).suffix.lower() if file.filename else ""
    if file_ext not in ALLOWED_EXTENSIONS:
        raise HTTPException(
            status_code=400,
            detail=f"不支持的文件格式: {file_ext}。支持: {', '.join(ALLOWED_EXTENSIONS)}",
        )

    # 2. 保存临时文件
    temp_dir = Path(tempfile.gettempdir())
    temp_file_path = temp_dir / f"funasr_{int(time.time() * 1000)}_{file.filename}"

    try:
        with open(temp_file_path, "wb") as f:
            content = await file.read()
            f.write(content)

        print(f"\U0001f3a4 开始转写: {file.filename} ({len(content)/1024/1024:.1f}MB)")

        # 3. 执行转写
        start_time = time.time()
        result = model_instance.generate(
            input=str(temp_file_path),
            batch_size_s=300,
            hotword=hotword,
        )
        end_time = time.time()

        # 4. 解析结果
        if result and len(result) > 0 and "text" in result[0]:
            text = result[0]["text"]
            duration = result[0].get("time_speech", end_time - start_time)

            return JSONResponse(
                content={
                    "status": "success",
                    "filename": file.filename,
                    "text": text,
                    "duration_s": round(duration, 2),
                    "cost_time_s": round(end_time - start_time, 2),
                }
            )
        else:
            raise HTTPException(status_code=500, detail="转写结果为空,音频可能无法识别")

    except Exception as e:
        raise HTTPException(status_code=500, detail=f"转写失败: {str(e)}")

    finally:
        # 5. 清理临时文件
        if temp_file_path.exists():
            os.remove(temp_file_path)


if __name__ == "__main__":
    import uvicorn
    # host="0.0.0.0" 允许局域网访问
    # port=8000 端口号
    # workers=1 CPU环境建议单进程
    uvicorn.run(app, host="0.0.0.0", port=8000, workers=1)

启动服务

前台运行(测试用)

Bash
cd /root
python asr_server.py

后台运行(生产用)

Bash
1
2
3
4
nohup python asr_server.py > /var/log/asr.log 2>&1 &

# 查看日志
tail -f /var/log/asr.log

验证服务启动成功

Bash
curl http://localhost:8000/health

预期返回:

JSON
1
2
3
4
5
{
  "status": "healthy",
  "model_loaded": true,
  "uptime": 1746686000.123
}

API 接口说明

1. 健康检查

Bash
curl http://localhost:8000/health

2. 语音转写

请求方式: POST /asr

请求参数:

参数 类型 必填 说明
file form-data 音频文件
hotword form-data 热词(空格分隔)

支持格式: wav / mp3 / ogg / flac / m4a

请求示例:

Bash
1
2
3
4
5
# 基本调用
curl -X POST -F "file=@/root/test.m4a" http://localhost:8000/asr

# 带热词
curl -X POST -F "file=@/root/test.m4a" -F "hotword=公司名 项目名" http://localhost:8000/asr

响应格式:

JSON
1
2
3
4
5
6
7
{
  "status": "success",
  "filename": "test.m4a",
  "text": "这是一个测试。",
  "duration_s": 4.97,
  "cost_time_s": 0.73
}

客户端调用示例

Python 客户端脚本

创建文件 /root/asr_client.py

Python
import requests


def transcribe(file_path, hotword=None):
    """调用 FunASR API 进行语音转文字"""
    url = "http://localhost:8000/asr"

    with open(file_path, "rb") as f:
        files = {"file": (file_path.split("/")[-1], f)}
        data = {"hotword": hotword} if hotword else {}

        response = requests.post(url, files=files, data=data)

    if response.status_code == 200:
        data = response.json()
        print("\u2705 转写成功")
        print(f"   文件名:   {data['filename']}")
        print(f"   时长:     {data['duration_s']}秒")
        print(f"   耗时:     {data['cost_time_s']}秒")
        print(f"   内容:     {data['text']}")
        return data
    else:
        print(f"\u274c 失败: {response.json()['detail']}")


# 测试
if __name__ == "__main__":
    transcribe("/root/test.m4a", hotword="测试词")

注意事项

内存

项目 占用
FunASR 模型 4~6 GB
Python 运行时 ~500 MB
系统开销 ~2 GB
剩余可用 ~5 GB

16 GB 内存够用,但避免同时运行其他大型服务(如 MySQL、K8s 组件)。

并发

参数 推荐值
workers 1(CPU 环境)
单次音频时长 ≤ 10 分钟
并发请求 低(4 核 CPU 推理效率有限)

运行环境

项目 配置
系统 Rocky Linux 9.7
CPU 4 核
内存 16 GB
硬盘 80 GB
Python 3.12.3
FunASR 1.3.1
FFmpeg 5.1.8

进程守护

建议使用 systemd 实现开机自启和进程守护:

INI
# /etc/systemd/system/funasr-api.service
[Unit]
Description=FunASR API Service
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=/root
Environment="PATH=/root/.pyenv/versions/3.12.3/bin:/root/funasr-env/bin:/usr/bin"
ExecStart=/root/funasr-env/bin/python /root/asr_server.py
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

启动:

Bash
1
2
3
systemctl daemon-reload
systemctl enable --now funasr-api
systemctl status funasr-api