【Android App】在线语音识别功能实现(使用云知声平台与WebSocket 超详细 附源码)
创始人
2024-02-20 06:54:57
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类

package com.example.voice;import android.media.AudioFormat;
import android.os.Bundle;
import android.os.Environment;
import androidx.appcompat.app.AppCompatActivity;import android.text.TextUtils;
import android.util.Log;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;import com.example.voice.constant.SoundConstant;
import com.example.voice.task.AsrClientEndpoint;
import com.example.voice.task.VoicePlayTask;
import com.example.voice.task.VoiceRecognizeTask;
import com.example.voice.util.AssetsUtil;
import com.example.voice.util.SoundUtil;public class VoiceRecognizeActivity extends AppCompatActivity {private final static String TAG = "VoiceRecognizeActivity";private String SAMPLE_FILE = "sample/spring.pcm"; // 样本音频名称private TextView tv_recognize_text; // 声明一个文本视图对象private Button btn_recognize; // 声明一个按钮对象private String mSamplePath; // 样本音频的文件路径private boolean isRecognizing = false; // 是否正在识别private VoiceRecognizeTask mRecognizeTask; // 声明一个原始音频识别线程对象public void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_voice_recognize);findViewById(R.id.iv_back).setOnClickListener(v -> finish());TextView tv_title = findViewById(R.id.tv_title);tv_title.setText("在线语音识别");TextView tv_option = findViewById(R.id.tv_option);tv_option.setText("识别样本音频");tv_recognize_text = findViewById(R.id.tv_recognize_text);btn_recognize = findViewById(R.id.btn_recognize);btn_recognize.setOnClickListener(v -> {if (!isRecognizing) { // 未在识别btn_recognize.setText("停止实时识别");new Thread(() -> onlineRecognize("")).start(); // 启动在线识别语音的线程} else { // 正在识别btn_recognize.setText("开始实时识别");new Thread(() -> mRecognizeTask.cancel()).start(); // 启动取消识别语音的线程}isRecognizing = !isRecognizing;});tv_option.setOnClickListener(v -> {new Thread(() -> onlineRecognize(mSamplePath)).start(); // 启动在线识别语音的线程});mSamplePath = String.format("%s/%s",getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString(), SAMPLE_FILE);// 把资产目录下的样本音频文件复制到存储卡new Thread(() -> AssetsUtil.Assets2Sd(this, SAMPLE_FILE, mSamplePath)).start();}// 在线识别音频文件(文件路径为空的话,表示识别实时语音)private void onlineRecognize(String filePath) {runOnUiThread(() -> {tv_recognize_text.setText("");Toast.makeText(this, "开始识别语音", Toast.LENGTH_SHORT).show();});// 创建语音识别任务,并指定语音监听器AsrClientEndpoint asrTask = new AsrClientEndpoint(this, filePath, arg -> {Log.d(TAG, "arg[0]="+arg[0]+",arg[2]="+arg[2]);tv_recognize_text.setText(arg[2].toString());if (Boolean.TRUE.equals(arg[0])) {Toast.makeText(this, "语音识别结束", Toast.LENGTH_SHORT).show();}});SoundUtil.startSoundTask(SoundConstant.URL_ASR, asrTask); // 启动语音识别任务if (TextUtils.isEmpty(filePath)) { // 文件路径为空,表示识别实时语音// 创建一个原始音频识别线程mRecognizeTask = new VoiceRecognizeTask(this, asrTask);mRecognizeTask.start(); // 启动原始音频识别线程} else { // 文件路径非空,表示识别音频文件int[] params = new int[] {16000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT};// 创建一个原始音频播放线程VoicePlayTask playTask = new VoicePlayTask(this, filePath, params);playTask.start(); // 启动原始音频播放线程}}@Overrideprotected void onPause() {super.onPause();if (mRecognizeTask != null) {new Thread(() -> mRecognizeTask.cancel()).start(); // 启动取消识别语音的线程}}}

任务类

package com.example.voice.task;import android.app.Activity;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;public class VoiceRecognizeTask extends Thread {private final static String TAG = "VoiceRecognizeTask";private Activity mAct; // 声明一个活动实例private int mFrequence = 16000; // 音频的采样频率,单位赫兹private int mChannel = AudioFormat.CHANNEL_IN_MONO; // 音频的声道类型private int mFormat = AudioFormat.ENCODING_PCM_16BIT; // 音频的编码格式private boolean isCancel = false; // 是否取消录音private AsrClientEndpoint mAsrTask; // 语音识别任务public VoiceRecognizeTask(Activity act, AsrClientEndpoint asrTask) {mAct = act;mAsrTask = asrTask;}@Overridepublic void run() {// 根据定义好的几个配置,来获取合适的缓冲大小int bufferSize = AudioRecord.getMinBufferSize(mFrequence, mChannel, mFormat);bufferSize = Math.max(bufferSize, 9600);byte[] buffer = new byte[bufferSize]; // 创建缓冲区// 根据音频配置和缓冲区构建原始音频录制实例AudioRecord record = new AudioRecord(MediaRecorder.AudioSource.MIC,mFrequence, mChannel, mFormat, bufferSize);// 设置需要通知的时间周期为1秒record.setPositionNotificationPeriod(1000);record.startRecording(); // 开始录制原始音频int i=0;// 没有取消录制,则持续读取缓冲区while (!isCancel) {int bufferReadResult = record.read(buffer, 0, buffer.length);mAsrTask.sendRealtimeAudio(i++, buffer, bufferReadResult);}record.stop(); // 停止原始音频录制}// 取消实时录音public void cancel() {isCancel = true;mAsrTask.stopAsr(); // 停止语音识别}}

客户端类

package com.example.voice.task;import android.app.Activity;
import android.text.TextUtils;
import android.util.Log;import org.json.JSONObject;import javax.websocket.*;import java.io.FileInputStream;
import java.io.InputStream;
import java.nio.ByteBuffer;@ClientEndpoint
public class AsrClientEndpoint {private final static String TAG = "AsrClientEndpoint";private Activity mAct; // 声明一个活动实例private String mFileName; // 语音文件名称private VoiceListener mListener; // 语音监听器private Session mSession; // 连接会话public AsrClientEndpoint(Activity act, String fileName, VoiceListener listener) {mAct = act;mFileName = fileName;mListener = listener;}@OnOpenpublic void onOpen(final Session session) {mSession = session;Log.d(TAG, "->创建连接成功");try {// 组装请求开始的json报文JSONObject frame = new JSONObject();frame.put("type", "start");JSONObject data = new JSONObject();frame.put("data", data);data.put("domain", "general"); // 领域。general(通用),law(司法),technology(科技),medical(医疗)data.put("lang", "cn"); // 语言。cn(中文普通话)、en(英语)data.put("format", "pcm"); // 音频格式。支持mp3和pcmdata.put("sample", "16k"); // 采样率。16k,8kdata.put("variable", "true"); // 是否可变结果data.put("punctuation", "true"); // 是否开启标点data.put("post_proc", "true"); // 是否开启数字转换data.put("acoustic_setting", "near"); // 音响设置。near近讲,far远讲data.put("server_vad", "false"); // 智能断句data.put("max_start_silence", "1000"); // 智能断句前静音data.put("max_end_silence", "500"); // 智能断句尾静音// 发送开始请求session.getBasicRemote().sendText(frame.toString());} catch (Exception e) {e.printStackTrace();}// 文件名非空,表示从音频文件中识别文本if (!TextUtils.isEmpty(mFileName)) {new Thread(() -> sendAudioData(session)).start();}}// 发送音频文件的语音数据private void sendAudioData(final Session session) {try (InputStream is = new FileInputStream(mFileName)) {byte[] audioData = new byte[9600];int length = 0;while ((length = is.read(audioData)) != -1) {Log.d(TAG, "发送语音数据 length="+length);ByteBuffer buffer = ByteBuffer.wrap(audioData, 0, length);session.getAsyncRemote().sendBinary(buffer);Thread.sleep(200); // 模拟采集音频休眠}} catch (Exception e) {e.printStackTrace();}stopAsr(); // 停止语音识别}// 发送实时语音数据public synchronized void sendRealtimeAudio(int seq, byte[] data, int length) {if (mSession!=null && mSession.isOpen()) {Log.d(TAG, "发送语音数据 seq="+seq+",length="+length);ByteBuffer buffer = ByteBuffer.wrap(data, 0, length);mSession.getAsyncRemote().sendBinary(buffer);}}// 停止语音识别public void stopAsr() {try {// 组装请求结束的json报文JSONObject frame = new JSONObject();frame.put("type", "end");if (mSession!=null && mSession.isOpen()) {// 发送结束请求mSession.getBasicRemote().sendText(frame.toString());}} catch (Exception e) {e.printStackTrace();}}@OnMessagepublic void processMessage(Session session, String message) {Log.d(TAG, "服务端返回:" + message);try {JSONObject jsonObject = new JSONObject(message);boolean end = jsonObject.getBoolean("end"); // 是否结束识别int code = jsonObject.getInt("code"); // 处理结果String msg = jsonObject.getString("msg"); // 结果说明if (code != 0) {Log.d(TAG, "错误码:" + code + ",错误描述:" + msg);return;}String text = jsonObject.getString("text");mAct.runOnUiThread(() -> mListener.voiceDealEnd(end, msg, text));if (end) {Log.d(TAG, mFileName + "识别结束");session.close(); // 关闭连接会话}} catch (Exception e) {e.printStackTrace();}}@OnErrorpublic void processError(Throwable t) {t.printStackTrace();}
}

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

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
有效的括号 一、题目 给定一个只包括 '(',')','{','}'...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
【Ctfer训练计划】——(三... 作者名:Demo不是emo  主页面链接:主页传送门 创作初心ÿ...