一个用 Rust 实现的智能音箱系统,支持多种大模型后端、语音合成和语音识别。
- 🗣️ LLM 抽象层 - 支持 OpenAI、DeepSeek、Qwen 等多种大模型
- 🔊 TTS 语音合成 - 支持 Edge TTS (免费)、Sherpa 和 Mock Provider
- 🎤 ASR 语音识别 - 支持 Whisper API、Sherpa 和 Mock Provider
- 👂 Wake 唤醒词检测 - 支持 Porcupine、Sherpa 和 Mock Provider
- 🎭 声纹识别 - 支持说话人识别和声纹管理
- 🔌 可插拔架构 - 所有模块均支持自定义 Provider
- 🛠️ 工具系统 - 支持定时器、音乐播放、天气查询等工具
src/
├── main.rs # 入口和演示
├── lib.rs # 库入口
├── config/ # 配置管理
├── database/ # SQLite 数据库
├── llm/ # 大语言模型模块
├── tts/ # 语音合成模块
├── asr/ # 语音识别模块
├── wake/ # 唤醒词检测模块
├── audio/ # 音频 I/O 和 VAD
├── speaker/ # 声纹识别模块
├── pipeline/ # 语音交互管道
├── tools/ # 工具执行器
└── state/ # 状态机管理
# 克隆项目
git clone https://github.com/iabetor/pitalk.git
cd pitalk
# 构建 (开发模式)
cargo build
# 构建 (生产模式)
cargo build --release创建配置文件和环境变量:
# 方式一:使用 YAML 配置文件
cp configs/pitalk.yaml ~/.config/pitalk.yaml
# 方式二:使用环境变量
export OPENAI_API_KEY=sk-xxx
export DEEPSEEK_API_KEY=sk-xxx
export QWEN_API_KEY=sk-xxx配置文件示例 (~/.config/pitalk.yaml):
# LLM 配置
llm:
default_model: qwen
models:
- name: qwen
api_url: ${QWEN_API_URL}
api_key: ${QWEN_API_KEY}
model: qwen-turbo
# 音频配置
audio:
sample_rate: 16000
mic_gain: 2.0
# 对话配置
dialog:
continuous_timeout: 8
wake_reply: "我在"
system_prompt: |
你是一个智能语音助手,名字叫 PiTalk。# 运行演示程序 (使用 Mock Provider,无需配置)
cargo run
# 启用真实音频设备 (需要音频硬件)
cargo run --features cpal
# 启用 Sherpa 本地语音处理 (需要模型文件)
cargo run --features sherpa
# 组合使用
cargo run --features cpal,sherpa# 运行所有测试
cargo test
# 运行特定测试
cargo test speaker
cargo test integration
# 显示测试输出
cargo test -- --nocaptureuse pitalk::llm::{LLMConfig, OpenAIProvider, LLMProvider, LLMProviderExt, Message};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 使用 DeepSeek (国内友好)
let config = LLMConfig::deepseek("your-api-key")
.with_system_prompt("你是一个智能音箱助手,回答要简洁友好");
let provider = OpenAIProvider::new(config)?;
// 简单问答
let response = provider.ask("今天天气怎么样?").await?;
println!("回复: {}", response);
// 多轮对话
let mut conversation = provider.new_conversation();
conversation.user("我叫小明");
let reply = provider.chat(&conversation).await?;
println!("助手: {}", reply);
conversation.user("你还记得我的名字吗?");
let reply = provider.chat(&conversation).await?;
println!("助手: {}", reply);
Ok(())
}use pitalk::tts::{TTSProvider, TTSProviderExt};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 使用 Mock TTS (开发和测试)
let tts = MockTTSProvider::new();
// 合成语音
let text = "你好,我是 PiTalk 智能音箱";
let audio_data = tts.synthesize_all(text).await?;
println!("音频大小: {} bytes", audio_data.len());
// 保存到文件
tokio::fs::write("output.mp3", &audio_data).await?;
println!("已保存到 output.mp3");
// 列出可用语音
let voices = tts.list_voices().await?;
for voice in voices.iter().take(5) {
println!("- {} ({})", voice.name, voice.language);
}
Ok(())
}use pitalk::tts::{TencentTTSProvider, TencentTTSConfig, TTSProvider, TTSProviderExt};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 从环境变量创建 (推荐)
// 需要设置: TENCENT_SECRET_ID 和 TENCENT_SECRET_KEY
let tts = TencentTTSProvider::from_env()?;
// 或者直接指定密钥
let config = TencentTTSConfig::new("your-secret-id", "your-secret-key")
.with_voice_type(1001) // 标准女声
.with_speed(0) // 正常语速
.with_volume(5); // 中等音量
let tts = TencentTTSProvider::with_config(config)?;
let audio = tts.synthesize_all("你好世界").await?;
tokio::fs::write("tencent_output.mp3", &audio).await?;
Ok(())
}获取密钥: 前往 腾讯云控制台 获取 SecretId 和 SecretKey
use pitalk::tts::{EdgeTTSProvider, TTSProvider, TTSProviderExt};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 使用 Edge TTS (免费、高质量)
let tts = EdgeTTSProvider::new()
.with_voice("zh-CN-XiaoxiaoNeural"); // 中文女声
let audio = tts.synthesize_all("你好世界").await?;
tokio::fs::write("edge_output.mp3", &audio).await?;
Ok(())
}注意: Edge TTS 目前为实验性功能,微软 API 可能随时变更。
use pitalk::asr::{ASRConfig, WhisperAPIProvider, ASRProvider, ASRProviderExt};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 使用 OpenAI Whisper API
let config = ASRConfig::new("your-openai-api-key")
.with_language("zh") // 中文
.with_model("whisper-1");
let asr = WhisperAPIProvider::new(config)?;
// 从文件转录
let text = asr.transcribe_file("recording.wav").await?;
println!("识别结果: {}", text);
// 从内存数据转录
let audio_data = tokio::fs::read("another.wav").await?;
let text = asr.transcribe(&audio_data).await?;
println!("识别结果: {}", text);
// 查看支持的语言
let languages = asr.supported_languages();
println!("支持的语言: {:?}", languages);
Ok(())
}use pitalk::asr::{MockASRProvider, ASRProvider, ASRProviderExt};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Mock ASR 返回固定文本,用于测试
let asr = MockASRProvider::new()
.with_response("这是一段模拟的识别结果");
let text = asr.transcribe_file("test.wav").await?;
println!("模拟结果: {}", text);
Ok(())
}use pitalk::wake::{MockWakeProvider, WakeProvider};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 使用 Mock Wake Provider (测试用)
let mut wake = MockWakeProvider::new()
.with_keyword("hey pitalk")
.with_auto_detect_after_frames(3);
// 模拟音频帧 (16kHz, 16-bit, mono, 512 samples)
let audio_frame = [0i16; 512];
// 处理音频流
for _ in 0..3 {
wake.process(&audio_frame).await?;
}
// 第 4 次检测到唤醒词
if let Some(event) = wake.process(&audio_frame).await? {
println!("检测到: {}", event.keyword);
}
Ok(())
}// 需要添加 pv_porcupine 依赖并启用 feature
use pitalk::wake::{PorcupineProvider, WakeProvider, BuiltinKeyword};
let mut wake = PorcupineProvider::new_with_builtin(
"your-access-key", // 从 console.picovoice.ai 获取
BuiltinKeyword::Jarvis,
)?;
wake.set_sensitivity(0.5);
if let Some(event) = wake.process(&audio_frame).await? {
println!("唤醒词: {}", event.keyword);
}所有模块均采用 Trait 抽象,支持自定义实现:
// LLM Provider
pub trait LLMProvider: Send + Sync {
async fn chat(&self, messages: &[Message]) -> Result<String, LLMError>;
async fn chat_stream(&self, messages: &[Message]) -> Result<MessageStream, LLMError>;
}
// TTS Provider
pub trait TTSProvider: Send + Sync {
async fn synthesize(&self, text: &str) -> Result<AudioStream, TTSError>;
async fn list_voices(&self) -> Result<Vec<Voice>, TTSError>;
}
// ASR Provider
pub trait ASRProvider: Send + Sync {
async fn transcribe(&self, audio: &[u8]) -> Result<String, ASRError>;
}| Flag | 说明 | 依赖 |
|---|---|---|
| (default) | 基础功能,使用 Mock Provider | 无 |
cpal |
真实音频设备 I/O | cpal, 音频硬件 |
sherpa |
本地语音处理 (ASR/TTS/VAD/KWS/声纹) | sherpa-rs, 模型文件 |
porcupine |
Porcupine 唤醒词检测 | pv_porcupine |
| 模块 | 状态 | 说明 |
|---|---|---|
| LLM | ✅ 完成 | OpenAI、DeepSeek、Qwen、Mock |
| TTS | ✅ 完成 | Edge TTS、Sherpa、Mock |
| ASR | ✅ 完成 | Whisper API、Sherpa、Mock |
| Wake | ✅ 完成 | Porcupine、Sherpa、Mock |
| Audio | ✅ 完成 | cpal、Mock、VAD |
| Speaker | ✅ 完成 | 声纹提取、说话人识别 |
| Pipeline | ✅ 完成 | 语音交互流程编排 |
| Tools | ✅ 完成 | 定时器、音乐、天气、智能家居 |
| Database | ✅ 完成 | SQLite 存储 (对话、声纹、设置) |
| 配置 | 最低 | 推荐 |
|---|---|---|
| 树莓派 | 4B (4GB) | 5 (8GB) |
| 麦克风 | USB 麦克风 | ReSpeaker 2/4-Mics HAT |
| 音箱 | 3.5mm/蓝牙 | 带功放的小音箱 |
MIT License