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

news2025/2/25 10:23:35

需要源码和相关资源请点赞关注收藏后评论区留下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();
    }
}

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/40513.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

webpack打包vue文件+gulp打包sass文件

webpack打包vue文件 1,下载依赖 npm i vue-loader npm i webpack-cli2,编写webpack配置文件 /*** 关于webpack的配置文件*/const path require(path)const { VueLoaderPlugin } require(vue-loader)const glob require(glob) // node自带的读取文件的库 /*** …

会多门编程语言的你,最推荐哪3-5门语言?

如果你还想在编程的路上继续提高,那我建议你至少学习4种编程语言。可用的编程语言有很多,所以选择一种感兴趣的学习就可以了。我这么建议的原因是,要掌握编程,建立信心,提高能力,最简单的办法就是学习多种编…

浅析工作流调度器Azkaban

title: Azkaban系列 第一章 概述 1.1 为什么需要工作流调度器 1、一个完整的数据分析系统通常都是由大量任务单元组成: shell 脚本程序,java 程序,mapreduce 程序、hive 脚本等 2、各任务单元之间存在时间先后及前后依赖关系 3、为了很好地…

TIA西门子博途V18安装教程及注意事项

TIA西门子博途V18安装教程及注意事项 前提条件: TIA Portal V18需要.Net Framework 3.5环境,所以在安装TIA V18之前要先安装它。大家可以在控制面板中的程序和功能中检查是否已经安装,如果没有,可以参考以下步骤自行安装: 操作系统&#x

jsp旅行网系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 旅行网系统 是一套完善的web设计系统,对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发,数据库为Mysql,使用java语…

[附源码]Python计算机毕业设计房屋租赁系统

项目运行 环境配置: Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术: django python Vue 等等组成,B/S模式 pychram管理等等。 环境需要 1.运行环境:最好是python3.7.7,…

2022级浙大MEM录取经验过程分享——有需求就要去匹配

我是2022 级 浙大MEM 新录取的考生,去年联考初试取得了213 分的成绩,综合拿到了148分,去年的提面中也拿到了优秀资格,在备考的过程中自己的一些心得体会和经验分享给大家,希望能够有所帮助。我的本科其实比较普通&…

python循环中的continue和break

目录 一.python中的continue 案例1 结果是 注意 案例2 结果是 二.python中的break 案例1 结果是 注意 案例2 结果是 三. python中continue和break的总结 一.python中的continue continue关键字用于:中断本次循环,直接进入下一次循环 continue可以用于:fo…

2.1.2 运算放大器的组成与分类、运算放大器的发展历程

笔者电子信息专业硕士毕业,获得过多次电子设计大赛、大学生智能车、数学建模国奖,现就职于南京某半导体芯片公司,从事硬件研发,电路设计研究。对于学电子的小伙伴,深知入门的不易,特开次博客交流分享经验&a…

1-10嵌入式Linux系统开发与应用|嵌入式Linux|第三章 Linux编程环境|下篇

目录 1.gcc编译器的使用 1.1gcc软件包 1.2一个基本实例 1.3gcc的主要选项 1.3.1指定函数库和包含文件的查找路径 1.3.2出错检查及警告 1.3.3优化选项 优化带来的问题 1.3.4调试选项 2.GNU C扩展简介 3.GNU make管理项目 3.1make简介 使用make管理项目的原因 4.编…

Nginx那些事儿2

负载均衡 当访问的服务具有多个实例时,需要根据某种“均衡”的策略决定请求发往哪个节点,这就是所谓的负载均衡,目的是为了将数据流量分摊到多个服务器执行,减轻每台服务器的压力,从而提高了数据的吞吐量 负载均衡的种类 常见的硬件有NetScaler、F5、Radware和Array等商用的负…

读懂英文文章所需的单词量

简介 备考托福,GRE需要背上万单词,除去考试通关的因素,就想看看是不是真有必要花时间去背那么多单词。 实验使用从初中到GRE不同等级考试要求的单词表,代入Brown文本数据集,评估背会各等级单词后,能看懂多…

基于51单片机的教室智能照明控制系统

硬件方案 本系统以51单片机作为控制模块的核心部件,采用热释红外人体传感器检测人体的存在,采用光敏三极管构成的电路检测环境光的强度;根据教室合理开灯的条件,通过对人体存在信号和环境光信号的识别与判断,完成对教室…

关于浙江22年下半年教师资格证面试报名注册时间

1 哪些考生可以报名 笔试各科成绩合格且在有效期内的并符合各省面试报考条件人员,可参加报名面试: 2 报名分三阶段 12月9日~12日:网上报名 12月5日起开始注册,根据各省报考公告,考生登陆“NTCE-中国教育考试网”(ht…

Delphi记录

文章目录软件安装基础参考书名词释义基本语法常用函数数学运算函数字符处理函数日期时间函数顺序类型函数操作IDE设置去掉Delphi程序启动时的welcome page(欢迎页)设置环境变量的PATH及library的path安装控件如何在Delphi中安装库?安装Add-in-Exprexx安装TMS FlexCel 7.1 D10.…

Java#数据结构----1

目录 一.栈和队列 栈 队列 二.数组和链表 数组 链表 一.栈和队列 栈 栈的特点:后进先出,先进后出 数据进入栈模型的过程称为:压/进栈 数据离开栈模型的过程称为:弹/出栈 队列 队列的特点:先进先出,后进后出 数据从后端进入队列的过程称为: 入队列 数据从前端离开队列的过…

iptables学习

iptables不算是一个真正的防火墙,它是一个配置Linux内核防火墙的命令行工具。将用户的安全设置同步到对应的安全框架–Netfilter。netfilter位于内核空间,iptables位于用户空间。 iptables用于ipv4,ip6tables用于IPv6。 netfilter/ptables 一…

python tkinter 的使用 — 桌面应用程序开发

前言 嗨喽,大家好呀~这里是爱看美女的茜茜呐 又到了学Python时刻~ Tkinter模块(“Tk 接口”)是Python的标准Tk GUI工具包的接口. Tk和Tkinter可以在大多数的Unix平台下使用,同样可以应用在Windows和Macintosh系统里. Tk8.0的后续版本可以通过ttk实现本地窗口风格…

新电脑Mac安装前端环境,未完待续~

电脑:MacBook Pro (15-inch, 2017) 版本接近可以用迁移助理 太久远就只能新电脑环境重装了, 微信小程序,支付宝小程序,安卓,IOS 无非这几种 以下就是一名前端小程序开发人员环境配置步骤 仅供参考 新电脑安装 1.下载常…

【iOS】UICollectionView的基本使用

UICollectionView是与UITableView相似的控件,不过它的布局更加自由。 与UITableView的不同 tableViewcollectionView初始化需要指定布局style。需要指定一个布局类。子视图布局一行代表一个cell,布局只需要考虑行高。无视行列限制,一个item…