前言
在一些场景中,为了方便用户。我们会提供语音转文字的服务。比如说在搜索页中,常规的交互是用户在输入框中输入内容之后,点击搜索按钮进行搜索业务,为了提高用户体验,用户可以通过【语音】的方式输入想要搜索的关键词,省去打字的麻烦
一、语音搜索实现逻辑
我们要实现这一功能的思路: 1. 录制音频 2. 把音频buffer传给语音转化引擎 - 输出文字
二、使用步骤
1.申请权限
应用在申请权限时,需要在项目的配置文件中,逐个声明需要的权限,否则应用将无法获取授权。
- 如何在配置文件中声明权限:
应用需要在 module.json5 配置文件的 requestPermissions 标签中声明权限。
代码如下(示例):
{
"module" : {
// ...
"requestPermissions":[
{
"name" : "ohos.permission.PERMISSION1", //权限名字
"reason": "$string:reason", //使用权限的理由
"usedScene": { //权限使用的场景,该字段用于应用上架校验。
"abilities": [
"FormAbility"
],
"when":"inuse"
}
},
{
"name" : "ohos.permission.PERMISSION2",
"reason": "$string:reason",
"usedScene": {
"abilities": [
"FormAbility"
],
"when":"always"
}
}
]
}
}
| 属性 | 含义 |
|---|---|
| name | 需要使用的权限名称 (必填) |
| reason | 申请权限的原因 (选填) |
| usedScene | 权限使用的场景,该字段用于应用上架校验(必填) |
- 用if条件语句检查是否有权限,如果用户开启权限则正常进行逻辑的处理。 注:理解下面代码可参考@ohos.abilityAccessCtrl (程序访问控制管理)
//接口依赖的元素bundleManager。应用程序信息,三方应用可以通过bundleManager.getBundleInfoForSelf获取自身的应用程序信息
import { abilityAccessCtrl, bundleManager, Permissions } from "@kit.AbilityKit";
//检查是否有这个permissions权限的方法
async checkPermission(permission:Permissions[],callback: () => void){
// bundleManager模块提供应用信息查询能力。BundleFlag是包信息标志,指示需要获取的包信息的内容。用于获取包含applicationInfo的bundleInfo
const getbundleFlags=bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION
//以同步方法根据给定的bundleFlags获取当前应用的BundleInfo
const appInfo = bundleManager.getBundleInfoForSelfSync(getbundleFlags)
//访问控制管理:获取访问控制模块对象
const manager = abilityAccessCtrl.createAtManager()
//判断每个权限是否都开启
let isHave = permissions.every(item => {
//校验应用是否被授予权限,同步返回结果。 appInfo是程序的配置信息里面的accessTokenId是要校验应用的身份标识。
let status = manager.checkAccessTokenSync(appInfo.appInfo.accessTokenId, item)
//GrantStatus表示授权状态的枚举。PERMISSION_GRANTED表示已授权。
return status === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
})
if (isHave) {
// 此时拥有
callback() // 对方可以正常进行逻辑的处理了
} else {
...
...
}
}
- 如果用户并没有授权我们要使用的权限,我们在else里面进行逻辑处理
// 第一次弹窗
//用于UIAbility拉起弹框请求用户授权。使用promise异步回调。
//如果用户拒绝授权,将无法再次拉起弹框,需要用户在系统应用“设置”的界面中,手动授予权限。
// 或是调用requestPermissionOnSetting,拉起权限设置弹框,引导用户授权。
const result = await manager.requestPermissionsFromUser(getContext(), permissions)
- 判断用户是否授权,如果用户开启权限则正常进行逻辑的处理
//每个权限
let againHave = result.authResults.every(status => {
return status === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
})
if (againHave) {
callback() // 对方可以正常进行逻辑的处理了
} else {
...
}
- 若用户拒绝,用户想要使用功能该功能。我们要通过requestPermissionOnSetting,拉起权限设置弹框,引导用户授权。
manager.requestPermissionOnSetting(getContext(), permissions)
(因为这个方法可能会多次调用在不同的地方,所以我们可以把他封装成一个工具类)
//@ohos.abilityAccessCtrl (程序访问控制管理)
//接口依赖的元素bundleManager。应用程序信息,三方应用可以通过bundleManager.getBundleInfoForSelf获取自身的应用程序信息
import { abilityAccessCtrl, bundleManager, Permissions } from "@kit.AbilityKit";
class CheckPermissionManager{
//检查是否有这个permissions权限的方法
async checkPermission(permission:Permissions[],callback: () => void){
...
})
}
}
export const checkPermissionManager = new checkPermissionManager()
2.录制语言
和录音机不同的是,我们在这里使用Audio Kit(音频服务)下的 ArkTS API @ohos.multimedia.audio(音频管理)模块。因为我们录的音频数据要实时转化文字,不需要写入沙箱文件,音频数据以内存形式存在。
1.获取音频渲染器。使用Promise方式异步返回结果。
import { audio } from '@kit.AudioKit';
capturer: audio.AudioCapturer | null = null // 录音对象
async createCapturer() {
let audioStreamInfo: audio.AudioStreamInfo = {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100,
channels: audio.AudioChannel.CHANNEL_1,
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW
};
let audioRendererInfo: audio.AudioRendererInfo = {
usage: audio.StreamUsage.STREAM_USAGE_MUSIC,
rendererFlags: 0
};
let audioRendererOptions: audio.AudioRendererOptions = {
streamInfo: audioStreamInfo,
rendererInfo: audioRendererInfo
};
this.capturer = await audio.createAudioCapturer(audioCapturerOptions)
}
- 开始录音方法
async start(callback: (bf: ArrayBuffer) => void) {
//是否存在录音对象,存在才可以录音
if (!this.capturer) {
await this.createCapturer() // 创建录音对象
}
// 先监听 再开始录音
this.capturer?.on("readData", (bf) => {
// bf 一段段的语音转化二进制对象的具体参数
//console.log('',bf.byteLength.toString())
callback(bf)
})
this.capturer?.start() // 开启录音
}
- 停止录音方法
// 停止录音的方法
async stop() {
if (this.capturer) {
await this.capturer.stop() // 停止录音
await this.capturer.release() // 释放资源
this.capturer = null // 释放内存对象 垃圾回收 gc
// 正常的局部变量
}
}
因为这个方法可能会多次调用在不同的地方,所以我们可以把他封装成一个工具类
import { audio } from "@kit.AudioKit"
export class AudioRecordManager {
...
步骤1
步骤2
步骤3
}
export const audioRecordManager = new AudioRecordManager()
3.语音识别引擎
对引擎进行初始化,并创建SpeechRecognitionEngine实例。
engine: speechRecognizer.SpeechRecognitionEngine | null = null
sessionId: string = "123456"
textCallBack?: (text: string) => void
- 创建语音识别引擎
语音识别类,用于执行语音识别过程中的相关操作。在调用SpeechRecognitionEngine的方法前,需要先通过createEngine方法创建一个SpeechRecognitionEngine实例。
async createEngine() {
// 创建引擎,通过callback形式返回
// 设置创建引擎参数
let extraParam: Record<string, Object> = { "locate": "CN", "recognizerMode": "short" };
let initParamsInfo: speechRecognizer.CreateEngineParams = {
language: 'zh-CN',
online: 1,
extraParams: extraParam
};
// 调用createEngine方法
this.engine = await speechRecognizer.createEngine(initParamsInfo)
}
- 只初始化一次
async init(callBack?: (text: string) => void) {
this.textCallBack = callBack
if (!this.engine) {
await this.createEngine() // 创建引擎
}
// 此时引擎已经创建了
// this.engine?.setListener({})
this.setListener() // 设置监听项
// 开启监听
// 设置开始识别的相关参数
let recognizerParams: speechRecognizer.StartParams = {
sessionId: this.sessionId,
audioInfo: {
audioType: 'pcm',
sampleRate: 16000,
soundChannel: 1,
sampleBit: 16
} //audioInfo参数配置请参考AudioInfo
}
// 调用开始识别方法
this.engine?.startListening(recognizerParams);
}
- 传入音频流,调用writeAudio方法,开始写入音频流。读取音频文件时,开发者需预先准备一个pcm格式音频文件。
可以通过如下方式获取音频流: 1、通过录音获取音频流; 2、从音频文件中读取音频流(本文所选用的方法) // 写入音频流,音频流长度仅支持640或1280
// 连续不断的输入
start(bf: ArrayBuffer) {
this.engine?.writeAudio(this.sessionId, new Uint8Array(bf))
}
- 停止输入
stop() {
this.engine?.finish(this.sessionId) // 先结束
this.engine?.shutdown() // 释放资源
this.engine = null // 真正的释放资源
}
- 用来接收语音识别相关的回调信息。
setListener() {
// let that = this // this给到一个变量
// 创建回调对象
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) => {
// Logger.info(result)
if (this.textCallBack) {
this.textCallBack(result.result)
}
// 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}`);
}
}
// 设置回调
this.engine?.setListener(setListener);
}
因为这个方法可能会多次调用在不同的地方,所以我们可以把他封装成一个工具类
代码如下(示例):
import { speechRecognizer } from "@kit.CoreSpeechKit"
export class SpeechToTextMananger {
...把上面的代码块按顺序放入
}
export const speechToTextMananger = new SpeechToTextMananger()
三、实现功能
通过上面的代码编写我们已经有三个可供使用的工具类。
- 长按语音时
async startRecord() {
//进行鉴权-只在长按语音时触发
checkPermissionManager.checkPermission(["ohos.permission.MICROPHONE"], () => {
//已经授权则
// 开始录音
speechToTextMananger.init((text) => {
this.keyword = text // 拿到识别文本 显示到页面上
}) // 先初始化
audioCapturerManager.start((bf) => {
// 拿到语音转化 => 语音识别
speechToTextMananger.start(bf) // 传入音频buffer得到转化文字
})
})
- 停止长按
async closeRecord() {
audioRecordManager.stop()
speechToTextMananger.stop()
}
- 使用该功能需要用到手势处理的API,为组件绑定不同类型的手势事件,并设置事件的响应方法。 我们使用用于触发长按手势事件LongPressGesture接口。
export enum VoiceState {
DEFAULT,
VOICING,
VOICEOVER
}
@State voiceState: VoiceState = VoiceState.DEFAULT
Button() {
Row({ space: 4 }) {
//可以在这个位置放置一个语音搜索图标
if (this.voiceState === VoiceState.VOICING) {
Text('松开立即搜索')
.fontSize(14)
.fontColor($r('[basic].color.white'))
} else {
Text('长按语音搜索')
.fontSize(14)
.fontColor($r('[basic].color.white'))
}
}
}
.gesture(
LongPressGesture()
//LongPress手势识别成功回调。
.onAction(() => {
this.startRecord()
})
//LongPress手势识别成功,最后一根手指抬起后触发回调。
.onActionEnd(() => {
this.closeRecord()
})
//LongPress手势识别成功,接收到触摸取消事件触发回调。
.onActionCancel(() => {
this.closeRecord()
})
)
总结
以上就是我们实现语音转文字这一技术的全面讲解,希望大家能有收获噢~