2026-04-14 10:08:41 +08:00

11 KiB
Raw Permalink Blame History

云端无人机语音服务 (Cloud Voice Service)

基于 FastAPI + WebSocket 的云端语音交互服务,为无人机提供 LLM 意图识别TTS 文字转语音 能力。

📋 特性

  • 协议规范: 完整实现 Cloud Voice Protocol v1.0 (text_uplink)
  • LLM 意图识别: 阿里云百炼 Qwen 模型,区分飞控指令与闲聊
  • TTS 本地推理: Piper-TTS 高效本地合成语音 (24kHz PCM)
  • 流式输出: LLM 结果 + TTS 音频块流式下发
  • 并发支持: 最多 4 路无人机并发会话
  • 模块化架构: 易于扩展新的 LLM/TTS 提供者

📁 项目结构

voicellmcloud/
├── app/                          # 主应用
│   ├── main.py                   # FastAPI 入口
│   ├── config.py                 # 配置管理
│   ├── protocols/                # 协议层
│   │   ├── models.py             # 消息数据模型
│   │   └── validators.py         # 协议验证
│   ├── websocket/                # WebSocket 管理
│   │   ├── session.py            # 会话管理
│   │   └── handler.py            # 消息处理
│   ├── services/                 # 业务服务接口
│   │   ├── llm_service.py        # LLM 接口
│   │   ├── tts_service.py        # TTS 接口
│   │   └── intent_service.py     # 意图识别
│   ├── providers/                # 第三方服务实现
│   │   ├── dashscope_llm.py      # 阿里云 LLM
│   │   └── piper_tts.py          # Piper TTS
│   └── utils/                    # 工具
│       ├── audio.py              # 音频处理
│       └── logger.py             # 日志
├── models/                       # TTS 模型文件目录
├── requirements.txt              # Python 依赖
├── .env                          # 环境配置
├── .env.example                  # 配置示例
├── start.sh / start.bat          # 启动脚本
└── README.md

🚀 快速开始

1. 环境准备

# Python 3.10+
python --version

# 安装依赖
pip install -r requirements.txt

2. 下载 Piper TTS 模型

# 下载中文模型到 models/ 目录
python -m piper.download_voice zh_CN-huayan-medium

# 或手动下载
# https://huggingface.co/rhasspy/piper-voices/tree/v1.0.0/zh_CN/huayan/medium
# 将 zh_CN-huayan-medium.onnx 和 .json 放到 models/

3. 配置环境变量

# 复制配置示例
cp .env.example .env

# 编辑 .env修改以下配置
# - DASHSCOPE_API_KEY: 阿里云百炼 API Key (已预填)
# - TTS_MODEL_DIR: Piper 模型目录 (默认 models)
# - BEARER_TOKEN: 鉴权 Token (客户端需一致)

4. 启动服务

Linux/macOS:

chmod +x start.sh
./start.sh

Windows:

start.bat

或直接运行:

python -m uvicorn app.main:app --host 0.0.0.0 --port 8765 --reload

5. 验证服务

# 健康检查
curl http://localhost:8765/health

# 应返回:
# {"status":"ok","active_sessions":0,"llm_provider":"dashscope","tts_provider":"piper"}

🔧 配置说明

所有配置通过 .env 文件或环境变量设置:

配置项 默认值 说明
WS_HOST 0.0.0.0 WebSocket 监听地址
WS_PORT 8765 WebSocket 端口
BEARER_TOKEN drone-voice-cloud-token-2024 鉴权 Token
DASHSCOPE_API_KEY - 阿里云百炼 API Key
LLM_MODEL qwen-plus LLM 模型 (qwen-turbo/plus/max)
LLM_CONTEXT_TURNS 4 保留历史对话轮数
TTS_PROVIDER piper TTS 提供者
TTS_VOICE_NAME zh_CN-huayan-medium Piper 语音名称
MAX_CONCURRENT_SESSIONS 4 最大并发会话数
LOG_LEVEL INFO 日志级别

📡 WebSocket 协议

完整协议见 CLOUD_VOICE_PROTOCOL_v1_text_uplink.md

连接地址

ws://<server-ip>:8765/v1/voice/session

基本时序

客户端                              服务端
  |                                    |
  |------ session.start -------------->|
  |                                    |
  |<----- session.ready ---------------|
  |                                    |
  |------ turn.text ------------------>|
  |                                    |
  |<----- dialog_result --------------|
  |<----- tts_audio_chunk (text) -----|
  |<----- tts_audio_chunk (binary) ---|
  |<----- turn.complete --------------|
  |                                    |
  |------ session.end --------------->|

示例消息

session.start:

{
  "type": "session.start",
  "proto_version": "1.0",
  "transport_profile": "text_uplink",
  "session_id": "uuid-v4",
  "auth_token": "your-token",
  "client": {
    "device_id": "drone-001",
    "locale": "zh-CN",
    "capabilities": {
      "playback_sample_rate_hz": 24000,
      "prefer_tts_codec": "pcm_s16le"
    }
  }
}

turn.text:

