ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

sherpa-onnx Java Android 流式语音识别 Demo 详解:模型准备、识别主循环与服务架构

sherpa-onnx Java Android 流式语音识别 Demo 详解:模型准备、识别主循环与服务架构 sherpa-onnx Java Android 流式语音识别 Demo 详解模型准备、识别主循环与服务架构【免费下载链接】sherpa-onnxSpeech-to-text, text-to-speech, speaker diarization, speech enhancement, source separation, and VAD using next-gen Kaldi with onnxruntime without Internet connection. Support embedded systems, Android, iOS, HarmonyOS, Raspberry Pi, RISC-V, RK NPU, Axera NPU, Ascend NPU, x86_64 servers, websocket server/client, support 12 programming languages项目地址: https://gitcode.com/GitHub_Trending/sh/sherpa-onnx本篇基于 sherpa-onnx 仓库中的 Android Java Demo完整讲清如何在 Android 上用 Java 构建一个离线流式语音识别应用从下载流式 zipformer 中英双语模型并放入 assets 目录到前台服务中基于AudioRecord采集麦克风音频、按 0.1 秒分片喂给OnlineRecognizer解码、用端点检测切分语句的完整链路。读完本文你可以复制并运行该 Demo并理解MainActivity、识别 Service、ViewModel 三者之间的调用关系与 sherpa-onnx Java API 的核心用法。一、Demo 定位离线流式语音识别android/SherpaOnnxJavaDemo是 sherpa-onnx 提供的一个纯 Java 编写的 Android 示例功能非常聚焦启动后申请麦克风权限立即启动一个前台服务持续录音使用流式 transducer 模型zipformer对麦克风音频做实时增量解码通过端点检测endpoint判断一句话结束把带序号的识别结果逐行显示在界面上全程离线不依赖任何网络服务。工程采用最小化依赖compileSdk 34、minSdk 28、targetSdk 34Java 17 编译级别。核心依赖是 sherpa-onnx 官方发布的 AAR通过 JitPack 坐标引入见 app/build.gradledependencies { implementation androidx.appcompat:appcompat:1.3.1 implementation com.google.android.material:material:1.3.0 implementation androidx.constraintlayout:constraintlayout:1.1.3 implementation pub.devrel:easypermissions:3.0.0 // implementation files(.../sherpa-onnx-release.aar) // 也可以本地 AAR implementation com.github.k2-fsa.sherpa-onnx:sherpa-onnx:v1.13.7 }该 AAR 内封装了 sherpa-onnx 的 Java API源码位于 sherpa-onnx/java-api 目录以及 native 库MainActivity中用到的OnlineRecognizer、OnlineRecognizerConfig、OnlineStream等类均出自它。二、运行前准备下载模型并放入 assets这是 README 给出的核心操作步骤。运行 Demo 前必须先下载流式 zipformer 中英双语模型sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20并放到app/src/main/assets/下。1. 下载与整理模型文件README 中给出的完整命令如下假设你位于android/SherpaOnnxJavaDemo目录# Assume we are inside # /Users/fangjun/open-source/sherpa-onnx/android/SherpaOnnxJavaDemo cd app/src/main/assets/ wget https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20.tar.bz2 tar xvf sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20.tar.bz2 rm sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20.tar.bz2 mv sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20/encoder-epoch-99-avg-1.int8.onnx ./ mv sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20/decoder-epoch-99-avg-1.onnx ./ mv sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20/joiner-epoch-99-avg-1.int8.onnx ./ mv sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20/tokens.txt ./ rm -rf sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20/* mv encoder-epoch-99-avg-1.int8.onnx sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20/ mv decoder-epoch-99-avg-1.onnx sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20/ mv joiner-epoch-99-avg-1.int8.onnx sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20/ mv tokens.txt sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20/命令中多次mv是 README 的历史写法其本质目的只有一个让 assets 下最终只保留 transducer 模型所需的 4 个文件。整理后的目录结构应为app/src/main/assets/ └── sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20 ├── decoder-epoch-99-avg-1.onnx ├── encoder-epoch-99-avg-1.int8.onnx ├── joiner-epoch-99-avg-1.int8.onnx └── tokens.txt其中 encoder 与 joiner 是int8 量化版本文件名带.int8.后缀decoder 保持 fp32tokens.txt是词表。README 同时强调了一点记得删除用不到的文件以减小最终 APK 体积Remember to remove unused files to reduce the file size of the final APK。因为 assets 目录会原封不动打进 APK模型文件的体积直接决定 APK 大小。2. 模型路径与代码的对应关系Demo 源码中模型目录名是硬编码的见 SpeechSherpaRecognitionService.javaString modelDir sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20; OnlineTransducerModelConfig onlineTransducerModelConfig new OnlineTransducerModelConfig(); onlineTransducerModelConfig.setEncoder(modelDir /encoder-epoch-99-avg-1.int8.onnx); onlineTransducerModelConfig.setDecoder(modelDir /decoder-epoch-99-avg-1.onnx); onlineTransducerModelConfig.setJoiner(modelDir /joiner-epoch-99-avg-1.int8.onnx); OnlineModelConfig onlineModelConfig new OnlineModelConfig(); onlineModelConfig.setTransducer(onlineTransducerModelConfig); onlineModelConfig.setTokens(modelDir /tokens.txt); onlineModelConfig.setModelType(zipformer); onlineModelConfig.setDebug(true); OnlineRecognizerConfig config new OnlineRecognizerConfig(); config.setModelConfig(onlineModelConfig); recognizer new OnlineRecognizer(getAssets(), config);也就是说如果你换用其他流式模型不仅要替换 assets 里的文件还要同步修改setEncoder/setDecoder/setJoiner/setTokens的文件名以及setModelType例如其他 transducer 模型仍为zipformerCTC 模型则改用 CTC 对应的模型配置类。三、组件与调用链MainActivity、Service 与 ViewModel该 Demo 的组件分工清晰从源码可以梳理出如下调用链MainActivity请求麦克风权限MainActivity.java 使用 EasyPermissions 库动态申请RECORD_AUDIO授权通过后调用startSpeechService()用ContextCompat.startForegroundService()拉起识别服务并订阅AppViewModel中的LiveData把识别结果显示到TextView上Application作为 ViewModel 宿主Application.java 实现了ViewModelStoreOwner并持有静态单例使 Service 与 Activity 能够共享同一个AppViewModelAppViewModel承载结果AppViewModel.java 只有一个MutableLiveDataString speechRecognitionResult识别结果通过postValue()从工作线程投递回主线程SpeechSherpaRecognitionService承担全部识别逻辑录音、解码、结果推送都在这里下文详述。AndroidManifest.xml 中声明了两个必要权限与一个非导出 Serviceuses-permission android:nameandroid.permission.FOREGROUND_SERVICE / uses-permission android:nameandroid.permission.RECORD_AUDIO / ... service android:name.service.SpeechSherpaRecognitionService android:exportedfalse/FOREGROUND_SERVICE用于满足 Android 前台服务要求RECORD_AUDIO用于麦克风采集两者分别对应源码中的startForegroundService()与AudioRecord权限检查。四、音频采集16 kHz 单声道 16-bit PCMSpeechSherpaRecognitionService在onCreate()中完成音频子系统初始化源码private final int sampleRateInHz 16000; // L51 private int audioSource MediaRecorder.AudioSource.MIC; private int channelConfig AudioFormat.CHANNEL_IN_MONO; private int audioFormat AudioFormat.ENCODING_PCM_16BIT;关键参数含义参数取值说明sampleRateInHz16000流式 ASR 模型的输入采样率模型训练时即为 16 kHz必须一致channelConfigCHANNEL_IN_MONO单声道语音识别标准格式audioFormatENCODING_PCM_16BIT16-bit PCM每个样本 2 字节缓冲区getMinBufferSize() * 2在最小缓冲基础上翻倍降低丢帧风险代码在创建AudioRecord前会先检查RECORD_AUDIO权限未授权则直接return权限申请由 Activity 层负责。AudioRecord的startRecording()在识别器初始化完成后才调用随后启动一条专门的处理线程执行processSamples()。五、识别主循环0.1 秒分片、增量解码与端点处理识别的核心逻辑在processSamples()源码它体现了 sherpa-onnx 流式识别的标准工作模式OnlineStream stream recognizer.createStream(); double interval 0.1; int bufferSize (int) (interval * sampleRateInHz); // 1600 个采样点 short[] buffer new short[bufferSize]; while (isRecording) { int ret audioRecord ! null ? audioRecord.read(buffer, 0, buffer.length) : -1; if (ret 0) { float[] samples new float[ret]; for (int i 0; i ret; i) { samples[i] buffer[i] / 32768.0f; // 16-bit 转 float 归一化 } stream.acceptWaveform(samples, sampleRateInHz); while (recognizer.isReady(stream)) { recognizer.decode(stream); } boolean isEndpoint recognizer.isEndpoint(stream); String text recognizer.getResult(stream).getText(); if (isEndpoint) { float[] tailPaddings new float[(int) (0.8 * sampleRateInHz)]; stream.acceptWaveform(tailPaddings, sampleRateInHz); // 补 0.8s 静音 while (recognizer.isReady(stream)) { recognizer.decode(stream); } text recognizer.getResult(stream).getText(); } ... if (isEndpoint) { recognizer.reset(stream); ... idx 1; } appViewModel.setSpeechRecognitionResult(textToDisplay); } } stream.release();可以拆解为四个要点固定分片长度每 0.1 秒1600 个采样点读取一次麦克风数据。流式模型本身不关心分片大小但固定分片能稳定解码节奏归一化输入16-bit 短整型除以32768.0f转为[-1, 1)区间的 float再交给stream.acceptWaveform(samples, 16000)增量解码acceptWaveform后循环执行isReady → decode直到流中不再有可解码帧。isReady表示内部队列里还有足够特征帧可供解码一步端点endpoint处理isEndpoint返回 true 时说明一句话已说完。此时代码先补一段 0.8 秒的全零静音0.8 * sampleRateInHz个采样点再解码是为了让模型把句尾未吐出的 token 收尾得到更完整的最终文本随后reset(stream)清空该流状态为下一句话做准备并用自增索引idx给每句话编号后累积到lastText中显示。这些isReady/decode/isEndpoint/reset/getResult/createStream方法在 Java API 中是对 native 层的薄封装。从 OnlineRecognizer.java 源码看Java 对象只持有一个long ptrnative 识别器指针所有方法最终都落到private native方法上见 native 方法声明解码计算实际发生在 C 层onnxruntime 推理因此 Java 侧无需关心模型张量细节。六、识别器配置OnlineRecognizerConfig 的默认值Demo 只显式设置了OnlineModelConfigtransducer 三件套 tokens 模型类型其余参数都走OnlineRecognizerConfig的默认值。从 OnlineRecognizerConfig.java 的 Builder 源码可以看到完整默认配置配置项默认值含义enableEndpointtrue启用端点检测Demo 的按句切分正依赖它decodingMethodgreedy_search贪心解码流式 transducer 场景下的常规选择maxActivePaths4用于 beam search 时的最大假设路径数hotwordsFile/hotwordsScore/1.5热词文件与偏置分数Demo 未启用blankPenalty0.0blank 惩罚项featConfig/lmConfig/hr等各自 Builder 默认值特征提取、语言模型、同音字替换其中与按句切分最相关的是endpointConfig。Demo 未显式配置因此使用 EndpointConfig.java 中定义的三条默认规则规则条件默认参数rule1不要求含非静音尾部静音 ≥ 2.4s 即判端点rule2必须含非静音语音后静音 ≥ 1.4s 即判端点rule3不要求含非静音语句长度 ≥ 20s 强制判端点防止超长流也就是说正常语速下用户停顿约 1.4 秒以上Demo 就会输出一行新结果如果一直不说话2.4 秒静音也会触发端点此时文本为空代码里有空文本保护不会误加编号连续说话超过 20 秒会被强制截断为一句。若要在自己项目中调整切句节奏通过OnlineRecognizerConfig.Builder.setEndpointConfig(...)修改上述阈值即可。七、Assets 复制到内部存储initializeSherpaDirDemo 在创建识别器前会先把模型从 APK 的 assets 递归复制到应用私有目录getFilesDir()见 initializeSherpaDirAssetManager assetManager getAssets(); File outDir new File(getFilesDir(), internalDir); ... String[] assets assetManager.list(assetDir); for (String asset : assets) { if (assetManager.list(assetPath).length 0) { outFile.mkdirs(); initializeSherpaDir(assetPath, internalDir / asset); // 递归复制子目录 } else { // 1KB 缓冲逐块拷贝文件 } }该函数先判断目标目录是否存在、再mkdirs()然后按assetManager.list()的结果递归处理目录继续递归文件则用 1024 字节缓冲流式拷贝。这一设计使模型文件以真实文件形式存在于内部存储便于 native 层按路径加载同时也会复制到内部存储中做缓存避免每次直接从压缩的 APK 读取。拷贝失败仅打日志Log.e(ModelCopy, ...)不中断流程——但此时识别器初始化大概率会失败排查问题时应首先检查 assets 里模型四件套是否齐全即第二节强调的目录结构。八、前台服务、通知与生命周期管理由于识别是常驻的录音任务Demo 把逻辑放在前台服务中并做了完整的生命周期处理通知渠道createNotificationChannel()在 Android 8.0 创建speech_channelIMPORTANCE_LOW级别startForegroundService()构建通知并调用startForeground(1, notification)图标为 ic_bg_mic_24.xml避免被系统在后台杀掉粘性重启onStartCommand()返回START_STICKY服务被系统回收后会被重启资源释放onDestroy()中依次执行audioRecord.stop()、audioRecord.release()、executor.shutdown()、stopForeground(true)确保麦克风与线程池不泄漏线程模型模型初始化在单线程ExecutorService中异步执行executor.execute(this::initializeSherpa)识别循环跑在独立的recordingThread上UI 更新则通过postValue回到主线程——三个线程各司其职这也是 Android 上处理音频识别任务的常见结构。九、运行验证与扩展要点按上述步骤准备模型后用 Android Studio 打开 android/SherpaOnnxJavaDemo 目录即可编译安装。验证时注意首次启动会弹出麦克风权限请求允许后 App 立即进入前台录音状态通知栏出现 Running in the foreground中英双语说话时停顿约 1.4 秒以上界面会新增一行序号: 识别文本由于onlineModelConfig.setDebug(true)打开了调试日志可用adb logcat观察 sherpa-onnx 的初始化与解码日志若要减小 APK 体积确认 assets 下只有第二节列出的 4 个模型文件若要更换模型例如其他流式 zipformer 变体或 CTC 模型需同步修改initializeSherpa()中的文件名与setModelType并确认对应模型支持流式解码。十、小结SherpaOnnxJavaDemo的价值在于用一个最小工程展示了 sherpa-onnx 在 Android 上做离线流式 ASR的完整范式模型侧assets 放置流式 transducer 四件套encoder/decoder/joiner/tokens注意 int8 量化文件与 APK 体积采集侧AudioRecord16 kHz 单声道 16-bit PCM0.1 秒分片short → float归一化后acceptWaveform解码侧isReady → decode增量解码isEndpoint触发后补 0.8s 静音收尾、reset进入下一句默认端点规则为 2.4s/1.4s/20s 三条工程侧前台服务 通知渠道保证常驻ViewModel/LiveData 打通 Service 到 UI 的结果回传onDestroy完整释放音频与线程资源。对照 SpeechSherpaRecognitionService.java 与 sherpa-onnx/java-api 中的 API 源码可以将这套结构平移到自己的 Android 产品中替换模型配置即可适配 sherpa-onnx 支持的其他流式识别模型。【免费下载链接】sherpa-onnxSpeech-to-text, text-to-speech, speaker diarization, speech enhancement, source separation, and VAD using next-gen Kaldi with onnxruntime without Internet connection. Support embedded systems, Android, iOS, HarmonyOS, Raspberry Pi, RISC-V, RK NPU, Axera NPU, Ascend NPU, x86_64 servers, websocket server/client, support 12 programming languages项目地址: https://gitcode.com/GitHub_Trending/sh/sherpa-onnx创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表