小言_互联网的博客

【Android App】在线语音识别功能实现(使用云知声平台与WebSocket 超详细 附源码)

411人阅读  评论(0)

需要源码和相关资源请点赞关注收藏后评论区留下QQ~~~

一、在线语音识别

云知声的语音识别同样采用WebSocket接口,待识别的音频流支持MP3和PCM两种格式,对于在线语音识别来说,云知声使用JSON串封装报文,待识别的音频以二进制形式发给服务器,可分为以下几个步骤

云知声平台的创建及使用可以参考以下这篇博客

云知声的注册及使用

1:定义WebSocket客户端的语音识别功能

在请求报文中填写朗读领域 音频格式 采样率等识别参数 再把JSON串传给WebSocket服务器

把字节数字格式的原始音频通过sendBinary方法分批发给服务器

等到所有音频数据发送完毕 再向服务器发一个结束识别的报文 也就是type字段为end的JSON串

在识别过程中 服务器还会数次返回JSON格式的应答报文 只有报文中的end字段为true时才表示识别结束

2:定义PCM音频的实时录制线程

在线识别的音频源既可能是实时录制的音频文件,也可能是PCM音频,在实时录音的情况下,还需自定义专门的录音线程,每录制一段PCM数据就发给WebSocket服务器

3:创建并启动语音识别任务

回到测试页面的获得代码,先创建 WebSocket客户端的语音识别任务,再通过WebSocket客户端启动语音识别任务 串联之后的在线识别语音

点击开始实时识别按钮后开始说话,然后可以观察到语音识别结果

 

也可以点击右上角的识别样本音频,这时候会自动播放一段古诗 然后再点击识别 

 

 

代码如下

Java类


  
  1. package com.example.voice;
  2. import android.media.AudioFormat;
  3. import android.os.Bundle;
  4. import android.os.Environment;
  5. import androidx.appcompat.app.AppCompatActivity;
  6. import android.text.TextUtils;
  7. import android.util.Log;
  8. import android.widget.Button;
  9. import android.widget.TextView;
  10. import android.widget.Toast;
  11. import com.example.voice.constant.SoundConstant;
  12. import com.example.voice.task.AsrClientEndpoint;
  13. import com.example.voice.task.VoicePlayTask;
  14. import com.example.voice.task.VoiceRecognizeTask;
  15. import com.example.voice.util.AssetsUtil;
  16. import com.example.voice.util.SoundUtil;
  17. public class VoiceRecognizeActivity extends AppCompatActivity {
  18. private final static String TAG = "VoiceRecognizeActivity";
  19. private String SAMPLE_FILE = "sample/spring.pcm"; // 样本音频名称
  20. private TextView tv_recognize_text; // 声明一个文本视图对象
  21. private Button btn_recognize; // 声明一个按钮对象
  22. private String mSamplePath; // 样本音频的文件路径
  23. private boolean isRecognizing = false; // 是否正在识别
  24. private VoiceRecognizeTask mRecognizeTask; // 声明一个原始音频识别线程对象
  25. public void onCreate (Bundle savedInstanceState) {
  26. super.onCreate(savedInstanceState);
  27. setContentView(R.layout.activity_voice_recognize);
  28. findViewById(R.id.iv_back).setOnClickListener(v -> finish());
  29. TextView tv_title = findViewById(R.id.tv_title);
  30. tv_title.setText( "在线语音识别");
  31. TextView tv_option = findViewById(R.id.tv_option);
  32. tv_option.setText( "识别样本音频");
  33. tv_recognize_text = findViewById(R.id.tv_recognize_text);
  34. btn_recognize = findViewById(R.id.btn_recognize);
  35. btn_recognize.setOnClickListener(v -> {
  36. if (!isRecognizing) { // 未在识别
  37. btn_recognize.setText( "停止实时识别");
  38. new Thread(() -> onlineRecognize( "")).start(); // 启动在线识别语音的线程
  39. } else { // 正在识别
  40. btn_recognize.setText( "开始实时识别");
  41. new Thread(() -> mRecognizeTask.cancel()).start(); // 启动取消识别语音的线程
  42. }
  43. isRecognizing = !isRecognizing;
  44. });
  45. tv_option.setOnClickListener(v -> {
  46. new Thread(() -> onlineRecognize(mSamplePath)).start(); // 启动在线识别语音的线程
  47. });
  48. mSamplePath = String.format( "%s/%s",
  49. getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString(), SAMPLE_FILE);
  50. // 把资产目录下的样本音频文件复制到存储卡
  51. new Thread(() -> AssetsUtil.Assets2Sd( this, SAMPLE_FILE, mSamplePath)).start();
  52. }
  53. // 在线识别音频文件(文件路径为空的话,表示识别实时语音)
  54. private void onlineRecognize (String filePath) {
  55. runOnUiThread(() -> {
  56. tv_recognize_text.setText( "");
  57. Toast.makeText( this, "开始识别语音", Toast.LENGTH_SHORT).show();
  58. });
  59. // 创建语音识别任务,并指定语音监听器
  60. AsrClientEndpoint asrTask = new AsrClientEndpoint( this, filePath, arg -> {
  61. Log.d(TAG, "arg[0]="+arg[ 0]+ ",arg[2]="+arg[ 2]);
  62. tv_recognize_text.setText(arg[ 2].toString());
  63. if (Boolean.TRUE.equals(arg[ 0])) {
  64. Toast.makeText( this, "语音识别结束", Toast.LENGTH_SHORT).show();
  65. }
  66. });
  67. SoundUtil.startSoundTask(SoundConstant.URL_ASR, asrTask); // 启动语音识别任务
  68. if (TextUtils.isEmpty(filePath)) { // 文件路径为空,表示识别实时语音
  69. // 创建一个原始音频识别线程
  70. mRecognizeTask = new VoiceRecognizeTask( this, asrTask);
  71. mRecognizeTask.start(); // 启动原始音频识别线程
  72. } else { // 文件路径非空,表示识别音频文件
  73. int[] params = new int[] { 16000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT};
  74. // 创建一个原始音频播放线程
  75. VoicePlayTask playTask = new VoicePlayTask( this, filePath, params);
  76. playTask.start(); // 启动原始音频播放线程
  77. }
  78. }
  79. @Override
  80. protected void onPause () {
  81. super.onPause();
  82. if (mRecognizeTask != null) {
  83. new Thread(() -> mRecognizeTask.cancel()).start(); // 启动取消识别语音的线程
  84. }
  85. }
  86. }

