“阿强,手写板怎么又不见了?”
最近,程序员阿强的那位敢于尝试新事物的外婆,又迷上了网购。在不太费劲儿地把购物软件摸得门儿清之后,没想到,本认为顺畅的网购之路,卡在了搜寻物品上。
在手写输入环节,要么误操作,无心中更换到不相熟的输入法;要么误按了界面上形象的指令字符……于是阿强也常常收到外婆发来的求助。
其实,不止是购物利用,时下智能手机里装载的大部APP,都是歪斜于年老群体的交互设计,老年人想要体验学会应用,很难真香。
在一次次急躁领导外婆实现操作后,阿强,这个成熟coder给本人提了个需要:晋升外婆的网购体验。不是一味让她适应输入法,而是让输入法投合外婆的应用偏好习惯。
手动输出易出错,那就写个语音转文字的输出办法,只有启动录音按钮,实时语音辨认输出,简略又快捷,外婆用了说直说好!
成果演示
实时语音辨认和音频转文字有丰盛的应用场景
1、游戏利用中的使用:当你在联机游戏场组队开黑时,通过实时语音辨认跟队友无阻沟通,不占用双手的同时,也防止了开麦露出声音的难堪。。
2、办公利用中的使用:职场里,耗时长的会议,手打码字记录即低效,还容易漏掉细节,凭借音频文件转文字性能,转写会议探讨内容,会后对转写的文字进行梳理润色,事倍功半。
3、学习利用中的使用:时下越来越多的音频教学材料,一边观看一边暂停做笔记,很容易打断学习节奏,毁坏学习过程的完整性,有了音频文件转写,零碎的学习完教材后,再对文字进行温习梳理,学习体验更佳。
实现原理
华为机器学习服务提供实时语音辨认和音频文件转写能力。
实时语音辨认反对将实时输出的短语音(时长不超过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/' } }}
参见云端鉴权信息应用须知,设置利用的鉴权信息。
- 增加编译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
- 在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 }}
- 在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>
2、接入实时语音辨认能力
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; }}
- 创立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");
- 覆写“onActivityResult”办法,用于解决语音辨认服务返回后果。
@Overrideprotected 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; } }}
3. 接入音频文件转写能力
- 申请动静权限。
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); }}
- 新建音频文件转写引擎并初始化;新建音频文件转写配置器。
// 设置 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);
- 新建侦听器回调,用于解决音频文件转写后果:
短语音转写:实用于时长小于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);}
- 获取音频,上传音频文件到转写引擎中:
//获取音频文件的uriUri 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;}
拜访华为开发者联盟官网,理解更多相干内容
获取开发领导文档
华为挪动服务开源仓库地址:GitHub、Gitee