300行代碼讓外婆實現(xiàn)語音搜索購物功能
“阿強,手寫板怎么又不見了?”
最近,程序員阿強的那位勇于嘗試新事物的外婆,又迷上了網(wǎng)購。在不太費勁兒地把購物軟件摸得門兒清之后,沒想到,本以為順暢的網(wǎng)購之路,卡在了搜索物品上。
在手寫輸入環(huán)節(jié),要么誤操作,無意中更換到不熟悉的輸入法;要么誤按了界面上抽象的指令字符……于是阿強也經(jīng)常收到外婆發(fā)來的求助。
其實,不止是購物應(yīng)用,時下智能手機里裝載的大部APP,都是傾斜于年輕群體的交互設(shè)計,老年人想要體驗學(xué)會使用,很難真香。
在一次次耐心指導(dǎo)外婆完成操作后,阿強,這個成熟coder給自己提了個需求:提升外婆的網(wǎng)購體驗。不是一味讓她適應(yīng)輸入法,而是讓輸入法迎合外婆的使用偏好習(xí)慣。
手動輸入易出錯,那就寫個語音轉(zhuǎn)文字的輸入方法,只要啟動錄音按鈕,實時語音識別輸入,簡單又快捷,外婆用了說直說好!
效果示例
應(yīng)用場景
實時語音識別和音頻轉(zhuǎn)文字有著豐富的應(yīng)用的場景。
游戲應(yīng)用中的運用:當你在聯(lián)機游戲場組隊開黑時,通過實時語音識別跟隊友無阻溝通,不占用雙手的同時,也避免了開麥露出聲音的尷尬。
辦公應(yīng)用中的運用:職場里,耗時長的會議,手打碼字記錄即低效,還容易漏掉細節(jié),憑借音頻文件轉(zhuǎn)文字功能,轉(zhuǎn)寫會議討論內(nèi)容,會后對轉(zhuǎn)寫的文字進行梳理潤色,事半功倍。
學(xué)習(xí)應(yīng)用中的運用:時下越來越多的音頻教學(xué)材料,一邊觀看一邊暫停做筆記,很容易打斷學(xué)習(xí)節(jié)奏,破壞學(xué)習(xí)過程的完整性,有了音頻文件轉(zhuǎn)寫,系統(tǒng)的學(xué)習(xí)完教材后,再對文字進行復(fù)習(xí)梳理,學(xué)習(xí)體驗更佳。
實現(xiàn)原理
華為機器學(xué)習(xí)服務(wù)提供實時語音識別和音頻文件轉(zhuǎn)寫能力。
實時語音識別
支持將實時輸入的短語音(時長不超過60秒)轉(zhuǎn)換為文本,識別準確率可達95%以上。目前支持中文普通話、英語、中英混說、法語、德語、西班牙語、意大利語、阿拉伯語的識別。
- 支持實時出字。
- 提供拾音界面、無拾音界面兩種方式。
- 支持端點檢測,可準確定位開始和結(jié)束點。
- 支持靜音檢測,語音中未說話部分不發(fā)送語音包。
- 支持數(shù)字格式的智能轉(zhuǎn)換,例如語音輸入“二零二一年”時,能夠智能識別為“2021年”。
音頻文件轉(zhuǎn)寫
可將5小時內(nèi)的音頻文件轉(zhuǎn)換成文字,支持輸出標點符號,形成斷句合理、易于理解的文本信息。同時支持生成帶有時間戳的文本信息,便于后續(xù)進行更多功能開發(fā)。當前版本支持中英文的轉(zhuǎn)寫。
開發(fā)步驟
開發(fā)前準備
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/' } } }
參見云端鑒權(quán)信息使用須知,設(shè)置應(yīng)用的鑒權(quán)信息。
2. 添加編譯SDK依賴:
dependencies { //音頻文件轉(zhuǎn)寫能力 SDK implementation 'com.huawei.hms:ml-computer-voice-aft:2.2.0.300' // 實時語音轉(zhuǎn)寫 SDK. implementation 'com.huawei.hms:ml-computer-voice-asr:2.2.0.300' // 實時語音轉(zhuǎn)寫 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中添加權(quán)限:
<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.進行權(quán)限動態(tài)申請:
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.創(chuàng)建Intent,用于設(shè)置實時語音識別參數(shù)。
//設(shè)置您應(yīng)用的鑒權(quán)信息 MLApplication.getInstance().setApiKey(AGConnectServicesConfig.fromContext(this).getString("client/api_key")); 通過intent進行識別設(shè)置。 Intent intentPlugin = new Intent(this, MLAsrCaptureActivity.class) // 設(shè)置識別語言為英語,若不設(shè)置,則默認識別英語。支持設(shè)置:"zh-CN":中文;"en-US":英語等。 .putExtra(MLAsrCaptureConstants.LANGUAGE, MLAsrConstants.LAN_ZH_CN) // 設(shè)置拾音界面是否顯示識別結(jié)果 .putExtra(MLAsrCaptureConstants.FEATURE, MLAsrCaptureConstants.FEATURE_WORDFLUX); startActivityForResult(intentPlugin, "1");
3.覆寫“onActivityResult”方法,用于處理語音識別服務(wù)返回結(jié)果。
@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 { //將語音識別結(jié)果設(shè)置在搜索框上 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; } } }
接入音頻文件轉(zhuǎn)寫能力
1.申請動態(tài)權(quán)限。
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.新建音頻文件轉(zhuǎn)寫引擎并初始化;新建音頻文件轉(zhuǎn)寫配置器。
// 設(shè)置 ApiKey. MLApplication.getInstance().setApiKey(AGConnectServicesConfig.fromContext(getApplication()).getString("client/api_key")); MLRemoteAftSetting setting = new MLRemoteAftSetting.Factory() // 設(shè)置轉(zhuǎn)寫語言編碼,使用BCP-47規(guī)范,當前支持中文普通話、英文轉(zhuǎn)寫。 .setLanguageCode("zh") // 設(shè)置是否在轉(zhuǎn)寫輸出的文本中自動增加標點符號,默認為false。 .enablePunctuation(true) // 設(shè)置是否連帶輸出每段音頻的文字轉(zhuǎn)寫結(jié)果和對應(yīng)的音頻時移,默認為false(此參數(shù)僅小于1分鐘的音頻需要設(shè)置)。 .enableWordTimeOffset(true) // 設(shè)置是否輸出句子出現(xiàn)在音頻文件中的時間偏移值,默認為false。 .enableSentenceTimeOffset(true) .create(); // 新建音頻文件轉(zhuǎn)寫引擎。 MLRemoteAftEngine engine = MLRemoteAftEngine.getInstance(); engine.init(this); // 將偵聽器回調(diào)傳給第一步中定義的音頻文件轉(zhuǎn)寫引擎中 engine.setAftListener(aftListener);
3.新建偵聽器回調(diào),用于處理音頻文件轉(zhuǎn)寫結(jié)果:
短語音轉(zhuǎn)寫:適用于時長小于1分鐘的音頻文件
private MLRemoteAftListener aftListener = new MLRemoteAftListener() { public void onResult(String taskId, MLRemoteAftResult result, Object ext) { // 獲取轉(zhuǎn)寫結(jié)果通知。 if (result.isComplete()) { // 轉(zhuǎn)寫結(jié)果處理。 } } @Override public void onError(String taskId, int errorCode, String message) { // 轉(zhuǎn)寫錯誤回調(diào)函數(shù)。 } @Override public void onInitComplete(String taskId, Object ext) { // 預(yù)留接口。 } @Override public void onUploadProgress(String taskId, double progress, Object ext) { // 預(yù)留接口。 } @Override public void onEvent(String taskId, int eventId, Object ext) { // 預(yù)留接口。 } };
長語音轉(zhuǎn)寫:適用于時長大于1分鐘的音頻文件
private MLRemoteAftListener asrListener = new MLRemoteAftListener() { @Override public void onInitComplete(String taskId, Object ext) { Log.e(TAG, "MLAsrCallBack onInitComplete"); // 長語音初始化完成,開始轉(zhuǎn)寫 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) { // 文件上傳成功 // 獲取轉(zhuǎn)寫結(jié)果 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; } } }; // 上傳轉(zhuǎn)寫任務(wù) private void start(String taskId) { Log.e(TAG, "start"); engine.setAftListener(asrListener); engine.startTask(taskId); } // 獲取轉(zhuǎn)寫結(jié)果 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輪訓(xùn)獲取長語音轉(zhuǎn)寫結(jié)果 mTimer.schedule(mTimerTask, 5000, 10000); // 界面銷毀前要清除 timerTaskMap timerTaskMap.put(taskId, mTimerTask); }
4.獲取音頻,上傳音頻文件到轉(zhuǎn)寫引擎中:
//獲取音頻文件的uri Uri uri = getFileUri(); //獲取音頻時間 Long audioTime = getAudioFileTimeFromUri(uri); //判斷音頻時間是否超過60秒 if (audioTime < 60000) { // uri為從本地存儲或者錄音機讀取到的語音資源,僅支持時長在1分鐘之內(nèi)的本地音頻 this.taskId = this.engine.shortRecognize(uri, this.setting); Log.i(TAG, "Short audio transcription."); } else { // longRecognize為長語音轉(zhuǎn)寫接口,用于轉(zhuǎn)寫時長大于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; }
到此這篇關(guān)于300行代碼讓外婆實現(xiàn)語音搜索購物功能的文章就介紹到這了,更多相關(guān)外婆實現(xiàn)語音搜索購物內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Unity3D中shader 輪廓描邊效果實現(xiàn)代碼
這篇文章主要介紹了Unity3D中shader 輪廓描邊效果的相關(guān)資料,需要的朋友可以參考下2017-03-03TCP 四種定時器(重傳定時器,堅持計時器,?;疃〞r器,時間等待計時器)
這篇文章主要介紹了TCP 四種定時器,重傳定時器,堅持計時器,保活定時器,時間等待計時器的相關(guān)資料,需要的朋友可以參考下2017-03-03bilibili彈幕轉(zhuǎn)ass程序制作思路及過程
本文主要是為了方便線下播放Bilibili的彈幕,而專門制作的一款將彈幕轉(zhuǎn)換為ASS的程序,介紹了程序制作的思路及過程,有需要的朋友可以參考下2014-09-09