三文带你轻松上手鸿蒙的AI语音02-声音文件转文本
接上一文
前言
本文主要实现 使用鸿蒙的AI语音功能将声音文件识别并转换成文本
实现流程
- 利用
AudioCapturer
录制声音,生成录音文件 - 利用AI语音功能,实现识别
两个录音库介绍
在HarmonyOS NEXT 应用开中,实现录音的两个核心库分别为
- AudioCapturer
- AVRecorder
AVRecorder录制出来的声音封装格式只能是aac,这个文件格式我们的AI语音引擎不支持,AI语音引擎只支持pcm格式,而 AudioCapturer录制的声音封装格式则是pcm。因此我们选择使用 AudioCapturer 来录制声音
AudioCapturer 介绍
AudioCapturer是音频采集器,用于录制PCM(Pulse Code Modulation)音频数据,适合有音频开发经验的开发者实现更灵活的录制功
能。
状态变化示意图
能看到使用 AudioCapturer 的主要流程为
- 创建 AudioCapturer 实例
- 调用 start 方法开始录音
- 调用stop方法停止录音
- 调用release方法释放实例
创建 AudioCapturer 实例
文末会提供封装好,可以直接使用的代码 下面的代码示例都是基于封装好的代码进行的
我们通过调用 createAudioCapturer方法实现创建 AudioCapturer 实例,其中该方法需要传递相关参数。
调用 start 方法开始录音
开始调用 start 方法时,需要准备相关数据。如
- 提供录音的文件名,可以自定义
- 写入录音数据的回调函数(在录制声音的过程中持续触发)
- 调用start方法
调用stop方法停止录音
调用stop方法则相对简单,直接调用即可
调用release方法释放实例
同理
封装好的录音代码
\entry\src\main\ets\utils\AudioCapturerManager.ets
下面是这个类的属性和方法的总结:
属性
- static audioCapturer:
- 类型是
audio.AudioCapturer | null
,是一个静态属性,用于存储当前的音频捕获器实例。
- private static recordFilePath:
- 类型是
string
,是一个静态私有属性,用于存储录音文件的路径。
方法
- static async createAudioCapturer():
- 如果
audioCapturer
已经存在,则直接返回该实例;否则创建一个新的音频捕获器实例,并设置其音频流信息和音频捕获信息,然后创建并返回新的实例。
- static async startRecord(fileName: string):
- 异步静态方法,用于启动录音过程。首先调用
createAudioCapturer()
方法确保有一个音频捕获器实例。之后初始化缓冲区大小,并打开或创建一个指定名称的.wav
录音文件。定义一个读取数据的回调函数,用于将捕获到的数据写入文件中。最后开始录音,并记录下录音文件的路径。
- static async stopRecord():
- 异步静态方法,用于停止录音过程。停止音频捕获器的工作,释放其资源,并清除
audioCapturer
实例。
// 导入音频处理模块 import { audio } from '@kit.AudioKit'; // 导入文件系统模块 import fs from '@ohos.file.fs'; // 定义一个管理音频录制的类 export class AudioCapturerManager { // 静态属性,用于存储当前的音频捕获器实例 static audioCapturer: audio.AudioCapturer | null = null; // 静态私有属性,用于存储录音文件的路径 private static recordFilePath: string = ""; // 静态异步方法,用于创建音频捕获器实例 static async createAudioCapturer() { if (AudioCapturerManager.audioCapturer) { return AudioCapturerManager.audioCapturer } // 设置音频流信息配置 let audioStreamInfo: audio.AudioStreamInfo = { samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_16000, // 设置采样率为16kHz channels: audio.AudioChannel.CHANNEL_1, // 设置单声道 sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 设置样本格式为16位小端 encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 设置编码类型为原始数据 }; // 设置音频捕获信息配置 let audioCapturerInfo: audio.AudioCapturerInfo = { source: audio.SourceType.SOURCE_TYPE_MIC, // 设置麦克风为音频来源 capturerFlags: 0 // 捕获器标志,此处为默认值 }; // 创建音频捕获选项对象 let audioCapturerOptions: audio.AudioCapturerOptions = { streamInfo: audioStreamInfo, // 使用上面定义的音频流信息 capturerInfo: audioCapturerInfo // 使用上面定义的音频捕获信息 }; // 创建音频捕获器实例 AudioCapturerManager.audioCapturer = await audio.createAudioCapturer(audioCapturerOptions); // 返回创建的音频捕获器实例 return AudioCapturerManager.audioCapturer; } // 静态异步方法,用于启动录音过程 static async startRecord(fileName: string) { await AudioCapturerManager.createAudioCapturer() // 初始化缓冲区大小 let bufferSize: number = 0; // 定义一个内部类来设置写入文件时的选项 class Options { offset?: number; // 文件写入位置偏移量 length?: number; // 写入数据的长度 } // 获取应用的文件目录路径 let path = getContext().filesDir; // 设置录音文件的完整路径 let filePath = `${path}/${fileName}.wav`; // 打开或创建录音文件 let file = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE); // 定义一个读取数据的回调函数 let readDataCallback = (buffer: ArrayBuffer) => { // 创建一个写入文件的选项对象 let options: Options = { offset: bufferSize, // 文件当前位置偏移量 length: buffer.byteLength // 数据长度 }; // 将数据写入文件 fs.writeSync(file.fd, buffer, options); // 更新缓冲区大小 bufferSize += buffer.byteLength; }; // 给音频捕获器实例注册读取数据的事件监听器 AudioCapturerManager.audioCapturer?.on('readData', readDataCallback); // 开始录音 AudioCapturerManager.audioCapturer?.start(); AudioCapturerManager.recordFilePath = filePath; // 返回录音文件的路径 return filePath; } // 静态异步方法,用于停止录音过程 static async stopRecord() { // 停止音频捕获器的工作 await AudioCapturerManager.audioCapturer?.stop(); // 释放音频捕获器的资源 await AudioCapturerManager.audioCapturer?.release(); // 清除音频捕获器实例 AudioCapturerManager.audioCapturer = null; } }
页面中开始录音
可以通过以下路径查看录音文件是否真实生成
/data/app/el2/100/base/你的项目的boundle名称/haps/entry/files
页面代码
Index.ets
import { PermissionManager } from '../utils/permissionMananger' import { Permissions } from '@kit.AbilityKit' import SpeechRecognizerManager from '../utils/SpeechRecognizerManager' import { AudioCapturerManager } from '../utils/AudioCapturerManager' @Entry @Component struct Index { @State text: string = "" fileName: string = "" // 1 申请权限 fn1 = async () => { // 准备好需要申请的权限 麦克风权限 const permissions: Permissions[] = ["ohos.permission.MICROPHONE"] // 检查是否拥有权限 const isPermission = await PermissionManager.checkPermission(permissions) if (!isPermission) { // 如果没权限,就主动申请 PermissionManager.requestPermission(permissions) } } // 2 实时语音识别 fn2 = () => { SpeechRecognizerManager.init(res => { console.log("实时语音识别", JSON.stringify(res)) this.text = res.result }) } // 3 开始录音 fn3 = () => { this.fileName = Date.now().toString() AudioCapturerManager.startRecord(this.fileName) } // 4 接收录音 fn4 = () => { AudioCapturerManager.stopRecord() } build() { Column({ space: 10 }) { Text(this.text) Button("申请权限") .onClick(this.fn1) Button("实时语音识别") .onClick(this.fn2) Button("开始录音") .onClick(this.fn3) Button("结束录音") .onClick(this.fn4) } .width("100%") .height("100%") .justifyContent(FlexAlign.Center) } }
使用AI语音功能 实现声音文件转文本
该流程其实和和上一章的实时识别声音功能类似,只是多了一个步骤
- 创建AI语音引擎
- 注册语音监听事件
- 开始监听
- 读取录音文件
创建AI语音引擎
/** * 创建引擎 */ private static async createEngine() { // 设置创建引擎参数 SpeechRecognizerManager.asrEngine = await speechRecognizer.createEngine(SpeechRecognizerManager.initParamsInfo) }
注册语音监听事件
/** * 设置回调 */ private static setListener(callback: (srr: speechRecognizer.SpeechRecognitionResult) => void = () => { }) { // 创建回调对象 let setListener: speechRecognizer.RecognitionListener = { // 开始识别成功回调 onStart(sessionId: string, eventMessage: string) { }, // 事件回调 onEvent(sessionId: string, eventCode: number, eventMessage: string) { }, // 识别结果回调,包括中间结果和最终结果 onResult(sessionId: string, result: speechRecognizer.SpeechRecognitionResult) { SpeechRecognizerManager.speechResult = result callback && callback(result) }, // 识别完成回调 onComplete(sessionId: string, eventMessage: string) { }, // 错误回调,错误码通过本方法返回 // 如:返回错误码1002200006,识别引擎正忙,引擎正在识别中 // 更多错误码请参考错误码参考 onError(sessionId: string, errorCode: number, errorMessage: string) { console.log("errorMessage", errorMessage) }, } // 设置回调 SpeechRecognizerManager.asrEngine?.setListener(setListener); }
开始监听
需要设置 recognitionMode 为 1 表示识别语音文件
/** * 开始监听 * */ static startListening2() { try { // 设置开始识别的相关参数 let recognizerParams: speechRecognizer.StartParams = { // 会话id sessionId: SpeechRecognizerManager.sessionId, // 音频配置信息。 audioInfo: { // 音频类型。 当前仅支持“pcm” audioType: 'pcm', // 音频的采样率。 当前仅支持16000采样率 sampleRate: 16000, // 音频返回的通道数信息。 当前仅支持通道1。 soundChannel: 1, // 音频返回的采样位数。 当前仅支持16位 sampleBit: 16 }, // 录音识别 extraParams: { // 0:实时录音识别 会自动打开麦克风 录制实时语音 // 1 识别语音文件 "recognitionMode": 1, // 最大支持音频时长 maxAudioDuration: 60000 } } // 调用开始识别方法 SpeechRecognizerManager.asrEngine?.startListening(recognizerParams); } catch (e) { console.log("e", e.code, e.message) } };
读取录音文件
需要调用 SpeechRecognizerManager.asrEngine?.writeAudio 来监听语音文件
/** * * @param fileName {string} 语音文件名称 */ private static async writeAudio(fileName: string) { let ctx = getContext(); let filePath: string = `${ctx.filesDir}/${fileName}.wav`; let file = fileIo.openSync(filePath, fileIo.OpenMode.READ_WRITE); 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); // 调用AI语音引擎识别 SpeechRecognizerManager.asrEngine?.writeAudio(SpeechRecognizerManager.sessionId, uint8Array); offset = offset + 1280; } fileIo.closeSync(file); }
一步调用
/** * 初始化ai语音转文字引擎 */ static async init2(callback: (srr: speechRecognizer.SpeechRecognitionResult) => void = () => { }, fileName: string) { try { await SpeechRecognizerManager.createEngine() SpeechRecognizerManager.setListener(callback) SpeechRecognizerManager.startListening2() SpeechRecognizerManager.writeAudio(fileName) } catch (e) { console.log("e", e.message) } }
完整代码
import { speechRecognizer } from '@kit.CoreSpeechKit'; import { fileIo } from '@kit.CoreFileKit'; class SpeechRecognizerManager { /** * 语种信息 * 语音模式:长 */ private static extraParam: Record<string, Object> = { "locate": "CN", "recognizerMode": "short" }; private static initParamsInfo: speechRecognizer.CreateEngineParams = { /** * 地区信息 * */ language: 'zh-CN', /** * 离线模式:1 */ online: 1, extraParams: this.extraParam }; /** * 引擎 */ private static asrEngine: speechRecognizer.SpeechRecognitionEngine | null = null /** * 录音结果 */ static speechResult: speechRecognizer.SpeechRecognitionResult | null = null /** * 会话ID */ private static sessionId: string = "asr" + Date.now() /** * 创建引擎 */ private static async createEngine() { // 设置创建引擎参数 SpeechRecognizerManager.asrEngine = await speechRecognizer.createEngine(SpeechRecognizerManager.initParamsInfo) } /** * 设置回调 */ private static setListener(callback: (srr: speechRecognizer.SpeechRecognitionResult) => void = () => { }) { // 创建回调对象 let setListener: speechRecognizer.RecognitionListener = { // 开始识别成功回调 onStart(sessionId: string, eventMessage: string) { }, // 事件回调 onEvent(sessionId: string, eventCode: number, eventMessage: string) { }, // 识别结果回调,包括中间结果和最终结果 onResult(sessionId: string, result: speechRecognizer.SpeechRecognitionResult) { SpeechRecognizerManager.speechResult = result callback && callback(result) }, // 识别完成回调 onComplete(sessionId: string, eventMessage: string) { }, // 错误回调,错误码通过本方法返回 // 如:返回错误码1002200006,识别引擎正忙,引擎正在识别中 // 更多错误码请参考错误码参考 onError(sessionId: string, errorCode: number, errorMessage: string) { console.log("errorMessage", errorMessage) }, } // 设置回调 SpeechRecognizerManager.asrEngine?.setListener(setListener); } /** * 开始监听 * */ static startListening() { try { // 设置开始识别的相关参数 let recognizerParams: speechRecognizer.StartParams = { // 会话id sessionId: SpeechRecognizerManager.sessionId, // 音频配置信息。 audioInfo: { // 音频类型。 当前仅支持“pcm” audioType: 'pcm', // 音频的采样率。 当前仅支持16000采样率 sampleRate: 16000, // 音频返回的通道数信息。 当前仅支持通道1。 soundChannel: 1, // 音频返回的采样位数。 当前仅支持16位 sampleBit: 16 }, // 录音识别 extraParams: { // 0:实时录音识别 会自动打开麦克风 录制实时语音 "recognitionMode": 0, // 最大支持音频时长 maxAudioDuration: 60000 } } // 调用开始识别方法 SpeechRecognizerManager.asrEngine?.startListening(recognizerParams); } catch (e) { console.log("e", e.code, e.message) } }; /** * * @param fileName {string} 语音文件名称 */ private static async writeAudio(fileName: string) { let ctx = getContext(); let filePath: string = `${ctx.filesDir}/${fileName}.wav`; let file = fileIo.openSync(filePath, fileIo.OpenMode.READ_WRITE); 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); // 调用AI语音引擎识别 SpeechRecognizerManager.asrEngine?.writeAudio(SpeechRecognizerManager.sessionId, uint8Array); offset = offset + 1280; } fileIo.closeSync(file); } /** * 开始监听 * */ static startListening2() { try { // 设置开始识别的相关参数 let recognizerParams: speechRecognizer.StartParams = { // 会话id sessionId: SpeechRecognizerManager.sessionId, // 音频配置信息。 audioInfo: { // 音频类型。 当前仅支持“pcm” audioType: 'pcm', // 音频的采样率。 当前仅支持16000采样率 sampleRate: 16000, // 音频返回的通道数信息。 当前仅支持通道1。 soundChannel: 1, // 音频返回的采样位数。 当前仅支持16位 sampleBit: 16 }, // 录音识别 extraParams: { // 0:实时录音识别 会自动打开麦克风 录制实时语音 // 1 识别语音文件 "recognitionMode": 1, // 最大支持音频时长 maxAudioDuration: 60000 } } // 调用开始识别方法 SpeechRecognizerManager.asrEngine?.startListening(recognizerParams); } catch (e) { console.log("e", e.code, e.message) } }; /** * 取消识别 */ static cancel() { SpeechRecognizerManager.asrEngine?.cancel(SpeechRecognizerManager.sessionId) } /** * 释放ai语音转文字引擎 */ static shutDown() { SpeechRecognizerManager.asrEngine?.shutdown() } /** * 停止并且释放资源 */ static async release() { SpeechRecognizerManager.cancel() SpeechRecognizerManager.shutDown() } /** * 初始化ai语音转文字引擎 */ static async init(callback: (srr: speechRecognizer.SpeechRecognitionResult) => void = () => { }) { await SpeechRecognizerManager.createEngine() SpeechRecognizerManager.setListener(callback) SpeechRecognizerManager.startListening() } /** * 初始化ai语音转文字引擎 */ static async init2(callback: (srr: speechRecognizer.SpeechRecognitionResult) => void = () => { }, fileName: string) { try { await SpeechRecognizerManager.createEngine() SpeechRecognizerManager.setListener(callback) SpeechRecognizerManager.startListening2() SpeechRecognizerManager.writeAudio(fileName) } catch (e) { console.log("e", e.message) } } }
export default SpeechRecognizerManager
页面代码
import { PermissionManager } from '../utils/permissionMananger' import { Permissions } from '@kit.AbilityKit' import SpeechRecognizerManager from '../utils/SpeechRecognizerManager' import { AudioCapturerManager } from '../utils/AudioCapturerManager' import TextToSpeechManager from '../utils/TextToSpeechManager' @Entry @Component struct Index { @State text: string = "" fileName: string = "" // 1 申请权限 fn1 = async () => { // 准备好需要申请的权限 麦克风权限 const permissions: Permissions[] = ["ohos.permission.MICROPHONE"] // 检查是否拥有权限 const isPermission = await PermissionManager.checkPermission(permissions) if (!isPermission) { // 如果没权限,就主动申请 PermissionManager.requestPermission(permissions) } } // 2 实时语音识别 fn2 = () => { SpeechRecognizerManager.init(res => { console.log("实时语音识别", JSON.stringify(res)) this.text = res.result }) } // 3 开始录音 fn3 = () => { this.fileName = Date.now().toString() AudioCapturerManager.startRecord(this.fileName) } // 4 接收录音 fn4 = () => { AudioCapturerManager.stopRecord() } // 5 声音文件转换文本 fn5 = () => { SpeechRecognizerManager.init2(res => { this.text = res.result console.log("声音文件转换文本", JSON.stringify(res)) }, this.fileName) } // 6 文本合成声音 fn6 = async () => { const tts = new TextToSpeechManager() await tts.createEngine() tts.setListener((res) => { console.log("res", JSON.stringify(res)) }) tts.speak("我送你离开 千里之外") } build() { Column({ space: 10 }) { Text(this.text) Button("申请权限") .onClick(this.fn1) Button("实时语音识别") .onClick(this.fn2) Button("开始录音") .onClick(this.fn3) Button("结束录音") .onClick(this.fn4) Button("声音文件转换文本") .onClick(this.fn5) Button("文本合成声音") .onClick(this.fn6) } .width("100%") .height("100%") .justifyContent(FlexAlign.Center) } }