2019-01-11 15:56:24 weixin_44328479 阅读数 6232
  • C++语音识别开篇

    本篇mark老师将教大家使用第三方库的调用来简单的实现语音识别。随着机器学习和人工智能的热闹,国内语音行业也可谓是百花齐放。 语音识别一个伟大的时代已在我们身边悄悄走来。

    5915 人正在学习 去看看 杨波

简介

本文记录百度离线识别与讯飞离线语音识别,针对的是应用本身级别的。

百度语音离线识别集成

  1. 首先下载开发架包:bdasr_V3_20180801_d6f298a.jar,这个是离在线融合的SDK ;

  2. 导入so库:可在下载的demo中找到,复制到自己工程中同名的路径下
    在这里插入图片描述

  3. 需要百度开放平台上去申请 API Key 和 Secret Key,这是认证权限的必备信息,只有认证通过才可以继续使用语音功能,如语音唤醒,在线识别,离线识别等等。首先需要百度开放平台上去申请 API Key 和 Secret Key,这是认证权限的必备信息,只有认证通过才可以继续使用语音功能,如语音唤醒,在线识别,离线识别等等。
    在这里插入图片描述
    而鉴权认证就是要成功获取到AccessToken(Access Token 是用户身份验证和授权的凭证),而这个认证过程需要自己在应用中编写,之后会提到。
    将获取到的AppID、API Key 和 Secret Key添加到项目的配置文件中去;

  4. AndroidManifest.xml 文件的配置:
    设置权限(如果是Android 6.0 以上还需要获取动态权限,之后会说到)

    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

设置APP_ID, APP_KEY, APP_SECRET

    <meta-data android:name="com.baidu.speech.APP_ID"
        android:value="9788136" />
    <meta-data
        android:name="com.baidu.speech.API_KEY"
        android:value="0GjQNO5H4pGPf9HyA3AmZEbz" />
    <meta-data
        android:name="com.baidu.speech.SECRET_KEY"
        android:value="db981ef3ec647ba8a09b599ad7447a24" />
  1. 注意事项:在申请API Key时需要设置应用包名,并且保证官网申请时的与app/bulid.gradle里的applicationId一致,否则会导致离线命令词或是唤醒报“no licenece”的错误。

离线识别实现

1.可以先准备本地词库和唤醒词库

2.基于SDK的使用:

  • 初始化EventManager类
 EventManager  asr = EventManagerFactory.create(context, "asr");//语音识别器
 EventManager  wp = EventManagerFactory.create(context, "wp");//语音唤醒器
  • 定义输出事件类
public class RecogResultManager implements EventListener{
    ...........
    
     /**
     * 回调事件
     */
    @Override
    public void onEvent(String name, String params, byte[] data, int offset, int length) {
        String logMessage = "name:" + name + "; params:" + params;
        Log.d(TAG, logMessage);
        switch (name) {
            case SpeechConstant.CALLBACK_EVENT_ASR_READY:
                // 引擎准备就绪,可以开始说话              
                break;
            case SpeechConstant.CALLBACK_EVENT_ASR_BEGIN:
                // 检测到用户的已经开始说话
                break;
            case SpeechConstant.CALLBACK_EVENT_ASR_END:
                // 检测到用户的已经停止说话
                break;
            case SpeechConstant.CALLBACK_EVENT_ASR_PARTIAL:
                // 临时识别结果, 长语音模式需要从此消息中取出结果
                break;
              case SpeechConstant.CALLBACK_EVENT_ASR_FINISH:
                // 识别结束, 最终识别结果或可能的错误
                break;
            case SpeechConstant.CALLBACK_EVENT_ASR_LONG_SPEECH:
                // 长语音
                break;
            case SpeechConstant.CALLBACK_EVENT_ASR_VOLUME:
                //音量值 
                break;
            case SpeechConstant.CALLBACK_EVENT_ASR_AUDIO:
                if (data.length != length) {
                 //可能出错的地方:回调返回的数据有问题
                   Log.d(TAG, "internal error: asr.audio" +
                            " callback data length is not equal to length param");
                 .....
                }
                break;
            case SpeechConstant.CALLBACK_EVENT_WAKEUP_SUCCESS:
                //语音唤醒成功 
                break;
            case SpeechConstant.CALLBACK_EVENT_WAKEUP_ERROR:
                //语音唤醒失败
                break;
            .......

            default:
                break;
        }
    }
}    


  • 注册自己的输出事件类
asr.registerListener(eventListener);
wp.registerListener(eventListener);
  • 加载唤醒词库与离线词库
 /**
 - 加载唤醒词库
  */
    private void loadWakeup() {
        Map<String, Object> params = new HashMap<>();
        params.put(SpeechConstant.WP_WORDS_FILE, "assets://WakeUp.bin");
        //开始识别语音唤醒
        mWakeup.start(params);
    }
    
 /**
 - 加载离线词库
  */
    private void loadWakeup() {
       Map<String, Object> params = new LinkedHashMap<>();
       //设置此参数使用与语音唤醒后进入语音识别模式
        params.put(SpeechConstant.VAD, SpeechConstant.VAD_DNN);
        params.put(SpeechConstant.DECODER, 2);
        params.put(SpeechConstant.ASR_OFFLINE_ENGINE_GRAMMER_FILE_PATH, "assets://baidu_speech_grammar.bsg");
       //设置唤醒到识别的停顿时间
        if (backTrackInMs > 0) {
            params.put(SpeechConstant.AUDIO_MILLS, System.currentTimeMillis() - backTrackInMs);
        }
        mRecognizer.cancel();
        //开始进行识别
        mRecognizer.start(params);
    }
     注:对于语音唤醒到识别有两种方案:
     方案1: backTrackInMs > 0,唤醒词说完后,直接接句子,中间没有停顿,
            开启回溯,连同唤醒词一起整句识别,推荐4个字 1500ms,backTrackInMs 最大 15000,即15s.
     方案2: backTrackInMs = 0,唤醒词说完后,中间有停顿,不开启回溯。唤醒词识别回调后,正常开启识别。
            官方demo里采用默认设置的是1500,本人demo中选择的是方案2,因为测试结果方案2的识别效果好一些,详见下面测试结果。
  • 查询权限,获取AccessToken
public void check() {
        appendLogMessage("try to check appId " + appId + " ,appKey=" + appKey + " ,secretKey" + secretKey);
        if (appId == null || appId.isEmpty()) {
              errorMessage = "appId 为空";
              fixMessage = "填写appID";
          }
        if (appKey == null || appKey.isEmpty()) {
                errorMessage = "appKey 为空";
                fixMessage = "填写appID";
          }
        if (secretKey == null || secretKey.isEmpty()) {
                 errorMessage = "secretKey 为空";
                  fixMessage = "secretKey";
         }
        try {
                checkOnline();
        } catch (UnknownHostException e) {
                infoMessage = "无网络或者网络不连通,忽略检测 : " + e.getMessage();
        } catch (Exception e) {
                errorMessage = e.getClass().getCanonicalName() + ":" + e.getMessage();
                fixMessage = " 重新检测appId, appKey, appSecret是否正确";
         }
 }

/**
 *  获取并校验token
 */
  public void checkOnline() throws Exception {
            String urlpath = "http://openapi.baidu.com/oauth/2.0/token?grant_type=client_credentials&client_id="
                    + appKey + "&client_secret=" + secretKey;
            URL url = new URL(urlpath);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(1000);
            InputStream is = conn.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(is));
            StringBuilder result = new StringBuilder();
            String line = "";
            do {
                line = reader.readLine();
                if (line != null) {
                    result.append(line);
                }
            } while (line != null);
           String res = result.toString();
           if (!res.contains("audio_voice_assistant_get")) {
                errorMessage = "appid:" + appId + ",没有audio_voice_assistant_get 权限,请在网页上开通\"语音识别\"能力";
                fixMessage = "secretKey";
                return;
            }
            appendLogMessage("openapi return " + res);
            JSONObject jsonObject = new JSONObject(res);
            String error = jsonObject.optString("error");
            if (error != null && !error.isEmpty()) {
                errorMessage = "appkey secretKey 错误" + ", error:" + error + ", json is" + result;
                fixMessage = " 重新检测appId对应的 appKey, appSecret是否正确";
                return;
            }
           String token = jsonObject.getString("access_token");
           if (token == null || !token.endsWith("-" + appId)) {
                errorMessage = "appId 与 appkey及 appSecret 不一致。
                appId = " + appId + " ,token = " + token;
                fixMessage = " 重新检测appId对应的 appKey, appSecret是否正确";
            }
        }
    }
  • 加载动态权限(Android 6.0以上需要)
    在主Activity中添加:

 private void initPermission() {
        String[] permissions = {Manifest.permission.RECORD_AUDIO,
                Manifest.permission.ACCESS_NETWORK_STATE,
                Manifest.permission.INTERNET,
                Manifest.permission.READ_PHONE_STATE,
                Manifest.permission.WRITE_EXTERNAL_STORAGE
        };
        ArrayList<String> toApplyList = new ArrayList<>();
       for (String perm : permissions) {
            if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(this, perm)) {
                toApplyList.add(perm);
                // 进入到这里代表没有权限.
            }
        }
        String[] tmpList = new String[toApplyList.size()];
        if (!toApplyList.isEmpty()) {
            ActivityCompat.requestPermissions(this, toApplyList.toArray(tmpList), 123);
        }
    }
    
@Override
   public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        // 此处为android 6.0以上动态授权的回调,用户自行实现。
    }
  • 控制识别/唤醒
   /**
    * 语音唤醒开始
    */
    public void start(Map<String, Object> params) {
        String json = new JSONObject(params).toString();
        wp.send(SpeechConstant.WAKEUP_START, json, null, 0, 0);
    }
    
    /**
     * 停止语音唤醒
     */
    public void stop() {
        wp.send(SpeechConstant.WAKEUP_STOP, null, null, 0, 0);
    }

    /**
     * 开始识别
     */
    public void start(Map<String, Object> params) {
        String json = new JSONObject(params).toString();
        asr.send(SpeechConstant.ASR_START, json, null, 0, 0);
    }
    
    /**
     * 提前结束录音等待识别结果。
     */
    public void stop() {
        if (!isInited) {
            throw new RuntimeException("release() was called");
        }
        asr.send(SpeechConstant.ASR_CANCEL, "{}", null, 0, 0);
    }
    
    /**
     * 取消本次识别,取消后将立即停止不会返回识别结果。
     * cancel 与stop的区别是 cancel在stop的基础上,完全停止整个识别流程,
     */
    public void cancel() {
        if (!isInited) {
            throw new RuntimeException("release() was called");
        }
        asr.send(SpeechConstant.ASR_CANCEL, "{}", null, 0, 0);
    }
  • 事件管理器退出,回收资源,不能一直占用着mic资源。
    /**
     * 释放语音唤醒资源
     */
    public void release() {
       if (wp== null) {
            return;
        }
        stop();
        wp.unregisterListener(eventListener);
        wp = null;
        isInited = false;
    }
   
    /**
     * 释放语音识别资源
     */
      public void release() {
        if (asr == null) {
            return;
        }
        cancel();
        asr.send(SpeechConstant.ASR_KWS_UNLOAD_ENGINE, null, null, 0, 0);
        asr.unregisterListener(eventListener);
        asr = null;
        isInited = false;
    }

