【鸿蒙开发】第二十四章 AI - Core Speech Kit(基础语音服务)
目录
1 简介
1.1 场景介绍
1.2 约束与限制
2 文本转语音
2.1 场景介绍
2.2 约束与限制
2.3 开发步骤
2.4 设置播报策略
2.4.1 设置单词播报方式
2.4.2 设置数字播报策略
2.4.3 插入静音停顿
2.4.4 指定汉字发音
2.5 开发实例
3 语音识别
3.1 场景介绍
3.2 约束与限制
3.3 开发步骤
3.4 开发实例
1 简介
Core Speech Kit(基础语音服务)集成了语音类基础AI能力,包括文本转语音(TextToSpeech)及语音识别(SpeechRecognizer)能力,便于用户与设备进行互动,实现将实时输入的语音与文本之间相互转换。
1.1 场景介绍
- 文本转语音:将一段不超过10000字符的文本合成为语音并进行播报。
- 语音识别:将一段音频信息(短语音模式不超过60s,长语音模式不超过8h)转换为文本,可以将pcm音频文件或者实时语音转换为文字。
1.2 约束与限制
AI能力 | 约束 |
---|---|
文本转语音 |
|
语音识别 |
|
2 文本转语音
Core Speech Kit支持将一篇不超过10000字符的中文文本(简体中文、繁体中文、数字、中文语境下的英文)合成为语音,并以聆小珊女声音色中文播报。
开发者可对播报的策略进行设置,包括单词播报、数字播报、静音停顿、汉字发音策略。
2.1 场景介绍
手机/平板等设备在无网状态下,系统应用无障碍(屏幕朗读)接入文本转语音能力,为视障人士或不方便阅读场景提供播报能力。
2.2 约束与限制
该能力当前不支持模拟器。
2.3 开发步骤
1. 在使用文本转语音时,将实现文本转语音相关的类添加至工程。
import { textToSpeech } from '@kit.CoreSpeechKit';
import { BusinessError } from '@kit.BasicServicesKit';
2. 调用createEngine接口,创建textToSpeechEngine实例。createEngine接口提供了两种调用形式,当前以其中一种作为示例,其他方式可参考API参考。
let ttsEngine: textToSpeech.TextToSpeechEngine;// 设置创建引擎参数
let extraParam: Record<string, Object> = {"style": 'interaction-broadcast', "locate": 'CN', "name": 'EngineName'};
let initParamsInfo: textToSpeech.CreateEngineParams = {language: 'zh-CN',person: 0,online: 1,extraParams: extraParam
};// 调用createEngine方法
textToSpeech.createEngine(initParamsInfo, (err: BusinessError, textToSpeechEngine: textToSpeech.TextToSpeechEngine) => {if (!err) {console.info('Succeeded in creating engine');// 接收创建引擎的实例ttsEngine = textToSpeechEngine;} else {console.error(`Failed to create engine. Code: ${err.code}, message: ${err.message}.`);}
});
3. 得到TextToSpeechEngine实例对象后,实例化SpeakParams对象、SpeakListener对象,并传入待合成及播报的文本originalText,调用speak接口进行播报。
// 设置speak的回调信息
let speakListener: textToSpeech.SpeakListener = {// 开始播报回调onStart(requestId: string, response: textToSpeech.StartResponse) {console.info(`onStart, requestId: ${requestId} response: ${JSON.stringify(response)}`);},// 合成完成及播报完成回调onComplete(requestId: string, response: textToSpeech.CompleteResponse) {console.info(`onComplete, requestId: ${requestId} response: ${JSON.stringify(response)}`);},// 停止播报回调onStop(requestId: string, response: textToSpeech.StopResponse) {console.info(`onStop, requestId: ${requestId} response: ${JSON.stringify(response)}`);},// 返回音频流onData(requestId: string, audio: ArrayBuffer, response: textToSpeech.SynthesisResponse) {console.info(`onData, requestId: ${requestId} sequence: ${JSON.stringify(response)} audio: ${JSON.stringify(audio)}`);},// 错误回调onError(requestId: string, errorCode: number, errorMessage: string) {console.error(`onError, requestId: ${requestId} errorCode: ${errorCode} errorMessage: ${errorMessage}`);}
};
// 设置回调
ttsEngine.setListener(speakListener);
let originalText: string = 'Hello HarmonyOS';
// 设置播报相关参数
let extraParam: Record<string, Object> = {"queueMode": 0, "speed": 1, "volume": 2, "pitch": 1, "languageContext": 'zh-CN',
"audioType": "pcm", "soundChannel": 3, "playType": 1 };
let speakParams: textToSpeech.SpeakParams = {requestId: '123456', // requestId在同一实例内仅能用一次,请勿重复设置extraParams: extraParam
};
// 调用播报方法
// 开发者可以通过修改speakParams主动设置播报策略
ttsEngine.speak(originalText, speakParams);
4. (可选)当需要停止合成及播报时,可调用stop接口。
ttsEngine.stop();
5. (可选)当需要查询文本转语音服务是否处于忙碌状态时,可调用isBusy接口。
ttsEngine.isBusy();
6.(可选)当需要查询支持的语种音色信息时,可调用listVoices接口。
listVoices接口提供了两种调用形式,当前以其中一种作为示例,其他方式可参考API参考。
// 在组件中声明并初始化字符串voiceInfo
@State voiceInfo: string = "";// 设置查询相关参数
let voicesQuery: textToSpeech.VoiceQuery = {requestId: '12345678', // requestId在同一实例内仅能用一次,请勿重复设置online: 1
};
// 调用listVoices方法,以callback返回
ttsEngine.listVoices(voicesQuery, (err: BusinessError, voiceInfo: textToSpeech.VoiceInfo[]) => {if (!err) {// 接收目前支持的语种音色等信息this.voiceInfo = JSON.stringify(voiceInfo);console.info(`Succeeded in listing voices, voiceInfo is ${this.voiceInfo}`);} else {console.error(`Failed to list voices. Code: ${err.code}, message: ${err.message}`);}
});
2.4 设置播报策略
由于不同场景下,模型自动判断所选择的播报策略可能与实际需求不同,此章节提供对于播报策略进行主动设置的方法。
说明
以下取值说明均为有效取值,若所使用的数值在有效取值之外则播报结果可能与预期不符,并产生错误的播报结果。
2.4.1 设置单词播报方式
文本格式:[hN] (N=0/1/2)
N取值说明:
取值 | 说明 |
---|---|
0 | 智能判断单词播放方式。默认值为0。 |
1 | 逐个字母进行播报。 |
2 | 以单词方式进行播报。 |
文本示例:
"hello[h1] world"
hello使用单词发音,world及后续单词将会逐个字母进行发音。
2.4.2 设置数字播报策略
格式:[nN] (N=0/1/2)
N取值说明:
取值 | 说明 |
---|---|
0 | 智能判断数字处理策略。默认值为0。 |
1 | 作为号码逐个数字播报。 |
2 | 作为数值播报。超过18位数字不支持,自动按逐个数字进行播报。 |
文本示例:
"[n2]123[n1]456[n0]"
其中,123将会按照数值播报,456则会按照号码播报,而后的文本中的数字,均会自动判断
2.4.3 插入静音停顿
格式:[pN]
描述:N为无符号整数,单位为ms。
文本示例:
"你好[p500]小艺"
该句播报时,将会在“你好”后插入500ms的静音停顿。
2.4.4 指定汉字发音
汉字声调用后接一位数字1~5分别表示阴平、阳平、上声、去声和轻声5个声调。
格式:[=MN]
描述:M表示拼音,N表示声调。
N取值说明:
取值 | 说明 |
---|---|
1 | 阴平 |
2 | 阳平 |
3 | 上声 |
4 | 去声 |
5 | 轻声 |
文本示例:
"着[=zhuo2]手"
2.5 开发实例
点击按钮,播报一段文本。
import { textToSpeech } from '@kit.CoreSpeechKit';
import { BusinessError } from '@kit.BasicServicesKit';let ttsEngine: textToSpeech.TextToSpeechEngine;
@Entry
@Component
struct Index {@State createCount: number = 0;@State result: boolean = false;@State voiceInfo: string = "";@State text: string = "";@State textContent: string = "";@State utteranceId: string = "123456";@State originalText: string = "\n\t\t古人学问无遗力,少壮工夫老始成;\n\t\t" +"纸上得来终觉浅,绝知此事要躬行。\n\t\t";@State illegalText: string = "";build() {Column() {Scroll() {Column() {TextArea({ placeholder: 'Please enter tts original text', text: `${this.originalText}` }).margin(20).focusable(false).border({ width: 5, color: 0x317AE7, radius: 10, style: BorderStyle.Dotted }).onChange((value: string) => {this.originalText = value;console.info(`original text: ${this.originalText}`);})Button() {Text("CreateEngineByCallback").fontColor(Color.White).fontSize(20)}.type(ButtonType.Capsule).backgroundColor("#0x317AE7").width("80%").height(50).margin(10).onClick(() => {this.createCount++;console.info(`CreateTtsEngine:createCount:${this.createCount}`);this.createByCallback();})Button() {Text("speak").fontColor(Color.White).fontSize(20)}.type(ButtonType.Capsule).backgroundColor("#0x317AE7").width("80%").height(50).margin(10).onClick(() => {this.createCount++;this.speak();})Button() {Text("listVoicesCallback").fontColor(Color.White).fontSize(20)}.type(ButtonType.Capsule).backgroundColor("#0x317AE7").width("80%").height(50).margin(10).onClick(() => {this.listVoicesCallback();})Button() {Text("stop").fontColor(Color.White).fontSize(20)}.type(ButtonType.Capsule).backgroundColor("#0x317AE7").width("80%").height(50).margin(10).onClick(() => {// 停止播报console.info("Stop button clicked.");ttsEngine.stop();})Button() {Text("isBusy").fontColor(Color.White).fontSize(20)}.type(ButtonType.Capsule).backgroundColor("#0x317AE7").width("80%").height(50).margin(10).onClick(() => {// 查询播报状态let isBusy = ttsEngine.isBusy();console.info(`isBusy: ${isBusy}`);})Button() {Text("shutdown").fontColor(Color.White).fontSize(20)}.type(ButtonType.Capsule).backgroundColor("#0x317AA7").width("80%").height(50).margin(10).onClick(() => {// 释放引擎ttsEngine.shutdown();})}.layoutWeight(1)}.width('100%').height('100%')}}// 创建引擎,通过callback形式返回private createByCallback() {// 设置创建引擎参数let extraParam: Record<string, Object> = {"style": 'interaction-broadcast', "locate": 'CN', "name": 'EngineName'};let initParamsInfo: textToSpeech.CreateEngineParams = {language: 'zh-CN',person: 0,online: 1,extraParams: extraParam};// 调用createEngine方法textToSpeech.createEngine(initParamsInfo, (err: BusinessError, textToSpeechEngine: textToSpeech.TextToSpeechEngine) => {if (!err) {console.info('Succeeded in creating engine.');// 接收创建引擎的实例ttsEngine = textToSpeechEngine;} else {console.error(`Failed to create engine. Code: ${err.code}, message: ${err.message}.`);}});};// 调用speak播报方法private speak() {let speakListener: textToSpeech.SpeakListener = {// 开始播报回调onStart(requestId: string, response: textToSpeech.StartResponse) {console.info(`onStart, requestId: ${requestId} response: ${JSON.stringify(response)}`);},// 完成播报回调onComplete(requestId: string, response: textToSpeech.CompleteResponse) {console.info(`onComplete, requestId: ${requestId} response: ${JSON.stringify(response)}`);}, // 停止播报完成回调,调用stop方法并完成时会触发此回调onStop(requestId: string, response: textToSpeech.StopResponse) {console.info(`onStop, requestId: ${requestId} response: ${JSON.stringify(response)}`);},// 返回音频流onData(requestId: string, audio: ArrayBuffer, response: textToSpeech.SynthesisResponse) {console.info(`onData, requestId: ${requestId} sequence: ${JSON.stringify(response)} audio: ${JSON.stringify(audio)}`);},// 错误回调,播报过程发生错误时触发此回调onError(requestId: string, errorCode: number, errorMessage: string) {console.error(`onError, requestId: ${requestId} errorCode: ${errorCode} errorMessage: ${errorMessage}`);}};// 设置回调ttsEngine.setListener(speakListener);// 设置播报相关参数let extraParam: Record<string, Object> = {"queueMode": 0, "speed": 1, "volume": 2, "pitch": 1, "languageContext": 'zh-CN', "audioType": "pcm", "soundChannel": 3, "playType":1}let speakParams: textToSpeech.SpeakParams = {requestId: '123456-a', // requestId在同一实例内仅能用一次,请勿重复设置extraParams: extraParam};// 调用speak播报方法ttsEngine.speak(this.originalText, speakParams);};// 查询语种音色信息,以callback形式返回private listVoicesCallback() {// 设置查询相关参数let voicesQuery: textToSpeech.VoiceQuery = {requestId: '123456-b', // requestId在同一实例内仅能用一次,请勿重复设置online: 1};// 调用listVoices方法,以callback返回语种音色查询结果ttsEngine.listVoices(voicesQuery, (err: BusinessError, voiceInfo: textToSpeech.VoiceInfo[]) => {if (!err) {// 接收目前支持的语种音色等信息this.voiceInfo = JSON.stringify(voiceInfo);console.info(`Succeeded in listing voices, voiceInfo is ${voiceInfo}`);} else {console.error(`Failed to list voices. Code: ${err.code}, message: ${err.message}`);}});};
}
3 语音识别
将一段中文音频信息(中文、中文语境下的英文;短语音模式不超过60s,长语音模式不超过8h)转换为文本,音频信息可以为pcm音频文件或者实时语音。
3.1 场景介绍
手机/平板等设备在无网状态下,为听障人士或不方便收听音频场景提供音频转文本能力。
3.2 约束与限制
该能力当前不支持模拟器。
3.3 开发步骤
1. 在使用语音识别时,将实现语音识别相关的类添加至工程。
import { speechRecognizer } from '@kit.CoreSpeechKit';
import { BusinessError } from '@kit.BasicServicesKit';
2. 调用createEngine方法,对引擎进行初始化,并创建SpeechRecognitionEngine实例。
createEngine方法提供了两种调用形式,当前以其中一种作为示例,其他方式可参考API参考。
let asrEngine: speechRecognizer.SpeechRecognitionEngine;
let sessionId: string = '123456';
// 创建引擎,通过callback形式返回
// 设置创建引擎参数
let extraParam: Record<string, Object> = {"locate": "CN", "recognizerMode": "short"};
let initParamsInfo: speechRecognizer.CreateEngineParams = {language: 'zh-CN',online: 1,extraParams: extraParam
};
// 调用createEngine方法
speechRecognizer.createEngine(initParamsInfo, (err: BusinessError, speechRecognitionEngine: speechRecognizer.SpeechRecognitionEngine) => {if (!err) {console.info('Succeeded in creating engine.');// 接收创建引擎的实例asrEngine = speechRecognitionEngine;} else {console.error(`Failed to create engine. Code: ${err.code}, message: ${err.message}.`);}
});
3. 得到SpeechRecognitionEngine实例对象后,实例化RecognitionListener对象,调用setListener方法设置回调,用来接收语音识别相关的回调信息。
// 创建回调对象
let setListener: speechRecognizer.RecognitionListener = {// 开始识别成功回调onStart(sessionId: string, eventMessage: string) {console.info(`onStart, sessionId: ${sessionId} eventMessage: ${eventMessage}`);},// 事件回调onEvent(sessionId: string, eventCode: number, eventMessage: string) {console.info(`onEvent, sessionId: ${sessionId} eventCode: ${eventCode} eventMessage: ${eventMessage}`);},// 识别结果回调,包括中间结果和最终结果onResult(sessionId: string, result: speechRecognizer.SpeechRecognitionResult) {console.info(`onResult, sessionId: ${sessionId} sessionId: ${JSON.stringify(result)}`);},// 识别完成回调onComplete(sessionId: string, eventMessage: string) {console.info(`onComplete, sessionId: ${sessionId} eventMessage: ${eventMessage}`);},// 错误回调,错误码通过本方法返回// 如:返回错误码1002200006,识别引擎正忙,引擎正在识别中// 更多错误码请参考错误码参考onError(sessionId: string, errorCode: number, errorMessage: string) {console.error(`onError, sessionId: ${sessionId} errorCode: ${errorCode} errorMessage: ${errorMessage}`);}
}
// 设置回调
asrEngine.setListener(setListener);
4. 分别为音频文件转文字和麦克风转文字功能设置开始识别的相关参数,调用startListening方法,开始合成。
// 开始识别
private startListeningForWriteAudio() {// 设置开始识别的相关参数let recognizerParams: speechRecognizer.StartParams = {sessionId: this.sessionId,audioInfo: { audioType: 'pcm', sampleRate: 16000, soundChannel: 1, sampleBit: 16 } //audioInfo参数配置请参考AudioInfo}// 调用开始识别方法asrEngine.startListening(recognizerParams);
};private startListeningForRecording() {let audioParam: speechRecognizer.AudioInfo = { audioType: 'pcm', sampleRate: 16000, soundChannel: 1, sampleBit: 16 }let extraParam: Record<string, Object> = {"recognitionMode": 0,"vadBegin": 2000,"vadEnd": 3000,"maxAudioDuration": 20000}let recognizerParams: speechRecognizer.StartParams = {sessionId: this.sessionId,audioInfo: audioParam,extraParams: extraParam}console.info('startListening start');asrEngine.startListening(recognizerParams);
};
5. 传入音频流,调用writeAudio方法,开始写入音频流。读取音频文件时,开发者需预先准备一个pcm格式音频文件。
let uint8Array: Uint8Array = new Uint8Array();
// 可以通过如下方式获取音频流:1、通过录音获取音频流;2、从音频文件中读取音频流
// 2、从音频文件中读取音频流:demo参考
// 写入音频流,音频流长度仅支持640或1280
asrEngine.writeAudio(sessionId, uint8Array);
6. (可选)当需要查询语音识别服务支持的语种信息,可调用listLanguages方法。
listLanguages方法提供了两种调用形式,当前以其中一种作为示例,其他方式可参考API参考。
// 设置查询相关的参数
let languageQuery: speechRecognizer.LanguageQuery = {sessionId: sessionId
};
// 调用listLanguages方法
asrEngine.listLanguages(languageQuery).then((res: Array<string>) => {console.info(`Succeeded in listing languages, result: ${JSON.stringify(res)}.`);
}).catch((err: BusinessError) => {console.error(`Failed to list languages. Code: ${err.code}, message: ${err.message}.`);
});
7. (可选)当需要结束识别时,可调用finish方法。
// 结束识别
asrEngine.finish(sessionId);
8.(可选)当需要取消识别时,可调用cancel方法。
// 取消识别
asrEngine.cancel(sessionId);
9. (可选)当需要释放语音识别引擎资源时,可调用shutdown方法。
// 释放识别引擎资源
asrEngine.shutdown();
10. 需要在module.json5配置文件中添加ohos.permission.MICROPHONE权限,确保麦克风使用正常。详细步骤可查看声明权限章节。
//...
"requestPermissions": [{"name" : "ohos.permission.MICROPHONE","reason": "$string:reason","usedScene": {"abilities": ["EntryAbility"],"when":"inuse"}}
],
//...
3.4 开发实例
点击按钮,将一段音频信息转换为文本。index.ets文件如下:
import { speechRecognizer } from '@kit.CoreSpeechKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { fileIo } from '@kit.CoreFileKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import AudioCapturer from './AudioCapturer';const TAG = 'CoreSpeechKitDemo';let asrEngine: speechRecognizer.SpeechRecognitionEngine;@Entry
@Component
struct Index {@State createCount: number = 0;@State result: boolean = false;@State voiceInfo: string = "";@State sessionId: string = "123456";private mAudioCapturer = new AudioCapturer();build() {Column() {Scroll() {Column() {Button() {Text("CreateEngineByCallback").fontColor(Color.White).fontSize(20)}.type(ButtonType.Capsule).backgroundColor("#0x317AE7").width("80%").height(50).margin(10).onClick(() => {this.createCount++;hilog.info(0x0000, TAG, `CreateAsrEngine:createCount:${this.createCount}`);this.createByCallback();})Button() {Text("setListener").fontColor(Color.White).fontSize(20)}.type(ButtonType.Capsule).backgroundColor("#0x317AE7").width("80%").height(50).margin(10).onClick(() => {this.setListener();})Button() {Text("startRecording").fontColor(Color.White).fontSize(20)}.type(ButtonType.Capsule).backgroundColor("#0x317AE7").width("80%").height(50).margin(10).onClick(() => {this.startRecording();})Button() {Text("writeAudio").fontColor(Color.White).fontSize(20)}.type(ButtonType.Capsule).backgroundColor("#0x317AE7").width("80%").height(50).margin(10).onClick(() => {this.writeAudio();})Button() {Text("queryLanguagesCallback").fontColor(Color.White).fontSize(20)}.type(ButtonType.Capsule).backgroundColor("#0x317AE7").width("80%").height(50).margin(10).onClick(() => {this.queryLanguagesCallback();})Button() {Text("finish").fontColor(Color.White).fontSize(20)}.type(ButtonType.Capsule).backgroundColor("#0x317AE7").width("80%").height(50).margin(10).onClick(() => {// 结束识别hilog.info(0x0000, TAG, "finish click:-->");asrEngine.finish(this.sessionId);})Button() {Text("cancel").fontColor(Color.White).fontSize(20)}.type(ButtonType.Capsule).backgroundColor("#0x317AE7").width("80%").height(50).margin(10).onClick(() => {// 取消识别hilog.info(0x0000, TAG, "cancel click:-->");asrEngine.cancel(this.sessionId);})Button() {Text("shutdown").fontColor(Color.White).fontSize(20)}.type(ButtonType.Capsule).backgroundColor("#0x317AA7").width("80%").height(50).margin(10).onClick(() => {// 释放引擎asrEngine.shutdown();})}.layoutWeight(1)}.width('100%').height('100%')}}// 创建引擎,通过callback形式返回private createByCallback() {// 设置创建引擎参数let extraParam: Record<string, Object> = {"locate": "CN", "recognizerMode": "short"};let initParamsInfo: speechRecognizer.CreateEngineParams = {language: 'zh-CN',online: 1,extraParams: extraParam};// 调用createEngine方法speechRecognizer.createEngine(initParamsInfo, (err: BusinessError, speechRecognitionEngine:speechRecognizer.SpeechRecognitionEngine) => {if (!err) {hilog.info(0x0000, TAG, 'Succeeded in creating engine.');// 接收创建引擎的实例asrEngine = speechRecognitionEngine;} else {// 无法创建引擎时返回错误码1002200001,原因:语种不支持、模式不支持、初始化超时、资源不存在等导致创建引擎失败// 无法创建引擎时返回错误码1002200006,原因:引擎正在忙碌中,一般多个应用同时调用语音识别引擎时触发// 无法创建引擎时返回错误码1002200008,原因:引擎已被销毁hilog.error(0x0000, TAG, `Failed to create engine. Code: ${err.code}, message: ${err.message}.`);}});}// 查询语种信息,以callback形式返回private queryLanguagesCallback() {// 设置查询相关参数let languageQuery: speechRecognizer.LanguageQuery = {sessionId: '123456'};// 调用listLanguages方法asrEngine.listLanguages(languageQuery, (err: BusinessError, languages: Array<string>) => {if (!err) {// 接收目前支持的语种信息hilog.info(0x0000, TAG, `Succeeded in listing languages, result: ${JSON.stringify(languages)}`);} else {hilog.error(0x0000, TAG, `Failed to create engine. Code: ${err.code}, message: ${err.message}.`);}});};// 开始识别private startListeningForWriteAudio() {// 设置开始识别的相关参数let recognizerParams: speechRecognizer.StartParams = {sessionId: this.sessionId,audioInfo: { audioType: 'pcm', sampleRate: 16000, soundChannel: 1, sampleBit: 16 } //audioInfo参数配置请参考AudioInfo}// 调用开始识别方法asrEngine.startListening(recognizerParams);};private startListeningForRecording() {let audioParam: speechRecognizer.AudioInfo = { audioType: 'pcm', sampleRate: 16000, soundChannel: 1, sampleBit: 16 }let extraParam: Record<string, Object> = {"recognitionMode": 0,"vadBegin": 2000,"vadEnd": 3000,"maxAudioDuration": 20000}let recognizerParams: speechRecognizer.StartParams = {sessionId: this.sessionId,audioInfo: audioParam,extraParams: extraParam}hilog.info(0x0000, TAG, 'startListening start');asrEngine.startListening(recognizerParams);};// 写音频流private async writeAudio() {this.startListeningForWriteAudio();hilog.error(0x0000, TAG, `Failed to read from file. Code`);let ctx = getContext(this);let filenames: string[] = fileIo.listFileSync(ctx.filesDir);if (filenames.length <= 0) {hilog.error(0x0000, TAG, `Failed to read from file. Code`);return;}hilog.error(0x0000, TAG, `Failed to read from file. Code`);let filePath: string = `${ctx.filesDir}/${filenames[0]}`;let file = fileIo.openSync(filePath, fileIo.OpenMode.READ_WRITE);try {let buf: ArrayBuffer = new ArrayBuffer(1280);let offset: number = 0;while (1280 == fileIo.readSync(file.fd, buf, {offset: offset})) {let uint8Array: Uint8Array = new Uint8Array(buf);asrEngine.writeAudio("123456", uint8Array);await this.countDownLatch(1);offset = offset + 1280;}} catch (err) {hilog.error(0x0000, TAG, `Failed to read from file. Code: ${err.code}, message: ${err.message}.`);} finally {if (null != file) {fileIo.closeSync(file);}}}// 麦克风语音转文本private async startRecording() {this.startListeningForRecording();// 录音获取音频let data: ArrayBuffer;hilog.info(0x0000, TAG, 'create capture success');this.mAudioCapturer.init((dataBuffer: ArrayBuffer) => {hilog.info(0x0000, TAG, 'start write');hilog.info(0x0000, TAG, 'ArrayBuffer ' + JSON.stringify(dataBuffer));data = dataBufferlet uint8Array: Uint8Array = new Uint8Array(data);hilog.info(0x0000, TAG, 'ArrayBuffer uint8Array ' + JSON.stringify(uint8Array));// 写入音频流asrEngine.writeAudio("1234567", uint8Array);});};// 计时public async countDownLatch(count: number) {while (count > 0) {await this.sleep(40);count--;}}// 睡眠private sleep(ms: number):Promise<void> {return new Promise(resolve => setTimeout(resolve, ms));}// 设置回调private setListener() {// 创建回调对象let setListener: speechRecognizer.RecognitionListener = {// 开始识别成功回调onStart(sessionId: string, eventMessage: string) {hilog.info(0x0000, TAG, `onStart, sessionId: ${sessionId} eventMessage: ${eventMessage}`);},// 事件回调onEvent(sessionId: string, eventCode: number, eventMessage: string) {hilog.info(0x0000, TAG, `onEvent, sessionId: ${sessionId} eventCode: ${eventCode} eventMessage: ${eventMessage}`);},// 识别结果回调,包括中间结果和最终结果onResult(sessionId: string, result: speechRecognizer.SpeechRecognitionResult) {hilog.info(0x0000, TAG, `onResult, sessionId: ${sessionId} sessionId: ${JSON.stringify(result)}`);},// 识别完成回调onComplete(sessionId: string, eventMessage: string) {hilog.info(0x0000, TAG, `onComplete, sessionId: ${sessionId} eventMessage: ${eventMessage}`);},// 错误回调,错误码通过本方法返回// 返回错误码1002200002,开始识别失败,重复启动startListening方法时触发// 更多错误码请参考错误码参考onError(sessionId: string, errorCode: number, errorMessage: string) {hilog.error(0x0000, TAG, `onError, sessionId: ${sessionId} errorCode: ${errorCode} errorMessage: ${errorMessage}`);},}// 设置回调asrEngine.setListener(setListener);};
}
添加AudioCapturer.ts文件用于获取麦克风音频流。
'use strict';
/** Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.*/import {audio} from '@kit.AudioKit';
import { hilog } from '@kit.PerformanceAnalysisKit';const TAG = 'AudioCapturer';/*** Audio collector tool*/
export default class AudioCapturer {/*** Collector object*/private mAudioCapturer = null;/*** Audio Data Callback Method*/private mDataCallBack: (data: ArrayBuffer) => void = null;/*** Indicates whether recording data can be obtained.*/private mCanWrite: boolean = true;/*** Audio stream information*/private audioStreamInfo = {samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_16000,channels: audio.AudioChannel.CHANNEL_1,sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW}/*** Audio collector information*/private audioCapturerInfo = {source: audio.SourceType.SOURCE_TYPE_MIC,capturerFlags: 0}/*** Audio Collector Option Information*/private audioCapturerOptions = {streamInfo: this.audioStreamInfo,capturerInfo: this.audioCapturerInfo}/*** Initialize* @param audioListener*/public async init(dataCallBack: (data: ArrayBuffer) => void) {if (null != this.mAudioCapturer) {hilog.error(0x0000, TAG, 'AudioCapturerUtil already init');return;}this.mDataCallBack = dataCallBack;this.mAudioCapturer = await audio.createAudioCapturer(this.audioCapturerOptions).catch(error => {hilog.error(0x0000, TAG, `AudioCapturerUtil init createAudioCapturer failed, code is ${error.code}, message is ${error.message}`);});}/*** start recording*/public async start() {hilog.error(0x0000, TAG, `AudioCapturerUtil start`);let stateGroup = [audio.AudioState.STATE_PREPARED, audio.AudioState.STATE_PAUSED, audio.AudioState.STATE_STOPPED];if (stateGroup.indexOf(this.mAudioCapturer.state) === -1) {hilog.error(0x0000, TAG, `AudioCapturerUtil start failed`);return;}this.mCanWrite = true;await this.mAudioCapturer.start();while (this.mCanWrite) {let bufferSize = await this.mAudioCapturer.getBufferSize();let buffer = await this.mAudioCapturer.read(bufferSize, true);this.mDataCallBack(buffer)}}/*** stop recording*/public async stop() {if (this.mAudioCapturer.state !== audio.AudioState.STATE_RUNNING && this.mAudioCapturer.state !== audio.AudioState.STATE_PAUSED) {hilog.error(0x0000, TAG, `AudioCapturerUtil stop Capturer is not running or paused`);return;}this.mCanWrite = false;await this.mAudioCapturer.stop();if (this.mAudioCapturer.state === audio.AudioState.STATE_STOPPED) {hilog.info(0x0000, TAG, `AudioCapturerUtil Capturer stopped`);} else {hilog.error(0x0000, TAG, `Capturer stop failed`);}}/*** release*/public async release() {if (this.mAudioCapturer.state === audio.AudioState.STATE_RELEASED || this.mAudioCapturer.state === audio.AudioState.STATE_NEW) {hilog.error(0x0000, TAG, `Capturer already released`);return;}await this.mAudioCapturer.release();this.mAudioCapturer = null;if (this.mAudioCapturer.state == audio.AudioState.STATE_RELEASED) {hilog.info(0x0000, TAG, `Capturer released`);} else {hilog.error(0x0000, TAG, `Capturer release failed`);}}
}
在EntryAbility.ets文件中添加麦克风权限。
import { abilityAccessCtrl, AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';export default class EntryAbility extends UIAbility {onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');}onDestroy(): void {hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');}onWindowStageCreate(windowStage: window.WindowStage): void {// Main window is created, set main page for this abilityhilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');let atManager = abilityAccessCtrl.createAtManager();atManager.requestPermissionsFromUser(this.context, ['ohos.permission.MICROPHONE']).then((data) => {hilog.info(0x0000, 'testTag', 'data:' + JSON.stringify(data));hilog.info(0x0000, 'testTag', 'data permissions:' + data.permissions);hilog.info(0x0000, 'testTag', 'data authResults:' + data.authResults);}).catch((err: BusinessError) => {hilog.error(0x0000, 'testTag', 'errCode: ' + err.code + 'errMessage: ' + err.message);});windowStage.loadContent('pages/Index', (err, data) => {if (err.code) {hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');return;}hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');});}onWindowStageDestroy(): void {// Main window is destroyed, release UI related resourceshilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');}onForeground(): void {// Ability has brought to foregroundhilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');}onBackground(): void {// Ability has back to backgroundhilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');}
}
相关文章:
【鸿蒙开发】第二十四章 AI - Core Speech Kit(基础语音服务)
目录 1 简介 1.1 场景介绍 1.2 约束与限制 2 文本转语音 2.1 场景介绍 2.2 约束与限制 2.3 开发步骤 2.4 设置播报策略 2.4.1 设置单词播报方式 2.4.2 设置数字播报策略 2.4.3 插入静音停顿 2.4.4 指定汉字发音 2.5 开发实例 3 语音识别 3.1 场景介绍 3.2 约束…...

Java/Kotlin双语革命性ORM框架Jimmer(一)——介绍与简单使用
概览 Jimmer是一个Java/Kotlin双语框架 包含一个革命性的ORM 以此ORM为基础打造了一套综合性方案解决方案,包括 DTO语言 更全面更强大的缓存机制,以及高度自动化的缓存一致性 更强大客户端文档和代码生成能力,包括Jimmer独创的远程异常 …...
番外02:前端八股文面试题-CSS篇
一:CSS基础 1:CSS选择器及其优先级 2:display的属性值及其作用 属性值作用none元素不显示,并且会从文档流中移除block块类型,默认元素为父元素宽度,可设置宽高,换行显示inline行内元素类型&a…...
Redis Copilot:基于Redis为AI打造的副驾工具
我们最近发布了Redis Copilot,以帮助开发者更快地使用Redis构建应用。我们的使命是使应用程序快速运行,并简化构建过程。为此,Redis Copilot作为您的AI助手,能够让您更迅速地完成与Redis相关的任务。您今天就可以在Redis Insight中…...
JavaScript遍历对象的7种方式
注:纯手打,如有错误欢迎评论区交流! 转载请注明出处:https://blog.csdn.net/testleaf/article/details/145523427 编写此文是为了更好地学习前端知识,如果损害了有关人的利益,请联系删除! 本文章…...
如何避免NACK重传风暴
策略 1,10 次 NACK 模块对同一包号的最大请求次数,超过这个最大次数限制,会把该包号移出 nack_list,放弃对该包的重传请求。 策略 2,20 毫秒 NACK 模块每隔 20 毫秒批量处理 nack_list,获取一批请求包号…...

并发工具CountDownLatch、CyclicBarrier、Semaphore
文章目录 学习链接CountDownLatchCountDownLatch类的作用类的主要方法介绍图解await和countDown方法两个典型用法注意点总结示例CountDownLatchDemo1CountDownLatchDemo2CountDownLatchDemo1And2 CyclicBarrierCyclicBarrier循环栅栏CyclicBarrier和CountDownLatch的区别示例Cy…...

十二. Redis 集群操作配置(超详细配图,配截图详细说明)
十二. Redis 集群操作配置(超详细配图,配截图详细说明) 文章目录 十二. Redis 集群操作配置(超详细配图,配截图详细说明)1. 为什么需要集群-高可用性2. 集群概述(及其搭建)3. Redis 集群的使用4. Redis 集群故障恢复5. Redis 集群的 Jedis 开发(使用Java…...

网络工程师 (26)TCP/IP体系结构
一、层次 四层: 网络接口层:TCP/IP协议的最底层,负责网络层与硬件设备间的联系。该层协议非常多,包括逻辑链路和媒体访问控制,负责与物理传输的连接媒介打交道,主要功能是接收数据报,并把接收到…...

TensorFlow域对抗训练DANN神经网络分析MNIST与Blobs数据集梯度反转层提升目标域适应能力可视化...
全文链接:https://tecdat.cn/?p39656 本文围绕基于TensorFlow实现的神经网络对抗训练域适应方法展开研究。详细介绍了梯度反转层的原理与实现,通过MNIST和Blobs等数据集进行实验,对比了不同训练方式(仅源域训练、域对抗训练等&am…...
保姆级教程--DeepSeek部署
以DeepSeek-R1或其他类似模型为例,涵盖环境配置、代码部署和运行测试的全流程: 准备工作 1. 注册 Cloud Studio - 访问 [Cloud Studio 官网](https://cloudstudio.net/),使用腾讯云账号登录。 - 完成实名认证(如需长期使用…...
机器学习之心的创作纪念日
机缘 今天,是我成为创作者的第1460天。 在这段时间里,获得了很大的成长。 虽然日常忙碌但还在坚持创作、初心还在。 日常 创作已经成为我生活的一部分,尤其是在我的工作中,创作是不可或缺的,创作都是核心能力之一。…...
VeryReport和FastReport两款报表软件深度分析对比
在当今数据驱动的商业环境中,报表软件已经成为企业管理和数据分析的重要工具。无论是中小型企业还是大型企业,都需要依赖高效的报表工具来快速生成、分析和展示数据。市面上有许多报表工具,其中VeryReport和FastReport是两款备受关注的报表软…...
libtorch的c++,加载*.pth
一、转换模型为TorchScript 前提:python只保存了参数,没存结构 要在C中使用libtorch(PyTorch的C接口),读取和加载通过torch.save保存的模型( torch.save(pdn.state_dict()这种方式,只保存了…...

去除 RequestTemplate 对象中的指定请求头
目录 目标实现获取 RequestTemplate 对象去除请求头 目标 去除 RequestTemplate 对象中的指定请求头,如 Authorization 等。 实现 获取 RequestTemplate 对象 获取 RequestTemplate 对象的方式有很多种,如 通过 feign 虚拟客户端配置器: …...

b s架构 网络安全 网络安全架构分析
目录 文章目录 目录网络安全逻辑架构 微分段(Micro-segmentation)防火墙即服务(Firewall asa Service ,FWaaS)安全网络网关(Secure web gateway)净化域名系统(Sanitized Domain Na…...

【DeepSeek论文精读】2. DeepSeek LLM:以长期主义扩展开源语言模型
欢迎关注[【AIGC论文精读】](https://blog.csdn.net/youcans/category_12321605.html)原创作品 【DeepSeek论文精读】1. 从 DeepSeek LLM 到 DeepSeek R1 【DeepSeek论文精读】2. DeepSeek LLM:以长期主义扩展开源语言模型 【DeepSeek论文精读】3. DeepS…...
Spring Boot和SpringMVC的关系
Spring Boot和SpringMVC都是Spring框架的一部分,但它们的作用和使用方式有所不同。为了更好地理解它们的关系,我们可以从以下几个方面进行详细说明: 1. SpringBoot的作用 SpringBoot是一个开源框架,它的目的是简化Spring应用程序…...

java基础4(黑马)
一、方法 1.定义 方法:是一种语法结构,它可以把一段代码封装成一个功能,以便重复使用。 方法的完整格式: package cn.chang.define;public class MethodDemo1 {public static void main(String[] args) {// 目标:掌…...

nodejs - vue 视频切片上传,本地正常,线上环境导致磁盘爆满bug
nodejs 视频切片上传,本地正常,线上环境导致磁盘爆满bug 原因: 然后在每隔一分钟执行du -sh ls ,发现文件变得越来越大,即文件下的mp4文件越来越大 最后导致磁盘直接爆满 排查原因 1、尝试将m3u8文件夹下的所有视…...

大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...

Flask RESTful 示例
目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题: 下面创建一个简单的Flask RESTful API示例。首先,我们需要创建环境,安装必要的依赖,然后…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...

【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...

Neko虚拟浏览器远程协作方案:Docker+内网穿透技术部署实践
前言:本文将向开发者介绍一款创新性协作工具——Neko虚拟浏览器。在数字化协作场景中,跨地域的团队常需面对实时共享屏幕、协同编辑文档等需求。通过本指南,你将掌握在Ubuntu系统中使用容器化技术部署该工具的具体方案,并结合内网…...
TJCTF 2025
还以为是天津的。这个比较容易,虽然绕了点弯,可还是把CP AK了,不过我会的别人也会,还是没啥名次。记录一下吧。 Crypto bacon-bits with open(flag.txt) as f: flag f.read().strip() with open(text.txt) as t: text t.read…...
大模型真的像人一样“思考”和“理解”吗?
Yann LeCun 新研究的核心探讨:大语言模型(LLM)的“理解”和“思考”方式与人类认知的根本差异。 核心问题:大模型真的像人一样“思考”和“理解”吗? 人类的思考方式: 你的大脑是个超级整理师。面对海量信…...