当前位置:   article > 正文

科大讯飞 新版AIkit 离线语音听写 Java 版本_java 集成科大讯飞离线语言识别

java 集成科大讯飞离线语言识别

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

实测一点问题都没

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

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

  1. package com.mhzk.xunfeitest;
  2. import androidx.appcompat.app.AppCompatActivity;
  3. import androidx.core.app.ActivityCompat;
  4. import androidx.core.content.ContextCompat;
  5. import android.Manifest;
  6. import android.content.Context;
  7. import android.content.pm.PackageManager;
  8. import android.os.Bundle;
  9. import android.util.Log;
  10. import android.view.View;
  11. import android.widget.TextView;
  12. import android.widget.Toast;
  13. import com.iflytek.aikit.core.AiHandle;
  14. import com.iflytek.aikit.core.AiStatus;
  15. public class MainActivity extends AppCompatActivity implements AbilityCallback{
  16. private String TAG = "内容初始化";
  17. private AiStatus state;
  18. private AiHandle handle;
  19. private int REQUEST_STORAGE_PERMISSION = 100;
  20. private TextView start;
  21. private TextView content;
  22. private AudioRecordUtil instance;
  23. private StringBuffer strBuffer;
  24. @Override
  25. protected void onCreate(Bundle savedInstanceState) {
  26. super.onCreate(savedInstanceState);
  27. setContentView(R.layout.activity_main);
  28. start = findViewById(R.id.one);
  29. content = findViewById(R.id.three);
  30. instance = AudioRecordUtil.getInstance();
  31. instance.initSDK(this);
  32. AudioRecordUtil.setCallBack(this);
  33. start.setOnClickListener(new View.OnClickListener() {
  34. @Override
  35. public void onClick(View view) {
  36. if(start.getText().toString().equals("开始录音")){
  37. instance.start(MainActivity.this);
  38. }else {
  39. instance.stop();
  40. }
  41. }
  42. });
  43. requestStoragePermission(this);
  44. }
  45. /**
  46. * 查看当前设备是否有存储权限:
  47. * 没有:请求获取权限
  48. * 有:复制当前项目assets下的xtts文件夹到设备根目录下(语音合成所必须的文件)
  49. * @param context
  50. */
  51. private void requestStoragePermission(Context context) {
  52. if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE)
  53. != PackageManager.PERMISSION_GRANTED) {
  54. ActivityCompat.requestPermissions(this,
  55. new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
  56. REQUEST_STORAGE_PERMISSION);
  57. }
  58. }
  59. /**
  60. * 请求获取存储权限
  61. * @param requestCode
  62. * @param permissions
  63. * @param grantResults
  64. */
  65. @Override
  66. public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
  67. super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  68. if (requestCode == REQUEST_STORAGE_PERMISSION) {
  69. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
  70. Log.i(TAG, "onRequestPermissionsResult: permission granted");
  71. //再次判断存储权限是否已授予
  72. boolean permission = FileUtils.hasStoragePermission(getApplicationContext());
  73. if (!permission) {
  74. Toast.makeText(getApplicationContext(), "没有存储权限,请重新获取!", Toast.LENGTH_SHORT).show();
  75. return;
  76. }
  77. // 应用具有存储权限
  78. Log.i(TAG,"成功获取存储权限!");
  79. //判断xtts文件是否存在,不存在则复制,存在则忽略
  80. FileUtils.createXttsDirAndCopyFile(getApplicationContext());
  81. } else {
  82. Log.i(TAG, "onRequestPermissionsResult: permission denied");
  83. Toast.makeText(this, "You Denied Permission", Toast.LENGTH_SHORT).show();
  84. }
  85. }
  86. }
  87. /**
  88. * 开始
  89. */
  90. @Override
  91. public void onAbilityBegin() {
  92. start.setText("暂停录音");
  93. content.setText("内容展示");
  94. }
  95. /**
  96. *能力结果输出
  97. * @param result 结果
  98. */
  99. @Override
  100. public void onAbilityResult(String result) {
  101. String s = content.getText().toString();
  102. strBuffer = new StringBuffer(s);
  103. strBuffer.append("\n"+result );
  104. String value = strBuffer.toString();
  105. runOnUiThread(new Runnable() {
  106. @Override
  107. public void run() {
  108. content.setText(value);
  109. }
  110. });
  111. }
  112. /**
  113. *结束
  114. * @param code
  115. * @param error
  116. */
  117. @Override
  118. public void onAbilityError(int code, Throwable error) {
  119. runOnUiThread(new Runnable() {
  120. @Override
  121. public void run() {
  122. start.setText("开始录音");
  123. }
  124. });
  125. instance.stop();
  126. }
  127. /**
  128. * 能力结束
  129. */
  130. @Override
  131. public void onAbilityEnd() {
  132. runOnUiThread(new Runnable() {
  133. @Override
  134. public void run() {
  135. start.setText("开始录音");
  136. }
  137. });
  138. }
  139. }

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

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

  1. package com.mhzk.xunfeitest;
  2. import android.annotation.SuppressLint;
  3. import android.content.Context;
  4. import android.media.AudioFormat;
  5. import android.media.AudioRecord;
  6. import android.media.MediaRecorder;
  7. import android.os.Environment;
  8. import android.util.Log;
  9. import androidx.annotation.NonNull;
  10. import com.iflytek.aikit.core.AiAudio;
  11. import com.iflytek.aikit.core.AiEvent;
  12. import com.iflytek.aikit.core.AiHandle;
  13. import com.iflytek.aikit.core.AiHelper;
  14. import com.iflytek.aikit.core.AiListener;
  15. import com.iflytek.aikit.core.AiRequest;
  16. import com.iflytek.aikit.core.AiResponse;
  17. import com.iflytek.aikit.core.AiStatus;
  18. import com.iflytek.aikit.core.AuthListener;
  19. import com.iflytek.aikit.core.DataStatus;
  20. import com.iflytek.aikit.core.ErrType;
  21. import java.io.File;
  22. import java.io.FileOutputStream;
  23. import java.io.OutputStream;
  24. import java.io.UnsupportedEncodingException;
  25. import java.util.List;
  26. import java.util.concurrent.atomic.AtomicBoolean;
  27. public class AudioRecordUtil {
  28. private static final String TAG = "AudioPlayByKeyUtils";
  29. //设置音频采样率,44100是目前的标准,但是某些设备仍然支持22050,16000,11025
  30. private final int sampleRateInHz = 16000;
  31. //设置音频的录制的声道CHANNEL_IN_STEREO为双声道,CHANNEL_CONFIGURATION_MONO为单声道
  32. private final int channelConfig = AudioFormat.CHANNEL_IN_MONO;
  33. //音频数据格式:PCM 16位每个样本。保证设备支持。PCM 8位每个样本。不一定能得到设备支持。
  34. private final int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
  35. //录制状态
  36. private boolean recorderState = true;
  37. private byte[] buffer;
  38. private static AudioRecord audioRecord;
  39. private static AudioRecordUtil audioRecordUtil = new AudioRecordUtil();
  40. private static AiHandle handle;
  41. private File recordFile;
  42. private AtomicBoolean atomicBoolean = new AtomicBoolean();
  43. private byte[] lockArray = new byte[0];
  44. private int recordMinBufferSize;
  45. //SDK初始化
  46. public void initSDK(Context context) {
  47. try {
  48. //外部存储绝对路径
  49. File externalStorageDirectory = Environment.getExternalStorageDirectory();
  50. // 初始化参数构建
  51. AiHelper.Params params = AiHelper.Params.builder()
  52. .appId(context.getString(R.string.appId))
  53. .apiKey(context.getString(R.string.apiKey))
  54. .apiSecret(context.getString(R.string.apiSecret))
  55. .workDir("/sdcard/iflytekAikit")//SDK工作路径,这里为绝对路径
  56. .authInterval(333) //授权更新间隔
  57. .build();
  58. // 初始化
  59. AiHelper.getInst().init(context, params);
  60. // 注册SDK 初始化状态监听
  61. AiHelper.getInst().registerListener(coreListener);
  62. // 注册能力结果监听 R.string.enginID 为离线的语音听写ID,写死就好 ee62fa27c
  63. AiHelper.getInst().registerListener(context.getString(R.string.enginID), aiRespListener);
  64. } catch (Exception e) {
  65. Log.e(TAG, "语音合成初始化出现异常" + e.getMessage());
  66. }
  67. }
  68. public static AudioRecordUtil getInstance() {
  69. return audioRecordUtil;
  70. }
  71. private AudioRecordUtil() {
  72. init();
  73. }
  74. @SuppressLint("MissingPermission")
  75. private void init() {
  76. recordMinBufferSize = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
  77. //指定 AudioRecord 缓冲区大小
  78. buffer = new byte[recordMinBufferSize];
  79. //根据录音参数构造AudioRecord实体对象
  80. audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRateInHz, channelConfig,
  81. audioFormat, recordMinBufferSize);
  82. }
  83. /**
  84. * 开始录制
  85. */
  86. public void start(Context context) {
  87. //已初始化则略过
  88. // initSDK(context);
  89. //能力逆初始化, 部分能力,比如语种切换的时候 需要逆初始化
  90. AiHelper.getInst().engineUnInit(context.getString(R.string.enginID));
  91. int ret = -1;
  92. ret = AiHelper.getInst().engineInit(context.getString(R.string.enginID));
  93. if (ret != 0) {
  94. abilityCallback.onAbilityError(ret, new Throwable("引擎初始化失败:$ret"));
  95. return;
  96. }
  97. int[] indexs0 = {0};
  98. int[] indexs1 = {1};
  99. ret = AiHelper.getInst()
  100. .specifyDataSet(context.getString(R.string.enginID), "PPROC_NOT_REP", indexs0);
  101. if (ret != 0) {
  102. abilityCallback.onAbilityError(ret, new Throwable("open esr specifyDataSet 失败:$ret"));
  103. return;
  104. }
  105. ret = AiHelper.getInst()
  106. .specifyDataSet(context.getString(R.string.enginID), "PPROC_REPLACE",indexs1);
  107. if (ret != 0) {
  108. abilityCallback.onAbilityError(ret, new Throwable("open esr specifyDataSet 失败:$ret"));
  109. return;
  110. }
  111. //音量及播报人等参数设置
  112. AiRequest.Builder paramBuilder = audioParam();
  113. handle = AiHelper.getInst().start(context.getString(R.string.enginID), paramBuilder.build(), null);
  114. atomicBoolean.set(true);
  115. if (!handle.isSuccess()) {
  116. Log.e(TAG, "ERROR::START | handle code:" + handle.getCode());
  117. return;
  118. }
  119. if (audioRecord.getState() == AudioRecord.RECORDSTATE_STOPPED) {
  120. recorderState = true;
  121. audioRecord.startRecording();
  122. abilityCallback.onAbilityBegin();
  123. String absolutePath = MyApp.mApplication.getExternalCacheDir().getAbsolutePath();
  124. recordFile = new File(absolutePath + "/" + System.currentTimeMillis() + ".pcm");
  125. }else {
  126. init();
  127. recorderState = true;
  128. audioRecord.startRecording();
  129. abilityCallback.onAbilityBegin();
  130. String absolutePath = MyApp.mApplication.getExternalCacheDir().getAbsolutePath();
  131. recordFile = new File(absolutePath + "/" + System.currentTimeMillis() + ".pcm");
  132. }
  133. new RecordThread().start();
  134. }
  135. /**
  136. * 停止录制
  137. */
  138. public void stop() {
  139. recorderState = false;
  140. if (audioRecord.getState() == AudioRecord.RECORDSTATE_RECORDING) {
  141. audioRecord.stop();
  142. }
  143. audioRecord.release();
  144. int ret = AiHelper.getInst().end(handle);
  145. if (ret != 0) {
  146. String error = "end failed" + ret;
  147. Log.e(TAG, error);
  148. }
  149. }
  150. private class RecordThread extends Thread {
  151. @Override
  152. public void run() {
  153. //输出流
  154. OutputStream os = null;
  155. try {
  156. os = new FileOutputStream(recordFile);
  157. // BufferedOutputStream bos = new BufferedOutputStream(os);
  158. // DataOutputStream dos = new DataOutputStream(bos);
  159. while (recorderState) {
  160. int read = audioRecord.read(buffer, 0, buffer.length);
  161. for (int i = 0; i < read; i++) {
  162. // dos.writeShort(buffer[i]);
  163. }
  164. if (read == recordMinBufferSize) {
  165. AiStatus status = AiStatus.CONTINUE;
  166. if (atomicBoolean.get()) {
  167. status = AiStatus.BEGIN;
  168. atomicBoolean.set(false);
  169. }
  170. writeData(buffer, status);
  171. } else {
  172. byte[] copy = new byte[read];
  173. System.arraycopy(buffer, 0, copy, 0, read);
  174. AiStatus status = AiStatus.CONTINUE;
  175. if (atomicBoolean.get()) {
  176. status = AiStatus.BEGIN;
  177. atomicBoolean.set(false);
  178. }
  179. writeData(copy, status);
  180. }
  181. os.write(buffer);
  182. }
  183. os.flush();
  184. os.close();
  185. } catch (Exception e) {
  186. e.printStackTrace();
  187. }
  188. }
  189. }
  190. /**
  191. * 写入音频数据
  192. *
  193. * @param status 送入的数据的状态,告诉引擎送入的首帧数据、中间数据、还是尾帧
  194. */
  195. private void writeData(byte[] audio, AiStatus status) {
  196. if (handle == null) {
  197. return;
  198. }
  199. synchronized (lockArray) {
  200. AiRequest.Builder dataBuilder = AiRequest.builder();
  201. AiAudio.Holder holder = AiAudio.get("PCM").data(audio);
  202. holder.status(status);
  203. dataBuilder.payload(holder.valid());
  204. int ret = AiHelper.getInst().write(dataBuilder.build(), handle);
  205. if (ret != 0) {
  206. destory();
  207. Log.w(TAG, "writeData is error => $ret");
  208. } else {
  209. ret = AiHelper.getInst().read("ee62fa27c", handle);
  210. if (ret != 0) {
  211. Log.w(TAG, "read error code => $ret");
  212. destory();
  213. } else {
  214. Log.w(TAG, "read success code => $ret");
  215. }
  216. }
  217. }
  218. }
  219. /**
  220. * SDK监听回调
  221. */
  222. private static AuthListener coreListener = new AuthListener() {
  223. @Override
  224. public void onAuthStateChange(final ErrType type, final int code) {
  225. Log.i(TAG, "core listener code:" + code);
  226. switch (type) {
  227. case AUTH:
  228. Log.i(TAG, "SDK状态:授权结果码" + code);
  229. break;
  230. case HTTP:
  231. Log.i(TAG, "SDK状态:HTTP认证结果" + code);
  232. break;
  233. default:
  234. Log.i(TAG, "SDK状态:其他错误");
  235. }
  236. }
  237. };
  238. /**
  239. * 能力监听回调
  240. */
  241. private static AiListener aiRespListener = new AiListener() {
  242. //获取合成结果,封装到缓存数组中
  243. @Override
  244. public void onResult(int handleID, List<AiResponse> outputData, Object usrContext) {
  245. if (outputData == null || outputData.isEmpty()) {
  246. return;
  247. }
  248. if (null != outputData && outputData.size() > 0) {
  249. for (int i = 0; i < outputData.size(); i++) {
  250. byte[] bytes = outputData.get(i).getValue();
  251. String key = outputData.get(i).getKey();
  252. if (key.contains("plain") || key.contains("pgs")) {
  253. try {
  254. String s = new String(bytes, "GBK");
  255. Log.e(TAG, key + " " + s);
  256. abilityCallback.onAbilityResult(key+ " "+s);
  257. } catch (UnsupportedEncodingException e) {
  258. e.printStackTrace();
  259. }
  260. if (key.contains("plain")) {
  261. stopAsr();
  262. }
  263. }
  264. }
  265. if (outputData.get(0).getStatus() == DataStatus.END.getValue()) {
  266. stopAsr();
  267. }
  268. }
  269. }
  270. @Override
  271. public void onEvent(int handleID, int event, List<AiResponse> eventData, Object usrContext) {
  272. if (event == AiEvent.EVENT_UNKNOWN.getValue()) {
  273. }
  274. if (event == AiEvent.EVENT_START.getValue()) {
  275. }
  276. if (event == AiEvent.EVENT_END.getValue()) {
  277. if (handle != null) {
  278. int rets = AiHelper.getInst().end(handle);
  279. if (rets != 0) {
  280. String error = "end failed" + rets;
  281. Log.e(TAG, error);
  282. }
  283. }
  284. }
  285. if (event == AiEvent.EVENT_PROGRESS.getValue()) {
  286. }
  287. }
  288. @Override
  289. public void onError(int handleID, int err, String msg, Object usrContext) {
  290. if (handle != null) {
  291. int rets = AiHelper.getInst().end(handle);
  292. if (rets != 0) {
  293. String error = "end failed" + rets;
  294. Log.e(TAG, error);
  295. }
  296. }
  297. }
  298. };
  299. /**
  300. * 音量及播报人等参数设置
  301. */
  302. @NonNull
  303. private static AiRequest.Builder audioParam() {
  304. AiRequest.Builder paramBuilder = AiRequest.builder();
  305. paramBuilder.param("lmLoad", true);
  306. paramBuilder.param("vadLoad", true);
  307. paramBuilder.param("puncLoad", true);
  308. paramBuilder.param("numLoad", true);
  309. paramBuilder.param("postprocOn", true);
  310. paramBuilder.param("lmOn", true);
  311. paramBuilder.param("vadOn", true);
  312. paramBuilder.param("vadLinkOn", false);
  313. paramBuilder.param("vadNeed", true);
  314. paramBuilder.param("vadThreshold", 0.1332);
  315. paramBuilder.param("vadEnergyThreshold", 9);
  316. return paramBuilder;
  317. }
  318. /**
  319. * 释放资源
  320. */
  321. public static void destory() {
  322. stopAsr();
  323. }
  324. /**
  325. * 停止语音识别
  326. */
  327. private static void stopAsr() {
  328. if (audioRecord != null) {
  329. if (audioRecord.getState() == AudioRecord.STATE_INITIALIZED) {
  330. audioRecord.stop();
  331. }
  332. }
  333. audioRecord.release();
  334. int ret = AiHelper.getInst().end(handle);
  335. if (ret == 0) {
  336. abilityCallback.onAbilityEnd();
  337. } else {
  338. abilityCallback.onAbilityError(ret, new Throwable("aiHandle end error"));
  339. }
  340. }
  341. private static AbilityCallback abilityCallback;
  342. public static void setCallBack(AbilityCallback callBack) {
  343. abilityCallback = callBack;
  344. }
  345. }

