科大讯飞语音转文字 WebAPI 接口调用

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

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值