前言:科大讯飞的新版离线语音听写,由于官网demo是kt语言开发的,咱也看不懂kt,搜遍了全网也没看到一个java版的新版离线语音demo,现记录下,留给有缘人参考!!!!!毕竟咱在这上面遇到了不少的坑。如果能留言指正,那就更好了。

实测一点问题都没

一、先把官网Demo中resource下的文件放到sdk目录下,示例如下

一、Activity简单布局 加几个语音听写的监听回调

package com.mhzk.xunfeitest;import androidx.appcompat.app.AppCompatActivity;import androidx.core.app.ActivityCompat;import androidx.core.content.ContextCompat;import android.Manifest;import android.content.Context;import android.content.pm.PackageManager;import android.os.Bundle;import android.util.Log;import android.view.View;import android.widget.TextView;import android.widget.Toast;import com.iflytek.aikit.core.AiHandle;import com.iflytek.aikit.core.AiStatus;public class MainActivity extends AppCompatActivity implements AbilityCallback{private String TAG = "内容初始化";private AiStatus state;private AiHandle handle;private int REQUEST_STORAGE_PERMISSION = 100;private TextView start;private TextView content;private AudioRecordUtil instance;private StringBuffer strBuffer;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);start = findViewById(R.id.one);content = findViewById(R.id.three);instance = AudioRecordUtil.getInstance();instance.initSDK(this);AudioRecordUtil.setCallBack(this);start.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if(start.getText().toString().equals("开始录音")){instance.start(MainActivity.this);}else {instance.stop();}}});requestStoragePermission(this);}/** * 查看当前设备是否有存储权限: *没有:请求获取权限 *有:复制当前项目assets下的xtts文件夹到设备根目录下(语音合成所必须的文件) * @param context */private void requestStoragePermission(Context context) {if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED) {ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},REQUEST_STORAGE_PERMISSION);}}/** * 请求获取存储权限 * @param requestCode * @param permissions * @param grantResults */@Overridepublic void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);if (requestCode == REQUEST_STORAGE_PERMISSION) {if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {Log.i(TAG, "onRequestPermissionsResult: permission granted");//再次判断存储权限是否已授予boolean permission = FileUtils.hasStoragePermission(getApplicationContext());if (!permission) {Toast.makeText(getApplicationContext(), "没有存储权限,请重新获取!", Toast.LENGTH_SHORT).show();return;}// 应用具有存储权限Log.i(TAG,"成功获取存储权限!");//判断xtts文件是否存在,不存在则复制,存在则忽略FileUtils.createXttsDirAndCopyFile(getApplicationContext());} else {Log.i(TAG, "onRequestPermissionsResult: permission denied");Toast.makeText(this, "You Denied Permission", Toast.LENGTH_SHORT).show();}}}/** * 开始 */@Overridepublic void onAbilityBegin() {start.setText("暂停录音");content.setText("内容展示");}/** *能力结果输出 * @param result 结果 */@Overridepublic void onAbilityResult(String result) {String s = content.getText().toString();strBuffer = new StringBuffer(s);strBuffer.append("\n"+result );String value = strBuffer.toString();runOnUiThread(new Runnable() {@Overridepublic void run() {content.setText(value);}});}/** *结束 * @param code * @param error */@Overridepublic void onAbilityError(int code, Throwable error) {runOnUiThread(new Runnable() {@Overridepublic void run() {start.setText("开始录音");}});instance.stop();}/** * 能力结束 */@Overridepublic void onAbilityEnd() {runOnUiThread(new Runnable() {@Overridepublic void run() {start.setText("开始录音");}});}}

二、AudioRecord进行音频录制并写入本地创建的pcm文件

写入过程中把流数据复制一份传给sdk做文字转换

package com.mhzk.xunfeitest;import android.annotation.SuppressLint;import android.content.Context;import android.media.AudioFormat;import android.media.AudioRecord;import android.media.MediaRecorder;import android.os.Environment;import android.util.Log;import androidx.annotation.NonNull;import com.iflytek.aikit.core.AiAudio;import com.iflytek.aikit.core.AiEvent;import com.iflytek.aikit.core.AiHandle;import com.iflytek.aikit.core.AiHelper;import com.iflytek.aikit.core.AiListener;import com.iflytek.aikit.core.AiRequest;import com.iflytek.aikit.core.AiResponse;import com.iflytek.aikit.core.AiStatus;import com.iflytek.aikit.core.AuthListener;import com.iflytek.aikit.core.DataStatus;import com.iflytek.aikit.core.ErrType;import java.io.File;import java.io.FileOutputStream;import java.io.OutputStream;import java.io.UnsupportedEncodingException;import java.util.List;import java.util.concurrent.atomic.AtomicBoolean;public class AudioRecordUtil {private static final String TAG = "AudioPlayByKeyUtils";//设置音频采样率,44100是目前的标准,但是某些设备仍然支持22050,16000,11025private final int sampleRateInHz = 16000;//设置音频的录制的声道CHANNEL_IN_STEREO为双声道,CHANNEL_CONFIGURATION_MONO为单声道private final int channelConfig = AudioFormat.CHANNEL_IN_MONO;//音频数据格式:PCM 16位每个样本。保证设备支持。PCM 8位每个样本。不一定能得到设备支持。private final int audioFormat = AudioFormat.ENCODING_PCM_16BIT;//录制状态private boolean recorderState = true;private byte[] buffer;private static AudioRecord audioRecord;private static AudioRecordUtil audioRecordUtil = new AudioRecordUtil();private static AiHandle handle;private File recordFile;private AtomicBoolean atomicBoolean = new AtomicBoolean();private byte[] lockArray = new byte[0];private int recordMinBufferSize;//SDK初始化public void initSDK(Context context) {try {//外部存储绝对路径File externalStorageDirectory = Environment.getExternalStorageDirectory();// 初始化参数构建AiHelper.Params params = AiHelper.Params.builder().appId(context.getString(R.string.appId)).apiKey(context.getString(R.string.apiKey)).apiSecret(context.getString(R.string.apiSecret)).workDir("/sdcard/iflytekAikit")//SDK工作路径,这里为绝对路径.authInterval(333) //授权更新间隔.build();// 初始化AiHelper.getInst().init(context, params);// 注册SDK 初始化状态监听AiHelper.getInst().registerListener(coreListener);// 注册能力结果监听R.string.enginID 为离线的语音听写ID,写死就好 ee62fa27cAiHelper.getInst().registerListener(context.getString(R.string.enginID), aiRespListener);} catch (Exception e) {Log.e(TAG, "语音合成初始化出现异常" + e.getMessage());}}public static AudioRecordUtil getInstance() {return audioRecordUtil;}private AudioRecordUtil() {init();}@SuppressLint("MissingPermission")private void init() {recordMinBufferSize = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);//指定 AudioRecord 缓冲区大小buffer = new byte[recordMinBufferSize];//根据录音参数构造AudioRecord实体对象audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRateInHz, channelConfig,audioFormat, recordMinBufferSize);}/** * 开始录制 */public void start(Context context) {//已初始化则略过//initSDK(context);//能力逆初始化, 部分能力,比如语种切换的时候 需要逆初始化AiHelper.getInst().engineUnInit(context.getString(R.string.enginID));int ret = -1;ret = AiHelper.getInst().engineInit(context.getString(R.string.enginID));if (ret != 0) {abilityCallback.onAbilityError(ret, new Throwable("引擎初始化失败:$ret"));return;}int[] indexs0 = {0};int[] indexs1 = {1};ret = AiHelper.getInst().specifyDataSet(context.getString(R.string.enginID), "PPROC_NOT_REP", indexs0);if (ret != 0) {abilityCallback.onAbilityError(ret, new Throwable("open esr specifyDataSet 失败:$ret"));return;}ret = AiHelper.getInst().specifyDataSet(context.getString(R.string.enginID), "PPROC_REPLACE",indexs1);if (ret != 0) {abilityCallback.onAbilityError(ret, new Throwable("open esr specifyDataSet 失败:$ret"));return;}//音量及播报人等参数设置AiRequest.Builder paramBuilder = audioParam();handle = AiHelper.getInst().start(context.getString(R.string.enginID), paramBuilder.build(), null);atomicBoolean.set(true);if (!handle.isSuccess()) {Log.e(TAG, "ERROR::START | handle code:" + handle.getCode());return;}if (audioRecord.getState() == AudioRecord.RECORDSTATE_STOPPED) {recorderState = true;audioRecord.startRecording();abilityCallback.onAbilityBegin();String absolutePath = MyApp.mApplication.getExternalCacheDir().getAbsolutePath();recordFile = new File(absolutePath + "/" + System.currentTimeMillis() + ".pcm");}else {init();recorderState = true;audioRecord.startRecording();abilityCallback.onAbilityBegin();String absolutePath = MyApp.mApplication.getExternalCacheDir().getAbsolutePath();recordFile = new File(absolutePath + "/" + System.currentTimeMillis() + ".pcm");}new RecordThread().start();}/** * 停止录制 */public void stop() {recorderState = false;if (audioRecord.getState() == AudioRecord.RECORDSTATE_RECORDING) {audioRecord.stop();}audioRecord.release();int ret = AiHelper.getInst().end(handle);if (ret != 0) {String error = "end failed" + ret;Log.e(TAG, error);}}private class RecordThread extends Thread {@Overridepublic void run() {//输出流OutputStream os = null;try {os = new FileOutputStream(recordFile);//BufferedOutputStream bos = new BufferedOutputStream(os);//DataOutputStream dos = new DataOutputStream(bos);while (recorderState) {int read = audioRecord.read(buffer, 0, buffer.length);for (int i = 0; i  $ret");} else {ret = AiHelper.getInst().read("ee62fa27c", handle);if (ret != 0) {Log.w(TAG, "read error code => $ret");destory();} else {Log.w(TAG, "read success code => $ret");}}}}/** * SDK监听回调 */private static AuthListener coreListener = new AuthListener() {@Overridepublic void onAuthStateChange(final ErrType type, final int code) {Log.i(TAG, "core listener code:" + code);switch (type) {case AUTH:Log.i(TAG, "SDK状态:授权结果码" + code);break;case HTTP:Log.i(TAG, "SDK状态:HTTP认证结果" + code);break;default:Log.i(TAG, "SDK状态:其他错误");}}};/** * 能力监听回调 */private static AiListener aiRespListener = new AiListener() {//获取合成结果,封装到缓存数组中@Overridepublic void onResult(int handleID, List outputData, Object usrContext) {if (outputData == null || outputData.isEmpty()) {return;}if (null != outputData && outputData.size() > 0) {for (int i = 0; i < outputData.size(); i++) {byte[] bytes = outputData.get(i).getValue();String key = outputData.get(i).getKey();if (key.contains("plain") || key.contains("pgs")) {try {String s = new String(bytes, "GBK");Log.e(TAG, key + "" + s);abilityCallback.onAbilityResult(key+ ""+s);} catch (UnsupportedEncodingException e) {e.printStackTrace();}if (key.contains("plain")) {stopAsr();}}}if (outputData.get(0).getStatus() == DataStatus.END.getValue()) {stopAsr();}}}@Overridepublic void onEvent(int handleID, int event, List eventData, Object usrContext) {if (event == AiEvent.EVENT_UNKNOWN.getValue()) {}if (event == AiEvent.EVENT_START.getValue()) {}if (event == AiEvent.EVENT_END.getValue()) {if (handle != null) {int rets = AiHelper.getInst().end(handle);if (rets != 0) {String error = "end failed" + rets;Log.e(TAG, error);}}}if (event == AiEvent.EVENT_PROGRESS.getValue()) {}}@Overridepublic void onError(int handleID, int err, String msg, Object usrContext) {if (handle != null) {int rets = AiHelper.getInst().end(handle);if (rets != 0) {String error = "end failed" + rets;Log.e(TAG, error);}}}};/** * 音量及播报人等参数设置 */@NonNullprivate static AiRequest.Builder audioParam() {AiRequest.Builder paramBuilder = AiRequest.builder();paramBuilder.param("lmLoad", true);paramBuilder.param("vadLoad", true);paramBuilder.param("puncLoad", true);paramBuilder.param("numLoad", true);paramBuilder.param("postprocOn", true);paramBuilder.param("lmOn", true);paramBuilder.param("vadOn", true);paramBuilder.param("vadLinkOn", false);paramBuilder.param("vadNeed", true);paramBuilder.param("vadThreshold", 0.1332);paramBuilder.param("vadEnergyThreshold", 9);return paramBuilder;}/** * 释放资源 */public static void destory() {stopAsr();}/** * 停止语音识别 */private static void stopAsr() {if (audioRecord != null) {if (audioRecord.getState() == AudioRecord.STATE_INITIALIZED) {audioRecord.stop();}}audioRecord.release();int ret = AiHelper.getInst().end(handle);if (ret == 0) {abilityCallback.onAbilityEnd();} else {abilityCallback.onAbilityError(ret, new Throwable("aiHandle end error"));}}private static AbilityCallback abilityCallback;public static void setCallBack(AbilityCallback callBack) {abilityCallback = callBack;}}

接口类

package com.mhzk.xunfeitest;public interface AbilityCallback {/** * 开始 */void onAbilityBegin();/** * 能力结果输出 * * @param result 结果 */void onAbilityResult(String result);/** * 结束 * * @param code * @param error */void onAbilityError(int code, Throwable error);/** * 能力结束 */void onAbilityEnd();}

Activity中权限使用类,用不用都行,看自己

package com.mhzk.xunfeitest;import android.Manifest;import android.content.Context;import android.content.pm.PackageManager;import android.content.res.AssetManager;import android.os.Build;import android.os.Environment;import android.util.Log;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;/** * 讯飞语音合成文件复制公共功能 *以下五个文件: *e3fe94474_1.0.0_xTTS_CnCn_xiaoyan_2018_arm.irf *e4b08c6f3_1.0.0_xTTS_CnCn_xiaofeng_2018_fix_arm.dat *e4caee636_1.0.2_xTTS_CnCn_front_Emb_arm_2017.irf *e05d571cc_1.0.0_xTTS_CnCn_xiaoyan_2018_fix_arm.dat *ebdbd61ae_1.0.0_xTTS_CnCn_xiaofeng_2018_arm.irf */public class FileUtils {private static final String TAG = "FileUtils";// 获取外部存储路径public static String getExternalStoragePath() {return Environment.getExternalStorageDirectory().getAbsolutePath();}// 创建xtts目录public static void createDirectory(String directoryPath) {File directory = new File(directoryPath);if (!directory.exists()) {if (directory.mkdirs()) {Log.d(TAG, "Directory created: " + directoryPath);} else {Log.e(TAG, "Failed to create directory: " + directoryPath);}} else {Log.d(TAG, "Directory already exists: " + directoryPath);}}// 判断目录是否为空public static boolean isDirectoryEmpty(String directoryPath) {File directory = new File(directoryPath);if (directory.exists() && directory.isDirectory()) {File[] files = directory.listFiles();return files == null || files.length == 0;}return true;}// 递归复制文件public static void copyFiles(Context context, String sourceDir, String destinationDir) throws IOException {AssetManager assetManager = context.getAssets();String[] files = assetManager.list(sourceDir);if (files != null && files.length > 0) {createDirectory(destinationDir);for (String fileName : files) {String sourcePath = sourceDir + File.separator + fileName;String destinationPath = destinationDir + File.separator + fileName;if (assetManager.list(sourcePath).length > 0) {// 如果是目录,递归复制目录copyFiles(context, sourcePath, destinationPath);} else {// 如果是文件,复制文件copyFile(context, sourcePath, destinationPath);}}}}// 复制文件public static void copyFile(Context context, String sourcePath, String destinationPath) throws IOException {InputStream inputStream = null;OutputStream outputStream = null;try {inputStream = context.getAssets().open(sourcePath);outputStream = new FileOutputStream(destinationPath);byte[] buffer = new byte[4096];int length;while ((length = inputStream.read(buffer)) > 0) {outputStream.write(buffer, 0, length);}Log.d(TAG, "File copied: " + destinationPath);} finally {if (inputStream != null) {try {inputStream.close();} catch (IOException e) {Log.e(TAG, "Failed to close input stream", e);}}if (outputStream != null) {try {outputStream.close();} catch (IOException e) {Log.e(TAG, "Failed to close output stream", e);}}}}/** * 创建讯飞语音合成所必须的目录:xtts并复制音频文件 * @param context */public static void createXttsDirAndCopyFile(Context context){// 获取外部存储路径String externalStoragePath = FileUtils.getExternalStoragePath();String xttsFolderPath = externalStoragePath + File.separator + context.getString(R.string.dir);// 创建xtts文件夹FileUtils.createDirectory(xttsFolderPath);// 判断xtts文件夹是否为空if (FileUtils.isDirectoryEmpty(xttsFolderPath)) {// 复制assets目录下的xtts文件夹中的所有文件到外部存储的xtts文件夹中try {FileUtils.copyFiles(context, context.getString(R.string.dir), xttsFolderPath);} catch (IOException e) {Log.e(TAG, "文件复制失败"+e.getMessage());}} else {// xtts文件夹不为空Log.d(TAG, "xtts folder is not empty. Skipping the operation.");}}public static boolean hasStoragePermission(Context context) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {int permissionResult = context.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);return permissionResult == PackageManager.PERMISSION_GRANTED;}return true;}}

Activity的布局

要demo的可以私信