接口类

  1. package com.mhzk.xunfeitest;
  2. public interface AbilityCallback {
  3. /**
  4. * 开始
  5. */
  6. void onAbilityBegin();
  7. /**
  8. * 能力结果输出
  9. *
  10. * @param result 结果
  11. */
  12. void onAbilityResult(String result);
  13. /**
  14. * 结束
  15. *
  16. * @param code
  17. * @param error
  18. */
  19. void onAbilityError(int code, Throwable error);
  20. /**
  21. * 能力结束
  22. */
  23. void onAbilityEnd();
  24. }

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

  1. package com.mhzk.xunfeitest;
  2. import android.Manifest;
  3. import android.content.Context;
  4. import android.content.pm.PackageManager;
  5. import android.content.res.AssetManager;
  6. import android.os.Build;
  7. import android.os.Environment;
  8. import android.util.Log;
  9. import java.io.File;
  10. import java.io.FileOutputStream;
  11. import java.io.IOException;
  12. import java.io.InputStream;
  13. import java.io.OutputStream;
  14. /**
  15. * 讯飞语音合成文件复制公共功能
  16. * 以下五个文件:
  17. * e3fe94474_1.0.0_xTTS_CnCn_xiaoyan_2018_arm.irf
  18. * e4b08c6f3_1.0.0_xTTS_CnCn_xiaofeng_2018_fix_arm.dat
  19. * e4caee636_1.0.2_xTTS_CnCn_front_Emb_arm_2017.irf
  20. * e05d571cc_1.0.0_xTTS_CnCn_xiaoyan_2018_fix_arm.dat
  21. * ebdbd61ae_1.0.0_xTTS_CnCn_xiaofeng_2018_arm.irf
  22. */
  23. public class FileUtils {
  24. private static final String TAG = "FileUtils";
  25. // 获取外部存储路径
  26. public static String getExternalStoragePath() {
  27. return Environment.getExternalStorageDirectory().getAbsolutePath();
  28. }
  29. // 创建xtts目录
  30. public static void createDirectory(String directoryPath) {
  31. File directory = new File(directoryPath);
  32. if (!directory.exists()) {
  33. if (directory.mkdirs()) {
  34. Log.d(TAG, "Directory created: " + directoryPath);
  35. } else {
  36. Log.e(TAG, "Failed to create directory: " + directoryPath);
  37. }
  38. } else {
  39. Log.d(TAG, "Directory already exists: " + directoryPath);
  40. }
  41. }
  42. // 判断目录是否为空
  43. public static boolean isDirectoryEmpty(String directoryPath) {
  44. File directory = new File(directoryPath);
  45. if (directory.exists() && directory.isDirectory()) {
  46. File[] files = directory.listFiles();
  47. return files == null || files.length == 0;
  48. }
  49. return true;
  50. }
  51. // 递归复制文件
  52. public static void copyFiles(Context context, String sourceDir, String destinationDir) throws IOException {
  53. AssetManager assetManager = context.getAssets();
  54. String[] files = assetManager.list(sourceDir);
  55. if (files != null && files.length > 0) {
  56. createDirectory(destinationDir);
  57. for (String fileName : files) {
  58. String sourcePath = sourceDir + File.separator + fileName;
  59. String destinationPath = destinationDir + File.separator + fileName;
  60. if (assetManager.list(sourcePath).length > 0) {
  61. // 如果是目录,递归复制目录
  62. copyFiles(context, sourcePath, destinationPath);
  63. } else {
  64. // 如果是文件,复制文件
  65. copyFile(context, sourcePath, destinationPath);
  66. }
  67. }
  68. }
  69. }
  70. // 复制文件
  71. public static void copyFile(Context context, String sourcePath, String destinationPath) throws IOException {
  72. InputStream inputStream = null;
  73. OutputStream outputStream = null;
  74. try {
  75. inputStream = context.getAssets().open(sourcePath);
  76. outputStream = new FileOutputStream(destinationPath);
  77. byte[] buffer = new byte[4096];
  78. int length;
  79. while ((length = inputStream.read(buffer)) > 0) {
  80. outputStream.write(buffer, 0, length);
  81. }
  82. Log.d(TAG, "File copied: " + destinationPath);
  83. } finally {
  84. if (inputStream != null) {
  85. try {
  86. inputStream.close();
  87. } catch (IOException e) {
  88. Log.e(TAG, "Failed to close input stream", e);
  89. }
  90. }
  91. if (outputStream != null) {
  92. try {
  93. outputStream.close();
  94. } catch (IOException e) {
  95. Log.e(TAG, "Failed to close output stream", e);
  96. }
  97. }
  98. }
  99. }
  100. /**
  101. * 创建讯飞语音合成所必须的目录:xtts并复制音频文件
  102. * @param context
  103. */
  104. public static void createXttsDirAndCopyFile(Context context){
  105. // 获取外部存储路径
  106. String externalStoragePath = FileUtils.getExternalStoragePath();
  107. String xttsFolderPath = externalStoragePath + File.separator + context.getString(R.string.dir);
  108. // 创建xtts文件夹
  109. FileUtils.createDirectory(xttsFolderPath);
  110. // 判断xtts文件夹是否为空
  111. if (FileUtils.isDirectoryEmpty(xttsFolderPath)) {
  112. // 复制assets目录下的xtts文件夹中的所有文件到外部存储的xtts文件夹中
  113. try {
  114. FileUtils.copyFiles(context, context.getString(R.string.dir), xttsFolderPath);
  115. } catch (IOException e) {
  116. Log.e(TAG, "文件复制失败"+e.getMessage());
  117. }
  118. } else {
  119. // xtts文件夹不为空
  120. Log.d(TAG, "xtts folder is not empty. Skipping the operation.");
  121. }
  122. }
  123. public static boolean hasStoragePermission(Context context) {
  124. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
  125. int permissionResult = context.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);
  126. return permissionResult == PackageManager.PERMISSION_GRANTED;
  127. }
  128. return true;
  129. }
  130. }

