Flutter音频识别插件tflite_audio的使用

发布于 1周前 作者 zlyuanteng 来自 Flutter

Flutter音频识别插件tflite_audio的使用

简介

tflite_audio 是一个用于Flutter的音频分类插件,支持iOS和Android平台。它不仅可以处理存储的音频文件,还可以处理实时录音,并支持Google Teachable Machine模型。

插件特点

  1. 音频文件识别:支持存储的单声道WAV文件。
  2. 实时录音识别:支持实时录音并进行音频分类。
  3. 可调参数:提供多种参数供用户调整录音和推理过程。
  4. 自动重塑音频输入:自动调整音频输入的形状和转置。
  5. 多种模型类型支持
    • Google Teachable Machine(原始音频输入)
    • 原始音频输入
    • 解码WAV输入
    • 实验性特征:频谱图、梅尔频谱图、MFCC输入

安装与配置

添加依赖

pubspec.yaml文件中添加tflite_audio依赖:

dependencies:
  tflite_audio: ^最新版本号

运行以下命令安装依赖:

flutter pub get

添加模型和标签

  1. 创建一个assets文件夹,并将你的tflite模型和标签文件放入其中。
  2. pubspec.yaml文件中添加模型和标签文件路径:
assets:
  - assets/your_model.tflite
  - assets/your_labels.txt

权限设置

Android

AndroidManifest.xml文件中添加录音权限:

<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

build.gradle文件中添加以下配置:

aaptOptions {
    noCompress 'tflite'
}

如果使用Google Teachable Machine模型,还需要在build.gradle中启用select-ops:

dependencies {
    implementation 'org.tensorflow:tensorflow-lite-select-tf-ops:+'
}

iOS

Info.plist文件中添加麦克风使用描述:

<key>NSMicrophoneUsageDescription</key>
<string>Record audio for playback</string>

Podfile中设置最低iOS版本:

platform :ios, '12.0'

如果使用Google Teachable Machine模型,还需要在Podfile中添加以下行:

pod 'TensorFlowLiteSelectTfOps', '~> 2.6.0'

并在Xcode中强制加载Select Ops:

  1. 打开项目工作区:<YourApp>/ios/Runner.xcworkspace
  2. 选择顶级Runner
  3. 选择Runner项目
  4. 在“Build Settings”选项卡中,点击“Other Linker Flags”
  5. 添加以下行:
-force_load $(SRCROOT)/Pods/TensorFlowLiteSelectTfOps/Frameworks/TensorFlowLiteSelectTfOps.framework/TensorFlowLiteSelectTfOps

使用示例

示例代码

以下是一个完整的示例代码,展示了如何使用tflite_audio插件进行音频识别:

import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:developer';
import 'package:tflite_audio/tflite_audio.dart';
import 'package:flutter/services.dart';
import 'dart:convert';

