300行代碼讓外婆實(shí)現(xiàn)語(yǔ)音搜索購(gòu)物功能
“阿強(qiáng),手寫板怎么又不見(jiàn)了?”
最近,程序員阿強(qiáng)的那位勇于嘗試新事物的外婆,又迷上了網(wǎng)購(gòu)。在不太費(fèi)勁兒地把購(gòu)物軟件摸得門兒清之后,沒(méi)想到,本以為順暢的網(wǎng)購(gòu)之路,卡在了搜索物品上。
在手寫輸入環(huán)節(jié),要么誤操作,無(wú)意中更換到不熟悉的輸入法;要么誤按了界面上抽象的指令字符……于是阿強(qiáng)也經(jīng)常收到外婆發(fā)來(lái)的求助。
其實(shí),不止是購(gòu)物應(yīng)用,時(shí)下智能手機(jī)里裝載的大部APP,都是傾斜于年輕群體的交互設(shè)計(jì),老年人想要體驗(yàn)學(xué)會(huì)使用,很難真香。
在一次次耐心指導(dǎo)外婆完成操作后,阿強(qiáng),這個(gè)成熟coder給自己提了個(gè)需求:提升外婆的網(wǎng)購(gòu)體驗(yàn)。不是一味讓她適應(yīng)輸入法,而是讓輸入法迎合外婆的使用偏好習(xí)慣。
手動(dòng)輸入易出錯(cuò),那就寫個(gè)語(yǔ)音轉(zhuǎn)文字的輸入方法,只要啟動(dòng)錄音按鈕,實(shí)時(shí)語(yǔ)音識(shí)別輸入,簡(jiǎn)單又快捷,外婆用了說(shuō)直說(shuō)好!
效果示例

應(yīng)用場(chǎng)景
實(shí)時(shí)語(yǔ)音識(shí)別和音頻轉(zhuǎn)文字有著豐富的應(yīng)用的場(chǎng)景。
游戲應(yīng)用中的運(yùn)用:當(dāng)你在聯(lián)機(jī)游戲場(chǎng)組隊(duì)開(kāi)黑時(shí),通過(guò)實(shí)時(shí)語(yǔ)音識(shí)別跟隊(duì)友無(wú)阻溝通,不占用雙手的同時(shí),也避免了開(kāi)麥露出聲音的尷尬。
辦公應(yīng)用中的運(yùn)用:職場(chǎng)里,耗時(shí)長(zhǎng)的會(huì)議,手打碼字記錄即低效,還容易漏掉細(xì)節(jié),憑借音頻文件轉(zhuǎn)文字功能,轉(zhuǎn)寫會(huì)議討論內(nèi)容,會(huì)后對(duì)轉(zhuǎn)寫的文字進(jìn)行梳理潤(rùn)色,事半功倍。
學(xué)習(xí)應(yīng)用中的運(yùn)用:時(shí)下越來(lái)越多的音頻教學(xué)材料,一邊觀看一邊暫停做筆記,很容易打斷學(xué)習(xí)節(jié)奏,破壞學(xué)習(xí)過(guò)程的完整性,有了音頻文件轉(zhuǎn)寫,系統(tǒng)的學(xué)習(xí)完教材后,再對(duì)文字進(jìn)行復(fù)習(xí)梳理,學(xué)習(xí)體驗(yàn)更佳。
實(shí)現(xiàn)原理
華為機(jī)器學(xué)習(xí)服務(wù)提供實(shí)時(shí)語(yǔ)音識(shí)別和音頻文件轉(zhuǎn)寫能力。
實(shí)時(shí)語(yǔ)音識(shí)別
支持將實(shí)時(shí)輸入的短語(yǔ)音(時(shí)長(zhǎng)不超過(guò)60秒)轉(zhuǎn)換為文本,識(shí)別準(zhǔn)確率可達(dá)95%以上。目前支持中文普通話、英語(yǔ)、中英混說(shuō)、法語(yǔ)、德語(yǔ)、西班牙語(yǔ)、意大利語(yǔ)、阿拉伯語(yǔ)的識(shí)別。
- 支持實(shí)時(shí)出字。
- 提供拾音界面、無(wú)拾音界面兩種方式。
- 支持端點(diǎn)檢測(cè),可準(zhǔn)確定位開(kāi)始和結(jié)束點(diǎn)。
- 支持靜音檢測(cè),語(yǔ)音中未說(shuō)話部分不發(fā)送語(yǔ)音包。
- 支持?jǐn)?shù)字格式的智能轉(zhuǎn)換,例如語(yǔ)音輸入“二零二一年”時(shí),能夠智能識(shí)別為“2021年”。
音頻文件轉(zhuǎn)寫
可將5小時(shí)內(nèi)的音頻文件轉(zhuǎn)換成文字,支持輸出標(biāo)點(diǎn)符號(hào),形成斷句合理、易于理解的文本信息。同時(shí)支持生成帶有時(shí)間戳的文本信息,便于后續(xù)進(jìn)行更多功能開(kāi)發(fā)。當(dāng)前版本支持中英文的轉(zhuǎn)寫。

