# zhipin-cloud-audio-server **Repository Path**: zhipin-cloud/zhipin-cloud-audio-server ## Basic Information - **Project Name**: zhipin-cloud-audio-server - **Description**: 智聘云图语音服务 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2026-09-17 - **Last Updated**: 2026-10-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # zhipin-cloud-audio-server(智聘云图语音服务) > 基于 Python + FastAPI 的语音服务,为智聘云图平台提供 **文字转语音(TTS)** 与 **语音转文字(ASR)** 能力。 **技术栈:** `Python 3.13` · `FastAPI` · `uvicorn` · `Piper(VITS / ONNX)` · `Vosk(Kaldi)` · `ffmpeg` · `uv` ### 核心特性 - **本地推理**:TTS 基于 Piper ONNX、ASR 基于 Vosk Kaldi,CPU 即可实时处理,无需外网、无 API Key - **多格式识别**:ASR 支持 WAV / mp3 / m4a / webm(opus) 等任意格式,内置静态 ffmpeg 自动转码,前端可零改造直传浏览器录音 - **HTTP 服务**:统一 REST 接口,TTS 支持 WAV 二进制与 base64,ASR 支持文件上传与 base64 - **纯本地服务**:不依赖注册中心 / 外部中间件,clone 后即可启动 - **可扩展**:`audio/` 下按引擎注册,`TTS_SERVICES` / `ASR_SERVICES` 分离,新增引擎互不影响 ### 快速开始 ```bash # 1. 安装依赖 uv sync # 2. 启动服务(模型已随仓库提供) uv run python main.py ``` 服务默认监听 `0.0.0.0:18610`,启动时自动加载 TTS / ASR 模型。 ### 模型准备 模型已随仓库提供,无需额外下载: ``` audio/tts/models/zh_CN-huayan-medium/ # Piper 中文女声(22050 Hz) ├── model.onnx # ~60 MB └── model.onnx.json audio/asr/models/vosk-model-small-cn/ # Vosk 中文识别模型 ├── am/ # final.mdl ├── conf/ # mfcc.conf(--sample-frequency=16000) / model.conf ├── graph/ # Gr.fst / HCLr.fst / disambig_tid.int / phones/ └── ivector/ # final.dubm / final.ie / final.mat / global_cmvn.stats / splice.conf ``` > 若 clone / pull 不完整(如启用了 Git LFS 未执行 `git lfs pull`)导致模型缺失,服务会因 `FileNotFoundError` 启动失败。 #### 更换模型 - **TTS**:替换 `audio/tts/models/zh_CN-huayan-medium/` 下的 `model.onnx` 与 `model.onnx.json`,或改 `application.yml` 的 `audio.tts.model-dir` / `audio.tts.model-file` 指向新目录 - **ASR**:替换 `audio/asr/models/vosk-model-small-cn/`(保持 `am`/`conf`/`graph`/`ivector` 结构),或改 `audio.asr.model-dir` - 容器部署需重新构建镜像:`docker compose up -d --build` ### API 接口 | 方法 | 路径 | 说明 | |------|------|------| | GET | `/hello` | 健康检查 | | POST | `/audio/tts` | 文字转语音,返回 WAV 二进制 | | POST | `/audio/tts/base64` | 文字转语音,返回 base64 JSON | | POST | `/audio/asr` | 语音转文字,`multipart/form-data` 上传音频文件 | | POST | `/audio/asr/base64` | 语音转文字,base64 JSON 上传音频 | ### TTS:文字转语音 #### 请求体(两个接口共用) | 字段 | 类型 | 必填 | 默认 | 说明 | |------|------|:---:|:---:|------| | `text` | string | 是 | - | 待合成文本,最大 1000 字 | | `engine` | string | 否 | `piper` | 合成引擎 | | `speed` | float | 否 | `1.0` | 语速倍率,`2.0` 为两倍速 | | `volume` | float | 否 | `1.0` | 音量倍率 | #### 1)`POST /audio/tts` → 返回 `audio/wav` 二进制 ```bash curl -X POST http://127.0.0.1:18610/audio/tts \ -H "Content-Type: application/json" \ -d '{"text":"你好,欢迎使用智聘云图语音服务。","engine":"piper","speed":1.0,"volume":1.0}' \ -o hello.wav ``` ```python import requests resp = requests.post( "http://127.0.0.1:18610/audio/tts", json={"text": "你好,欢迎使用智聘云图语音服务。", "engine": "piper", "speed": 1.0, "volume": 1.0}, ) resp.raise_for_status() with open("hello.wav", "wb") as f: f.write(resp.content) ``` #### 2)`POST /audio/tts/base64` → 返回 JSON ```bash curl -X POST http://127.0.0.1:18610/audio/tts/base64 \ -H "Content-Type: application/json" \ -d '{"text":"你好,欢迎使用智聘云图语音服务。","engine":"piper"}' ``` 响应: ```json { "format": "wav", "sampleRate": 22050, "audio": "" } ``` ```python import base64 import requests resp = requests.post( "http://127.0.0.1:18610/audio/tts/base64", json={"text": "你好,欢迎使用智聘云图语音服务。", "engine": "piper"}, ) data = resp.json() with open("hello.wav", "wb") as f: f.write(base64.b64decode(data["audio"])) ``` #### 3)`GET /hello` → 健康检查 ```bash curl http://127.0.0.1:18610/hello # "hello world!" ``` ### ASR:语音转文字 - **支持格式**:WAV(任意采样率/位深)/ mp3 / m4a / aac / flac / ogg / **webm(opus)** 等,内置静态 ffmpeg 自动转码为 16k 单声道,**浏览器 `MediaRecorder` 录音可直接上传** - **时长上限**:默认 300 秒(`audio.asr.max-audio-seconds`) - **词级时间戳**:默认关闭,传 `wordTimestamps=true` 开启,返回 `words[] = {word, start, end, conf}` #### 请求参数 | 字段 | 类型 | 必填 | 默认 | 说明 | |------|------|:---:|:---:|------| | `file` / `audio` | file / base64 | 是 | - | 音频文件(`/audio/asr` 为 form 字段 `file`,`/audio/asr/base64` 为 JSON 字段 `audio`) | | `engine` | string | 否 | `vosk` | 识别引擎 | | `wordTimestamps` | bool | 否 | `false` | 是否返回词级时间戳 | #### 1)`POST /audio/asr` → `multipart/form-data` 文件上传 ```bash curl -X POST http://127.0.0.1:18610/audio/asr \ -F "file=@hello.wav" \ -F "engine=vosk" \ -F "wordTimestamps=true" ``` ```python import requests with open("hello.wav", "rb") as f: resp = requests.post( "http://127.0.0.1:18610/audio/asr", files={"file": ("hello.wav", f, "audio/wav")}, data={"engine": "vosk", "wordTimestamps": "true"}, ) print(resp.json()["text"]) ``` #### 2)`POST /audio/asr/base64` → JSON 上传 ```bash curl -X POST http://127.0.0.1:18610/audio/asr/base64 \ -H "Content-Type: application/json" \ -d '{"audio":"","engine":"vosk","wordTimestamps":false}' ``` 响应: ```json { "text": "你好欢迎使用语音服务", "words": [ {"word": "你好", "start": 0.36, "end": 0.72, "conf": 0.99} ], "sampleRate": 16000, "duration": 3.44, "engine": "vosk" } ``` > `words` 仅在 `wordTimestamps=true` 时非空。 #### 3)TTS → ASR 闭环示例 ```python import base64 import requests base = "http://127.0.0.1:18610" # 文字 → 语音(22050Hz WAV) wav = requests.post(f"{base}/audio/tts", json={"text": "你好,欢迎使用智聘云图语音服务。"}).content # 语音 → 文字(直接回灌,无需前端转码) resp = requests.post( f"{base}/audio/asr", files={"file": ("tts.wav", wav, "audio/wav")}, data={"wordTimestamps": "true"}, ) print(resp.json()["text"]) ``` #### 错误码 | 状态码 | 场景 | |:---:|------| | 400 | 音频内容为空 / 格式不支持或文件损坏 / 时长超限 | | 404 | `engine` 未注册(含 `audio.asr.enabled=false` 时) | | 503 | 引擎已注册但模型未就绪 | ### 日常开发命令 | 命令 | 说明 | |------|------| | `uv sync` | 安装/同步依赖 | | `uv run python main.py` | 启动服务 | | `uv add ` | 新增依赖 | | `uv remove ` | 移除依赖 | | `uv lock` | 刷新锁文件 | ### 项目结构 ``` ├── main.py # 入口(uvicorn) ├── application.yml # 应用配置 ├── audio/ # 语音业务模块(引擎 + 注册表) │ ├── BaseAudioService.py # 服务抽象基类 │ ├── Services.py # TTS_SERVICES / ASR_SERVICES 注册表 │ ├── tts/ # 文字转语音 │ │ ├── BaseTtsService.py │ │ ├── PiperTtsService.py │ │ └── models/ # 模型目录(随仓库提供) │ └── asr/ # 语音转文字 │ ├── BaseAsrService.py │ ├── AudioDecoder.py # 任意格式 → 16k 单声道(内置 ffmpeg) │ ├── VoskAsrService.py │ └── models/ # 模型目录(随仓库提供) ├── web/ # FastAPI Web 层(WebApp + routers) ├── config/ # 配置管理(YAML / 日志) ├── common/ # 公共常量 └── util/ # 工具类 ``` ### 配置说明 `application.yml` 支持 `${ENV:default}` 占位符,可直接用环境变量覆盖。 | 配置项 | 说明 | |--------|------| | `server.port` | 服务端口(默认 18610) | | `server.host` | 监听地址(默认 0.0.0.0) | | `audio.tts.engine` | TTS 引擎名(默认 piper) | | `audio.tts.model-dir` | TTS 模型目录 | | `audio.tts.model-file` | TTS 模型文件名 | | `audio.tts.sample-rate` | TTS 采样率(22050) | | `audio.tts.max-text-length` | 单次最大文本长度(1000) | | `audio.asr.enabled` | ASR 开关(默认 true;false 时不注册引擎,接口返回 404) | | `audio.asr.engine` | ASR 引擎名(默认 vosk) | | `audio.asr.model-dir` | ASR 模型目录 | | `audio.asr.language` | ASR 语言(默认 cn) | | `audio.asr.max-audio-seconds` | 单次识别音频时长上限(300 秒) | | `audio.asr.word-timestamps` | 默认是否返回词级时间戳(false) | ### 新增语音引擎 1. 在 `audio/` 下新建子包,继承对应基类(TTS → `BaseTtsService`,ASR → `BaseAsrService`) 2. 实现生命周期与业务方法: - TTS:`name()` / `init()` / `destroy()` / `sample_rate()` / `synthesize()` - ASR:`name()` / `init()` / `destroy()` / `available()` / `transcribe()` 3. 在 `audio/Services.py` 的 `TTS_SERVICES` 或 `ASR_SERVICES` 中注册 4. 在 `application.yml` 中补充该引擎配置项,并在 `common/Common.py` 添加配置键常量 ### 部署 Docker 部署详见 [`DEPLOY.md`](./DEPLOY.md)。