void main() => runApp(const MyApp());

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
  final isRecording = ValueNotifier<bool>(false);
  Stream<Map<dynamic, dynamic>>? result;

  // Google Teachable Machine模型示例
  final String model = 'assets/google_teach_machine_model.tflite';
  final String label = 'assets/google_teach_machine_label.txt';
  final String inputType = 'rawAudio';
  final String audioDirectory = 'assets/sample_audio_44k_mono.wav';
  final int sampleRate = 44100;
  final int bufferSize = 11016;

  // 可选参数
  final bool outputRawScores = false;
  final int numOfInferences = 5;
  final int numThreads = 1;
  final bool isAsset = true;

  // 调整模型检测参数
  final double detectionThreshold = 0.3;
  final int averageWindowDuration = 1000;
  final int minimumTimeBetweenSamples = 30;
  final int suppressionTime = 1500;

  @override
  void initState() {
    super.initState();
    TfliteAudio.loadModel(
      inputType: inputType,
      model: model,
      label: label,
    );
  }

  void getResult() {
    result = TfliteAudio.startAudioRecognition(
      sampleRate: sampleRate,
      bufferSize: bufferSize,
      numOfInferences: numOfInferences,
      detectionThreshold: detectionThreshold,
      averageWindowDuration: averageWindowDuration,
      minimumTimeBetweenSamples: minimumTimeBetweenSamples,
      suppressionTime: suppressionTime,
    );

    result?.listen((event) => log("Recognition Result: " + event["recognitionResult"].toString())).onDone(() => isRecording.value = false);
  }

  Future<List<String>> fetchLabelList() async {
    List<String> _labelList = [];
    await rootBundle.loadString(label).then((q) {
      for (String i in const LineSplitter().convert(q)) {
        _labelList.add(i);
      }
    });
    return _labelList;
  }

  String showResult(AsyncSnapshot snapshot, String key) => snapshot.hasData ? snapshot.data[key].toString() : '0 ';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        key: _scaffoldKey,
        appBar: AppBar(
          title: const Text('Tflite-audio/speech'),
        ),
        body: StreamBuilder<Map<dynamic, dynamic>>(
          stream: result,
          builder: (BuildContext context, AsyncSnapshot<Map<dynamic, dynamic>> inferenceSnapshot) {
            return FutureBuilder(
              future: fetchLabelList(),
              builder: (BuildContext context, AsyncSnapshot<List<String>> labelSnapshot) {
                switch (inferenceSnapshot.connectionState) {
                  case ConnectionState.none:
                    if (labelSnapshot.hasData) {
                      return labelListWidget(labelSnapshot.data);
                    } else {
                      return const CircularProgressIndicator();
                    }
                  case ConnectionState.waiting:
                    return Stack(children: [
                      Align(
                        alignment: Alignment.bottomRight,
                        child: inferenceTimeWidget('calculating..'),
                      ),
                      labelListWidget(labelSnapshot.data),
                    ]);
                  default:
                    return Stack(children: [
                      Align(
                        alignment: Alignment.bottomRight,
                        child: inferenceTimeWidget(showResult(inferenceSnapshot, 'inferenceTime') + 'ms'),
                      ),
                      labelListWidget(labelSnapshot.data, showResult(inferenceSnapshot, 'recognitionResult')),
                    ]);
                }
              },
            );
          },
        ),
        floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
        floatingActionButton: ValueListenableBuilder(
          valueListenable: isRecording,
          builder: (context, value, widget) {
            if (value == false) {
              return FloatingActionButton(
                onPressed: () {
                  isRecording.value = true;
                  setState(() {
                    getResult();
                  });
                },
                backgroundColor: Colors.blue,
                child: const Icon(Icons.mic),
              );
            } else {
              return FloatingActionButton(
                onPressed: () {
                  log('Audio Recognition Stopped');
                  TfliteAudio.stopAudioRecognition();
                },
                backgroundColor: Colors.red,
                child: const Icon(Icons.adjust),
              );
            }
          },
        ),
      ),
    );
  }

  Widget labelListWidget(List<String>? labelList, [String? result]) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: labelList!.map((labels) {
          if (labels == result) {
            return Padding(
              padding: const EdgeInsets.all(5.0),
              child: Text(
                labels.toString(),
                textAlign: TextAlign.center,
                style: const TextStyle(
                  fontWeight: FontWeight.bold,
                  fontSize: 25,
                  color: Colors.green,
                ),
              ),
            );
          } else {
            return Padding(
              padding: const EdgeInsets.all(5.0),
              child: Text(
                labels.toString(),
                textAlign: TextAlign.center,
                style: const TextStyle(
                  fontWeight: FontWeight.bold,
                  color: Colors.black,
                ),
              ),
            );
          }
        }).toList(),
      ),
    );
  }

  Widget inferenceTimeWidget(String result) {
    return Padding(
      padding: const EdgeInsets.all(20.0),
      child: Text(
        result,
        textAlign: TextAlign.center,
        style: const TextStyle(
          fontWeight: FontWeight.bold,
          fontSize: 20,
          color: Colors.black,
        ),
      ),
    );
  }
}

代码说明

  1. 初始化模型:在initState方法中加载模型和标签文件。
  2. 开始录音识别:通过startAudioRecognition方法启动录音识别,并设置相关参数。
  3. 结果监听:使用StreamBuilder监听识别结果,并在界面上显示。
  4. 浮行动作按钮:控制录音的开始和停止。

参数说明

  • outputRawScores:是否输出原始分数。
  • numThreads:线程数,增加线程数可以减少推理时间,但会占用更多CPU资源。
  • isAsset:模型、标签或音频文件是否在资产文件中。
  • numOfInferences:录音和推理的次数。
  • sampleRate:采样率,推荐值为16000、22050、44100。
  • bufferSize:缓冲区大小,影响录音长度。
  • detectionThreshold:检测阈值,忽略概率低于该阈值的预测。
  • averageWindowDuration:用于移除过时的结果。
  • minimumTimeBetweenSamples:忽略频繁的结果。
  • suppressionTime:避免过早触发检测。

