文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

一招教你打通鸿蒙语音识别和语音播报

2024-12-02 07:13

关注

​51CTO和华为官方合作共建的鸿蒙技术社区​

​https://harmonyos.51cto.com​

前言

大家好久不见了,我是Piwriw.,自从上次分享完关于算法与数据结构的系列之后,我也一直想回到我的开篇的鸿蒙技术上,但是我一直都没有想到很好的方向😞,又是因为年前年后的问题,就一直在社区潜水🐟🐟🐟,也是在社区里面发现了很多有趣的技术,最近很火的eST实现冰墩墩,做到人手一个冰墩墩哈哈哈。

但是就在前几天,我"突然"想到了一个我最近一个项目也在用的技术(这个项目是我参加鸿蒙开发者创新大赛创,所以暂时没办法公布给大家,之后有机会的话,我会再分享出来)—>语言播报(Text to Speech)和语音识别(Automatic Speech Recognition, ASR),这些都基于了鸿蒙官网的开发项目下的自带的AI能力—>语音识别、语音播报,其实这二个能力可以是基础能力,但是又是十分重要的能力,毕竟谁能拒绝一个掷地有声的交互呢?😁😁

目前由于我本身是使用Java开发,所以在下面的代码中,我只提供Java版本,不过我是在官方文档也没有找到Js和eTS也可以支持这个能力的参考说明,在下面的代码中,我尽量实现了高可用,我把语音识别和语音播报都做成了工具类,所以可能很多详细的配置选项,大家可以上官方文档查看。

语音识别

权限申请

由于我们使用了语音,使用我们要申请录音权限

在config.json中配置上ohos.permission.MICROPHONE的能力。

五大语音识别API类

至于五大类各执详细的接口内部方法对应的功能,我在这里不过多强调,在官方文档的说明已经十分详细了------>语音识别概述

六大约束和限制

高可用语音识别类

可能看到上面乱七八糟的描述,你已经“晕”了,这是什么,我看不懂啊,没关系,往下看

使用方法

前置: AsrUtils中audioCaptureUtils.init(“你的项目包名”);

传入context实现,ASR的初始化

 AsrUtils.InitAsrUtils(this);

使用录音开始前,start()

 AsrUtils.start();

结束语音录入,stop()

    AsrUtils.stop();

通过getResultAndClear()方法获取识别结果,返回识别结果,并且除掉缓存,为下一次准备

    String result =AsrUtils.getResultAndClear();

AsrUtils工具类参考代码

package com.piwriw.puzzlepictures.utils;

import ohos.ai.asr.AsrClient;
import ohos.ai.asr.AsrIntent;
import ohos.ai.asr.AsrListener;
import ohos.ai.asr.util.AsrError;
import ohos.ai.asr.util.AsrResultKey;
import ohos.app.Context;
import ohos.media.audio.AudioStreamInfo;
import ohos.utils.PacMap;
import ohos.utils.zson.ZSONObject;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


