【Android +Tensroflow Lite】实现从基于机器学习语音中识别指令讲解及实战(超详细 附源码和演示视频)

简介: 【Android +Tensroflow Lite】实现从基于机器学习语音中识别指令讲解及实战(超详细 附源码和演示视频)

需要源码和配置文件请点赞关注收藏后评论区留言~~~

一、基于机器学习的语音推断

Tensorflow基于分层和模块化的设计思想,整个框架以C语言的编程接口为界,分为前端和后端两大部分 Tensorflow框架结构如下图

二、Tensorflow Lite简介

虽然Tensorflow是一款十分优秀的机器学习框架,但是它层次众多,不适合在单个设备上独立运行,为此Google推出了Tensorflow Lite,也就是Tensorflow的精简版,它可以在移动设备,嵌入式设备和物联网设备上运行Tensorflow模型

Tensorflow Lite包括下列两个主要组件

Tensorflow Lite解释器 允许在设备端的不同硬件上运行优化过的模型

Tensorflow Lite转换器 将Tensorflow模型转换为解释器使用的格式 同时通过优化提高应用性能

Tensorflow Lite允许在网络边缘的设备上执行机器学习任务,无须在设备与服务器之间来回发送数据 对开发者来说 在设备端执行机器学习任务有以下好处

缩短延迟 数组无须往返服务器

保护隐私 任何数据都不会离开设备

减少连接 不需要互联网连接

降低功耗 网络连接非常耗电

三、从语音中识别指令实战

首先给App工程手工添加Tensorflow Lite支持

implementation 'org.tensorflow:tensorflow-lite:2.5.0'

同时还要引入语音识别的配置文件 请点赞关注收藏后评论区留言私信博主

然后在活动代码中初始化Tensorflow Lite 分别读取标签配置 加载模型文件

运行效果如下

语音识别支持App支持识别英文单词指令 识别到的指令会高亮显示在App界面 并且标出吻合度

需要对着手机大声朗读上述英文单词 就可观察到语音推断结果

所以此处连接真机测试效果更好 模拟机不好录制语音~~~

演示视频如下

Android机器学习语音推断

 

 

四、代码

部分源码如下 需要全部代码请点赞关注收藏后评论区留言~~~

