# star-recorder **Repository Path**: leonarial/star-recorder ## Basic Information - **Project Name**: star-recorder - **Description**: star-recorder - **Primary Language**: Python - **License**: Not specified - **Default Branch**: new - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-12-25 - **Last Updated**: 2026-10-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 星纪 · ASR 实时服务 > 目录定位:`server/`(项目根 `xingji/` 下的子模块) 本服务是星纪平台的实时语音识别(ASR)后端,基于 **Flask + Flask-SocketIO**,采用 **FireRedVAD 语音端点检测 + Qwen3ASR 整句转写** 架构。服务对外提供两种接入方式: - **WebSocket(实时录音)**:边录边转,VAD 检测到完整语音段后立即调用 Qwen3ASR 整句转写,结果实时推送给前端,**仅用于实时文字显示,不持久化任何数据**。 - **REST API(长音频转写)**:`/api/vad_split_transcribe` 接口对整段音频做 FireRedVAD 非流式切分,再批量送 Qwen3ASR 转写并合并,返回完整文本与字级时间戳。 > 说明:Qwen3ASR 通过**外部 vLLM 服务**调用,本服务本身只加载 FireRedVAD 模型,不加载 ASR 模型。 --- ## 功能概览 - **实时录音识别(WebSocket)** - FireRedVAD Stream-VAD 流式语音端点检测,自动跳过静音段。 - 检测到完整语音段后调用 Qwen3ASR 进行整句转写。 - 推理任务在独立线程池执行,不阻塞 WebSocket 消息接收。 - 支持**断线重连**:意外断连后会话保留 300 秒,客户端可恢复继续录音。 - **长音频 VAD 切分转写(REST API)** - FireRedVAD 非流式后处理对长音频做语音段切分。 - 分段打包为 batch 一次性发送给 Qwen3ASR vLLM(GPU 层并行调度)。 - 返回完整文本与带偏移校正的字级时间戳。 - **设备自适应**:自动检测 CUDA GPU > Apple MPS > CPU。 - **模型缓存**:`MODELSCOPE_CACHE`、`HF_HOME` 可自定义缓存目录;Docker 部署通过挂载卷持久化。 --- ## 架构概览 ``` ┌──────────────────────── asr_api_server.py ────────────────────────┐ │ │ WebSocket ──► │ RealtimeASR(每会话) │ 实时录音 │ ├─ add_audio → vad_buffer + all_audio │ │ ├─ FireRedVAD Stream-VAD → 语音端点事件(start / end) │ │ └─ end 事件 → 截取该段音频 → Qwen3ASR /transcribe → 推送结果 │ │ │ REST ──────► │ /api/vad_split_transcribe │ 长音频 │ ├─ FireRedVAD 非流式切分 → segments[] │ │ └─ batch 调用 Qwen3ASR /transcribe → 合并文本 + 时间戳 │ │ │ │ /api/health · /api/asr/models · /api/asr/formats │ └───────────────────────────────────────────────────────────────────┘ │ HTTP(base64 WAV) ▼ 外部 Qwen3ASR vLLM 服务 (默认 http://10.8.75.209:9010) ``` --- ## 目录结构 ``` server/ ├── asr_api_server.py # 主服务入口(Flask + SocketIO,REST + WebSocket) ├── requirements.txt # Python 依赖 ├── Dockerfile # 镜像构建(基于 pytorch 官方镜像) ├── docker-compose.yml # 容器编排(GPU + 模型缓存挂载) ├── .dockerignore ├── WebSocket接口文档.md # WebSocket 实时协议说明 ├── logs/ # 运行期日志(可挂载) ├── models_cache/ # FireRedVAD 模型缓存(MODELSCOPE_CACHE) ├── hf_cache/ # HuggingFace 缓存(HF_HOME) └── audio_backups/ # 预留挂载(compose 已挂载,当前代码未写入) ../FireRedVAD/ # FireRedVAD 本地源码包(同级目录,需 pip install) ``` --- ## 运行依赖 ### 必备 - Python 3.10+ - **FireRedVAD 本地包**:位于项目同级的 `FireRedVAD/` 目录,需手动安装(含 `kaldi_native_fbank`、`kaldiio`、`textgrid` 等依赖): ```bash pip install ../FireRedVAD/ ``` 服务启动时会自动把 `../FireRedVAD` 加入 `sys.path`,即使未 pip 安装也可直接导入,但建议正式安装以保证 `kaldi_native_fbank` 等扩展可用。 - **PyTorch**:本地运行需自行安装匹配设备(CUDA / MPS / CPU)的版本;Docker 镜像已内置。 - **外部 Qwen3ASR vLLM 服务**:必须在 `QWEN3ASR_URL`(默认 `http://10.8.75.209:9010`)可用,否则转写调用会失败。 - FFmpeg:非必需(`asr_api_server.py` 使用 soundfile + librosa 处理音频)。 ### Python 依赖 ```bash pip install -r requirements.txt ``` 核心依赖:`flask`、`flask-socketio`、`flask-cors`、`soundfile`、`librosa`、`scipy`、`requests`、`huggingface_hub`、`numpy<2.0`。 --- ## 本地开发 ```bash cd server # 1. 安装 FireRedVAD 本地包 pip install ../FireRedVAD/ # 2. 安装其余依赖 pip install -r requirements.txt # 3. 启动服务(默认端口 5006) python asr_api_server.py ``` 启动后控制台会打印: - 设备检测结果(CUDA GPU / Apple MPS / CPU)。 - FireRedVAD 模型加载情况(首次启动会从 HuggingFace 自动下载到 `models_cache/`)。 - REST API 与 WebSocket 事件列表。 - 服务访问地址:`http://localhost:5006`。 ### FireRedVAD 模型加载策略 1. 优先从 `${MODELSCOPE_CACHE}/fireredvad/Stream-VAD/` 加载(默认即 `server/models_cache/fireredvad/Stream-VAD/`,需存在 `cmvn.ark` 与 `model.pth.tar`)。 2. 若本地不存在,自动从 HuggingFace `FireRedTeam/FireRedVAD` 下载到缓存目录。 3. 也可手动预下载: ```bash # HuggingFace huggingface-cli download FireRedTeam/FireRedVAD --local-dir ./models_cache/fireredvad # 或 ModelScope modelscope download --model xukaituo/FireRedVAD --local_dir ./models_cache/fireredvad ``` --- ## Docker & Compose 镜像基于 `pytorch/pytorch:2.1.0-cuda11.8-cudnn8-runtime`,**构建上下文为项目根目录(`..`)**,因为需要同时复制 `server/` 与 `FireRedVAD/`。 ### 构建 ```bash # 在项目根目录(xingji/)执行,构建上下文为根目录 docker build -f server/Dockerfile -t xingji-asr:latest . ``` ### docker-compose ```bash cd server docker-compose up -d ``` 默认配置: - **GPU**:通过 `deploy.resources` 声明 nvidia GPU(FireRedVAD 自动检测并加速)。 - **端口**:`5006:5006`。 - **挂载卷**:`./models_cache`、`./hf_cache`、`./audio_backups`、`./logs` → 容器内对应目录。 - **环境变量**:`MODELSCOPE_CACHE`、`HF_HOME`、`CUDA_VISIBLE_DEVICES=1`、`TZ=Asia/Shanghai`。 - **健康检查**:`GET /api/health`,启动宽限期 120 秒。 - **镜像仓库**:`docker-compose.yml` 中 `image` 指向私有仓库 `10.8.101.91:8443/autoreport/xingji-asr`,可按需修改。 --- ## REST API | 方法 | 路径 | 描述 | | ---- | -------------------------- | -------------------------------------- | | GET | `/api/health` | 健康检查,返回 VAD 加载状态与服务地址 | | GET | `/api/asr/models` | 当前模型信息 | | GET | `/api/asr/formats` | 支持的音频格式列表 | | POST | `/api/vad_split_transcribe`| 长音频 VAD 切分 + 分段转写 + 合并 | ### 健康检查 ```bash curl http://localhost:5006/api/health ``` ```json { "success": true, "message": "ASR API服务正常运行", "firered_vad_loaded": true, "qwen3asr_url": "http://10.8.75.209:9010" } ``` ### 长音频 VAD 切分转写 `POST /api/vad_split_transcribe` 请求体(JSON): | 字段 | 类型 | 说明 | | -------------------- | -------------------- | ----------------------------------------------- | | `audio` | string | base64 data URL(`data:audio/wav;base64,...`),**单条**。服务端内部做 VAD 切分后再 batch 送 Qwen3ASR,客户端无需(也不应)传数组 | | `language` | string \| null | 语言提示,透传给 Qwen3ASR | | `return_time_stamps` | bool | 是否返回字级时间戳,默认 `true` | 示例: ```bash # AUDIO_DATA_URL 形如 data:audio/wav;base64,UklGRi.... curl -X POST http://localhost:5006/api/vad_split_transcribe \ -H "Content-Type: application/json" \ -d "{\"audio\":\"$AUDIO_DATA_URL\",\"language\":null,\"return_time_stamps\":true}" ``` 响应(JSON): | 字段 | 类型 | 说明 | | ---------------- | ------- | ------------------------------------------------- | | `success` | bool | 是否成功 | | `text` | string | 所有语音段拼接的完整文本 | | `language` | string | 检测到的语言(取首个分段结果) | | `time_stamps` | array | 字级时间戳 `[{text, start, end}]`,已叠加段偏移;`return_time_stamps=false` 时为 `null` | | `segments_count` | int | VAD 切分出的语音段数 | > 实现细节:本服务用 FireRedVAD 切分得到语音段后,将各段打包为 **batch** 一次性 POST 给 Qwen3ASR vLLM 的 `/transcribe`,由 vLLM 在 GPU 层并行调度,随后对返回的时间戳叠加每段的起始偏移(`segment_offset_ms`)以还原全局时间。输入音频若为多声道会按均值降混为单声道,采样率非 16kHz 会自动重采样到 16kHz。 ### 支持的音频格式 `wav`、`mp3`、`ogg`、`flac`、`m4a`、`aac`、`wma`、`webm`。 --- ## WebSocket 接入 服务端地址:`ws://:5006/socket.io/`(Socket.IO;协议版本由 flask-socketio 5.x 默认协商,客户端使用 socket.io-client v4 兼容)。 ### 事件流程 ``` 客户端 服务器 │ │ ├──────── connect ─────────────────► │ │ ◄──────── connected {session_id} ──┤ │ │ ├──────── start_recording {hotwords} ► │ │ ◄──────── recording_started ────────┤ │ │ ├──────── audio_data (PCM bytes) ───► │ 持续发送 │ ◄──────── transcription ────────────┤ 每句整句结束推送 │ │ ├──────── stop_recording ──────────► │ 强制转写未完成段 │ ◄──────── transcription (finalize) ─┤ (若有未完成段) │ │ │ 断线恢复(可选) │ ├──────── resume_recording {sid} ──► │ │ ◄──────── resume_result ────────────┤ ``` ### 客户端 → 服务端 | 事件 | 载荷 | 说明 | | ------------------ | ---------------------------------------------- | ------------------------------------------------------------ | | `start_recording` | `{ "hotwords": string \| null }`(可省略) | 开始录音,创建会话。`hotwords` 当前透传但 Qwen3ASR 统一服务暂不支持 | | `audio_data` | PCM int16 字节(16kHz、单声道、小端) | 持续发送音频块。服务端无强制分块限制(单帧上限 10MB),前端通常按数百毫秒一帧发送 | | `stop_recording` | 无 | 停止录音,强制 finalize 当前未完成语音段 | | `resume_recording` | `{ "original_session_id": string }` | 断线重连后恢复原会话 | ### 服务端 → 客户端 | 事件 | 载荷 | 说明 | | ------------------ | ------------------------------------------------------------------------- | ------------------------------------------ | | `connected` | `{ "session_id": string }` | 连接成功 | | `recording_started`| `{ "status": "ok" }` | 录音会话已创建 | | `transcription` | `{ type:"sentence_final", sentence:{text}, sentences:[{text}...], full_text }` | 一句整句转写完成时推送;`full_text` 为已确认句子的累加文本 | | `resume_result` | `{ success, current_text, duration_s }` 或 `{ success:false, reason }` | 断线重连结果 | | `error` | `{ "message": string }` | 错误信息(如会话不存在) | ### 音频参数要求 | 参数 | 值 | | ------ | ---------- | | 采样率 | 16000 Hz | | 位深度 | 16-bit int | | 声道 | 单声道 | | 编码 | PCM | | 字节序 | 小端 | ### 断线重连机制 - 客户端断连时,若会话仍在录音且未进入 `stop_recording` 的 finalize 流程,会话会被移入宽限保留区,**保留 300 秒**(`SESSION_GRACE_PERIOD`)。 - 客户端重连后发送 `resume_recording`,携带原始 `session_id`,服务端从宽限区恢复 `RealtimeASR` 实例并绑定到新 socket,返回已确认文本与已录时长。 - 超过 300 秒未恢复的会话由后台守护线程自动清理。 > 完整的实时协议描述另见 `WebSocket接口文档.md`(注意:该文档为早期版本,部分事件结构以本 README 及源码为准)。 --- ## 配置项 | 配置项 | 来源 | 默认值 | 说明 | | ------------------ | -------------------- | ----------------------------------------------- | -------------------------------------- | | `QWEN3ASR_URL` | 环境变量 | `http://10.8.75.209:9010` | 外部 Qwen3ASR vLLM 服务地址(在 `docker-compose.yml` 的 `environment` 中配置) | | `MODELSCOPE_CACHE` | 环境变量 | `server/models_cache` | FireRedVAD 模型缓存目录 | | `HF_HOME` | 环境变量 | `server/hf_cache` | HuggingFace 缓存目录 | | 服务端口 | 代码常量 | `5006` | Flask-SocketIO 监听端口 | SocketIO 稳定性相关参数(代码内置,无需配置):`ping_timeout=120s`、`ping_interval=30s`、`max_http_buffer_size=10MB`、推理线程池 `max_workers=4`。 --- ## 限制与运维须知 以下事实影响部署、容量与安全,使用前请知悉(均可对照 `asr_api_server.py` 源码): - **运行模式**:服务以 Flask-SocketIO + Werkzeug 开发服务器启动(`allow_unsafe_werkzeug=True`),**非生产级 WSGI**;生产部署须在前端用 Nginx 反向代理并做好限流/鉴权。 - **单进程、不可水平扩容**:SocketIO 采用 `threading` 模式,会话状态(`active_sessions` / `disconnected_sessions`)保存在进程内存,**不支持多副本横向扩展**;如需扩展须引入 Redis 等消息后端并改造会话管理。 - **并发上限**:推理线程池 `max_workers=4`,同时进行整句转写的实时会话超过 4 路后开始排队,实时文字会出现延迟——请据此规划并发容量。 - **无鉴权、CORS 全开**:所有 REST 路由与 WebSocket 事件均不鉴权,且 `cors_allowed_origins='*'`,任何可达端口的客户端都可调用并消耗 GPU/推理资源,**必须通过网络层(内网 / Nginx + 鉴权 / IAM)保护**。 - **超时与静默丢字**:调用 Qwen3ASR 的超时为实时单段 60s、批量转写 300s;实时段转写失败时仅记 `WARN` 日志并跳过该段(不推送、不发 `error`),丢字需查服务端日志排查。 - **降级运行**:FireRedVAD 导入或加载失败时服务**不退出**,而是降级启动——实时录音不产生任何 `transcription` 事件,`/api/vad_split_transcribe` 会返回 "FireRedVAD 非流式组件未加载"。部署后请用 `GET /api/health` 的 `firered_vad_loaded` 字段确认。 - **单段时长上限**:VAD 单段连续语音上限约 20 秒(`max_speech_frame=2000`),超长独白会被强制切分后分别转写。 - **停止语义**:`stop_recording` 最多等待 5 秒(`50×0.1s`)排空在途推理,超时则直接强制 finalize 当前未完成段,末句可能延迟或缺失。 --- ## 常见问题 1. **FireRedVAD 导入失败(`kaldi_native_fbank` 等缺失)** - 未安装本地 FireRedVAD 包。执行 `pip install ../FireRedVAD/` 安装其扩展依赖。 2. **转写失败 / 一直收到空文本** - 检查外部 Qwen3ASR vLLM 服务(`QWEN3ASR_URL`)是否可达。 3. **首次启动很慢 / 模型下载失败** - 首次启动会从 HuggingFace 下载 FireRedVAD 模型;可按上文手动预下载到 `models_cache/fireredvad/`。 4. **Mac 上走 MPS 性能/稳定性问题** - FireRedVAD 会自动尝试 MPS;如需强制 CPU,可在无 CUDA 的环境下让其回退(代码在检测不到 CUDA 与 MPS 时使用 CPU)。 5. **WebSocket 长连接断开** - 已调高 `ping_timeout` 与 `ping_interval`;若经 Nginx 反代,需放行 `/socket.io/` 长连接并调大代理超时。意外断连可借助断线重连机制恢复。 --- ## 部署建议 - 使用 Nginx 反向代理时,放行 `/socket.io/` 长连接,调大 `proxy_read_timeout`。 - Docker 部署务必将 `models_cache`、`hf_cache` 挂载到宿主机,避免重建容器重复下载模型。 - 生产环境建议为外部 Qwen3ASR vLLM 服务配置稳定地址与监控;本服务自身无状态依赖 Qwen3ASR,Qwen3ASR 不可用会直接导致转写失败。 --- 如需扩展或联调,可重点关注 `asr_api_server.py` 中: - 模型初始化:`init_models()` - REST 路由:`/api/vad_split_transcribe`、`/api/health` 等 - WebSocket 事件:`handle_start_recording` / `handle_audio_data` / `handle_stop_recording` / `handle_resume_recording` - 实时识别核心类:`RealtimeASR`(VAD + 分段转写)