public class AsrUtils {
//采样率限
private static final int VIDEO_SAMPLE_RATE = 16000;
private static final int VAD_END_WAIT_MS = 2000;
private static final int VAD_FRONT_WAIT_MS = 4800;
private static final int TIMEOUT_DURATION = 20000;
private static final int BYTES_LENGTH = 1280;
//线程池相关参数
private static final int CAPACITY = 6;
private static final int ALIVE_TIME = 3;
private static final int POOL_SIZE = 3;

//录音线程
private static ThreadPoolExecutor poolExecutor;

public static int state = 0;
//识别结果
public static String result;
//是否开启语音识别
//当开启时才写入PCM流
private static boolean isStarted = false;

//ASR客户端
private static AsrClient asrClient;
//ASR监听对象
private static AsrListener listener;
private static AsrIntent asrIntent;
//音频录制工具类
private static AudioCaptureUtils audioCaptureUtils;

public static void InitAsrUtils(Context context) {
//实例化一个单声道,采集频率16000HZ的音频录制工具类实例
audioCaptureUtils = new AudioCaptureUtils(AudioStreamInfo.ChannelMask.CHANNEL_IN_MONO, VIDEO_SAMPLE_RATE);
//初始化降噪音效
audioCaptureUtils.init("你的项目包名");
//结果值初始置空
result = "";

//给录音控件初始化一个新的线程池
poolExecutor = new ThreadPoolExecutor(
POOL_SIZE,
POOL_SIZE,
ALIVE_TIME,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(CAPACITY),
new ThreadPoolExecutor.DiscardOldestPolicy());

if (asrIntent == null) {
asrIntent = new AsrIntent();
//设置音频来源为PCM流
//此处也可设置为文件
asrIntent.setAudioSourceType(AsrIntent.AsrAudioSrcType.ASR_SRC_TYPE_PCM);
asrIntent.setVadEndWaitMs(VAD_END_WAIT_MS);
asrIntent.setVadFrontWaitMs(VAD_FRONT_WAIT_MS);
asrIntent.setTimeoutThresholdMs(TIMEOUT_DURATION);
}

if (asrClient == null) {
//实例化AsrClient
asrClient = AsrClient.createAsrClient(context).orElse(null);
}
if (listener == null) {
//实例化MyAsrListener
listener = new MyAsrListener();
//初始化AsrClient
asrClient.init(asrIntent, listener);
}

}

//实现AsrListener接口监听类
private static class MyAsrListener implements AsrListener {

@Override
public void onInit(PacMap pacMap) {
Utils.logInfo("------ init");
state = 1;
}

@Override
public void onBeginningOfSpeech() {
state = 2;
}

@Override
public void onRmsChanged(float v) {

}

@Override
public void onBufferReceived(byte[] bytes) {

}

@Override
public void onEndOfSpeech() {
state = 3;
}

@Override
public void onError(int i) {
state = -1;
if (i == AsrError.ERROR_SPEECH_TIMEOUT) {
//当超时时重新监听
asrClient.startListening(asrIntent);
} else {
Utils.logInfo("======error code:" + i);
asrClient.stopListening();
}
}

@Override
public void onResults(PacMap pacMap) {
state = 10;
//获取最终结果
String results = pacMap.getString(AsrResultKey.RESULTS_RECOGNITION);
ZSONObject zsonObject = ZSONObject.stringToZSON(results);
ZSONObject infoObject;
if (zsonObject.getZSONArray("result").getZSONObject(0) instanceof ZSONObject) {
infoObject = zsonObject.getZSONArray("result").getZSONObject(0);
String resultWord = infoObject.getString("ori_word").replace(" ", "");
result += resultWord;
}
}

//中途识别结果
//pacMap.getString(AsrResultKey.RESULTS_INTERMEDIATE)
@Override
public void onIntermediateResults(PacMap pacMap) {
state = 9;
}


@Override
public void onEnd() {
state = 5;
//当还在录音时,重新监听
if (isStarted)
asrClient.startListening(asrIntent);
}

@Override
public void onEvent(int i, PacMap pacMap) {

}

@Override
public void onAudioStart() {
state = 2;

}

@Override
public void onAudioEnd() {
state = 3;
}

}

public static void start() {
if (!isStarted) {
isStarted = true;
asrClient.startListening(asrIntent);
poolExecutor.submit(new AudioCaptureRunnable());
}
}

public static void stop() {
isStarted = false;
asrClient.stopListening();
audioCaptureUtils.stop();
// asrClient.destroy();
}

//音频录制的线程
private static class AudioCaptureRunnable implements Runnable {
@Override
public void run() {
byte[] buffers = new byte[BYTES_LENGTH];
//开启录音
audioCaptureUtils.start();
while (isStarted) {
//读取录音的PCM流
int ret = audioCaptureUtils.read(buffers, 0, BYTES_LENGTH);
if (ret <= 0) {
Utils.logInfo("======Error read data");
} else {
asrClient.writePcm(buffers, BYTES_LENGTH);
}
}
}
}

public static String getResult() {
return result;
}

public static String getResultAndClear() {
if (result == "")
return "";
String results = getResult();
result = "";
return results;
}
}

语音播报

五大语音播报类

同样的具体五大类的详细接口内部功能,大家参考

语音播报开发指导

二大约束与限制

支持超长文本播报,最大文本长度为100000个字符

语音播报不支持多线程调用

高可用的语音播报类

使用方法

前置:这次我们要生成的为2个类:TtsUtils和AudioCaptureUtils

传入contex实现初始化

  TtsUtils.initTtsEngine(this);

通过readText(str)方法,播报内容

  TtsUtils.readText(播报内容)

AudioCaptureUtils和AsrUtils

package com.piwriw.puzzlepictures.utils;

import ohos.media.audio.AudioCapturer;
import ohos.media.audio.AudioCapturerInfo;
import ohos.media.audio.AudioStreamInfo;
import ohos.media.audio.SoundEffect;

import java.util.UUID;