添加动作识别:

之前考虑是用数据库匹配识别结果,来找到相应的组件,但是demo里做得着急,只是通过管理activity,以activity和name为媒介来找到相应的组件,从而执行相应的动作。

public abstract class BaseActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        VrApplication.getInstance().addActivity(this);
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        VrApplication.getInstance().removeActivity(this);
    }
}
public class VrApplication extends Application {

    private static final VrApplication INSTANCE = new VrApplication();

    private  List<BaseActivity> mActivities;

    private VrApplication() {}

    public static VrApplication getInstance() {
        return VrApplication.INSTANCE;
    }

    public  void addActivity(BaseActivity activity){
        if(mActivities == null){
            mActivities = new ArrayList<>();
        }
        mActivities.add(activity);
    }
    
      public  void removeActivity(BaseActivity activity){
        mActivities.remove(activity);
    }

    public List<BaseActivity> getActivities() {
        return mActivities;
    }

    /**
     * 关闭应用后需要清空管理
     */
     public  void finishAll(){
        for(BaseActivity activity : mActivities){
            if(! activity.isFinishing()){
                activity.finish();
            }
        }
    }
}

所有的界面activity需要继承BaseActivity,初始化组件时保存起来,界面销毁时clear()。

 private void initView() {
        mViews = new HashMap<>();
        EditText etCode = (EditText) findViewById(R.id.tv_point_code);
        Button action = (Button) findViewById(R.id.btn_action);
        mViews.put(etCode.getHint().toString(), etCode);
        mViews.put(action.getText().toString(), action);
    }

回调监听处理结果,进行确认组件去执行相应的动作:

public class RecogResultManager implements EventListener, IStatus {
     ...............
     
    /**
     * 回调事件
     */
    @Override
    public void onEvent(String name, String params, byte[] data, int offset, int length) {
           switch (name) {
                ........
                case SpeechConstant.CALLBACK_EVENT_ASR_PARTIAL:
                     // 临时识别结果, 长语音模式需要从此消息中取出结果
                     handlePartial(params, data, offset, length);
                     break;
                case SpeechConstant.CALLBACK_EVENT_ASR_FINISH:
                     // 识别结束, 最终识别结果或可能的错误
                     handleFinish(params);
                     break;
                ........
                case SpeechConstant.CALLBACK_EVENT_WAKEUP_SUCCESS:
                     //语音唤醒成功
                     handleWpSuccess(name, params);
                     break;
                case SpeechConstant.CALLBACK_EVENT_WAKEUP_ERROR:
                     handleWpErro(name, params);
                     break;
          }
    } 
    ........
}               

处理语音唤醒,语音唤醒后,要切换开始进行语音识别:

private void handleWpMsg() {
        Map<String, Object> mParams = new LinkedHashMap<>();
        mParams.put(SpeechConstant.VAD, SpeechConstant.VAD_DNN);
        mParams.put(SpeechConstant.DECODER, 2);
        mParams.put(SpeechConstant.ASR_OFFLINE_ENGINE_GRAMMER_FILE_PATH, BSGPATH);
         if (mBackTrackInMs > 0) {
            mParams.put(SpeechConstant.AUDIO_MILLS, System.currentTimeMillis() - mBackTrackInMs);
        }
        mRecognizer.cancel();
        mRecognizer.start(mParams);
 }

处理语音识别结果:

private void analysData(RecogResult recogResult) {
        String results = recogResult.getBestResult();
        //从缓存中获取当前的Activity
        BaseActivity activityInstance = UiUtil.getActivityInstance(mContext);
        if (activityInstance == null) {
            return;
        }
        Map<String, View> views = activityInstance.getViews();
        for (Map.Entry<String, View> entry : views.entrySet()) {
            if (results.contains(entry.getKey())) {
                action(entry.getValue(), results);
            }
        }
  }

执行动作:

 private void action(View value, String results) {
        if (value instanceof Button) {
            value.performClick();
        } else if (value instanceof EditText) {
            ((EditText) value).setText(results);
        }
 }

注意事项

1.即使只进行离线操作,第一次也需要进行联网进行认证授权才可以进行接下来的语音识别。
2. 不用时要及时释放,不能一直占用mic资源,影响其他使用。
3. 环境嘈杂时,识别率会很低,需要依赖降噪算法。
4.本demo针对一个应用来说,通过语音识别命令,并执行操作,跳转界面,点击操作,选择操作等,有一定的局限性,因为离线时,只能识别词库中的词,所以EditText或是TextView填写值的时候,可能没有用户所说的那个值,目前只是依赖在线识别来控制这块,还有待进一步研究。

demo下载地址:

链接: https://download.csdn.net/download/weixin_44328479/10910474.
Alt

2013-02-04 19:59:50 LoveAnnuoa 阅读数 11989
  • C++语音识别开篇

    本篇mark老师将教大家使用第三方库的调用来简单的实现语音识别。随着机器学习和人工智能的热闹,国内语音行业也可谓是百花齐放。 语音识别一个伟大的时代已在我们身边悄悄走来。

    5915 人正在学习 去看看 杨波

前言:因为在学校要做语音相关的开发,并且应用环境多数是在无网络状态下,故要用到离线语音识别。经过一番查阅,发现目前在做离线语音识别的,尤其是android环境下的离线语音识别的人确实很少。(插一句如果想要实现离线语音合成的可以看一下中科院慧声语音工作室提供的开放平台)在查阅资料期间联系过许多android方面经验丰富的前辈、朋友,但都没有接触过这个。因为是0基础,所以基本碰到一个东西就是新的,需要一点一点的学,不过即使现在能够成功运行demo,但也只是大体上的了解,主要是应用,用到什么学什么,并不深入。

下面,我将根据自己搭建环境和运行中遇到的问题,说一下自己的问题和解决方法,最后给出相关下载链接,给大家节省些时间,希望能帮助到遇到类似问题的朋友,也希望能互相学习交流。

由于许多朋友需要源代码,我便将我用过的源代码及声学模型和词典放到了csdn资源中供大家下载地址如下所示:http://download.csdn.net/detail/loveannuoa/5386461  这个工程和下面说的第二个链接代码是一样的,只是声学模型和词典不同,这个工程的数据是我自己弄的。

另外,本篇文章主要说的再linux下搭建环境,windows下的环境我也搭建完成,如果有不明白的可以先看一下我的另一篇关于windows下搭建环境的文章:http://blog.csdn.net/loveannuoa/article/details/8689478

 

重要提示:有一点之前没有说明,在整个环境配置中涉及了两个demo,一个是官方提供的即http://sourceforge.net/projects/cmusphinx/files/?source=navbar

另一个则是http://zuoshu.iteye.com/blog/1463867其中的。后者是“zuoshu”自己修改后的。根据测试,能够达到中文高识别率的是后者,即使唤作简单的中文数据词典文件,用前者的程序也没法识别。

搭建环境篇

注意:我在网上查到的几个搭建环境的帖子中都是针对Linux系统下的,一开始我没有注意到这个问题,是在windows下用cygwin模拟linux下做的,结果都没有成功。所以建议大家如果对cygwin中各种包、命令、错误不是很熟悉的情况下还是用linux系统吧,我是用的Ubuntu12.04和win7双系统,VM太卡受不了。下面指出的搭建环境的方法虽然能将demo运行起来,但识别率和识别速度实在是太难以让人满意,所以在中文识别篇中有提高识别率的方法。

我是通过两篇文章搭建的环境,分别是,

(1)http://blog.csdn.net/sununs11/article/details/8034294

         这个是CSDN中原版的android下利用pocketsphinx实现离线语音识别环境搭建方法,在myException网站和csdn其他博客中也有转载。通过这篇文章能够顺利的将demo跑起来。

(2)http://ucla.jamesyxu.com/?p=118

         这个应该是最早的讲解android离线语音识别的,而且确实不愧是老外的东西,讲解的很细致,同时也有问题分析。但这个教程中得第四步,即在demo的swig文件夹中运行”make”指令的这一步我始终没有执行过去,老是出错(错误会在下边指出)。作者貌似挺牛逼的,对网友们提出的问题都很热心的解答(但不知为什么我给他发过邮件,却一直没有点我,难道有反华倾向,呵呵)。

主要问题和注意事项集中在:

(1)    在安装sphinxbase和pocketsphinx包的时候会提示缺少其他的包(例如bison、libtool等),这个比较简单在cygwin下直接运行setup安装相应的包,如果在linux系统下直接用“sudo apt-get install “包名””就可以安装缺少的包。

(2)   在执行在swig目下make这一步中,首先,swig是需要单独安装,cygwin安装方法和(1)中类似,ubuntu下可以通过软件库下载安装。其次,无论在cygwin还是在ubuntu下都怎么弄都不成功,在cygwin下会提示问题(1)中提到的两个包未找到,这种情况可以参考http://cgwxyz.blog.163.com/blog/static/262806020105307929424/。另外,解决这个问题后还会出现.c文件中函数未定义的错误,我感觉这是cygwin本身的不足,可能就跟两个包没安装好有关。

在Ubuntu下,错误提示是缺少jni.h,这个包在jdk/include中,但无论我用“make  -I “jni.h的路径””将.h包含进来还直接将.h文件拷贝过来都依旧提示该错误。我没能解决了,如果大家有感兴趣的可以试一下,弄好了希望能通知一下,互相学习一下。

(3)    在demo的jni目录下执行ndk编译的时候,一定先修改Android.mk文件中SPHINX_PATH路径,修改时要将连同”$”在内的原来的路径都删掉,改成自己安装两个包的父目录,对于cygwin下要以“/cygdrive/….”开头,同样linux系统下也要先以”/”开头。

(4)    在更改工程属性中的swig和ndk时,要注意对于swig,在refresh选项中时选择的”thefloder…..”,而对于ndk则选择的是”theproject…..”,不要当做一样的,一定仔细按照步骤进行。


中文识别篇

我个人的理解:语音识别除了demo工程外还需要中/英文声学模型和语言模型,也就是环境搭建最后提到的hmm和lm文件夹,这两个文件夹在pocketsphinx中有,但是将这两个文件夹放入设备中测试时,不论中文还是英文识别率都很低而且识别速度也很慢。

因此以下几篇文章给出了解决办法:

(1)http://zuoshu.iteye.com/blog/1463867

这篇文章可以实现英文的识别,但是提到的中文识别目前我本人未实现,原因在于本文所给出的在线生成语音模型和字典的网站无法解析中文,只能生成英文字典。但是这篇文章给出了一个很好的生成语言模型和字典文件的网址

