2026-06-12 15:36:35 +08:00
|
|
|
|
---
|
2026-06-12 15:55:55 +08:00
|
|
|
|
tags: [技术选型, 数据库, PostgreSQL, 持久化, 前端, 边缘推理, 架构设计]
|
2026-06-12 15:36:35 +08:00
|
|
|
|
create time: 2026-06-12 15:33
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-06-12 15:55:55 +08:00
|
|
|
|
# 技术选型
|
2026-06-12 15:36:35 +08:00
|
|
|
|
|
|
|
|
|
|
## 概述
|
|
|
|
|
|
|
2026-06-12 15:55:55 +08:00
|
|
|
|
本文档是 [[项目架构与技术栈]] 的补充阅读——记录项目中各项技术的**选型过程、替代方案对比和决策理由**。技术选型没有"绝对正确",只有"更适合",所以每个选型都会列出候选方案和取舍逻辑,方便后续回顾和复盘。
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
graph TD
|
|
|
|
|
|
A["技术选型"] --> B["持久化层"]
|
|
|
|
|
|
A --> C["前端边缘处理层"]
|
|
|
|
|
|
B --> B1["数据库选型: PostgreSQL"]
|
|
|
|
|
|
C --> C1["边缘推理: ONNX Runtime Web"]
|
|
|
|
|
|
C --> C2["语音检测: @ricky0123/vad-web"]
|
|
|
|
|
|
C --> C3["媒体采集: MediaDevices API"]
|
|
|
|
|
|
```
|
2026-06-12 15:36:35 +08:00
|
|
|
|
|
|
|
|
|
|
> [!info] 定位
|
2026-06-12 15:55:55 +08:00
|
|
|
|
> 持久化部分是**拓展选型文档**,不阻塞 MVP 开发。MVP 阶段用 Redis 做会话存储即可;当产品需要"历史可查、成本可算"时,再引入持久化方案。前端边缘处理部分则是 MVP 阶段就需要确定的技术栈。
|
2026-06-12 15:36:35 +08:00
|
|
|
|
|
|
|
|
|
|
## 正文
|
|
|
|
|
|
|
|
|
|
|
|
### 项目的数据特征
|
|
|
|
|
|
|
|
|
|
|
|
选数据库之前,先搞清楚我们的数据长什么样:
|
|
|
|
|
|
|
|
|
|
|
|
| 数据 | 结构特征 | 读写模式 | 数据量级 |
|
|
|
|
|
|
|------|---------|---------|---------|
|
|
|
|
|
|
| 对话消息 | 强结构化(角色/内容/时间/关联图像) | 写多读少,按会话聚合读取 | 中(每用户日均 ~100 条) |
|
|
|
|
|
|
| 会话元信息 | 强结构化(用户/时间/状态) | 写少读少 | 低 |
|
|
|
|
|
|
| 对话上下文 | 半结构化(JSON 数组,含图像引用) | 高频读写,TTL 过期 | 低(仅当前窗口) |
|
|
|
|
|
|
| 用量统计 | 强结构化(数字/日期/聚合) | 写多,定期聚合读 | 低(日粒度汇总后很小) |
|
|
|
|
|
|
| 用户偏好 | 强结构化(KV 配置) | 写极少读少 | 极低 |
|
|
|
|
|
|
| 关键帧图像 | 非结构化二进制 | 写少,按需读 | 大(单张 100KB~1MB) |
|
|
|
|
|
|
|
|
|
|
|
|
> [!question] 思考
|
|
|
|
|
|
> 从上表可以看出,核心数据(对话、会话、统计)都是**强结构化**的,有明确的字段和关联关系。这意味着关系型数据库天然适配。但"对话上下文"是半结构化 JSON,这就需要数据库对 JSON 有良好支持。
|
|
|
|
|
|
|
|
|
|
|
|
### 候选方案全景对比
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
graph TD
|
|
|
|
|
|
A["持久化技术选型"] --> B["关系型数据库"]
|
|
|
|
|
|
A --> C["文档型数据库"]
|
|
|
|
|
|
A --> D["嵌入式数据库"]
|
|
|
|
|
|
B --> B1["PostgreSQL"]
|
|
|
|
|
|
B --> B2["MySQL"]
|
|
|
|
|
|
B --> B3["TiDB"]
|
|
|
|
|
|
C --> C1["MongoDB"]
|
|
|
|
|
|
D --> D1["SQLite"]
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
| 维度 | PostgreSQL | MySQL | SQLite | MongoDB | TiDB |
|
|
|
|
|
|
|------|-----------|-------|--------|---------|------|
|
|
|
|
|
|
| **数据模型** | 关系型 + JSONB | 关系型 | 关系型(嵌入式) | 文档型(BSON) | 关系型(分布式) |
|
|
|
|
|
|
| **JSON 支持** | JSONB 原生索引 | JSON 类型,索引弱 | 无原生支持 | 天生擅长 | 兼容 MySQL JSON |
|
|
|
|
|
|
| **关联查询** | 强 | 强 | 强 | 弱(需嵌套/lookup) | 强 |
|
|
|
|
|
|
| **聚合统计** | 窗口函数/CTE 完善 | 基础聚合 | 基础聚合 | 聚合管道 | 强 |
|
|
|
|
|
|
| **并发能力** | 高(MVCC) | 中 | 低(单写锁) | 高 | 极高(分布式) |
|
|
|
|
|
|
| **运维复杂度** | 中 | 低 | 极低 | 中 | 高 |
|
|
|
|
|
|
| **Go 生态** | pgx / GORM | go-sql-driver | go-sqlite3 | mongo-go-driver | 兼容 MySQL 驱动 |
|
|
|
|
|
|
| **部署方式** | Docker / 云服务 | Docker / 云服务 | 单文件嵌入 | Docker / Atlas | 集群部署 |
|
|
|
|
|
|
| **成本** | 开源免费 | 开源免费 | 开源免费 | 社区版免费 | 开源免费 |
|
|
|
|
|
|
|
|
|
|
|
|
### 逐个分析:为什么不选它们?
|
|
|
|
|
|
|
|
|
|
|
|
#### SQLite —— 太轻了
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
graph LR
|
|
|
|
|
|
A["SQLite"] --> B["单文件数据库"]
|
|
|
|
|
|
B --> C{"适合本项目?"}
|
|
|
|
|
|
C -->|"否"| D["写并发受限"]
|
|
|
|
|
|
C -->|"否"| E["无法多实例共享"]
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
- **优点**:零配置,一个 `.db` 文件搞定,开发阶段极方便
|
|
|
|
|
|
- **致命问题**:**写锁是全局的**——同一时刻只能有一个写操作。当 WebSocket 并发写入对话消息时,会频繁锁等待
|
|
|
|
|
|
- **另一个问题**:多个 Go Gateway 实例无法共享同一个 SQLite 文件(除非用 NFS,但性能极差)
|
|
|
|
|
|
|
|
|
|
|
|
> [!tip] 什么时候选 SQLite?
|
|
|
|
|
|
> 如果是**单机部署的桌面应用或 CLI 工具**,SQLite 是最佳选择。但我们的项目是 Web 服务、多实例部署,不合适。
|
|
|
|
|
|
|
|
|
|
|
|
#### MySQL —— 能用,但 JSON 处理弱
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
graph LR
|
|
|
|
|
|
A["MySQL"] --> B["JSON 支持"]
|
|
|
|
|
|
B --> C{"够用吗?"}
|
|
|
|
|
|
C -->|"勉强"| D["JSON 索引弱"]
|
|
|
|
|
|
C -->|"否"| E["无 JSONB 二进制存储"]
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
- **优点**:运维简单,社区庞大,很多团队更熟悉
|
|
|
|
|
|
- **本项目的痛点**:对话上下文是 JSON 数组(含图像引用、角色标记),MySQL 的 JSON 类型支持**索引能力弱**,无法对 JSON 内部字段高效查询
|
|
|
|
|
|
- **另一个痛点**:缺少 `gen_random_uuid()` 等原生函数,需要应用层生成 UUID
|
|
|
|
|
|
|
|
|
|
|
|
> [!question] 思考
|
|
|
|
|
|
> 如果团队只熟悉 MySQL,能不能用?**完全可以**。JSON 上的差异在 MVP 阶段几乎无感,只是后续做复杂查询(如"找出所有包含某关键词的对话")时会比 PostgreSQL 麻烦一些。
|
|
|
|
|
|
|
|
|
|
|
|
#### MongoDB —— 文档型,关联查询弱
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
graph LR
|
|
|
|
|
|
A["MongoDB"] --> B["天然 JSON 存储"]
|
|
|
|
|
|
B --> C{"适合本项目?"}
|
|
|
|
|
|
C -->|"否"| D["关联查询弱"]
|
|
|
|
|
|
C -->|"否"| E["聚合统计不如 SQL 直观"]
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
- **优点**:Schema-less,存 JSON 天然舒适,水平扩展能力强
|
|
|
|
|
|
- **本项目的痛点**:
|
|
|
|
|
|
- `messages` 需要按 `session_id` 关联 `sessions`,再按 `user_id` 聚合——这在 MongoDB 中要用 `$lookup`,写法复杂且性能不如 SQL JOIN
|
|
|
|
|
|
- 用量统计的"按天聚合"用 SQL 的 `GROUP BY + SUM` 一句话搞定,MongoDB 的聚合管道代码量多 3-5 倍
|
|
|
|
|
|
|
|
|
|
|
|
> [!info] 什么时候选 MongoDB?
|
|
|
|
|
|
> 如果数据模型是**高度嵌套的文档**(如博客系统、CMS),且很少跨文档关联查询,MongoDB 是好选择。我们的数据关联性强,不太适合。
|
|
|
|
|
|
|
|
|
|
|
|
#### TiDB —— 杀鸡用牛刀
|
|
|
|
|
|
|
|
|
|
|
|
- **优点**:兼容 MySQL 协议,分布式架构,水平扩展无上限
|
|
|
|
|
|
- **本项目的痛点**:部署复杂(至少 3 个 PD + 3 个 TiKV + 2 个 TiDB),运维成本高
|
|
|
|
|
|
- **结论**:单机 PostgreSQL 完全够用,引入 TiDB 纯属过度设计
|
|
|
|
|
|
|
|
|
|
|
|
> [!tip] 什么时候选 TiDB?
|
|
|
|
|
|
> 数据量过亿、需要跨地域部署、单机 PostgreSQL 已经扛不住时。对于本项目,短期内不会遇到这个瓶颈。
|
|
|
|
|
|
|
|
|
|
|
|
### 为什么选 PostgreSQL?
|
|
|
|
|
|
|
|
|
|
|
|
综合以上分析,PostgreSQL 在本项目的核心需求上**全面契合**:
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
graph TD
|
|
|
|
|
|
A["项目需求"] --> B["强结构化数据"]
|
|
|
|
|
|
A --> C["JSON 半结构化"]
|
|
|
|
|
|
A --> D["关联查询"]
|
|
|
|
|
|
A --> E["聚合统计"]
|
|
|
|
|
|
A --> F["Go 生态"]
|
|
|
|
|
|
|
|
|
|
|
|
B --> PG["PostgreSQL"]
|
|
|
|
|
|
C --> PG
|
|
|
|
|
|
D --> PG
|
|
|
|
|
|
E --> PG
|
|
|
|
|
|
F --> PG
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
逐条对应:
|
|
|
|
|
|
|
|
|
|
|
|
| 项目需求 | PostgreSQL 的匹配点 |
|
|
|
|
|
|
|---------|-------------------|
|
|
|
|
|
|
| 对话历史是强结构化数据 | 原生关系型,SQL 标准完备 |
|
|
|
|
|
|
| 对话上下文含 JSON(图像引用/角色标记) | **JSONB** 类型支持索引、路径查询、部分更新 |
|
|
|
|
|
|
| messages ↔ sessions 外键关联 | 完整的 FK 约束 + JOIN 支持 |
|
|
|
|
|
|
| 用量按天/周/月聚合 | 窗口函数、CTE、`DATE_TRUNC` 等分析能力 |
|
|
|
|
|
|
| Go 后端对接 | `pgx` 驱动性能优秀,GORM/Ent ORM 支持成熟 |
|
|
|
|
|
|
| 后续可能存图像元信息 | 可结合对象存储,PG 只存引用路径 |
|
|
|
|
|
|
| 未来可能加全文搜索 | 内置 `tsvector` 全文检索,无需额外引入 Elasticsearch |
|
|
|
|
|
|
|
|
|
|
|
|
### Go 后端集成示例
|
|
|
|
|
|
|
|
|
|
|
|
使用 `pgx` 驱动连接 PostgreSQL:
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
import "github.com/jackc/pgx/v5/pgxpool"
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化连接池
|
|
|
|
|
|
pool, _ := pgxpool.New(ctx, "postgres://user:pass@localhost:5432/vision_ai")
|
|
|
|
|
|
|
|
|
|
|
|
// 保存一条对话消息
|
|
|
|
|
|
func SaveMessage(ctx context.Context, pool *pgxpool.Pool, msg *Message) error {
|
|
|
|
|
|
_, err := pool.Exec(ctx,
|
|
|
|
|
|
`INSERT INTO messages (session_id, role, content, image_url, tokens_used)
|
|
|
|
|
|
VALUES ($1, $2, $3, $4, $5)`,
|
|
|
|
|
|
msg.SessionID, msg.Role, msg.Content, msg.ImageURL, msg.TokensUsed,
|
|
|
|
|
|
)
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查询用户近 7 天用量汇总
|
|
|
|
|
|
func GetWeeklyUsage(ctx context.Context, pool *pgxpool.Pool, userID string) ([]UsageRow, error) {
|
|
|
|
|
|
rows, _ := pool.Query(ctx,
|
|
|
|
|
|
`SELECT date, llm_tokens, estimated_cost
|
|
|
|
|
|
FROM usage_daily
|
|
|
|
|
|
WHERE user_id = $1 AND date >= CURRENT_DATE - INTERVAL '7 days'
|
|
|
|
|
|
ORDER BY date`, userID)
|
|
|
|
|
|
defer rows.Close()
|
|
|
|
|
|
// ... scan rows
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-12 16:05:34 +08:00
|
|
|
|
JSONB 包容查询示例——精确匹配 JSONB 子结构:
|
2026-06-12 15:36:35 +08:00
|
|
|
|
|
|
|
|
|
|
```sql
|
2026-06-12 16:05:34 +08:00
|
|
|
|
-- @> 是包容操作符,检查 content 是否包含 {"text": "花"} 这个子结构
|
|
|
|
|
|
-- 适合"字段精确匹配"场景;若需模糊关键词搜索,应使用 tsvector 全文检索
|
2026-06-12 15:36:35 +08:00
|
|
|
|
SELECT id, content, created_at
|
|
|
|
|
|
FROM messages
|
|
|
|
|
|
WHERE role = 'user'
|
|
|
|
|
|
AND content @> '{"text": "花"}'
|
|
|
|
|
|
ORDER BY created_at DESC
|
|
|
|
|
|
LIMIT 20;
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 选型决策流程图
|
|
|
|
|
|
|
|
|
|
|
|
遇到新项目时,可以按这个流程快速决策:
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
flowchart TD
|
|
|
|
|
|
START["需要持久化?"] -->|"否"| REDIS["继续用 Redis"]
|
|
|
|
|
|
START -->|"是"| STRUCT{"数据强结构化?"}
|
|
|
|
|
|
STRUCT -->|"是"| SCALE{"数据量级?"}
|
|
|
|
|
|
STRUCT -->|"否, 高度嵌套"| MONGO["考虑 MongoDB"]
|
|
|
|
|
|
SCALE -->|"< 100GB, 单机可扛"| PG["PostgreSQL"]
|
|
|
|
|
|
SCALE -->|"海量, 需水平扩展"| TIDB["考虑 TiDB / CockroachDB"]
|
|
|
|
|
|
SCALE -->|"极小, 单文件即可"| SQLITE["考虑 SQLite"]
|
|
|
|
|
|
PG --> JSON{"有 JSON 需求?"}
|
|
|
|
|
|
JSON -->|"是"| PG_OK["PostgreSQL (JSONB)"]
|
|
|
|
|
|
JSON -->|"否"| MYSQL{"团队熟悉 MySQL?"}
|
|
|
|
|
|
MYSQL -->|"是"| MYSQL_OK["MySQL 也行"]
|
|
|
|
|
|
MYSQL -->|"否"| PG_OK
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
> [!question] 思考
|
|
|
|
|
|
> 技术选型没有"绝对正确",只有"更适合"。PostgreSQL 在本项目中胜出,核心原因是**数据模型匹配 + JSONB 能力 + Go 生态成熟**这三点的交集。如果换一个纯 KV 场景(如缓存),Redis 才是正确答案。
|
|
|
|
|
|
|
|
|
|
|
|
### 与现有架构的整合
|
|
|
|
|
|
|
|
|
|
|
|
引入 PostgreSQL 后,存储层变为**冷热分离**架构:
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
graph LR
|
|
|
|
|
|
subgraph Hot["热数据层"]
|
|
|
|
|
|
REDIS["Redis"]
|
|
|
|
|
|
end
|
|
|
|
|
|
subgraph Cold["冷数据层"]
|
|
|
|
|
|
PG["PostgreSQL"]
|
|
|
|
|
|
end
|
|
|
|
|
|
subgraph App["Go Gateway"]
|
|
|
|
|
|
WRITE["写入路径"]
|
|
|
|
|
|
READ["读取路径"]
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
WRITE -->|"实时会话状态"| REDIS
|
|
|
|
|
|
WRITE -->|"对话历史 + 用量"| PG
|
|
|
|
|
|
READ -->|"当前上下文(快)"| REDIS
|
|
|
|
|
|
READ -->|"历史记录(慢)"| PG
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
> [!info] 写入策略
|
|
|
|
|
|
> 建议采用**异步写入**——实时对话消息先写 Redis(快),然后异步批量刷入 PostgreSQL(慢)。这样不会因为数据库写入延迟影响对话体验。可以用 Go channel + goroutine 实现简单的异步写入队列。
|
|
|
|
|
|
|
2026-06-12 15:55:55 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 前端边缘处理层选型
|
|
|
|
|
|
|
|
|
|
|
|
> [!info] 选型背景
|
|
|
|
|
|
> 项目的核心交互流程是"用户说话 → AI 看 → AI 回答"。前端需要完成**媒体采集、语音检测、轻量推理**三件事,然后才把"值得处理的数据"发给后端。这三个环节的技术选型直接影响**交互延迟和首屏加载速度**。
|
|
|
|
|
|
|
|
|
|
|
|
### 总览
|
|
|
|
|
|
|
|
|
|
|
|
| 能力 | 当前选型 | 选择理由 |
|
|
|
|
|
|
|------|---------|---------|
|
|
|
|
|
|
| 边缘推理 | **ONNX Runtime Web** | 通用推理引擎,模型无关,WASM 加速 |
|
|
|
|
|
|
| 语音检测 | **@ricky0123/vad-web** | 包装原生 WebRTC VAD,零延迟,体积极小 |
|
|
|
|
|
|
| 媒体采集 | **MediaDevices API** | 浏览器原生接口,无中间层,零依赖 |
|
|
|
|
|
|
|
|
|
|
|
|
### 边缘推理:ONNX Runtime Web
|
|
|
|
|
|
|
|
|
|
|
|
在浏览器端跑 VAD 和关键帧检测,需要一个轻量推理引擎。候选方案如下:
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
graph TD
|
|
|
|
|
|
A["浏览器端推理需求"] --> B["ONNX Runtime Web"]
|
|
|
|
|
|
A --> C["TensorFlow.js"]
|
|
|
|
|
|
A --> D["MediaPipe"]
|
|
|
|
|
|
A --> E["Transformers.js"]
|
|
|
|
|
|
B --> B1["通用推理引擎"]
|
|
|
|
|
|
C --> C1["TF 生态专用"]
|
|
|
|
|
|
D --> D1["开箱即用 CV 任务"]
|
|
|
|
|
|
E --> E1["HuggingFace 生态"]
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
| 方案 | 特点 | 适用场景 |
|
|
|
|
|
|
|------|------|---------|
|
|
|
|
|
|
| **ONNX Runtime Web** | 通用推理引擎,支持任意 ONNX 模型,WASM 加速 | 需要在浏览器跑**自定义模型**(VAD、关键帧检测) |
|
|
|
|
|
|
| **TensorFlow.js** | Google 生态,支持 WebGL/WebGPU 加速 | 模型本身就是 TF 格式,或需要 GPU 加速 |
|
|
|
|
|
|
| **MediaPipe** | Google 出品,封装了姿态/手势/人脸等开箱即用方案 | 只需要常见 CV 任务(人脸检测、姿态估计),不需要自定义模型 |
|
|
|
|
|
|
| **Transformers.js** | Hugging Face 生态,直接跑 HuggingFace 上的模型 | 想快速集成 NLP/CV 预训练模型(如 Whisper、CLIP) |
|
|
|
|
|
|
|
|
|
|
|
|
> [!question] 思考
|
2026-06-12 16:05:34 +08:00
|
|
|
|
> 为什么 ONNX Runtime Web 胜出?项目需要同时跑**两种**轻量模型——VAD 和关键帧检测,这是自定义 pipeline,不是单一 CV 任务。ONNX 是跨框架的通用格式,无论模型用什么框架训练,都可以导出为 ONNX 并在浏览器中用统一引擎加载。TensorFlow.js 被锁死在 TF 生态,MediaPipe 虽然开箱即用但灵活性不够(不能自定义模型逻辑)。**ONNX Runtime 的核心优势是"模型无关"**。
|
2026-06-12 15:55:55 +08:00
|
|
|
|
|
|
|
|
|
|
### 语音检测:@ricky0123/vad-web
|
|
|
|
|
|
|
|
|
|
|
|
VAD(Voice Activity Detection)是交互流程的**起始触发器**——用户有没有在说话?触发必须又快又准。
|
|
|
|
|
|
|
|
|
|
|
|
| 方案 | 特点 | 适用场景 |
|
|
|
|
|
|
|------|------|---------|
|
2026-06-12 16:05:34 +08:00
|
|
|
|
| **@ricky0123/vad-web** | 基于 WebRTC VAD,体积极小(~100KB 含 WASM),纯前端零延迟 | 只需要"有没有人说话"的二分类判断 |
|
2026-06-12 15:55:55 +08:00
|
|
|
|
| **Web Audio API + 能量检测** | 用 AnalyserNode 计算音量 RMS,阈值判断 | 极简场景,但抗噪能力差 |
|
|
|
|
|
|
| **ONNX 跑 Silero VAD** | 神经网络级 VAD,准确率高,但推理开销更大 | 嘈杂环境下需要更精准的检测 |
|
|
|
|
|
|
| **Picovoice Porcupine** | 商业级唤醒词引擎,支持自定义唤醒词 | 需要"嘿 Siri"式的唤醒词功能 |
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
graph LR
|
|
|
|
|
|
A["VAD 方案对比"] --> B["轻量级"]
|
|
|
|
|
|
A --> C["重量级"]
|
|
|
|
|
|
B --> B1["能量检测: 最轻, 不抗噪"]
|
|
|
|
|
|
B --> B2["vad-web: 轻量, WebRTC 原生算法"]
|
|
|
|
|
|
C --> C1["Silero VAD: 精准, 需加载 ONNX 模型"]
|
|
|
|
|
|
C --> C2["Porcupine: 商业级, 需付费"]
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
> [!question] 思考
|
|
|
|
|
|
> 用手写能量检测虽然更轻,但不抗噪(咳嗽、环境噪音都会误触发);用 Silero VAD 虽然更准,但需要加载 ONNX 模型,增加首屏时间和内存占用。**@ricky0123/vad-web 是"够用且最轻"的平衡点**——直接包装浏览器原生的 WebRTC VAD 算法(C 代码编译为 WASM),延迟接近零。
|
|
|
|
|
|
|
|
|
|
|
|
### 媒体采集:MediaDevices API
|
|
|
|
|
|
|
|
|
|
|
|
摄像头和麦克风的采集是整个流程的源头。
|
|
|
|
|
|
|
|
|
|
|
|
| 方案 | 特点 | 适用场景 |
|
|
|
|
|
|
|------|------|---------|
|
|
|
|
|
|
| **MediaDevices API** | 浏览器原生 API,零依赖,直接拿 MediaStream | 标准的摄像头/麦克风采集 |
|
|
|
|
|
|
| **react-webcam 等封装库** | React 组件封装,减少胶水代码 | 快速原型,但灵活性受限 |
|
|
|
|
|
|
| **WebRTC(含 getUserMedia)** | 完整的点对点通信栈 | 需要浏览器之间直接传音视频(如视频会议) |
|
|
|
|
|
|
| **Capacitor/Cordova 原生桥** | 混合 App 方案,调用原生摄像头 | 目标不是浏览器而是移动 App |
|
|
|
|
|
|
|
|
|
|
|
|
> [!question] 思考
|
|
|
|
|
|
> `navigator.mediaDevices.getUserMedia()` 是所有浏览器音视频采集的**唯一标准入口**。所有上层封装库底层都是调这个 API。项目需要的是原始 MediaStream(直接送进 VAD 和关键帧检测),不是封装好的组件。用封装库反而要多一层解包,**没有中间商**。
|
|
|
|
|
|
|
|
|
|
|
|
### 选型共同逻辑
|
|
|
|
|
|
|
|
|
|
|
|
这三个技术选择有一个共同的决策模式——**选择最薄的抽象层**:
|
|
|
|
|
|
|
|
|
|
|
|
| 技术 | "最薄"体现在哪里 |
|
|
|
|
|
|
|------|----------------|
|
|
|
|
|
|
| **ONNX Runtime Web** | 不绑定特定框架,模型格式通用 |
|
|
|
|
|
|
| **@ricky0123/vad-web** | 包装原生 WebRTC VAD,没有多余的模型加载 |
|
|
|
|
|
|
| **MediaDevices API** | 直接用浏览器原生接口,不加封装层 |
|
|
|
|
|
|
|
|
|
|
|
|
这与 [[项目架构与技术栈]] 中"前端做轻量预处理"的原则一致:前端层只需要采集和判断"有没有值得发给后端的数据",不需要复杂的模型推理能力。更重的方案(TensorFlow.js、Silero VAD)在后端 Go 网关和云端 AI 服务面前,属于在不该重的地方加重。
|
|
|
|
|
|
|
2026-06-12 15:36:35 +08:00
|
|
|
|
## 关联笔记
|
|
|
|
|
|
|
|
|
|
|
|
- [[项目架构与技术栈]]
|
|
|
|
|
|
- [[成本控制]]
|
|
|
|
|
|
- [[项目架构与技术栈/技术名词解释]]
|