准备工作

  • 注册讯飞账号,做相关的认证,只有认证通过才能下载部分免费的资源。官网地址:https://console.xfyun.cn/

  • 创建我的应用后再在离线命令识别

请添加图片描述

1、必要文件包复制到自己的项目目录中

1、在libs 目录下放置这些包

注意重新加载一些gradle

在这里插入图片描述

2、将assets 文件放置在 app 目录下

该文件下 iflytek/recognize.xml 文件是语音识别的ui文件,如果没有的话,点击语音识别app会直接闪退.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JhCKjT2W-1680255017857)(讯飞语音识别.assets/image-20230331101912961.png)]

3、在build 目录下添加一下配置

sourceSets 这目录一定要添加,不然会报错 (创建对象失败,请确认 libmsc.so 放置正确,\n 且有调用 createUtility 进行初始化) 实质上是 包找不到 SpeechRecognizer 对象创建失败。(天坑 天坑 天坑 天坑 天坑)

android {
    sourceSets{
        main{
            jniLibs.srcDir(['libs'])
        }
    }
}
dependencies {
    implementation files('libs/Msc.jar')  
}
4、工具类拷贝到项目文件中

这里主要是一些讯飞提供的工具类,主要使用到的是json转换工具

setting 文件下的是语音命令识别和语音合成(暂时不使用)的设置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c7hF2MxJ-1680255017859)(讯飞语音识别.assets/image-20230331102924007.png)]

5、在AndroidManifest.xml 添加权限
  <!--连接网络权限,用于执行云端语音能力 -->
    <uses-permission android:name="android.permission.INTERNET"/>
    <!--获取手机录音机使用权限,听写、识别、语义理解需要用到此权限 -->
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <!--读取网络信息状态 -->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <!--外存储写权限,构建语法需要用到此权限 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

2、新建 IatActivity 声明必要的类成员

private static String TAG = "IatActivity";
// 语音听写对象
private SpeechRecognizer mIat;
// 语音听写UI
private RecognizerDialog mIatDialog;
// 听写结果内容
private EditText mResultText;
// 用HashMap存储听写结果
private HashMap<String, String> mIatResults = new LinkedHashMap<>();
private SharedPreferences mSharedPreferences;
private Toast mToast;
private String mEngineType = "cloud";

3、初始化监听

  private InitListener mInitListener = new InitListener() {

        @Override
        public void onInit(int code) {
            Log.d(TAG, "SpeechRecognizer init() code = " + code);
            if (code != ErrorCode.SUCCESS) {
                showTip("初始化失败,错误码:" + code + ",请点击网址https://www.xfyun.cn/document/error-code查询解决方案");
            }
        }
    };

4、语音命令识别的周期监听

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

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

        @Override
        public void onError(SpeechError error) {
            // Tips:
            // 错误码:10118(您没有说话),可能是录音机权限被禁,需要提示用户打开应用的录音权限。
            showTip(error.getPlainDescription(true));
        }

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

        @Override
        public void onResult(RecognizerResult results, boolean isLast) {

            String text = JsonParser.parseIatResult(results.getResultString());
            mResultText.append(text);

            mResultText.setSelection(mResultText.length());
            if (isLast) {
                //TODO 最后的结果
            }
        }

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

        @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_AUDIO_URL);
                Log.d(TAG, "session id =" + sid);
            }
        }
    };

5、离线命令UI监听

 private RecognizerDialogListener mRecognizerDialogListener = new RecognizerDialogListener() {
        public void onResult(RecognizerResult results, boolean isLast) {
            Log.d(TAG, "recognizer result:" + results.getResultString());

            /**
             *  识别出的语言类容就是 text 解析了的json 文本 在这里可以 发送蓝牙数据
             */
            String text = JsonParser.parseIatResult(results.getResultString());
            System.out.println("语音听写结果为:" + text);
            if(!text.equals("")){
                System.out.println("蓝牙发送了");
                bluetoothUtils.write("1");
            }
            mResultText.append(text);
            mResultText.setSelection(mResultText.length());
        }

        /**
         * 识别回调错误.
         */
        public void onError(SpeechError error) {
            showTip(error.getPlainDescription(true));
        }

    };

6、获取离线资源

在离线命令识别这里其实没有使用到的,因为.jet文件是语音发音包,就是官方提供的免费语音发音包

private String getResourcePath() {
    StringBuffer tempBuffer = new StringBuffer();
    //识别通用资源
    tempBuffer.append(ResourceUtil.generateResourcePath(this, ResourceUtil.RESOURCE_TYPE.assets, "iat/common.jet"));
    tempBuffer.append(";");
    tempBuffer.append(ResourceUtil.generateResourcePath(this, ResourceUtil.RESOURCE_TYPE.assets, "iat/sms_16k.jet"));
    //识别8k资源-使用8k的时候请解开注释
    return tempBuffer.toString();
}

