“阿强,手写板怎么又不见了?”
最近,程序员阿强的那位勇于尝试新事物的外婆,又迷上了网购。在不太费劲儿地把购物软件摸得门儿清之后,没想到,本以为顺畅的网购之路,卡在了搜索物品上。
在手写输入环节,要么误操作,无意中更换到不熟悉的输入法;要么误按了界面上抽象的指令字符……于是阿强也经常收到外婆发来的求助。
其实,不止是购物应用,时下智能手机里装载的大部APP,都是倾斜于年轻群体的交互设计,老年人想要体验学会使用,很难真香。
在一次次耐心指导外婆完成操作后,阿强,这个成熟coder给自己提了个需求:提升外婆的网购体验。不是一味让她适应输入法,而是让输入法迎合外婆的使用偏好习惯。
手动输入易出错,那就写个语音转文字的输入方法,只要启动录音按钮,实时语音识别输入,简单又快捷,外婆用了说直说好!
效果示例
应用场景
实时语音识别和音频转文字有着丰富的应用的场景。
- 游戏应用中的运用:当你在联机游戏场组队开黑时,通过实时语音识别跟队友无阻沟通,不占用双手的同时,也避免了开麦露出声音的尴尬。
- 办公应用中的运用:职场里,耗时长的会议,手打码字记录即低效,还容易漏掉细节,凭借音频文件转文字功能,转写会议讨论内容,会后对转写的文字进行梳理润色,事半功倍。
- 学习应用中的运用:时下越来越多的音频教学材料,一边观看一边暂停做笔记,很容易打断学习节奏,破坏学习过程的完整性,有了音频文件转写,系统的学习完教材后,再对文字进行复习梳理,学习体验更佳。
实现原理
华为机器学习服务提供实时语音识别和音频文件转写能力。
实时语音识别
支持将实时输入的短语音(时长不超过60秒)转换为文本,识别准确率可达95%以上。目前支持中文普通话、英语、中英混说、法语、德语、西班牙语、意大利语、阿拉伯语的识别。
- 支持实时出字。
- 提供拾音界面、无拾音界面两种方式。
- 支持端点检测,可准确定位开始和结束点。
- 支持静音检测,语音中未说话部分不发送语音包。
- 支持数字格式的智能转换,例如语音输入“二零二一年”时,能够智能识别为“2021年”。
音频文件转写
可将5小时内的音频文件转换成文字,支持输出标点符号,形成断句合理、易于理解的文本信息。同时支持生成带有时间戳的文本信息,便于后续进行更多功能开发。当前版本支持中英文的转写。
开发步骤
开发前准备
1. 配置华为Maven仓地址并将agconnect-services.json文件放到app目录下:
打开Android Studio项目级“build.gradle”文件。
添加HUAWEI agcp插件以及Maven代码库。
- 在“allprojects > repositories”中配置HMS Core SDK的Maven仓地址。
- 在“buildscript > repositories”中配置HMS Core SDK的Maven仓地址。
- 如果App中添加了“agconnect-services.json”文件则需要在“buildscript > dependencies”中增加agcp配置。
-
buildscript {
-
repositories {
-
google()
-
jcenter()
-
maven { url
'https:
//developer.huawei.com/repo/' }
-
}
-
dependencies {
-
classpath
'com.android.tools.build:gradle:
3.5
.4'
-
classpath
'com.huawei.agconnect:agcp:
1.4
.1
.300'
-
// NOTE: Do not place your application dependencies here; they belong
-
// in the individual module build.gradle files
-
}
-
}
-
-
allprojects {
-
repositories {
-
google()
-
jcenter()
-
maven { url
'https:
//developer.huawei.com/repo/' }
-
}
-
}
参见云端鉴权信息使用须知,设置应用的鉴权信息。
2. 添加编译SDK依赖:
-
dependencies {
-
//音频文件转写能力 SDK
-
implementation
'com.huawei.hms:ml-computer-voice-aft:2.2.0.300'
-
// 实时语音转写 SDK.
-
implementation
'com.huawei.hms:ml-computer-voice-asr:2.2.0.300'
-
// 实时语音转写 plugin.
-
implementation
'com.huawei.hms:ml-computer-voice-asr-plugin:2.2.0.300'
-
...
-
}
-
apply
plugin:
'com.huawei.agconnect'
// HUAWEI agconnect Gradle plugin
3.在app的build中配置签名文件并将签名文件(xxx.jks)放入app目录下:
-
signingConfigs
{
-
release
{
-
storeFile
file("xxx.jks")
-
keyAlias
xxx
-
keyPassword
xxxxxx
-
storePassword
xxxxxx
-
v1SigningEnabled
true
-
v2SigningEnabled
true
-
}
-
-
}
-
-
buildTypes
{
-
release
{
-
minifyEnabled
false
-
proguardFiles
getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
-
}
-
-
debug
{
-
signingConfig
signingConfigs.release
-
debuggable
true
-
}
-
}
4.在Manifest.xml中添加权限:
-
<uses-permission android:name="android.permission.INTERNET" />
-
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
-
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
-
<uses-permission android:name="android.permission.RECORD_AUDIO" />
-
-
<application
-
android:requestLegacyExternalStorage=
"true"
-
...
-
</
application>
-
接入实时语音识别能力
1.进行权限动态申请:
-
if (ActivityCompat.checkSelfPermission(
this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
-
requestCameraPermission();
-
}
-
-
private void requestCameraPermission() {
-
final String[] permissions = new String[]{Manifest.permission.RECORD_AUDIO};
-
if (!ActivityCompat.shouldShowRequestPermissionRationale(
this, Manifest.permission.RECORD_AUDIO)) {
-
ActivityCompat.requestPermissions(
this, permissions, Constants.AUDIO_PERMISSION_CODE);
-
return;
-
}
-
}
2.创建Intent,用于设置实时语音识别参数。
-
//设置您应用的鉴权信息
-
MLApplication.getInstance().setApiKey(AGConnectServicesConfig.fromContext(
this).getString(
"client/api_key"));
-
通过intent进行识别设置。
-
Intent intentPlugin = new Intent(
this, MLAsrCaptureActivity.
class)
-
// 设置识别语言为英语,若不设置,则默认识别英语。支持设置:
"zh-CN":中文;
"en-US":英语等。
-
.putExtra(MLAsrCaptureConstants.LANGUAGE, MLAsrConstants.LAN_ZH_CN)
-
// 设置拾音界面是否显示识别结果
-
.putExtra(MLAsrCaptureConstants.FEATURE, MLAsrCaptureConstants.FEATURE_WORDFLUX);
-
startActivityForResult(intentPlugin,
"1");
3.覆写“onActivityResult”方法,用于处理语音识别服务返回结果。
-
@Override
-
protected void onActivityResult(int requestCode, int resultCode,
@Nullable Intent
data) {
-
super.onActivityResult(requestCode, resultCode,
data);
-
String text =
"";
-
if (
null ==
data) {
-
addTagItem(
"Intent data is null.",
true);
-
}
-
if (requestCode ==
"1") {
-
if (
data ==
null) {
-
return;
-
}
-
Bundle bundle =
data.getExtras();
-
if (bundle ==
null) {
-
return;
-
}
-
switch (resultCode) {
-
case MLAsrCaptureConstants.ASR_SUCCESS:
-
// 获取语音识别得到的文本信息。
-
if (bundle.containsKey(MLAsrCaptureConstants.ASR_RESULT)) {
-
text = bundle.getString(MLAsrCaptureConstants.ASR_RESULT);
-
}
-
if (text ==
null ||
"".equals(text)) {
-
text =
"Result is null.";
-
Log.e(TAG, text);
-
}
else {
-
//将语音识别结果设置在搜索框上
-
searchEdit.setText(text);
-
goSearch(text,
true);
-
}
-
break;
-
// 返回值为MLAsrCaptureConstants.ASR_FAILURE表示识别失败。
-
case MLAsrCaptureConstants.ASR_FAILURE:
-
// 判断是否包含错误码。
-
if (bundle.containsKey(MLAsrCaptureConstants.ASR_ERROR_CODE)) {
-
text = text + bundle.getInt(MLAsrCaptureConstants.ASR_ERROR_CODE);
-
// 对错误码进行处理。
-
}
-
// 判断是否包含错误信息。
-
if (bundle.containsKey(MLAsrCaptureConstants.ASR_ERROR_MESSAGE)) {
-
String errorMsg = bundle.getString(MLAsrCaptureConstants.ASR_ERROR_MESSAGE);
-
// 对错误信息进行处理。
-
if (errorMsg !=
null && !
"".equals(errorMsg)) {
-
text =
"[" + text +
"]" + errorMsg;
-
}
-
}
-
//判断是否包含子错误码。
-
if (bundle.containsKey(MLAsrCaptureConstants.ASR_SUB_ERROR_CODE)) {
-
int subErrorCode = bundle.getInt(MLAsrCaptureConstants.ASR_SUB_ERROR_CODE);
-
// 对子错误码进行处理。
-
text =
"[" + text +
"]" + subErrorCode;
-
}
-
Log.e(TAG, text);
-
break;
-
default:
-
break;
-
}
-
}
-
}
接入音频文件转写能力
1.申请动态权限。
-
private
static
final
int REQUEST_EXTERNAL_STORAGE =
1;
-
private
static
final String[] PERMISSIONS_STORAGE = {
-
Manifest.permission.READ_EXTERNAL_STORAGE,
-
Manifest.permission.WRITE_EXTERNAL_STORAGE };
-
public static void verifyStoragePermissions(Activity activity) {
-
// Check if we have write permission
-
int permission = ActivityCompat.checkSelfPermission(activity,
-
Manifest.permission.WRITE_EXTERNAL_STORAGE);
-
if (permission != PackageManager.PERMISSION_GRANTED) {
-
// We don't have permission so prompt the user
-
ActivityCompat.requestPermissions(activity, PERMISSIONS_STORAGE,
-
REQUEST_EXTERNAL_STORAGE);
-
}
-
}
2.新建音频文件转写引擎并初始化;新建音频文件转写配置器。
-
// 设置 ApiKey.
-
MLApplication.getInstance().setApiKey(AGConnectServicesConfig.fromContext(getApplication()).getString(
"client/api_key"));
-
MLRemoteAftSetting setting = new MLRemoteAftSetting.Factory()
-
// 设置转写语言编码,使用BCP-47规范,当前支持中文普通话、英文转写。
-
.setLanguageCode(
"zh")
-
// 设置是否在转写输出的文本中自动增加标点符号,默认为false。
-
.enablePunctuation(
true)
-
// 设置是否连带输出每段音频的文字转写结果和对应的音频时移,默认为false(此参数仅小于1分钟的音频需要设置)。
-
.enableWordTimeOffset(
true)
-
// 设置是否输出句子出现在音频文件中的时间偏移值,默认为false。
-
.enableSentenceTimeOffset(
true)
-
.create();
-
-
// 新建音频文件转写引擎。
-
MLRemoteAftEngine engine = MLRemoteAftEngine.getInstance();
-
engine.
init(
this);
-
// 将侦听器回调传给第一步中定义的音频文件转写引擎中
-
engine.setAftListener(aftListener);
3.新建侦听器回调,用于处理音频文件转写结果:
短语音转写:适用于时长小于1分钟的音频文件
-
private MLRemoteAftListener aftListener =
new MLRemoteAftListener() {
-
public
void onResult(
String taskId, MLRemoteAftResult result,
Object ext) {
-
// 获取转写结果通知。
-
if (result.isComplete()) {
-
// 转写结果处理。
-
}
-
}
-
@Override
-
public
void onError(
String taskId, int errorCode,
String message) {
-
// 转写错误回调函数。
-
}
-
@Override
-
public
void onInitComplete(
String taskId,
Object ext) {
-
// 预留接口。
-
}
-
@Override
-
public
void onUploadProgress(
String taskId, double progress,
Object ext) {
-
// 预留接口。
-
}
-
@Override
-
public
void onEvent(
String taskId, int eventId,
Object ext) {
-
// 预留接口。
-
}
-
};
长语音转写:适用于时长大于1分钟的音频文件
-
private MLRemoteAftListener asrListener =
new MLRemoteAftListener() {
-
@Override
-
public
void onInitComplete(
String taskId,
Object ext) {
-
Log.e(TAG,
"MLAsrCallBack onInitComplete");
-
// 长语音初始化完成,开始转写
-
start(taskId);
-
}
-
@Override
-
public
void onUploadProgress(
String taskId, double progress,
Object ext) {
-
Log.e(TAG,
" MLAsrCallBack onUploadProgress");
-
}
-
@Override
-
public
void onEvent(
String taskId, int eventId,
Object ext) {
-
// 用于长语音
-
Log.e(TAG,
"MLAsrCallBack onEvent" + eventId);
-
if (MLAftEvents.UPLOADED_EVENT == eventId) {
// 文件上传成功
-
// 获取转写结果
-
startQueryResult(taskId);
-
}
-
}
-
@Override
-
public
void onResult(
String taskId, MLRemoteAftResult result,
Object ext) {
-
Log.e(TAG,
"MLAsrCallBack onResult taskId is :" + taskId +
" ");
-
if (result !=
null) {
-
Log.e(TAG,
"MLAsrCallBack onResult isComplete: " + result.isComplete());
-
if (result.isComplete()) {
-
TimerTask timerTask = timerTaskMap.get(taskId);
-
if (
null != timerTask) {
-
timerTask.cancel();
-
timerTaskMap.remove(taskId);
-
}
-
if (result.getText() !=
null) {
-
Log.e(TAG, taskId +
" MLAsrCallBack onResult result is : " + result.getText());
-
tvText.setText(result.getText());
-
}
-
List<MLRemoteAftResult.Segment> words = result.getWords();
-
if (words !=
null && words.size() !=
0) {
-
for (MLRemoteAftResult.Segment word : words) {
-
Log.e(TAG,
"MLAsrCallBack word text is : " + word.getText() +
", startTime is : " + word.getStartTime() +
". endTime is : " + word.getEndTime());
-
}
-
}
-
List<MLRemoteAftResult.Segment> sentences = result.getSentences();
-
if (sentences !=
null && sentences.size() !=
0) {
-
for (MLRemoteAftResult.Segment sentence : sentences) {
-
Log.e(TAG,
"MLAsrCallBack sentence text is : " + sentence.getText() +
", startTime is : " + sentence.getStartTime() +
". endTime is : " + sentence.getEndTime());
-
}
-
}
-
}
-
}
-
}
-
@Override
-
public
void onError(
String taskId, int errorCode,
String message) {
-
Log.i(TAG,
"MLAsrCallBack onError : " + message +
"errorCode, " + errorCode);
-
switch (errorCode) {
-
case MLAftErrors.ERR_AUDIO_FILE_NOTSUPPORTED:
-
break;
-
}
-
}
-
};
-
// 上传转写任务
-
private
void start(
String taskId) {
-
Log.e(TAG,
"start");
-
engine.setAftListener(asrListener);
-
engine.startTask(taskId);
-
}
-
// 获取转写结果
-
private
Map<
String, TimerTask> timerTaskMap =
new HashMap<>();
-
private
void startQueryResult(final
String taskId) {
-
Timer mTimer =
new Timer();
-
TimerTask mTimerTask =
new TimerTask() {
-
@Override
-
public
void run() {
-
getResult(taskId);
-
}
-
};
-
// 10s轮训获取长语音转写结果
-
mTimer.schedule(mTimerTask,
5000,
10000);
-
// 界面销毁前要清除 timerTaskMap
-
timerTaskMap.put(taskId, mTimerTask);
-
}
4.获取音频,上传音频文件到转写引擎中:
-
//获取音频文件的uri
-
Uri uri = getFileUri();
-
//获取音频时间
-
Long audioTime = getAudioFileTimeFromUri(uri);
-
//判断音频时间是否超过60秒
-
if (audioTime <
60000) {
-
// uri为从本地存储或者录音机读取到的语音资源,仅支持时长在1分钟之内的本地音频
-
this.taskId =
this.engine.shortRecognize(uri,
this.setting);
-
Log.i(TAG,
"Short audio transcription.");
-
}
else {
-
// longRecognize为长语音转写接口,用于转写时长大于1分钟,小于5小时的语音。
-
this.taskId =
this.engine.longRecognize(uri,
this.setting);
-
Log.i(TAG,
"Long audio transcription.");
-
}
-
-
private
Long getAudioFileTimeFromUri(Uri uri) {
-
Long time =
null;
-
Cursor cursor =
this.getContentResolver()
-
.query(uri,
null,
null,
null,
null);
-
if (cursor !=
null) {
-
-
cursor.moveToFirst();
-
time = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION));
-
}
else {
-
MediaPlayer mediaPlayer = new MediaPlayer();
-
try {
-
mediaPlayer.setDataSource(String.valueOf(uri));
-
mediaPlayer.prepare();
-
}
catch (IOException e) {
-
Log.e(TAG,
"Failed to read the file time.");
-
}
-
time =
Long.valueOf(mediaPlayer.getDuration());
-
}
-
return time;
-
}
>>访问华为开发者联盟官网,了解更多相关内容
>>获取开发指导文档
关注我们,第一时间了解华为移动服务最新技术资讯~
转载:https://blog.csdn.net/HUAWEI_HMSCore/article/details/115242088