DroneMind/voicellmcloud/docs/API_SPECIFICATION.md
2026-04-14 10:08:41 +08:00

305 lines
9.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 云端无人机语音服务 - 接口规范 (v1.0)
本文档定义了香橙派客户端与云端语音服务之间的完整交互协议。
---
## 1. 连接信息
| 项目 | 值 |
|------|-----|
| **协议** | WebSocket (ws://) |
| **地址** | `ws://<服务器IP>:8765/v1/voice/session` |
| **鉴权** | Bearer Token |
### 1.1 仅 TTS不走 LLM
在**已建立**的 WebSocket 会话上发送 **`tts.synthesize`**(见 §3.3),下行仍为 `tts_audio_chunk`text 元数据 + binary PCM**`turn.complete`**;与 `turn.text` **互斥排队**,共用同一连接与播放器路径,无需额外 HTTP。
### 1.2 机端麦克风 + 云端 Fun-ASR`pcm_asr_uplink`
`session.start` 中设置 **`transport_profile`: `pcm_asr_uplink`**,使用 **`turn.audio.start` / `turn.audio.chunk` / `turn.audio.end`** 上行 **16 kHz mono pcm_s16le**chunk 内 **Base64** 裸 PCM。服务端用**与 Qwen 相同的百炼 API Key** 调用阿里云 **Fun-ASR 实时识别**;识别完成后下行与 `text_uplink` 相同(并可选 **`asr.partial`**)。**完整字段与时序见** [`CLOUD_VOICE_PROTOCOL_pcm_asr_uplink_v1.md`](./CLOUD_VOICE_PROTOCOL_pcm_asr_uplink_v1.md)。
**机端「未唤醒不上云 / 节省 Fun-ASR 按量」**:见 [`CLOUD_VOICE_CLIENT_WAKE_GATE_v1.md`](./CLOUD_VOICE_CLIENT_WAKE_GATE_v1.md)。
**小爱类多轮会话问候、滴声、断句提示、5s 超时、飞控/闲聊分支,服务端+机端分工)**:见 [`CLOUD_VOICE_ASSISTANT_SESSION_v1.md`](./CLOUD_VOICE_ASSISTANT_SESSION_v1.md)。
---
## 2. 完整通信时序
```
客户端 (香橙派) 云端服务端
| |
|-------- 1. session.start ------------------->|
| |
|<------- 2. session.ready --------------------|
| |
|-------- 3. turn.text (STT 结果) ------------>|
| |
|<------- 4. dialog_result (闲聊/飞控JSON) ----|
|<------- 5. tts_audio_chunk (text 帧) --------|
|<------- 6. tts_audio_chunk (binary 音频帧) --|
|<------- 7. ... (多个音频块) ... --------------|
|<------- 8. turn.complete -------------------|
| |
|-------- 9. session.end --------------------->|
```
**仅 TTS无对话**:在 `session.ready` 之后可发送 `tts.synthesize`(每条自带 `turn_id`);服务端仅下行 `tts_audio_chunk*``turn.complete`**无** `dialog_result` / `llm.text_delta`)。
---
## 3. 消息格式详情
### 3.1 建立会话 (session.start)
**客户端 → 服务端**
```json
{
"type": "session.start",
"proto_version": "1.0",
"transport_profile": "text_uplink",
"session_id": "唯一会话ID-UUID",
"auth_token": "与服务端 .env 中的 BEARER_TOKEN 一致",
"client": {
"device_id": "设备唯一标识符",
"locale": "zh-CN",
"capabilities": {
"playback_sample_rate_hz": 24000,
"prefer_tts_codec": "pcm_s16le"
}
}
}
```
---
### 3.2 服务端就绪 (session.ready)
**服务端 → 客户端**
```json
{
"type": "session.ready",
"proto_version": "1.0",
"transport_profile": "text_uplink",
"session_id": "唯一会话ID-UUID",
"server_caps": {
"accepts_audio_uplink": false,
"llm": true,
"tts_codecs": ["pcm_s16le"],
"llm_context_turns": 4
}
}
```
---
### 3.3 仅 TTS 上行 (tts.synthesize)
`session.ready` 之后,不经过 LLM由服务端直接合成 `text` 并推送 **`tts_audio_chunk*`**,最后 **`turn.complete`**`metrics.llm_ms``0`)。**不**下发 `dialog_result``llm.text_delta`**不**写入对话历史。与 **`turn.text` pipeline 互斥**(同会话上一条处理完再发下一条)。
**客户端 → 服务端**
```json
{
"type": "tts.synthesize",
"proto_version": "1.0",
"transport_profile": "text_uplink",
"turn_id": "唯一轮次ID-UUID与 tts_audio_chunk.turn_id 一致)",
"text": "要播报的中文"
}
```
`text` 长度受服务端 `TTS_MAX_CHARS` 限制,超出截断。)
---
### 3.4 发送文本 (turn.text)
**客户端 → 服务端**
```json
{
"type": "turn.text",
"proto_version": "1.0",
"transport_profile": "text_uplink",
"turn_id": "唯一轮次ID-UUID",
"text": "本地 STT 识别的完整中文文本",
"is_final": true,
"source": "device_stt"
}
```
---
### 3.5 识别结果 (dialog_result)
**服务端 → 客户端**
客户端必须根据 `routing` 字段判断处理逻辑。
#### 场景 A闲聊 (chitchat)
```json
{
"type": "dialog_result",
"proto_version": "1.0",
"transport_profile": "text_uplink",
"turn_id": "唯一轮次ID-UUID",
"user_input": {
"text": "用户原始文本",
"language": "zh",
"is_final": true,
"source": "device_stt"
},
"routing": "chitchat",
"flight_intent": null,
"chat_reply": "你好我无法获取实时天气信息建议查看手机天气App。",
"tts_hint": {
"speak_summary_or_reply": true,
"voice_id": "default"
}
}
```
**客户端动作:**
1. 使用 `chat_reply` 字段的内容进行 TTS 播放。
2. **必须**继续接收后续的 `tts_audio_chunk``turn.complete`
---
#### 场景 B飞控指令 (flight_intent) ✨ 重点
```json
{
"type": "dialog_result",
"proto_version": "1.0",
"transport_profile": "text_uplink",
"turn_id": "唯一轮次ID-UUID",
"user_input": {
"text": "起飞然后在前方十米悬停",
"language": "zh",
"is_final": true,
"source": "device_stt"
},
"routing": "flight_intent",
"flight_intent": {
"is_flight_intent": true,
"version": 1,
"actions": [
{"type": "takeoff", "args": {}},
{"type": "goto", "args": {"frame": "local_ned", "x": 10, "y": 0, "z": null}},
{"type": "hover", "args": {}}
],
"summary": "起飞后前往机头方向约十米并保持高度"
},
"chat_reply": null,
"tts_hint": {
"speak_summary_or_reply": true,
"voice_id": "default"
}
}
```
> 飞控载荷的严格字段表与 `wait` / `takeoff.relative_altitude_m` / 可选 `trace_id` 等,见 `docs/FLIGHT_INTENT_SCHEMA_v1.md`。
**🔊 TTS 播报约定(重要):**
* **当 `routing``flight_intent` 时,云端 TTS 合成并下发的语音固定为:**
> **"识别到飞控指令,正在下发指令"**
* 客户端收到 `flight_intent` 后,应开始解析 `actions` 数组并准备下发飞控,同时播放该提示音。
**客户端动作:**
1. **解析 `flight_intent.actions`** 数组,按顺序执行飞控逻辑。
2. **必须**继续接收后续的 `tts_audio_chunk`(听到提示音)和 `turn.complete`**绝对不能提前断开或退出循环**
---
### 3.6 TTS 音频流 (tts_audio_chunk)
**协议规则:**
音频流由成对的消息组成:
1. **Text 帧 (JSON)**:描述接下来音频块的元数据。
2. **Binary 帧 (Raw Bytes)**:真正的 PCM 音频数据。
**Text 帧 (JSON) 格式:**
```json
{
"type": "tts_audio_chunk",
"proto_version": "1.0",
"transport_profile": "text_uplink",
"turn_id": "唯一轮次ID-UUID",
"seq": 0,
"codec": "pcm_s16le",
"sample_rate_hz": 24000,
"is_final": false
}
```
**Binary 帧 (音频数据)**
* **格式**PCM S16LE (16-bit, Little-Endian)
* **采样率**24000 Hz
* **声道**Mono (单声道)
**⚠️ 客户端接收关键逻辑(必须遵守):**
```python
msg = await ws.recv()
# ✅ 必须判断类型!
if isinstance(msg, bytes):
# 这是二进制音频数据,追加到缓冲区
audio_buffer.append(msg)
# ❌ 绝对不能对 msg 执行 json.loads(msg)
else:
# 这是 JSON 元数据
data = json.loads(msg)
...
```
---
### 3.7 轮次完成 (turn.complete)
**服务端 → 客户端**
收到此消息表示当前轮次LLM + TTS**完全结束**。
```json
{
"type": "turn.complete",
"proto_version": "1.0",
"transport_profile": "text_uplink",
"turn_id": "唯一轮次ID-UUID",
"metrics": {
"llm_ms": 1400,
"tts_first_byte_ms": 3100
}
}
```
**客户端动作:**
1. 收到此消息后,拼接 `audio_buffer` 中的二进制块。
2. 将完整的 PCM 数据送入声卡播放。
3. 准备下一轮对话。
---
## 4. 客户端检查清单 (Checklist)
客户端团队在联调前,请确保以下逻辑已正确实现:
- [ ] **区分帧类型**:接收循环中是否有 `isinstance(msg, bytes)` 判断?
- [ ] **禁止提前退出**:无论 `routing` 是闲聊还是飞控,**必须**收到 `turn.complete` 才能结束当前 `run_turn`
- [ ] **JSON 解析安全**:确保**只对 Text 帧**调用 `json.loads()`Binary 帧直接 `append`
- [ ] **音频播放**:收到的 PCM 数据是 `int16` 格式,播放前需转为 `float32` (除以 32768.0)。
- [ ] **飞控提示音**:收到 `routing=flight_intent` 时,期望听到 TTS 语音:"识别到飞控指令,正在下发指令"。
---
**版本**: v1.0
**更新日期**: 2024-04-07
**协议标识**: `text_uplink`