開(kāi)發(fā)步驟
開(kāi)發(fā)前準(zhǔn)備
1. 配置華為Maven倉(cāng)地址并將agconnect-services.json文件放到app目錄下:
打開(kāi)Android Studio項(xiàng)目級(jí)“build.gradle”文件。

添加HUAWEI agcp插件以及Maven代碼庫(kù)。
- 在“allprojects > repositories”中配置HMS Core SDK的Maven倉(cāng)地址。
- 在“buildscript > repositories”中配置HMS Core SDK的Maven倉(cāng)地址。
- 如果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/' }
}
}
參見(jiàn)云端鑒權(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'
// 實(shí)時(shí)語(yǔ)音轉(zhuǎn)寫 SDK.
implementation 'com.huawei.hms:ml-computer-voice-asr:2.2.0.300'
// 實(shí)時(shí)語(yǔ)音轉(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>
接入實(shí)時(shí)語(yǔ)音識(shí)別能力
1.進(jìn)行權(quán)限動(dòng)態(tài)申請(qǐng):
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ǔ)音識(shí)別參數(shù)。
//設(shè)置您應(yīng)用的鑒權(quán)信息
MLApplication.getInstance().setApiKey(AGConnectServicesConfig.fromContext(this).getString("client/api_key"));
通過(guò)intent進(jìn)行識(shí)別設(shè)置。
Intent intentPlugin = new Intent(this, MLAsrCaptureActivity.class)
// 設(shè)置識(shí)別語(yǔ)言為英語(yǔ),若不設(shè)置,則默認(rèn)識(shí)別英語(yǔ)。支持設(shè)置:"zh-CN":中文;"en-US":英語(yǔ)等。
.putExtra(MLAsrCaptureConstants.LANGUAGE, MLAsrConstants.LAN_ZH_CN)
// 設(shè)置拾音界面是否顯示識(shí)別結(jié)果
.putExtra(MLAsrCaptureConstants.FEATURE, MLAsrCaptureConstants.FEATURE_WORDFLUX);
startActivityForResult(intentPlugin, "1");
3.覆寫“onActivityResult”方法,用于處理語(yǔ)音識(shí)別服務(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:
// 獲取語(yǔ)音識(shí)別得到的文本信息。
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 {
//將語(yǔ)音識(shí)別結(jié)果設(shè)置在搜索框上
searchEdit.setText(text);
goSearch(text, true);
}
break;
// 返回值為MLAsrCaptureConstants.ASR_FAILURE表示識(shí)別失敗。
case MLAsrCaptureConstants.ASR_FAILURE:
// 判斷是否包含錯(cuò)誤碼。
if (bundle.containsKey(MLAsrCaptureConstants.ASR_ERROR_CODE)) {
text = text + bundle.getInt(MLAsrCaptureConstants.ASR_ERROR_CODE);
// 對(duì)錯(cuò)誤碼進(jìn)行處理。
}
// 判斷是否包含錯(cuò)誤信息。
if (bundle.containsKey(MLAsrCaptureConstants.ASR_ERROR_MESSAGE)) {
String errorMsg = bundle.getString(MLAsrCaptureConstants.ASR_ERROR_MESSAGE);
// 對(duì)錯(cuò)誤信息進(jìn)行處理。
if (errorMsg != null && !"".equals(errorMsg)) {
text = "[" + text + "]" + errorMsg;
}
}
//判斷是否包含子錯(cuò)誤碼。
if (bundle.containsKey(MLAsrCaptureConstants.ASR_SUB_ERROR_CODE)) {
int subErrorCode = bundle.getInt(MLAsrCaptureConstants.ASR_SUB_ERROR_CODE);
// 對(duì)子錯(cuò)誤碼進(jìn)行處理。
text = "[" + text + "]" + subErrorCode;
}
Log.e(TAG, text);
break;
default:
break;
}
}
}
接入音頻文件轉(zhuǎn)寫能力
1.申請(qǐng)動(dòng)態(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)寫語(yǔ)言編碼,使用BCP-47規(guī)范,當(dāng)前支持中文普通話、英文轉(zhuǎn)寫。
.setLanguageCode("zh")
// 設(shè)置是否在轉(zhuǎn)寫輸出的文本中自動(dòng)增加標(biāo)點(diǎn)符號(hào),默認(rèn)為false。
.enablePunctuation(true)
// 設(shè)置是否連帶輸出每段音頻的文字轉(zhuǎn)寫結(jié)果和對(duì)應(yīng)的音頻時(shí)移,默認(rèn)為false(此參數(shù)僅小于1分鐘的音頻需要設(shè)置)。
.enableWordTimeOffset(true)
// 設(shè)置是否輸出句子出現(xiàn)在音頻文件中的時(shí)間偏移值,默認(rèn)為false。
.enableSentenceTimeOffset(true)
.create();
// 新建音頻文件轉(zhuǎn)寫引擎。
MLRemoteAftEngine engine = MLRemoteAftEngine.getInstance();
engine.init(this);
// 將偵聽(tīng)器回調(diào)傳給第一步中定義的音頻文件轉(zhuǎn)寫引擎中
engine.setAftListener(aftListener);
3.新建偵聽(tīng)器回調(diào),用于處理音頻文件轉(zhuǎn)寫結(jié)果:
短語(yǔ)音轉(zhuǎn)寫:適用于時(shí)長(zhǎng)小于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)寫錯(cuò)誤回調(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ù)留接口。
}
};
長(zhǎng)語(yǔ)音轉(zhuǎn)寫:適用于時(shí)長(zhǎng)大于1分鐘的音頻文件
private MLRemoteAftListener asrListener = new MLRemoteAftListener() {
@Override
public void onInitComplete(String taskId, Object ext) {
Log.e(TAG, "MLAsrCallBack onInitComplete");
// 長(zhǎng)語(yǔ)音初始化完成,開(kāi)始轉(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) {
// 用于長(zhǎng)語(yǔ)音
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)獲取長(zhǎng)語(yǔ)音轉(zhuǎn)寫結(jié)果
mTimer.schedule(mTimerTask, 5000, 10000);
// 界面銷毀前要清除 timerTaskMap
timerTaskMap.put(taskId, mTimerTask);
}
4.獲取音頻,上傳音頻文件到轉(zhuǎn)寫引擎中:
//獲取音頻文件的uri
Uri uri = getFileUri();
//獲取音頻時(shí)間
Long audioTime = getAudioFileTimeFromUri(uri);
//判斷音頻時(shí)間是否超過(guò)60秒
if (audioTime < 60000) {
// uri為從本地存儲(chǔ)或者錄音機(jī)讀取到的語(yǔ)音資源,僅支持時(shí)長(zhǎng)在1分鐘之內(nèi)的本地音頻
this.taskId = this.engine.shortRecognize(uri, this.setting);
Log.i(TAG, "Short audio transcription.");
} else {
// longRecognize為長(zhǎng)語(yǔ)音轉(zhuǎn)寫接口,用于轉(zhuǎn)寫時(shí)長(zhǎng)大于1分鐘,小于5小時(shí)的語(yǔ)音。
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行代碼讓外婆實(shí)現(xiàn)語(yǔ)音搜索購(gòu)物功能的文章就介紹到這了,更多相關(guān)外婆實(shí)現(xiàn)語(yǔ)音搜索購(gòu)物內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
git merge --ff/--no-ff/--ff-only 三種選項(xiàng)參數(shù)的區(qū)別解析
這篇文章主要介紹了git merge --ff/--no-ff/--ff-only 三種選項(xiàng)參數(shù)的區(qū)別解析,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04
Hbuilder連遠(yuǎn)程接服務(wù)器上傳代碼的圖文教程
下面小編就為大家分享一篇Hbuilder連遠(yuǎn)程接服務(wù)器上傳代碼的圖文教程,具有很好的參考價(jià)值,一起跟隨小編過(guò)來(lái)看看吧,希望對(duì)大家有所幫助2017-11-11
權(quán)限控制之粗粒度與細(xì)粒度概念及實(shí)現(xiàn)簡(jiǎn)單介紹
這篇文章主要介紹了權(quán)限控制之粗粒度與細(xì)粒度概念及實(shí)現(xiàn)簡(jiǎn)單介紹,具有一定參考價(jià)值,需要的朋友可以了解下。2017-10-10
計(jì)算機(jī)二級(jí)如何一次性通過(guò)?給NCRE焦躁心情降溫!
計(jì)算機(jī)二級(jí)到現(xiàn)階段應(yīng)該如何備考,該聽(tīng)什么課?該針對(duì)哪些考點(diǎn)重點(diǎn)學(xué)習(xí),這些都要做到心里有數(shù),有計(jì)劃性。這篇文章為大家分享了計(jì)算機(jī)二級(jí)備考技巧,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08
vant/vue實(shí)現(xiàn)小程序下拉刷新功能方法詳解
這篇文章主要介紹了vant/vue實(shí)現(xiàn)小程序下拉刷新功能方法詳解,需要的朋友可以參考下2022-12-12
Unity3D中shader 輪廓描邊效果實(shí)現(xiàn)代碼
這篇文章主要介紹了Unity3D中shader 輪廓描邊效果的相關(guān)資料,需要的朋友可以參考下2017-03-03
ffmpeg播放器實(shí)現(xiàn)詳解之框架搭建過(guò)程
這篇文章主要介紹了ffmpeg播放器實(shí)現(xiàn)詳解之框架搭建過(guò)程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07

