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行代码让外婆实现语音搜索购物功能的文章就介绍到这了,更多相关外婆实现语音搜索购物内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android自定义控件实现UC浏览器语音搜索效果

    最近项目上要实现语音搜索功能,界面样式要模仿一下UC浏览器的样式,UC浏览器中有一个控件,会随着声音大小浮动,然后寻思偷个懒,百度一下,结果也没有找到类似的,只能自己动手了. 先上图看我实现的效果: 这是自定义控件的代码,里面注释也很明白,就不费话了 public class CustomCircleView extends View{ private Paint mPaint; private int strokeWidth = 0; //圆环的宽度 private Bitmap bitmap

  • 300行代码让外婆实现语音搜索购物功能

    "阿强,手写板怎么又不见了?" 最近,程序员阿强的那位勇于尝试新事物的外婆,又迷上了网购.在不太费劲儿地把购物软件摸得门儿清之后,没想到,本以为顺畅的网购之路,卡在了搜索物品上. 在手写输入环节,要么误操作,无意中更换到不熟悉的输入法:要么误按了界面上抽象的指令字符--于是阿强也经常收到外婆发来的求助. 其实,不止是购物应用,时下智能手机里装载的大部APP,都是倾斜于年轻群体的交互设计,老年人想要体验学会使用,很难真香. 在一次次耐心指导外婆完成操作后,阿强,这个成熟coder给自己提

  • 300行代码实现go语言即时通讯聊天室

    学了2年Java,因为工作原因需要转Golang,3天时间学习了下go的基本语法,做这样一个聊天室小项目来巩固串联一下语法. 实现的功能:公聊,私聊,修改用户名 只用到了四个类: main.go:用来启动服务器 server.go:服务器相关代码 client.go:客户端相关代码,用户可以直接操作的可视化界面 user.go:用户类,用来封装用户的业务逻辑 架构图 完整代码 server.go package main import ( "fmt" "io" &q

  • Python小游戏之300行代码实现俄罗斯方块

    前言 本文代码基于 python3.6 和 pygame1.9.4. 俄罗斯方块是儿时最经典的游戏之一,刚开始接触 pygame 的时候就想写一个俄罗斯方块.但是想到旋转,停靠,消除等操作,感觉好像很难啊,等真正写完了发现,一共也就 300 行代码,并没有什么难的. 先来看一个游戏截图,有点丑,好吧,我没啥美术细胞,但是主体功能都实现了,可以玩起来. 现在来看一下实现的过程. 外形 俄罗斯方块整个界面分为两部分,一部分是左边的游戏区域,另一部分是右边的显示区域,显示得分.速度.下一个方块样式等.

  • C/C++百行代码实现热门游戏消消乐功能的示例代码

    游戏设计 首先我们需要使用第三方框架,这里我使用的是sfml,不会使用sfml在我的上几篇文章当中-扫雷(上)有详细的开发环境搭建介绍 首先准备图片资源 一张背景图片,一张宝石图片 窗口初始化加载图片 Texture t1; t1.loadFromFile("images/bg2.png"); 当鼠标第一次单击时,记录下位置,第二次单击又记录一下位置,如果两个小方块相邻就交换位置,如果不相邻如图c的位置则,不发生变化 判断行或列如果三张一样的图片相邻,清除一下图片,进行刷新 实列 #i

  • python用10行代码实现对黄色图片的检测功能

    本文实例讲述了python用10行代码实现对黄色图片的检测功能.分享给大家供大家参考.具体如下: 原理:将图片转换为YCbCr模式,在图片中寻找图片色值像素,如果在皮肤色值内的像素面积超过整个画面的1/3,就认为是黄色图片. 申明:简单场景还是够用了,稍微复杂一点就不准确了,例如:整幅画面是人的头像,皮肤色值的像素必然超过50%,被误认为黄色图片就太武断了. 需要安装python图片库PIL支持 porn_detect.py如下: import sys,PIL.Image as Image im

  • 超简单的几行代码搞定Android底部导航栏功能

    超简单,几行代码搞定Android底部导航栏-–应项目需求以及小伙伴的留言,新加了两个方法: 设置底部导航栏背景图片 添加底部导航栏选项卡切换监听事件 底部导航栏的实现也不难,就是下边是几个Tab切换,上边一般是一个FrameLayout,然后FrameLayout中切换fragment. 网上有不少关于Android底部导航栏的文章,不过好像都只是关于下边Tab切的,没有实现Tab与fragment的联动,用的时候还要自己手写这部分代码,对我这个比较懒(据说,懒是程序员的一种美德_#)得程序员

  • 3行代码快速实现Spring Boot Oauth2服务功能

    这里的3行代码并不是指真的只需要写3行代码,而是基于我已经写好的一个Spring Boot Oauth2服务.仅仅需要修改3行数据库配置信息,即可得到一个Spring Boot Oauth2服务. 项目地址https://github.com/jeesun/oauthserver oauthserver 简介 oauthserver是一个基于Spring Boot Oauth2的完整的独立的Oauth服务器.仅仅需要创建相关数据表,修改数据库的连接信息,你就可以得到一个Oauth服务器. 支持的

  • 神级程序员JavaScript300行代码搞定汉字转拼音

    一.汉字转拼音的现状 首先应该说,汉字转拼音是个强需求,比如联系人按拼音字母排序/筛选:比如目的地(典型如机票购买) 按拼音首字母分类等等.但是这个需求的解决方案,但好像没听过什么巧妙的实现(特别是浏览器端),大概都需要一个庞大的字典. 具体到JavaScript,查查github和npm,比较优秀的处理汉字转拼音的库有pinyin 和pinyinjs,可以看到,两者都自带了庞大的字典. 这些字典动辄几十上百KB(有的甚至几MB),想在浏览器端使用还是需要一些勇气的.所以当我们碰到汉字转拼音的需

  • 输入自动提示搜索提示功能的使用说明:sugggestion.txt

    readme: 本文件记录了suggestion.js文件的功能使用说明: 复制代码 代码如下: /* * 功能:该js文件中的代码实现了[输入自动搜索提示]功能,如百度.google搜索框中输入一些字符会以下拉列表形式给出一些提示,提高了用户体验: * 使用技术:JQuery+Ajax * * 一.如何使用该功能? * 1.使用该功能是需引入以下文件: * 1)<link type="text/css" rel="stylesheet" href="

  • 输入自动提示搜索提示功能的javascript:sugggestion.js

    复制代码 代码如下: /** * 功能:该js文件中的代码实现了[输入自动搜索提示]功能,如百度.google搜索框中输入一些字符会以下拉列表形式给出一些提示,提高了用户体验 * 使用说明:参见suggestions.txt文件 * Author:sunfei(孙飞) Date:2013.08.21 */ var SugObj = new Object(); $(document).ready(function(){ //文件加载完成后获取输入框属性信息,确保搜索提示数据和文本输入框中数据的显示

随机推荐