任务类


  
  1. package com.example.voice.task;
  2. import android.app.Activity;
  3. import android.media.AudioFormat;
  4. import android.media.AudioRecord;
  5. import android.media.MediaRecorder;
  6. public class VoiceRecognizeTask extends Thread {
  7. private final static String TAG = "VoiceRecognizeTask";
  8. private Activity mAct; // 声明一个活动实例
  9. private int mFrequence = 16000; // 音频的采样频率,单位赫兹
  10. private int mChannel = AudioFormat.CHANNEL_IN_MONO; // 音频的声道类型
  11. private int mFormat = AudioFormat.ENCODING_PCM_16BIT; // 音频的编码格式
  12. private boolean isCancel = false; // 是否取消录音
  13. private AsrClientEndpoint mAsrTask; // 语音识别任务
  14. public VoiceRecognizeTask (Activity act, AsrClientEndpoint asrTask) {
  15. mAct = act;
  16. mAsrTask = asrTask;
  17. }
  18. @Override
  19. public void run () {
  20. // 根据定义好的几个配置,来获取合适的缓冲大小
  21. int bufferSize = AudioRecord.getMinBufferSize(mFrequence, mChannel, mFormat);
  22. bufferSize = Math.max(bufferSize, 9600);
  23. byte[] buffer = new byte[bufferSize]; // 创建缓冲区
  24. // 根据音频配置和缓冲区构建原始音频录制实例
  25. AudioRecord record = new AudioRecord(MediaRecorder.AudioSource.MIC,
  26. mFrequence, mChannel, mFormat, bufferSize);
  27. // 设置需要通知的时间周期为1秒
  28. record.setPositionNotificationPeriod( 1000);
  29. record.startRecording(); // 开始录制原始音频
  30. int i= 0;
  31. // 没有取消录制,则持续读取缓冲区
  32. while (!isCancel) {
  33. int bufferReadResult = record.read(buffer, 0, buffer.length);
  34. mAsrTask.sendRealtimeAudio(i++, buffer, bufferReadResult);
  35. }
  36. record.stop(); // 停止原始音频录制
  37. }
  38. // 取消实时录音
  39. public void cancel () {
  40. isCancel = true;
  41. mAsrTask.stopAsr(); // 停止语音识别
  42. }
  43. }

