300行代碼讓外婆實現語音搜索購物功能
“阿強,手寫板怎麼又不見瞭?”
最近,程序員阿強的那位勇於嘗試新事物的外婆,又迷上瞭網購。在不太費勁兒地把購物軟件摸得門兒清之後,沒想到,本以為順暢的網購之路,卡在瞭搜索物品上。
在手寫輸入環節,要麼誤操作,無意中更換到不熟悉的輸入法;要麼誤按瞭界面上抽象的指令字符……於是阿強也經常收到外婆發來的求助。
其實,不止是購物應用,時下智能手機裡裝載的大部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; }
到此這篇關於300行代碼讓外婆實現語音搜索購物功能的文章就介紹到這瞭,更多相關外婆實現語音搜索購物內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- None Found