Activity的布局

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:app="http://schemas.android.com/apk/res-auto"
  4. xmlns:tools="http://schemas.android.com/tools"
  5. android:layout_width="match_parent"
  6. android:orientation="vertical"
  7. android:gravity="center_horizontal"
  8. android:layout_height="match_parent"
  9. tools:context=".MainActivity">
  10. <TextView
  11. android:id="@+id/one"
  12. android:layout_width="wrap_content"
  13. android:layout_height="wrap_content"
  14. android:text="开始录音"
  15. android:textSize="20sp"
  16. app:layout_constraintBottom_toBottomOf="parent"
  17. app:layout_constraintEnd_toEndOf="parent"
  18. app:layout_constraintStart_toStartOf="parent"
  19. app:layout_constraintTop_toTopOf="parent" />
  20. <TextView
  21. android:layout_marginTop="20dp"
  22. android:id="@+id/three"
  23. android:layout_width="wrap_content"
  24. android:layout_height="wrap_content"
  25. android:text="内容展示"
  26. android:textSize="20sp"
  27. app:layout_constraintBottom_toBottomOf="parent"
  28. app:layout_constraintEnd_toEndOf="parent"
  29. app:layout_constraintStart_toStartOf="parent"
  30. app:layout_constraintTop_toTopOf="parent" />
  31. </LinearLayout>

 要demo的可以私信

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/378311
推荐阅读
相关标签
  

闽ICP备14008679号