http://www.speech.cs.cmu.edu/tools/lmtool-new.html

 

(2)http://www.cnblogs.com/yin52133/archive/2012/07/12/2588201.html#2525875

这个是在demo跑起来之后,为解决识别中文的办法。特点:速度快,识别率很高,但是范围小。而且对于.dic字典文件来说需要自己设计,这就需要有一个比较规范字典文件模板作比照,可以用pocketsphinx/model/lm/zh_CN中得字典文件,同时也可以用上述链接中提到的zh_broadcastnews_utf8.dic,下载地址是:

http://sourceforge.net/projects/cmusphinx/files/Acoustic%20and%20Language%20Models/

 

以下是在环境搭建和demo运行中可能会用到的一些资料的下载链接:

(1)Sphinxbase和pocketsphinx的下载链接:

http://sourceforge.net/projects/cmusphinx/files/?source=navbar

(2)demo的下载地址:

http://sourceforge.net/projects/cmusphinx/files/?source=navbar

(3)一些sphinx和pocketsphinx基础知识学习的链接,涉及到一些识别引擎、识别原理的分析,挺专业的我没太看懂,以后还会继续学习。

http://blog.csdn.net/zouxy09/article/details/7941585

 

总结:

现在知识刚刚能够运行起demo来,能够简单的识别一些中文,对于识别引擎,识别原理我了解的太少,也是由于现在弄离线语音识别的太少,大多是在线的而且识别率和速度都很理想,导致几乎除了sphinx以外没有其他的语音识别引擎。

非常高兴大家能看到这里,这是我写的第一篇CSDN博文,以后希望能坚持写下去,希望与大家交流技术方面的知识,互相学习,大家如果在搭配环境或者运行demo上有什么问题,可以给我发私信,我会尽力帮忙的,互相学习共同进步。

2016-11-23 13:46:32 u014418171 阅读数 1837
  • C++语音识别开篇

    本篇mark老师将教大家使用第三方库的调用来简单的实现语音识别。随着机器学习和人工智能的热闹,国内语音行业也可谓是百花齐放。 语音识别一个伟大的时代已在我们身边悄悄走来。

    5915 人正在学习 去看看 杨波

本文将使用PocketSphinx来实现Android平台的离线语音识别

优点: 离线,不用联网 识别较准(大家都说99%  我觉得只有80%)
缺点: 自定义语音命令麻烦  只能小范围识别 

网上看了很多例子 我跟你说 那都是坑 根本就是错误的教程…
不废话 开始教程:

1.先确定要识别的词组

我的需求是实现离线识别 以下词组

中文:  向前 向后 向左 向右
英文: FORWARD BACKWARD TURNLEFT TURNRIGHT

(很多这样的教程 好像基本没教怎么识别英文来着…)

2.生成并修改lm和dic文件

先生成中文的 新建一个txt文件 命名为 text.txt
并把 向前 向后 向左 向右这四个词写上去,然后保存..
如图

注意很多文章都说这样的格式 < s>向前< /s>
这样的格式 我只想跟你说 你被坑了… 这样做出来的语音识别demo是没有任何识别反应的 真害人..

打开 http://www.speech.cs.cmu.edu/tools/lmtool-new.html 在线转换lm文件 , 点击选择文件选择刚才的text.txt文件

然后点击 COMPILE KNOWLEDGE BASE 提交
然后分别打开这两个文件 查看内容 并分别复制内容到新建的text.dic和text.lm文件里, 保存 (如果乱码 请使用谷歌浏览器 或 修改浏览器显示编码)

例如 我的 text.dic文件文本内容为:(我本来是写向前 向后 向左 向右 现在你看 顺序乱了 我不知道这样的目的 我建议保留转换后的顺序 可能与优先级有关吧)

向前
向右
向后
向左

而text.lm的内容为

Language model created by QuickLM on Tue Nov 22 03:42:55 EST 2016
Copyright (c) 1996-2010 Carnegie Mellon University and Alexander I. Rudnicky
The model is in standard ARPA format, designed by Doug Paul while he was at MITRE.
The code that was used to produce this language model is available in Open Source.
Please visit http://www.speech.cs.cmu.edu/tools/ for more information
The (fixed) discount mass is 0.5. The backoffs are computed using the ratio method.
This model based on a corpus of 4 sentences and 6 words
\data\
ngram 1=6
ngram 2=8
ngram 3=4

\1-grams:

-0.7782 -0.3010

-0.7782 -0.2218

-1.3802 向前 -0.2218

-1.3802 向右 -0.2218

-1.3802 向后 -0.2218

-1.3802 向左 -0.2218

\2-grams:

-0.9031 向前 0.0000

-0.9031 向右 0.0000

-0.9031 向后 0.0000

-0.9031 向左 0.0000

-0.3010 向前 -0.3010

-0.3010 向右 -0.3010

-0.3010 向后 -0.3010

-0.3010 向左 -0.3010

\3-grams:

-0.3010 向前

-0.3010 向右

-0.3010 向后

-0.3010 向左

\end\

还有的说有什么UTF-8编码问题 我只想说 我没遇到 我用的Sublime Text3文本编辑器 你如果有问题 你换我这个文本编辑器试试
完成上面的然后 脑残的一步来了 手动找字典(拼音)
哪里来的拼音字典? 先去这里下载一个压缩文件

https://sourceforge.net/projects/cmusphinx/files/pocketsphinx/0.7/ 

然后找到 pocketsphinx-0.7.tar.gz 点击下载(下不动用迅雷)

下载好后解压

pocketsphinx-0.7\pocketsphinx-0.7\model\hmm\zh\tdt_sc_8k //为中文语言模型文件
pocketsphinx-0.7\model\lm\zh_CN\xxxx.dic//为拼音读音字典
//同理
pocketsphinx-0.7\model\hmm\en_US\hub4wsj_sc_8k //为英文语言模型文件
pocketsphinx-0.7\model\lm\en_US\xxxx.dic //为英文读音字典

你还会发现有台湾的等等

我目录的中文语言文件字典为mandarin_notone.dic 打开 你会发现里面有很多文字对应读音(拼音)

这时 我们回到刚才的text.dic和text.lm文件 打开text.dic文件
一个词一个词 在mandarin_notone.dic字典中快捷键查找 (词找不到就单个字找) 然后复制拼音到相应的词语右边 注意拼音和词语要至少留一个空格 然后拼音和拼音之间也要留一个空格
编辑text.dic文件,我的找好了 如下:

向前  x iang q ian
向右  x iang y ou
向后  x iang h ou
向左  x iang z uo

好了 保存…

英文的也一样步骤 字典的话,要从英文字典上查 英文最好都大写 并且多个单词时最好不留空格

3.Demo修改

代码太多 略.. 但我文章下面直接放demo
网上其他教程很多都是打开demo就闪退 其实原因是
你还要手动复制模型和字典文件到sd卡上

我写的这个demo也是基于它, 但我解决了这个问题 我把文件放在 assets里 并且我把模型文件和字典都整合放到一起了 ,如下

这样 就能自动复制文件到临时路径 并让你们快速实现离线语音功能 程序员不需要担心其他操作.
实际情况下那demo遗留了一个 严重的bug 就是过100s左右会c库异常闪退 这个问题我也解决了
还有个 文字不断重复 和回调一直回调同一个识别答案这个问题也解决了…

我做了判断 , 文件都放在data/data/com.packagename.xxx/file/
并且根据手机语言切换识别中文还是英文…

public RecognizerTask(Context context) {

String dataPath = context.getFilesDir().getAbsolutePath();

File zhPath =newFile(dataPath +"/voice/zh");

if(!zhPath.exists()) {

zhPath.mkdirs();

}

File enPath =newFile(dataPath +"/voice/en");

if(!enPath.exists()) {

enPath.mkdirs();

}

pocketsphinx

.setLogfile(dataPath +"/voice/pocketsphinx.log");

String rootPath = isZh(context) ? zhPath.getPath() : enPath.getPath();//根据环境选择中英文识别

String dicPath = rootPath +"/text.dic";

String imPath = rootPath +"/text.lm";

if(!newFile(dicPath).exists()) {

releaseAssets(context,"/", dataPath);

}

Config c =newConfig();

c.setString("-hmm", rootPath);

c.setString("-dict", dicPath);

c.setString("-lm", imPath);

c.setFloat("-samprate",8000.0);

c.setInt("-maxhmmpf",2000);

c.setInt("-maxwpf",10);

c.setInt("-pl_window",2);

c.setBoolean("-backtrace",true);

c.setBoolean("-bestpath",false);

this.ps =newDecoder(c);

this.audio =null;

this.audioq =newLinkedBlockingQueue();

this.use_partials =false;

this.mailbox = Event.NONE;

}

代码很简单的 放Demo吧
—安卓离线语音识别 PocketSphinx Demo.zip—
http://download.csdn.net/detail/u014418171/9690129

安卓开发者交流群欢迎您加入
418263790

2018-08-27 22:30:29 hfut_why 阅读数 7767
  • C++语音识别开篇

    本篇mark老师将教大家使用第三方库的调用来简单的实现语音识别。随着机器学习和人工智能的热闹,国内语音行业也可谓是百花齐放。 语音识别一个伟大的时代已在我们身边悄悄走来。

    5915 人正在学习 去看看 杨波

      最近因为项目的需求,需要在无网络的情况下实现语音识别的功能,因为之前在线识别一直用的科大的,所以经理就和我说,你花半天时间简单熟悉一下,然后出一个Demo,下午有人过来看;因为之前科大在线SR也是别人做的,准确的说我只是了解过一点,也写过相关的blog——百度语音识别结合云知声离线TTSDemo(AS)Android原生TTS的基本使用以及配合中文语音包实现中文TTS等,但是就半天不到的时间写一个Demo还是很赶的,比较不熟悉。下面就来简单的总结一下这半天的经历。   

源码下载地址

 

第一阶段    基础准备

第一步:找到科大讯飞开发平台官网,注册账户

平台地址

第二步:点击右上角“控制台”进入个人控制台

第三步:创建应用,根据选择的服务生成SDK并下载

 

      这里我们添加离线命令词识别服务,获取了对应SDK之后,也就完成的最基本的准备工作了,生成的APPID很重要哟,这个不用说你也应该知道。我们的第一阶段就算完成了

 

第二阶段    Demo导入

第四步:打开AS,创建一个和上图同名的应用

第五步:导入SDK解压文件夹下的sample目录里面的的mscV5PlusDemomodule

 

这里面需要实现在AS项目中导入module操作,如下图所示:

 

选择上面sample下面对应的mscV5PlusDemo即可,如果有需要调整sdk版本的就按照错误提示调整就好了,比较简单;至此,我们就把SDK中的Demo(mscV5PlusDemo)导入到了我们的项目中:

 

第六步:这个时候选择导入的module,在arm机上运行,发现并不能正常运行,那么你需要考虑以下几个问题