{
  "type": "turn.text",
  "proto_version": "1.0",
  "transport_profile": "text_uplink",
  "turn_id": "uuid-v4",
  "text": "起飞然后在前方十米悬停",
  "is_final": true,
  "source": "device_stt"
}

🧪 测试

测试用例

  1. 闲聊: "今天天气怎么样"

    • 预期: routing=chitchat, TTS 播报闲聊回复
  2. 飞控指令: "起飞然后在前方十米悬停"

    • 预期: routing=flight_intent, actions=[takeoff, goto], TTS 播报 summary
  3. 返航: "返航"

    • 预期: routing=flight_intent, actions=[return_home]
  4. 非法音频消息: 发送 turn.audio_chunk

    • 预期: error code=INVALID_MESSAGE
  5. 鉴权失败: 使用错误 token

    • 预期: error code=UNAUTHORIZED

Python 测试客户端

import asyncio
import json
import websockets

async def test_client():
    uri = "ws://localhost:8765/v1/voice/session"
    
    async with websockets.connect(uri) as ws:
        # 1. 发送 session.start
        await ws.send(json.dumps({
            "type": "session.start",
            "proto_version": "1.0",
            "transport_profile": "text_uplink",
            "session_id": "test-session-001",
            "auth_token": "drone-voice-cloud-token-2024",
            "client": {
                "device_id": "test-drone",
                "locale": "zh-CN",
                "capabilities": {
                    "playback_sample_rate_hz": 24000,
                    "prefer_tts_codec": "pcm_s16le"
                }
            }
        }))
        
        # 接收 session.ready
        ready = await ws.recv()
        print(f"← {ready}")
        
        # 2. 发送 turn.text
        await ws.send(json.dumps({
            "type": "turn.text",
            "proto_version": "1.0",
            "transport_profile": "text_uplink",
            "turn_id": "test-turn-001",
            "text": "你好,今天天气怎么样?",
            "is_final": True,
            "source": "device_stt"
        }))
        
        # 3. 接收响应
        while True:
            msg = await ws.recv()
            if isinstance(msg, bytes):
                print(f"← 音频数据 ({len(msg)} bytes)")
            else:
                data = json.loads(msg)
                print(f"← {data['type']}: {json.dumps(data, ensure_ascii=False)}")
                
                if data.get('type') == 'turn.complete':
                    break

asyncio.run(test_client())

🏗 架构设计

模块化层次

┌─────────────────────────────────────────┐
│          FastAPI Application            │
│  (app/main.py)                          │
├─────────────────────────────────────────┤
│         WebSocket Handler               │
│  (app/websocket/handler.py)             │
├──────────┬──────────┬───────────────────┤
│  LLM     │  TTS     │  Intent Service   │
│ Service  │ Service  │  (意图识别)       │
├──────────┼──────────┼───────────────────┤
│DashScope │  Piper   │  协议模型/验证    │
│ (阿里云) │  (本地)  │                   │
└──────────┴──────────┴───────────────────┘

扩展新的 LLM/TTS 提供者

只需实现对应接口并注册:

# 1. 实现接口
class MyLLMService(LLMServiceInterface):
    async def chat(...): ...
    async def initialize(...): ...
    async def shutdown(...): ...

# 2. 在 app/main.py 中添加
if settings.LLM_PROVIDER == "my_llm":
    llm_service = MyLLMService()

📊 性能指标

指标 预期值
LLM 推理延迟 1-3s (阿里云 qwen-plus)
TTS 首字节延迟 <200ms (Piper 本地)
音频采样率 24000 Hz
音频格式 PCM S16LE (mono)
最大并发 4 sessions

🔮 后续规划

  • 支持本地 H200 部署 LLM (vLLM/TGI)
  • 多语言 TTS 支持
  • WebSocket TLS (WSS) 支持
  • Prometheus 指标监控
  • 会话持久化与断线重连
  • Docker 容器化部署

📝 开发说明

添加新模块

# 创建模块目录
mkdir app/new_module
touch app/new_module/__init__.py
touch app/new_module/module.py

日志级别

# 修改 .env
LOG_LEVEL=DEBUG  # 查看详细日志
LOG_LEVEL=INFO   # 生产环境

调试技巧

# 在 handler.py 中添加断点
import pdb; pdb.set_trace()

常见问题

Q: Piper TTS 初始化失败?

# 检查模型文件是否存在
ls -lh models/zh_CN-huayan-medium.onnx

# 重新下载
python -m piper.download_voice zh_CN-huayan-medium

Q: LLM 调用超时?

# 检查 API Key
echo $DASHSCOPE_API_KEY

# 增加超时时间
LLM_TIMEOUT=60

Q: 客户端连接被拒绝?

# 检查 BEARER_TOKEN 是否一致
# 服务器 .env 中的 BEARER_TOKEN 必须与客户端 auth_token 一致

扩展阅读

📄 许可证

内部项目 - 无人机云端语音交互服务

🤝 贡献

提交 Issue 或 Pull Request 以改进本项目。


版本: v1.0.0
更新日期: 2024-04-07
协议版本: Cloud Voice Protocol v1.0 (text_uplink)