三文带你轻松上手鸿蒙的 AI 语音 02-声音文件转文本
接上一文
前言
本文主要实现 使用鸿蒙的 AI 语音功能将声音文件识别并转换成文本
实现流程
- 利用
AudioCapturer
录制声音,生成录音文件
- 利用 AI 语音功能,实现识别

两个录音库介绍
在HarmonyOS NEXT 应用开中,实现录音的两个核心库分别为
- AudioCapturer
- AVRecorder
AVRecorder 录制出来的声音封装格式只能是 aac,这个文件格式我们的 AI 语音引擎不支持,AI 语音引擎只支持 pcm 格式,而 AudioCapturer 录制的声音封装格式则是 pcm。因此我们选择使用 AudioCapturer 来录制声音
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
实例。
页面中开始录音

可以通过以下路径查看录音文件是否真实生成
/data/app/el2/100/base/你的项目的boundle名称/haps/entry/files

页面代码
Index.ets
使用 AI 语音功能 实现声音文件转文本
该流程其实和和上一章的实时识别声音功能类似,只是多了一个步骤
- 创建 AI 语音引擎
- 注册语音监听事件
- 开始监听
- 读取录音文件
创建 AI 语音引擎
注册语音监听事件
开始监听
需要设置 recognitionMode 为 1 表示识别语音文件
读取录音文件
需要调用 SpeechRecognizerManager.asrEngine?.writeAudio 来监听语音文件
一步调用
完整代码
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
);
// 延迟40ms
SpeechRecognizerManager.sleep(40);
offset = offset + 1280;
}
fileIo.closeSync(file);
}
static sleep(time: number) {
return Promise<null>((resolve: Function) => {
setTimeout(() => {
resolve();
}, 40);
});
}
/**
* 开始监听
* */
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;
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
- 153.
- 154.
- 155.
- 156.
- 157.
- 158.
- 159.
- 160.
- 161.
- 162.
- 163.
- 164.
- 165.
- 166.
- 167.
- 168.
- 169.
- 170.
- 171.
- 172.
- 173.
- 174.
- 175.
- 176.
- 177.
- 178.
- 179.
- 180.
- 181.
- 182.
- 183.
- 184.
- 185.
- 186.
- 187.
- 188.
- 189.
- 190.
- 191.
- 192.
- 193.
- 194.
- 195.
- 196.
- 197.
- 198.
- 199.
- 200.
- 201.
- 202.
- 203.
- 204.
- 205.
- 206.
- 207.
- 208.
- 209.
- 210.
- 211.
- 212.
- 213.
- 214.
- 215.
- 216.
- 217.
- 218.
- 219.
- 220.
- 221.
- 222.
- 223.
- 224.
- 225.
- 226.
- 227.
- 228.
- 229.
- 230.
- 231.
- 232.
- 233.
- 234.
- 235.
- 236.
- 237.
- 238.
- 239.
- 240.
- 241.
- 242.
- 243.
- 244.
页面代码

👍👍👍