7、参数设置

参数设置这块,主要是为命令识别功能做配置,就像使用springboot 一样需要在yml里面配置,这里只是举个例子,在讯飞这里,命令识别有参数设置,语音合成也有参数设置,需要区分好。

   /**
     * 参数设置
     *
     * @return
     */
    public void setParam() {
        // 清空参数
        mIat.setParameter(SpeechConstant.PARAMS, null);
        String lag = mSharedPreferences.getString("iat_language_preference", "mandarin");
        // 设置引擎
        mIat.setParameter(SpeechConstant.ENGINE_TYPE, mEngineType);
        // 设置返回结果格式
        mIat.setParameter(SpeechConstant.RESULT_TYPE, "json");

        //mIat.setParameter(MscKeys.REQUEST_AUDIO_URL,"true");

        //	this.mTranslateEnable = mSharedPreferences.getBoolean( this.getString(R.string.pref_key_translate), false );
        if (mEngineType.equals(SpeechConstant.TYPE_LOCAL)) {
            // 设置本地识别资源
            mIat.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
        }
        // 在线听写支持多种小语种,若想了解请下载在线听写能力,参看其speechDemo
        if (lag.equals("en_us")) {
            // 设置语言
            mIat.setParameter(SpeechConstant.LANGUAGE, "en_us");
            mIat.setParameter(SpeechConstant.ACCENT, null);

            // 设置语言
            mIat.setParameter(SpeechConstant.LANGUAGE, "zh_cn");
            // 设置语言区域
            mIat.setParameter(SpeechConstant.ACCENT, lag);
        }

        // 设置语音前端点:静音超时时间,即用户多长时间不说话则当做超时处理
        mIat.setParameter(SpeechConstant.VAD_BOS, mSharedPreferences.getString("iat_vadbos_preference", "4000"));

        // 设置语音后端点:后端点静音检测时间,即用户停止说话多长时间内即认为不再输入, 自动停止录音
        mIat.setParameter(SpeechConstant.VAD_EOS, mSharedPreferences.getString("iat_vadeos_preference", "1000"));

        // 设置标点符号,设置为"0"返回结果无标点,设置为"1"返回结果有标点
        mIat.setParameter(SpeechConstant.ASR_PTT, mSharedPreferences.getString("iat_punc_preference", "1"));

        // 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限
        mIat.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
        mIat.setParameter(SpeechConstant.ASR_AUDIO_PATH,
                getExternalFilesDir("msc").getAbsolutePath() + "/iat.wav");
    }

8、弹窗提示

/**
 * 弹窗提示
 * @param
 */
private void showTip(final String str) {
    if (mToast != null) {
        mToast.cancel();
    }
    mToast = Toast.makeText(getApplicationContext(), str, Toast.LENGTH_SHORT);
    mToast.show();
}

9、语音权限提示窗口

平常我们使用app 都会获取手机的权限,只有用户同意后才能使用app。这里就是这样的一个效果

/**
 * 语音权限提示窗口
 * @param
 */
private void showPrivacyDialog() {
    AppCompatTextView textView = new AppCompatTextView(this);
    textView.setPadding(100, 50, 100, 50);
    textView.setText(
            HtmlCompat.fromHtml("我们非常重视对您个人信息的保护,承诺严格按照讯飞开放平台<font color='#3B5FF5'>《隐私政策》</font>保护及处理您的信息,是否确定同意?",
                    HtmlCompat.FROM_HTML_MODE_LEGACY));
    textView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setData(Uri.parse("https://www.xfyun.cn/doc/policy/sdk_privacy.html"));
            startActivity(intent);
        }
    });
    AlertDialog dialog = new AlertDialog.Builder(this)
            .setTitle("温馨提示")
            .setView(textView)
            .setPositiveButton("同意", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    mSharedPreferences.edit().putBoolean(SpeechApp.PRIVACY_KEY, true).apply();
                    dialog.dismiss();
                }
            })
            .setNegativeButton("不同意", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    mSharedPreferences.edit().putBoolean(SpeechApp.PRIVACY_KEY, false).apply();
                    finish();
                    System.exit(0);
                }
            })
            .create();
    dialog.setCanceledOnTouchOutside(false);
    dialog.show();
}

10、动态申请权限

andriod 的高版本在AndroidManifest.xml 配置了权限还不够,还需要在 onCreate()中动态申请权限

注意 安装12以上(sdk 33 )需要这样写

 String permissions[] = {android.Manifest.permission.RECORD_AUDIO,
               android.Manifest.permission.ACCESS_NETWORK_STATE,
               android.Manifest.permission.INTERNET,
               android.Manifest.permission.WRITE_EXTERNAL_STORAGE
       };

