【上篇回顾】上一篇我们实现了实时视觉检测NPU推理延迟低至5ms通过流水线设计达到了200 FPS。这一篇我们将挑战更复杂的多模型流水线——语音助手从麦克风输入到音箱输出全部在X2 Elite本地完成。一、场景描述在骁龙X2 Elite上实现端侧智能语音助手实时语音活动检测VAD检测用户是否在说话流式语音识别ASR使用 Whisper 模型将语音转文字本地大语言模型响应LLM使用 Phi-3-mini 生成回复语音合成输出TTS使用 VITS 模型将回复转为语音目标完全离线运行所有模型部署在NPU上端到端延迟 500ms不含LLM生成。二、全链路AI应用开发流程图如下三、模型选型与量化模块模型量化格式后端说明VADSilero VADINT8NPU轻量语音活动检测ASRWhisper-smallINT8NPU编码器解码器80M参数LLMPhi-3-mini (3.8B)INT4NPU微软开源小语言模型TTSVITS-ChineseINT8NPU端到端语音合成四、语音处理Pipeline架构图语音处理Pipeline示意图如下麦克风输入 ↓ ┌─────────────────────────────────────────────────────────────┐ │ 音频流 (16kHz, 512/帧) │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ VAD (Silero on NPU) │ │ • 实时检测语音活动 │ │ • 输出is_speech (bool) │ └─────────────────────────────────────────────────────────────┘ ↓ (语音结束) ┌─────────────────────────────────────────────────────────────┐ │ ASR (Whisper on NPU) │ │ • Mel特征提取 │ │ • Encoder → Decoder自回归 │ │ • 输出文本 │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ LLM (Phi-3-mini on NPU) │ │ • Prompt构造 Tokenize │ │ • 自回归生成 │ │ • 输出回复文本 │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ TTS (VITS on NPU) │ │ • 文本→音素 │ │ • VITS推理 │ │ • 输出音频 (22.05kHz) │ └─────────────────────────────────────────────────────────────┘ ↓ 扬声器播放五、完整代码实现importnumpyasnpimportonnxruntimeasortimportsounddeviceassdfromcollectionsimportdequeimporttimeclassX2EliteVoiceAssistant:X2 Elite端侧语音助手 - 完全离线全链路NPU加速def__init__(self):# NPU配置与视觉篇保持一致self.npu_providers[(QNNExecutionProvider,{backend_path:QnnHtp.dll,htp_performance_mode:burst,enable_htp_fp16_precision:1,qnn_context_cache_enable:1,qnn_context_cache_path:./cache/voice_cache.bin,htp_arch:77,}),CPUExecutionProvider]print([X2 Elite Voice] 正在加载模型到NPU...)load_starttime.time()# 1. 加载VAD模型 (Silero)self.vad_sessionort.InferenceSession(silero_vad.onnx,providersself.npu_providers)# 2. 加载Whisper (编码器解码器)self.whisper_encoderort.InferenceSession(whisper_encoder.onnx,providersself.npu_providers)self.whisper_decoderort.InferenceSession(whisper_decoder.onnx,providersself.npu_providers)# 3. 加载LLM (Phi-3-mini INT4)self.llm_sessionort.InferenceSession(phi3_mini_int4_qnn.onnx,providersself.npu_providers)# 4. 加载TTS (VITS)self.tts_sessionort.InferenceSession(vits_chinese_int8.onnx,providersself.npu_providers)load_endtime.time()print(f[X2 Elite Voice] 所有模型加载完成耗时{load_end-load_start:.1f}s)# 音频参数self.sample_rate16000# Whisper 标准采样率self.chunk_size512# 32ms per chunkself.audio_bufferdeque(maxlenself.sample_rate*30)# 30秒缓冲self.is_speakingFalseself.speech_frames[]defvad_detect(self,audio_chunk:np.ndarray)-bool:语音活动检测 - Silero VAD on NPUinput_dataaudio_chunk.astype(np.float32).reshape(1,-1)srnp.array([self.sample_rate],dtypenp.int64)resultself.vad_session.run(None,{input:input_data,sr:sr})speech_probresult[0][0]returnspeech_prob0.5def_extract_mel(self,audio):提取Mel频谱Whisper预处理- 完整实现# 【补充】原文件第13页给出了以下完整实现简化版# 实际可使用 librosa 或 Whisper 原生的 log_mel_spectrogram# 参数采样率16000FFT窗口400步长160Mel频带80importlibrosa# 计算Mel频谱mel_speclibrosa.feature.melspectrogram(yaudio,srself.sample_rate,n_mels80,n_fft400,hop_length160,power2.0)# 转换为对数刻度log_melnp.log(mel_spec1e-10)# 归一化到[-1, 1]Whisper期望的输入范围log_mel(log_mel-log_mel.mean())/(log_mel.std()1e-8)returnlog_mel.astype(np.float32)# 形状: (80, time_frames)deftranscribe(self,audio:np.ndarray)-str:语音识别 - Whisper on NPUprint([ASR] 开始识别...)t0time.time()# 提取Mel特征mel_featuresself._extract_mel(audio)# (80, T)# Encoder推理NPUencoder_outputself.whisper_encoder.run(None,{mel:mel_features[np.newaxis,...]# 添加batch维度})[0]# Decoder自回归生成NPUtokens[50258]# |startoftranscript|for_inrange(448):decoder_inputnp.array([tokens],dtypenp.int64)logitsself.whisper_decoder.run(None,{tokens:decoder_input,audio_features:encoder_output})[0]next_tokennp.argmax(logits[0,-1,:])ifnext_token50257:# |endoftext|breaktokens.append(int(next_token))# 解码token为文本需使用WhisperTokenizerfromtransformersimportWhisperTokenizer tokenizerWhisperTokenizer.from_pretrained(openai/whisper-small)texttokenizer.decode(tokens,skip_special_tokensTrue)print(f[ASR] 识别结果:{text}(耗时:{time.time()-t0:.2f}s))returntextdef_tokenize(self,text):文本转token IDPhi-3 tokenizerfromtransformersimportAutoTokenizer tokenizerAutoTokenizer.from_pretrained(microsoft/Phi-3-mini-4k-instruct)returntokenizer.encode(text)def_detokenize(self,tokens):token ID转文本Phi-3fromtransformersimportAutoTokenizer tokenizerAutoTokenizer.from_pretrained(microsoft/Phi-3-mini-4k-instruct)returntokenizer.decode(tokens)defgenerate_response(self,user_text:str)-str:LLM响应生成 - Phi-3-mini on NPUprint([LLM] 正在生成回复...)t0time.time()# 构造promptPhi-3聊天格式promptf|user|\n{user_text}|end|\n|assistant|\ninput_idsself._tokenize(prompt)generated_tokens[]for_inrange(256):outputsself.llm_session.run(None,{input_ids:np.array([input_ids],dtypenp.int64)})logitsoutputs[0][0,-1,:]next_tokenint(np.argmax(logits))ifnext_token32007:# |end|breakgenerated_tokens.append(next_token)input_ids.append(next_token)responseself._detokenize(generated_tokens)print(f[LLM] 回复:{response}(耗时:{time.time()-t0:.2f}s))returnresponsedef_text_to_phonemes(self,text):文本转音素IDVITS前端# 实际可使用 g2p 库如 g2p_en, pypinyin 等# 此处为简化示例importpypinyin# 将中文转为拼音再映射到音素ID需预先构建音素表pinyinspypinyin.lazy_pinyin(text)# 简单映射实际需要完整的音素集phoneme_ids[ord(p[0])%100forpinpinyinsifp]# 占位returnphoneme_idsdefsynthesize_speech(self,text:str)-np.ndarray:语音合成 - VITS on NPUprint([TTS] 正在合成语音...)t0time.time()phoneme_idsself._text_to_phonemes(text)input_datanp.array([phoneme_ids],dtypenp.int64)input_lengthsnp.array([len(phoneme_ids)],dtypenp.int64)audio_outputself.tts_session.run(None,{input:input_data,input_lengths:input_lengths,scales:np.array([0.667,1.0,0.8],dtypenp.float32)})[0]print(f[TTS] 合成完成 (耗时:{time.time()-t0:.2f}s))returnaudio_output.squeeze()defaudio_callback(self,indata,frames,time_info,status):音频流回调 - 实时处理audio_chunkindata[:,0].copy()is_speechself.vad_detect(audio_chunk)ifis_speech:ifnotself.is_speaking:self.is_speakingTrueself.speech_frames[]self.speech_frames.append(audio_chunk)else:ifself.is_speakingandlen(self.speech_frames)10:self.is_speakingFalsespeech_audionp.concatenate(self.speech_frames)self._process_utterance(speech_audio)def_process_utterance(self,audio:np.ndarray):处理一段完整语音textself.transcribe(audio)responseself.generate_response(text)audio_responseself.synthesize_speech(response)sd.play(audio_response,samplerate22050)sd.wait()defstart(self):启动语音助手print([X2 Elite Voice] 语音助手启动请说话...)withsd.InputStream(samplerateself.sample_rate,channels1,blocksizeself.chunk_size,callbackself.audio_callback):input(按Enter键停止...\n)if__name____main__:assistantX2EliteVoiceAssistant()assistant.start()六、性能数据6.1 各模块延迟与实时率模型精度延迟实时率Whisper-smallINT8~180ms/chunk5.5x 实时Phi-3-mini (3.8B)INT4~15 tokens/s—VITS-ChineseINT8~50ms/句20x 实时6.2 端到端典型耗时一段5秒语音阶段耗时VAD 语音采集实时Whisper 识别~0.5-0.8sPhi-3 生成约20 tokens~1.3sVITS 合成~0.05s总计~1.8-2.1s七、优化建议流式ASR可改用 Whisper 的实时流式模式需自定义状态管理进一步降低延迟。LLM 预热首次推理较慢含缓存编译后续调用会明显加快。VAD 参数调优根据实际环境调整speech_prob阈值0.5 可上下浮动。内存管理Phi-3-mini 约占用 2-3GB 内存建议系统内存 ≥ 16GB。音频设备使用高质量麦克风可提升 ASR 准确率。八、常见问题问题解决方案VAD 误触发提高阈值到 0.7 或使用更长的静音判定时间Whisper 识别错误检查音频采样率是否为 16000或使用 larger 模型LLM 输出不符合预期调整 prompt 格式或使用 system promptTTS 音质差更换 VITS 预训练模型或调整 scales 参数【下篇预告】语音助手已经能听会说了但还缺一点“想象力”。下一篇我们将开始AIGC文生图的上半部分在X2 Elite上跑Stable Diffusion 1.5实现2秒一张512x512图片完全离线。
骁龙X2 Elite边缘AI应用开发实战(3): 端侧智能语音助手全链路实现
发布时间:2026/6/12 2:47:07
【上篇回顾】上一篇我们实现了实时视觉检测NPU推理延迟低至5ms通过流水线设计达到了200 FPS。这一篇我们将挑战更复杂的多模型流水线——语音助手从麦克风输入到音箱输出全部在X2 Elite本地完成。一、场景描述在骁龙X2 Elite上实现端侧智能语音助手实时语音活动检测VAD检测用户是否在说话流式语音识别ASR使用 Whisper 模型将语音转文字本地大语言模型响应LLM使用 Phi-3-mini 生成回复语音合成输出TTS使用 VITS 模型将回复转为语音目标完全离线运行所有模型部署在NPU上端到端延迟 500ms不含LLM生成。二、全链路AI应用开发流程图如下三、模型选型与量化模块模型量化格式后端说明VADSilero VADINT8NPU轻量语音活动检测ASRWhisper-smallINT8NPU编码器解码器80M参数LLMPhi-3-mini (3.8B)INT4NPU微软开源小语言模型TTSVITS-ChineseINT8NPU端到端语音合成四、语音处理Pipeline架构图语音处理Pipeline示意图如下麦克风输入 ↓ ┌─────────────────────────────────────────────────────────────┐ │ 音频流 (16kHz, 512/帧) │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ VAD (Silero on NPU) │ │ • 实时检测语音活动 │ │ • 输出is_speech (bool) │ └─────────────────────────────────────────────────────────────┘ ↓ (语音结束) ┌─────────────────────────────────────────────────────────────┐ │ ASR (Whisper on NPU) │ │ • Mel特征提取 │ │ • Encoder → Decoder自回归 │ │ • 输出文本 │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ LLM (Phi-3-mini on NPU) │ │ • Prompt构造 Tokenize │ │ • 自回归生成 │ │ • 输出回复文本 │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ TTS (VITS on NPU) │ │ • 文本→音素 │ │ • VITS推理 │ │ • 输出音频 (22.05kHz) │ └─────────────────────────────────────────────────────────────┘ ↓ 扬声器播放五、完整代码实现importnumpyasnpimportonnxruntimeasortimportsounddeviceassdfromcollectionsimportdequeimporttimeclassX2EliteVoiceAssistant:X2 Elite端侧语音助手 - 完全离线全链路NPU加速def__init__(self):# NPU配置与视觉篇保持一致self.npu_providers[(QNNExecutionProvider,{backend_path:QnnHtp.dll,htp_performance_mode:burst,enable_htp_fp16_precision:1,qnn_context_cache_enable:1,qnn_context_cache_path:./cache/voice_cache.bin,htp_arch:77,}),CPUExecutionProvider]print([X2 Elite Voice] 正在加载模型到NPU...)load_starttime.time()# 1. 加载VAD模型 (Silero)self.vad_sessionort.InferenceSession(silero_vad.onnx,providersself.npu_providers)# 2. 加载Whisper (编码器解码器)self.whisper_encoderort.InferenceSession(whisper_encoder.onnx,providersself.npu_providers)self.whisper_decoderort.InferenceSession(whisper_decoder.onnx,providersself.npu_providers)# 3. 加载LLM (Phi-3-mini INT4)self.llm_sessionort.InferenceSession(phi3_mini_int4_qnn.onnx,providersself.npu_providers)# 4. 加载TTS (VITS)self.tts_sessionort.InferenceSession(vits_chinese_int8.onnx,providersself.npu_providers)load_endtime.time()print(f[X2 Elite Voice] 所有模型加载完成耗时{load_end-load_start:.1f}s)# 音频参数self.sample_rate16000# Whisper 标准采样率self.chunk_size512# 32ms per chunkself.audio_bufferdeque(maxlenself.sample_rate*30)# 30秒缓冲self.is_speakingFalseself.speech_frames[]defvad_detect(self,audio_chunk:np.ndarray)-bool:语音活动检测 - Silero VAD on NPUinput_dataaudio_chunk.astype(np.float32).reshape(1,-1)srnp.array([self.sample_rate],dtypenp.int64)resultself.vad_session.run(None,{input:input_data,sr:sr})speech_probresult[0][0]returnspeech_prob0.5def_extract_mel(self,audio):提取Mel频谱Whisper预处理- 完整实现# 【补充】原文件第13页给出了以下完整实现简化版# 实际可使用 librosa 或 Whisper 原生的 log_mel_spectrogram# 参数采样率16000FFT窗口400步长160Mel频带80importlibrosa# 计算Mel频谱mel_speclibrosa.feature.melspectrogram(yaudio,srself.sample_rate,n_mels80,n_fft400,hop_length160,power2.0)# 转换为对数刻度log_melnp.log(mel_spec1e-10)# 归一化到[-1, 1]Whisper期望的输入范围log_mel(log_mel-log_mel.mean())/(log_mel.std()1e-8)returnlog_mel.astype(np.float32)# 形状: (80, time_frames)deftranscribe(self,audio:np.ndarray)-str:语音识别 - Whisper on NPUprint([ASR] 开始识别...)t0time.time()# 提取Mel特征mel_featuresself._extract_mel(audio)# (80, T)# Encoder推理NPUencoder_outputself.whisper_encoder.run(None,{mel:mel_features[np.newaxis,...]# 添加batch维度})[0]# Decoder自回归生成NPUtokens[50258]# |startoftranscript|for_inrange(448):decoder_inputnp.array([tokens],dtypenp.int64)logitsself.whisper_decoder.run(None,{tokens:decoder_input,audio_features:encoder_output})[0]next_tokennp.argmax(logits[0,-1,:])ifnext_token50257:# |endoftext|breaktokens.append(int(next_token))# 解码token为文本需使用WhisperTokenizerfromtransformersimportWhisperTokenizer tokenizerWhisperTokenizer.from_pretrained(openai/whisper-small)texttokenizer.decode(tokens,skip_special_tokensTrue)print(f[ASR] 识别结果:{text}(耗时:{time.time()-t0:.2f}s))returntextdef_tokenize(self,text):文本转token IDPhi-3 tokenizerfromtransformersimportAutoTokenizer tokenizerAutoTokenizer.from_pretrained(microsoft/Phi-3-mini-4k-instruct)returntokenizer.encode(text)def_detokenize(self,tokens):token ID转文本Phi-3fromtransformersimportAutoTokenizer tokenizerAutoTokenizer.from_pretrained(microsoft/Phi-3-mini-4k-instruct)returntokenizer.decode(tokens)defgenerate_response(self,user_text:str)-str:LLM响应生成 - Phi-3-mini on NPUprint([LLM] 正在生成回复...)t0time.time()# 构造promptPhi-3聊天格式promptf|user|\n{user_text}|end|\n|assistant|\ninput_idsself._tokenize(prompt)generated_tokens[]for_inrange(256):outputsself.llm_session.run(None,{input_ids:np.array([input_ids],dtypenp.int64)})logitsoutputs[0][0,-1,:]next_tokenint(np.argmax(logits))ifnext_token32007:# |end|breakgenerated_tokens.append(next_token)input_ids.append(next_token)responseself._detokenize(generated_tokens)print(f[LLM] 回复:{response}(耗时:{time.time()-t0:.2f}s))returnresponsedef_text_to_phonemes(self,text):文本转音素IDVITS前端# 实际可使用 g2p 库如 g2p_en, pypinyin 等# 此处为简化示例importpypinyin# 将中文转为拼音再映射到音素ID需预先构建音素表pinyinspypinyin.lazy_pinyin(text)# 简单映射实际需要完整的音素集phoneme_ids[ord(p[0])%100forpinpinyinsifp]# 占位returnphoneme_idsdefsynthesize_speech(self,text:str)-np.ndarray:语音合成 - VITS on NPUprint([TTS] 正在合成语音...)t0time.time()phoneme_idsself._text_to_phonemes(text)input_datanp.array([phoneme_ids],dtypenp.int64)input_lengthsnp.array([len(phoneme_ids)],dtypenp.int64)audio_outputself.tts_session.run(None,{input:input_data,input_lengths:input_lengths,scales:np.array([0.667,1.0,0.8],dtypenp.float32)})[0]print(f[TTS] 合成完成 (耗时:{time.time()-t0:.2f}s))returnaudio_output.squeeze()defaudio_callback(self,indata,frames,time_info,status):音频流回调 - 实时处理audio_chunkindata[:,0].copy()is_speechself.vad_detect(audio_chunk)ifis_speech:ifnotself.is_speaking:self.is_speakingTrueself.speech_frames[]self.speech_frames.append(audio_chunk)else:ifself.is_speakingandlen(self.speech_frames)10:self.is_speakingFalsespeech_audionp.concatenate(self.speech_frames)self._process_utterance(speech_audio)def_process_utterance(self,audio:np.ndarray):处理一段完整语音textself.transcribe(audio)responseself.generate_response(text)audio_responseself.synthesize_speech(response)sd.play(audio_response,samplerate22050)sd.wait()defstart(self):启动语音助手print([X2 Elite Voice] 语音助手启动请说话...)withsd.InputStream(samplerateself.sample_rate,channels1,blocksizeself.chunk_size,callbackself.audio_callback):input(按Enter键停止...\n)if__name____main__:assistantX2EliteVoiceAssistant()assistant.start()六、性能数据6.1 各模块延迟与实时率模型精度延迟实时率Whisper-smallINT8~180ms/chunk5.5x 实时Phi-3-mini (3.8B)INT4~15 tokens/s—VITS-ChineseINT8~50ms/句20x 实时6.2 端到端典型耗时一段5秒语音阶段耗时VAD 语音采集实时Whisper 识别~0.5-0.8sPhi-3 生成约20 tokens~1.3sVITS 合成~0.05s总计~1.8-2.1s七、优化建议流式ASR可改用 Whisper 的实时流式模式需自定义状态管理进一步降低延迟。LLM 预热首次推理较慢含缓存编译后续调用会明显加快。VAD 参数调优根据实际环境调整speech_prob阈值0.5 可上下浮动。内存管理Phi-3-mini 约占用 2-3GB 内存建议系统内存 ≥ 16GB。音频设备使用高质量麦克风可提升 ASR 准确率。八、常见问题问题解决方案VAD 误触发提高阈值到 0.7 或使用更长的静音判定时间Whisper 识别错误检查音频采样率是否为 16000或使用 larger 模型LLM 输出不符合预期调整 prompt 格式或使用 system promptTTS 音质差更换 VITS 预训练模型或调整 scales 参数【下篇预告】语音助手已经能听会说了但还缺一点“想象力”。下一篇我们将开始AIGC文生图的上半部分在X2 Elite上跑Stable Diffusion 1.5实现2秒一张512x512图片完全离线。