# 我们如何检测"用户说完话了"

## 核心：TEN VAD (Voice Activity Detection)

### VAD 是什么？

**VAD = 语音活动检测** - 判断当前音频帧是"人说话"还是"静音"

---

## 代码解析

### 1. 初始化 TEN VAD
```python
# agent.py 第 144-161 行
class TenVAD:
    def __init__(self):
        self.threshold = float(config.vad.get("threshold", 0.3))  # 阈值
        self.min_speech_ms = float(config.vad.get("min_speech_ms", 300))      # 最短语音 300ms
        self.min_silence_ms = float(config.vad.get("min_silence_ms", 500))    # 最短静音 500ms
        self._vad = None
        self._is_speaking = False  # 当前是否在说话
        self._buffer = bytearray()  # 缓冲音频数据
    
    async def initialize(self):
        import ten_vad
        # 创建 VAD 实例：每帧 256 samples (16ms@16kHz), 阈值 0.3
        self._vad = ten_vad.TenVad(hop_size=256, threshold=self.threshold)
```

---

### 2. 处理每一帧音频
```python
# agent.py 第 163-196 行
def process_frame(self, audio_data: bytes) -> Optional[bool]:
    # 1. 取一帧 (256 samples = 16ms @ 16kHz)
    audio_np = np.frombuffer(audio_data, dtype=np.int16)[:256]
    
    # 2. VAD 判断：这一帧是语音还是静音？
    score, is_speech = self._vad.process(audio_np)
    
    now = time.time() * 1000  # 当前时间 (毫秒)
    
    if is_speech:
        # ─── 检测到语音 ───
        if not self._is_speaking:
            # 刚开始说话
            self._is_speaking = True
            self._speech_start_time = now
            self._buffer = bytearray()  # 清空旧数据
            return True  # 通知上层：用户开始说话
        
        # 正在说话，收集音频
        self._buffer.extend(audio_data)
        self._silence_start_time = 0
        
    else:
        # ─── 检测到静音 ───
        if self._is_speaking:
            # 之前一直在说话，现在停了
            
            # 情况 1：说话太短 (<300ms)，不算
            if now - self._speech_start_time < self.min_speech_ms:
                self._is_speaking = False
                self._buffer = bytearray()
                return False
            
            # 情况 2：记录静音开始时间
            if self._silence_start_time == 0:
                self._silence_start_time = now
            
            # 情况 3：静音超过 500ms → 用户说完了！
            if now - self._silence_start_time >= self.min_silence_ms:
                self._is_speaking = False
                return False  # 通知上层：用户说完话了
    
    return None  # 继续监听
```

---

### 3. 主循环使用 VAD
```python
# agent.py 第 498-512 行
async def process_audio_frame(self, audio_data: bytes):
    # 1. 交给 VAD 判断
    vad_result = self.vad.process_frame(audio_data)
    
    if vad_result is True:
        # ─── 用户开始说话 ───
        if self._state == AgentState.SPEAKING:
            # AI 正在说话时用户插嘴 → 打断
            asyncio.create_task(self._handle_barge_in())
        elif self._state == AgentState.IDLE:
            # AI 没在说话，进入监听状态
            await self.set_state(AgentState.LISTENING)
    
    elif vad_result is False:
        # ─── 用户说完话了 ───
        if self._state == AgentState.LISTENING:
            # 获取缓冲的完整音频
            audio_buffer = self.vad.get_buffer()
            if len(audio_buffer) > 3200:  # 至少 200ms
                await self._handle_speech(audio_buffer)
```

---

## 流程图

```
音频输入 (16kHz)
    │
    ▼
┌─────────────────────┐
│  每 16ms 一帧 (256   │
│     samples)        │
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐
│  TEN VAD 判断:      │
│  is_speech = True?  │
└─────────┬───────────┘
          │
     ┌────┴────┐
     ▼         ▼
   True      False
     │         │
     ▼         ▼
┌────────┐  ┌──────────────┐
│开始说话│  │之前说话？    │
│收集音频│  └──────┬───────┘
└────────┘         │
              ┌────┴────┐
              ▼         ▼
           是         否
              │         │
              ▼         ▼
        ┌────────┐  ┌────────┐
        │说话太短│  │记录静音│
        │(<300ms)│  │开始时间│
        └────────┘  └───┬────┘
                        │
                        ▼
                  ┌──────────────┐
                  │静音 > 500ms? │
                  └──────┬───────┘
                       ┌─┴─┐
                      是  否
                       │   │
                       ▼   ▼
                  ┌────────┐  ┌────────┐
                  │✅ 用户 │  │继续监听│
                  │说完了！│  │       │
                  └────────┘  └────────┘
```

---

## 实际效果

### 配置参数
```yaml
# config.yaml
vad:
  threshold: 0.3      # VAD 敏感度 (0-1, 越高越不敏感)
  min_speech_ms: 300  # 最短语音 300ms (防止误触发)
  min_silence_ms: 500 # 最短静音 500ms (判断"说完")
```

### 性能数据
| 指标 | 值 |
|------|-----|
| 帧延迟 | **0.234ms** (TEN VAD) |
| Silero VAD | 0.337ms (慢 30%) |
| 帧粒度 | 256 samples = 16ms |
| 库大小 | 306KB (vs Silero 2.2MB) |

---

## 总结

**我们检测"用户说完话了"的逻辑：**

1. **每 16ms** 检测一帧音频
2. **VAD 判断** 这帧是语音还是静音
3. **收集音频** 直到检测到静音
4. **静音超过 500ms** → 判定"用户说完话了"
5. **触发处理** → STT → LLM → TTS

**FastRTC 的方式：**
- 同样用 VAD (但用的是 Silero，慢 30%)
- 底层逻辑相同，只是封装成 ReplyOnPause

**我们的优势：**
- ✅ TEN VAD 比 Silero 快 30%
- ✅ 可自定义阈值
- ✅ 可微调灵敏度
