侧耳倾听:一款实时语音识别APP(含源码)

2025-08-07·
XIETU
XIETU
· 8 分钟阅读时长 · 阅读量

缘起

有一次和老妈聊天,但是老妈耳朵不好,我要用很大声才能听清,我就想开发个软件可以将我说的话转为文字显示出来,并且要实时的,文字还要大。

功能介绍

  • 实时语音识别;支持中文、英文;无需网络连接;仅需录音及浮窗权限
  • 支持浮窗显示;大小字体显示;手动保存;定长保存(超过300字)
  • 内置WeNet开源模型
  • 视频演示

开发将近两个月,界面的交互效果还不理想,也还能扩展一下,比如说英汉互译、加载本地录音文件进行识别等,但是耗时太长了,严重影响了我的主线任务,所以暂时放弃。

WeNet简介

和DeepSeek聊天,了解到国内有开源模型可以使用,我选择了WeNet,特点:端到端模型,工业级落地,支持流式识别。
中文模型:wenet_aishell1
魔搭社区地址: https://modelscope.cn/models/wenet/aishell
Aishell1 数据集训练,209MB;必需文件:final.zip,train.yaml,units.txt。
英文模型:wenet_librispeech
魔搭社区地址: https://modelscope.cn/models/wenet/librispeech
Librispeech 数据集训练,213MB;必需文件:final.zip,train.yaml,units.txt。

WeNet 语音识别工具包(3.1.0版本)

仓库地址:https://github.com/wenet-e2e/wenet

1、 主要目录:

  • .github/: 包含GitHub工作流和issue模板
  • docs/: 项目文档和图片资源
  • examples/: 各种语音数据集的训练示例(aishell, librispeech等)
  • runtime/: 运行时组件
  • wenet/: 核心源代码
  • tools/: 辅助工具
    我们只需要runtime/android目录下的文件。

2、android目录结构:

\wenet-3.1.0\runtime\android
├── .gitignore
├── .gradle\
│   ├── 7.5\
│   │   ├── checksums\
│   │   │   └── checksums.lock
│   │   ├── dependencies-accessors\
│   │   │   ├── dependencies-accessors.lock
│   │   │   └── gc.properties
│   │   ├── fileChanges\
│   │   │   └── last-build.bin
│   │   ├── fileHashes\
│   │   │   └── fileHashes.lock
│   │   ├── gc.properties
│   │   └── vcsMetadata\
├── buildOutputCleanup\
│   │   ├── buildOutputCleanup.lock
│   │   └── cache.properties
│   └── vcs-1\
│       └── gc.properties
├── README.md
├── app\
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   ├── src\
│   │   ├── androidTest\
│   │   │   └── java\
│   │   ├── main\
│   │   │   ├── AndroidManifest.xml
│   │   │   ├── assets\
│   │   │   ├── cpp\
│   │   │   ├── java\
│   │   │   └── res\
│   │   └── test\
│   │       └── java\
│   └── wenet.keystore
├── build.gradle
├── gradle\
│   └── wrapper\
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle

android\app\src\main\cpp文件夹中只有CMakeLists.txt和wenet.cc,以及8个快捷方式, 你需要将此8个快捷方式指向的文件夹考进来,他们在wenet-3.1.0\runtime\core目录下,分别是:
bin、cmake、decoder、frontend、kaldi、patch、post_processor和utils。

3、 build提示缺少WeTextProcessing:

WeTextProcessing 是一款专注于 文本标准化(Text Normalization, TN) 和 逆向文本标准化(Inverse Text Normalization, ITN) 的开源工具包,由 wenet-e2e 团队开发并维护。它主要用于自然语言处理(NLP)任务中的文本预处理和后处理,广泛应用于语音识别(ASR)、机器翻译、信息检索、聊天机器人等场景。

  • 文本标准化(TN):将非标准文本(如口语化表达)转换为标准书面形式。
    示例:
    输入:“二点五平方电线” → 输出:“2.5平方电线”
    输入:“你好 WeTextProcessing 1.0 船新版本儿” → 输出:“你好 WeTextProcessing 1.0 全新版本”。

  • 逆向文本标准化(ITN):将标准化文本还原为自然语言表达。
    示例:
    输入:“2.5平方电线” → 输出:“二点五平方电线”
    输入:“1:05” → 输出:“一点零五分”。

  • 你需要引入此库文件,或者直接下载解压到目录\main\cpp
    仓库地址:https://github.com/wenet-e2e/WeTextProcessing

APP源码目录结构

包含两个模块:

1、主模块:app

主模块包含了APP的界面和逻辑,主要包括:

(1) MainActivity.kt:
  • 语音识别功能:
    使用RecognizerService进行语音识别;管理音频录制(AudioRecord)和识别状态;支持中英文切换识别

  • 界面管理功能:
    主界面和悬浮窗(FloatingWindowManager)的切换;字体大小调整;文本显示区域管理

  • 文件操作功能:
    识别结果保存到文件;自动保存选项;清空当前结果

  • 权限管理:
    处理悬浮窗权限请求;音频录制权限检查