(1)Demo中的离线命令词识别的commen.jet文件位置错误

在解压文件夹的res目录下找到asr文件夹,将其copy到Demo里面的assets目录下:

 

(2)一定要在arm机上测试,因为这个Demo里面只有armeabi的so文件

(3)如果可以运行,进入如下界面,发现里面不仅仅只有我们需要的离线命令词识别,还有在线识别等等:

 

我们点击“立刻体验语法识别”,关闭设备网络,选择下图中的“本地”,然后点击“构建语法”,再点击“开始识别”;

这个时候很有可能再报错误,查看错误码发现原来是没有录音权限等权限问题,这个时候你就纳闷了,明明Demo代码中已经添加了权限:

   <uses-permission android:name="android.permission.RECORD_AUDIO" />
   <uses-permission android:name="android.permission.INTERNET" />
   <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
   <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
   <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
   <uses-permission android:name="android.permission.READ_PHONE_STATE" />
   <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
   <uses-permission android:name="android.permission.READ_CONTACTS" />
   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
   <uses-permission android:name="android.permission.WRITE_SETTINGS" />
   <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
   <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
   <uses-permission android:name="android.permission.BLUETOOTH" />
   <uses-permission android:name="android.permission.BROADCAST_STICKY" />

为什么还有问题,这个时候你再进入到Demo代码里面查看,里面并没有做6.0以及以上版本的动态权限申请处理,所以怎么办了,要么我们自己加上,要么换一个低一点的机子测试一下。

// 开始识别,没有权限判断
case R.id.isr_recognize:
   ((EditText)findViewById(R.id.isr_text)).setText(null);// 清空显示内容
   // 设置参数
   if (!setParam()) {
      showTip("请先构建语法。");
      return;
   };
   
   ret = mAsr.startListening(mRecognizerListener);
   if (ret != ErrorCode.SUCCESS) {
      showTip("识别失败,错误码: " + ret);   
   }
   break;

这里我们就不深究了,因为后面还有好多内容了,假设这个时候你能够正常运行了,也能在Demo中完成离线命令词识别了。那么下一阶段就是瘦身处理了。

 

第三阶段    功能瘦身

第七步:提取离线命令词识别功能

      不得不说,这个Demo对于我们只使用离线命令词识别来说有一点冗余,太多了;下面我们就来把离线命令词功能抽取出来,如下图:

 

实现离线命令词识别的功能实现主要是上图中红色框中AsrDemo中的逻辑,其源码如下:

package com.iflytek.mscv5plusdemo;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Bundle;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.EditText;
import android.widget.RadioGroup;
import android.widget.RadioGroup.OnCheckedChangeListener;
import android.widget.Toast;

import com.iflytek.cloud.ErrorCode;
import com.iflytek.cloud.GrammarListener;
import com.iflytek.cloud.InitListener;
import com.iflytek.cloud.LexiconListener;
import com.iflytek.cloud.RecognizerListener;
import com.iflytek.cloud.RecognizerResult;
import com.iflytek.cloud.SpeechConstant;
import com.iflytek.cloud.SpeechError;
import com.iflytek.cloud.SpeechRecognizer;
import com.iflytek.cloud.util.ContactManager;
import com.iflytek.cloud.util.ContactManager.ContactListener;
import com.iflytek.cloud.util.ResourceUtil;
import com.iflytek.cloud.util.ResourceUtil.RESOURCE_TYPE;
import com.iflytek.speech.util.FucUtil;
import com.iflytek.speech.util.JsonParser;
import com.iflytek.speech.util.XmlParser;

public class AsrDemo extends Activity implements OnClickListener{
   private static String TAG = AsrDemo.class.getSimpleName();
   // 语音识别对象
   private SpeechRecognizer mAsr;
   private Toast mToast;  
   // 缓存
   private SharedPreferences mSharedPreferences;
   // 本地语法文件
   private String mLocalGrammar = null;
   // 本地词典
   private String mLocalLexicon = null;
   // 云端语法文件
   private String mCloudGrammar = null;
   // 本地语法构建路径    
   private String grmPath = Environment.getExternalStorageDirectory()
                        .getAbsolutePath() + "/msc/test";
   // 返回结果格式,支持:xml,json
   private String mResultType = "json";
   
   private  final String KEY_GRAMMAR_ABNF_ID = "grammar_abnf_id";
   private  final String GRAMMAR_TYPE_ABNF = "abnf";
   private  final String GRAMMAR_TYPE_BNF = "bnf";

   private String mEngineType = "cloud";
   @SuppressLint("ShowToast")
   public void onCreate(Bundle savedInstanceState)
   {
      super.onCreate(savedInstanceState);
      this.requestWindowFeature(Window.FEATURE_NO_TITLE);
      setContentView(R.layout.isrdemo);
      initLayout();
      
      // 初始化识别对象
      mAsr = SpeechRecognizer.createRecognizer(this, mInitListener);    

      // 初始化语法、命令词
      mLocalLexicon = "张海羊\n刘婧\n王锋\n";
      mLocalGrammar = FucUtil.readFile(this,"call.bnf", "utf-8");
      mCloudGrammar = FucUtil.readFile(this,"grammar_sample.abnf","utf-8");
      
      // 获取联系人,本地更新词典时使用
      ContactManager mgr = ContactManager.createManager(AsrDemo.this, mContactListener); 
      mgr.asyncQueryAllContactsName();
      mSharedPreferences = getSharedPreferences(getPackageName(),    MODE_PRIVATE);
      mToast = Toast.makeText(this,"",Toast.LENGTH_SHORT);   
      
   }
   
