讯飞科技的语音技术,个人认为非常强大,但是技术文档的介绍,还是有些不完全,也没有在线的技术客服,相信有很多朋友都会遇到大大小小的问题,第三方SDK的集成,或者集成后方法的调用等。
废话不多说,这里我先介绍下最近我使用的声纹识别技术——自由说(文本密码,数字密码也会介绍到),本人也不是技术大牛,如果有错误,各位请指点,当然如有不清晰的地方也可在评论区提问。
工具:AndroidStudio
第一步:注册讯飞账号
注册账号——创建新应用——立即开通(开通需要的服务)—— 选中对应SDK、平台、你创建的应用
第二步 集成SDK
SDK文件
把其中的libs文件中的所有文件全部拖到 并且分别把这两个Msc.jar Sunflower.jar (右键)add as library 。注意如果项目中有对不同机型的配置文件 那么需要把armeabi arm64-v8a 等里面的.so文件分别复制到对应的文件夹中。 (这里我的项目没有把sunflower.jar 引入)
最好在build.gradle里面加上一句话
sourceSets{
main{
jniLibs.srcDirs=['libs']
}
}
第三步 方法调用
直接粘代码,细节部位,看注释,这里给大家一个运行顺序(注意 这里官网给的Demo并没有自由说的设置,我这Demo在官网的基础上进行改动,详情看注释)
这里重视下 自由说
大概步骤:
SpeechUtility的初始化
——布局组件的初始化
——(这里开始进入声纹验证的顺序)
——设置选择的注册密码类型(文本型,数字型,自由说型)
——获取与密码类对应的密码(注:自由说就不需要这一步,粗暴点说不需要点击“获得密码”这一按钮。 文本密码,只支持倒霉的 “芝麻开门”,不支持自定义,好傻。 数字密码没什么说的。)
——注册,也就是开始录取你的声音进行储存。
——验证,开始验证现在的声音是不是已注册的声音,根据调用result的参数完成相关操作
import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; import android.media.MediaRecorder; import android.os.Bundle; import android.os.Environment; import android.text.TextUtils; import android.util.Log; import android.view.Gravity; 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.TextView; import android.widget.Toast; import com.iflytek.cloud.ErrorCode; import com.iflytek.cloud.InitListener; import com.iflytek.cloud.SpeakerVerifier; import com.iflytek.cloud.SpeechConstant; import com.iflytek.cloud.SpeechError; import com.iflytek.cloud.SpeechListener; import com.iflytek.cloud.SpeechUtility; import com.iflytek.cloud.VerifierListener; import com.iflytek.cloud.VerifierResult; /**VerifierResult 返回字段说明(重要的几个 其他的什么vid trs。。。滚走) * * sst 业务类型 train 或者 verify * ret 返回值 0成功 -1失败 * suc 本次注册已成功的train次数 * rgn 本次注册需要的次数 * err 注册/验证返回的错误码 * score 当前声纹相似度 */ public class DetailActivity extends Activity implements OnClickListener { private static final String TAG = DetailActivity.class.getSimpleName(); private static final int PWD_TYPE_TEXT = 1; private static final int PWD_TYPE_FREE = 2; private static final int PWD_TYPE_NUM = 3; // 当前声纹密码类型,1、2、3分别为文本、自由说和数字密码 private int mPwdType = PWD_TYPE_TEXT; // 声纹识别对象 private SpeakerVerifier mVerifier; // 声纹AuthId,用户在云平台的身份标识,也是声纹模型的标识 // 请使用英文字母或者字母和数字的组合,勿使用中文字符 private String mAuthId = ""; // 文本声纹密码 private String mTextPwd = ""; // 数字声纹密码 private String mNumPwd = ""; // 数字声纹密码段,默认有5段 private String[] mNumPwdSegs; private EditText mResultEditText; private TextView mAuthIdTextView; private RadioGroup mPwdTypeGroup; private TextView mShowPwdTextView; private TextView mShowMsgTextView; private TextView mShowRegFbkTextView; private TextView mRecordTimeTextView; private AlertDialog mTextPwdSelectDialog; private Toast mToast; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); SpeechUtility.createUtility(this, "appid="+"你在官网的id"); setContentView(R.layout.activity_main); //1111111111111111111111111111111111111111111111111111 initUi(); // 每个应用不同的AuthId mAuthId = "uname"; mAuthIdTextView.setText(mAuthId); // 初始化SpeakerVerifier,InitListener为初始化完成后的回调接口 mVerifier = SpeakerVerifier.createVerifier(DetailActivity.this, new InitListener() { @Override public void onInit(int errorCode) { if (ErrorCode.SUCCESS == errorCode) { showTip("引擎初始化成功"); } else { showTip("引擎初始化失败,错误码:" + errorCode); } } }); } // @SuppressLint("ShowToast") 111111111111111111 private void initUi() { mResultEditText = (EditText) findViewById(R.id.edt_result); mAuthIdTextView = (TextView) findViewById(R.id.txt_authorid); mShowPwdTextView = (TextView) findViewById(R.id.showPwd); mShowMsgTextView = (TextView) findViewById(R.id.showMsg); mShowRegFbkTextView = (TextView) findViewById(R.id.showRegFbk); mRecordTimeTextView = (TextView) findViewById(R.id.recordTime); findViewById(R.id.isv_register).setOnClickListener(DetailActivity.this); findViewById(R.id.isv_verify).setOnClickListener(DetailActivity.this); findViewById(R.id.isv_stop_record).setOnClickListener(DetailActivity.this); findViewById(R.id.isv_cancel).setOnClickListener(DetailActivity.this); findViewById(R.id.isv_getpassword).setOnClickListener(DetailActivity.this); findViewById(R.id.isv_search).setOnClickListener(DetailActivity.this); findViewById(R.id.isv_delete).setOnClickListener(DetailActivity.this); // 密码选择RadioGroup初始化 mPwdTypeGroup = (RadioGroup) findViewById(R.id.radioGroup); 选择当前的mPwdType 这里重视下 自由说 mPwdTypeGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup group, int checkedId) { initTextView(); switch (checkedId) { case R.id.radioText: mPwdType = PWD_TYPE_TEXT; break; case R.id.radioNumber: mPwdType = PWD_TYPE_NUM; break; case R.id.radioFree: mPwdType = PWD_TYPE_FREE; break; default: break; } } }); mToast = Toast.makeText(DetailActivity.this, "", Toast.LENGTH_SHORT); mToast.setGravity(Gravity.BOTTOM|Gravity.CENTER_HORIZONTAL, 0, 0); } /** * 初始化TextView和密码文本 * ①4444444444444444444444444444444 ②44444444444444444 */ private void initTextView(){ mTextPwd = null; mNumPwd = null; mResultEditText.setText(""); mShowPwdTextView.setText(""); mShowMsgTextView.setText(""); mShowRegFbkTextView.setText(""); mRecordTimeTextView.setText(""); } /** * 设置radio的状态 */ private void setRadioClickable(boolean clickable){ // 设置RaioGroup状态为非按下状态 mPwdTypeGroup.setPressed(false); findViewById(R.id.radioText).setClickable(clickable); findViewById(R.id.radioNumber).setClickable(clickable); findViewById(R.id.radioFree).setClickable(clickable); } /** * 执行模型操作 * * @param operation 操作命令 * @param listener 操作结果回调对象 */ private void performModelOperation(String operation, SpeechListener listener) { // 清空参数 mVerifier.setParameter(SpeechConstant.PARAMS, null); //设置密码类型(就是咱要读的) mVerifier.setParameter(SpeechConstant.ISV_PWDT, "" + mPwdType); if (mPwdType == PWD_TYPE_TEXT) { // 文本密码删除需要传入密码 if (TextUtils.isEmpty(mTextPwd)) { showTip("请获取密码后进行操作"); return; } // 对于文本密码和数字密码,必须设置密码的文本内容,pwdText的取值为“芝麻开门”或者是从云端拉取的数字密码(每8位用“-”隔开,如“62389704-45937680-32758406-29530846-58206497”)。自由说略过此步 mVerifier.setParameter(SpeechConstant.ISV_PWD, mTextPwd); } else if (mPwdType == PWD_TYPE_NUM) { }else if (mPwdType==PWD_TYPE_FREE){ } setRadioClickable(false); // 设置auth_id,不能设置为空 mVerifier.sendRequest(operation, mAuthId, listener); } //5555555555555555555555555555555点击各个部件的监听回调 @Override public void onClick(View v) { if( !checkInstance() ){ return; } switch (v.getId()) { 第一步: case R.id.isv_getpassword: // 获取密码之前先终止之前的注册或验证过程 mVerifier.cancel(); initTextView(); setRadioClickable(false); // 清空参数 mVerifier.setParameter(SpeechConstant.PARAMS, null); mVerifier.setParameter(SpeechConstant.ISV_PWDT, "" + mPwdType); if (mPwdType!=PWD_TYPE_FREE) 本地的监听参数 mVerifier.getPasswordList(mPwdListenter); break; case R.id.isv_search: performModelOperation("que", mModelOperationListener); break; case R.id.isv_delete: performModelOperation("del", mModelOperationListener); break; 第二步 注册 case R.id.isv_register: // 清空参数 mVerifier.setParameter(SpeechConstant.PARAMS, null); mVerifier.setParameter(SpeechConstant.ISV_AUDIO_PATH, Environment.getExternalStorageDirectory().getAbsolutePath() + "/msc/test.pcm"); // 对于某些麦克风非常灵敏的机器,如nexus、samsung i9300等,建议加上以下设置对录音进行消噪处理 // mVerify.setParameter(SpeechConstant.AUDIO_SOURCE, "" + MediaRecorder.AudioSource.VOICE_RECOGNITION); if (mPwdType == PWD_TYPE_TEXT) { // 文本密码注册需要传入密码 if (TextUtils.isEmpty(mTextPwd)) { showTip("请获取密码后进行操作"); return; } mVerifier.setParameter(SpeechConstant.ISV_PWD, mTextPwd); mShowPwdTextView.setText("请读出:" + mTextPwd); mShowMsgTextView.setText("训练 第" + 1 + "遍,剩余4遍"); } else if (mPwdType == PWD_TYPE_NUM) { // 数字密码注册需要传入密码 if (TextUtils.isEmpty(mNumPwd)) { showTip("请获取密码后进行操作"); return; } mVerifier.setParameter(SpeechConstant.ISV_PWD, mNumPwd); ((TextView) findViewById(R.id.showPwd)).setText("请读出:" + mNumPwd.substring(0, 8)); mShowMsgTextView.setText("训练 第" + 1 + "遍,剩余4遍"); }else if (mPwdType==PWD_TYPE_FREE){ 这里插一句嘴,自由说的注册参数之次数 设置为“1” 音质的的设置“8000” mVerifier.setParameter(SpeechConstant.ISV_RGN,"1"); mVerifier.setParameter(SpeechConstant.SAMPLE_RATE,"8000"); } setRadioClickable(false); // 设置auth_id,不能设置为空 mVerifier.setParameter(SpeechConstant.AUTH_ID, mAuthId); // 设置业务类型为注册 mVerifier.setParameter(SpeechConstant.ISV_SST, "train"); // 设置声纹密码类型 mVerifier.setParameter(SpeechConstant.ISV_PWDT, "" + mPwdType); // 开始注册 mVerifier.startListening(mRegisterListener); break; 第三步验证,与上一步注册极其相似,详情请看 case R.id.isv_verify: // 清空提示信息 ((TextView) findViewById(R.id.showMsg)).setText(""); // 清空参数 mVerifier.setParameter(SpeechConstant.PARAMS, null); mVerifier.setParameter(SpeechConstant.ISV_AUDIO_PATH, Environment.getExternalStorageDirectory().getAbsolutePath() + "/msc/verify.pcm"); mVerifier = SpeakerVerifier.getVerifier(); // 设置业务类型为验证 mVerifier.setParameter(SpeechConstant.ISV_SST, "verify"); // 对于某些麦克风非常灵敏的机器,如nexus、samsung i9300等,建议加上以下设置对录音进行消噪处理 // mVerify.setParameter(SpeechConstant.AUDIO_SOURCE, "" + MediaRecorder.AudioSource.VOICE_RECOGNITION); if (mPwdType == PWD_TYPE_TEXT) { // 文本密码注册需要传入密码 if (TextUtils.isEmpty(mTextPwd)) { showTip("请获取密码后进行操作"); return; } mVerifier.setParameter(SpeechConstant.ISV_PWD, mTextPwd); ((TextView) findViewById(R.id.showPwd)).setText("请读出:" + mTextPwd); } else if (mPwdType == PWD_TYPE_NUM) { // 数字密码注册需要传入密码 String verifyPwd = mVerifier.generatePassword(8); mVerifier.setParameter(SpeechConstant.ISV_PWD, verifyPwd); ((TextView) findViewById(R.id.showPwd)).setText("请读出:" + verifyPwd); }else if (mPwdType==PWD_TYPE_FREE){ mVerifier.setParameter(SpeechConstant.SAMPLE_RATE,"8000"); ((TextView)findViewById(R.id.showPwd)).setText("请随便说些用于验证"); } setRadioClickable(false); // 设置auth_id,不能设置为空 mVerifier.setParameter(SpeechConstant.AUTH_ID, mAuthId); mVerifier.setParameter(SpeechConstant.ISV_PWDT, "" + mPwdType); // 开始验证 mVerifier.startListening(mVerifyListener); break; case R.id.isv_stop_record: mVerifier.stopListening(); break; case R.id.isv_cancel: setRadioClickable(true); mVerifier.cancel(); initTextView(); break; default: break; } } 第一步的监听参数: 通过解析获得密码,注意 这里自由说不需要密码,所以这里没有它的case private String[] items; private SpeechListener mPwdListenter = new SpeechListener() { @Override public void onEvent(int eventType, Bundle params) { } @Override public void onBufferReceived(byte[] buffer) { setRadioClickable(true); String result = new String(buffer); switch (mPwdType) { case PWD_TYPE_TEXT: try { JSONObject object = new JSONObject(result); if (!object.has("txt_pwd")) { initTextView(); return; } JSONArray pwdArray = object.optJSONArray("txt_pwd"); items = new String[pwdArray.length()]; for (int i = 0; i < pwdArray.length(); i++) { items[i] = pwdArray.getString(i); } mTextPwdSelectDialog = new AlertDialog.Builder( DetailActivity.this) .setTitle("请选择密码文本") .setItems(items, new DialogInterface.OnClickListener() { @Override public void onClick( DialogInterface arg0, int arg1) { mTextPwd = items[arg1]; mResultEditText.setText("您的密码:" + mTextPwd); } }).create(); mTextPwdSelectDialog.show(); } catch (JSONException e) { e.printStackTrace(); } break; case PWD_TYPE_NUM: StringBuffer numberString = new StringBuffer(); try { JSONObject object = new JSONObject(result); if (!object.has("num_pwd")) { initTextView(); return; } JSONArray pwdArray = object.optJSONArray("num_pwd"); numberString.append(pwdArray.get(0)); for (int i = 1; i < pwdArray.length(); i++) { numberString.append("-" + pwdArray.get(i)); } } catch (JSONException e) { e.printStackTrace(); } mNumPwd = numberString.toString(); mNumPwdSegs = mNumPwd.split("-"); mResultEditText.setText("您的密码:\n" + mNumPwd); break; default: break; } } @Override public void onCompleted(SpeechError error) { setRadioClickable(true); if (null != error && ErrorCode.SUCCESS != error.getErrorCode()) { showTip("获取失败:" + error.getErrorCode()); } } }; private SpeechListener mModelOperationListener = new SpeechListener() { @Override public void onEvent(int eventType, Bundle params) { } @Override public void onBufferReceived(byte[] buffer) { setRadioClickable(true); String result = new String(buffer); try { JSONObject object = new JSONObject(result); String cmd = object.getString("cmd"); int ret = object.getInt("ret"); if ("del".equals(cmd)) { if (ret == ErrorCode.SUCCESS) { showTip("删除成功"); mResultEditText.setText(""); } else if (ret == ErrorCode.MSP_ERROR_FAIL) { showTip("删除失败,模型不存在"); } } else if ("que".equals(cmd)) { if (ret == ErrorCode.SUCCESS) { showTip("模型存在"); } else if (ret == ErrorCode.MSP_ERROR_FAIL) { showTip("模型不存在"); } } } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Override public void onCompleted(SpeechError error) { setRadioClickable(true); if (null != error && ErrorCode.SUCCESS != error.getErrorCode()) { showTip("操作失败:" + error.getPlainDescription(true)); } } }; 第三步 监听参数 private VerifierListener mVerifyListener = new VerifierListener() { @Override public void onVolumeChanged(int volume, byte[] data) { showTip("当前正在说话,音量大小:" + volume); Log.d(TAG, "返回音频数据:"+data.length); } @Override public void onResult(VerifierResult result) { setRadioClickable(true); mShowMsgTextView.setText(result.source); if (result.ret == 0) { // 验证通过 这里就意味着通过了!!! mShowMsgTextView.setText("验证通过,打开****"); } else{ // 验证不通过 switch (result.err) { case VerifierResult.MSS_ERROR_IVP_GENERAL: mShowMsgTextView.setText("内核异常"); break; case VerifierResult.MSS_ERROR_IVP_TRUNCATED: mShowMsgTextView.setText("出现截幅"); break; case VerifierResult.MSS_ERROR_IVP_MUCH_NOISE: mShowMsgTextView.setText("太多噪音"); break; case VerifierResult.MSS_ERROR_IVP_UTTER_TOO_SHORT: mShowMsgTextView.setText("录音太短"); break; case VerifierResult.MSS_ERROR_IVP_TEXT_NOT_MATCH: mShowMsgTextView.setText("验证不通过,您所读的文本不一致"); break; case VerifierResult.MSS_ERROR_IVP_TOO_LOW: mShowMsgTextView.setText("音量太低"); break; case VerifierResult.MSS_ERROR_IVP_NO_ENOUGH_AUDIO: mShowMsgTextView.setText("音频长达不到自由说的要求"); break; default: mShowMsgTextView.setText("验证不通过,相似度仅为"+result.score+"%。"); break; } } } // 保留方法,暂不用 @Override public void onEvent(int eventType, int arg1, int arg2, Bundle arg3) { // 以下代码用于获取与云端的会话id,当业务出错时将会话id提供给技术支持人员,可用于查询会话日志,定位出错原因 // if (SpeechEvent.EVENT_SESSION_ID == eventType) { // String sid = obj.getString(SpeechEvent.KEY_EVENT_SESSION_ID); // Log.d(TAG, "session id =" + sid); // } } @Override public void onError(SpeechError error) { setRadioClickable(true); switch (error.getErrorCode()) { case ErrorCode.MSP_ERROR_NOT_FOUND: mShowMsgTextView.setText("模型不存在,请先注册"); break; default: showTip("onError Code:" + error.getPlainDescription(true)); break; } } @Override public void onEndOfSpeech() { // 此回调表示:检测到了语音的尾端点,已经进入识别过程,不再接受语音输入 showTip("结束说话"); } @Override public void onBeginOfSpeech() { // 此回调表示:sdk内部录音机已经准备好了,用户可以开始语音输入 showTip("开始说话"); } }; 第二步的监听参数,通过调用result的参数获取结果 private VerifierListener mRegisterListener = new VerifierListener() { @Override public void onVolumeChanged(int volume, byte[] data) { showTip("当前正在说话,音量大小:" + volume); Log.d(TAG, "返回音频数据:"+data.length); } @Override public void onResult(VerifierResult result) { ((TextView)findViewById(R.id.showMsg)).setText(result.source); if (result.ret == ErrorCode.SUCCESS) { switch (result.err) { case VerifierResult.MSS_ERROR_IVP_GENERAL: mShowMsgTextView.setText("内核异常"); break; case VerifierResult.MSS_ERROR_IVP_EXTRA_RGN_SOPPORT: mShowRegFbkTextView.setText("训练达到最大次数"); break; case VerifierResult.MSS_ERROR_IVP_TRUNCATED: mShowRegFbkTextView.setText("出现截幅"); break; case VerifierResult.MSS_ERROR_IVP_MUCH_NOISE: mShowRegFbkTextView.setText("太多噪音"); break; case VerifierResult.MSS_ERROR_IVP_UTTER_TOO_SHORT: mShowRegFbkTextView.setText("录音太短"); break; case VerifierResult.MSS_ERROR_IVP_TEXT_NOT_MATCH: mShowRegFbkTextView.setText("训练失败,您所读的文本不一致"); break; case VerifierResult.MSS_ERROR_IVP_TOO_LOW: mShowRegFbkTextView.setText("音量太低"); break; case VerifierResult.MSS_ERROR_IVP_NO_ENOUGH_AUDIO: mShowMsgTextView.setText("音频长达不到自由说的要求"); default: mShowRegFbkTextView.setText(""); break; } if (result.suc == result.rgn) { setRadioClickable(true); mShowMsgTextView.setText("注册成功"); if (PWD_TYPE_TEXT == mPwdType) { mResultEditText.setText("您的文本密码声纹ID:\n" + result.vid); Log.e(TAG, "onResult:文字 "+result.vid ); } else if (PWD_TYPE_NUM == mPwdType) { mResultEditText.setText("您的数字密码声纹ID:\n" + result.vid); Log.e(TAG, "onResult:数字 "+result.vid ); }else if (mPwdType==PWD_TYPE_FREE){ mResultEditText.setText("您的数字密码声纹ID:\n" + result.vid); Log.e(TAG, "onResult: 自由"+result.vid ); } } else { int nowTimes = result.suc + 1; int leftTimes = result.rgn - nowTimes; if (PWD_TYPE_TEXT == mPwdType) { mShowPwdTextView.setText("请读出:" + mTextPwd); } else if (PWD_TYPE_NUM == mPwdType) { mShowPwdTextView.setText("请读出:" + mNumPwdSegs[nowTimes - 1]); } mShowMsgTextView.setText("训练 第" + nowTimes + "遍,剩余" + leftTimes + "遍"); } }else { setRadioClickable(true); mShowMsgTextView.setText("注册失败,请重新开始。"); } } // 保留方法,暂不用 @Override public void onEvent(int eventType, int arg1, int arg2, Bundle arg3) { // 以下代码用于获取与云端的会话id,当业务出错时将会话id提供给技术支持人员,可用于查询会话日志,定位出错原因 // if (SpeechEvent.EVENT_SESSION_ID == eventType) { // String sid = obj.getString(SpeechEvent.KEY_EVENT_SESSION_ID); // Log.d(TAG, "session id =" + sid); // } } @Override public void onError(SpeechError error) { setRadioClickable(true); if (error.getErrorCode() == ErrorCode.MSP_ERROR_ALREADY_EXIST) { showTip("模型已存在,如需重新注册,请先删除"); } else { showTip("onError Code:" + error.getPlainDescription(true)); } } @Override public void onEndOfSpeech() { showTip("结束说话"); } @Override public void onBeginOfSpeech() { showTip("开始说话"); } }; @Override public void finish() { if (null != mTextPwdSelectDialog) { mTextPwdSelectDialog.dismiss(); } super.finish(); } @Override protected void onDestroy() { if (null != mVerifier) { mVerifier.stopListening(); mVerifier.destroy(); } super.onDestroy(); } private void showTip(final String str) { mToast.setText(str); mToast.show(); } private boolean checkInstance(){ if( null == mVerifier ){ // 创建单例失败,与 21001 错误为同样原因,参考 http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=9688 this.showTip( "创建对象失败,请确认 libmsc.so 放置正确,\n 且有调用 createUtility 进行初始化" ); return false; }else{ return true; } } }
这里插一句,关于mPwdType==自由说的情况下,一定要注意mVerifier的参数设置,否则会出现错误参数,错误参数值的错误。
以下几个步骤,大家看下