(2) FloatingWindowManager.kt:
  • 悬浮窗管理功能:
    创建和管理悬浮窗口界面;处理悬浮窗的显示、隐藏和移除;管理悬浮窗的布局参数
  • 界面交互功能:
    实现悬浮窗的拖动功能;处理点击事件显示/隐藏按钮栏;提供关闭按钮返回主界面
  • 字体大小控制:
    切换大/小字体显示;同步主界面和悬浮窗的字体大小设置
  • 屏幕方向锁定:
    提供方向锁定/解锁功能;处理横竖屏切换时的布局调整
  • 状态同步:
    与主界面(MainActivity)保持状态同步;共享文本显示框引用;同步字体大小设置
  • 布局管理:
    动态调整悬浮窗布局;处理不同屏幕方向下的布局变化;计算和更新控件尺寸

2、语音识别模块:wenetmodel

包含模型文件,Recognize.java、VoiceRectView.java、RecognizerService.java和cpp文件夹;

(1) assets目录:
  • wenetmodels_zn/:中文模型
  • wenetmodels_en/:英文模型
(2) Recognize.java:
  • 这个类是语音识别功能的核心桥梁,将 Java 层的调用转发到底层的 C++ 实现。主要功能是作为 Java 本地接口(JNI)的封装类,用于连接 Java 层与底层的 WeNet 语音识别引擎。具体功能包括:

  • 本地库加载:
    静态代码块负责加载名为 wenet 的本地库;包含加载成功/失败的日志记录;加载失败时会抛出运行时异常

  • JNI 方法声明:
    init(): 初始化模型和解码器;reset(): 重置解码器状态;acceptWaveform(): 接收音频波形数据;setInputFinished(): 设置输入结束标志;getFinished(): 检查识别是否完成;startDecode(): 启动解码线程;getResult(): 获取识别结果;clearResultAtEndpoint(): 清空端点累积结果;getState(): 获取当前解码状态

(3) VoiceRectView.java:用来绘制波形,我没用到。
(4) RecognizerService.java:
  • 语音识别服务管理:
    作为后台服务运行语音识别功能;处理服务的生命周期(onCreate, onStartCommand, onDestroy);提供前台服务通知

  • 模型管理:
    管理中文和英文语音识别模型;从assets目录解压模型文件到设备存储;提供模型初始化功能

  • 语音识别处理:
    启动/停止语音识别过程;处理音频数据队列;调用底层识别引擎(Recognize类)进行语音识别

  • 回调机制:
    提供初始化回调(InitializationCallback);提供识别结果回调(RecognitionCallback)

  • 多线程处理:
    使用独立线程进行模型初始化和语音识别;使用BlockingQueue处理音频数据;使用AtomicBoolean管理识别状态

  • 通知管理:
    创建和管理前台服务通知;处理通知点击返回主界面

(5) cpp目录:

包含wenet.cc文件,这是C++层的实现代码,负责加载模型、初始化识别器、处理音频数据、调用识别引擎和返回识别结果。

(6) 另外:

文件夹wenetmodel\build中的pytorch_android-1.13.0.aar和wenet-openfst-android-1.0.2.aar重新建构时不要删除。

3、调用逻辑:

    1)、初始化阶段
        // MainActivity中初始化
        recognizer = new RecognizerService(context)  // 构造服务
        recognizer.initialize(callback)  // 触发初始化流程
            extractAssets()  // 从APK包中解压模型文件到手机存储:data/user/0/com.lhh.voiceAssistant.debug/files/wenet_models/
                Recognize.init(modelDir)  // 初始化识别引擎
                Recognize.reset()  // 重置引擎状态
    2)、语音识别阶段
        recognizer.startRecognition(callback)  // 开始识别
        Recognize.startDecode()  // 启动解码器
        // 循环处理音频队列
        while (isProcessing) {
            audioQueue.poll()  // 获取音频数据
            Recognize.acceptWaveform(audioData)  // 送入引擎
            Recognize.getResult()  // 获取识别结果
            callback.onResult()  // 回调结果
        }
    3)、辅助方法调用
        // 音频输入
        recognizer.feedAudioData(short[])  // 填充音频数据到队列
            audioQueue.offer()  // 存入队列

        // 停止识别
        recognizer.stopRecognition()  
            Recognize.setInputFinished()  // 通知输入结束
    4)、关键调用时序图
        MainActivity.onCreate()
        → RecognizerService.initialize()
            → extractAssets() 
            → Recognize.init()
        → startRecognition()
            → 循环处理 audioQueue
        → stopRecognition()

经验教训:

  • 依赖库的版本错配是所有问题的根源:建议项目初期一定要和AI商量好最合适的版本配置。

  • CMAKE配置:
    由于模型使用的C语言,就要构建系统的配置文件CMakeLists.txt用来跨平台编译。遇到最多的问题就是各种库的链接和依赖顺序。 这在我的源码中,或者相应文件夹下通过文件STUDYME.md进行了记录。

下载地址

app地址(百度网盘) 提取码:mvbr,大约400多兆
源码地址(百度网盘) 提取码:btqy,也是400多兆,我把建构的过程文件删了,你需要重新建构。

请支持我坚持下去,不限额捐赠!!!

微信捐赠

支付宝捐赠