   /**
    * 初始化Layout。
    */
   private void initLayout(){
      findViewById(R.id.isr_recognize).setOnClickListener(this);
      
      findViewById(R.id.isr_grammar).setOnClickListener(this);
      findViewById(R.id.isr_lexcion).setOnClickListener(this);
      
      findViewById(R.id.isr_stop).setOnClickListener(this);
      findViewById(R.id.isr_cancel).setOnClickListener(this);

      //选择云端or本地
      RadioGroup group = (RadioGroup)this.findViewById(R.id.radioGroup);
      group.setOnCheckedChangeListener(new OnCheckedChangeListener() {
         @Override
         public void onCheckedChanged(RadioGroup group, int checkedId) {
            if(checkedId == R.id.radioCloud)
            {
               ((EditText)findViewById(R.id.isr_text)).setText(mCloudGrammar);
               findViewById(R.id.isr_lexcion).setEnabled(false);
               mEngineType = SpeechConstant.TYPE_CLOUD;
            }else if(checkedId == R.id.radioLocal)
            {
               ((EditText)findViewById(R.id.isr_text)).setText(mLocalGrammar);
               findViewById(R.id.isr_lexcion).setEnabled(true);
               mEngineType =  SpeechConstant.TYPE_LOCAL;
            }
         }
      });
   }
    
   
   String mContent;// 语法、词典临时变量
    int ret = 0;// 函数调用返回值
   @Override
   public void onClick(View view) {      
      if( null == mAsr ){
         // 创建单例失败,与 21001 错误为同样原因,参考 http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=9688
         this.showTip( "创建对象失败,请确认 libmsc.so 放置正确,\n 且有调用 createUtility 进行初始化" );
         return;
      }
      
      if(null == mEngineType) {
         showTip("请先选择识别引擎类型");
         return;
      }  
      switch(view.getId())
      {
         case R.id.isr_grammar:
            showTip("上传预设关键词/语法文件");
            // 本地-构建语法文件,生成语法id
            if (mEngineType.equals(SpeechConstant.TYPE_LOCAL)) {
               ((EditText)findViewById(R.id.isr_text)).setText(mLocalGrammar);
               mContent = new String(mLocalGrammar);
               mAsr.setParameter(SpeechConstant.PARAMS, null);
               // 设置文本编码格式
               mAsr.setParameter(SpeechConstant.TEXT_ENCODING,"utf-8");
               // 设置引擎类型
               mAsr.setParameter(SpeechConstant.ENGINE_TYPE, mEngineType);
               // 设置语法构建路径
               mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, grmPath);
               //使用8k音频的时候请解开注释
//             mAsr.setParameter(SpeechConstant.SAMPLE_RATE, "8000");
               // 设置资源路径
               mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
               ret = mAsr.buildGrammar(GRAMMAR_TYPE_BNF, mContent, grammarListener);
               if(ret != ErrorCode.SUCCESS){
                  showTip("语法构建失败,错误码:" + ret);
               }
            }
            // 在线-构建语法文件,生成语法id
            else { 
               ((EditText)findViewById(R.id.isr_text)).setText(mCloudGrammar);
               mContent = new String(mCloudGrammar);
               // 指定引擎类型
               mAsr.setParameter(SpeechConstant.ENGINE_TYPE, mEngineType);
               // 设置文本编码格式
               mAsr.setParameter(SpeechConstant.TEXT_ENCODING,"utf-8");
                ret = mAsr.buildGrammar(GRAMMAR_TYPE_ABNF, mContent, grammarListener);
               if(ret != ErrorCode.SUCCESS)
                  showTip("语法构建失败,错误码:" + ret);
            }
            break;
         // 本地-更新词典
         case R.id.isr_lexcion: 
            ((EditText)findViewById(R.id.isr_text)).setText(mLocalLexicon);
            mContent = new String(mLocalLexicon);
            mAsr.setParameter(SpeechConstant.PARAMS, null);
            // 设置引擎类型
            mAsr.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);
            // 设置资源路径
            mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
            //使用8k音频的时候请解开注释
//          mAsr.setParameter(SpeechConstant.SAMPLE_RATE, "8000");
            // 设置语法构建路径
            mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, grmPath);
            // 设置语法名称
            mAsr.setParameter(SpeechConstant.GRAMMAR_LIST, "call");
            // 设置文本编码格式
            mAsr.setParameter(SpeechConstant.TEXT_ENCODING,"utf-8");
            ret = mAsr.updateLexicon("contact", mContent, lexiconListener);
            if(ret != ErrorCode.SUCCESS){
               showTip("更新词典失败,错误码:" + ret);
            }
            break;
         // 开始识别
         case R.id.isr_recognize:
            ((EditText)findViewById(R.id.isr_text)).setText(null);// 清空显示内容
            // 设置参数
            if (!setParam()) {
               showTip("请先构建语法。");
               return;
            };
            
            ret = mAsr.startListening(mRecognizerListener);
            if (ret != ErrorCode.SUCCESS) {
               showTip("识别失败,错误码: " + ret);   
            }
            break;
         // 停止识别
         case R.id.isr_stop:
            mAsr.stopListening();
            showTip("停止识别");
            break;
         // 取消识别
         case R.id.isr_cancel:
            mAsr.cancel();
            showTip("取消识别");
            break;
      }
   }
   
   /**
     * 初始化监听器。
     */
    private InitListener mInitListener = new InitListener() {

      @Override
      public void onInit(int code) {
         Log.d(TAG, "SpeechRecognizer init() code = " + code);
         if (code != ErrorCode.SUCCESS) {
              showTip("初始化失败,错误码:"+code);
           }
      }
    };
       
   /**
     * 更新词典监听器。
     */
   private LexiconListener lexiconListener = new LexiconListener() {
      @Override
      public void onLexiconUpdated(String lexiconId, SpeechError error) {
         if(error == null){
            showTip("词典更新成功");
         }else{
            showTip("词典更新失败,错误码:"+error.getErrorCode());
         }
      }
   };
   
   /**
     * 构建语法监听器。
     */
   private GrammarListener grammarListener = new GrammarListener() {
      @Override
      public void onBuildFinish(String grammarId, SpeechError error) {
         if(error == null){
            if (mEngineType.equals(SpeechConstant.TYPE_CLOUD)) {
               Editor editor = mSharedPreferences.edit();
               if(!TextUtils.isEmpty(grammarId))
                  editor.putString(KEY_GRAMMAR_ABNF_ID, grammarId);
               editor.commit();
            }
            showTip("语法构建成功:" + grammarId);
         }else{
            showTip("语法构建失败,错误码:" + error.getErrorCode());
         }        
      }
   };
   /**
    * 获取联系人监听器。
    */
   private ContactListener mContactListener = new ContactListener() {
      @Override
      public void onContactQueryFinish(String contactInfos, boolean changeFlag) {
         //获取联系人
         mLocalLexicon = contactInfos;
      }     
   };
   /**
     * 识别监听器。
     */
    private RecognizerListener mRecognizerListener = new RecognizerListener() {
        
        @Override
        public void onVolumeChanged(int volume, byte[] data) {
           showTip("当前正在说话,音量大小:" + volume);
           Log.d(TAG, "返回音频数据:"+data.length);
        }
        
      @Override
      public void onResult(final RecognizerResult result, boolean isLast) {
         if (null != result && !TextUtils.isEmpty(result.getResultString())) {
            Log.d(TAG, "recognizer result:" + result.getResultString());
            String text = "";
            if (mResultType.equals("json")) {
               text = JsonParser.parseGrammarResult(result.getResultString(), mEngineType);
            } else if (mResultType.equals("xml")) {
               text = XmlParser.parseNluResult(result.getResultString());
            }
            // 显示
            ((EditText) findViewById(R.id.isr_text)).setText(text);
         } else {
            Log.d(TAG, "recognizer result : null");
         }
      }
        
        @Override
        public void onEndOfSpeech() {
           // 此回调表示:检测到了语音的尾端点,已经进入识别过程,不再接受语音输入          
         showTip("结束说话");
        }
        
        @Override
        public void onBeginOfSpeech() {
           // 此回调表示:sdk内部录音机已经准备好了,用户可以开始语音输入
           showTip("开始说话");
        }

      @Override
      public void onError(SpeechError error) {
         showTip("onError Code:"    + error.getErrorCode());
      }

      @Override
      public void onEvent(int eventType, int arg1, int arg2, Bundle obj) {
         // 以下代码用于获取与云端的会话id,当业务出错时将会话id提供给技术支持人员,可用于查询会话日志,定位出错原因
         // 若使用本地能力,会话id为null
         // if (SpeechEvent.EVENT_SESSION_ID == eventType) {
         //    String sid = obj.getString(SpeechEvent.KEY_EVENT_SESSION_ID);
         //    Log.d(TAG, "session id =" + sid);
         // }
      }

    };
    
   

   private void showTip(final String str) {
      runOnUiThread(new Runnable() {
         @Override
         public void run() {
            mToast.setText(str);
            mToast.show();
         }
      });
   }

   /**
    * 参数设置
    * @param
    * @return 
    */
   public boolean setParam(){
      boolean result = false;
      // 清空参数
      mAsr.setParameter(SpeechConstant.PARAMS, null);
      // 设置识别引擎
      mAsr.setParameter(SpeechConstant.ENGINE_TYPE, mEngineType);
      if("cloud".equalsIgnoreCase(mEngineType))
      {
         String grammarId = mSharedPreferences.getString(KEY_GRAMMAR_ABNF_ID, null);
         if(TextUtils.isEmpty(grammarId))
         {
            result =  false;
         }else {
            // 设置返回结果格式
            mAsr.setParameter(SpeechConstant.RESULT_TYPE, mResultType);
            // 设置云端识别使用的语法id
            mAsr.setParameter(SpeechConstant.CLOUD_GRAMMAR, grammarId);
            result =  true;
         }
      }
      else
      {
         // 设置本地识别资源
         mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
         // 设置语法构建路径
         mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, grmPath);
         // 设置返回结果格式
         mAsr.setParameter(SpeechConstant.RESULT_TYPE, mResultType);
         // 设置本地识别使用语法id
         mAsr.setParameter(SpeechConstant.LOCAL_GRAMMAR, "call");
         // 设置识别的门限值
         mAsr.setParameter(SpeechConstant.MIXED_THRESHOLD, "30");
         // 使用8k音频的时候请解开注释
//       mAsr.setParameter(SpeechConstant.SAMPLE_RATE, "8000");
         result = true;
      }
      
      // 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限
      // 注:AUDIO_FORMAT参数语记需要更新版本才能生效
      mAsr.setParameter(SpeechConstant.AUDIO_FORMAT,"wav");
      mAsr.setParameter(SpeechConstant.ASR_AUDIO_PATH, Environment.getExternalStorageDirectory()+"/msc/asr.wav");
      return result;
   }
   
   //获取识别资源路径
   private String getResourcePath(){
      StringBuffer tempBuffer = new StringBuffer();
      //识别通用资源
      tempBuffer.append(ResourceUtil.generateResourcePath(this, RESOURCE_TYPE.assets, "asr/common.jet"));
      //识别8k资源-使用8k的时候请解开注释
//    tempBuffer.append(";");
//    tempBuffer.append(ResourceUtil.generateResourcePath(this, RESOURCE_TYPE.assets, "asr/common_8k.jet"));
      return tempBuffer.toString();
   }
   
   @Override
   protected void onDestroy() {
      super.onDestroy();
      if( null != mAsr ){
         // 退出时释放连接
         mAsr.cancel();
         mAsr.destroy();
      }
   }
   
}

看着还是比较多的,我之所以说多而没有说难就是因为它并不难;下面的介绍中我们会对其进行再次瘦身。

 

第八步:为自己的Demo做准备工作

(1)把assets目录copy到我们的module中

(2)把jniLibs目录copy到我们的module中

这里是在Project视图下完成的,这里在Android视图下展示效果更好一下

(3)打开Project视图,把libs目录中的内容复制到我们的module中

(4)在build.gradle(Module:app)中的depandencies下添加依赖:

compile files('libs/Msc.jar')

 

(5)把Demo中的工具类copy到我们的module中

 

截止到现在,我们还在准备阶段,下面就进入正题,来对我们的需要的功能的实现做一个简要的梳理

 

第九步:提取离线命令词识别功能到我们的项目

定义一个activity,CallStepActivity,把AsrDemo中的逻辑代码copy到CallStepActivty中,把对应的布局文件也对应copy进来

 

第十步:梳理逻辑,继续瘦身

上面也说了,AsrDemo中的Demo还是有点冗余,因为好多我们用不上或者暂时用不上,比如在线的命令词识别等肯定用不上,比如词典更新我们暂时用不上,下面就来分析一下单纯使用离线命令词识别的实现(下面是重点

(1)根据应用ID初始化SpeechUtility,通常在程序入口Application中完成

package com.hfut.offlinerecongnizer.activity.util;

import android.app.Application;

import com.hfut.offlinerecongnizer.R;
import com.iflytek.cloud.SpeechConstant;
import com.iflytek.cloud.SpeechUtility;

/**
 * author:why
 * created on: 2018/8/27 11:10
 * description:
 */
public class MyApplication extends Application {

    @Override
    public void onCreate() {
        // 应用程序入口处调用,避免手机内存过小,杀死后台进程后通过历史intent进入Activity造成SpeechUtility对象为null
        // 注意:此接口在非主进程调用会返回null对象,如需在非主进程使用语音功能,请增加参数:SpeechConstant.FORCE_LOGIN+"=true"
        // 参数间使用“,”分隔。
        // 设置你申请的应用appid
        // 注意: appid 必须和下载的SDK保持一致,否则会出现10407错误
        StringBuffer param = new StringBuffer();
        param.append("appid=" + getString(R.string.app_id));
        param.append(",");
        // 设置使用v5+
        param.append(SpeechConstant.ENGINE_MODE + "=" + SpeechConstant.MODE_MSC);
        SpeechUtility.createUtility(MyApplication.this, param.toString());
        super.onCreate();
    }
}

(2)在Activity中初始化初始化监听器,用于初始化语音识别引擎

/**
 * 初始化监听器。
 */
private InitListener mInitListener = new InitListener() {

    @Override
    public void onInit(int code) {
        Log.d(TAG, "SpeechRecognizer init() code = " + code);
        if (code != ErrorCode.SUCCESS) {
            showTip("初始化失败,错误码:" + code);
        }
    }
};

(3)初始化语音识别监听器

/**
 * 识别监听器。
 */
private RecognizerListener mRecognizerListener = new RecognizerListener() {

    @Override
    public void onVolumeChanged(int volume, byte[] data) {
        showTip("当前正在说话,音量大小:" + volume);
        Log.d(TAG, "返回音频数据:" + data.length);
    }

    @Override
    public void onResult(final RecognizerResult result, boolean isLast) {
        if (null != result && !TextUtils.isEmpty(result.getResultString())) {
            Log.d(TAG, "recognizer result:" + result.getResultString());
            String text = "";
            if (mResultType.equals("json")) {
                text = JsonParser.parseGrammarResult(result.getResultString(), SpeechConstant.TYPE_LOCAL);
            } else if (mResultType.equals("xml")) {
                text = XmlParser.parseNluResult(result.getResultString());
            }
            // 显示
            ((EditText) findViewById(R.id.isr_text)).setText(text);
        } else {
            Log.d(TAG, "recognizer result : null");
        }
    }

    @Override
    public void onEndOfSpeech() {
        // 此回调表示:检测到了语音的尾端点,已经进入识别过程,不再接受语音输入
        showTip("结束说话");
    }

    @Override
    public void onBeginOfSpeech() {
        // 此回调表示:sdk内部录音机已经准备好了,用户可以开始语音输入
        showTip("开始说话");
    }

    @Override
    public void onError(SpeechError error) {
        showTip("onError Code:" + error.getErrorCode());
    }

    @Override
    public void onEvent(int i, int i1, int i2, Bundle bundle) {

    }
};

(4)初始化语法文件构建监听器

/**
 * 构建语法监听器。
 */
private GrammarListener grammarListener = new GrammarListener() {
    @Override
    public void onBuildFinish(String grammarId, SpeechError error) {
        if (error == null) {
            showTip("语法构建成功:" + grammarId);
        } else {
            showTip("语法构建失败,错误码:" + error.getErrorCode());
        }
    }
};

(5)初始化语音识别引擎并完成参数设置

// 初始化识别引擎
mAsr = SpeechRecognizer.createRecognizer(this, mInitListener);
//设置识别引擎参数
setParam();

其中setPatam():

public void setParam() {
    boolean result = true;
    // 清空参数
    mAsr.setParameter(SpeechConstant.PARAMS, null);
    // 设置识别引擎
    mAsr.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);
    // 设置本地识别资源
    mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
    // 设置语法构建路径
    mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, grmPath);
    // 设置返回结果格式
    mAsr.setParameter(SpeechConstant.RESULT_TYPE, mResultType);
    // 设置本地识别使用语法id
    mAsr.setParameter(SpeechConstant.LOCAL_GRAMMAR, "call");
    // 设置识别的门限值
    mAsr.setParameter(SpeechConstant.MIXED_THRESHOLD, "30");

}

