HarmonyOS开发:智能语音助手全栈实现
HarmonyOS开发:智能语音助手全栈实现
核心要点:基于HarmonyOS语音识别、语音合成、自然语言理解构建端到端智能语音助手
一、背景与动机
想象一下这个场景:你正在厨房做饭,双手沾满了面粉,手机响了——你想查一下明天的天气。这时候如果还要擦手、解锁、打开天气App,那体验简直太糟糕了。但如果只需要说一句"明天天气怎么样",就能得到答案,那感觉就完全不同了。
语音交互正在从"锦上添花"变成"刚需"。开车时查导航、做饭时设定时、运动时切歌——这些场景下,语音是唯一自然的交互方式。而HarmonyOS作为全场景操作系统,天然适合语音交互——手机、手表、音箱、车机,都需要语音能力。
本文将带你从零实现一个完整的智能语音助手,涵盖语音识别(ASR)、自然语言理解(NLU)、技能执行、语音合成(TTS)四大模块。这不是一个玩具Demo,而是一个可以扩展的、工程化的全栈实现。
二、核心原理
2.1 语音助手架构
一个完整的语音助手,本质上是一个"听→懂→做→说"的闭环系统。用户说话→语音识别转文字→自然语言理解提取意图→执行对应技能→语音合成播报结果。
flowchart TD
A[🎤 用户语音输入] --> B[语音识别 ASR]
B --> C[原始文本]
C --> D[自然语言理解 NLU]
D --> E[意图识别]
D --> F[槽位提取]
E --> G{意图路由}
F --> G
G -->|天气查询| H1[天气技能]
G -->|定时提醒| H2[提醒技能]
G -->|音乐播放| H3[音乐技能]
G -->|系统控制| H4[系统技能]
G -->|通用问答| H5[对话技能]
H1 --> I[技能执行结果]
H2 --> I
H3 --> I
H4 --> I
H5 --> I
I --> J[语音合成 TTS]
J --> K[🔊 语音播报]
classDef primary fill:#4F46E5,stroke:#3730A3,color:#fff
classDef warning fill:#F59E0B,stroke:#D97706,color:#fff
classDef error fill:#EF4444,stroke:#DC2626,color:#fff
classDef info fill:#06B6D4,stroke:#0891B2,color:#fff
classDef purple fill:#8B5CF6,stroke:#7C3AED,color:#fff
class A,B,C primary
class D,E,F info
class G warning
class H1,H2,H3,H4,H5 purple
class I,J,K error
2.2 语音识别(ASR)原理
HarmonyOS的语音识别支持两种模式:
- 短语音识别:适合指令类场景,录音结束后一次性返回结果
- 长语音识别:适合会议记录、语音输入等场景,实时流式返回
核心流程:音频采集→VAD(语音活动检测)→特征提取→声学模型解码→语言模型重排序→输出文本
2.3 自然语言理解(NLU)原理
NLU模块负责从文本中提取"意图+槽位":
- 意图(Intent):用户想做什么,如"查天气"“设闹钟”
- 槽位(Slot):意图的参数,如"明天"“北京”
例如:“明天北京天气怎么样” → 意图=天气查询,槽位={时间:明天, 地点:北京}
2.4 语音合成(TTS)原理
TTS将文本转为语音,核心流程:文本分析→韵律预测→声学特征生成→声码器合成→音频输出。HarmonyOS支持多种音色和语速调节。
三、代码实战
3.1 语音识别模块
// SpeechRecognizer.ets - 语音识别模块
import { speechRecognizer } from '@kit.AISpeechKit';
import { BusinessError } from '@kit.BasicServicesKit';
// 识别结果回调接口
export interface RecognizeCallback {
onResult: (text: string, isFinal: boolean) => void;
onError: (error: string) => void;
onVolumeChange: (volume: number) => void;
}
export class SpeechRecognizer {
private asrEngine: speechRecognizer.SpeechRecognitionEngine | null = null;
private isListening: boolean = false;
private callback: RecognizeCallback | null = null;
// 初始化语音识别引擎
async init(callback: RecognizeCallback): Promise<boolean> {
this.callback = callback;
try {
// 检查语音识别能力是否可用
const isAvailable = speechRecognizer.isAvailable();
if (!isAvailable) {
callback.onError('设备不支持语音识别');
return false;
}
// 创建识别引擎
const extraParams: Record<string, Object> = {
"locate": "CN", // 中国区
"language": "zh-CN", // 中文
"vadBegin": 2000, // 语音起始超时(ms)
"vadEnd": 3000, // 语音结束超时(ms)
"maxAudioDuration": 30000 // 最大录音时长(ms)
};
const initParams: speechRecognizer.CreateParams = {
language: 'zh-CN',
extraParams: extraParams
};
this.asrEngine = speechRecognizer.createEngine(initParams);
// 设置识别回调
const listener: speechRecognizer.RecognitionListener = {
// 识别结果回调
onResult: (result: speechRecognizer.RecognitionResult) => {
if (result.isFinal) {
// 最终结果
this.callback?.onResult(result.result, true);
this.isListening = false;
} else {
// 中间结果(实时显示)
this.callback?.onResult(result.result, false);
}
},
// 识别完成
onComplete: () => {
this.isListening = false;
console.info('[ASR] 识别完成');
},
// 错误回调
onError: (error: BusinessError) => {
this.isListening = false;
this.callback?.onError(`识别错误: ${error.code} - ${error.message}`);
},
// 音量变化
onVolumeChange: (volume: number) => {
this.callback?.onVolumeChange(volume);
}
};
this.asrEngine.setListener(listener);
console.info('[ASR] 语音识别引擎初始化成功');
return true;
} catch (error) {
const err = error as BusinessError;
callback.onError(`初始化失败: ${err.code} - ${err.message}`);
return false;
}
}
// 开始监听
startListening(): boolean {
if (!this.asrEngine || this.isListening) {
return false;
}
try {
const recognizerParams: speechRecognizer.StartParams = {
sessionId: `session_${Date.now()}`,
audioInfo: {
audioType: 'pcm',
sampleRate: 16000,
soundChannel: 1,
sampleBit: 16
},
extraParams: {
"vadBegin": 2000,
"vadEnd": 3000,
"maxAudioDuration": 30000
}
};
this.asrEngine.startListening(recognizerParams);
this.isListening = true;
console.info('[ASR] 开始监听');
return true;
} catch (error) {
const err = error as BusinessError;
this.callback?.onError(`启动失败: ${err.code}`);
return false;
}
}
// 停止监听
stopListening(): void {
if (this.asrEngine && this.isListening) {
this.asrEngine.finish(`session_stop_${Date.now()}`);
this.isListening = false;
}
}
// 取消识别
cancel(): void {
if (this.asrEngine) {
this.asrEngine.cancel(`session_cancel_${Date.now()}`);
this.isListening = false;
}
}
// 释放引擎
release(): void {
if (this.asrEngine) {
this.asrEngine.shutdown();
this.asrEngine = null;
this.isListening = false;
}
}
// 获取监听状态
getIsListening(): boolean {
return this.isListening;
}
}
3.2 自然语言理解与技能路由
// NLUEngine.ets - 自然语言理解与技能路由
// 意图定义
export enum IntentType {
WEATHER = 'weather', // 天气查询
ALARM = 'alarm', // 闹钟/定时
MUSIC = 'music', // 音乐播放
SYSTEM_CONTROL = 'system', // 系统控制
GENERAL_QA = 'general_qa', // 通用问答
UNKNOWN = 'unknown' // 未识别
}
// 意图识别结果
export interface NLUResult {
intent: IntentType;
slots: Record<string, string>; // 槽位键值对
confidence: number; // 置信度
rawText: string; // 原始文本
}
// 技能执行结果
export interface SkillResult {
success: boolean;
responseText: string; // 回复文本
action?: string; // 需要执行的动作
actionParams?: Record<string, string>; // 动作参数
}
export class NLUEngine {
// 意图关键词映射表
private intentPatterns: Map<IntentType, string[]> = new Map([
[IntentType.WATHER, ['天气', '气温', '温度', '下雨', '下雪', '刮风', '晴天', '阴天']],
[IntentType.ALARM, ['闹钟', '定时', '提醒', '叫醒', '分钟后', '小时后']],
[IntentType.MUSIC, ['播放', '音乐', '歌曲', '唱歌', '听歌']],
[IntentType.SYSTEM_CONTROL, ['打开', '关闭', '调亮', '调暗', '音量', '亮度', '蓝牙', 'WiFi']],
[IntentType.GENERAL_QA, ['什么是', '为什么', '怎么', '如何', '多少', '谁', '哪里']]
]);
// 时间关键词映射
private timePatterns: Map<string, string> = new Map([
['今天', 'today'], ['明天', 'tomorrow'], ['后天', 'dayAfterTomorrow'],
['现在', 'now'], ['早上', 'morning'], ['下午', 'afternoon'],
['晚上', 'evening'], ['今晚', 'tonight']
]);
// 解析用户输入
parse(text: string): NLUResult {
const normalizedText = text.trim();
// 1. 意图识别:基于关键词匹配
let bestIntent = IntentType.UNKNOWN;
let bestScore = 0;
for (const [intent, keywords] of this.intentPatterns) {
let score = 0;
for (const keyword of keywords) {
if (normalizedText.includes(keyword)) {
score += keyword.length; // 匹配的关键词越长,分数越高
}
}
if (score > bestScore) {
bestScore = score;
bestIntent = intent;
}
}
// 2. 槽位提取
const slots: Record<string, string> = {};
// 提取时间槽位
for (const [keyword, value] of this.timePatterns) {
if (normalizedText.includes(keyword)) {
slots['time'] = value;
break;
}
}
// 提取地点槽位(简化版:城市名列表匹配)
const cities = ['北京', '上海', '广州', '深圳', '杭州', '成都', '武汉', '南京', '西安', '重庆'];
for (const city of cities) {
if (normalizedText.includes(city)) {
slots['location'] = city;
break;
}
}
// 提取数字槽位(用于定时等场景)
const numberMatch = normalizedText.match(/(\d+)\s*(分钟|小时|秒)/);
if (numberMatch) {
slots['duration'] = numberMatch[1];
slots['unit'] = numberMatch[2];
}
// 提取音乐相关槽位
if (bestIntent === IntentType.MUSIC) {
const songMatch = normalizedText.match(/播放\s*(.+)/);
if (songMatch) {
slots['song'] = songMatch[1].replace(/的.*歌$/, '').trim();
}
}
// 计算置信度
const confidence = bestScore > 0 ? Math.min(bestScore / 5, 1.0) : 0;
return {
intent: bestIntent,
slots,
confidence,
rawText: normalizedText
};
}
}
// 技能执行器
export class SkillExecutor {
private nluEngine: NLUEngine = new NLUEngine();
// 执行技能
async execute(nluResult: NLUResult): Promise<SkillResult> {
switch (nluResult.intent) {
case IntentType.WEATHER:
return this.executeWeather(nluResult);
case IntentType.ALARM:
return this.executeAlarm(nluResult);
case IntentType.MUSIC:
return this.executeMusic(nluResult);
case IntentType.SYSTEM_CONTROL:
return this.executeSystemControl(nluResult);
case IntentType.GENERAL_QA:
return this.executeGeneralQA(nluResult);
default:
return {
success: false,
responseText: '抱歉,我还没有学会这个技能,你可以试试问我天气、设闹钟、播放音乐哦~'
};
}
}
// 天气技能
private async executeWeather(nluResult: NLUResult): Promise<SkillResult> {
const location = nluResult.slots['location'] || '当前位置';
const time = nluResult.slots['time'] || 'today';
// 实际项目中调用天气API
// 这里用模拟数据演示
const weatherData: Record<string, string> = {
'北京_today': '晴天,气温18-28°C,空气质量良好',
'上海_today': '多云,气温20-26°C,有轻微雾霾',
'北京_tomorrow': '阴转小雨,气温15-22°C,记得带伞'
};
const key = `${location}_${time}`;
const weather = weatherData[key] || `${location}${time === 'tomorrow' ? '明天' : '今天'}天气信息暂未获取到`;
return {
success: true,
responseText: weather,
action: 'weather',
actionParams: { location, time }
};
}
// 闹钟技能
private async executeAlarm(nluResult: NLUResult): Promise<SkillResult> {
const duration = nluResult.slots['duration'];
const unit = nluResult.slots['unit'] || '分钟';
if (!duration) {
return {
success: false,
responseText: '请告诉我要定多长时间的闹钟,比如"5分钟后提醒我"'
};
}
const minutes = unit === '小时' ? parseInt(duration) * 60 : parseInt(duration);
return {
success: true,
responseText: `好的,${duration}${unit}后我会提醒你`,
action: 'setAlarm',
actionParams: { duration: String(minutes * 60 * 1000) } // 毫秒
};
}
// 音乐技能
private async executeMusic(nluResult: NLUResult): Promise<SkillResult> {
const song = nluResult.slots['song'] || '随机推荐';
return {
success: true,
responseText: `正在为你播放${song}`,
action: 'playMusic',
actionParams: { song }
};
}
// 系统控制技能
private async executeSystemControl(nluResult: NLUResult): Promise<SkillResult> {
const text = nluResult.rawText;
if (text.includes('蓝牙') && text.includes('打开')) {
return {
success: true,
responseText: '已为你打开蓝牙',
action: 'enableBluetooth',
actionParams: {}
};
}
if (text.includes('音量')) {
return {
success: true,
responseText: '已为你调整音量',
action: 'adjustVolume',
actionParams: {}
};
}
return {
success: false,
responseText: '暂时不支持这个系统操作'
};
}
// 通用问答技能
private async executeGeneralQA(nluResult: NLUResult): Promise<SkillResult> {
// 实际项目中接入大模型API
return {
success: true,
responseText: `关于"${nluResult.rawText}",这是一个很好的问题。我正在学习中,稍后可以给你更好的回答。`,
action: 'generalQA',
actionParams: { question: nluResult.rawText }
};
}
}
3.3 语音合成模块
// SpeechSynthesizer.ets - 语音合成模块
import { textToSpeech } from '@kit.AISpeechKit';
import { BusinessError } from '@kit.BasicServicesKit';
export class SpeechSynthesizer {
private ttsEngine: textToSpeech.TextToSpeechEngine | null = null;
private isSpeaking: boolean = false;
// 初始化TTS引擎
async init(): Promise<boolean> {
try {
const isAvailable = textToSpeech.isAvailable();
if (!isAvailable) {
console.error('[TTS] 设备不支持语音合成');
return false;
}
const extraParams: Record<string, Object> = {
"style": "interaction", // 交互风格
"locate": "CN", // 中国区
"language": "zh-CN" // 中文
};
const initParams: textToSpeech.CreateParams = {
language: 'zh-CN',
person: 0, // 音色索引(0=女声)
online: 0, // 0=离线模式
extraParams: extraParams
};
this.ttsEngine = textToSpeech.createEngine(initParams);
// 设置回调
const listener: textToSpeech.SpeakListener = {
onStart: (requestId: string) => {
this.isSpeaking = true;
console.info(`[TTS] 开始播报: ${requestId}`);
},
onComplete: (requestId: string) => {
this.isSpeaking = false;
console.info(`[TTS] 播报完成: ${requestId}`);
},
onStop: (requestId: string) => {
this.isSpeaking = false;
},
onData: (buffer: ArrayBuffer, request: textToSpeech.Request) => {
// 音频数据回调(可用于自定义音频处理)
},
onError: (requestId: string, errorCode: number, errorMessage: string) => {
this.isSpeaking = false;
console.error(`[TTS] 播报错误: ${errorCode} - ${errorMessage}`);
}
};
this.ttsEngine.setListener(listener);
console.info('[TTS] 语音合成引擎初始化成功');
return true;
} catch (error) {
const err = error as BusinessError;
console.error(`[TTS] 初始化失败: ${err.code} - ${err.message}`);
return false;
}
}
// 语音播报
speak(text: string, speed: number = 1.0): boolean {
if (!this.ttsEngine || this.isSpeaking) {
return false;
}
try {
const speakParams: textToSpeech.SpeakParams = {
requestId: `tts_${Date.now()}`,
extraParams: {
"speed": speed.toString(), // 语速
"volume": "2", // 音量
"pitch": "1.0" // 音调
}
};
this.ttsEngine.speak(text, speakParams);
return true;
} catch (error) {
console.error(`[TTS] 播报失败: ${JSON.stringify(error)}`);
return false;
}
}
// 停止播报
stop(): void {
if (this.ttsEngine && this.isSpeaking) {
this.ttsEngine.stop();
this.isSpeaking = false;
}
}
// 是否正在播报
getIsSpeaking(): boolean {
return this.isSpeaking;
}
// 释放资源
release(): void {
if (this.ttsEngine) {
this.ttsEngine.shutdown();
this.ttsEngine = null;
this.isSpeaking = false;
}
}
}
3.4 完整的语音助手UI
// VoiceAssistantPage.ets - 智能语音助手主页面
import { router } from '@kit.ArkUI';
@Entry
@Component
struct VoiceAssistantPage {
@State isListening: boolean = false;
@State isSpeaking: boolean = false;
@State currentText: string = ''; // 实时识别文本
@State chatHistory: ChatMessage[] = []; // 对话历史
@State volumeLevel: number = 0; // 音量级别
@State pulseScale: number = 1.0; // 脉冲动画缩放
@State statusText: string = '点击麦克风开始对话';
// 引擎实例
private asr: SpeechRecognizer = new SpeechRecognizer();
private tts: SpeechSynthesizer = new SpeechSynthesizer();
private nlu: NLUEngine = new NLUEngine();
private executor: SkillExecutor = new SkillExecutor();
private scroller: Scroller = new Scroller();
// 脉冲动画定时器
private pulseTimer: number = -1;
aboutToAppear() {
this.initEngines();
}
async initEngines() {
// 初始化ASR
await this.asr.init({
onResult: (text: string, isFinal: boolean) => {
this.currentText = text;
if (isFinal) {
this.handleUserInput(text);
}
},
onError: (error: string) => {
this.statusText = `识别出错: ${error}`;
this.isListening = false;
this.stopPulseAnimation();
},
onVolumeChange: (volume: number) => {
this.volumeLevel = volume;
}
});
// 初始化TTS
await this.tts.init();
}
// 处理用户输入
async handleUserInput(text: string) {
// 添加用户消息
this.chatHistory.push({
role: 'user',
content: text,
timestamp: Date.now()
});
this.isListening = false;
this.statusText = '正在思考...';
this.stopPulseAnimation();
// NLU解析
const nluResult = this.nlu.parse(text);
// 执行技能
const skillResult = await this.executor.execute(nluResult);
// 添加助手回复
this.chatHistory.push({
role: 'assistant',
content: skillResult.responseText,
timestamp: Date.now()
});
// 语音播报
this.isSpeaking = true;
this.statusText = '正在回复...';
this.tts.speak(skillResult.responseText);
// 延迟重置状态(等待TTS完成)
setTimeout(() => {
this.isSpeaking = false;
this.statusText = '点击麦克风继续对话';
}, skillResult.responseText.length * 200); // 粗略估算播报时长
// 滚动到底部
setTimeout(() => {
this.scroller.scrollEdge(Edge.Bottom);
}, 100);
}
build() {
Column() {
// 标题栏
this.TitleBar();
// 对话区域
this.ChatArea();
// 底部控制区
this.ControlArea();
}
.width('100%')
.height('100%')
.backgroundColor('#0A0A1A')
}
@Builder TitleBar() {
Row() {
Text('🤖 智能语音助手')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
Blank()
Text(this.statusText)
.fontSize(12)
.fontColor('#9CA3AF')
}
.width('100%')
.padding({ left: 20, right: 20, top: 16, bottom: 8 })
}
@Builder ChatArea() {
List({ space: 12, scroller: this.scroller }) {
ForEach(this.chatHistory, (msg: ChatMessage) => {
ListItem() {
this.ChatBubble(msg);
}
})
// 实时识别文本
if (this.currentText && this.isListening) {
ListItem() {
Row() {
Text(this.currentText)
.fontSize(15)
.fontColor('#E0E7FF')
.maxLines(3)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
.width('75%')
.padding(12)
.borderRadius({ topLeft: 4, topRight: 16, bottomLeft: 16, bottomRight: 16 })
.backgroundColor('#312E81')
.alignItems(VerticalAlign.Start)
}
}
}
.width('100%')
.layoutWeight(1)
.padding({ left: 16, right: 16 })
}
@Builder ChatBubble(msg: ChatMessage) {
if (msg.role === 'user') {
// 用户消息 - 右对齐
Row() {
Blank()
Text(msg.content)
.fontSize(15)
.fontColor('#FFFFFF')
.padding(12)
.borderRadius({ topLeft: 16, topRight: 4, bottomLeft: 16, bottomRight: 16 })
.backgroundColor('#4F46E5')
.maxLines(10)
}
.width('100%')
} else {
// 助手消息 - 左对齐
Row() {
Text(msg.content)
.fontSize(15)
.fontColor('#E0E7FF')
.padding(12)
.borderRadius({ topLeft: 4, topRight: 16, bottomLeft: 16, bottomRight: 16 })
.backgroundColor('#1E1B4B')
.maxLines(10)
}
.width('80%')
.alignItems(VerticalAlign.Start)
}
}
@Builder ControlArea() {
Column() {
// 音量指示条
if (this.isListening) {
Row() {
ForEach([1, 2, 3, 4, 5, 6, 7, 8], (item: number) => {
Column()
.width(4)
.height(`${Math.min(this.volumeLevel * 3, item * 8)}%`)
.backgroundColor(this.volumeLevel * 3 > item * 8 ? '#4F46E5' : '#1A1A2E')
.borderRadius(2)
.margin({ right: 3 })
})
}
.width('60%')
.height(40)
.justifyContent(FlexAlign.Center)
.alignItems(VerticalAlign.Bottom)
}
// 麦克风按钮
Stack() {
// 外圈脉冲动画
Circle()
.width(this.isListening ? 100 : 80)
.height(this.isListening ? 100 : 80)
.fill(Color.Transparent)
.stroke(this.isListening ? '#EF4444' : '#4F46E5')
.strokeWidth(2)
.opacity(this.isListening ? 0.3 : 0)
.scale({ x: this.pulseScale, y: this.pulseScale })
// 内圈按钮
Circle()
.width(72)
.height(72)
.fill(this.isListening ? '#EF4444' : '#4F46E5')
.shadow({
radius: this.isListening ? 20 : 10,
color: this.isListening ? '#EF444480' : '#4F46E580',
offsetY: 4
})
// 麦克风图标
Text(this.isListening ? '⏹' : '🎤')
.fontSize(28)
.fontColor('#FFFFFF')
}
.width(100)
.height(100)
.margin({ top: 16, bottom: 16 })
.onClick(() => this.toggleListening())
// 快捷指令
Row() {
ForEach(['今天天气', '设5分钟闹钟', '播放音乐'], (cmd: string) => {
Text(cmd)
.fontSize(12)
.fontColor('#C7D2FE')
.backgroundColor('#1E1B4B')
.borderRadius(14)
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.margin({ right: 8 })
.onClick(() => this.handleUserInput(cmd))
})
}
.margin({ bottom: 24 })
}
.width('100%')
.alignItems(HorizontalAlign.Center)
.padding({ top: 8 })
}
// 切换监听状态
private toggleListening() {
if (this.isSpeaking) {
this.tts.stop();
this.isSpeaking = false;
}
if (this.isListening) {
this.asr.stopListening();
this.isListening = false;
this.stopPulseAnimation();
this.statusText = '已停止监听';
} else {
const started = this.asr.startListening();
if (started) {
this.isListening = true;
this.currentText = '';
this.statusText = '正在聆听...';
this.startPulseAnimation();
}
}
}
// 启动脉冲动画
private startPulseAnimation() {
this.stopPulseAnimation();
this.pulseTimer = setInterval(() => {
this.pulseScale = this.pulseScale === 1.0 ? 1.15 : 1.0;
}, 600);
}
// 停止脉冲动画
private stopPulseAnimation() {
if (this.pulseTimer !== -1) {
clearInterval(this.pulseTimer);
this.pulseTimer = -1;
}
this.pulseScale = 1.0;
}
aboutToDisappear() {
this.stopPulseAnimation();
this.asr.release();
this.tts.release();
}
}
// 对话消息数据结构
interface ChatMessage {
role: 'user' | 'assistant';
content: string;
timestamp: number;
}
四、踩坑与注意事项
4.1 麦克风权限
语音识别需要麦克风权限ohos.permission.MICROPHONE,这是用户授权级别的权限。必须在module.json5中声明并运行时请求。如果用户拒绝授权,ASR引擎初始化会失败。
{
"requestPermissions": [
{
"name": "ohos.permission.MICROPHONE",
"reason": "$string:mic_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
}
4.2 ASR引擎生命周期
这是最容易踩的坑。ASR引擎是有限资源,必须正确管理生命周期:
- 不要重复创建引擎:一个应用只需一个ASR实例
- 使用完毕必须调用shutdown():否则会占用系统资源,影响其他应用的语音功能
- 不要在onComplete中立即重新startListening:需要等待至少100ms间隔
4.3 TTS与ASR的冲突
如果TTS正在播报时启动ASR,TTS的声音会被麦克风拾取,导致"回声"问题。解决方案:
- TTS播报时自动暂停ASR
- 使用AEC(声学回声消除)技术
- 在TTS完成后再启动ASR
// 正确的流程
tts.speak(response);
// 等TTS播报完成后再启动ASR
// 在onComplete回调中重新startListening
4.4 后台语音识别
HarmonyOS对后台运行有限制,应用进入后台后ASR可能会被系统回收。如果需要后台持续识别(如语音唤醒),需要申请长时任务:
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
// 申请长时任务
backgroundTaskManager.startBackgroundRunning(
context,
backgroundTaskManager.BackgroundMode.AUDIO_RECORDING,
{ title: '语音助手', text: '正在后台监听语音指令' }
);
4.5 NLU的局限性
基于关键词的NLU方案简单但粗糙,容易误判。比如"打开窗户看看天气"可能被识别为"系统控制"而非"天气查询"。实际项目中建议:
- 使用HarmonyOS的NLU API(如果可用)
- 接入云端大模型做意图理解
- 增加上下文管理(多轮对话)
五、HarmonyOS 6适配
5.1 API变更
| 变更项 | HarmonyOS 5 | HarmonyOS 6 |
|---|---|---|
| ASR引擎 | speechRecognizer |
speechRecognizer(兼容) |
| TTS引擎 | textToSpeech |
textToSpeech(兼容) |
| 语音唤醒 | 不支持 | 新增voiceWakeUp API |
| 多音色 | 有限音色 | 新增情感化音色 |
| 离线模型 | 基础模型 | 增强版离线模型 |
5.2 语音唤醒
HarmonyOS 6最令人兴奋的新增功能是语音唤醒。应用可以注册唤醒词,在设备休眠状态下监听特定词语:
// HarmonyOS 6 新增
import { voiceWakeUp } from '@kit.AISpeechKit';
const wakeUpEngine = voiceWakeUp.createEngine({
wakeupWord: '小华小华',
sensitivity: voiceWakeUp.SensitivityLevel.MEDIUM
});
wakeUpEngine.on('wakeup', () => {
// 检测到唤醒词,启动ASR
asr.startListening();
});
5.3 迁移要点
- 语音唤醒集成:利用新的voiceWakeUp API实现"Hey 小华"唤醒功能
- 情感化TTS:使用新的音色参数让回复更自然
- 增强版离线模型:HarmonyOS 6的离线ASR准确率显著提升,建议优先使用离线模式
六、总结
本文从零实现了一个完整的HarmonyOS智能语音助手,核心知识点如下:
智能语音助手
├── 语音识别(ASR)
│ ├── speechRecognizer引擎创建与配置
│ ├── 实时识别与最终结果处理
│ ├── 音量回调与VAD参数
│ └── 生命周期管理(init→start→stop→shutdown)
├── 自然语言理解(NLU)
│ ├── 基于关键词的意图识别
│ ├── 槽位提取(时间/地点/数字/歌曲)
│ ├── 意图路由与技能分发
│ └── 置信度评估
├── 技能执行
│ ├── 天气查询技能
│ ├── 闹钟定时技能
│ ├── 音乐播放技能
│ ├── 系统控制技能
│ └── 通用问答技能
├── 语音合成(TTS)
│ ├── textToSpeech引擎创建
│ ├── 语速/音调/音量控制
│ └── 播报状态回调
├── UI交互
│ ├── 对话气泡界面
│ ├── 麦克风脉冲动画
│ ├── 音量可视化
│ └── 快捷指令
└── 工程化
├── 权限管理(麦克风)
├── ASR/TTS冲突处理
├── 后台长时任务
└── HarmonyOS 6语音唤醒
关键收获:
- ASR和TTS引擎是有限资源,必须严格管理生命周期
- TTS播报时不要同时启动ASR,否则会产生回声
- 基于关键词的NLU是起步方案,生产环境应接入大模型
- 后台语音识别需要申请长时任务权限
- HarmonyOS 6的语音唤醒能力将彻底改变语音助手的交互体验
语音助手是AI落地的最佳场景之一。HarmonyOS的端侧AI能力让语音交互更快速、更隐私、更可靠。随着语音唤醒等新能力的加入,语音助手将从"按住说话"进化到"随时唤醒",这是质的飞跃。
- 点赞
- 收藏
- 关注作者
评论(0)