package com.example.voice;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.example.voice.adapter.WordRecyclerAdapter;
import com.example.voice.bean.WordInfo;
import com.example.voice.tensorflow.RecognizeCommands;
import org.tensorflow.lite.Interpreter;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
public class VoiceInferenceActivity extends AppCompatActivity {
    private final static String TAG = "VoiceInferenceActivity";
    private TextView tv_cost; // 声明一个文本视图对象
    private WordRecyclerAdapter mAdapter; // 英语单词的循环适配器
    private String[] mWordArray = new String[]{"Yes", "No", "Up", "Down", "Left", "Right", "On", "Off", "Stop", "Go"};
    private List<WordInfo> mWordList = new ArrayList<>(); // 单词信息列表
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_voice_inference);
        initView(); // 初始化视图
        initTensorflow(); // 初始化Tensorflow
    }
    // 初始化视图
    private void initView() {
        TextView tv_rate = findViewById(R.id.tv_rate);
        tv_rate.setText(SAMPLE_RATE + " Hz");
        tv_cost = findViewById(R.id.tv_cost);
        for (String word : mWordArray) {
            mWordList.add(new WordInfo(word, null));
        }
        RecyclerView rv_word = findViewById(R.id.rv_word);
        GridLayoutManager manager = new GridLayoutManager(this, 2);
        rv_word.setLayoutManager(manager);
        mAdapter = new WordRecyclerAdapter(this, mWordList);
        rv_word.setAdapter(mAdapter);
    }
    private static final int SAMPLE_RATE = 16000;
    private static final int SAMPLE_DURATION_MS = 1000;
    private static final int RECORDING_LENGTH = (int) (SAMPLE_RATE * SAMPLE_DURATION_MS / 1000);
    private static final long AVERAGE_WINDOW_DURATION_MS = 1000;
    private static final float DETECTION_THRESHOLD = 0.50f;
    private static final int SUPPRESSION_MS = 1500;
    private static final int MINIMUM_COUNT = 3;
    private static final long MINIMUM_TIME_BETWEEN_SAMPLES_MS = 30;
    private static final String LABEL_FILENAME = "conv_actions_labels.txt";
    private static final String MODEL_FILENAME = "conv_actions_frozen.tflite";
    // Working variables.
    private short[] recordBuffer = new short[RECORDING_LENGTH];
    private int recordOffset = 0;
    private boolean continueRecord = true;
    private Thread recordThread;
    private boolean continueRecognize = true;
    private Thread recognizeThread;
    private final ReentrantLock recordBufferLock = new ReentrantLock();
    private List<String> labelList = new ArrayList<>(); // 指令标签列表
    private RecognizeCommands recognizeCommands = null; // 待识别的指令
    private Interpreter.Options tfLiteOptions = new Interpreter.Options(); // 解释器选项
    private Interpreter tfLite; // Tensorflow Lite的解释器
    private long costTime; // 每次语音识别的耗费时间
    // 初始化Tensorflow
    private void initTensorflow() {
        Log.d(TAG, "Reading labels from: " + LABEL_FILENAME);
        try (BufferedReader br = new BufferedReader(new InputStreamReader(getAssets().open(LABEL_FILENAME)))) {
            String line;
            while ((line = br.readLine()) != null) {
                labelList.add(line);
            }
        } catch (Exception e) {
            throw new RuntimeException("Problem reading label file!", e);
        }
        Log.d(TAG, "labelList.size()=" + labelList.size());
        // 设置一个对象来平滑识别结果,以提高准确率
        recognizeCommands = new RecognizeCommands(
                labelList,
                AVERAGE_WINDOW_DURATION_MS,
                DETECTION_THRESHOLD,
                SUPPRESSION_MS,
                MINIMUM_COUNT,
                MINIMUM_TIME_BETWEEN_SAMPLES_MS);
        try {
            MappedByteBuffer tfLiteModel = loadModelFile(getAssets(), MODEL_FILENAME);
            tfLite = new Interpreter(tfLiteModel, tfLiteOptions);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        tfLite.resizeInput(0, new int[]{RECORDING_LENGTH, 1});
        tfLite.resizeInput(1, new int[]{1});
        startRecord(); // 开始录音
        startRecognize(); // 开始识别
    }
    private MappedByteBuffer loadModelFile(AssetManager assets, String modelFilename) throws Exception {
        Log.d(TAG, "modelFilename="+modelFilename);
        AssetFileDescriptor descriptor = assets.openFd(modelFilename);
        FileInputStream fis = new FileInputStream(descriptor.getFileDescriptor());
        FileChannel fileChannel = fis.getChannel();
        long startOffset = descriptor.getStartOffset();
        long declaredLength = descriptor.getDeclaredLength();
        return fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength);
    }
    // 开始录音
    public synchronized void startRecord() {
        if (recordThread != null) {
            return;
        }
        continueRecord = true;
        recordThread = new Thread(() -> record());
        recordThread.start();
    }
    // 停止录音
    public synchronized void stopRecord() {
        if (recordThread == null) {
            return;
        }
        continueRecord = false;
        recordThread = null;
    }
    // 录制音频
    private void record() {
        android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);
        // Estimate the buffer size we'll need for this device.
        int bufferSize = AudioRecord.getMinBufferSize(
                SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
        if (bufferSize == AudioRecord.ERROR || bufferSize == AudioRecord.ERROR_BAD_VALUE) {
            bufferSize = SAMPLE_RATE * 2;
        }
        short[] audioBuffer = new short[bufferSize / 2];
        AudioRecord record = new AudioRecord(
                MediaRecorder.AudioSource.DEFAULT,
                SAMPLE_RATE,
                AudioFormat.CHANNEL_IN_MONO,
                AudioFormat.ENCODING_PCM_16BIT,
                bufferSize);
        if (record.getState() != AudioRecord.STATE_INITIALIZED) {
            Log.e(TAG, "Audio Record can't initialize!");
            return;
        }
        record.startRecording();
        Log.d(TAG, "Start record");
        // Loop, gathering audio data and copying it to a round-robin buffer.
        while (continueRecord) {
            int numberRead = record.read(audioBuffer, 0, audioBuffer.length);
            int maxLength = recordBuffer.length;
            int newRecordOffset = recordOffset + numberRead;
            int secondCopyLength = Math.max(0, newRecordOffset - maxLength);
            int firstCopyLength = numberRead - secondCopyLength;
            // We store off all the data for the recognition thread to access. The ML
            // thread will copy out of this buffer into its own, while holding the
            // lock, so this should be thread safe.
            recordBufferLock.lock();
            try {
                System.arraycopy(audioBuffer, 0, recordBuffer, recordOffset, firstCopyLength);
                System.arraycopy(audioBuffer, firstCopyLength, recordBuffer, 0, secondCopyLength);
                recordOffset = newRecordOffset % maxLength;
            } finally {
                recordBufferLock.unlock();
            }
        }
        record.stop();
        record.release();
    }
    // 开始识别
    public synchronized void startRecognize() {
        if (recognizeThread != null) {
            return;
        }
        continueRecognize = true;
        recognizeThread = new Thread(() -> recognize());
        recognizeThread.start();
    }
    // 停止识别
    public synchronized void stopRecognize() {
        if (recognizeThread == null) {
            return;
        }
        continueRecognize = false;
        recognizeThread = null;
    }
    // 识别语音
    private void recognize() {
        Log.d(TAG, "Start recognition");
        short[] inputBuffer = new short[RECORDING_LENGTH];
        float[][] floatInputBuffer = new float[RECORDING_LENGTH][1];
        float[][] outputScores = new float[1][labelList.size()];
        int[] sampleRateList = new int[]{SAMPLE_RATE};
        // Loop, grabbing recorded data and running the recognition model on it.
        while (continueRecognize) {
            long startTime = System.currentTimeMillis();
            // The record thread places data in this round-robin buffer, so lock to
            // make sure there's no writing happening and then copy it to our own
            // local version.
            recordBufferLock.lock();
            try {
                int maxLength = recordBuffer.length;
                int firstCopyLength = maxLength - recordOffset;
                int secondCopyLength = recordOffset;
                System.arraycopy(recordBuffer, recordOffset, inputBuffer, 0, firstCopyLength);
                System.arraycopy(recordBuffer, 0, inputBuffer, firstCopyLength, secondCopyLength);
            } finally {
                recordBufferLock.unlock();
            }
            // We need to feed in float values between -1.0f and 1.0f, so divide the
            // signed 16-bit inputs.
            for (int i = 0; i < RECORDING_LENGTH; ++i) {
                floatInputBuffer[i][0] = inputBuffer[i] / 32767.0f;
            }
            Object[] inputArray = {floatInputBuffer, sampleRateList};
            Map<Integer, Object> outputMap = new HashMap<>();
            outputMap.put(0, outputScores);
            // Run the model.
            tfLite.runForMultipleInputsOutputs(inputArray, outputMap);
            // Use the smoother to figure out if we've had a real recognition event.
            final RecognizeCommands.RecognitionResult result =
                    recognizeCommands.processLatestResults(outputScores[0], System.currentTimeMillis());
            costTime = System.currentTimeMillis() - startTime;
            runOnUiThread( () -> {
                tv_cost.setText(costTime + " ms");
                // If we do have a new command, highlight the right list entry.
                if (!result.foundCommand.startsWith("_") && result.isNewCommand) {
                    int position = labelList.indexOf(result.foundCommand) - 2;
                    WordInfo word = mWordList.get(position);
                    word.percent = Math.round(result.score * 100) + "%";
                    mWordList.set(position, word);
                    mAdapter.notifyItemChanged(position);
                    new Handler(Looper.myLooper()).postDelayed(() -> {
                        word.percent = "";
                        mWordList.set(position, word);
                        mAdapter.notifyItemChanged(position);
                    }, 1500);
                }
            });
            try {
                // We don't need to run too frequently, so snooze for a bit.
                Thread.sleep(MINIMUM_TIME_BETWEEN_SAMPLES_MS);
            } catch (InterruptedException e) {
            }
        }
        Log.d(TAG, "End recognition");
    }
}

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

