amr 音频录音必须也是单声道、音频8000频率。 录制和听写要一致。
单声道、音频是8000、将amr文件转换成pcm格式 以下是代码
//调用科大讯飞处理类解析语音文件 SpeechRecognizeHandle speechRecognizeHandle = new SpeechRecognizeHandle(“音频压缩包地址”, ”我这里是IM消息id“, ”这里是IM会话id 你们可以不用传“, new CloudCallBack() { @Override public void onSuccess(Object obj) { }}
package com.minxing.kit.internal.common.util.speech;
import android.content.Context;
import android.media.MediaFormat;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
import com.arthenica.mobileffmpeg.Config;
import com.arthenica.mobileffmpeg.FFmpeg;
import com.gt.base.utils.KLog;
import com.gt.library_cloud_sdklib.utils.CloudCallBack;
import com.iflytek.cloud.ErrorCode;
import com.iflytek.cloud.InitListener;
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.SpeechUtility;
import com.minxing.kit.MXKit;
import com.minxing.kit.core.concurrent.ThreadPoolManager;
import com.minxing.kit.internal.common.bean.im.ConversationMessage;
import com.minxing.kit.internal.common.util.AESUtil;
import com.minxing.kit.internal.common.util.Clip;
import com.minxing.kit.internal.common.util.FileUtils;
import com.minxing.kit.internal.common.util.PcmAmrToWavUtil;
import com.minxing.kit.utils.logutils.MXLog;
import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONTokener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocket;
/**
* 科大讯飞语音识别工具
*/
public abstract class SpeechRecognizeHandle {
private HashMap<Integer, Integer> callHashMap;
// 用HashMap存储听写结果
private HashMap<String, String> mIatResults = new LinkedHashMap<>();
private SpeechRecognizer mIat;
// 引擎类型
private String mEngineType = SpeechConstant.TYPE_CLOUD;
//解码转换
private AudioDecode audioDecode;
public SpeechRecognizeHandle(String filePath, int messageId, int conversationId, CloudCallBack callBack) {
ThreadPoolManager.getGlobalThreadPool().execute(new Runnable() {
@Override
public void run() {
try {
if (callHashMap == null) {
callHashMap = new HashMap<>();
}
int index = filePath.lastIndexOf(File.separator);
//得到存储文件名称和路径
String pcmFileName = filePath.substring(index, filePath.length()).replace("amr", "pcm");
String pcmFilePath = MXKit.getInstance().getKitConfiguration().getAppStoreHome() + "app_voice";
String pcmFileAbsolutlyPath = pcmFilePath + pcmFileName;
//检测本地是否存在已经解压后地文件
File outputFile = new File(pcmFileAbsolutlyPath);
boolean fileExist = FileUtils.exists(pcmFileAbsolutlyPath);
if (fileExist) {
File pcmFile = new File(pcmFileAbsolutlyPath);
String authUrl = WebIATWS.getAuthUrl(WebIATWS.hostUrl, WebIATWS.apiKey, WebIATWS.apiSecret);
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
String url = authUrl.toString().replace("http://", "ws://").replace("https://", "wss://");
Request request = new Request.Builder().url(url).build();
if (pcmFile != null) {// call.request().tag()
Call newCall = okHttpClient.newCall(request);
callHashMap.put(messageId, conversationId);
WebIATWS webIATWS = new WebIATWS(pcmFile, messageId, callHashMap, callBack);
okHttpClient.newWebSocket(request, webIATWS);
} else {
com.alibaba.fastjson.JSONObject jsonObject = new com.alibaba.fastjson.JSONObject();
jsonObject.put("messageId", messageId);
callBack.mxError(jsonObject.toJSONString());
}
return;
}
//进行解压转换、
new AmrToPcmConversionTask(messageId, conversationId, callBack, filePath, outputFile.getAbsolutePath()).execute();
// 敏行解压文件流方式
// if (oldWay(filePath, messageId, conversationId,callBack, pcmFileName, pcmFilePath, pcmFileAbsolutlyPath))
// return;
} catch (Exception e) {
com.alibaba.fastjson.JSONObject jsonObject = new com.alibaba.fastjson.JSONObject();
jsonObject.put("messageId", messageId);
callBack.mxError(jsonObject.toJSONString());
}
}
});
// audioDecodeFun(filePath);
// SpeechUtility.createUtility(context, "appid=" + "98e0c527");
// voice2words(filePath, context, callBack);
}
private class AmrToPcmConversionTask extends AsyncTask<String, Void, File> {
private int messageId;
private int conversationId;
public CloudCallBack callBack;
private String filePath;
private String pcmFilePath;
public AmrToPcmConversionTask(int messageId, int conversationId, CloudCallBack callback, String filePath, String pcmFilePath) {
this.messageId = messageId;
this.conversationId = conversationId;
this.callBack = callback;
this.filePath = filePath;
this.pcmFilePath = pcmFilePath;
}
@Override
protected File doInBackground(String... paths) {
// -i: 输入文件路径,指定需要转换的音频文件的路径。在这个例子中,变量 filePath 存储了输入文件的路径。
//
// -c:a: 指定音频编解码器,这里设置为 pcm_s16le,表示使用 16 位有符号的线性脉冲编码(PCM)进行编码。
//
// -ar: 设置音频采样率,这里设置为 8000 Hz。采样率表示每秒钟从音频流中提取的样本数。
//
// -ac: 设置音频通道数,这里设置为 1,表示单声道。对于语音文件而言,通常使用单声道即可。
//
// -f: 指定输出文件格式,这里设置为 s16le,表示输出格式为 16 位有符号的线性脉冲编码。在某些情况下,需要明确指定输出格式。
//
// 输出文件路径: 这里表示输出文件的路径,即转换后的音频文件将保存在 pcmFilePath 中。
//
// -v: 设置 FFmpeg 的日志级别为 debug,这会输出详细的调试信息,有助于调试和排查问题。
String[] cmd = {"-i", filePath, "-c:a", "pcm_s16le", "-ar", "8000", "-ac", "1", "-f", "s16le", pcmFilePath, "-v", "debug"};
int rc = FFmpeg.execute(cmd);
if (rc == Config.RETURN_CODE_SUCCESS) {
// 转换成功后返回输出的 PCM 文件的 File 对象
return new File(pcmFilePath);
}
return null;
}
@Override
protected void onPostExecute(File pcmFile) {
super.onPostExecute(pcmFile);
//转换完成地文件、 这个是我们地消息id messageId
try {
if (pcmFile == null) {
com.alibaba.fastjson.JSONObject jsonObject = new com.alibaba.fastjson.JSONObject();
jsonObject.put("messageId", messageId);
callBack.mxError(jsonObject.toJSONString());
// 转换失败的处理
String error = Config.getLastCommandOutput();
KLog.esLog("SpeechRecognizeHandle", "Error: " + error);
return;
}
//
String authUrl = WebIATWS.getAuthUrl(WebIATWS.hostUrl, WebIATWS.apiKey, WebIATWS.apiSecret);
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
//将url中的 schema http://和https://分别替换为ws:// 和 wss://
String url = authUrl.toString().replace("http://", "ws://").replace("https://", "wss://");
Request request = new Request.Builder().url(url).build();
if (pcmFile != null) {
Call newCall = okHttpClient.newCall(request);
callHashMap.put(messageId, conversationId);
WebIATWS webIATWS = new WebIATWS(pcmFile, messageId, callHashMap, callBack);
okHttpClient.newWebSocket(request, webIATWS);
} else {
com.alibaba.fastjson.JSONObject jsonObject = new com.alibaba.fastjson.JSONObject();
jsonObject.put("messageId", messageId);
callBack.mxError(jsonObject.toJSONString());
}
} catch (Exception exception) {
exception.getMessage();
com.alibaba.fastjson.JSONObject jsonObject = new com.alibaba.fastjson.JSONObject();
jsonObject.put("messageId", messageId);
callBack.mxError(jsonObject.toJSONString());
}
}
}
private void oldWay(String filePath, int messageId, int conversationId, CloudCallBack callBack, String pcmFileName, String pcmFilePath, String pcmFileAbsolutlyPath) throws Exception {
// SpeechUtility.createUtility(context, "appid=" + "98e0c527");
// audioDecodeFun(filePath);
try {
if (callHashMap == null) {
callHashMap = new HashMap<>();
}
// SpeechUtility.createUtility(context, "appid=" + "98e0c527");
// int index = filePath.lastIndexOf(File.separator);
// String pcmFileName = filePath.substring(index, filePath.length()).replace("amr", "pcm");
// String pcmFilePath = MXKit.getInstance().getKitConfiguration().getAppStoreHome() + File.separator + "app_voice";
// String pcmFileAbsolutlyPath = pcmFilePath + File.separator + pcmFileName;
boolean fileExist = FileUtils.exists(pcmFileAbsolutlyPath);
if (fileExist) {
File pcmFile = new File(pcmFileAbsolutlyPath);
String authUrl = WebIATWS.getAuthUrl(WebIATWS.hostUrl, WebIATWS.apiKey, WebIATWS.apiSecret);
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
String url = authUrl.toString().replace("http://", "ws://").replace("https://", "wss://");
Request request = new Request.Builder().url(url).build();
if (pcmFile != null) {// call.request().tag()
Call newCall = okHttpClient.newCall(request);
// callHashMap.put(messageId, newCall);
callHashMap.put(messageId, conversationId);
WebIATWS webIATWS = new WebIATWS(pcmFile, messageId, callHashMap, callBack);
okHttpClient.newWebSocket(request, webIATWS);
} else {
com.alibaba.fastjson.JSONObject jsonObject = new com.alibaba.fastjson.JSONObject();
jsonObject.put("messageId", messageId);
callBack.mxError(jsonObject.toJSONString());
}
return;
}
audioDecode = AudioDecode.newInstance();
audioDecode.setFilePath(filePath);
audioDecode.prepare(messageId, callBack);
String finalFilePath = filePath;
audioDecode.setOnCompleteListener(pcmData -> {
try {
if (TextUtils.isEmpty(finalFilePath) || pcmData == null || pcmData.isEmpty()) {
com.alibaba.fastjson.JSONObject jsonObject = new com.alibaba.fastjson.JSONObject();
jsonObject.put("messageId", messageId);
callBack.mxError(jsonObject.toJSONString());
return;
}
File pcmFile = FileUtils.bytesToFile(pcmData, pcmFilePath, pcmFileName);
// File pcmFile = new File(ootPut);
String authUrl = WebIATWS.getAuthUrl(WebIATWS.hostUrl, WebIATWS.apiKey, WebIATWS.apiSecret);
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
//将url中的 schema http://和https://分别替换为ws:// 和 wss://
String url = authUrl.toString().replace("http://", "ws://").replace("https://", "wss://");
Request request = new Request.Builder().url(url).build();
if (pcmFile != null) {
Call newCall = okHttpClient.newCall(request);
// callHashMap.put(messageId, newCall);
callHashMap.put(messageId, conversationId);
WebIATWS webIATWS = new WebIATWS(pcmFile, messageId, callHashMap, callBack);
okHttpClient.newWebSocket(request, webIATWS);
} else {
com.alibaba.fastjson.JSONObject jsonObject = new com.alibaba.fastjson.JSONObject();
jsonObject.put("messageId", messageId);
callBack.mxError(jsonObject.toJSONString());
}
} catch (Exception exception) {
exception.getMessage();
com.alibaba.fastjson.JSONObject jsonObject = new com.alibaba.fastjson.JSONObject();
jsonObject.put("messageId", messageId);
callBack.mxError(jsonObject.toJSONString());
}
});
audioDecode.startAsync();
} catch (Exception e) {
com.alibaba.fastjson.JSONObject jsonObject = new com.alibaba.fastjson.JSONObject();
jsonObject.put("messageId", messageId);
callBack.mxError(jsonObject.toJSONString());
}
}
public void voice2words(String filePath, Context context, CloudCallBack callBack) {
mIatResults.clear();
//1、创建SpeechRecognizer对象,第二个参数:本地识别时传InitListener
mIat = SpeechRecognizer.createRecognizer(context, mInitListener);
if (mIat == null) {
//防止SpeechRecognizer初始化失败崩溃
return;
}
// 设置音频来源为外部文件
mIat.setParameter(SpeechConstant.AUDIO_SOURCE, "-1");
mIat.setParameter(SpeechConstant.SAMPLE_RATE, "8000");//设置正确的采样率
setParam();
int ret = 0; // 函数调用返回值
ret = mIat.startListening(mRecognizerListener);
if (ret != ErrorCode.SUCCESS) {
} else {
//iatFun();//讯飞demo里面的方法
audioDecodeFun(filePath, callBack);
}
}
//听写监听器
private RecognizerListener mRecognizerListener = new RecognizerListener() {
//volume音量值0~30,data音频数据
@Override
public void onVolumeChanged(int volume, byte[] bytes) {
Log.e("MXSpeechRecognize", "==音量改变==" + volume);
}
//开始录音
// 此回调表示:sdk内部录音机已经准备好了,用户可以开始语音输入
@Override
public void onBeginOfSpeech() {
Log.e("MXSpeechRecognize", "==onBeginOfSpeech==");
}
//结束录音
@Override
public void onEndOfSpeech() {
Log.e("MXSpeechRecognize", "==onEndOfSpeech==");
}
/**
* 听写结果回调接口,返回Json格式结果
* 一般情况下会通过onResults接口多次返回结果,完整的识别内容是多次结果的累加
* isLast等于true时会话结束。
*/
@Override
public void onResult(RecognizerResult recognizerResult, boolean isLast) {
Log.e("MXSpeechRecognize", "==onResult==" + recognizerResult + " " + isLast);
printResult(recognizerResult, isLast);
}
//会话发生错误回调接口
// Tips:
// 错误码:10118(您没有说话),可能是录音机权限被禁,需要提示用户打开应用的录音权限。
@Override
public void onError(SpeechError error) {
Log.e("MXSpeechRecognize", "==onError==" + error.getErrorDescription() + " " + error.getErrorCode());
returnWords(null, true);
}
//扩展用接口
@Override
public void onEvent(int eventType, int arg1, int arg2, Bundle bundle) {
}
};
private void printResult(RecognizerResult recognizerResult, boolean isLast) {
String text = parseIatResult(recognizerResult.getResultString());
String sn = null;
//读取Json结果中的sn字段
try {
JSONObject resultJson = new JSONObject(recognizerResult.getResultString());
sn = resultJson.optString("sn");
} catch (Exception e) {
e.printStackTrace();
}
mIatResults.put(sn, text);
StringBuilder sb = new StringBuilder();
for (String key : mIatResults.keySet()) {
sb.append(mIatResults.get(key));
}
returnWords(sb.toString(), isLast);
}
//回调方法 ,返回识别后的文字
public abstract void returnWords(String words, boolean isLast);
/**
* 工具类
*
* @param audioPath
*/
private void audioDecodeFun(final String audioPath, CloudCallBack callBack) {
int index = audioPath.lastIndexOf(File.separator);
String pcmFileName = audioPath.substring(index, audioPath.length()).replace("amr", "pcm");
String pcmFilePath = MXKit.getInstance().getKitConfiguration().getAppStoreHome() + File.separator + "app_voice";
String pcmFileAbsolutlyPath = pcmFilePath + File.separator + pcmFileName;
// boolean fileExist = FileUtils.exists(pcmFileAbsolutlyPath);
boolean fileExist = false;
if (fileExist) {
File pcmFile = new File(pcmFileAbsolutlyPath);
generateFile(pcmFile);
return;
}
//pcm文件不存在,进行转码
audioDecode = AudioDecode.newInstance();
audioDecode.setFilePath(audioPath);
// audioDecode.prepare();
audioDecode.setOnCompleteListener(pcmData -> {
if (TextUtils.isEmpty(audioPath) || pcmData == null || pcmData.isEmpty()) {
return;
}
int index1 = audioPath.lastIndexOf(File.separator);
String pcmFileName1 = audioPath.substring(index1, audioPath.length()).replace("amr", "pcm");
String pcmFilePath1 = MXKit.getInstance().getKitConfiguration().getAppStoreHome() + "app_voice";
File pcmFile = FileUtils.bytesToFile(pcmData, pcmFilePath1, pcmFileName1);
// generateFile(pcmFile);
// File pcmFile = new File(appVoicePath);
// Log.e("========", "[pcmFile]" + pcmFile.getAbsolutePath());
String authUrl = null;
try {
authUrl = WebIATWS.getAuthUrl(WebIATWS.hostUrl, WebIATWS.apiKey, WebIATWS.apiSecret);
OkHttpClient client = new OkHttpClient.Builder().build();
//将url中的 schema http://和https://分别替换为ws:// 和 wss://
String url = authUrl.toString().replace("http://", "ws://").replace("https://", "wss://");
//System.out.println(url);
Request request = new Request.Builder().url(url).build();
// System.out.println(client.newCall(request).execute());
//System.out.println("url===>" + url);
if (pcmFile != null) {
// newCall = client.newCall(request);
// WebIATWS webIATWS = new WebIATWS(pcmFile, callBack);
// client.newWebSocket(request, webIATWS);
} else {
// callBack.mxError("转换失败");
}
} catch (Exception e) {
e.printStackTrace();
}
audioDecode.release();
});
audioDecode.startAsync();
}
private void generateFile(File pcmFile) {
FileInputStream fis = null;
final byte[] buffer = new byte[64 * 1024];
try {
fis = new FileInputStream(pcmFile);
if (0 == fis.available()) {
// mResult.append("no audio avaible!");
mIat.cancel();
} else {
int lenRead = buffer.length;
while (buffer.length == lenRead) {
lenRead = fis.read(buffer);
mIat.writeAudio(buffer, 0, lenRead);
}//end of while
mIat.stopListening();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (null != fis) {
fis.close();
fis = null;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 初始化监听器。
*/
private InitListener mInitListener = new InitListener() {
@Override
public void onInit(int code) {
if (code != ErrorCode.SUCCESS) {
}
}
};
/**
* 参数设置
*/
private void setParam() {
//参数设置
/**
* 应用领域 服务器为不同的应用领域,定制了不同的听写匹配引擎,使用对应的领域能获取更 高的匹配率
* 应用领域用于听写和语音语义服务。当前支持的应用领域有:
* 短信和日常用语:iat (默认)
* 视频:video
* 地图:poi
* 音乐:music
*/
mIat.setParameter(SpeechConstant.DOMAIN, "iat");
/**
* 在听写和语音语义理解时,可通过设置此参数,选择要使用的语言区域
* 当前支持:
* 简体中文:zh_cn(默认)
* 美式英文:en_us
*/
mIat.setParameter(SpeechConstant.LANGUAGE, "zh_cn");
/**
* 每一种语言区域,一般还有不同的方言,通过此参数,在听写和语音语义理解时, 设置不同的方言参数。
* 当前仅在LANGUAGE为简体中文时,支持方言选择,其他语言区域时, 请把此参数值设为null。
* 普通话:mandarin(默认)
* 粤 语:cantonese
* 四川话:lmz
* 河南话:henanese
*/
mIat.setParameter(SpeechConstant.ACCENT, "mandarin");
// 设置听写引擎
mIat.setParameter(SpeechConstant.ENGINE_TYPE, mEngineType);
//设置语音前端点:静音超时时间,即用户多长时间不说话则当做超时处理
//默认值:短信转写5000,其他4000
mIat.setParameter(SpeechConstant.VAD_BOS, "6000");
// 设置语音后端点:后端点静音检测时间,即用户停止说话多长时间内即认为不再输入, 自动停止录音
mIat.setParameter(SpeechConstant.VAD_EOS, "6000");
// 设置标点符号,设置为"0"返回结果无标点,设置为"1"返回结果有标点
mIat.setParameter(SpeechConstant.ASR_PTT, "1");
// 设置音频保存路径,保存音频格式支持pcm、wav
mIat.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
// mIat.setParameter(SpeechConstant.ASR_AUDIO_PATH, Environment.getExternalStorageDirectory()+"/msc/"+System.currentTimeMillis()+".wav");
//文本,编码
mIat.setParameter(SpeechConstant.TEXT_ENCODING, "utf-8");
}
private String parseIatResult(String json) {
StringBuilder ret = new StringBuilder();
try {
JSONTokener tokener = new JSONTokener(json);
JSONObject jsonObject = new JSONObject(tokener);
JSONArray words = jsonObject.getJSONArray("ws");
int len = words.length();
for (int i = 0; i < len; i++) {
//转写结果词,默认使用第一个结果
JSONArray items = words.getJSONObject(i).getJSONArray("cw");
JSONObject object = items.getJSONObject(0);
ret.append(object.getString("w"));
//如果需要多候选结果,解析数组其他字段
/*for(int j = 0; j < items.length(); j++){
JSONObject obj = items.getJSONObject(j);
ret.append(obj.getString("w"));
}*/
}
} catch (Exception e) {
e.printStackTrace();
}
return ret.toString();
}
}
//webIATWS类 接口调用拿到内容回调到上一层
package com.minxing.kit.internal.common.util.speech;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import com.alibaba.fastjson.JSONObject;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.gt.base.utils.UiUtil;
import com.gt.library_cloud_sdklib.utils.CloudCallBack;
import okhttp3.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* 语音听写流式 WebAPI 接口调用示例 接口文档(必看):https://doc.xfyun.cn/rest_api/语音听写(流式版).html
* webapi 听写服务参考帖子(必看):http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=38947&extra=
* 语音听写流式WebAPI 服务,热词使用方式:登陆开放平台https://www.xfyun.cn/后,找到控制台--我的应用---语音听写---个性化热词,上传热词
* 注意:热词只能在识别的时候会增加热词的识别权重,需要注意的是增加相应词条的识别率,但并不是绝对的,具体效果以您测试为准。
* 错误码链接:https://www.xfyun.cn/document/error-code (code返回错误码时必看)
* 语音听写流式WebAPI 服务,方言或小语种试用方法:登陆开放平台https://www.xfyun.cn/后,在控制台--语音听写(流式)--方言/语种处添加
* 添加后会显示该方言/语种的参数值
*
* @author iflytek
*/
public class WebIATWS extends WebSocketListener {
public static final String hostUrl = "https://iat-api.xfyun.cn/v2/iat"; //中英文,http url 不支持解析 ws/wss schema
// private static final String hostUrl = "https://iat-niche-api.xfyun.cn/v2/iat";//小语种
public static final String appid = " "; //在控制台-我的应用获取
public static final String apiSecret = " "; //在控制台-我的应用-语音听写(流式版)获取
public static final String apiKey = " "; //在控制台-我的应用-语音听写(流式版)获取
public static final int StatusFirstFrame = 0;
public static final int StatusContinueFrame = 1;
public static final int StatusLastFrame = 2;
public static final Gson json = new Gson();
Decoder decoder = new Decoder();
// 开始时间
private static Date dateBegin = new Date();
// 结束时间
private static Date dateEnd = new Date();
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyy-MM-dd HH:mm:ss.SSS");
private File fileVoice;
private CloudCallBack webCallBack;
public WebSocket webSocket;
public int messageId;
public int conversationId;
public HashMap<Integer, Integer> formatHashMap;
public HashMap<Integer, StringBuilder> formatStringBuilder;
public WebIATWS(File file, int messageId, HashMap<Integer, Integer> hashMap, CloudCallBack callback) {
this.fileVoice = file;
this.messageId = messageId;
this.formatHashMap = hashMap;
this.webCallBack = callback;
}
@Override
public void onOpen(WebSocket webSocket, Response response) {
super.onOpen(webSocket, response);
this.webSocket = webSocket;
new Thread(() -> {
int frameSize = 12800;
//连接成功,开始发送数据
int status = 0; // 音频的状态
try (FileInputStream fs = new FileInputStream(fileVoice)) {
byte[] buffer = new byte[frameSize];
// 发送音频
end:
while (true) {
int len = fs.read(buffer);
if (len == -1) {
status = StatusLastFrame; //文件读完,改变status 为 2
}
switch (status) {
case StatusFirstFrame: // 第一帧音频status = 0
JsonObject frame = new JsonObject();
JsonObject business = new JsonObject(); //第一帧必须发送
JsonObject common = new JsonObject(); //第一帧必须发送
JsonObject data = new JsonObject(); //每一帧都要发送
// 填充common
common.addProperty("app_id", appid);
//填充business
business.addProperty("language", "zh_cn");
//business.addProperty("language", "en_us");//英文
//business.addProperty("language", "ja_jp");//日语,在控制台可添加试用或购买
//business.addProperty("language", "ko_kr");//韩语,在控制台可添加试用或购买
//business.addProperty("language", "ru-ru");//俄语,在控制台可添加试用或购买
business.addProperty("domain", "iat");
business.addProperty("vad_eos", 8000);//用于设置端点检测的静默时间,单位是毫秒。
business.addProperty("accent", "mandarin");//中文方言请在控制台添加试用,添加后即展示相应参数值
//business.addProperty("nunum", 0);
//business.addProperty("ptt", 0);//标点符号
//business.addProperty("rlang", "zh-hk"); // zh-cn :简体中文(默认值)zh-hk :繁体香港(若未授权不生效,在控制台可免费开通)
//business.addProperty("vinfo", 1);
business.addProperty("dwa", "wpgs");//动态修正(若未授权不生效,在控制台可免费开通)
//business.addProperty("nbest", 5);// 句子多候选(若未授权不生效,在控制台可免费开通)
//business.addProperty("wbest", 3);// 词级多候选(若未授权不生效,在控制台可免费开通)
//填充data
data.addProperty("status", StatusFirstFrame);
data.addProperty("format", "audio/L16;rate=8000");
// data.addProperty("encoding", "lame");//mp3
data.addProperty("encoding", "raw");//pcm
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
data.addProperty("audio", Base64.getEncoder().encodeToString(Arrays.copyOf(buffer, len)));
}
//填充frame
frame.add("common", common);
frame.add("business", business);
frame.add("data", data);
webSocket.send(frame.toString());
status = StatusContinueFrame; // 发送完第一帧改变status 为 1
break;
case StatusContinueFrame: //中间帧status = 1
JsonObject frame1 = new JsonObject();
JsonObject data1 = new JsonObject();
data1.addProperty("status", StatusContinueFrame);
data1.addProperty("format", "audio/L16;rate=8000");
data1.addProperty("encoding", "lame");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
data1.addProperty("audio", Base64.getEncoder().encodeToString(Arrays.copyOf(buffer, len)));
}
frame1.add("data", data1);
webSocket.send(frame1.toString());
break;
case StatusLastFrame: // 最后一帧音频status = 2 ,标志音频发送结束
JsonObject frame2 = new JsonObject();
JsonObject data2 = new JsonObject();
data2.addProperty("status", StatusLastFrame);
data2.addProperty("audio", "");
data2.addProperty("format", "audio/L16;rate=8000");
data2.addProperty("encoding", "lame");
frame2.add("data", data2);
webSocket.send(frame2.toString());
break end;
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
com.alibaba.fastjson.JSONObject jsonObject = new com.alibaba.fastjson.JSONObject();
jsonObject.put("messageId", messageId);
jsonObject.put("exception", "【165】" + e.getMessage());
webCallBack.mxError(jsonObject.toJSONString());
} catch (IOException e) {
e.printStackTrace();
com.alibaba.fastjson.JSONObject jsonObject = new com.alibaba.fastjson.JSONObject();
jsonObject.put("messageId", messageId);
jsonObject.put("exception", "【170】" + e.getMessage());
webCallBack.mxError(jsonObject.toJSONString());
}
}).start();
}
@Override
public void onMessage(WebSocket webSocket, String text) {
super.onMessage(webSocket, text);
ResponseData resp = json.fromJson(text, ResponseData.class);
if (resp != null) {
if (resp.getCode() != 0) {
com.alibaba.fastjson.JSONObject jsonObject = new com.alibaba.fastjson.JSONObject();
jsonObject.put("messageId", messageId);
if (formatHashMap != null) {
if (formatHashMap.containsKey(messageId)) {
/**
* 异常原因:句柄错误,请求无效句柄,听写websocket接口,客户端调用问题,例如语音听写英文模式下,请求第一帧就发送了status=2。*
* 解决方案:最后一帧,数据上传完毕后再发送status=2。*
*/
JsonObject frame2 = new JsonObject();
JsonObject data2 = new JsonObject();
data2.addProperty("status", StatusLastFrame);
frame2.add("data", data2);
webSocket.send(frame2.toString());
jsonObject.put("formatCode", resp.getCode());
formatHashMap.remove(messageId);
}
}
jsonObject.put("exception", "【200】" + resp.getMessage());
webCallBack.mxError(jsonObject.toJSONString());
Log.e("=====", "code=>" + resp.getCode() + " error=>" + resp.getMessage() + " sid=" + resp.getSid());
Log.e("=====", "错误码查询链接:https://www.xfyun.cn/document/error-code");
return;
}
if (resp.getData() != null) {
if (resp.getData().getResult() != null) {
Text te = resp.getData().getResult().getText();
if (resp.getData().getStatus() == StatusContinueFrame) {
try {
if (decoder != null) {
if (te != null) {
decoder.decode(te);
if (!TextUtils.isEmpty(decoder.toString())) {
// Log.e("=====", "进行中 " + resp.getSid() + " 识别结果 ==》" + decoder.toString());
JSONObject jsonObject = new JSONObject();
jsonObject.put("messageId", messageId);
jsonObject.put("formatText", decoder.toString());
webCallBack.onSuccess(jsonObject.toJSONString());
}
}
} else {
decoder = new Decoder();
}
} catch (Exception e) {
e.printStackTrace();
com.alibaba.fastjson.JSONObject jsonObject = new com.alibaba.fastjson.JSONObject();
jsonObject.put("messageId", messageId);
jsonObject.put("exception", resp.getCode() + "[Result]" + JSONObject.toJSONString(resp.getData().getResult()) + "【237】" + JSONObject.toJSONString(te));
webCallBack.mxError(jsonObject.toJSONString());
}
}
}
if (resp.getData().getStatus() == StatusLastFrame) {
Log.e("=====", "《== 最终识 " + resp.getSid() + " 别结果 ==》" + decoder.toString());
if (!TextUtils.isEmpty(decoder.toString())) {
/**
* 存在文本内容*
*/
JSONObject jsonObject = new JSONObject();
jsonObject.put("messageId", messageId);//当前消息id
//去除重复、得到最新
String replaceStr = decoder.toString();
jsonObject.put("formatText", replaceStr);
jsonObject.put("formatEnd", "ok");///结束符号:formatEnd
webCallBack.onSuccess(jsonObject.toJSONString());
} else {
JSONObject jsonObject = new JSONObject();
jsonObject.put("messageId", messageId);//当前消息id
webCallBack.mxError(jsonObject.toJSONString());
}
decoder.discard();
webSocket.close(1000, "");
}
}
}
}
@Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
super.onFailure(webSocket, t, response);
try {
if (null != response) {
int code = response.code();
Log.e("=====", "onFailure code:" + code);
Log.e("=====", "onFailure body:" + response.body().string());
if (101 != code) {
System.exit(0);
com.alibaba.fastjson.JSONObject jsonObject = new com.alibaba.fastjson.JSONObject();
jsonObject.put("messageId", messageId);
jsonObject.put("exception", "【318】" + code);
webCallBack.mxError(jsonObject.toJSONString());
}
}
} catch (IOException e) {
com.alibaba.fastjson.JSONObject jsonObject = new com.alibaba.fastjson.JSONObject();
jsonObject.put("messageId", messageId);
jsonObject.put("exception", "【320】" + e.getMessage());
webCallBack.mxError(jsonObject.toJSONString());
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static String getAuthUrl(String hostUrl, String apiKey, String apiSecret) throws Exception {
URL url = new URL(hostUrl);
SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
format.setTimeZone(TimeZone.getTimeZone("GMT"));
String date = format.format(new Date());
StringBuilder builder = new StringBuilder("host: ").append(url.getHost()).append("\n").
append("date: ").append(date).append("\n").
append("GET ").append(url.getPath()).append(" HTTP/1.1");
Charset charset = Charset.forName("UTF-8");
Mac mac = Mac.getInstance("hmacsha256");
SecretKeySpec spec = new SecretKeySpec(apiSecret.getBytes(charset), "hmacsha256");
mac.init(spec);
byte[] hexDigits = mac.doFinal(builder.toString().getBytes(charset));
String sha = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
sha = Base64.getEncoder().encodeToString(hexDigits);
}
String authorization = String.format("api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", apiKey, "hmac-sha256", "host date request-line", sha);
HttpUrl httpUrl = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
httpUrl = HttpUrl.parse("https://" + url.getHost() + url.getPath()).newBuilder().//
addQueryParameter("authorization", Base64.getEncoder().encodeToString(authorization.getBytes(charset))).//
addQueryParameter("date", date).//
addQueryParameter("host", url.getHost()).//
build();
}
return httpUrl.toString();
}
public static class ResponseData {
private int code;
private String message;
private String sid;
private Data data;
public int getCode() {
return code;
}
public String getMessage() {
return this.message;
}
public String getSid() {
return sid;
}
public Data getData() {
return data;
}
}
public static class Data {
private int status;
private Result result;
public int getStatus() {
return status;
}
public Result getResult() {
return result;
}
}
public static class Result {
int bg;
int ed;
String pgs;
int[] rg;
int sn;
Ws[] ws;
boolean ls;
JsonObject vad;
public Text getText() {
Text text = new Text();
StringBuilder sb = new StringBuilder();
for (Ws ws : this.ws) {
sb.append(ws.cw[0].w);
}
text.sn = this.sn;
text.text = sb.toString();
text.sn = this.sn;
text.rg = this.rg;
text.pgs = this.pgs;
text.bg = this.bg;
text.ed = this.ed;
text.ls = this.ls;
text.vad = this.vad == null ? null : this.vad;
return text;
}
}
public static class Ws {
Cw[] cw;
int bg;
int ed;
}
public static class Cw {
int sc;
String w;
}
public static class Text {
int sn;
int bg;
int ed;
String text;
String pgs;
int[] rg;
boolean deleted;
boolean ls;
JsonObject vad;
@Override
public String toString() {
return "Text{" +
"bg=" + bg +
", ed=" + ed +
", ls=" + ls +
", sn=" + sn +
", text='" + text + '\'' +
", pgs=" + pgs +
", rg=" + Arrays.toString(rg) +
", deleted=" + deleted +
", vad=" + (vad == null ? "null" : vad.getAsJsonArray("ws").toString()) +
'}';
}
}
//解析返回数据,仅供参考
public static class Decoder {
private Text[] texts;
private int defc = 10;
public Decoder() {
this.texts = new Text[this.defc];
}
public synchronized void decode(Text text) {
if (text.toString().equals("{}")) {
return;
}
if (text.sn >= this.defc) {
this.resize();
}
if ("rpl".equals(text.pgs)) {
for (int i = text.rg[0]; i <= text.rg[1]; i++) {
if (texts[i] != null) {
this.texts[i].deleted = true;
}
}
}
this.texts[text.sn] = text;
}
public String toString() {
StringBuilder sb = new StringBuilder();
for (Text t : this.texts) {
if (t != null && !t.deleted) {
sb.append(t.text);
}
}
return sb.toString();
}
public void resize() {
int oc = this.defc;
this.defc <<= 1;
Text[] old = this.texts;
this.texts = new Text[this.defc];
for (int i = 0; i < oc; i++) {
this.texts[i] = old[i];
}
}
public void discard() {
for (int i = 0; i < this.texts.length; i++) {
this.texts[i] = null;
}
}
}
}