(6)完成语法构建

private void  buildGrammer() {
        mLocalGrammar = FucUtil.readFile(this, "call.bnf", "utf-8");
        // 本地-构建语法文件,生成语法id
        ((EditText) findViewById(R.id.isr_text)).setText(mLocalGrammar);
        mContent = new String(mLocalGrammar);
        mAsr.setParameter(SpeechConstant.PARAMS, null);
        // 设置文本编码格式
        mAsr.setParameter(SpeechConstant.TEXT_ENCODING, "utf-8");
        // 设置引擎类型
        mAsr.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);
        // 设置语法构建路径
        mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, grmPath);
        // 设置资源路径
        mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
        ret = mAsr.buildGrammar(GRAMMAR_TYPE_BNF, mContent, grammarListener);
        if (ret != ErrorCode.SUCCESS) {
            showTip("语法构建失败,错误码:" + ret);
        } else {
            showTip("语法构建成功");
        }

    }

(7)开启识别,停止识别,取消识别分别是:

 mAsr.startListening(mRecognizerListener);
 mAsr.stopListening();
 mAsr.cancel();

第十一步:最简单的功能实现代码

所以最后组合起来,我们实现剥离了所有其他功能的只是实现离线命令词识别的代码,CallStepActivity代码如下:

package com.hfut.offlinerecongnizer.activity.activity;

import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.SharedPreferences;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.hfut.offlinerecongnizer.R;
import com.hfut.offlinerecongnizer.activity.util.FucUtil;
import com.hfut.offlinerecongnizer.activity.util.JsonParser;
import com.hfut.offlinerecongnizer.activity.util.XmlParser;
import com.iflytek.cloud.ErrorCode;
import com.iflytek.cloud.GrammarListener;
import com.iflytek.cloud.InitListener;
import com.iflytek.cloud.LexiconListener;
import com.iflytek.cloud.RecognizerListener;
import com.iflytek.cloud.RecognizerResult;
import com.iflytek.cloud.SpeechConstant;
import com.iflytek.cloud.SpeechError;
import com.iflytek.cloud.SpeechRecognizer;
import com.iflytek.cloud.util.ContactManager;
import com.iflytek.cloud.util.ResourceUtil;

/**
 * @author why
 * @date 2018-8-27 15:09:38
 */
public class CallStepActivity extends AppCompatActivity implements View.OnClickListener {

    private static String TAG = OffLineTestActivity.class.getSimpleName();
    // 语音识别对象
    private SpeechRecognizer mAsr;
    private Toast mToast;
    // 本地语法文件
    private String mLocalGrammar = null;
    // 本地语法构建路径
    private String grmPath = Environment.getExternalStorageDirectory()
            .getAbsolutePath() + "/msc/call";
    // 返回结果格式,支持:xml,json
    private String mResultType = "json";
    private final String GRAMMAR_TYPE_BNF = "bnf";

    @SuppressLint("ShowToast")
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_call_step);
        initLayout();
        mToast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
        // 初始化识别引擎
        mAsr = SpeechRecognizer.createRecognizer(this, mInitListener);

        //构建本地语法
        buildGrammer();
    }

    /**
     * 初始化Layout。
     */
    private void initLayout() {
        findViewById(R.id.isr_recognize).setOnClickListener(this);
        findViewById(R.id.isr_stop).setOnClickListener(this);
        findViewById(R.id.isr_cancel).setOnClickListener(this);
    }

    String mContent;// 语法、词典临时变量
    int ret = 0;// 函数调用返回值

    @Override
    public void onClick(View view) {
        if (null == mAsr) {
            // 创建单例失败,与 21001 错误为同样原因,参考 http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=9688
            this.showTip("创建对象失败,请确认 libmsc.so 放置正确,\n 且有调用 createUtility 进行初始化");
            return;
        }
        switch (view.getId()) {

            // 开始识别
            case R.id.isr_recognize:
                ((EditText) findViewById(R.id.isr_text)).setText(null);// 清空显示内容
                //设置识别引擎参数
                setParam();
                ret = mAsr.startListening(mRecognizerListener);
                if (ret != ErrorCode.SUCCESS) {
                    showTip("识别失败,错误码: " + ret);
                }
                break;

            // 停止识别
            case R.id.isr_stop:
                mAsr.stopListening();
                showTip("停止识别");
                break;

            // 取消识别
            case R.id.isr_cancel:
                mAsr.cancel();
                showTip("取消识别");
                break;
        }
    }

    /**
     * 初始化监听器。
     */
    private InitListener mInitListener = new InitListener() {

        @Override
        public void onInit(int code) {
            Log.d(TAG, "SpeechRecognizer init() code = " + code);
            if (code != ErrorCode.SUCCESS) {
                showTip("初始化失败,错误码:" + code);
            }
        }
    };


    /**
     * 构建语法监听器。
     */
    private GrammarListener grammarListener = new GrammarListener() {
        @Override
        public void onBuildFinish(String grammarId, SpeechError error) {
            if (error == null) {
                showTip("语法构建成功:" + grammarId);
            } else {
                showTip("语法构建失败,错误码:" + error.getErrorCode());
            }
        }
    };

    /**
     * 识别监听器。
     */
    private RecognizerListener mRecognizerListener = new RecognizerListener() {

        @Override
        public void onVolumeChanged(int volume, byte[] data) {
            showTip("当前正在说话,音量大小:" + volume);
            Log.d(TAG, "返回音频数据:" + data.length);
        }

        @Override
        public void onResult(final RecognizerResult result, boolean isLast) {
            if (null != result && !TextUtils.isEmpty(result.getResultString())) {
                Log.d(TAG, "recognizer result:" + result.getResultString());
                String text = "";
                if (mResultType.equals("json")) {
                    text = JsonParser.parseGrammarResult(result.getResultString(), SpeechConstant.TYPE_LOCAL);
                } else if (mResultType.equals("xml")) {
                    text = XmlParser.parseNluResult(result.getResultString());
                }
                // 显示
                ((EditText) findViewById(R.id.isr_text)).setText(text);
            } else {
                Log.d(TAG, "recognizer result : null");
            }
        }

        @Override
        public void onEndOfSpeech() {
            // 此回调表示:检测到了语音的尾端点,已经进入识别过程,不再接受语音输入
            showTip("结束说话");
        }

        @Override
        public void onBeginOfSpeech() {
            // 此回调表示:sdk内部录音机已经准备好了,用户可以开始语音输入
            showTip("开始说话");
        }

        @Override
        public void onError(SpeechError error) {
            showTip("onError Code:" + error.getErrorCode());
        }

        @Override
        public void onEvent(int i, int i1, int i2, Bundle bundle) {

        }
    };

    private void showTip(final String str) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mToast.setText(str);
                mToast.show();
            }
        });
    }

    /**
     * 参数设置
     *
     * @param
     * @return
     */
    public void setParam() {
        boolean result = true;
        // 清空参数
        mAsr.setParameter(SpeechConstant.PARAMS, null);
        // 设置识别引擎
        mAsr.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);
        // 设置本地识别资源
        mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
        // 设置语法构建路径
        mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, grmPath);
        // 设置返回结果格式
        mAsr.setParameter(SpeechConstant.RESULT_TYPE, mResultType);
        // 设置本地识别使用语法id
        mAsr.setParameter(SpeechConstant.LOCAL_GRAMMAR, "call");
        // 设置识别的门限值
        mAsr.setParameter(SpeechConstant.MIXED_THRESHOLD, "30");
        // 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限
        // 注:AUDIO_FORMAT参数语记需要更新版本才能生效
        mAsr.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
        mAsr.setParameter(SpeechConstant.ASR_AUDIO_PATH, Environment.getExternalStorageDirectory() + "/msc/asr.wav");
    }

    //获取识别资源路径
    private String getResourcePath() {
        StringBuffer tempBuffer = new StringBuffer();
        //识别通用资源
        tempBuffer.append(ResourceUtil.generateResourcePath(this, ResourceUtil.RESOURCE_TYPE.assets, "asr/common.jet"));
        return tempBuffer.toString();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (null != mAsr) {
            // 退出时释放连接
            mAsr.cancel();
            mAsr.destroy();
        }
    }


    private void buildGrammer() {

        mLocalGrammar = FucUtil.readFile(this, "call.bnf", "utf-8");
        // 本地-构建语法文件,生成语法id
        ((EditText) findViewById(R.id.isr_text)).setText(mLocalGrammar);
        mContent = new String(mLocalGrammar);
        mAsr.setParameter(SpeechConstant.PARAMS, null);
        // 设置文本编码格式
        mAsr.setParameter(SpeechConstant.TEXT_ENCODING, "utf-8");
        // 设置引擎类型
        mAsr.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);
        // 设置语法构建路径
        mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, grmPath);
        //使用8k音频的时候请解开注释
//             mAsr.setParameter(SpeechConstant.SAMPLE_RATE, "8000");
        // 设置资源路径
        mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
        ret = mAsr.buildGrammar(GRAMMAR_TYPE_BNF, mContent, grammarListener);
        if (ret != ErrorCode.SUCCESS) {
            showTip("语法构建失败,错误码:" + ret);
        } else {
            showTip("语法构建成功");
        }

    }
}

activity_call_step.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:focusable="true"
    android:focusableInTouchMode="true"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    android:padding="10dip" >
    <include layout="@layout/title" />

    <EditText
        android:id="@+id/isr_text"
        android:layout_width="fill_parent"
        android:layout_height="0dip"
        android:layout_weight="1"
        android:gravity="top|left"
        android:textSize="20sp" />

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dip"
        android:layout_marginBottom="2dip"
        android:gravity="center_horizontal"
        android:orientation="horizontal" >
    </LinearLayout>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dip"
        android:layout_marginBottom="2dip"
        android:gravity="center_horizontal"
        android:orientation="horizontal" >
        <Button
            android:id="@+id/isr_recognize"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="开始识别"
            android:textSize="20sp" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="2dip"
        android:layout_marginLeft="10dip"
        android:layout_marginRight="10dip"
        android:layout_marginTop="2dip"
        android:gravity="center_horizontal"
        android:orientation="horizontal" >

        <Button
            android:id="@+id/isr_stop"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="停止录音"
            android:textSize="20sp" />

        <Button
            android:id="@+id/isr_cancel"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="取消"
            android:textSize="20sp" />
    </LinearLayout>

