本地端到端语音(ASR-LLM-TTS)
你有没有想象过在本地快速搭建一个完整的语音对话系统?只需一台普通电脑,无需联网,甚至低算力设备也能流畅运行。这篇博客将手把手带你实现一个端到端的语音交互系统,从语音输入到语音回复,全链路完整展示。ASR-LLM-TTS Onnx 项目实现一个在本地运行的端到端语音对话系统,能够完成“语音 -> 文本 -> 对话回复 -> 语音”的全过程。项目地址:https://github.com/muggle
你有没有想象过在本地快速搭建一个完整的语音对话系统?只需一台普通电脑,无需联网,甚至低算力设备也能流畅运行。这篇博客将手把手带你实现一个端到端的语音交互系统,从语音输入到语音回复,全链路完整展示。
ASR-LLM-TTS Onnx 项目实现一个在本地运行的端到端语音对话系统,能够完成“语音 -> 文本 -> 对话回复 -> 语音”的全过程。
项目地址:https://github.com/muggle-stack/asr-llm-tts
流程
- 自动语音识别(ASR):通过麦克风录制用户的语音输入,并将音频转换为文本。
- 大语言模型(LLM):将识别出的文本发送给本地的大语言模型,生成对用户话语的回答(文本形式)。
- 文本转语音合成(TTS):将LLM产生的回答文本转换为语音,通过扬声器播放输出。
不想看内部实现的,想直接体验的直接跳到最后的使用章节。
设计目的
项目的设计目标是在本地以低算力完成以上流程,因此特别注重模型拆分优化和并行处理。与将ASR、对话和TTS集成到一个“大模型”中不同,本项目将这三部分解耦为独立模块,各模块使用针对性的轻量模型,实现整体更高效率。
- 优化的小模型组合:ASR、LLM、TTS 各自采用不同的小模型或推理引擎,以在保证精度的前提下尽量降低资源占用 。ASR模块采用精度高且模型小的语音识别模型,TTS模块采用轻量高效的语音合成模型等。
- 模块解耦与可替换:通过Pipeline将ASR、LLM、TTS串联,每个模块相互独立,遵循统一接口。这样可以灵活替换任意一部分的实现或模型,而无需改动其他模块
- 异步流水线并行:采用多线程/多进程 + 队列将三个阶段串联,使各阶段能够流水线并行处理
- 资源隔离与异构计算:不同模块可使用不同的硬件资源并行运行,充分利用硬件能力,提高整体吞吐。
- 易于扩展:由于采用节点串联的架构,功能扩展只需在Pipeline中插入新节点即可
ASR
ASR(Automatic Speech Recognition)模块负责将输入的语音信号转换为文本。本项目中ASR模块采用了阿里 sensevoice-small 模型及其优化变体来实现高精度的语音转写。sensevoice是一个基于Transformer的大规模语音识别模型,支持多语言和长音频转录,其原理是将音频转为梅尔频谱输入一个Encoder-Decoder网络,输出对应的文本序列。
实现原理上,无论哪种后端,ASR模块本质都需要先将原始音频转换为模型输入的特征,然后通过模型推理得到文本结果:
- 将音频信号重采样/转换为所需采样率(模型要求16kHz单声道)并归一化音量;
- 计算音频的梅尔频谱或Log-Mel特征作为模型输入;
- 模型推理输出词序列(token),再解码为可读文本。
在将音频送入ASR模型之前,进行必要的前处理,以确保语音数据格式和质量满足模型要求。前处理需要进行的操作:
音频采集:通过麦克风录制实时音频流。项目中使用Python的音频库(例如 sounddevice 或 pyaudio)打开麦克风输入,以一定的帧率持续读取音频数据块。
import pyaudio
self.RATE = RATE_VAD # 采样率 16kHz
self.FRAME_SIZE = WIN_SAMPLES # 每帧 512 个采样点
self.FORMAT = pyaudio.paInt16 # 16位整型
self.CHANNELS = 1 # 单声道
self.pa = pyaudio.PyAudio()
self.stream = self.pa.open(format=self.FORMAT,
channels=self.CHANNELS,
rate=self.RATE,
input=True,
frames_per_buffer=self.FRAME_SIZE,
input_device_index=device_index)
在将音频送入模型前,需要确保音频格式符合模型要求:16kHz采样率、16-bit PCM。如果麦克风输入不是16kHz,需要重采样到16kHz(代码里我注释了,可以自己改 from scipy.signal import resample 这个库就能搞),并将音频数据整理为 NumPy 数组或直接写入临时 WAV 文件供模型读取。
这里面我添加了vad的功能,自动检测说话是否停止,进而停止录音,在 VAD 推理前,音频帧会被转换为 float32 并归一化到 [-1, 1] 区间:
wav = (np.frombuffer(pcm_bytes, dtype=np.int16)
.astype(np.float32) / 32768.0)[np.newaxis, :] # (1,512)
将原始 PCM 数据转换为神经网络模型可接受的格式。
self.sess = ort.InferenceSession(silero_model_path, providers=['CPUExecutionProvider'])
使用 ONNX Runtime 加载 Silero VAD 模型,无需 PyTorch,torch比较大,不适合在端侧进行部署。
推理以及后处理:
# asr/asr.py
def generate(self, audio_file):
if isinstance(audio_file, np.ndarray):
audio_path = audio_file
if isinstance(audio_file, str):
audio_path = [audio_file]
asr_res = self._model(audio_path, language='zh', use_itn=True)
asr_res = asr_res[0][0].tolist()
text = rich_transcription_postprocess(asr_res[0])
return text
# asr/models/postprocess_utils.py
def rich_transcription_postprocess(result):
# 分词、去除特殊符号
text = re.sub(r"[^\w\s]", "", result["text"])
# 时间戳处理
...
return text
ASR处理完以后,得到纯文本输出,接着将文本给llm进行输出。
LLM
llm模块比较简单,用到 ollama 的 python 库去获取本地ollama的API,这里我用的模型是本地的qwen3-0.6b可以自行修改:
def get_chat_stream(self, text, messages):
messages.append({"role": "user", "content": text})
stream = chat(
model=self._model_path,
messages=messages,
stream=self._stream
)
return stream
# 处理函数调用的主逻辑
def generate(self, text):
self.messages = [
{
"role": "system",
"content": (
"你是一个问答助手,正常回答用户问题。\n"
)
}
]
# 获取聊天流
stream = self.get_chat_stream(text, self.messages)
TTS
TTS部分采用的是meloTTS。TTS的完整实现链路如下:
┌──────────────────┐
│ 原始文本 text │
└────────┬─────────┘
│ split_utils.split_sentence
▼
┌──────────────────┐
│ 句子序列 s_i │
└────────┬─────────┘
│ chinese_mix.clean_text
│ ├─ 数字 / 标点标准化 utils.*
│ ├─ 中文 lazy_pinyin + ToneSandhi
│ └─ 英文 g2p_en
▼
┌──────────────────┐
│ phone / tone │
│ lang_id 序列 │
└────────┬─────────┘
│ melotts_onnx.get_text_for_tts_infer
│ ├─ intersperse(blank) # CTC
│ └─ word2ph
▼
┌──────────────────┐
│ numpy int32 │
│ (phones,tones, │
│ langs,word2ph)│
└────────┬─────────┘
│ ONNX Encoder
│ (TTSModel._run_encoder)
▼
┌──────────────────┐
│ latent z_p │
│ pronoun_lens │
└────────┬─────────┘
│ calc_word2pronoun
│ generate_slices(dec_len)
▼
┌──────────────────┐
│ z_p 切片队列 │
└────────┬─────────┘
│ ONNX Decoder
│ (TTSModel._run_decoder)
▼
┌──────────────────┐
│ sub-audio PCM │
└────────┬─────────┘
│ merge_sub_audio
▼
┌──────────────────┐
│ 句级音频 wav_i │
└────────┬─────────┘
│ audio_numpy_concat (+50 ms 静音)
▼
┌──────────────────┐
│ 全句 WAV/PCM │
└──────────────────┘
- 文本前处理与分句
• 文本标准化(去除杂乱标点,统一数字表达等)
• 根据标点对长文本进行分句,提高推理效率
def split_sentence(text, min_len=10, language_str='ZH'):
# 根据中文或英文正则表达式进行分句
sentences = split_sentences_zh(text, min_len) if language_str == 'ZH' else split_sentences_latin(text, min_len)
return sentences
- 文本到音素转换与声调处理(G2P)
• 中英文混合文本分别进行音素转换
• 处理中文拼音声调规则
def clean_text(text, language_str='ZH'):
# 文本标准化和G2P
# 对于中文调用 lazy_pinyin 和 ToneSandhi 完成音素转换
# 对于英文调用 g2p_en 实现音素转换
norm_text, phones, tones, word2ph = chinese_mix.clean_text(text, language_str)
return norm_text, phones, tones, word2ph
- 编码器输入准备
• 将音素序列处理成带有CTC空白符的序列
• 生成编码器可接受的numpy数组输入
def get_text_for_tts_infer(text, language_str='ZH', symbol_to_id=None):
phones, tones, langs, norm_text, word2ph = clean_text(...)
phones = intersperse(phones, 0).astype(np.int32) # 插入CTC空白符
tones = intersperse(tones, 0).astype(np.int32)
langs = intersperse(langs, 0).astype(np.int32)
return phones, tones, langs, norm_text, word2ph
- 编码器推理 (ONNX Encoder)
• 将音素输入转化为 latent 特征表示
z_p, pronoun_lens, audio_len = sess_enc.run(None, input_feed)
- latent 特征切片
• 将过长的 latent 序列按照解码器长度限制进行切分,并带有一定重叠,方便后续拼接
def generate_slices(word2pronoun, dec_len):
return pn_slices, zp_slices
- 解码器推理(ONNX Decoder)
• 将每个latent切片解码为对应的音频片段
audio_segment = sess_dec.run(None, {'z_p': zp_slice, 'g': g})[0]
- 音频片段拼接(Overlap-Add)
• 将解码后的音频片段平滑拼接成一句完整的音频
def merge_sub_audio(sub_audio_list, pad_size, audio_len):
# 重叠相加,拼接音频
merged_audio = ...
return merged_audio
以上是模型需要的前后处理的具体实现,代码内部均已实现。代码已经封装成一个完整的pipeline,如果要移植代码,只需要在主程序导入类TTSModel,使用ort_predict推理方法即可得到输出音频。
使用
克隆代码
git clone https://github.com/muggle-stack/asr-llm-tts.git
cd asr-llm-tts
安装虚拟环境、依赖
sudo apt install venv
python -m venv .venv
source ~/.venv/bin/activate
pip install -r requirements.txt
安装ollama
# Linux
curl -fsSL https://ollama.com/install.sh | sh
Windows以及macOS需要去ollama官网手动下载https://ollama.com/download/windows
下载模型
ollama pull qwen3:0.6b
使用
python asr_llm_tts.py
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)