# SmartMeeting 智能会议

这是一个基于 Docker Compose 的智能会议部署项目，包含 Speakr 前后端、Paraformer ASR、声纹向量提取服务，以及实时 WebSocket 转写桥接服务。

## 目录结构

```text
/storage01/home/xll/202607/voice
├── docker-compose.yml          # 统一启动文件
├── config.env                  # 运行环境变量，主要改这里
├── asr/main.py                 # ASR 适配服务代码
├── speakr/                     # Speakr 前后端代码
├── voice-dected/               # 声纹向量提取服务代码
├── fastapi_wss/                # 实时 WebSocket 桥接服务代码
└── deploy/data/                # 运行数据，已被 git 忽略
```

## 启动和停止

启动全部服务：

```bash
cd /storage01/home/xll/202607/voice
docker compose up -d
```

查看状态和日志：

```bash
docker compose ps
docker compose logs -f
```

停止服务：

```bash
docker compose down
```

## 服务和端口

| 服务 | 容器名 | 端口 | 作用 |
| --- | --- | --- | --- |
| Speakr | `voice-speakr` | `8899` | 页面和后端 |
| ASR | `asr-test` | `59805` | Paraformer HTTP / WebSocket 识别 |
| Python 实时 ASR | `funasr-online` | `10096` | FunASR Python WebSocket，保留用于回退 |
| 官方 Runtime 实时 ASR | `funasr-runtime-2pass` | `10097` | FunASR 官方 C++/ONNX 2pass WebSocket |
| 声纹提取 | `voice-dected` | `18000` | `/extract_embedding` 声纹向量服务 |
| 实时桥接 | `fastapi-wss` | `10095` | 页面实时 ASR WebSocket 桥接 |
| Redis | `voice-redis` | `16380` | 实时桥接的缓存/会话存储 |

页面访问地址：

```text
http://192.168.0.46:8899/tool/speakr/
```

浏览器麦克风录音需要安全上下文。局域网 IP 的 HTTP 页面可能无法调用麦克风。最快的本机访问方式是开 SSH 隧道：

```bash
ssh -L 8899:127.0.0.1:8899 xll@192.168.0.46
```

然后浏览器打开：

```text
http://localhost:8899/tool/speakr/
```

## 配置文件

主要修改这个文件：

```text
/storage01/home/xll/202607/voice/config.env
```

改服务地址、模型地址、LLM 配置、声纹匹配阈值时，优先改 `config.env`。修改后执行：

```bash
cd /storage01/home/xll/202607/voice
docker compose up -d
```

重要变量：

```env
ASR_BASE_URL=http://asr-test:59805
FUNASR_WEBSOCKET_IP=ws://fastapi-wss:10095/websocket_offline
TARGET_WS_URL=ws://funasr-runtime-2pass:10095
# 如需回退到 Python 版实时 ASR，可改为：
# TARGET_WS_URL=ws://funasr-online:10096
VOICE_DETECTED_URL=http://voice-dected:8000
SPEAKER_DET_URL=http://voice-dected:8000/extract_embedding
REDIS_URL=redis://redis:6379/0

TEXT_MODEL_BASE_URL=http://192.168.0.46:59800/v1
TEXT_MODEL_NAME=46-qwen3.5-35B
TEXT_MODEL_API_KEY=none

VOICEPRINT_STORE=/data/asr_voiceprints.json
VOICEPRINT_MATCH_THRESHOLD=0.45
```

说明：

- Compose 内部服务互相访问时，用 `asr-test`、`voice-dected`、`redis` 这种服务名。
- 宿主机或外部机器访问时，用 `192.168.0.46:59805`、`192.168.0.46:18000`、`192.168.0.46:10095` 这种宿主机端口。
- `FUNASR_WEBSOCKET_IP` 是 Speakr 后端实时录音代理要连接的 WebSocket 地址。
- `TARGET_WS_URL` 是 `fastapi-wss` 再往后连接的 ASR WebSocket 地址。
- `VOICEPRINT_MATCH_THRESHOLD` 是声纹匹配阈值。值越低越容易匹配，值越高越不容易误匹配。

## 运行数据

运行数据放在：

```text
/storage01/home/xll/202607/voice/deploy/data/
```

常见子目录：

```text
deploy/data/speakr/uploads/      # 上传和录音文件
deploy/data/speakr/instance/     # Speakr SQLite 数据库
deploy/data/asr/                 # ASR 声纹库
deploy/data/redis/               # Redis 持久化数据
```

这些运行数据已被 `.gitignore` 忽略，不会提交到仓库。

## 声纹流程

Speakr 声纹管理页面会调用 ASR 适配层的兼容接口：

```text
POST   /voice_insert
GET    /get_voice_name
DELETE /delete_voice
```

ASR 适配层会把声纹向量保存到：

```text
deploy/data/asr/asr_voiceprints.json
```

转录时，`/asr` 会先把上传音频转成 `16k 单声道 wav`，再调用 `voice-dected` 提取声纹向量，并和已录入声纹做相似度匹配。如果分数超过 `VOICEPRINT_MATCH_THRESHOLD`，返回匹配到的说话人名称。

目前这个逻辑是“整段音频匹配一个最像的人”。如果一段录音里多人轮流说话，还需要进一步接入分段说话人识别。

## 常用检查命令

检查 ASR：

```bash
curl http://127.0.0.1:59805/health
```

检查声纹列表：

```bash
curl http://127.0.0.1:59805/get_voice_name
```

检查 Speakr 配置：

```bash
curl http://127.0.0.1:8899/tool/speakr/api/config
```

检查实时 WebSocket 链路：

```bash
docker exec -it fastapi-wss python - <<'PY'
import asyncio, json, websockets

async def main():
    async with websockets.connect("ws://127.0.0.1:10095") as ws:
        await ws.send(json.dumps({"mode": "2pass", "is_speaking": True}))
        await ws.send(json.dumps({"type": "stop", "is_speaking": False}))
        print(await ws.recv())

asyncio.run(main())
PY
```

## Git 操作

仓库地址：

```text
https://gitea.zkzdht.com/Ascend/SmartMeeting.git
```

常用命令：

```bash
cd /storage01/home/xll/202607/voice
git status
git add .
git commit -m "更新说明"
git push
```
