sherpa 语音唤醒

可以参考其他sherpa的语音唤醒文章。

主要代码:

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.text.TextUtils;
import android.util.Log;

import com.k2fsa.sherpa.onnx.FeatureConfig;
import com.k2fsa.sherpa.onnx.KeywordSpotter;
import com.k2fsa.sherpa.onnx.KeywordSpotterConfig;
import com.k2fsa.sherpa.onnx.OnlineModelConfig;
import com.k2fsa.sherpa.onnx.OnlineStream;
import com.k2fsa.sherpa.onnx.OnlineTransducerModelConfig;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.concurrent.Executors;

public class KwsTools {
    private static AudioRecord audioRecord;
    private static final int sampleRateInHz = 16000;
    private static final int channelConfig = AudioFormat.CHANNEL_IN_MONO;
    private static final int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
    private static KeywordSpotter spotter;
    private static OnlineStream stream;
    private static boolean run = true;
    private static KeywordSpotterConfig keyConfig = null;
    public static void init(Context context,Result result) {
        Executors.newSingleThreadExecutor().submit(() -> {
            if (context.checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
                return;
            }

            if (keyConfig == null)
            {
                String path = context.getExternalFilesDir(null) + "";
                String sherpa = "sherpa-onnx-kws-zipformer-wenetspeech-3.3M-2024-01-01";
                copyAsset(context,sherpa, path);
                copyAsset(context,"dexopt", path);
                String decoder = path + "/" + sherpa + "/decoder-epoch-12-avg-2-chunk-16-left-64.onnx";
                String encoder = path + "/" + sherpa + "/encoder-epoch-12-avg-2-chunk-16-left-64.onnx";
                String joiner = path + "/" + sherpa + "/joiner-epoch-12-avg-2-chunk-16-left-64.onnx";
                String tokens = path + "/" + sherpa + "/tokens.txt";
                String keywords = path + "/" + sherpa + "/keywords.txt";

                OnlineTransducerModelConfig ctc = OnlineTransducerModelConfig.builder()
                        .setDecoder(decoder).setEncoder(encoder).setJoiner(joiner).build();
                OnlineModelConfig modelConfig = OnlineModelConfig.builder()
                        .setTransducer(ctc).setTokens(tokens).setModelType("zipformer2").build();
                keyConfig = KeywordSpotterConfig.builder()
                        .setFeatureConfig(new FeatureConfig.Builder().setSampleRate(sampleRateInHz).setFeatureDim(80).build())
                        .setOnlineModelConfig(modelConfig)
                        .setKeywordsFile(keywords).build();
                spotter = new KeywordSpotter(keyConfig);
            }

            stream = spotter.createStream();
            try {
                int bufferSize = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
                audioRecord = new AudioRecord(
                        MediaRecorder.AudioSource.MIC, // 设置为麦克风录音
                        sampleRateInHz, // 采样率
                        channelConfig, // 单声道输入
                        audioFormat, // 16位PCM编码
                        bufferSize * 2 // 缓冲区大小
                );
                audioRecord.startRecording();
            } catch (IllegalStateException e)
            {
                Log.d("kws audio record error:", e.toString());
                return;
            }

            run = true;
            int i = (int) (sampleRateInHz * 0.1d);
            short[] sArr = new short[i];
            while (run) {
                Integer valueOf = audioRecord != null ? audioRecord.read(sArr, 0, i) : null;
                if (valueOf != null && valueOf > 0) {
                    float[] fArr = new float[valueOf];
                    for (int i2 = 0; i2 < valueOf; i2++) {
                        fArr[i2] = sArr[i2] / 32768.0f;
                    }
                    if (stream != null && spotter != null)
                    {
                        stream.acceptWaveform(fArr, sampleRateInHz);
                        while (spotter.isReady(stream)) {
                            spotter.decode(stream);
                            String text = spotter.getResult(stream).getKeyword();
                            if (!TextUtils.isEmpty(text)){
                                spotter.reset(stream);
                                result.result(text);
                                run = false;
                                break;
                            }
                        }
                    }
                }
            }
        });
    }