客户端类


  
  1. package com.example.voice.task;
  2. import android.app.Activity;
  3. import android.text.TextUtils;
  4. import android.util.Log;
  5. import org.json.JSONObject;
  6. import javax.websocket.*;
  7. import java.io.FileInputStream;
  8. import java.io.InputStream;
  9. import java.nio.ByteBuffer;
  10. @ClientEndpoint
  11. public class AsrClientEndpoint {
  12. private final static String TAG = "AsrClientEndpoint";
  13. private Activity mAct; // 声明一个活动实例
  14. private String mFileName; // 语音文件名称
  15. private VoiceListener mListener; // 语音监听器
  16. private Session mSession; // 连接会话
  17. public AsrClientEndpoint (Activity act, String fileName, VoiceListener listener) {
  18. mAct = act;
  19. mFileName = fileName;
  20. mListener = listener;
  21. }
  22. @OnOpen
  23. public void onOpen (final Session session) {
  24. mSession = session;
  25. Log.d(TAG, "->创建连接成功");
  26. try {
  27. // 组装请求开始的json报文
  28. JSONObject frame = new JSONObject();
  29. frame.put( "type", "start");
  30. JSONObject data = new JSONObject();
  31. frame.put( "data", data);
  32. data.put( "domain", "general"); // 领域。general(通用),law(司法),technology(科技),medical(医疗)
  33. data.put( "lang", "cn"); // 语言。cn(中文普通话)、en(英语)
  34. data.put( "format", "pcm"); // 音频格式。支持mp3和pcm
  35. data.put( "sample", "16k"); // 采样率。16k,8k
  36. data.put( "variable", "true"); // 是否可变结果
  37. data.put( "punctuation", "true"); // 是否开启标点
  38. data.put( "post_proc", "true"); // 是否开启数字转换
  39. data.put( "acoustic_setting", "near"); // 音响设置。near近讲,far远讲
  40. data.put( "server_vad", "false"); // 智能断句
  41. data.put( "max_start_silence", "1000"); // 智能断句前静音
  42. data.put( "max_end_silence", "500"); // 智能断句尾静音
  43. // 发送开始请求
  44. session.getBasicRemote().sendText(frame.toString());
  45. } catch (Exception e) {
  46. e.printStackTrace();
  47. }
  48. // 文件名非空,表示从音频文件中识别文本
  49. if (!TextUtils.isEmpty(mFileName)) {
  50. new Thread(() -> sendAudioData(session)).start();
  51. }
  52. }
  53. // 发送音频文件的语音数据
  54. private void sendAudioData (final Session session) {
  55. try ( InputStream is = new FileInputStream(mFileName)) {
  56. byte[] audioData = new byte[ 9600];
  57. int length = 0;
  58. while ((length = is.read(audioData)) != - 1) {
  59. Log.d(TAG, "发送语音数据 length="+length);
  60. ByteBuffer buffer = ByteBuffer.wrap(audioData, 0, length);
  61. session.getAsyncRemote().sendBinary(buffer);
  62. Thread.sleep( 200); // 模拟采集音频休眠
  63. }
  64. } catch (Exception e) {
  65. e.printStackTrace();
  66. }
  67. stopAsr(); // 停止语音识别
  68. }
  69. // 发送实时语音数据
  70. public synchronized void sendRealtimeAudio (int seq, byte[] data, int length) {
  71. if (mSession!= null && mSession.isOpen()) {
  72. Log.d(TAG, "发送语音数据 seq="+seq+ ",length="+length);
  73. ByteBuffer buffer = ByteBuffer.wrap(data, 0, length);
  74. mSession.getAsyncRemote().sendBinary(buffer);
  75. }
  76. }
  77. // 停止语音识别
  78. public void stopAsr () {
  79. try {
  80. // 组装请求结束的json报文
  81. JSONObject frame = new JSONObject();
  82. frame.put( "type", "end");
  83. if (mSession!= null && mSession.isOpen()) {
  84. // 发送结束请求
  85. mSession.getBasicRemote().sendText(frame.toString());
  86. }
  87. } catch (Exception e) {
  88. e.printStackTrace();
  89. }
  90. }
  91. @OnMessage
  92. public void processMessage (Session session, String message) {
  93. Log.d(TAG, "服务端返回:" + message);
  94. try {
  95. JSONObject jsonObject = new JSONObject(message);
  96. boolean end = jsonObject.getBoolean( "end"); // 是否结束识别
  97. int code = jsonObject.getInt( "code"); // 处理结果
  98. String msg = jsonObject.getString( "msg"); // 结果说明
  99. if (code != 0) {
  100. Log.d(TAG, "错误码:" + code + ",错误描述:" + msg);
  101. return;
  102. }
  103. String text = jsonObject.getString( "text");
  104. mAct.runOnUiThread(() -> mListener.voiceDealEnd(end, msg, text));
  105. if (end) {
  106. Log.d(TAG, mFileName + "识别结束");
  107. session.close(); // 关闭连接会话
  108. }
  109. } catch (Exception e) {
  110. e.printStackTrace();
  111. }
  112. }
  113. @OnError
  114. public void processError (Throwable t) {
  115. t.printStackTrace();
  116. }
  117. }

创作不易 觉得有帮助请点赞关注收藏~~~


转载:https://blog.csdn.net/jiebaoshayebuhui/article/details/128065315
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场