赞
踩
前言:科大讯飞的新版离线语音听写,由于官网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;
-
- @Override
- protected 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() {
- @Override
- public 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
- */
- @Override
- public 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();
- }
- }
- }
-
-
- /**
- * 开始
- */
- @Override
- public void onAbilityBegin() {
- start.setText("暂停录音");
- content.setText("内容展示");
- }
-
- /**
- *能力结果输出
- * @param result 结果
- */
- @Override
- public void onAbilityResult(String result) {
- String s = content.getText().toString();
- strBuffer = new StringBuffer(s);
- strBuffer.append("\n"+result );
- String value = strBuffer.toString();
-
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- content.setText(value);
- }
- });
- }
-
- /**
- *结束
- * @param code
- * @param error
- */
- @Override
- public void onAbilityError(int code, Throwable error) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- start.setText("开始录音");
- }
- });
-
- instance.stop();
- }
-
- /**
- * 能力结束
- */
- @Override
- public void onAbilityEnd() {
- runOnUiThread(new Runnable() {
- @Override
- public 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,11025
- private 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,写死就好 ee62fa27c
- AiHelper.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 {
-
- @Override
- public 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 < read; i++) {
- // dos.writeShort(buffer[i]);
- }
- if (read == recordMinBufferSize) {
- AiStatus status = AiStatus.CONTINUE;
- if (atomicBoolean.get()) {
- status = AiStatus.BEGIN;
- atomicBoolean.set(false);
- }
- writeData(buffer, status);
- } else {
- byte[] copy = new byte[read];
- System.arraycopy(buffer, 0, copy, 0, read);
-
- AiStatus status = AiStatus.CONTINUE;
- if (atomicBoolean.get()) {
- status = AiStatus.BEGIN;
- atomicBoolean.set(false);
- }
- writeData(copy, status);
- }
- os.write(buffer);
-
- }
- os.flush();
- os.close();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
-
-
- /**
- * 写入音频数据
- *
- * @param status 送入的数据的状态,告诉引擎送入的首帧数据、中间数据、还是尾帧
- */
- private void writeData(byte[] audio, AiStatus status) {
- if (handle == null) {
- return;
- }
- synchronized (lockArray) {
- AiRequest.Builder dataBuilder = AiRequest.builder();
- AiAudio.Holder holder = AiAudio.get("PCM").data(audio);
- holder.status(status);
- dataBuilder.payload(holder.valid());
- int ret = AiHelper.getInst().write(dataBuilder.build(), handle);
- if (ret != 0) {
- destory();
- Log.w(TAG, "writeData is error => $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() {
- @Override
- public 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() {
- //获取合成结果,封装到缓存数组中
- @Override
- public void onResult(int handleID, List<AiResponse> 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();
- }
- }
- }
-
- @Override
- public void onEvent(int handleID, int event, List<AiResponse> 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()) {
-
- }
- }
-
- @Override
- public 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);
- }
- }
- }
- };
-
-
- /**
- * 音量及播报人等参数设置
- */
- @NonNull
- private 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的布局
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:orientation="vertical"
- android:gravity="center_horizontal"
- android:layout_height="match_parent"
- tools:context=".MainActivity">
-
- <TextView
- android:id="@+id/one"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="开始录音"
- android:textSize="20sp"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
-
-
-
-
- <TextView
- android:layout_marginTop="20dp"
- android:id="@+id/three"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="内容展示"
- android:textSize="20sp"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
-
-
- </LinearLayout>
要demo的可以私信
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。