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

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

需要源码和相关资源请点赞关注收藏后评论区留下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(); // 启动原始音频播放线程
        }
    }
    @Override
    protected 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;
    }
    @Override
    public 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;
    }
    @OnOpen
    public 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和pcm
            data.put("sample", "16k"); // 采样率。16k,8k
            data.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();
        }
    }
    @OnMessage
    public 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();
        }
    }
    @OnError
    public void processError(Throwable t) {
        t.printStackTrace();
    }
}

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

相关实践学习
达摩院智能语音交互 - 声纹识别技术
声纹识别是基于每个发音人的发音器官构造不同,识别当前发音人的身份。按照任务具体分为两种: 声纹辨认:从说话人集合中判别出测试语音所属的说话人,为多选一的问题 声纹确认:判断测试语音是否由目标说话人所说,是二选一的问题(是或者不是) 按照应用具体分为两种: 文本相关:要求使用者重复指定的话语,通常包含与训练信息相同的文本(精度较高,适合当前应用模式) 文本无关:对使用者发音内容和语言没有要求,受信道环境影响比较大,精度不高 本课程主要介绍声纹识别的原型技术、系统架构及应用案例等。 讲师介绍: 郑斯奇,达摩院算法专家,毕业于美国哈佛大学,研究方向包括声纹识别、性别、年龄、语种识别等。致力于推动端侧声纹与个性化技术的研究和大规模应用。
相关文章
|
JavaScript 前端开发 Android开发
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
516 13
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
|
11月前
|
Android开发 数据安全/隐私保护 开发者
Android自定义view之模仿登录界面文本输入框(华为云APP)
本文介绍了一款自定义输入框的实现,包含静态效果、hint值浮动动画及功能扩展。通过组合多个控件完成界面布局,使用TranslateAnimation与AlphaAnimation实现hint文字上下浮动效果,支持密码加密解密显示、去除键盘回车空格输入、光标定位等功能。代码基于Android平台,提供完整源码与attrs配置,方便复用与定制。希望对开发者有所帮助。
232 0
|
NoSQL 应用服务中间件 PHP
布谷一对一直播源码android版环境配置流程及功能明细
部署需基于 CentOS 7.9 系统,硬盘不低于 40G,使用宝塔面板安装环境,包括 PHP 7.3(含 Redis、Fileinfo 扩展)、Nginx、MySQL 5.6、Redis 和最新 Composer。Swoole 扩展需按步骤配置。2021.08.05 后部署需将站点目录设为 public 并用 ThinkPHP 伪静态。开发环境建议 Windows 操作系统与最新 Android Studio,基础配置涉及 APP 名称修改、接口域名更换、包名调整及第三方登录分享(如 QQ、微信)的配置,同时需完成阿里云与腾讯云相关设置。
|
JavaScript 搜索推荐 Android开发
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
571 8
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
|
数据采集 JavaScript Android开发
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
628 7
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
|
弹性计算 JSON 自然语言处理
语音交互产品通过WebSocket协议对外提供实时语音流语音转写功能
阿里云智能语音交互产品通过WebSocket协议提供实时语音转写功能,支持长语音。音频流以Binary Frame上传,指令和事件为Text Frame。支持单声道、16 bit采样位数的PCM、WAV等格式,采样率8000Hz/16000Hz。可设置返回中间结果、添加标点、中文数字转阿拉伯数字,并支持多语言识别。服务端通过临时Token鉴权,提供外网和上海ECS内网访问URL。交互流程包括StartTranscription、StopTranscription指令及多种事件反馈。
|
存储 监控 API
app开发之安卓Android+苹果ios打包所有权限对应解释列表【长期更新】-以及默认打包自动添加权限列表和简化后的基本打包权限列表以uniapp为例-优雅草央千澈
app开发之安卓Android+苹果ios打包所有权限对应解释列表【长期更新】-以及默认打包自动添加权限列表和简化后的基本打包权限列表以uniapp为例-优雅草央千澈
1780 11
|
Android开发
Android开发表情emoji功能开发
本文介绍了一种在Android应用中实现emoji表情功能的方法,通过将图片与表情字符对应,实现在`TextView`中的正常显示。示例代码展示了如何使用自定义适配器加载emoji表情,并在编辑框中输入或删除表情。项目包含完整的源码结构,可作为开发参考。视频演示和源码详情见文章内链接。
432 4
Android开发表情emoji功能开发
|
安全 Android开发 iOS开发
Android vs iOS:探索移动操作系统的设计与功能差异###
【10月更文挑战第20天】 本文深入分析了Android和iOS两个主流移动操作系统在设计哲学、用户体验、技术架构等方面的显著差异。通过对比,揭示了这两种系统各自的独特优势与局限性,并探讨了它们如何塑造了我们的数字生活方式。无论你是开发者还是普通用户,理解这些差异都有助于更好地选择和使用你的移动设备。 ###
664 3
|
Android开发 开发者
Android平台无纸化同屏如何实现实时录像功能
Android平台无纸化同屏,如果需要本地录像的话,实现难度不大,只要复用之前开发的录像模块的就可以,对我们来说,同屏采集这块,只是数据源不同而已,如果是自采集的其他数据,我们一样可以编码录像。
208 0