/**
 * android 6.0 以上需要动态申请权限
 */
private void initPermission() {
    String permissions[] = {Manifest.permission.RECORD_AUDIO,
            Manifest.permission.ACCESS_NETWORK_STATE,
            Manifest.permission.INTERNET,
            Manifest.permission.WRITE_EXTERNAL_STORAGE
    };

    ArrayList<String> toApplyList = new ArrayList<String>();

    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);
    }

}

11、在onCreate() 方法中初始

 public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_iat);

        // 初始化识别无UI识别对象
        // 使用SpeechRecognizer对象,可根据回调消息自定义界面;
        mIat = SpeechRecognizer.createRecognizer(this, mInitListener);

        // 初始化听写Dialog,如果只使用有UI听写功能,无需创建SpeechRecognizer
        // 使用UI听写功能,请根据sdk文件目录下的notice.txt,放置布局文件和图片资源
        mIatDialog = new RecognizerDialog(this, mInitListener);
        mSharedPreferences = getSharedPreferences(IatSettings.PREFER_NAME, Activity.MODE_PRIVATE);
        mResultText = findViewById(R.id.tv_result));
        Button_Test();
    }

12、按钮点击 处理识别命令测试

    private void Button_Test(){
        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

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

                mResultText.setText(null);// 清空显示内容
                mIatResults.clear();
                // 设置参数
                setParam();
                boolean isShowDialog = mSharedPreferences.getBoolean(getString(R.string.pref_key_iat_show), true);
                if (isShowDialog) {
                    try {
                        // 显示听写对话框
                        mIatDialog.setListener(mRecognizerDialogListener);
                        mIatDialog.show(); // todo 对话框显示不出来一直报错
                        showTip(getString(R.string.text_begin));
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                } else {
                    // 不显示听写对话框
                    ret = mIat.startListening(mRecognizerListener);
                    if (ret != ErrorCode.SUCCESS) {
                        showTip("听写失败,错误码:" + ret + ",请点击网址https://www.xfyun.cn/document/error-code查询解决方案");
                    } else {
                        showTip(getString(R.string.text_begin));
                    }
                }
            }
        });
    }

13、界面activity_iat.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="93dp"
        android:layout_marginBottom="46dp"
        android:text="开始听写"
        app:layout_constraintBottom_toTopOf="@+id/tv_result"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/tv_result"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginBottom="493dp"
        android:layout_weight="1"
        android:gravity="center"
        android:text="语音识别到的内容"
        android:textColor="#000"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button" />

</androidx.constraintlayout.widget.ConstraintLayout>

14、strings.xml