</LinearLayout>

如果不出意外的话,运行应该没有任何问题的。至此,最难的最复杂的第三阶段已经结束了,下面就来看看第四阶段的工作任务:

 

第四阶段    提高

第十二步:丰富我们的功能

因为API里面提供了更新词典的功能(从这里我们也可以推出来后面介绍的bnf文件中词槽的定义也可以通过代码来实现):

mAsr.updateLexicon(groupName, mLocalLexicon, lexiconListener);

所以我们就该利用起来,毕竟如果我想修改某一个词槽的定义时,不能每次都是通过编辑bnf文件,然后在运行程序来实现,太麻烦了。这里我通过一个自定义的AlertDialog来实现对词槽的重新赋值,并列的同义词用“,”隔开即可,类似于bnf文件中的  |  符号;下面直接给出OffLineTestActivity代码:

package com.hfut.offlinerecongnizer.activity.activity;

import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RadioGroup;
import android.widget.Toast;

import com.hfut.offlinerecongnizer.R;
import com.hfut.offlinerecongnizer.activity.util.FucUtil;
import com.hfut.offlinerecongnizer.activity.util.JsonParser;
import com.hfut.offlinerecongnizer.activity.util.XmlParser;
import com.iflytek.cloud.ErrorCode;
import com.iflytek.cloud.GrammarListener;
import com.iflytek.cloud.InitListener;
import com.iflytek.cloud.LexiconListener;
import com.iflytek.cloud.RecognizerListener;
import com.iflytek.cloud.RecognizerResult;
import com.iflytek.cloud.SpeechConstant;
import com.iflytek.cloud.SpeechError;
import com.iflytek.cloud.SpeechRecognizer;
import com.iflytek.cloud.util.ContactManager;
import com.iflytek.cloud.util.ContactManager.ContactListener;
import com.iflytek.cloud.util.ResourceUtil;
import com.iflytek.cloud.util.ResourceUtil.RESOURCE_TYPE;

/**
 * @author why
 * @date 2018-8-27 13:20:58
 */
public class OffLineTestActivity extends AppCompatActivity implements View.OnClickListener {

    private static String TAG = OffLineTestActivity.class.getSimpleName();
    // 语音识别对象
    private SpeechRecognizer mAsr;
    private Toast mToast;
    // 缓存
    //private SharedPreferences mSharedPreferences;
    // 本地语法文件
    private String mLocalGrammar = null;
    // 本地词典
    private String mLocalLexicon = null;
    // 本地语法构建路径
    private String grmPath = Environment.getExternalStorageDirectory()
            .getAbsolutePath() + "/msc/call";
    // 返回结果格式,支持:xml,json
    private String mResultType = "json";
    private final String GRAMMAR_TYPE_BNF = "bnf";

    private String groupName;
    private String groupInfo;

    @SuppressLint("ShowToast")
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_off_line_test);
        initLayout();

        // 初始化识别引擎对象
        mAsr = SpeechRecognizer.createRecognizer(this, mInitListener);
        mToast = Toast.makeText(this, "", Toast.LENGTH_SHORT);

        //构建本地语法
        buildGrammer();
    }

    /**
     * 初始化Layout
     */
    private void initLayout() {
        findViewById(R.id.isr_recognize).setOnClickListener(this);
        findViewById(R.id.isr_lexcion).setOnClickListener(this);
        findViewById(R.id.isr_stop).setOnClickListener(this);
        findViewById(R.id.isr_cancel).setOnClickListener(this);
    }

    String mContent;// 语法、词典临时变量
    int ret = 0;// 函数调用返回值

    @Override
    public void onClick(View view) {
        if (null == mAsr) {
            // 创建单例失败,与 21001 错误为同样原因,参考 http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=9688
            this.showTip("创建对象失败,请确认 libmsc.so 放置正确,\n 且有调用 createUtility 进行初始化");
            return;
        }
        switch (view.getId()) {
            // 本地-更新词典
            case R.id.isr_lexcion:

                AlertDialog.Builder builder = new AlertDialog.Builder(this);
                LayoutInflater inflater = LayoutInflater.from(this);
                final View v = inflater.inflate(R.layout.user_info_editor, null);
                final EditText wordGroupName = v.findViewById(R.id.enter_word_group_name);
                final EditText wordGroupInfo = v.findViewById(R.id.enter_word_group_info);
                Button cancleButton = v.findViewById(R.id.register_cancle);
                Button confirmButton = v.findViewById(R.id.register_confirm);
                final Dialog dialog = builder.create();
                //点击EditText弹出软键盘
                cancleButton.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Toast.makeText(OffLineTestActivity.this, "取消", Toast.LENGTH_SHORT).show();
                        dialog.cancel();
                    }
                });


                confirmButton.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {

                        if (!wordGroupName.getText().toString().equals("")) {
                            groupName= wordGroupName.getText().toString();
                        }
                        if (!wordGroupInfo.getText().toString().equals("")) {
                            groupInfo = wordGroupInfo.getText().toString();
                        }
                        mLocalLexicon=getUpdateInfo(groupInfo);
                        ((EditText) findViewById(R.id.isr_text)).setText(mLocalLexicon);
                        mAsr.setParameter(SpeechConstant.PARAMS, null);
                        // 设置引擎类型
                        mAsr.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);
                        // 设置资源路径
                        mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
                        // 设置语法构建路径
                        mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, grmPath);
                        // 设置语法名称
                        mAsr.setParameter(SpeechConstant.GRAMMAR_LIST, "call");
                        // 设置文本编码格式
                        mAsr.setParameter(SpeechConstant.TEXT_ENCODING, "utf-8");
                        //执行更新操作
                        ret = mAsr.updateLexicon(groupName, mLocalLexicon, lexiconListener);
                        if (ret != ErrorCode.SUCCESS) {
                            showTip("更新词典失败,错误码:" + ret);
                        }
                        else{
                            showTip("更新词典成功" );
                        }
                        dialog.cancel();
                    }
                });

                dialog.show();
                dialog.getWindow().setContentView(v);//自定义布局应该在这里添加,要在dialog.show()的后面
                dialog.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
                dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
                break;
            // 开始识别
            case R.id.isr_recognize:
                //设置识别引擎参数
                setParam();
                ((EditText) findViewById(R.id.isr_text)).setText(null);// 清空显示内容
                ret = mAsr.startListening(mRecognizerListener);
                if (ret != ErrorCode.SUCCESS) {
                    showTip("识别失败,错误码: " + ret);
                }
                break;
            // 停止识别
            case R.id.isr_stop:
                mAsr.stopListening();
                showTip("停止识别");
                break;
            // 取消识别
            case R.id.isr_cancel:
                mAsr.cancel();
                showTip("取消识别");
                break;
        }
    }

    private String getUpdateInfo(String groupInfo) {
        String[] wordList=groupInfo.split(",");
        StringBuilder builder=new StringBuilder();
        for(int i=0;i<wordList.length;i++){
            if(i==wordList.length-1) {
                builder.append(wordList[i] );
                Log.d(TAG, "getUpdateInfo: "+wordList[i]);
            }else{
                builder.append(wordList[i] + "\n");
            }
        }
        return builder.toString();
    }

    /**
     * 初始化监听器。
     */
    private InitListener mInitListener = new InitListener() {

        @Override
        public void onInit(int code) {
            Log.d(TAG, "SpeechRecognizer init() code = " + code);
            if (code != ErrorCode.SUCCESS) {
                showTip("初始化失败,错误码:" + code);
            }
        }
    };

    /**
     * 更新词典监听器。
     */
    private LexiconListener lexiconListener = new LexiconListener() {
        @Override
        public void onLexiconUpdated(String lexiconId, SpeechError error) {
            if (error == null) {
                showTip("词典更新成功");
            } else {
                showTip("词典更新失败,错误码:" + error.getErrorCode());
            }
        }
    };

    /**
     * 构建语法监听器。
     */
    private GrammarListener grammarListener = new GrammarListener() {
        @Override
        public void onBuildFinish(String grammarId, SpeechError error) {
            if (error == null) {
                showTip("语法构建成功:" + grammarId);
            } else {
                showTip("语法构建失败,错误码:" + error.getErrorCode());
            }
        }
    };

    /**
     * 识别监听器。
     */
    private RecognizerListener mRecognizerListener = new RecognizerListener() {

        @Override
        public void onVolumeChanged(int volume, byte[] data) {
            showTip("当前正在说话,音量大小:" + volume);
            Log.d(TAG, "返回音频数据:" + data.length);
        }

        @Override
        public void onResult(final RecognizerResult result, boolean isLast) {
            if (null != result && !TextUtils.isEmpty(result.getResultString())) {
                Log.d(TAG, "recognizer result:" + result.getResultString());
                String text = "";
                if (mResultType.equals("json")) {
                    text = JsonParser.parseGrammarResult(result.getResultString(), SpeechConstant.TYPE_LOCAL);
                } else if (mResultType.equals("xml")) {
                    text = XmlParser.parseNluResult(result.getResultString());
                }
                // 显示
                ((EditText) findViewById(R.id.isr_text)).setText(text);
            } else {
                Log.d(TAG, "recognizer result : null");
            }
        }

        @Override
        public void onEndOfSpeech() {
            // 此回调表示:检测到了语音的尾端点,已经进入识别过程,不再接受语音输入
            showTip("结束说话");
        }

        @Override
        public void onBeginOfSpeech() {
            // 此回调表示:sdk内部录音机已经准备好了,用户可以开始语音输入
            showTip("开始说话");
        }

        @Override
        public void onError(SpeechError error) {
            showTip("onError Code:" + error.getErrorCode());
        }

        @Override
        public void onEvent(int i, int i1, int i2, Bundle bundle) {

        }
    };

    private void showTip(final String str) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mToast.setText(str);
                mToast.show();
            }
        });
    }

    /**
     * 参数设置
     *
     * @param
     * @return
     */
    public void setParam() {
        // 清空参数
        mAsr.setParameter(SpeechConstant.PARAMS, null);
        // 设置识别引擎
        mAsr.setParameter(SpeechConstant.ENGINE_TYPE,SpeechConstant.TYPE_LOCAL);
        // 设置本地识别资源
        mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
        // 设置语法构建路径
        mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, grmPath);
        // 设置返回结果格式
        mAsr.setParameter(SpeechConstant.RESULT_TYPE, mResultType);
        // 设置本地识别使用语法id
        mAsr.setParameter(SpeechConstant.LOCAL_GRAMMAR, "call");
        // 设置识别的门限值
        mAsr.setParameter(SpeechConstant.MIXED_THRESHOLD, "30");
        // 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限
        // 注:AUDIO_FORMAT参数语记需要更新版本才能生效
        mAsr.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
        mAsr.setParameter(SpeechConstant.ASR_AUDIO_PATH, Environment.getExternalStorageDirectory() + "/msc/asr.wav");

    }

    //获取识别资源路径
    private String getResourcePath() {
        StringBuffer tempBuffer = new StringBuffer();
        //识别通用资源
        tempBuffer.append(ResourceUtil.generateResourcePath(this, RESOURCE_TYPE.assets, "asr/common.jet"));
        return tempBuffer.toString();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (null != mAsr) {
            // 退出时释放连接
            mAsr.cancel();
            mAsr.destroy();
        }
    }


    private boolean buildGrammer() {

             mLocalGrammar = FucUtil.readFile(this, "call.bnf", "utf-8");
            // 本地-构建语法文件,生成语法id
            ((EditText) findViewById(R.id.isr_text)).setText(mLocalGrammar);
            mContent = new String(mLocalGrammar);
            mAsr.setParameter(SpeechConstant.PARAMS, null);
            // 设置文本编码格式
            mAsr.setParameter(SpeechConstant.TEXT_ENCODING, "utf-8");
            // 设置引擎类型
            mAsr.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);
            // 设置语法构建路径
            mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, grmPath);
            //使用8k音频的时候请解开注释