常见问题

  1. 如何调整录音长度/时间

    • 降低bufferSize值可以增加录音时间。
    • 降低采样率也可以增加录音时间。
    • 注意不要将值设置得过低,以免影响模型准确性。
  2. 如何减少模型的误报

    • 增加detectionThresholdaverageWindowDuration的默认值。
  3. iOS构建错误

    • 检查Podfilebuild.gradle中的配置。
    • 运行flutter cleanflutter pub get
  4. TensorFlow Lite错误

    • 确保启用了select-ops。
    • 尝试在实际设备上运行应用。

希望这些信息对你有所帮助!如果有任何问题,欢迎随时提问。


更多关于Flutter音频识别插件tflite_audio的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter音频识别插件tflite_audio的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter项目中使用tflite_audio插件进行音频识别的示例代码。这个插件允许你使用TensorFlow Lite模型进行音频识别。

首先,确保你已经在pubspec.yaml文件中添加了tflite_audiotflite依赖:

dependencies:
  flutter:
    sdk: flutter
  tflite: ^2.0.0  # 请检查最新版本号
  tflite_audio: ^0.6.0  # 请检查最新版本号

然后运行flutter pub get来安装这些依赖。

接下来,你需要一个已经训练好的TensorFlow Lite模型。假设你已经有一个模型文件model.tflite,并且这个模型接受音频输入并输出识别结果。

下面是一个简单的Flutter应用示例,展示如何使用tflite_audio插件加载模型并进行音频识别:

import 'package:flutter/material.dart';
import 'package:tflite/tflite.dart';
import 'package:tflite_audio/tflite_audio.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late Interpreter _interpreter;
  late TfliteAudio? _tfliteAudio;

  @override
  void initState() {
    super.initState();
    loadModel();
  }

  @override
  void dispose() {
    _interpreter.close();
    _tfliteAudio?.dispose();
    super.dispose();
  }

  Future<void> loadModel() async {
    // Load TensorFlow Lite model
    _interpreter = await Tflite.loadModel(
      model: 'assets/model.tflite',
      labels: 'assets/labels.txt',  // 如果你的模型有标签文件
    );

    // Initialize TfliteAudio
    _tfliteAudio = TfliteAudio(
      interpreter: _interpreter,
      sampleRate: 16000,  // 根据你的模型要求设置采样率
      numChannels: 1,     // 根据你的模型要求设置通道数
      audioInputFormat: AudioInputFormat.PCM16,
    );
  }

  Future<void> recognizeSpeech(File audioFile) async {
    try {
      var recognitionResult = await _tfliteAudio!.recognizeSpeechFromFile(audioFile.path);
      print('Recognition Result: $recognitionResult');
      // 在这里处理识别结果
    } catch (e) {
      print('Error during speech recognition: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter Audio Recognition'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              ElevatedButton(
                onPressed: () async {
                  // 这里你需要让用户选择一个音频文件,或者录制一个音频文件
                  // 这里假设你已经有一个音频文件路径 audioFilePath
                  String audioFilePath = 'path/to/your/audio/file.wav'; // 替换为你的音频文件路径
                  File audioFile = File(audioFilePath);
                  await recognizeSpeech(audioFile);
                },
                child: Text('Recognize Speech'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

注意事项:

  1. 模型文件:确保你的model.tflitelabels.txt(如果有)文件已经放在assets文件夹中,并且在pubspec.yaml中正确声明:

    flutter:
      assets:
        - assets/model.tflite
        - assets/labels.txt
    
  2. 音频文件:在上面的示例中,recognizeSpeech函数接受一个File对象作为音频输入。你需要确保音频文件的格式和采样率与你的TensorFlow Lite模型相匹配。

  3. 权限:如果你的应用需要录制音频,确保在AndroidManifest.xmlInfo.plist中声明了相应的权限。

  4. 错误处理:在实际应用中,你应该添加更多的错误处理和用户反馈机制,以处理模型加载失败、音频文件读取错误等情况。

这个示例提供了一个基本的框架,你可以根据需要进行扩展和修改。

回到顶部