相关文章
|
2天前
|
Android开发 开发者 Kotlin
Android实战经验之Kotlin中快速实现MVI架构
MVI架构通过单向数据流和不可变状态,提供了一种清晰、可预测的状态管理方式。在Kotlin中实现MVI架构,不仅提高了代码的可维护性和可测试性,还能更好地应对复杂的UI交互和状态管理。通过本文的介绍,希望开发者能够掌握MVI架构的核心思想,并在实际项目中灵活应用。
21 8
|
3月前
|
Android开发
布谷语音软件开发:android端语音软件搭建开发教程
语音软件搭建android端语音软件开发教程!
|
4月前
|
缓存 前端开发 Android开发
Android实战之如何截取Activity或者Fragment的内容?
本文首发于公众号“AntDream”,介绍了如何在Android中截取Activity或Fragment的屏幕内容并保存为图片。包括截取整个Activity、特定控件或区域的方法,以及处理包含RecyclerView的复杂情况。
43 3
|
5月前
|
Android开发 开发者 索引
Android实战经验之如何使用DiffUtil提升RecyclerView的刷新性能
本文介绍如何使用 `DiffUtil` 实现 `RecyclerView` 数据集的高效更新,避免不必要的全局刷新,尤其适用于处理大量数据场景。通过定义 `DiffUtil.Callback`、计算差异并应用到适配器,可以显著提升性能。同时,文章还列举了常见错误及原因,帮助开发者避免陷阱。
446 9
|
5月前
|
开发工具 Android开发 git
Android实战之组件化中如何进行版本控制和依赖管理
本文介绍了 Git Submodules 的功能及其在组件化开发中的应用。Submodules 允许将一个 Git 仓库作为另一个仓库的子目录,有助于保持模块独立、代码重用和版本控制。虽然存在一些缺点,如增加复杂性和初始化时间,但通过最佳实践可以有效利用其优势。
71 3
|
4月前
|
Android开发
Android实战之如何快速实现自动轮播图
本文介绍了在 Android 中使用 `ViewPager2` 和自定义适配器实现轮播图的方法,包括添加依赖、布局配置、创建适配器及实现自动轮播等步骤。
156 0
|
5月前
|
Java Android开发 C++
🚀Android NDK开发实战!Java与C++混合编程,打造极致性能体验!📊
在Android应用开发中,追求卓越性能是不变的主题。本文介绍如何利用Android NDK(Native Development Kit)结合Java与C++进行混合编程,提升应用性能。从环境搭建到JNI接口设计,再到实战示例,全面展示NDK的优势与应用技巧,助你打造高性能应用。通过具体案例,如计算斐波那契数列,详细讲解Java与C++的协作流程,帮助开发者掌握NDK开发精髓,实现高效计算与硬件交互。
222 1
|
4月前
|
Android开发
Android开发显示头部Bar的需求解决方案--Android应用实战
Android开发显示头部Bar的需求解决方案--Android应用实战
50 0
|
编解码 Shell Android开发
|
编解码 Shell Android开发
Android视频录制命令screenrecord
转自http://blog.csdn.net/wirelessqa/article/details/22725581 Android视频录制命令screenrecord 你要知道: screenrecord是一个shell命令 支持Android4.
1177 0

热门文章

最新文章

  • 1
    【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 2
    Android历史版本与APK文件结构
  • 3
    【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
  • 4
    当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
  • 5
    APP-国内主流安卓商店-应用市场-鸿蒙商店上架之必备前提·全国公安安全信息评估报告如何申请-需要安全评估报告的资料是哪些-优雅草卓伊凡全程操作
  • 6
    【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
  • 7
    【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
  • 8
    【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 9
    【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
  • 10
    Cellebrite UFED 4PC 7.71 (Windows) - Android 和 iOS 移动设备取证软件