268 lines
7.7 KiB
Markdown
268 lines
7.7 KiB
Markdown
# `VadText` 技术文档
|
||
|
||
```markdown
|
||
# bricks.VadText 模块技术文档
|
||
|
||
## 概述
|
||
|
||
`bricks.VadText` 是一个基于 Web Audio 和语音活动检测(VAD)的交互式语音识别组件,用于实时采集用户语音、转换为 WAV 格式并发送至后端 ASR(自动语音识别)服务,最终将识别结果展示在界面上。该组件封装了音频录制、编码、网络请求和 UI 控件逻辑。
|
||
|
||
该模块依赖于全局对象 `bricks`,并使用了 VAD(Voice Activity Detection)库进行语音检测。
|
||
|
||
---
|
||
|
||
## 1. 工具函数:`audioBufferToWav`
|
||
|
||
### 功能说明
|
||
将多通道的 `Float32Array[]` 音频数据(标准 Web Audio API 的 `AudioBuffer` 数据格式)转换为标准的 WAV 容器格式的 `ArrayBuffer`。
|
||
|
||
### 函数签名
|
||
```js
|
||
function audioBufferToWav(channelBuffers, sampleRate)
|
||
```
|
||
|
||
### 参数
|
||
| 参数名 | 类型 | 描述 |
|
||
|----------------|----------------------|------|
|
||
| `channelBuffers` | `Float32Array[]` | 包含每个声道音频数据的数组,例如 `[leftChannel, rightChannel]`。单声道时为 `[monoData]`。 |
|
||
| `sampleRate` | `number` | 音频采样率(Hz),如 16000、44100 等。 |
|
||
|
||
### 返回值
|
||
- `{ArrayBuffer}`:符合 RIFF/WAV 标准的二进制缓冲区,可用于生成 Blob 或 Base64 编码。
|
||
|
||
### 实现细节
|
||
- 输出为 **16-bit PCM**、小端字节序(little-endian)。
|
||
- 支持任意声道数(单声道/立体声等)。
|
||
- 自动填充 WAV 头部信息(44 字节标准头):
|
||
- RIFF 标识符与长度
|
||
- fmt 块:PCM 格式、声道数、采样率、比特率等
|
||
- data 块:实际音频样本
|
||
|
||
#### 关键步骤
|
||
1. 计算总样本数:`channelBuffers[0].length * channelBuffers.length`
|
||
2. 创建大小合适的 `ArrayBuffer`(44 字节头部 + 每样本 2 字节)
|
||
3. 写入 WAV 文件头字段(使用 `DataView` 和辅助函数 `writeString`)
|
||
4. 将浮点样本(范围 [-1, 1])转换为 16 位整数 PCM:
|
||
```js
|
||
s < 0 ? s * 0x8000 : s * 0x7FFF
|
||
```
|
||
|
||
> ⚠️ 注意:此函数假设所有通道具有相同长度。
|
||
|
||
---
|
||
|
||
## 2. 主类:`bricks.VadText`
|
||
|
||
继承自 `bricks.VBox`,提供垂直布局容器功能,并集成语音输入与文本输出界面。
|
||
|
||
### 构造函数
|
||
```js
|
||
new bricks.VadText(opts)
|
||
```
|
||
|
||
#### 参数 `opts`
|
||
| 属性名 | 类型 | 必需 | 默认值 | 描述 |
|
||
|--------|------|-------|--------|------|
|
||
| `name` | string | 否 | `'asr_text'` | 表单字段名称,用于 `getValue()` 输出 |
|
||
| `height` | string | 否 | `'100%'` | 组件高度(会被强制设置) |
|
||
| 其他属性 | any | 否 | - | 传递给父类 `VBox` |
|
||
|
||
#### 内部初始化内容
|
||
- **按钮 (`this.button`)**:点击切换录音状态,图标为 `speak.svg`
|
||
- **音频播放器 (`this.audio`)**:回放已录制的语音片段,样式为 `'filler'`
|
||
- **水平布局 (`hbox`)**:包含按钮和音频播放器
|
||
- **填充分隔符 (`this.filler`)**:容纳文本显示区域
|
||
- **文本控件 (`this.text_w`)**:显示 ASR 识别结果,支持换行
|
||
- **事件绑定**:
|
||
- 按钮点击 → `toggle_status()`
|
||
- 触发 `'audio_ready'` 事件 → `handle_audio()`
|
||
|
||
---
|
||
|
||
## 3. 核心方法
|
||
|
||
### `toggle_status()`
|
||
切换录音启停状态:
|
||
- 若正在录音(`this.vad` 存在),则调用 `stop()`
|
||
- 否则调用 `start()`
|
||
|
||
---
|
||
|
||
### `start()`
|
||
启动语音识别流程。
|
||
|
||
#### 流程
|
||
1. 更新按钮文本为 “stop”
|
||
2. 使用 `schedule_once` 延迟执行 `_start()` 以避免同步阻塞
|
||
|
||
---
|
||
|
||
### `async _start()`
|
||
异步初始化麦克风 VAD 实例。
|
||
|
||
#### 步骤
|
||
1. 如果已有全局 `bricks.vad` 实例,先停止它(保证唯一性)
|
||
2. 创建新的 `vad.MicVAD` 实例:
|
||
- 配置 `onSpeechEnd` 回调:当检测到语音结束时触发
|
||
- 回调中派发 `'audio_ready'` 事件并处理音频
|
||
3. 启动 VAD 监听
|
||
4. 设置全局引用 `bricks.vad = this`
|
||
5. 清空历史文本 `this.text = ''`
|
||
|
||
> ✅ 提示:`MicVAD.new()` 应返回 Promise,确保异步加载完成。
|
||
|
||
---
|
||
|
||
### `stop()`
|
||
停止当前录音。
|
||
|
||
#### 流程
|
||
1. 更新按钮文本为 “start”
|
||
2. 延迟调用 `_stop()`
|
||
|
||
---
|
||
|
||
### `async _stop()`
|
||
真正执行停止操作:
|
||
1. 暂停 VAD 实例
|
||
2. 清理 `this.vad` 和 `bricks.vad`
|
||
3. 如果已有识别文本,派发 `'changed'` 事件通知外部值变更
|
||
|
||
---
|
||
|
||
### `async handle_audio(audio)`
|
||
处理由 VAD 捕获的音频数据(`Float32Array` 单通道音频)。
|
||
|
||
#### 步骤
|
||
1. 调用 `audioBufferToWav([audio], 16000)` 生成 WAV 二进制
|
||
2. 使用 `arrayBufferToBase64()` 转为 Base64 字符串
|
||
3. 设置 `this.audio` 播放源为 Data URL(`data:audio/wav;base64,...`)
|
||
4. 发送 HTTP 请求到 ASR 接口:
|
||
- 方法:POST
|
||
- 参数:
|
||
- `model`: 使用的模型名(来自 `this.model`)
|
||
- `audio`: Base64 编码的音频
|
||
5. 成功响应:
|
||
- 追加 `rj.content` 到 `this.text`
|
||
- 更新文本显示
|
||
6. 失败响应:
|
||
- 弹出错误窗口(`bricks.Error`)
|
||
|
||
---
|
||
|
||
### `arrayBufferToBase64(buffer)`
|
||
将 `ArrayBuffer` 转换为 Base64 字符串。
|
||
|
||
#### 实现原理
|
||
1. 使用 `Uint8Array` 遍历字节
|
||
2. 转换为字符串(`String.fromCharCode`)
|
||
3. 调用 `btoa()` 进行 Base64 编码
|
||
|
||
> ❗ 性能提示:大音频文件下可能影响性能,建议改用 `FileReader` 或 `Buffer.from(buffer).toString('base64')`(Node.js 环境)
|
||
|
||
---
|
||
|
||
### `getValue()`
|
||
获取当前表单值,供外部收集数据使用。
|
||
|
||
#### 返回格式
|
||
```js
|
||
{
|
||
[this.name]: "累积识别文本"
|
||
}
|
||
```
|
||
|
||
> ⚠️ 当前实现有缺陷:未返回对象!应修改为:
|
||
```js
|
||
getValue(){
|
||
let d = {};
|
||
d[this.name] = this.text;
|
||
return d; // 缺少 return
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 注册组件
|
||
```js
|
||
bricks.Factory.register('VadText', bricks.VadText);
|
||
```
|
||
允许通过工厂模式动态创建该组件,例如:
|
||
```js
|
||
bricks.Factory.create('VadText', { model: 'whisper-small', url: '/asr' })
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 依赖与配置要求
|
||
|
||
### 外部依赖
|
||
- `vad.MicVAD`:语音活动检测模块(需提前加载)
|
||
- `bricks.Button`, `bricks.AudioPlayer`, `bricks.HBox`, `bricks.VBox`, `bricks.Filler`, `bricks.Text`, `bricks.HttpJson`, `bricks.Error`:UI 组件库
|
||
- `bricks_resource()`:资源路径解析函数(如 SVG 图标)
|
||
- `schedule_once(fn, delay)`:延迟执行工具函数
|
||
|
||
### 配置项(实例属性)
|
||
| 属性 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `url` | string | ASR 服务接口地址 |
|
||
| `model` | string | 指定使用的语音识别模型 |
|
||
|
||
> 示例初始化:
|
||
> ```js
|
||
> new bricks.VadText({
|
||
> name: 'user_speech',
|
||
> url: '/api/asr',
|
||
> model: 'deepseek-asr'
|
||
> })
|
||
> ```
|
||
|
||
---
|
||
|
||
## 6. 事件系统
|
||
|
||
| 事件名 | 触发时机 | 携带数据 |
|
||
|--------|----------|---------|
|
||
| `audio_ready` | VAD 检测到语音结束 | `Float32Array` 音频数据 |
|
||
| `changed` | 录音停止且存在识别文本 | `getValue()` 结果对象(需修复返回问题) |
|
||
|
||
---
|
||
|
||
## 7. 已知问题与改进建议
|
||
|
||
| 问题 | 描述 | 建议修复 |
|
||
|------|------|----------|
|
||
| `getValue()` 缺失返回值 | 当前方法无 `return` 语句 | 添加 `return d;` |
|
||
| Base64 编码效率低 | 大音频下循环拼接字符串性能差 | 改用 `Blob` + `FileReader` 或现代编码方式 |
|
||
| 固定采样率 16000 | 不灵活 | 可从配置或 VAD 获取真实采样率 |
|
||
| 错误处理仅弹窗 | 缺少重试机制 | 增加重试按钮或自动重连 |
|
||
| 全局状态污染 | `bricks.vad` 为共享状态 | 可考虑设计为单例管理器 |
|
||
|
||
---
|
||
|
||
## 8. 使用示例
|
||
|
||
```js
|
||
var vadWidget = new bricks.VadText({
|
||
name: 'transcript',
|
||
url: '/api/speech-to-text',
|
||
model: 'base'
|
||
});
|
||
|
||
vadWidget.bind('changed', function(data) {
|
||
console.log("识别结果:", data.transcript);
|
||
});
|
||
|
||
document.body.appendChild(vadWidget.dom);
|
||
```
|
||
|
||
---
|
||
|
||
## 9. 许可与版权
|
||
© 2025 Bricks Framework. All rights reserved.
|
||
```
|
||
|
||
---
|
||
|
||
✅ **文档版本**:1.0
|
||
📌 **最后更新**:2025年4月5日 |