public class AudioCaptureUtils {
private AudioStreamInfo audioStreamInfo;
private AudioCapturer audioCapturer;
private AudioCapturerInfo audioCapturerInfo;

//channelMask 声道
//SampleRate 频率
public AudioCaptureUtils(AudioStreamInfo.ChannelMask channelMask, int SampleRate) {
this.audioStreamInfo = new AudioStreamInfo.Builder()
.encodingFormat(AudioStreamInfo.EncodingFormat.ENCODING_PCM_16BIT)
.channelMask(channelMask)
.sampleRate(SampleRate)
.build();
this.audioCapturerInfo = new AudioCapturerInfo.Builder().audioStreamInfo(audioStreamInfo).build();
}

//packageName 包名
public void init(String packageName) {
this.init(SoundEffect.SOUND_EFFECT_TYPE_NS, packageName);
}

//soundEffect 音效uuid
//packageName 包名
public void init(UUID soundEffect, String packageName) {
if (audioCapturer == null || audioCapturer.getState() == AudioCapturer.State.STATE_UNINITIALIZED)
audioCapturer = new AudioCapturer(this.audioCapturerInfo);
audioCapturer.addSoundEffect(soundEffect, packageName);
}

public void stop() {
this.audioCapturer.stop();
}

public void destory() {
this.audioCapturer.stop();
this.audioCapturer.release();
}

public Boolean start() {
if (audioCapturer == null)
return false;
return audioCapturer.start();
}

//buffers 需要写入的数据流
//offset 数据流的偏移量
//byteslength 数据流的长度
public int read(byte[] buffers, int offset, int bytesLength) {
return audioCapturer.read(buffers, offset, bytesLength);
}

//获取AudioCapturer的实例audioCapturer
public AudioCapturer get() {
return this.audioCapturer;
}

}
package com.piwriw.puzzlepictures.utils;

import ohos.ai.tts.TtsClient;
import ohos.ai.tts.TtsListener;
import ohos.ai.tts.TtsParams;
import ohos.ai.tts.constants.TtsEvent;
import ohos.app.Context;
import ohos.utils.PacMap;

import java.io.IOException;
import java.util.UUID;


public class TtsUtils {

private static boolean initItsResult;
public static void readText(String str) {
if (initItsResult) {
Utils.logInfo("initItsResult is true, speakText");
TtsClient.getInstance().speakText(str, null);
} else {
Utils.logInfo("initItsResult is false");
}
}

public static boolean initTtsEngine(Context context ) {

TtsListener ttsListener = new TtsListener() {
@Override
public void onEvent(int eventType, PacMap pacMap) {

// 定义TTS客户端创建成功的回调函数
if (eventType == TtsEvent.CREATE_TTS_CLIENT_SUCCESS) {
TtsParams ttsParams = new TtsParams();
ttsParams.setDeviceId(UUID.randomUUID().toString());
initItsResult = TtsClient.getInstance().init(ttsParams);
}
}

@Override
public void onSpeechStart(String s) {

}

@Override
public void onSpeechProgressChanged(String s, int i) {

}

@Override
public void onSpeechFinish(String s) {

}

@Override
public void onStart(String utteranceId) {

}

@Override
public void onProgress(String utteranceId, byte[] audioData, int progress) {
}

@Override
public void onFinish(String utteranceId) {

}

@Override
public void onError(String s, String s1) {

}

};
try {
TtsClient.getInstance().create(context, ttsListener);
}
catch (Exception e){
e.printStackTrace();
return false;
}
return true;
}
}

尾声

这次呢,就大概给大家带来的东西就这么多了,可能看到这里,还是有朋友想知道我为什么要分享这个语音识别和语音播报,众所周知事出反常必有妖,其实就是我自己踩坑了😕😕,我自己在使用的时候,因为种种不知名的问题,导致我使用的时候出现了一些奇奇怪怪的问题(后面我想可能是生命周期的问题),一开始其实我是想把这个做成service服务的,但是很遗憾失败了(其实就是我不太会,嘻嘻),后面我就想到目前这个方法,问题好像就解决了。

可能看到这里,我这里还想再说几句,其实我在解决我上面的问题的时候,尝试了很多很多解决方法,但是很多问题,由于一些失败也好,还是有我本身做手机App开发的经验不足,还是我采用的是Harmonyos的,是非完全开源的(讲真,有点坑),也导致了我寻找BUG的时候直接受阻了,因为看不了😑😑😑

好了,在最后,我祝大家语音识别和语音播报的代码食用愉快!!!

​想了解更多内容,请访问:​

​51CTO和华为官方合作共建的鸿蒙技术社区​

​https://harmonyos.51cto.com​

来源:鸿蒙社区内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