//             mAsr.setParameter(SpeechConstant.SAMPLE_RATE, "8000");
            // 设置资源路径
            mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
            ret = mAsr.buildGrammar(GRAMMAR_TYPE_BNF, mContent, grammarListener);
            if (ret != ErrorCode.SUCCESS) {
                showTip("语法构建失败,错误码:" + ret);
            }
            else{
                showTip("语法构建成功");
            }
        return true;
    }
}

activity_off_line_test.xml代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:focusable="true"
    android:focusableInTouchMode="true"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    android:padding="10dip" >
    <include layout="@layout/title" />

    <EditText
        android:id="@+id/isr_text"
        android:layout_width="fill_parent"
        android:layout_height="0dip"
        android:layout_weight="1"
        android:gravity="top|left"
        android:textSize="20sp" />

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dip"
        android:layout_marginBottom="2dip"
        android:gravity="center_horizontal"
        android:orientation="horizontal" >

        <Button
            android:id="@+id/isr_recognize"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="开始识别"
            android:textSize="20sp" />

        <Button
            android:id="@+id/isr_lexcion"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="更新词典"
            android:textSize="20sp"
            android:enabled="true" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="2dip"
        android:layout_marginLeft="10dip"
        android:layout_marginRight="10dip"
        android:layout_marginTop="2dip"
        android:gravity="center_horizontal"
        android:orientation="horizontal" >

        <Button
            android:id="@+id/isr_stop"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="停止录音"
            android:textSize="20sp" />

        <Button
            android:id="@+id/isr_cancel"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="取消"
            android:textSize="20sp" />
    </LinearLayout>

</LinearLayout>

 

word_info_editor.xml代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#B0C4DE"
    android:orientation="vertical">

    <TextView
        android:layout_marginLeft="20dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="请编辑更新信息:"
        android:textColor="#000000"
        android:textSize="30dp" />

    <LinearLayout
        android:layout_marginLeft="20dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="词组名称:"
            android:textSize="20dp" />

        <EditText
            android:id="@+id/enter_word_group_name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="30dp"
            android:hint="请输入词组名称" />

    </LinearLayout>

    <LinearLayout
        android:layout_marginLeft="20dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="词组信息:"
            android:textSize="20dp" />

        <EditText
            android:id="@+id/enter_word_group_info"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="30dp"
            android:hint="请编写词组信息" />

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/register_cancle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="350dp"
            android:text="取消" />

        <Button
            android:id="@+id/register_confirm"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:text="确定" />

    </LinearLayout>

</LinearLayout>

其中还有一个所有布局都用到的title.xml代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:layout_gravity="top"
  android:gravity="center">
  <TextView
          android:text="@string/app_name"
          android:layout_width="fill_parent"
          android:layout_height="wrap_content"
          android:gravity="center"
          android:textSize="30sp"
          android:layout_margin="10dip"
    />
</LinearLayout>

上面介绍的离线命令词识别都是基于我们自己编辑的bnf文件中的规则来识别,下面给出一个文件示例:

#BNF+IAT 1.0 UTF-8;
!grammar call;

//通用词槽
!slot <want>;
!slot <deal>;
!slot <how>;

//联系相关词槽声明
!slot <contact>;//联系人
!slot <callPhone>;//联系方式
!slot <callTo>;//联系动作

//巡游相关词槽声明
!slot <destination>;//巡游点
!slot <goTo>;//去
!slot <goToPre>;//准备去

/*
专业语料相关
 */
 //办卡业务
 !slot <cardType>;

 //公积金业务
 !slot <percent>;
 !slot <wagesDeal>;
 !slot <wages>;

!start <commands>;
<commands>:<callRule>|<guideRule>|<dealCardRule>|<wagesDealRule>;

//通用语料
<want>:我想|我要|我准备;
<how>:如何|怎么|怎样;
<deal>:办理|解决|处理;

//测试语料
<contact>:黄老板|王华洋|齐带华|火警!id(119);
<callPhone>:打电话|发微信|发短信;
<callTo>:给;
<callRule>:<callTo><contact><callPhone>|<callPhone><callTo><contact>;//联系语料相关规则

//巡游语料
<destination>:卫生间|饮水机|现金柜台|取款机|充电器|大堂经理;
<goTo>:去|到|找;
<goToPre>:带我|请带我|我想;
<guideRule>:[<goToPre>]<goTo><destination>;//巡游语料相关规则

//办卡语料
<cardType>:卡|信用卡|儿童卡|储蓄卡;//卡片类型
<dealCardRule>:[<want>]<deal><cardType>;

//公积金业务
<percent>:比例;//公积金比例
<wagesDeal>:转移|提取;//处理公积金
<wages>:公积金;
<wagesDealRule>:[<how>]<wagesDeal><wages>;


具体的编辑规则请参考bnf文档编辑指南,后续我还会对这个编辑规则进行介绍,具体就介绍到这里。

注:欢迎扫码关注

 

2018-09-27 11:05:52 weixin_43112746 阅读数 7224
  • C++语音识别开篇

    本篇mark老师将教大家使用第三方库的调用来简单的实现语音识别。随着机器学习和人工智能的热闹,国内语音行业也可谓是百花齐放。 语音识别一个伟大的时代已在我们身边悄悄走来。

    5915 人正在学习 去看看 杨波

#用java调用科大讯飞的离线语音识别dll实现离线识别(JNA实现)(一)

本人接的任务,做离线语音识别,用的是科大讯飞的离线识别,java不支持离线了,所以下载了windows的离线包,用JNA进行调用。之前用的是jni,但是一直没有测试通过,本人又不会C++,研究了一个星期终究放弃。转而使用JNA,JNA使用只要导入一个jar包即可,JNA实际上是对JNI的一种封装。

【在线语音识别的实现看我的另一篇】科大讯飞 在线语音识别 音频来源为【文件】的java接入实现, 适用于初学者

说明

本人一边做一边写,记录问题和解决办法,留给自己思考和大家交流。如有不对的地方,欢迎批评指正!
【ps】现在已经做完,参考第二篇

一、注册用户,下载dll这个先略过,很简单,可以参考其他的……【待】
1、先进入讯飞开放平台,先注册【地址】https://www.xfyun.cn/
2、创建应用,记得平台选windows,因为java不支持离线
在这里插入图片描述

3、创建完成之后,在 我的应用 中
在这里插入图片描述

选择【离线命令词识别】
就完成创建了!

4、下载需要的SDK,这个SDK包含了动态库dll,静态库lib,头文件.h,以及非常重要的例子!
在这里插入图片描述
【点亮需要的功能,下载相应的SDK】
在这里插入图片描述
下载好之后,找出其中的dll文件,两个版本,上面是32位系统,下面是64位系统,本人是64位的
在这里插入图片描述

二、调用JNA
1下载jna的jar包
【JNA包下载地址】:https://download.csdn.net/download/weixin_43112746/10690193

2导入java工程中
在这里插入图片描述
jar包的位置,可以放在根目录,也可以像我一样创立一个lib文件夹来放置
然后
右键工程–properties—java Build Path----libraries—add jars
在这里插入图片描述
【找到相应的JNA包的位置,添加之后点击Apply and Close】就完成JNA包的导入
【JNA包下载地址】:https://download.csdn.net/download/weixin_43112746/10690193

没有做过的,先加载个例子试试!
在这里插入图片描述

创建一个class,一个interface,下面的是讯飞的dll文件放在根目录下
【接口】这里只测试登录的函数

package test;

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.win32.StdCallLibrary;

public interface VoiceLib extends Library {
	  int MSPLogin(String usr, String pwd, String params);
	  VoiceLib instance = (VoiceLib)Native.loadLibrary("msc_x64", VoiceLib.class);//接口直接对动态库文件进行调用,
	  //省去JNI的繁琐步骤
		

}

【类】
package test;

import com.sun.jna.Native;

public class Voice {
		public static void main(String[] args) {
		String usr=null;
		String pwd=null;
		String params="appid=5ba4bc08";//这里填写自己的appid,从讯飞的我的应用里面粘贴过来
		int a;
		a=VoiceLib.instance.MSPLogin(usr, pwd, params);//调用接口的方法传参数
		System.out.println(“a=+a);
		
		
	}
}




输出结果a=0
很明显测试成功了!
能够继续往下写了。

又遇到问题,参数的转换!
普通的倒是还好,转换关系如下:
在这里插入图片描述
来源:https://github.com/java-native-access/jna/blob/master/www/Mappings.md
可是问题来了,讯飞中有指针,句柄,回调函数等等各种乱七八糟的参数,这可怎么转换?头顶一直乌鸦飞过……

三、参数的转换(难点)
登录做完了,再做构建语法,其中用到的函数是

int MSPAPI QISRBuildGrammar(const char *grammarType, const char *grammarContent, unsigned int grammarLength,
 const char *params, GrammarCallBack callback, void *userData);

除了无符号int那个,其他都没有!又一只乌鸦飞过……
竟然还有语法回调函数!这是什么!又一只乌鸦飞过……
查一下回调函数
【百度】这是个链接哈

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

找个资源帖参考下:
https://blog.csdn.net/l527719041/article/details/77461328

哦!原来可以这么操作!

先把const char *解决,这是要转换成String 【待】

再来解决回调函数的参数问题!
【先做这么多,下回分解】
第二篇的链接:手把手的操作——用java调用科大讯飞的离线语音识别dll实现离线识别(JNA实现)(二)

没有更多推荐了,返回首页