711 lines
17 KiB
Markdown
711 lines
17 KiB
Markdown
# LiveKit 通话链路说明
|
||
|
||
本文档记录 `arcs_mini` 硬件端新增的通话链路:设备通过语音意图触发呼叫,连接 Node LiveKit 桥接服务,完成设备与设备、设备与 App/Web 之间的实时语音通话。
|
||
|
||
## 背景
|
||
|
||
原有主链路主要负责唤醒、语音对话、MCP 工具调用、TTS 播放等能力。新增通话能力后,硬件端多了一条独立的通话链路:
|
||
|
||
1. 设备联网后主动连接 LiveKit Node 桥接服务的 WebSocket。
|
||
2. 用户通过主链路语音对话说“给某人/某设备打电话”。
|
||
3. 主链路意图识别命中通话工具,调用硬件端 MCP 工具 `ls.built_in.call_device`。
|
||
4. MCP 工具把目标设备号 `toId` 交给硬件端 LiveKit 桥接模块。
|
||
5. 桥接模块通过 WebSocket 给 Node 发送 `device_call`。
|
||
6. Node 创建 invite、加入 LiveKit 房间,并把双方音频转成硬件可消费的 PCM 帧。
|
||
7. 硬件端收下行 PCM 播放,同时采集本机麦克风 PCM 上行给 Node。
|
||
|
||
这条链路和主对话链路是并行关系:主对话链路负责“识别用户想打电话”和“调用工具”,通话链路负责“真正建立通话和传输音频”。
|
||
|
||
## 核心链路
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant User as 用户
|
||
participant Device as 硬件端 arcs_mini
|
||
participant MainCloud as 主链路云服务
|
||
participant MCP as MCP 工具 call_device
|
||
participant Bridge as 硬件 LiveKit Bridge
|
||
participant Node as Node LiveKit 桥接服务
|
||
participant Peer as 被叫端/对端
|
||
|
||
Device->>Node: WiFi IP ready 后连接 /ws/device?deviceId=<did>
|
||
User->>Device: 单击唤醒后说“给小红打电话”
|
||
Device->>MainCloud: 主链路语音对话/意图识别
|
||
MainCloud->>Device: MCP tool call: ls.built_in.call_device(toId)
|
||
Device->>MCP: 执行 MCP 工具
|
||
MCP->>Bridge: voice_livekit_bridge_request_call(toId)
|
||
Bridge->>Node: {"type":"device_call","toId":"..."}
|
||
Node->>Peer: incoming_call / invite
|
||
Peer->>Node: accept
|
||
Node->>Device: {"type":"call_start","sampleRate":16000,"channels":1}
|
||
Node-->>Device: 二进制 PCM 下行帧
|
||
Device-->>Node: 二进制 PCM 上行帧
|
||
Device->>Node: 来电中三击 {"type":"device_reject","inviteId":"..."}
|
||
Device->>Node: {"type":"device_hangup"} 或收到 call_end
|
||
```
|
||
|
||
## 新增/修改代码清单
|
||
|
||
### 1. LiveKit WebSocket 与音频桥接
|
||
|
||
文件:`src/category/comm/msgs/voice_livekit_poll_msg.c`
|
||
|
||
这是硬件端通话链路的核心文件,职责包括:
|
||
|
||
- 设备联网后连接 Node 桥接服务。
|
||
- 维护通话状态机。
|
||
- 处理 Node 下发的 JSON 控制消息。
|
||
- 处理 Node 下发的二进制 PCM 音频帧。
|
||
- 从本机麦克风采集 PCM,上行发送给 Node。
|
||
- 处理来电响铃、双击接听、三击拒接、双击挂断。
|
||
- 通话结束后恢复唤醒和主云链路。
|
||
|
||
当前关键配置在文件顶部:
|
||
|
||
```c
|
||
#define LK_BRIDGE_WS_SCHEME "ws"
|
||
#define LK_BRIDGE_WS_HOST "39.108.110.91"
|
||
#define LK_BRIDGE_WS_PORT "3000"
|
||
#define LK_BRIDGE_WS_PATH_PREFIX "/ws/device?deviceId="
|
||
#define LK_BRIDGE_FALLBACK_DEVICE_ID "B"
|
||
#define LK_BRIDGE_DEFAULT_CALL_TARGET "f17c0a1a322ed2ad"
|
||
```
|
||
|
||
这些配置决定硬件端连接哪个 Node LiveKit 桥接服务。当前仍是编译期宏,部署到线上时需要改成目标服务器地址,或后续再改造成配置文件/云端下发。
|
||
|
||
#### 通话状态机
|
||
|
||
```c
|
||
typedef enum {
|
||
LK_CALL_IDLE = 0,
|
||
LK_CALL_RINGING,
|
||
LK_CALL_PREPARING,
|
||
LK_CALL_ACTIVE,
|
||
} lk_call_state_e;
|
||
```
|
||
|
||
含义:
|
||
|
||
- `LK_CALL_IDLE`:空闲状态,可以发起呼叫或接收来电。
|
||
- `LK_CALL_RINGING`:收到来电,设备播放提示音并显示表情,等待双击接听。
|
||
- `LK_CALL_PREPARING`:预留状态,用于播放器准备阶段。
|
||
- `LK_CALL_ACTIVE`:通话中,正在收发 PCM 音频。
|
||
|
||
#### WebSocket 连接
|
||
|
||
初始化入口:
|
||
|
||
```c
|
||
SYS_INIT(voice_livekit_poll_evt_init, SYS_INIT_LEVEL_PRE_APPLICATION, 50);
|
||
```
|
||
|
||
模块初始化时订阅:
|
||
|
||
```c
|
||
voice_msg_sub(VOICE_MSG_WIFI_IP_GOT, bridge_wifi_ip_got, NULL);
|
||
voice_msg_sub(VOICE_MSG_BUTTON_CHANGE, bridge_button_event, NULL);
|
||
```
|
||
|
||
当 WiFi 获取 IP 后,创建 `lk.bridge` 线程,线程会循环连接 Node:
|
||
|
||
```c
|
||
bridge_connect();
|
||
```
|
||
|
||
WebSocket 路径由设备 ID 拼出:
|
||
|
||
```c
|
||
/ws/device?deviceId=<device_did>
|
||
```
|
||
|
||
设备 ID 优先来自:
|
||
|
||
```c
|
||
get_app_datas()->did
|
||
```
|
||
|
||
如果设备 ID 为空,则使用 `LK_BRIDGE_FALLBACK_DEVICE_ID`。
|
||
|
||
#### JSON 控制消息
|
||
|
||
`handle_text_frame()` 负责处理 Node 发来的文本 JSON。
|
||
|
||
目前支持的消息:
|
||
|
||
- `hello`:连接确认。
|
||
- `play_url`:调试用,播放 URL。
|
||
- `stop`:停止播放。
|
||
- `incoming_call`:来电通知,进入响铃状态。
|
||
- `call_start`:通话开始,初始化音频播放和录音。
|
||
- `call_end`:通话结束,停止音频链路。
|
||
- `device_call_ack`:发起呼叫确认。
|
||
- `device_accept_ack`:接听确认。
|
||
- `device_reject_ack`:拒接确认。
|
||
- `device_hangup_ack`:挂断确认。
|
||
|
||
发给 Node 的消息:
|
||
|
||
```json
|
||
{"type":"device_call","toId":"目标设备号"}
|
||
{"type":"device_accept","inviteId":"来电 inviteId"}
|
||
{"type":"device_reject","inviteId":"来电 inviteId"}
|
||
{"type":"device_hangup"}
|
||
```
|
||
|
||
#### 二进制音频协议
|
||
|
||
二进制帧格式:
|
||
|
||
```c
|
||
#define LK_BRIDGE_MAGIC 0xA5
|
||
#define LK_BRIDGE_VERSION 0x01
|
||
#define LK_BRIDGE_HEADER_SIZE 20
|
||
#define LK_BRIDGE_SAMPLE_RATE 16000
|
||
#define LK_BRIDGE_CHANNELS 1
|
||
#define LK_BRIDGE_BITS 16
|
||
```
|
||
|
||
每个音频包:
|
||
|
||
- 前 20 字节:自定义 header。
|
||
- 后面 payload:PCM16 little-endian。
|
||
- 下行方向:Node -> 硬件,`flags = 0x00`。
|
||
- 上行方向:硬件 -> Node,`flags = 0x01`。
|
||
|
||
header 结构:
|
||
|
||
```c
|
||
typedef struct {
|
||
uint8_t magic;
|
||
uint8_t version;
|
||
uint8_t flags;
|
||
uint8_t channels;
|
||
uint32_t sample_rate;
|
||
uint32_t samples_per_channel;
|
||
uint32_t seq;
|
||
uint32_t timestamp;
|
||
} lk_bridge_header_t;
|
||
```
|
||
|
||
#### 下行播放
|
||
|
||
Node 下发的 PCM 二进制帧进入:
|
||
|
||
```c
|
||
handle_binary_frame()
|
||
```
|
||
|
||
处理流程:
|
||
|
||
1. 校验 magic/version/sampleRate/channels。
|
||
2. 确认当前处于 `LK_CALL_ACTIVE`。
|
||
3. 把 PCM payload 放入 `g_pcm_queue`。
|
||
4. `lk.audio.writer` 线程从队列取帧。
|
||
5. 调用 `lisa_audio_play_write()` 写入音频设备。
|
||
|
||
为降低断续感,播放端做了预填充:
|
||
|
||
```c
|
||
#define LK_BRIDGE_AUDIO_START_PREFILL 4
|
||
```
|
||
|
||
队列满时丢弃最旧帧,优先保持实时性,避免旧音频追播。
|
||
|
||
#### 上行录音
|
||
|
||
通话开始时,`start_stream_playback()` 会配置 `audio0`:
|
||
|
||
```c
|
||
lisa_audio_record_config()
|
||
lisa_audio_register_callback()
|
||
lisa_audio_record_start()
|
||
```
|
||
|
||
录音回调:
|
||
|
||
```c
|
||
livekit_audio_record_cb()
|
||
```
|
||
|
||
处理流程:
|
||
|
||
1. 音频设备回调 PCM。
|
||
2. `uplink_accumulate_pcm()` 聚合为 640 字节一帧。
|
||
3. 放入 `g_uplink_queue`。
|
||
4. `lk.audio.uplink` 线程组装 20 字节 header。
|
||
5. 调用 `lisa_ws_send_binary()` 发给 Node。
|
||
|
||
当前上行帧大小:
|
||
|
||
```c
|
||
#define LK_BRIDGE_UPLINK_FRAME_BYTES 640
|
||
```
|
||
|
||
对应 16kHz/16bit/mono 下约 20ms 音频。
|
||
|
||
#### 音频参数
|
||
|
||
当前通话链路使用:
|
||
|
||
```c
|
||
#define LK_BRIDGE_RECORD_ANALOG_GAIN 20
|
||
#define LK_BRIDGE_RECORD_DIGITAL_GAIN 0
|
||
#define LK_BRIDGE_PLAY_DIGITAL_GAIN (-18)
|
||
```
|
||
|
||
含义:
|
||
|
||
- `RECORD_ANALOG_GAIN`:设备麦克风采集增益。
|
||
- `RECORD_DIGITAL_GAIN`:设备麦克风数字增益。
|
||
- `PLAY_DIGITAL_GAIN`:设备播放通话音频的数字增益。
|
||
|
||
之前调试噪音时,主要就是围绕这几个参数和 Node 端音频增益做过调整。当前版本里,硬件端播放数字增益为 `-18`,上行录音模拟增益为 `20`。
|
||
|
||
#### 来电响铃与表情
|
||
|
||
收到 `incoming_call` 后进入 `LK_CALL_RINGING`:
|
||
|
||
```c
|
||
bridge_start_ringing()
|
||
```
|
||
|
||
行为:
|
||
|
||
- 保存 `inviteId` 和 `fromId`。
|
||
- 显示表情:
|
||
|
||
```c
|
||
#define LK_BRIDGE_RING_EMOJI "wait"
|
||
```
|
||
|
||
- 循环播放本地提示音:
|
||
|
||
```c
|
||
player_mgr_play(LOCAL, app_tone_get_url(TONE_ID_94), 0);
|
||
```
|
||
|
||
- 每 2500ms 重复一次:
|
||
|
||
```c
|
||
#define LK_BRIDGE_RING_REPEAT_MS 2500
|
||
```
|
||
|
||
通话接通或结束后显示:
|
||
|
||
```c
|
||
#define LK_BRIDGE_IDLE_EMOJI "neutral"
|
||
```
|
||
|
||
#### 按键交互
|
||
|
||
在 `bridge_button_event()` 中处理 button 0 的双击和三击:
|
||
|
||
```c
|
||
if (evt->action == VOICE_MSG_BUTTON_ACTION_DOUBLE_CLICK) {
|
||
if (g_call_state == LK_CALL_RINGING) {
|
||
bridge_request_accept();
|
||
} else if (g_call_state == LK_CALL_IDLE) {
|
||
// 当前不再用双击主动发起呼叫
|
||
} else {
|
||
bridge_request_hangup();
|
||
}
|
||
} else if (evt->action == VOICE_MSG_BUTTON_ACTION_TRIPLE_CLICK) {
|
||
if (g_call_state == LK_CALL_RINGING) {
|
||
bridge_request_reject();
|
||
}
|
||
}
|
||
```
|
||
|
||
当前设计:
|
||
|
||
- 来电中:双击接听。
|
||
- 来电中:三击拒接。
|
||
- 通话中:双击挂断。
|
||
- 空闲中:双击不主动发起呼叫,发起呼叫改由语音意图/MCP 工具触发。
|
||
- 空闲中:三击不再打开二维码/信息页,避免和来电拒接冲突。
|
||
|
||
### 2. MCP 通话工具
|
||
|
||
文件:`apps/arcs-mini/mcp-tools/mcp_tool_call_device.c`
|
||
|
||
这个文件新增 MCP 工具:
|
||
|
||
```c
|
||
MCP_TOOL_DEFINE(ls.built_in.call_device, call_device_list, call_device_call);
|
||
```
|
||
|
||
工具名:
|
||
|
||
```text
|
||
ls.built_in.call_device
|
||
```
|
||
|
||
入参:
|
||
|
||
```json
|
||
{
|
||
"toId": "目标设备号"
|
||
}
|
||
```
|
||
|
||
执行逻辑:
|
||
|
||
```c
|
||
voice_livekit_bridge_request_call(to_id);
|
||
```
|
||
|
||
也就是说,主链路意图识别到“打电话”后,只要返回 MCP tool call,硬件端就会通过该工具把目标设备号交给 LiveKit 桥接模块。
|
||
|
||
返回结果:
|
||
|
||
```json
|
||
{
|
||
"content": "呼叫已发起",
|
||
"ret": 0
|
||
}
|
||
```
|
||
|
||
如果失败则返回:
|
||
|
||
```json
|
||
{
|
||
"content": "呼叫发起失败",
|
||
"ret": -1
|
||
}
|
||
```
|
||
|
||
### 3. MCP 工具注册
|
||
|
||
文件:`apps/arcs-mini/mcp-tools/CMakeLists.txt`
|
||
|
||
新增编译文件:
|
||
|
||
```cmake
|
||
mcp_tool_call_device.c
|
||
```
|
||
|
||
这一步确保 `ls.built_in.call_device` 会进入固件,主链路才能调用到该工具。
|
||
|
||
### 4. LiveKit 事件模块注册
|
||
|
||
文件:`src/category/comm/msgs/CMakeLists.txt`
|
||
|
||
新增编译文件:
|
||
|
||
```cmake
|
||
voice_livekit_poll_msg.c
|
||
```
|
||
|
||
这一步确保 LiveKit WebSocket/音频桥接模块会进入固件,并通过 `SYS_INIT` 自动初始化。
|
||
|
||
### 5. 主按键逻辑调整
|
||
|
||
文件:`apps/arcs-mini/main.c`
|
||
|
||
主按键处理里,双击逻辑被调整为保留给 LiveKit 通话,不再触发原先可能冲突的行为:
|
||
|
||
```c
|
||
case VOICE_MSG_BUTTON_ACTION_DOUBLE_CLICK:
|
||
{
|
||
LISA_LOGI(TAG, "power button double click, reserved for livekit call");
|
||
break;
|
||
}
|
||
```
|
||
|
||
实际通话状态下的双击接听/挂断由 `voice_livekit_poll_msg.c` 中订阅的 `bridge_button_event()` 处理。
|
||
|
||
三击逻辑也被调整为保留给 LiveKit 来电拒接,主按键处理不再打开信息/二维码页:
|
||
|
||
```c
|
||
case VOICE_MSG_BUTTON_ACTION_TRIPLE_CLICK:
|
||
{
|
||
LISA_LOGI(TAG, "power button triple click, reserved for livekit reject");
|
||
break;
|
||
}
|
||
```
|
||
|
||
这样做的目的:
|
||
|
||
- 避免双击同时触发拍照识图、唤醒或其他主链路行为。
|
||
- 避免来电中三击拒接后又打开二维码/信息页。
|
||
- 让通话状态机拥有双击接听、三击拒接、双击挂断的优先处理权。
|
||
|
||
## 发起呼叫流程
|
||
|
||
### 语音发起
|
||
|
||
1. 用户单击设备按钮唤醒。
|
||
2. 用户说“给小红打电话”。
|
||
3. 主链路云服务识别为通话意图。
|
||
4. 主链路根据联系人/设备映射得到目标 `toId`。
|
||
5. 主链路返回 MCP 调用:
|
||
|
||
```json
|
||
{
|
||
"name": "ls.built_in.call_device",
|
||
"arguments": {
|
||
"toId": "目标设备号"
|
||
}
|
||
}
|
||
```
|
||
|
||
6. 硬件端执行 `mcp_tool_call_device.c`。
|
||
7. 调用 `voice_livekit_bridge_request_call(toId)`。
|
||
8. 硬件端通过 WebSocket 发:
|
||
|
||
```json
|
||
{"type":"device_call","toId":"目标设备号"}
|
||
```
|
||
|
||
9. Node 创建 invite,通知被叫端。
|
||
|
||
### 代码入口
|
||
|
||
```text
|
||
apps/arcs-mini/mcp-tools/mcp_tool_call_device.c
|
||
-> voice_livekit_bridge_request_call(toId)
|
||
src/category/comm/msgs/voice_livekit_poll_msg.c
|
||
-> lisa_ws_send_text({"type":"device_call","toId":"..."})
|
||
```
|
||
|
||
## 接听流程
|
||
|
||
1. Node 给硬件端发送:
|
||
|
||
```json
|
||
{
|
||
"type": "incoming_call",
|
||
"inviteId": "...",
|
||
"fromId": "..."
|
||
}
|
||
```
|
||
|
||
2. 硬件端进入 `LK_CALL_RINGING`。
|
||
3. 设备循环播放提示音并显示 `wait` 表情。
|
||
4. 用户双击按钮。
|
||
5. 硬件端发送:
|
||
|
||
```json
|
||
{"type":"device_accept","inviteId":"..."}
|
||
```
|
||
|
||
6. Node 完成接听,双方加入 LiveKit 房间。
|
||
7. Node 下发:
|
||
|
||
```json
|
||
{"type":"call_start","sampleRate":16000,"channels":1}
|
||
```
|
||
|
||
8. 硬件端启动录音和播放,进入 `LK_CALL_ACTIVE`。
|
||
|
||
## 拒接流程
|
||
|
||
来电中三击按钮:
|
||
|
||
```json
|
||
{"type":"device_reject","inviteId":"..."}
|
||
```
|
||
|
||
Node 收到后:
|
||
|
||
1. 校验 invite 仍为 `pending`。
|
||
2. 将 invite 状态更新为 `rejected`。
|
||
3. 回包给设备:
|
||
|
||
```json
|
||
{"type":"device_reject_ack","inviteId":"...","status":"rejected"}
|
||
```
|
||
|
||
4. 通知主叫端 `call_end`,reason 为 `rejected`。
|
||
|
||
硬件端发送拒接后会立即:
|
||
|
||
- 停止本地来电提示音。
|
||
- 清空当前 pending invite。
|
||
- 状态回到 `LK_CALL_IDLE`。
|
||
- 显示空闲表情。
|
||
|
||
## 挂断流程
|
||
|
||
### 设备主动挂断
|
||
|
||
通话中双击按钮:
|
||
|
||
```json
|
||
{"type":"device_hangup"}
|
||
```
|
||
|
||
Node 结束 invite,并通知双方 `call_end`。
|
||
|
||
### 对端挂断
|
||
|
||
Node 下发:
|
||
|
||
```json
|
||
{"type":"call_end","reason":"..."}
|
||
```
|
||
|
||
硬件端执行:
|
||
|
||
```c
|
||
stop_stream_playback();
|
||
```
|
||
|
||
然后:
|
||
|
||
- 停止播放。
|
||
- 停止录音。
|
||
- 清空上下行队列。
|
||
- 关闭 PA。
|
||
- 恢复唤醒。
|
||
- 请求主云链路重连。
|
||
|
||
对应代码:
|
||
|
||
```c
|
||
app_wakeup_start();
|
||
voice_cloud_reconnect_async();
|
||
```
|
||
|
||
## 与 Node LiveKit 桥接服务的协议约定
|
||
|
||
硬件端连接:
|
||
|
||
```text
|
||
ws://<LK_BRIDGE_WS_HOST>:<LK_BRIDGE_WS_PORT>/ws/device?deviceId=<deviceId>
|
||
```
|
||
|
||
文本消息使用 JSON。
|
||
|
||
音频消息使用二进制帧:
|
||
|
||
```text
|
||
20 字节 header + PCM16 LE payload
|
||
```
|
||
|
||
当前固定音频格式:
|
||
|
||
```text
|
||
sampleRate: 16000
|
||
channels: 1
|
||
bits: 16
|
||
frame: 20ms 左右
|
||
```
|
||
|
||
如果 Node 下发音频格式不一致,硬件端会丢帧并打印 `format mismatch`。
|
||
|
||
## 与主链路的关系
|
||
|
||
这次硬件端新增的是“工具执行能力”和“实时通话音频能力”,而不是在硬件端自己做联系人识别。
|
||
|
||
联系人识别/目标设备号选择由主链路完成:
|
||
|
||
- App/后台维护联系人别名与设备号映射。
|
||
- 主链路意图识别根据用户语音得到目标 `toId`。
|
||
- 硬件端只接受 `toId` 并发起呼叫。
|
||
|
||
所以硬件端不关心“小红”“爸爸”“卧室设备”这些自然语言名称,它只关心最终的设备标识。
|
||
|
||
## 调试日志关键字
|
||
|
||
设备端常用日志关键字:
|
||
|
||
```text
|
||
voice.app.lkbridge
|
||
bridge ws connect
|
||
bridge ws: connected
|
||
incoming call ringing
|
||
device call requested
|
||
device accept requested
|
||
device reject requested
|
||
device_reject_ack
|
||
device hangup requested
|
||
call_start
|
||
first pcm queued
|
||
writer stats
|
||
first uplink pcm sent
|
||
uplink stats
|
||
direct audio playback stopped
|
||
```
|
||
|
||
Node 端常用日志关键字:
|
||
|
||
```text
|
||
[bridge.ws] device=<id> connected
|
||
[bridge.call] device invite created
|
||
[bridge] incoming_call
|
||
[bridge.ws] device=<id> text: { type: 'device_reject', ... }
|
||
[bridge.call] invite=<id> ... pending->rejected by=<deviceId>
|
||
[call.accept]
|
||
[bridge] joined room
|
||
[bridge.uplink] audio stats
|
||
[bridge.ws] audio stats
|
||
```
|
||
|
||
## 当前限制与后续优化点
|
||
|
||
### 1. Node 地址仍是硬编码
|
||
|
||
当前硬件端地址在 `voice_livekit_poll_msg.c` 顶部宏里:
|
||
|
||
```c
|
||
#define LK_BRIDGE_WS_HOST "39.108.110.91"
|
||
#define LK_BRIDGE_WS_PORT "3000"
|
||
```
|
||
|
||
后续如果要更适合测试/生产切换,可以改为:
|
||
|
||
- 配置文件读取。
|
||
- BLE/云端配置下发。
|
||
- 编译参数注入。
|
||
|
||
### 2. 默认目标仍有兜底值
|
||
|
||
```c
|
||
#define LK_BRIDGE_DEFAULT_CALL_TARGET "f17c0a1a322ed2ad"
|
||
```
|
||
|
||
正常语音发起通话时,目标应该由 MCP 入参 `toId` 提供;默认目标只是防御性兜底。
|
||
|
||
### 3. 来电拒接已接入,但拒接入口固定为三击
|
||
|
||
当前来电中支持:
|
||
|
||
- 双击接听。
|
||
- 三击拒接。
|
||
- 对端取消或超时后 Node 下发结束。
|
||
|
||
为避免来电拒接后继续打开二维码页,主按键里的三击打开信息/二维码页逻辑已经移除。后续如果还需要二维码入口,建议放到设置菜单、五击/六击/七击组合,或由语音/页面触发,而不要复用三击。
|
||
|
||
### 4. 音频参数仍需要按设备环境微调
|
||
|
||
当前播放/录音参数已经经过一轮调试,但不同硬件、外壳、麦克风距离、扬声器音量下仍可能需要调整:
|
||
|
||
```c
|
||
LK_BRIDGE_RECORD_ANALOG_GAIN
|
||
LK_BRIDGE_RECORD_DIGITAL_GAIN
|
||
LK_BRIDGE_PLAY_DIGITAL_GAIN
|
||
LK_BRIDGE_AUDIO_BUFFER_COUNT
|
||
LK_BRIDGE_AUDIO_BUFFER_SAMPLES
|
||
LK_BRIDGE_AUDIO_START_PREFILL
|
||
```
|
||
|
||
### 5. 与主链路音频资源互斥
|
||
|
||
通话开始时会停止唤醒/录音/播放相关共享音频路径:
|
||
|
||
```c
|
||
app_wakeup_stop();
|
||
lisa_audio_record_stop();
|
||
lisa_audio_play_stop();
|
||
```
|
||
|
||
通话结束后再恢复:
|
||
|
||
```c
|
||
app_wakeup_start();
|
||
voice_cloud_reconnect_async();
|
||
```
|
||
|
||
这是为了避免主链路 ASR/TTS 和通话链路同时占用 `audio0` 导致噪声、卡顿或录音失败。
|