    public static void copyAsset(Context context,String assetPath, String root) {
        AssetManager assetManager = context.getAssets(); // 获取 AssetManager
        String[] files = null;
        try {
            // 获取指定目录下的所有文件和目录
            files = assetManager.list(assetPath);
        } catch (IOException e) {
            e.printStackTrace();
        }
        File file = new File(root + "/" + assetPath);
        if (!file.exists()) {
            file.mkdirs();
        }

        if (files != null) {
            for (String filename : files) {
                String assetFilePath = assetPath + "/" + filename; // 资源文件的完整路径
                String destFilePath = root + "/" + assetPath + "/" + filename; // 目标文件的完整路径
                try {
                    // 判断是否为目录
                    if (assetManager.list(assetFilePath) != null && assetManager.list(assetFilePath).length > 0) {
                        // 如果是目录,则创建目录并递归复制
                        boolean res = new File(destFilePath).mkdirs();
                        copyAsset(context,assetFilePath, root);
                    } else {
                        InputStream in = assetManager.open(assetFilePath);
                        OutputStream out = null;
                        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
                            out = Files.newOutputStream(Paths.get(destFilePath));
                        } else {
                            out = new FileOutputStream(destFilePath);
                        }
                        if (out == null) continue;

                        // 复制文件
                        byte[] buffer = new byte[1024];
                        int read;
                        while ((read = in.read(buffer)) != -1) {
                            out.write(buffer, 0, read);
                        }
                        // 关闭流
                        in.close();
                        out.flush();
                        out.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public interface Result {
        void result(String result);
    }
    public static void Stop(){
        run = false;
        if (audioRecord != null && audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING){
            audioRecord.stop();
        }
    }

    public static void destroy() {
        run = false;
        if (audioRecord != null){
            audioRecord.release();
            audioRecord = null;
        }
        if (stream != null){
            stream.release();
            stream = null;
        }
        if (spotter != null){
            spotter.release();
            spotter = null;
        }
    }
}

语音模型文件如下,实际使用只需以下相关模型文件,keywords可以手动通过tokens找到对应的音标组合,无需用命令行去跑:

我只用了arm64的,里面的so也只需要这两种,另两种是c++相关的。其他版本需要的话添加对应的文件夹和文件

同时build.gradle里的android {}里添加下面这段代码

libs里要添加这个,最新版的java库文件,java几看build.gradle的compileOptions,我用的是java8

k2-fsa 是一个开源的语音处理工具包,由 K2-FSA 团队开发,专注于提升语音识别、合成以及说话人识别等任务的效率与灵活性。它在设计上融合了现代深度学习框架的优势,并通过高效的推理引擎优化了模型部署和跨平台适配的问题[^1]。 ### 主要功能 1. **语音识别(ASR)** k2-fsa 支持端到端的语音识别流程,包括声学建模、语言建模和解码器的整合。其核心优势在于能够高效地构建和操作有限状态自动机(FST),从而实现快速准确的语音识别。 2. **语音合成(TTS)** 除了语音识别,k2-fsa 还支持文本转语音(TTS)任务,可以用于生成自然流畅的语音输出。 3. **说话人识别** 提供了基于深度学习的说话人识别模块,可用于身份验证或个性化语音助手等场景。 4. **跨平台兼容性** k2-fsa 在设计时充分考虑了跨平台部署的需求,支持多种操作系统(如 Linux、Windows 和 macOS)以及嵌入式设备。 5. **低资源消耗与实时性** 通过优化的推理引擎和轻量级模型架构,k2-fsa 能够在低资源环境下保持高性能,适用于边缘计算和实时语音处理场景。 --- ### 使用方法 #### 安装 k2-fsa 可以通过 Python 的 `pip` 工具进行安装: ```bash pip install k2-fsa ``` 如果需要从源码编译安装,请参考官方文档中的详细步骤。 #### 基本使用示例 以下是一个简单的语音识别流程示例: ```python import k2 import torch # 加载预训练模型和解码图 model = torch.load(&#39;asr_model.pt&#39;) decoding_graph = k2.Fsa.from_dict(torch.load(&#39;graph.fst&#39;)) # 准备输入语音特征(假设为 log-mel 特征) features = torch.randn(1, 100, 80) # batch_size=1, time_steps=100, feature_dim=80 # 进行前向推理 with torch.no_grad(): logits = model(features) lattice = k2.intersect(decoding_graph, logits) # 解码得到识别结果 best_path = k2.shortest_path(lattice) text = best_path.labels_to_str() print("识别结果:", text) ``` #### 构建自定义 FST k2-fsa 提供了强大的 FST 操作接口,可以用于构建复杂的解码图: ```python # 创建一个简单的 FST s = &#39;&#39;&#39; 0 1 a A 0.5 1 2 b B 1.0 2 3 c C 0.7 3 &#39;&#39;&#39; fst = k2.Fsa.from_str(s) fst = fst.to(&#39;cpu&#39;) # 打印 FST 结构 print(fst) ``` --- ### 开发与社区支持 k2-fsa 拥有活跃的开发者社区,用户可以通过 GitHub 获取最新版本的代码和文档。同时,社区也提供了丰富的教程和示例代码,帮助开发者快速上手。 --- ### 应用场景 - **智能语音助手** - **电话客服系统** - **语音翻译** - **无障碍技术** - **语音控制设备** ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值