前言
去年接了个智能音箱的项目,从麦克风阵列选型到唤醒词训练,再到对接云端大模型,整个链路走了一遍。说实话,之前觉得智能音箱就是个"语音识别+音响"的组合,真正做下来才发现里面的技术细节相当多。
今天把智能音箱的完整工作原理整理出来,从硬件到软件,从本地唤醒到云端对话,希望能帮到对这块感兴趣的朋友。
智能音箱整体架构
工作流程概览
┌─────────────────────────────────────────────────────────────────────────────┐
│ 智能音箱完整工作流程 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 用户: "小爱同学,今天天气怎么样?" │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ ① 麦克风阵列采集 │ ← 多麦克风拾音 │
│ │ - 远场拾音 (3-5米) │ │
│ │ - 回声消除 (AEC) │ │
│ │ - 噪声抑制 (NS) │ │
│ │ - 波束形成 (Beamforming) │ │
│ └─────────────────┬───────────────────┘ │
│ │ 增强后的音频 │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ ② 本地唤醒词检测 (KWS) │ ← 低功耗DSP持续运行 │
│ │ - 特征提取 (MFCC/FBank) │ │
│ │ - 小型神经网络推理 │ │
│ │ - 检测到"小爱同学" │ │
│ └─────────────────┬───────────────────┘ │
│ │ 唤醒成功,触发主系统 │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ ③ 语音端点检测 (VAD) │ ← 确定语音起止 │
│ │ - 检测用户说话开始 │ │
│ │ - 检测用户说话结束 │ │
│ │ - 录制完整指令音频 │ │
│ └─────────────────┬───────────────────┘ │
│ │ 完整语音数据 │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ ④ 音频编码 & 上传 │ ← 压缩传输 │
│ │ - Opus/Speex编码 │ │
│ │ - WebSocket/HTTP2传输 │ │
│ └─────────────────┬───────────────────┘ │
│ │ │
│ ══════════════════════════════════ 网络边界 │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ ⑤ 云端ASR语音识别 │ ← 云端服务器 │
│ │ - 声学模型 (AM) │ │
│ │ - 语言模型 (LM) │ │
│ │ - 输出: "今天天气怎么样" │ │
│ └─────────────────┬───────────────────┘ │
│ │ 文本 │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ ⑥ 大模型/NLU理解与生成 │ ← LLM处理 │
│ │ - 意图识别 │ │
│ │ - 槽位填充 (城市=当前位置) │ │
│ │ - 调用天气API │ │
│ │ - 生成回答文本 │ │
│ └─────────────────┬───────────────────┘ │
│ │ 回答文本 │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ ⑦ TTS语音合成 │ ← 文本转语音 │
│ │ - 文本前端处理 │ │
│ │ - 声学模型生成频谱 │ │
│ │ - 声码器生成波形 │ │
│ └─────────────────┬───────────────────┘ │
│ │ │
│ ══════════════════════════════════ 网络边界 │
│ │ 音频流 │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ ⑧ 本地播放 │ │
│ │ - 音频解码 │ │
│ │ - 功放驱动 │ │
│ │ - 扬声器输出 │ │
│ └─────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 音箱: "今天北京天气晴,气温15到23度,空气质量良好" │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
延迟分解(典型值):
- 唤醒检测: ~200ms
- VAD结束检测: ~300ms
- 网络传输: ~100ms
- ASR识别: ~300ms
- LLM生成: ~500ms-2s
- TTS合成: ~200ms
- 总延迟: 1.5-3.5秒
硬件架构
┌─────────────────────────────────────────────────────────────────────────────┐
│ 智能音箱硬件架构 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ 麦克风阵列 (顶部) │ │
│ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ │
│ │ │MIC1 │ │MIC2 │ │MIC3 │ │MIC4 │ │MIC5 │ │MIC6 │ │ │
│ │ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ │ │
│ │ │ │ │ │ │ │ │ │
│ │ └─────────┴─────────┴────┬────┴─────────┴─────────┘ │ │
│ └───────────────────────────────┼─────────────────────────────────────┘ │
│ │ I2S/PDM │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ 音频前端处理 (Audio Codec/DSP) │ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ ADC转换 │→│ AEC/NS/BF │→│ 特征提取 │ │ │
│ │ │ (多通道) │ │ (DSP处理) │ │ (MFCC) │ │ │
│ │ └─────────────┘ └─────────────┘ └──────┬──────┘ │ │
│ │ │ │ │
│ └────────────────────────────────────────────┼──────────────────────┘ │
│ │ │
│ ┌───────────────────────────────────────┼───────────────────┐ │
│ │ │ │ │
│ ▼ ▼ │ │
│ ┌─────────────┐ ┌─────────────────────┐ │ │
│ │ KWS芯片 │ │ 主控SoC │ │ │
│ │ (低功耗) │─────唤醒信号──────→│ (AP) │ │ │
│ │ │ │ │ │ │
│ │ - 专用NPU │ │ - CPU (Cortex-A) │ │ │
│ │ - <1mW功耗 │ │ - NPU/GPU │ │ │
│ │ - 始终运行 │ │ - WiFi/BT │ │ │
│ └─────────────┘ │ - Linux/Android │ │ │
│ └──────────┬──────────┘ │ │
│ │ │ │
│ ┌──────────────────────┼──────────────────┤ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌───────────┐ ┌───────────┐ ┌─────────┐ │
│ │ Flash │ │ DRAM │ │ eMMC │ │
│ │ (固件) │ │ (运行) │ │ (存储) │ │
│ └───────────┘ └───────────┘ └─────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ 音频输出 (底部) │ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ DAC │─────→│ 功放(D类) │─────→│ 扬声器 │ │ │
│ │ │ │ │ (10-20W) │ │ (全频/低音)│ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │
│ │ LED环形灯 │ │ 触摸按键 │ │ 电源管理 │ │
│ │ (状态指示) │ │ (音量/静音) │ │ (PMU) │ │
│ └────────────────┘ └────────────────┘ └────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
麦克风阵列与音频前端
麦克风阵列设计
c
/**
* 麦克风阵列配置
* 常见配置:2麦、4麦、6麦、8麦
*/
// 典型的6麦圆形阵列布局
/*
MIC1 (0°)
●
/ \
MIC6 ● ● MIC2 (60°)
(300°)
| |
| ● | ← 中心可能有参考麦克风
| (ref) |
MIC5 ● ● MIC3 (120°)
(240°) \ /
●
MIC4 (180°)
阵列半径: 通常 30-50mm
*/
typedef struct {
int num_mics; // 麦克风数量
float radius_mm; // 阵列半径
float mic_angles[8]; // 各麦克风角度
int sample_rate; // 采样率
int bit_depth; // 位深
} mic_array_config_t;
// 6麦克风配置示例
static const mic_array_config_t mic_config_6ch = {
.num_mics = 6,
.radius_mm = 35.0f,
.mic_angles = {0, 60, 120, 180, 240, 300}, // 均匀分布
.sample_rate = 16000, // 16kHz采样率
.bit_depth = 16 // 16位
};
/**
* 常见麦克风芯片
* - 模拟: Knowles SPH0645, InvenSense ICS-43434
* - 数字PDM: ST MP34DT01, Knowles SPK0838HT4H
*/
音频前端处理 (AFE)
音频前端处理是智能音箱的关键,包括回声消除、噪声抑制、波束形成等:
c
/**
* 音频前端处理流程
*/
/*
┌─────────────────────────────────────────────────────────────────┐
│ AFE (Audio Front-End) 处理流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 多路麦克风输入 扬声器参考信号 │
│ [MIC1][MIC2]...[MIC6] [REF] │
│ │ │ │
│ ▼ │ │
│ ┌───────────────┐ │ │
│ │ 高通滤波 │ 去除直流和低频噪声 │ │
│ │ (HPF) │ │ │
│ └───────┬───────┘ │ │
│ │ │ │
│ ▼ │ │
│ ┌───────────────────────────────────────┴───┐ │
│ │ 回声消除 (AEC) │ │
│ │ │ │
│ │ 扬声器播放的声音会被麦克风拾取, │ │
│ │ 需要从麦克风信号中减去这部分回声 │ │
│ │ │ │
│ │ d(n) = x(n) + s(n) + v(n) │ │
│ │ 其中: x=期望信号, s=回声, v=噪声 │ │
│ │ 目标: 估计s(n)并消除 │ │
│ └───────────────┬───────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────┐ │
│ │ 波束形成 (Beamforming) │ │
│ │ │ │
│ │ 利用多麦克风的空间信息,增强特定方向的 │ │
│ │ 声音,抑制其他方向的干扰 │ │
│ │ │ │
│ │ 常用算法: │ │
│ │ - 延迟求和 (Delay-and-Sum) │ │
│ │ - MVDR (最小方差无失真响应) │ │
│ │ - GEV (广义特征值) │ │
│ └───────────────┬───────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────┐ │
│ │ 噪声抑制 (NS) │ │
│ │ │ │
│ │ 去除环境噪声(空调、风扇、背景人声等) │ │
│ │ │ │
│ │ 常用算法: │ │
│ │ - 谱减法 │ │
│ │ - Wiener滤波 │ │
│ │ - 神经网络降噪 (RNNoise) │ │
│ └───────────────┬───────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────┐ │
│ │ 自动增益控制 (AGC) │ │
│ │ │ │
│ │ 归一化音量,适应不同说话距离和音量 │ │
│ └───────────────┬───────────────────────────┘ │
│ │ │
│ ▼ │
│ 增强后的单路音频 │
│ (送入KWS/ASR) │
│ │
└─────────────────────────────────────────────────────────────────┘
*/
/**
* 回声消除 (AEC) 简化实现
* 使用自适应滤波器估计回声路径
*/
typedef struct {
float *filter_coeffs; // 滤波器系数
float *ref_buffer; // 参考信号缓冲
int filter_len; // 滤波器长度
float mu; // 自适应步长
} aec_state_t;
/**
* NLMS自适应滤波算法
*/
float aec_process_sample(aec_state_t *aec, float mic_in, float ref_in)
{
// 更新参考缓冲
memmove(&aec->ref_buffer[1], &aec->ref_buffer[0],
(aec->filter_len - 1) * sizeof(float));
aec->ref_buffer[0] = ref_in;
// 估计回声: y_hat = w^T * x
float echo_estimate = 0;
for (int i = 0; i < aec->filter_len; i++) {
echo_estimate += aec->filter_coeffs[i] * aec->ref_buffer[i];
}
// 误差(残余信号)
float error = mic_in - echo_estimate;
// 计算参考信号能量
float energy = 0;
for (int i = 0; i < aec->filter_len; i++) {
energy += aec->ref_buffer[i] * aec->ref_buffer[i];
}
// NLMS更新: w = w + mu * e * x / (||x||^2 + eps)
float norm_factor = aec->mu / (energy + 1e-8f);
for (int i = 0; i < aec->filter_len; i++) {
aec->filter_coeffs[i] += norm_factor * error * aec->ref_buffer[i];
}
return error; // 返回消除回声后的信号
}
/**
* 延迟求和波束形成 (Delay-and-Sum Beamforming)
*/
typedef struct {
int num_mics;
float *delays; // 各麦克风延迟(采样点)
float **delay_buffers; // 延迟缓冲区
int buffer_len;
} beamformer_t;
/**
* 计算指向特定方向的延迟
* @param angle_deg 目标方向角度
* @param mic_angles 各麦克风角度
* @param radius_m 阵列半径
* @param sample_rate 采样率
*/
void calc_steering_delays(beamformer_t *bf, float angle_deg,
float *mic_angles, float radius_m, int sample_rate)
{
float c = 343.0f; // 声速 m/s
float angle_rad = angle_deg * M_PI / 180.0f;
for (int i = 0; i < bf->num_mics; i++) {
float mic_rad = mic_angles[i] * M_PI / 180.0f;
// 计算声程差
float path_diff = radius_m * cosf(angle_rad - mic_rad);
// 转换为采样点延迟
bf->delays[i] = path_diff / c * sample_rate;
}
}
/**
* 波束形成处理
*/
float beamform_process(beamformer_t *bf, float *mic_samples)
{
float output = 0;
for (int i = 0; i < bf->num_mics; i++) {
// 将新样本加入延迟缓冲
memmove(&bf->delay_buffers[i][1], &bf->delay_buffers[i][0],
(bf->buffer_len - 1) * sizeof(float));
bf->delay_buffers[i][0] = mic_samples[i];
// 插值获取延迟后的样本
int delay_int = (int)bf->delays[i];
float delay_frac = bf->delays[i] - delay_int;
if (delay_int < bf->buffer_len - 1) {
float sample = bf->delay_buffers[i][delay_int] * (1 - delay_frac) +
bf->delay_buffers[i][delay_int + 1] * delay_frac;
output += sample;
}
}
return output / bf->num_mics; // 平均
}
声源定位 (DOA)
c
/**
* 声源定位 - GCC-PHAT算法
* 通过估计麦克风对之间的时延差来定位声源
*/
#include <fftw3.h>
#include <complex.h>
typedef struct {
int fft_size;
fftwf_complex *fft_buf1;
fftwf_complex *fft_buf2;
fftwf_complex *gcc_buf;
float *gcc_result;
fftwf_plan fft_plan1;
fftwf_plan fft_plan2;
fftwf_plan ifft_plan;
} gcc_phat_t;
/**
* GCC-PHAT计算两路信号的时延
*/
float gcc_phat_tdoa(gcc_phat_t *gcc, float *sig1, float *sig2, int len)
{
// FFT
memset(gcc->fft_buf1, 0, gcc->fft_size * sizeof(fftwf_complex));
memset(gcc->fft_buf2, 0, gcc->fft_size * sizeof(fftwf_complex));
for (int i = 0; i < len; i++) {
gcc->fft_buf1[i] = sig1[i];
gcc->fft_buf2[i] = sig2[i];
}
fftwf_execute(gcc->fft_plan1);
fftwf_execute(gcc->fft_plan2);
// 计算互功率谱密度并归一化(PHAT加权)
for (int i = 0; i < gcc->fft_size; i++) {
fftwf_complex cross = gcc->fft_buf1[i] * conjf(gcc->fft_buf2[i]);
float mag = cabsf(cross);
if (mag > 1e-10f) {
gcc->gcc_buf[i] = cross / mag; // PHAT加权
} else {
gcc->gcc_buf[i] = 0;
}
}
// IFFT得到广义互相关函数
fftwf_execute(gcc->ifft_plan);
// 找峰值位置
int max_idx = 0;
float max_val = 0;
int half = gcc->fft_size / 2;
for (int i = 0; i < gcc->fft_size; i++) {
if (gcc->gcc_result[i] > max_val) {
max_val = gcc->gcc_result[i];
max_idx = i;
}
}
// 转换为时延(支持正负延迟)
int tdoa_samples = (max_idx <= half) ? max_idx : (max_idx - gcc->fft_size);
return (float)tdoa_samples;
}
/**
* 基于TDOA的声源定位
* 使用多对麦克风的时延估计声源方向
*/
float estimate_doa(float *mic_signals[], int num_mics, int frame_len,
float *mic_angles, float radius_m, int sample_rate)
{
gcc_phat_t gcc;
// ... 初始化gcc
float c = 343.0f;
float best_angle = 0;
float best_score = -1e9f;
// 搜索360度
for (int angle = 0; angle < 360; angle += 5) {
float score = 0;
float angle_rad = angle * M_PI / 180.0f;
// 对每对麦克风
for (int i = 0; i < num_mics; i++) {
for (int j = i + 1; j < num_mics; j++) {
// 预期时延
float mic_i_rad = mic_angles[i] * M_PI / 180.0f;
float mic_j_rad = mic_angles[j] * M_PI / 180.0f;
float expected_tdoa = (radius_m / c) * sample_rate *
(cosf(angle_rad - mic_i_rad) - cosf(angle_rad - mic_j_rad));
// 实测时延
float measured_tdoa = gcc_phat_tdoa(&gcc, mic_signals[i],
mic_signals[j], frame_len);
// 评分(时延越接近,分数越高)
score -= fabsf(expected_tdoa - measured_tdoa);
}
}
if (score > best_score) {
best_score = score;
best_angle = angle;
}
}
return best_angle;
}
唤醒词检测 (KWS)
KWS系统架构
┌─────────────────────────────────────────────────────────────────┐
│ 唤醒词检测 (KWS) 系统 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 增强后的音频流 │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 特征提取 │ │
│ │ │ │
│ │ 音频帧 ──→ 预加重 ──→ 分帧 ──→ 加窗 ──→ FFT │ │
│ │ │ │ │
│ │ └──→ Mel滤波器组 ──→ 对数 ──→ DCT ──→ MFCC │ │
│ │ 或 │ │
│ │ └──→ Mel滤波器组 ──→ 对数 ──→ FBank特征 │ │
│ │ │ │
│ │ 输出: 每帧 40维 FBank 或 13维 MFCC + Δ + ΔΔ │ │
│ └───────────────────────────┬─────────────────────────────┘ │
│ │ [Batch × Time × Features] │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 神经网络模型 │ │
│ │ │ │
│ │ 方案1: DNN (全连接) │ │
│ │ [输入层] → [隐藏层×3] → [输出层(唤醒词+非唤醒词)] │ │
│ │ 优点: 简单,计算量小 │ │
│ │ 缺点: 不能建模时序 │ │
│ │ │ │
│ │ 方案2: CNN (卷积) │ │
│ │ [输入] → [Conv2D×N] → [Pool] → [FC] → [Softmax] │ │
│ │ 优点: 参数少,适合频谱特征 │ │
│ │ │ │
│ │ 方案3: RNN/LSTM/GRU │ │
│ │ [输入] → [LSTM×2] → [FC] → [Softmax] │ │
│ │ 优点: 建模时序,效果好 │ │
│ │ 缺点: 计算量较大 │ │
│ │ │ │
│ │ 方案4: DS-CNN (Depthwise Separable CNN) ★推荐 │ │
│ │ [输入] → [DS-Conv×4] → [Avg Pool] → [FC] │ │
│ │ 优点: 参数少,效果好,适合嵌入式 │ │
│ │ │ │
│ │ 方案5: Attention-based │ │
│ │ 使用Self-Attention建模全局依赖 │ │
│ │ │ │
│ └───────────────────────────┬─────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 后处理 │ │
│ │ │ │
│ │ - 平滑: 滑动窗口平均,减少抖动 │ │
│ │ - 阈值: 置信度 > threshold 才触发 │ │
│ │ - 去抖: 连续N帧高于阈值才确认 │ │
│ │ - 冷却: 触发后一段时间不再响应 │ │
│ │ │ │
│ └───────────────────────────┬─────────────────────────────┘ │
│ │ │
│ ▼ │
│ 唤醒事件 │
│ │
└─────────────────────────────────────────────────────────────────┘
MFCC特征提取
c
/**
* MFCC特征提取完整实现
*/
#include <math.h>
#include <string.h>
#define SAMPLE_RATE 16000
#define FRAME_SIZE 400 // 25ms @ 16kHz
#define FRAME_SHIFT 160 // 10ms
#define FFT_SIZE 512
#define NUM_MEL_BINS 40
#define NUM_MFCC 13
/**
* 预加重滤波器
* y[n] = x[n] - α * x[n-1]
*/
void preemphasis(float *signal, int len, float alpha)
{
for (int i = len - 1; i > 0; i--) {
signal[i] -= alpha * signal[i - 1];
}
signal[0] *= (1 - alpha);
}
/**
* 汉明窗
*/
void hamming_window(float *frame, int len)
{
for (int i = 0; i < len; i++) {
float window = 0.54f - 0.46f * cosf(2 * M_PI * i / (len - 1));
frame[i] *= window;
}
}
/**
* Hz到Mel的转换
*/
float hz_to_mel(float hz)
{
return 2595.0f * log10f(1.0f + hz / 700.0f);
}
/**
* Mel到Hz的转换
*/
float mel_to_hz(float mel)
{
return 700.0f * (powf(10.0f, mel / 2595.0f) - 1.0f);
}
/**
* Mel滤波器组
*/
typedef struct {
int num_bins;
int fft_size;
int sample_rate;
float **filters; // [num_bins][fft_size/2+1]
} mel_filterbank_t;
void init_mel_filterbank(mel_filterbank_t *fb, int num_bins, int fft_size, int sample_rate)
{
fb->num_bins = num_bins;
fb->fft_size = fft_size;
fb->sample_rate = sample_rate;
int num_fft_bins = fft_size / 2 + 1;
// 分配滤波器
fb->filters = malloc(num_bins * sizeof(float *));
for (int i = 0; i < num_bins; i++) {
fb->filters[i] = calloc(num_fft_bins, sizeof(float));
}
// 计算Mel频率点
float mel_low = hz_to_mel(0);
float mel_high = hz_to_mel(sample_rate / 2.0f);
float *mel_points = malloc((num_bins + 2) * sizeof(float));
float *hz_points = malloc((num_bins + 2) * sizeof(float));
int *bin_points = malloc((num_bins + 2) * sizeof(int));
for (int i = 0; i < num_bins + 2; i++) {
mel_points[i] = mel_low + (mel_high - mel_low) * i / (num_bins + 1);
hz_points[i] = mel_to_hz(mel_points[i]);
bin_points[i] = (int)floorf((fft_size + 1) * hz_points[i] / sample_rate);
}
// 构建三角滤波器
for (int m = 0; m < num_bins; m++) {
for (int k = bin_points[m]; k < bin_points[m + 1]; k++) {
fb->filters[m][k] = (float)(k - bin_points[m]) /
(bin_points[m + 1] - bin_points[m]);
}
for (int k = bin_points[m + 1]; k < bin_points[m + 2]; k++) {
fb->filters[m][k] = (float)(bin_points[m + 2] - k) /
(bin_points[m + 2] - bin_points[m + 1]);
}
}
free(mel_points);
free(hz_points);
free(bin_points);
}
/**
* 应用Mel滤波器组
*/
void apply_mel_filterbank(mel_filterbank_t *fb, float *power_spectrum, float *mel_energies)
{
int num_fft_bins = fb->fft_size / 2 + 1;
for (int m = 0; m < fb->num_bins; m++) {
mel_energies[m] = 0;
for (int k = 0; k < num_fft_bins; k++) {
mel_energies[m] += power_spectrum[k] * fb->filters[m][k];
}
// 取对数(加小值防止log(0))
mel_energies[m] = logf(mel_energies[m] + 1e-10f);
}
}
/**
* DCT-II变换(用于从FBank得到MFCC)
*/
void dct(float *input, float *output, int input_len, int output_len)
{
for (int k = 0; k < output_len; k++) {
output[k] = 0;
for (int n = 0; n < input_len; n++) {
output[k] += input[n] * cosf(M_PI * k * (n + 0.5f) / input_len);
}
// 归一化
output[k] *= sqrtf(2.0f / input_len);
}
}
/**
* 完整的MFCC提取
*/
typedef struct {
mel_filterbank_t mel_fb;
float *frame_buffer;
float *fft_buffer;
float *power_spectrum;
float *mel_energies;
float *mfcc;
// FFT相关
// ... (使用kiss_fft或其他库)
} mfcc_extractor_t;
void extract_mfcc(mfcc_extractor_t *ext, float *audio_frame, float *features)
{
// 1. 复制帧数据
memcpy(ext->frame_buffer, audio_frame, FRAME_SIZE * sizeof(float));
// 2. 预加重
preemphasis(ext->frame_buffer, FRAME_SIZE, 0.97f);
// 3. 加窗
hamming_window(ext->frame_buffer, FRAME_SIZE);
// 4. 零填充到FFT大小
memset(&ext->frame_buffer[FRAME_SIZE], 0,
(FFT_SIZE - FRAME_SIZE) * sizeof(float));
// 5. FFT
// fft_forward(ext->frame_buffer, ext->fft_buffer, FFT_SIZE);
// 6. 计算功率谱
for (int i = 0; i <= FFT_SIZE / 2; i++) {
float real = ext->fft_buffer[2 * i];
float imag = ext->fft_buffer[2 * i + 1];
ext->power_spectrum[i] = real * real + imag * imag;
}
// 7. Mel滤波
apply_mel_filterbank(&ext->mel_fb, ext->power_spectrum, ext->mel_energies);
// 8. DCT得到MFCC(如果需要)
dct(ext->mel_energies, ext->mfcc, NUM_MEL_BINS, NUM_MFCC);
// 输出FBank特征或MFCC
memcpy(features, ext->mel_energies, NUM_MEL_BINS * sizeof(float));
// 或: memcpy(features, ext->mfcc, NUM_MFCC * sizeof(float));
}
KWS神经网络模型
c
/**
* DS-CNN (Depthwise Separable CNN) 唤醒词模型
* 适合嵌入式部署,参数量小,效果好
*/
/**
* 模型结构(PyTorch定义,用于训练)
*/
/*
class DS_CNN(nn.Module):
def __init__(self, num_classes=2): # 唤醒词 + 非唤醒词
super().__init__()
# 输入: [Batch, 1, Time, Freq] = [B, 1, 98, 40]
# 98帧 × 40维FBank,约1秒音频
self.conv1 = nn.Conv2d(1, 64, kernel_size=(10, 4), stride=(2, 2), padding=(4, 1))
self.bn1 = nn.BatchNorm2d(64)
# 4个DS-Conv块
self.ds_blocks = nn.Sequential(
DSConvBlock(64, 64),
DSConvBlock(64, 64),
DSConvBlock(64, 64),
DSConvBlock(64, 64),
)
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.fc = nn.Linear(64, num_classes)
def forward(self, x):
x = F.relu(self.bn1(self.conv1(x)))
x = self.ds_blocks(x)
x = self.avg_pool(x)
x = x.view(x.size(0), -1)
x = self.fc(x)
return x
class DSConvBlock(nn.Module):
"""深度可分离卷积块"""
def __init__(self, in_ch, out_ch):
super().__init__()
# Depthwise卷积
self.dw_conv = nn.Conv2d(in_ch, in_ch, kernel_size=3, padding=1, groups=in_ch)
self.bn1 = nn.BatchNorm2d(in_ch)
# Pointwise卷积
self.pw_conv = nn.Conv2d(in_ch, out_ch, kernel_size=1)
self.bn2 = nn.BatchNorm2d(out_ch)
def forward(self, x):
x = F.relu(self.bn1(self.dw_conv(x)))
x = F.relu(self.bn2(self.pw_conv(x)))
return x
*/
/**
* C语言推理实现(量化INT8版本)
*/
// 模型参数(量化后)
typedef struct {
// Conv1: 1→64, kernel 10×4
int8_t conv1_weight[64][1][10][4];
int32_t conv1_bias[64];
float conv1_scale;
// DS-Conv blocks
struct {
int8_t dw_weight[64][1][3][3];
int32_t dw_bias[64];
float dw_scale;
int8_t pw_weight[64][64][1][1];
int32_t pw_bias[64];
float pw_scale;
} ds_blocks[4];
// FC: 64 → 2
int8_t fc_weight[2][64];
int32_t fc_bias[2];
float fc_scale;
// 输入输出scale
float input_scale;
int8_t input_zero_point;
} kws_model_t;
/**
* 量化推理
*/
typedef struct {
kws_model_t *model;
int8_t *feature_buffer; // 输入特征
int8_t *conv_buffer[2]; // 中间计算缓冲
int current_buf;
} kws_inference_t;
/**
* 运行KWS推理
* @param features 输入特征 [98帧 × 40维]
* @return 唤醒词置信度 (0.0 - 1.0)
*/
float kws_inference(kws_inference_t *kws, float *features)
{
// 1. 量化输入
for (int i = 0; i < 98 * 40; i++) {
int32_t q = (int32_t)roundf(features[i] / kws->model->input_scale) +
kws->model->input_zero_point;
kws->feature_buffer[i] = (int8_t)CLAMP(q, -128, 127);
}
// 2. Conv1
conv2d_int8(kws->feature_buffer, kws->conv_buffer[0],
98, 40, 1, // 输入: H, W, C
64, // 输出通道
10, 4, // kernel
2, 2, // stride
kws->model->conv1_weight,
kws->model->conv1_bias,
kws->model->conv1_scale);
// 3. DS-Conv blocks
int h = 49, w = 19, c = 64; // Conv1后的尺寸
for (int b = 0; b < 4; b++) {
// Depthwise Conv
depthwise_conv2d_int8(
kws->conv_buffer[kws->current_buf],
kws->conv_buffer[1 - kws->current_buf],
h, w, c,
3, 3, 1, 1, // kernel, stride
kws->model->ds_blocks[b].dw_weight,
kws->model->ds_blocks[b].dw_bias,
kws->model->ds_blocks[b].dw_scale);
kws->current_buf = 1 - kws->current_buf;
// Pointwise Conv
pointwise_conv2d_int8(
kws->conv_buffer[kws->current_buf],
kws->conv_buffer[1 - kws->current_buf],
h, w, c, c,
kws->model->ds_blocks[b].pw_weight,
kws->model->ds_blocks[b].pw_bias,
kws->model->ds_blocks[b].pw_scale);
kws->current_buf = 1 - kws->current_buf;
}
// 4. Global Average Pooling
float pooled[64];
for (int ch = 0; ch < 64; ch++) {
int32_t sum = 0;
for (int i = 0; i < h * w; i++) {
sum += kws->conv_buffer[kws->current_buf][i * c + ch];
}
pooled[ch] = (float)sum / (h * w);
}
// 5. FC层
float logits[2];
for (int i = 0; i < 2; i++) {
int32_t sum = kws->model->fc_bias[i];
for (int j = 0; j < 64; j++) {
sum += (int32_t)kws->model->fc_weight[i][j] * (int32_t)roundf(pooled[j]);
}
logits[i] = sum * kws->model->fc_scale;
}
// 6. Softmax
float exp0 = expf(logits[0]);
float exp1 = expf(logits[1]);
float confidence = exp1 / (exp0 + exp1); // 唤醒词概率
return confidence;
}
/**
* 唤醒词检测主循环
*/
typedef struct {
kws_inference_t kws;
mfcc_extractor_t mfcc;
float confidence_threshold; // 置信度阈值
int smooth_window; // 平滑窗口
float *confidence_history; // 置信度历史
int history_idx;
int trigger_count; // 连续触发计数
int trigger_threshold; // 触发阈值
uint32_t last_trigger_time; // 上次触发时间
uint32_t cooldown_ms; // 冷却时间
} kws_detector_t;
/**
* 处理一帧音频
* @return 1=检测到唤醒词, 0=未检测到
*/
int kws_process_frame(kws_detector_t *det, float *audio_frame)
{
// 1. 提取特征
float features[NUM_MEL_BINS];
extract_mfcc(&det->mfcc, audio_frame, features);
// 2. 更新特征缓冲(滑动窗口)
// 将新特征加入缓冲...
// 3. 每隔N帧运行一次推理
static int frame_count = 0;
if (++frame_count < 5) return 0; // 每5帧推理一次
frame_count = 0;
// 4. 推理
float confidence = kws_inference(&det->kws, det->feature_buffer);
// 5. 平滑
det->confidence_history[det->history_idx] = confidence;
det->history_idx = (det->history_idx + 1) % det->smooth_window;
float avg_confidence = 0;
for (int i = 0; i < det->smooth_window; i++) {
avg_confidence += det->confidence_history[i];
}
avg_confidence /= det->smooth_window;
// 6. 阈值判断
if (avg_confidence > det->confidence_threshold) {
det->trigger_count++;
} else {
det->trigger_count = 0;
}
// 7. 去抖 & 冷却
uint32_t now = get_time_ms();
if (det->trigger_count >= det->trigger_threshold) {
if (now - det->last_trigger_time > det->cooldown_ms) {
det->last_trigger_time = now;
det->trigger_count = 0;
return 1; // 检测到唤醒词!
}
}
return 0;
}
常见KWS芯片
c
/**
* 常见的KWS专用芯片
*/
/*
┌──────────────────────────────────────────────────────────────────────────────┐
│ KWS芯片选型对比 │
├────────────────┬─────────────┬────────────┬────────────┬────────────────────┤
│ 芯片 │ 厂商 │ 功耗 │ 特点 │ 适用场景 │
├────────────────┼─────────────┼────────────┼────────────┼────────────────────┤
│ 杭州国芯 │ │ │ │ │
│ GX8002 │ 国芯科技 │ <1mW │ 本土方案 │ 智能音箱/家电 │
│ │ │ │ 低功耗NPU │ │
├────────────────┼─────────────┼────────────┼────────────┼────────────────────┤
│ 探境科技 │ │ │ │ │
│ SH1001 │ 探境科技 │ <2mW │ 存算一体 │ TWS耳机/音箱 │
│ │ │ │ 超低功耗 │ │
├────────────────┼─────────────┼────────────┼────────────┼────────────────────┤
│ 知存科技 │ │ │ │ │
│ WTM2101 │ 知存科技 │ <1mW │ 存内计算 │ 可穿戴/IoT │
│ │ │ │ 模拟计算 │ │
├────────────────┼─────────────┼────────────┼────────────┼────────────────────┤
│ Syntiant │ │ │ │ │
│ NDP101/120 │ Syntiant │ <1mW │ 神经决策 │ 高端TWS耳机 │
│ │ │ │ 处理器 │ │
├────────────────┼─────────────┼────────────┼────────────┼────────────────────┤
│ Google │ │ │ │ │
│ Edge TPU │ Google │ ~2W │ 高性能 │ 智能音箱/显示器 │
│ │ │ │ 多功能 │ │
├────────────────┼─────────────┼────────────┼────────────┼────────────────────┤
│ 全志 │ │ │ │ │
│ R328/R329 │ 全志科技 │ ~100mW │ 带NPU的AP │ 带屏智能音箱 │
│ │ │ │ Linux支持 │ │
├────────────────┼─────────────┼────────────┼────────────┼────────────────────┤
│ 瑞芯微 │ │ │ │ │
│ RK3308/RV1109 │ 瑞芯微 │ ~200mW │ 多核+NPU │ 智能音箱/门铃 │
│ │ │ │ 接口丰富 │ │
└────────────────┴─────────────┴────────────┴────────────┴────────────────────┘
典型方案组合:
1. 低成本方案: GX8002(KWS) + RK3308(主控)
2. 中端方案: 内置NPU的SoC(如R329)一体化
3. 高端方案: 独立NPU + 高性能AP
*/
语音端点检测 (VAD)
唤醒后需要确定用户说话的起止点:
c
/**
* VAD (Voice Activity Detection) 实现
*/
typedef enum {
VAD_STATE_SILENCE, // 静音
VAD_STATE_SPEECH, // 语音
VAD_STATE_HANGOVER // 挂起(语音结束后的缓冲期)
} vad_state_t;
typedef struct {
vad_state_t state;
// 能量检测
float energy_threshold;
float noise_floor; // 噪声基底
float noise_adapt_rate; // 噪声自适应速率
// 过零率
float zcr_threshold;
// 帧计数
int speech_frames;
int silence_frames;
int hangover_frames;
// 阈值
int min_speech_frames; // 最小语音帧数
int min_silence_frames; // 最小静音帧数
int hangover_threshold; // 挂起阈值
// 录音控制
int is_recording;
float *record_buffer;
int record_len;
int max_record_len;
} vad_detector_t;
/**
* 计算帧能量(dB)
*/
float calc_frame_energy_db(float *frame, int len)
{
float sum = 0;
for (int i = 0; i < len; i++) {
sum += frame[i] * frame[i];
}
return 10 * log10f(sum / len + 1e-10f);
}
/**
* 计算过零率
*/
float calc_zcr(float *frame, int len)
{
int crossings = 0;
for (int i = 1; i < len; i++) {
if ((frame[i] >= 0 && frame[i-1] < 0) ||
(frame[i] < 0 && frame[i-1] >= 0)) {
crossings++;
}
}
return (float)crossings / (len - 1);
}
/**
* VAD处理一帧
* @return 1=语音结束(可以发送到ASR),0=继续
*/
int vad_process_frame(vad_detector_t *vad, float *frame, int len)
{
float energy = calc_frame_energy_db(frame, len);
float zcr = calc_zcr(frame, len);
// 动态更新噪声基底
if (vad->state == VAD_STATE_SILENCE) {
vad->noise_floor = vad->noise_floor * (1 - vad->noise_adapt_rate) +
energy * vad->noise_adapt_rate;
}
// 判断是否有语音
float threshold = vad->noise_floor + vad->energy_threshold;
int is_speech = (energy > threshold) || (zcr > vad->zcr_threshold);
// 状态机
switch (vad->state) {
case VAD_STATE_SILENCE:
if (is_speech) {
vad->speech_frames++;
if (vad->speech_frames >= vad->min_speech_frames) {
// 开始录音
vad->state = VAD_STATE_SPEECH;
vad->is_recording = 1;
vad->record_len = 0;
vad->silence_frames = 0;
}
} else {
vad->speech_frames = 0;
}
break;
case VAD_STATE_SPEECH:
// 录制
if (vad->is_recording && vad->record_len < vad->max_record_len) {
memcpy(&vad->record_buffer[vad->record_len], frame, len * sizeof(float));
vad->record_len += len;
}
if (!is_speech) {
vad->silence_frames++;
if (vad->silence_frames >= vad->min_silence_frames) {
vad->state = VAD_STATE_HANGOVER;
vad->hangover_frames = 0;
}
} else {
vad->silence_frames = 0;
}
break;
case VAD_STATE_HANGOVER:
// 继续录制挂起期间的音频
if (vad->is_recording && vad->record_len < vad->max_record_len) {
memcpy(&vad->record_buffer[vad->record_len], frame, len * sizeof(float));
vad->record_len += len;
}
if (is_speech) {
// 语音恢复
vad->state = VAD_STATE_SPEECH;
vad->silence_frames = 0;
} else {
vad->hangover_frames++;
if (vad->hangover_frames >= vad->hangover_threshold) {
// 语音结束
vad->state = VAD_STATE_SILENCE;
vad->is_recording = 0;
vad->speech_frames = 0;
return 1; // 语音结束,可以发送
}
}
break;
}
return 0;
}
/**
* 初始化VAD
*/
void vad_init(vad_detector_t *vad, float sample_rate)
{
vad->state = VAD_STATE_SILENCE;
vad->energy_threshold = 10.0f; // 比噪声高10dB
vad->noise_floor = -60.0f; // 初始噪声估计
vad->noise_adapt_rate = 0.01f;
vad->zcr_threshold = 0.15f;
// 帧数阈值(假设10ms帧移)
vad->min_speech_frames = 10; // 100ms确认语音开始
vad->min_silence_frames = 20; // 200ms开始结束检测
vad->hangover_threshold = 50; // 500ms静音确认结束
vad->is_recording = 0;
vad->record_len = 0;
vad->max_record_len = 16000 * 10; // 最长10秒
vad->record_buffer = malloc(vad->max_record_len * sizeof(float));
}
云端通信与ASR
音频上传协议
c
/**
* 与云端通信的协议实现
*/
#include <websocket.h>
#include <opus.h>
/**
* 音频编码(Opus)
*/
typedef struct {
OpusEncoder *encoder;
int sample_rate;
int channels;
int bitrate;
} audio_encoder_t;
void audio_encoder_init(audio_encoder_t *enc)
{
int error;
enc->sample_rate = 16000;
enc->channels = 1;
enc->bitrate = 16000; // 16kbps
enc->encoder = opus_encoder_create(enc->sample_rate, enc->channels,
OPUS_APPLICATION_VOIP, &error);
opus_encoder_ctl(enc->encoder, OPUS_SET_BITRATE(enc->bitrate));
opus_encoder_ctl(enc->encoder, OPUS_SET_COMPLEXITY(5));
}
/**
* 编码一帧
*/
int audio_encode_frame(audio_encoder_t *enc, int16_t *pcm, int frame_size,
uint8_t *output, int max_output)
{
return opus_encode(enc->encoder, pcm, frame_size, output, max_output);
}
/**
* WebSocket客户端
*/
typedef struct {
ws_client_t *ws;
audio_encoder_t encoder;
char *server_url;
char *auth_token;
// 回调
void (*on_asr_result)(const char *text);
void (*on_llm_response)(const char *text);
void (*on_tts_audio)(const uint8_t *data, int len);
} cloud_client_t;
/**
* 消息类型定义
*/
#define MSG_TYPE_AUDIO 0x01
#define MSG_TYPE_ASR_RESULT 0x02
#define MSG_TYPE_LLM_QUERY 0x03
#define MSG_TYPE_LLM_RESP 0x04
#define MSG_TYPE_TTS_AUDIO 0x05
#define MSG_TYPE_CONTROL 0x06
/**
* 上传音频数据
*/
void cloud_upload_audio(cloud_client_t *client, float *audio, int len)
{
// 1. 转换为16位整数
int16_t *pcm = malloc(len * sizeof(int16_t));
for (int i = 0; i < len; i++) {
pcm[i] = (int16_t)(audio[i] * 32767);
}
// 2. 分帧编码上传
int frame_size = 320; // 20ms @ 16kHz
uint8_t opus_buf[512];
for (int offset = 0; offset < len; offset += frame_size) {
int frame_len = (offset + frame_size <= len) ? frame_size : (len - offset);
// 编码
int encoded_len = audio_encode_frame(&client->encoder,
&pcm[offset], frame_len,
opus_buf, sizeof(opus_buf));
if (encoded_len > 0) {
// 构建消息
uint8_t msg[520];
msg[0] = MSG_TYPE_AUDIO;
msg[1] = (encoded_len >> 8) & 0xFF;
msg[2] = encoded_len & 0xFF;
memcpy(&msg[3], opus_buf, encoded_len);
// 发送
ws_send_binary(client->ws, msg, 3 + encoded_len);
}
}
// 3. 发送结束标志
uint8_t end_msg[] = {MSG_TYPE_AUDIO, 0, 0};
ws_send_binary(client->ws, end_msg, 3);
free(pcm);
}
/**
* 处理云端响应
*/
void cloud_on_message(cloud_client_t *client, uint8_t *data, int len)
{
uint8_t msg_type = data[0];
switch (msg_type) {
case MSG_TYPE_ASR_RESULT: {
// ASR识别结果
char text[256];
int text_len = (data[1] << 8) | data[2];
memcpy(text, &data[3], text_len);
text[text_len] = '\0';
if (client->on_asr_result) {
client->on_asr_result(text);
}
break;
}
case MSG_TYPE_LLM_RESP: {
// LLM回答(流式)
char text[1024];
int text_len = (data[1] << 8) | data[2];
memcpy(text, &data[3], text_len);
text[text_len] = '\0';
if (client->on_llm_response) {
client->on_llm_response(text);
}
break;
}
case MSG_TYPE_TTS_AUDIO: {
// TTS音频流
int audio_len = (data[1] << 8) | data[2];
if (client->on_tts_audio) {
client->on_tts_audio(&data[3], audio_len);
}
break;
}
}
}
/**
* 完整的一次对话流程
*/
void do_conversation(cloud_client_t *client, float *audio, int audio_len)
{
printf("上传音频...\n");
cloud_upload_audio(client, audio, audio_len);
// 等待并处理响应(在回调中处理)
// ASR结果 -> 显示识别文字
// LLM响应 -> 同时发给TTS
// TTS音频 -> 播放
}
云端服务架构
┌─────────────────────────────────────────────────────────────────────────────┐
│ 云端服务架构 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 设备侧 │ 云端 │
│ │ │
│ ┌─────────────┐ │ ┌─────────────────────────────┐ │
│ │ 智能音箱 │ │ │ 负载均衡器 │ │
│ │ │──WebSocket/HTTP2──→│ │ (Nginx/HAProxy) │ │
│ └─────────────┘ │ └────────────┬────────────────┘ │
│ │ │ │
│ │ ┌────────────┴────────────┐ │
│ │ │ │ │
│ │ ▼ ▼ │
│ │ ┌───────────┐ ┌───────────┐
│ │ │ 接入网关 │ │ 接入网关 │
│ │ │ Gateway │ │ Gateway │
│ │ └─────┬─────┘ └─────┬─────┘
│ │ │ │ │
│ │ │ 消息队列 │ │
│ │ └──────┬──────────────┘ │
│ │ │ │
│ │ ▼ │
│ │ ┌─────────────────────────────┐│
│ │ │ Kafka/RabbitMQ ││
│ │ └────────────┬────────────────┘│
│ │ │ │
│ │ ┌────────────┼────────────┐ │
│ │ │ │ │ │
│ │ ▼ ▼ ▼ │
│ │ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ │ ASR │ │ NLU/ │ │ TTS │ │
│ │ │Service│ │ LLM │ │Service│ │
│ │ │ │ │Service│ │ │ │
│ │ └──────┘ └──────┘ └──────┘ │
│ │ │ │ │ │
│ │ │ │ │ │
│ │ └──────────┼───────────┘ │
│ │ │ │
│ │ ▼ │
│ │ ┌─────────────────────────────┐│
│ │ │ Redis / Database ││
│ │ │ (会话状态/用户数据) ││
│ │ └─────────────────────────────┘│
│ │ │
└───────────────────────────────────────┴────────────────────────────────────┘
大模型集成
LLM调用流程
c
/**
* 大模型调用接口
*/
// 对话历史
typedef struct {
char role[16]; // "user" 或 "assistant"
char content[1024];
} chat_message_t;
typedef struct {
chat_message_t messages[20];
int message_count;
} chat_history_t;
/**
* LLM请求(以OpenAI格式为例,国内大模型类似)
*/
typedef struct {
const char *model; // 模型名称
chat_history_t *history; // 对话历史
float temperature; // 随机性
int max_tokens; // 最大输出长度
int stream; // 是否流式输出
} llm_request_t;
/**
* 构建LLM请求JSON
*/
char* build_llm_request(llm_request_t *req)
{
// 使用cJSON构建
cJSON *root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "model", req->model);
cJSON_AddNumberToObject(root, "temperature", req->temperature);
cJSON_AddNumberToObject(root, "max_tokens", req->max_tokens);
cJSON_AddBoolToObject(root, "stream", req->stream);
// 添加消息数组
cJSON *messages = cJSON_CreateArray();
// 系统提示
cJSON *system_msg = cJSON_CreateObject();
cJSON_AddStringToObject(system_msg, "role", "system");
cJSON_AddStringToObject(system_msg, "content",
"你是一个智能音箱助手,回答要简洁,适合语音播报。");
cJSON_AddItemToArray(messages, system_msg);
// 对话历史
for (int i = 0; i < req->history->message_count; i++) {
cJSON *msg = cJSON_CreateObject();
cJSON_AddStringToObject(msg, "role", req->history->messages[i].role);
cJSON_AddStringToObject(msg, "content", req->history->messages[i].content);
cJSON_AddItemToArray(messages, msg);
}
cJSON_AddItemToObject(root, "messages", messages);
char *json_str = cJSON_PrintUnformatted(root);
cJSON_Delete(root);
return json_str;
}
/**
* 流式解析LLM响应
* SSE (Server-Sent Events) 格式
*/
typedef struct {
char *buffer;
int buffer_len;
void (*on_token)(const char *token);
void (*on_complete)(const char *full_text);
} llm_stream_parser_t;
void parse_llm_stream_chunk(llm_stream_parser_t *parser, const char *chunk)
{
// SSE格式: data: {...}\n\n
if (strncmp(chunk, "data: ", 6) != 0) return;
const char *json_str = chunk + 6;
// 检查是否结束
if (strcmp(json_str, "[DONE]") == 0) {
if (parser->on_complete) {
parser->on_complete(parser->buffer);
}
return;
}
// 解析JSON
cJSON *root = cJSON_Parse(json_str);
if (!root) return;
cJSON *choices = cJSON_GetObjectItem(root, "choices");
if (choices && cJSON_GetArraySize(choices) > 0) {
cJSON *choice = cJSON_GetArrayItem(choices, 0);
cJSON *delta = cJSON_GetObjectItem(choice, "delta");
if (delta) {
cJSON *content = cJSON_GetObjectItem(delta, "content");
if (content && content->valuestring) {
// 获取新token
const char *token = content->valuestring;
// 累积到buffer
int token_len = strlen(token);
if (parser->buffer_len + token_len < 4096) {
strcat(parser->buffer, token);
parser->buffer_len += token_len;
}
// 回调
if (parser->on_token) {
parser->on_token(token);
}
}
}
}
cJSON_Delete(root);
}
/**
* 意图识别与槽位填充
* 对于简单指令,可以不经过LLM
*/
typedef enum {
INTENT_UNKNOWN,
INTENT_WEATHER, // 天气查询
INTENT_TIME, // 时间查询
INTENT_MUSIC, // 播放音乐
INTENT_ALARM, // 设置闹钟
INTENT_REMINDER, // 设置提醒
INTENT_SMART_HOME, // 智能家居控制
INTENT_GENERAL_CHAT // 通用聊天(走LLM)
} intent_type_t;
typedef struct {
intent_type_t intent;
char slots[10][2][64]; // [slot_name, slot_value]
int slot_count;
} nlu_result_t;
/**
* 简单规则NLU
*/
nlu_result_t* simple_nlu(const char *text)
{
nlu_result_t *result = malloc(sizeof(nlu_result_t));
result->intent = INTENT_UNKNOWN;
result->slot_count = 0;
// 天气查询
if (strstr(text, "天气") != NULL) {
result->intent = INTENT_WEATHER;
// 提取城市
char *city_keywords[] = {"北京", "上海", "广州", "深圳", "杭州"};
for (int i = 0; i < 5; i++) {
if (strstr(text, city_keywords[i])) {
strcpy(result->slots[0][0], "city");
strcpy(result->slots[0][1], city_keywords[i]);
result->slot_count = 1;
break;
}
}
return result;
}
// 时间查询
if (strstr(text, "几点") || strstr(text, "时间")) {
result->intent = INTENT_TIME;
return result;
}
// 播放音乐
if (strstr(text, "播放") || strstr(text, "放一首")) {
result->intent = INTENT_MUSIC;
// 提取歌名...
return result;
}
// 默认走LLM
result->intent = INTENT_GENERAL_CHAT;
return result;
}
/**
* 处理意图
*/
char* handle_intent(nlu_result_t *nlu, const char *original_text)
{
char *response = malloc(1024);
switch (nlu->intent) {
case INTENT_WEATHER: {
// 调用天气API
const char *city = (nlu->slot_count > 0) ? nlu->slots[0][1] : "北京";
// weather_api_query(city, response, 1024);
snprintf(response, 1024, "%s今天晴,气温15到23度。", city);
break;
}
case INTENT_TIME: {
time_t now = time(NULL);
struct tm *tm = localtime(&now);
snprintf(response, 1024, "现在是%d点%d分。", tm->tm_hour, tm->tm_min);
break;
}
case INTENT_GENERAL_CHAT: {
// 调用LLM
free(response);
return NULL; // 返回NULL表示需要走LLM
}
default:
snprintf(response, 1024, "抱歉,我不太明白。");
break;
}
return response;
}
TTS语音合成
TTS流程
c
/**
* TTS (Text-to-Speech) 处理
*/
/*
TTS流程:
输入文本: "今天天气晴,气温15到23度"
│
▼
┌─────────────────────────────────────┐
│ 文本前端 (Text Frontend) │
│ │
│ - 文本正则化 │
│ "15到23度" → "十五到二十三度" │
│ │
│ - 分词/词性标注 │
│ │
│ - 多音字处理 │
│ "了" → liǎo/le │
│ │
│ - 韵律预测 │
│ 插入停顿、重音标记 │
│ │
└─────────────────┬───────────────────┘
│ 音素序列 + 韵律标记
▼
┌─────────────────────────────────────┐
│ 声学模型 (Acoustic Model) │
│ │
│ 常见模型: │
│ - Tacotron2 │
│ - FastSpeech2 │
│ - VITS │
│ │
│ 输入: 音素序列 │
│ 输出: Mel频谱 │
│ │
└─────────────────┬───────────────────┘
│ Mel频谱
▼
┌─────────────────────────────────────┐
│ 声码器 (Vocoder) │
│ │
│ 常见模型: │
│ - WaveNet (高质量但慢) │
│ - WaveRNN │
│ - HiFi-GAN ★ (快+好) │
│ - MelGAN │
│ │
│ 输入: Mel频谱 │
│ 输出: 音频波形 │
│ │
└─────────────────┬───────────────────┘
│ 音频数据
▼
播放 / 下发
*/
/**
* 文本正则化示例
*/
typedef struct {
const char *pattern;
const char *replacement;
} normalize_rule_t;
// 数字转文字
char* normalize_number(const char *num_str)
{
static const char *digits[] = {
"零", "一", "二", "三", "四", "五", "六", "七", "八", "九"
};
// 简化实现...
int num = atoi(num_str);
char *result = malloc(64);
if (num < 10) {
strcpy(result, digits[num]);
} else if (num < 100) {
sprintf(result, "%s十%s",
(num / 10 > 1) ? digits[num / 10] : "",
(num % 10 > 0) ? digits[num % 10] : "");
}
// ... 更多处理
return result;
}
/**
* 本地TTS(离线方案,使用轻量级模型)
*/
typedef struct {
// FastSpeech2模型
void *fs2_model;
// HiFi-GAN声码器
void *vocoder_model;
// 音素字典
void *phoneme_dict;
} local_tts_t;
/**
* 云端TTS(质量更好)
* 大多数智能音箱使用云端TTS
*/
typedef struct {
const char *api_url;
const char *api_key;
const char *voice; // 音色
float speed; // 语速
float pitch; // 音调
} cloud_tts_config_t;
/**
* 请求云端TTS
*/
int request_cloud_tts(cloud_tts_config_t *config, const char *text,
void (*on_audio)(const uint8_t *data, int len))
{
// 构建请求
cJSON *root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "text", text);
cJSON_AddStringToObject(root, "voice", config->voice);
cJSON_AddNumberToObject(root, "speed", config->speed);
cJSON_AddNumberToObject(root, "pitch", config->pitch);
cJSON_AddStringToObject(root, "format", "mp3");
cJSON_AddBoolToObject(root, "stream", 1); // 流式返回
char *json_str = cJSON_PrintUnformatted(root);
// HTTP POST请求,流式接收音频数据
// 每收到一块数据就调用 on_audio 回调
// ...
cJSON_Delete(root);
free(json_str);
return 0;
}
完整系统集成
主控程序框架
c
/**
* 智能音箱主控程序
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
/**
* 系统状态
*/
typedef enum {
STATE_IDLE, // 待机,低功耗监听唤醒词
STATE_LISTENING, // 唤醒后,录制用户语音
STATE_PROCESSING, // 处理中,等待云端响应
STATE_SPEAKING, // 播放回答
STATE_ERROR // 错误状态
} system_state_t;
/**
* 系统上下文
*/
typedef struct {
system_state_t state;
// 音频前端
mic_array_config_t mic_config;
aec_state_t aec;
beamformer_t beamformer;
// 唤醒检测
kws_detector_t kws;
// VAD
vad_detector_t vad;
// 云端通信
cloud_client_t cloud;
// 音频播放
audio_player_t player;
// 对话历史
chat_history_t chat_history;
// LED控制
led_controller_t led;
// 线程
pthread_t audio_thread;
pthread_t network_thread;
volatile int running;
} smart_speaker_t;
static smart_speaker_t g_speaker;
/**
* 音频处理线程
*/
void* audio_thread_func(void *arg)
{
smart_speaker_t *speaker = (smart_speaker_t *)arg;
// 音频缓冲区
float mic_samples[6][FRAME_SIZE]; // 6麦克风
float enhanced_audio[FRAME_SIZE]; // 增强后
float ref_signal[FRAME_SIZE]; // 参考信号
while (speaker->running) {
// 1. 读取麦克风数据
audio_capture_read(mic_samples);
// 2. 读取扬声器参考信号(用于AEC)
audio_get_playback_ref(ref_signal);
// 3. 音频前端处理
// AEC
for (int i = 0; i < FRAME_SIZE; i++) {
float aec_out = aec_process_sample(&speaker->aec,
mic_samples[0][i],
ref_signal[i]);
enhanced_audio[i] = aec_out;
}
// 波束形成(如果有DOA信息)
float beam_out[FRAME_SIZE];
for (int i = 0; i < FRAME_SIZE; i++) {
float mics[6];
for (int m = 0; m < 6; m++) {
mics[m] = mic_samples[m][i];
}
beam_out[i] = beamform_process(&speaker->beamformer, mics);
}
// 4. 根据状态处理
switch (speaker->state) {
case STATE_IDLE:
// 运行唤醒词检测
if (kws_process_frame(&speaker->kws, enhanced_audio)) {
printf("唤醒词检测到!\n");
speaker->state = STATE_LISTENING;
led_set_mode(&speaker->led, LED_MODE_LISTENING);
vad_reset(&speaker->vad);
play_wakeup_sound();
}
break;
case STATE_LISTENING:
// VAD检测,录制用户语音
if (vad_process_frame(&speaker->vad, enhanced_audio)) {
printf("语音结束,开始处理\n");
speaker->state = STATE_PROCESSING;
led_set_mode(&speaker->led, LED_MODE_PROCESSING);
// 发送到云端
cloud_upload_audio(&speaker->cloud,
speaker->vad.record_buffer,
speaker->vad.record_len);
}
break;
case STATE_PROCESSING:
// 等待云端响应(在network线程处理)
break;
case STATE_SPEAKING:
// 播放中,继续监听打断词
// 可选:支持"停"等打断指令
break;
default:
break;
}
}
return NULL;
}
/**
* ASR结果回调
*/
void on_asr_result(const char *text)
{
printf("ASR: %s\n", text);
// 添加到对话历史
chat_history_t *history = &g_speaker.chat_history;
strcpy(history->messages[history->message_count].role, "user");
strcpy(history->messages[history->message_count].content, text);
history->message_count++;
// 尝试本地NLU处理
nlu_result_t *nlu = simple_nlu(text);
char *response = handle_intent(nlu, text);
if (response) {
// 本地处理成功,直接TTS
printf("本地回答: %s\n", response);
request_cloud_tts(&g_speaker.cloud.tts_config, response, on_tts_audio);
free(response);
} else {
// 需要LLM处理
printf("发送到LLM...\n");
// LLM请求在cloud模块中处理
}
free(nlu);
}
/**
* LLM响应回调(流式)
*/
void on_llm_response(const char *text)
{
printf("LLM: %s", text);
// 流式TTS:可以边生成边合成播放
// 或者等完整回答后再合成
}
/**
* TTS音频回调
*/
void on_tts_audio(const uint8_t *data, int len)
{
// 送入播放器
audio_player_write(&g_speaker.player, data, len);
if (g_speaker.state != STATE_SPEAKING) {
g_speaker.state = STATE_SPEAKING;
led_set_mode(&g_speaker.led, LED_MODE_SPEAKING);
}
}
/**
* 播放完成回调
*/
void on_playback_complete(void)
{
printf("播放完成\n");
g_speaker.state = STATE_IDLE;
led_set_mode(&g_speaker.led, LED_MODE_IDLE);
}
/**
* 初始化
*/
int smart_speaker_init(void)
{
memset(&g_speaker, 0, sizeof(g_speaker));
g_speaker.state = STATE_IDLE;
g_speaker.running = 1;
// 初始化各模块
audio_capture_init(&g_speaker.mic_config);
aec_init(&g_speaker.aec);
beamformer_init(&g_speaker.beamformer, &g_speaker.mic_config);
kws_init(&g_speaker.kws);
vad_init(&g_speaker.vad, 16000);
// 云端客户端
g_speaker.cloud.server_url = "wss://api.example.com/voice";
g_speaker.cloud.on_asr_result = on_asr_result;
g_speaker.cloud.on_llm_response = on_llm_response;
g_speaker.cloud.on_tts_audio = on_tts_audio;
cloud_client_connect(&g_speaker.cloud);
// 音频播放
audio_player_init(&g_speaker.player);
g_speaker.player.on_complete = on_playback_complete;
// LED
led_init(&g_speaker.led);
led_set_mode(&g_speaker.led, LED_MODE_IDLE);
// 启动线程
pthread_create(&g_speaker.audio_thread, NULL, audio_thread_func, &g_speaker);
printf("智能音箱初始化完成\n");
return 0;
}
/**
* 主函数
*/
int main(int argc, char *argv[])
{
printf("===================================\n");
printf(" 智能音箱系统启动\n");
printf("===================================\n");
if (smart_speaker_init() != 0) {
fprintf(stderr, "初始化失败\n");
return 1;
}
// 主循环
while (g_speaker.running) {
// 处理按键、网络事件等
sleep(1);
// 状态监控
printf("状态: %d, 对话轮数: %d\n",
g_speaker.state,
g_speaker.chat_history.message_count);
}
// 清理
g_speaker.running = 0;
pthread_join(g_speaker.audio_thread, NULL);
return 0;
}
LED状态指示
c
/**
* LED状态指示
* 智能音箱通常有环形LED显示状态
*/
typedef enum {
LED_MODE_OFF, // 关闭
LED_MODE_IDLE, // 待机(呼吸灯或关闭)
LED_MODE_LISTENING, // 监听中(蓝色渐变)
LED_MODE_PROCESSING, // 处理中(蓝色旋转)
LED_MODE_SPEAKING, // 说话中(绿色)
LED_MODE_ERROR, // 错误(红色)
LED_MODE_MUTED, // 静音(红色常亮)
LED_MODE_VOLUME // 音量调节
} led_mode_t;
typedef struct {
int num_leds; // LED数量(如12颗)
uint8_t brightness; // 亮度
led_mode_t mode;
float animation_phase;
pthread_t anim_thread;
volatile int running;
} led_controller_t;
/**
* LED动画线程
*/
void* led_animation_thread(void *arg)
{
led_controller_t *led = (led_controller_t *)arg;
while (led->running) {
switch (led->mode) {
case LED_MODE_IDLE:
// 呼吸灯效果
{
float b = (sinf(led->animation_phase) + 1) / 2;
for (int i = 0; i < led->num_leds; i++) {
led_set_rgb(i, 0, 0, (int)(b * 50)); // 淡蓝色
}
}
break;
case LED_MODE_LISTENING:
// 蓝色从一点扩散
{
for (int i = 0; i < led->num_leds; i++) {
float dist = fabsf(i - led->animation_phase * led->num_leds);
if (dist > led->num_leds / 2) {
dist = led->num_leds - dist;
}
int b = 255 - (int)(dist * 50);
if (b < 0) b = 0;
led_set_rgb(i, 0, 0, b);
}
}
break;
case LED_MODE_PROCESSING:
// 旋转效果
{
int head = (int)(led->animation_phase * led->num_leds) % led->num_leds;
for (int i = 0; i < led->num_leds; i++) {
int dist = (head - i + led->num_leds) % led->num_leds;
int b = 255 - dist * 40;
if (b < 0) b = 0;
led_set_rgb(i, 0, 0, b);
}
}
break;
case LED_MODE_SPEAKING:
// 绿色常亮
for (int i = 0; i < led->num_leds; i++) {
led_set_rgb(i, 0, 200, 0);
}
break;
case LED_MODE_ERROR:
// 红色闪烁
{
int on = ((int)(led->animation_phase * 4)) % 2;
for (int i = 0; i < led->num_leds; i++) {
led_set_rgb(i, on ? 255 : 0, 0, 0);
}
}
break;
default:
break;
}
led->animation_phase += 0.05f;
if (led->animation_phase > 1.0f) {
led->animation_phase -= 1.0f;
}
usleep(20000); // 50fps
}
return NULL;
}
关键性能指标
c
/**
* 智能音箱关键性能指标
*/
/*
┌────────────────────────────────────────────────────────────────────────────┐
│ 性能指标参考值 │
├────────────────────────┬─────────────────┬─────────────────────────────────┤
│ 指标 │ 典型值 │ 说明 │
├────────────────────────┼─────────────────┼─────────────────────────────────┤
│ 唤醒率 (Wake Rate) │ > 95% │ 正确唤醒次数 / 总唤醒尝试次数 │
├────────────────────────┼─────────────────┼─────────────────────────────────┤
│ 误唤醒率 (FAR) │ < 1次/24小时 │ 非唤醒词触发的次数 │
├────────────────────────┼─────────────────┼─────────────────────────────────┤
│ 唤醒距离 │ 3-5米 │ 在安静环境下的有效唤醒距离 │
├────────────────────────┼─────────────────┼─────────────────────────────────┤
│ ASR准确率 │ > 95% │ 字错误率 (WER) < 5% │
├────────────────────────┼─────────────────┼─────────────────────────────────┤
│ 端到端延迟 │ 1-3秒 │ 从说完话到开始播放回答 │
├────────────────────────┼─────────────────┼─────────────────────────────────┤
│ KWS功耗 │ < 2mW │ 待机状态唤醒检测功耗 │
├────────────────────────┼─────────────────┼─────────────────────────────────┤
│ 系统待机功耗 │ < 2W │ WiFi保持连接的待机功耗 │
├────────────────────────┼─────────────────┼─────────────────────────────────┤
│ 信噪比要求 │ > 5dB │ 能正常工作的最低信噪比 │
├────────────────────────┼─────────────────┼─────────────────────────────────┤
│ 回声消除效果 │ > 40dB │ ERLE (Echo Return Loss Enh.) │
└────────────────────────┴─────────────────┴─────────────────────────────────┘
*/
总结
智能音箱的核心技术链路:
| 模块 | 关键技术 |
|---|---|
| 麦克风阵列 | 多麦拾音、阵列布局设计 |
| 音频前端 | AEC回声消除、波束形成、噪声抑制 |
| 唤醒检测 | MFCC特征、轻量级神经网络、低功耗芯片 |
| VAD | 能量/过零率检测、端点判断 |
| 云端通信 | WebSocket、Opus编码、流式传输 |
| ASR | 声学模型、语言模型、端到端模型 |
| NLU/LLM | 意图识别、槽位填充、大模型生成 |
| TTS | 文本前端、声学模型、声码器 |
芯片选型建议:
| 场景 | 方案 |
|---|---|
| 低成本 | 单独KWS芯片 + 低端AP |
| 中端 | 带NPU的SoC一体方案 |
| 高端 | 独立DSP + 高性能AP |
整个智能音箱的技术栈跨度很大,从模拟电路到深度学习都有涉及。本文把主要的技术点都覆盖了,代码可以作为参考框架使用。
有问题欢迎评论区交流~
参考资料:
- 《语音信号处理》
- Kaldi/ESPnet语音识别工具包
- Google Speech Commands数据集
- Alexa/小爱同学技术博客