该文件是讯飞官网下载后提供的,只要是一些公共的字符串,自己创建的项目大多数都没有用到,不过最好写上不然有的找不到报错(坑),可以将不要的删掉

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">讯飞蓝牙集成app</string>
    <!-- 请替换成在语音云官网申请的appid -->
    <string name="app_id">23810fb7</string>
    <string name="example_explain">本示例为讯飞语音Android平台开发者提供语音听写、语法识别、语义理解和语音合成等代码样例,旨在让用户能够依据该示例快速开发出基于语音接口的应用程序。</string>
    <string name="text_tts_source">
        科大讯飞作为智能语音技术提供商,在智能语音技术领域有着长期的研究积累,并在中文语音合成、语音识别、口语评测等多项技术上拥有技术成果。科大讯飞是我国以语音技术为产业化方向的“国家863计划产业化基地”.....
    </string>
    <string name="text_tts_source_en">iFLYTEK is a national key software enterprise dedicated to the research of intelligent speech and language technologies, development of software and chip products, provision of speech information services, and integration of E-government systems. The intelligent speech technology of iFLYTEK, the core technology of the company, represents the top level in the world.
    </string>
    <string name="text_isr_abnf_hint">\t上传内容为:\n\t#ABNF 1.0 gb2312;\n\tlanguage zh-CN;\n\tmode voice;\n\troot $main;\n\t$main = $place1 到$place2 ;\n\t$place1 = 北京 | 武汉 | 南京 | 天津 | 东京;\n\t$place2 = 上海 | 合肥;</string>
    <string name="text_understand_hint">\t您可以说:\n\t今天的天气怎么样?\n\t北京到上海的火车?\n\t来首歌吧?\n\n\t更多语义请登录:\n\thttp://aiui.xfyun.cn/ \n\t配置您的专属语义吧!</string>

    <!-- 听写 -->
    <string name="text_begin">请开始说话&#8230;</string>
    <string name="text_begin_recognizer">开始音频流识别</string>
    <string name="text_upload_contacts">上传联系人</string>
    <string name="text_upload_userwords">上传用户词表</string>
    <string name="text_upload_success">上传成功</string>
    <string name="text_userword_empty">词表下载失败或内容为空</string>
    <string name="text_download_success">下载成功</string>
    <string name="pref_key_iat_show">iat_show</string>
    <string name="pref_title_iat_show">显示听写界面</string>

    <string name="pref_key_translate">translate</string>
    <string name="pref_title_translate">翻译</string>

    <!-- 合成 -->
    <string-array name="voicer_cloud_entries">
        <item>小燕</item>
        <item>小宇</item>
        <item>凯瑟琳</item>
        <item>亨利</item>
        <item>玛丽</item>
        <item>小研</item>
        <item>小琪</item>
        <item>小峰</item>
        <item>小梅</item>
        <item>小莉</item>
        <item>小蓉</item>
        <item>小芸</item>
        <item>小坤</item>
        <item>小强 </item>
        <item>小莹</item>
        <item>小新</item>
        <item>楠楠</item>
        <item>老孙</item>
    </string-array>
    <string-array name="voicer_cloud_values">
        <item>xiaoyan</item>
        <item>xiaoyu</item>
        <item>catherine</item>
        <item>henry</item>
        <item>vimary</item>
        <item>vixy</item>
        <item>xiaoqi</item>
        <item>vixf</item>
        <item>xiaomei</item>
        <item>xiaolin</item>
        <item>xiaorong</item>
        <item>xiaoqian</item>
        <item>xiaokun</item>
        <item>xiaoqiang</item>
        <item>vixying</item>
        <item>xiaoxin</item>
        <item>nannan</item>
        <item>vils</item>
    </string-array>
    <string-array name="voicer_xtts_entries">
        <item>nannan</item>
        <item>小关</item>
        <item>宜丰</item>
        <item>小曦</item>
        <item>小燕</item>
        <!--  <item>小峰</item>-->
    </string-array>
    <string-array name="voicer_xtts_values">
        <item>nannan</item>
        <item>xiaoguan</item>
        <item>yifeng</item>
        <item>xiaoxi</item>
        <item>xiaoyan</item>
        <!--   <item>xiaofeng</item>-->
    </string-array>
    <string-array name="voicer_local_entries">
        <item>小燕</item>
        <item>小峰</item>

    </string-array>
    <string-array name="voicer_local_values">
        <item>xiaoyan</item>
        <item>xiaofeng</item>
    </string-array>
    <string-array name="stream_entries">
        <item>通话</item>
        <item>系统</item>
        <item>铃声</item>
        <item>音乐</item>
        <item>闹铃</item>
        <item>通知</item>
    </string-array>
    <string-array name="stream_values">
        <item>0</item>
        <item>1</item>
        <item>2</item>
        <item>3</item>
        <item>4</item>
        <item>5</item>
    </string-array>
    <string name="tts_toast_format" formatted="false">缓冲进度为%d%%,播放进度为%d%%</string>
    <!-- 语言 -->
    <string-array name="language_entries">
        <item>普通话</item>
        <item>粤语</item>
        <item>英语</item>
    </string-array>
    <string-array name="language_values">
        <item>mandarin</item>
        <item>cantonese</item>
        <item>en_us</item>
    </string-array>

    <!-- 标点符号 -->
    <string-array name="punc_entries">
        <item>有标点</item>
        <item>无标点</item>
    </string-array>
    <string-array name="punc_values">
        <item>1</item>
        <item>0</item>
    </string-array>

    <!-- 唤醒 -->
    <string name="example_explain_wake">唤醒是指通过说出特定的唤醒词来唤醒处于休眠状态下的设备,又分为唤醒和唤醒+识别。</string>
    <string name="wake_demo_hint">请点击“开始唤醒”后读出您在开放平台购买的唤醒词,当引擎计算得分大于您设置的门限值时,即可进行唤醒。</string>
    <string name="oneshot_demo_hint">请点击“唤醒+识别”后读出您在开放平台购买的唤醒词+语法中指定的识别对象,当引擎计算得分大于您设置的门限值时,即可进行唤醒+识别。</string>
    <string name="oneshot_resource_hint">本示例识别词语为“张三|李四|张海洋”,请开发者将语法文件中的唤醒词字段替换,本地语法定义详见demo工程的asset/wake.bnf文件。</string>
</resources>

测试效果

请添加图片描述

博主打算将语音和蓝牙整合,通过语音命令发送蓝牙指令,如果想了解andriod蓝牙请参考以下线观文章

完整代码仓库:茶凡_Matrix/Bluetooth_Send - 码云 - 开源中国 (gitee.com)

蓝牙开发博客: andriod12(sdk33)以上整合蓝牙app_茶凡_Matrix的博客-CSDN博客

Logo

助力合肥开发者学习交流的技术社区,不定期举办线上线下活动,欢迎大家的加入

更多推荐