上一篇的地址手把手的操作——用java调用科大讯飞的离线语音识别dll实现离线识别(JNA实现)(一)
上一篇讲到了最难的地方,参数的转换,这里单独写出来
**
三、参数的转换(难点)
**
注:本文是以讯飞提供的C语言例子作为模板改写,语音来源于文件
1、先分析提供的例子
本人使用的是VS2010
下载链接链接:https://pan.baidu.com/s/1CZX3k6nhsbLkuzB3mocyww 提取码:6r5g
2.45G大小,需要安装一段时间,因为用JNA只用看,这个版本够了
【为了给我一样对C/C++不了解的初阶,大神轻喷】
运行之后
【文件】–【打开】–【项目/解决方案】–自己找文件位置–【asr_offline_sample.vcxproj】
同样的方法打开头文件
【文件】–【打开】–【文件】–自己找文件位置–【头文件.h结尾的】
主要看例子asr_sample.c
在java中主要就是将这个文件进行改写,所以要先看懂
我自己看了很久,讲下结构吧:
上面几个是各项功能的函数,最下面是主函数main,跟java的结构很像吧
几大主要功能
【登录】
【建立语法】
【建立词典】
【语音识别】
【退出】
看上去很简单啊,接下来就是一个一个在java中实现
我先建立了一个lib接口,里面放调用的东西
(先把动态库加载进来)
public interface VoiceLib extends Library{
VoiceLib instance = (VoiceLib)Native.loadLibrary("msc_x64", VoiceLib.class);
}
#1登录
这个功能在(一)中已经有例子了,这里重写一遍就好
public interface VoiceLib extends Library{
VoiceLib instance = (VoiceLib)Native.loadLibrary("msc_x64", VoiceLib.class);
int MSPLogin(String usr, String pwd, String params);
}
新建主类xMsc
改写登录功能
public static void main(String[] args) {
String login_config = "appid=5ba4***"; //登录参数
UserData asr_data=null ;
int ret = 0;
ret = VoiceLib.instance.MSPLogin(null, null, login_config);
//第一个参数为用户名,第二个参数为密码,传null即可,第三个参数是登录参数
if (MSP_SUCCESS!= ret) {
System.out.println("登录失败!");
exit();
}
}
上面先改写下几个常量:
public class xMsc {
public static final int SAMPLE_RATE_16K = 16000;
public static final int SAMPLE_RATE_8K = 8000;
public static final int MAX_GRAMMARID_LEN = 32;
public static final int MAX_PARAMS_LEN = 1024;
public static final int MSP_SUCCESS = 0;
public static final int MSP_FAILED = 1;
private static int MSP_AUDIO_SAMPLE_FIRS = 1;
private static int MSP_AUDIO_SAMPLE_CONTINUE = 2;
private static int MSP_EP_LOOKING_FOR_SPEECH = 0;
private static int MSP_REC_STATUS_INCOMPLETE = 2;
private static int MSP_EP_AFTER_SPEECH = 3;
private static int MSP_AUDIO_SAMPLE_LAST = 4;
private static int MSP_REC_STATUS_COMPLETE = 5;
private static File f_pcm = null;
private static byte[] pcm_data =null;
private static int errcode = -1;
private static String session_id = "";
private static IntByReference ep_status = new IntByReference(MSP_EP_LOOKING_FOR_SPEECH);
private static IntByReference rec_status = new IntByReference(MSP_REC_STATUS_INCOMPLETE);
private static IntByReference rss_status1 = new IntByReference(MSP_AUDIO_SAMPLE_FIRS);
private static IntByReference err = new IntByReference(errcode);
public static final String ASR_RES_PATH = "fo|common.jet"; // 离线语法识别资源路径
public static final String GRM_BUILD_PATH = "./msc/GrmBuilld_x64"; // 构建离线语法识别网络生成数据保存路径
public static final String GRM_FILE = "./source/szx.bnf"; // 构建离线识别语法网络所用的语法文件
public static final String LEX_NAME = "contact"; // 更新离线识别语法的contact槽(语法文件为此示例中使用的call.bnf)
public static class UserData {
public static boolean build_fini; //标识语法构建是否完成
public static boolean update_fini; //标识更新词典是否完成
public static int errcode; //记录语法构建或更新词典回调错误码
public static String grammar_id; //保存语法构建返回的语法ID
};
里面的路径必须要跟我一样,尤其是 ASR_RES_PATH ,前面必须加"fo|"
有几个文件要拷贝过来啊,语法文件之类,我建立了一个source文件夹,都放里面了
现在的结构
【先一口气把main里面的全部改写完吧!】
public static void main(String[] args) {
String login_config = "appid=5ba4*****"; //登录参数,要改成自己的
asr_data = new UserData();
int ret = 0;
ret = VoiceLib.instance.MSPLogin(null, null, login_config); // 第一个参数为用户名,第二个参数为密码,传null即可,第三个参数是登录参数
if (MSP_SUCCESS != ret) {
System.out.println("登录失败!");
exit();
}
System.out.println("登录成功!");
System.out.println("构建离线识别语法网络...\n");
ret = build_grammar(asr_data); // 第一次使用某语法进行识别,需要先构建语法网络,获取语法ID,之后使用此语法进行识别,无需再次构建
if (MSP_SUCCESS != ret) {
System.out.println("构建语法调用失败!\n");
exit();
}
while (MSP_FAILED != asr_data.build_fini) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (MSP_SUCCESS != asr_data.errcode) {
exit();
}
System.out.println("离线识别语法网络构建完成,开始识别...\n");
ret = run_asr(asr_data);
if (MSP_SUCCESS != ret) {
System.out.println("离线语法识别出错: \n");
exit();
}
}
【注】词典我删掉了,和构建语法一样,需要的自己写喽
¥¥¥¥¥退出功能先封装一下¥¥¥¥¥¥
public static void exit(){
VoiceLib.instance.MSPLogout();
System.out.println("请按任意键退出...\n");
}
【再来一口气】把要用的函数全部封装到VoiceLib中,等下直接调用
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import com.szxinfo.recognizer.CallbackUtil.GrammarCallBack;
public interface VoiceLib extends Library{
String filePath = "mscx64";
VoiceLib instance = (VoiceLib)Native.loadLibrary(filePath, VoiceLib.class);
int MSPLogin(String usr, String pwd, String params);
int QISRBuildGrammar (String cs2, String grm_content, int grm_cnt_len, String grm_build_params, GrammarCallBack grammarCallBack, Pointer pointer);
String QISRSessionBegin(String grammarList, String asr_params, IntByReference errcode);
int QISRAudioWrite(String session_id, byte[] every, int waveLen, int audioStatus, IntByReference ep_status1, IntByReference rec_status1);
String QISRGetResult(String session_id, IntByReference rss_status1, int waitTime, IntByReference err);
int QISRSessionEnd(String sessionID, String hints);
// int GrammarCallBack(int ecode, String info, UserData udata);
int MSPLogout();
}
【这里的参数我都已经改好啦,等下到具体功能再解释】
然后写语法的
//重要分割线*********************************//
在讯飞提供的例子中,有一个回调函数和一个构建语法函数,都要改写,怎么办呢?
回调函数我封装到一个Util中(后面的词典回调一起了)
package com.xinzhi;
import com.sun.jna.Callback;
import com.sun.jna.Pointer;
public class CallbackUtil {
//语法回调函数的接口
public static interface GrammarCallBack extends Callback{
int build_grm_cb(int ecode, String info, Pointer udata);
}
//语法回调函数的实现
public static class GrammarCallBack_Realize implements GrammarCallBack{
@Override
public int build_grm_cb(int ecode, String info,Pointer udata) {
UserData g_udata=Util.fromPointer(udata, 0);
if (null != g_udata) {
xMsc.asr_data.build_fini = com.szxinfo.recognizer.xMsc.MSP_FAILED;
xMsc.asr_data.errcode = ecode;
}
if (com.szxinfo.recognizer.xMsc.MSP_SUCCESS == ecode && null != info) {
System.out.println("构建语法成功! 语法ID:"+info);
if (null != g_udata) {
xMsc.asr_data.grammar_id=info;
}
}
else {
System.out.println("构建语法失败!错误代码:"+ ecode);
}
return 0;
}
}
}
里面还有错误啊,回头来改!
【终板ps】这里面的错误已经全部修改完成
其中void,我使用Pointer来模拟*
再来写语法的
//构建离线识别语法网络
@SuppressWarnings("finally")
public static int build_grammar(UserData asr_data2) {
File grm_file = null;
String grm_content = null;
int grm_cnt_len = 0;
String grm_build_params = null;
int ret = MSP_SUCCESS;
grm_file = new File(GRM_FILE);
if (!grm_file.exists()) {
System.out.println("打开语法文件失败!");
return MSP_FAILED;
}
// 按照字节读取语法文件,并将字节添加到grm_content中
try {
FileInputStream in = new FileInputStream(grm_file);
InputStreamReader input = new InputStreamReader(in);
char[] cbuf = new char[(int) grm_file.length()];
@SuppressWarnings("unused")
int data=0;
data = input.read(cbuf);
// 构建语法的内容传的是字符串,将char[]转成String
StringBuilder builder = new StringBuilder();
for (char c : cbuf) {
builder.append(c);
}
grm_content = builder.toString();
// 释放资源
in.close();
input.close();
grm_file = null;
// 初始化构建语法参数
grm_build_params = "engine_type=local,asr_res_path=" + ASR_RES_PATH
+ ",sample_rate=" + SAMPLE_RATE_16K
+ ",grm_build_path=" + GRM_BUILD_PATH + ",";
// 实例化回调函数对象
CallbackUtil.GrammarCallBack callback = new CallbackUtil.GrammarCallBack_Realize();
// 语法长度为内容的长度
grm_cnt_len = grm_content.length();
// 传入参数,构建语法
ret = VoiceLib.instance.QISRBuildGrammar("bnf", grm_content, grm_cnt_len, grm_build_params, callback,
asr_data2.getPointer());
} catch (Exception e) {
e.printStackTrace();
} finally {
return ret;
}
}
QISRBuildGrammar(“bnf”, grm_content, grm_cnt_len, grm_build_params, callback, udata1);
主要是给这个函数配参数,现在还没有测通,有测通的告诉我一声!
【终板ps】已经全部测通!这里得到的语法id必须是call,如果你是一串字符串,说明读的不对!
解释:
第一个 是文件类型,不用说固定的
第二个 是语法内容,我用了文件缓冲读字符,按行读取字符,注意用的是StringBuilder,因为要拼接,你直接用String卡死了不要找我哈。
第三个 是文件长度,length一下就好
第四个 是参数,这里初始化直接赋值就好,注意字符串凭借的引号问题
第五个就是回调函数
第六个是那个无定向指针
现在报参数无效,错误码23002,估计是后面两个还不行,需要继续调整,知道的指导一下,我继续调试了!
【终板PS】没有正确的读call.bnf,终板更新已解决
最后是识别,这里坑太多,不会的下面留言
// 进行离线语法识别
public static int run_asr(UserData udata) {
String asr_params;
String rec_rslt = null;
String asr_audiof = null;
long pcm_count = 0;
long pcm_size;
@SuppressWarnings("unused")
int last_audio = 0;
int aud_stat = 1;
asr_audiof = get_audio_file();
f_pcm = new File(asr_audiof);
if (!f_pcm.exists()) {
System.out.println("打开语音文件失败!\n");
run_error();
}
pcm_size = f_pcm.length();
try {
@SuppressWarnings("resource")
InputStream in = new FileInputStream(f_pcm);
pcm_data = new byte[(int) pcm_size];
@SuppressWarnings("unused")
int n = 0;
n = in.read(pcm_data);
} catch (Exception e1) {
e1.printStackTrace();
}
// 离线语法识别参数设置
asr_params = "engine_type=local,asr_res_path=" + ASR_RES_PATH + ",sample_rate=" + SAMPLE_RATE_16K
+ ",grm_build_path=" + GRM_BUILD_PATH + ",local_grammar=" + udata.grammar_id
+ ",result_type=plain,result_encoding=GB2312,";
session_id = VoiceLib.instance.QISRSessionBegin(null, asr_params, err);
if (null == session_id) {
run_error();
}
System.out.println("开始识别...\n");
while (true) {
int len = 6400;
if (pcm_size < 12800) {
len = (int) pcm_size;
last_audio = 1;
}
aud_stat = MSP_AUDIO_SAMPLE_CONTINUE;// 0x02
if (0 == pcm_count) {
aud_stat = MSP_AUDIO_SAMPLE_FIRS;// 0x01
}
if (len <= 0) {
break;
}
System.out.print(">");
errcode = VoiceLib.instance.QISRAudioWrite(session_id, pcm_data, len, aud_stat, ep_status, rec_status);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (MSP_SUCCESS != errcode) {
run_error();
break;
}
pcm_count += (long) len;
pcm_size -= (long) len;
// 检测到音频结束
if (MSP_EP_AFTER_SPEECH == ep_status.getValue()) {
break;
}
}
// 主动点击音频结束
VoiceLib.instance.QISRAudioWrite(session_id, null, 0, MSP_AUDIO_SAMPLE_LAST, ep_status, rec_status);
pcm_data = null;
// 获取识别结果
while (MSP_REC_STATUS_COMPLETE != rss_status1.getValue() && MSP_SUCCESS == err.getValue()) {
rec_rslt = VoiceLib.instance.QISRGetResult(session_id, rss_status1, 0, err);
}
System.out.println("\n识别结束:\n");
System.out.println("=============================================================\n");
if (null != rec_rslt) {
String str = rec_rslt.substring(rec_rslt.indexOf("input=") + 6);
System.out.println("识别结果为:\n" + str);
} else {
System.out.println("没有识别结果!");
System.out.println("=============================================================\n");
run_exit();
}
return MSP_SUCCESS;
}
附加几个小方法:
public static void run_error() {
if (null != pcm_data) {
pcm_data = null;
}
if (null != f_pcm) {
f_pcm = null;
}
}
public static int run_exit() {
VoiceLib.instance.QISRSessionEnd(session_id, null);
return errcode;
}
至此,全部完工!
识别结果如下:
代码是plain解析的,截图是xml的,改下参数就好,编码别动,GB2312,不要utf-8