三文带你轻松上手鸿蒙的AI语音02-声音文件转文本
三文带你轻松上手鸿蒙的AI语音02-声音文件转文本
接上一文
前言
本文主要实现 使用鸿蒙的AI语音功能将声音文件识别并转换成文本
实现流程
- 利用
AudioCapturer
录制声音,生成录音文件 - 利用AI语音功能,实现识别
两个录音库介绍
在OpenHarmony 应用开中,实现录音的两个核心库分别为
- 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
实例。
- 异步静态方法,用于停止录音过程。停止音频捕获器的工作,释放其资源,并清除
// 导入音频处理模块
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);
// 延迟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
页面代码
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)
}
}
作者
作者:万少
來源:坚果派 著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。
- 0回答
- 0粉丝
- 0关注
- 三文带你轻松上手鸿蒙的AI语音03-文本合成声音
- 三文带你轻松上手鸿蒙的AI语音01-实时语音识别
- 鸿蒙原生开发手记:02-服务卡片开发
- 手把手带你实现 鸿蒙应用-键盘音乐
- 带你实现OpenHarmony 微信聊天02
- 轻松上手-骨架屏后动画显示
- 轻松上手-Navigation路由H5
- OpenHarmony 动画大全02-显式动画
- 轻松上手-MVVM_关系型数据库_云函数T云数据库
- 调频声波App(三)播放声音
- 带你实现 OpenHarmony 微信聊天 01
- 应用配置文件解读
- 鸿蒙Flutter实战:02-Windows环境搭建踩坑指南
- 鸿蒙如何查询应用包的名称、供应商、版本号、版本文本、安装时间、更新时间描述信息
- 【等待更新】OpenHarmony三方库与原生鸿蒙应用的融合之道(案例集)