《HarmonyOS技术精讲-Core Speech Kit(基础语音服务)》第2篇:语音识别核心功能——流式与非流式实现

发布时间:2026/7/5 2:00:09
《HarmonyOS技术精讲-Core Speech Kit(基础语音服务)》第2篇:语音识别核心功能——流式与非流式实现 HarmonyOS技术精讲-Core Speech Kit基础语音服务第2篇语音识别核心功能——流式与非流式实现语音识别在HarmonyOS应用开发中很多场景都绕不开。比如语音搜索、语音转写、语音控制等。官方提供的Core Speech Kit基础语音服务里SpeechRecognizer这个API是核心入口。很多人第一次接触时会发现官方示例能运行但放到实际项目里状态同步、生命周期、回调处理这些细节很容易出问题。这个功能本身不复杂但不同场景选错模式体验会差很多。流式识别适合实时反馈比如边说话边出文字一次性识别适合离线快速处理一段固定的音频。选错方案要么延迟太高要么资源浪费。它解决什么问题SpeechRecognizer提供了两种语音识别模式流式识别Streaming边输入边输出结果适合实时语音转写、语音搜索。非流式识别One-shot输入一段完整的音频一次性返回识别结果适合语音指令、固定音频转写。特性流式识别非流式识别输入方式持续麦克风输入一次性音频文件实时性高边说话边出结果低等待完整音频处理延迟低结果逐步返回较高需完整处理后返回适用场景实时字幕、语音搜索离线命令、一键转写资源消耗持续占用麦克风和网络单次请求占用少离线支持支持需加载离线模型支持需加载离线模型推荐场景流式语音转字幕、语音搜索、语音助手实时交互非流式语音指令如“打开设置”、会议录音转写不推荐场景不需要实时反馈的不应该用流式浪费资源需要快速响应的不应该用非流式用户等待体验差环境说明DevEco Studio 版本DevEco Studio 6.1.0 及以上 HarmonyOS SDK 版本HarmonyOS 6.1.0(23) 及以上 目标设备手机 / 平板核心实现流式语音识别流式识别核心是startListening和stopListening。关键在于正确设置setListener的回调处理onResults、onError、onEndOfSpeech等事件。// StreamingRecognizer.etsimport{speechRecognizer}fromkit.CoreSpeechKit;import{BusinessError}fromkit.BasicServicesKit;import{audio}fromkit.AudioKit;EntryComponentstruct StreamingRecognizerPage{StaterecognizedText:string;StateisListening:booleanfalse;privaterecognizer:speechRecognizer.SpeechRecognizer|nullnull;privateaudioCapturer:audio.AudioCapturer|nullnull;aboutToAppear():void{this.initRecognizer();}aboutToDisappear():void{this.stopListening();this.destroyRecognizer();}privateinitRecognizer():void{try{this.recognizerspeechRecognizer.createRecognizer();this.recognizer.setListener({onResults:(text:string){// 流式结果逐步更新this.recognizedTexttext;},onError:(error:BusinessError){console.error(Recognizer error:,error.message);this.isListeningfalse;},onEndOfSpeech:(){// 用户停止说话自动结束this.isListeningfalse;},onStartListening:(){console.log(开始监听);}});}catch(error){console.error(Create recognizer failed:,error.message);}}privatestartListening():void{if(!this.recognizer){return;}try{// 配置识别参数语言、模式constconfig:speechRecognizer.SpeechRecognizerConfig{language:zh-CN,// 简体中文recognitionMode:speechRecognizer.RecognitionMode.STREAMING,// 流式模式enablePunctuation:true,// 启用标点accuracy:speechRecognizer.RecognitionAccuracy.NORMAL};this.recognizer.startListening(config);this.isListeningtrue;this.recognizedText;}catch(error){console.error(Start listening failed:,error.message);}}privatestopListening():void{if(this.recognizerthis.isListening){try{this.recognizer.stopListening();}catch(error){console.error(Stop listening failed:,error.message);}this.isListeningfalse;}}privatedestroyRecognizer():void{if(this.recognizer){try{this.recognizer.destroy();}catch(error){console.error(Destroy recognizer failed:,error.message);}this.recognizernull;}}build(){Column({space:20}){Text(流式语音识别).fontSize(24).fontWeight(FontWeight.Bold)Text(this.recognizedText||等待语音输入...).width(90%).height(200).backgroundColor(#F5F5F5).borderRadius(12).padding(16).fontSize(18).textAlign(TextAlign.Start)Button(this.isListening?停止录音:开始录音).width(200).height(50).onClick((){if(this.isListening){this.stopListening();}else{this.startListening();}})}.width(100%).height(100%).padding(20)}}注意事项setListener必须在startListening之前调用否则会错过回调页面销毁时一定要调用destroy()否则会泄漏资源流式模式下onResults会频繁回调每次更新新的识别结果核心实现非流式一次性识别非流式识别适合传入一个完整的音频文件支持PCM、WAV等格式。核心是recognize方法传入音频数据通过回调返回结果。// OneShotRecognizer.etsimport{speechRecognizer}fromkit.CoreSpeechKit;import{BusinessError}fromkit.BasicServicesKit;import{fileIo}fromkit.CoreFileKit;import{common}fromkit.AbilityKit;EntryComponentstruct OneShotRecognizerPage{StateresultText:string;StateisProcessing:booleanfalse;privaterecognizer:speechRecognizer.SpeechRecognizer|nullnull;aboutToAppear():void{this.initRecognizer();}aboutToDisappear():void{this.destroyRecognizer();}privateinitRecognizer():void{try{this.recognizerspeechRecognizer.createRecognizer();this.recognizer.setListener({// 非流式一次性识别结果onResults:(text:string){this.resultTexttext;this.isProcessingfalse;},onError:(error:BusinessError){console.error(Recognizer error:,error.message);this.isProcessingfalse;}});}catch(error){console.error(Create recognizer failed:,error.message);}}privateasyncstartRecognition():Promisevoid{if(!this.recognizer||this.isProcessing){return;}this.isProcessingtrue;this.resultText;try{// 读取音频文件constcontextgetContext(this)ascommon.UIAbilityContext;constfileUri:stringcontext.cacheDir/test_audio.pcm;constfile:fileIo.FileawaitfileIo.open(fileUri,fileIo.OpenMode.READ_ONLY);constbuffer:ArrayBuffernewArrayBuffer(fileIo.statSync(file.fd).size);awaitfileIo.read(file.fd,buffer);fileIo.close(file);// 配置并执行识别constconfig:speechRecognizer.SpeechRecognizerConfig{language:zh-CN,recognitionMode:speechRecognizer.RecognitionMode.ONE_SHOT,// 一次性模式enablePunctuation:true};// 第二个参数是音频格式this.recognizer.recognize(buffer,config,speechRecognizer.AudioFormat.PCM_16BIT);}catch(error){console.error(Recognition failed:,error.message);this.isProcessingfalse;}}privatedestroyRecognizer():void{if(this.recognizer){try{this.recognizer.destroy();}catch(error){console.error(Destroy recognizer failed:,error.message);}this.recognizernull;}}build(){Column({space:20}){Text(一次性语音识别).fontSize(24).fontWeight(FontWeight.Bold)Text(this.resultText||请选择音频文件进行识别).width(90%).height(200).backgroundColor(#F5F5F5).borderRadius(12).padding(16).fontSize(18).textAlign(TextAlign.Start)Button(this.isProcessing?识别中...:开始识别从缓存读取音频).width(200).height(50).enabled(!this.isProcessing).onClick((){this.startRecognition();})}.width(100%).height(100%).padding(20)}}注意事项一次性识别的音频格式必须与recognize方法传入的参数一致否则识别失败音频文件路径需要是应用沙箱内的路径不能是外部路径一次性识别不支持部分结果只有等处理完成后才回调onResults配置热词与自定义词库热词功能可以提升特定词汇的识别准确率。比如医疗、法律等专业领域的术语。// 配置热词privateaddCustomWords(words:string[]):void{if(!this.recognizer){return;}try{// 自定义词汇接口this.recognizer.addWords(words);console.log(Custom words added:,words.join(, ));}catch(error){console.error(Add custom words failed:,error.message);}}// 使用示例privateaddHotWords():void{consthotWords:string[][鸿蒙,ArkTS,HarmonyOS,语音识别];this.addCustomWords(hotWords);}注意事项热词最好在startListening或recognize之前添加热词列表长度有限制约50-100个具体看设备能力热词只对当前识别器实例有效重新创建后需要重新添加常见问题与踩坑记录问题1页面返回后识别器状态丢失现象页面跳转到其他页面再返回后调用startListening报错或者回调不触发。原因SpeechRecognizer实例在页面销毁时没有被正确释放重新进入页面时创建了新实例但旧的实例可能还持有音频资源。解决方案严格遵循生命周期aboutToAppear中创建aboutToDisappear中销毁不要在页面间共享同一个识别器实例每个页面独立创建如果使用单页面架构确保在onPageHide时停止识别onPageShow时恢复问题2多次快速开始识别导致崩溃现象用户连续快速点击“开始录音”按钮应用闪退或卡死。原因SpeechRecognizer内部状态机不支持在startListening后未stopListening前再次调用startListening。连续快速点击会破坏状态。解决方案// 使用防抖或状态锁定privatestartListeningSafe():void{if(this.isListening||this.isProcessing){console.warn(Recognizer is busy, ignore this request);return;}// 确保先停止再启动if(this.recognizer){try{this.recognizer.stopListening();}catch(e){// 忽略未启动时的错误}}this.startListening();}最佳实践不要在回调里直接做耗时操作。onResults回调是在异步线程中执行的直接在里面更新UI没有问题但如果做文件IO、网络请求会阻塞后续回调处理。建议使用postMessage或runOnMainThread回到主线程处理。初始化识别器时检查权限。语音识别需要ohos.permission.MICROPHONE权限。如果没有提前授权startListening会静默失败。建议在页面加载时先请求权限。合理选择识别模式。流式识别内部会持续占用麦克风和编解码资源如果页面不可见比如被压入后台应该主动调用stopListening释放资源。一次性识别则不需要直接完成后自动释放。FAQQ为什么流式识别在真机上一直返回空字符串模拟器正常A模拟器通常使用虚拟麦克风输入真机需要确认是否授权ohos.permission.MICROPHONE权限以及是否有其他应用占用了麦克风。Q页面返回后重新进入识别页面识别结果不更新怎么办A检查aboutToAppear中是否重新创建了识别器实例。如果创建了但旧实例没有正确销毁会导致识别器状态冲突。建议在aboutToDisappear中调用destroy()。Q离线模式下首次使用需要下载模型下载失败如何处理A离线识别需要下载语音模型包约100-200MB下载过程是异步的。建议在aboutToAppear中调用speechRecognizer.downloadModel监听下载进度如果失败可以提示用户检查网络或切换在线模式。