Fangjun Kuang
Committed by GitHub

Add Dart API for keyword spotter (#1162)

@@ -4,6 +4,10 @@ set -ex @@ -4,6 +4,10 @@ set -ex
4 4
5 cd dart-api-examples 5 cd dart-api-examples
6 6
  7 +pushd keyword-spotter
  8 +./run-zh.sh
  9 +popd
  10 +
7 pushd non-streaming-asr 11 pushd non-streaming-asr
8 12
9 echo '----------SenseVoice----------' 13 echo '----------SenseVoice----------'
@@ -108,6 +108,7 @@ jobs: @@ -108,6 +108,7 @@ jobs:
108 cp scripts/dart/non-streaming-asr-pubspec.yaml dart-api-examples/non-streaming-asr/pubspec.yaml 108 cp scripts/dart/non-streaming-asr-pubspec.yaml dart-api-examples/non-streaming-asr/pubspec.yaml
109 cp scripts/dart/streaming-asr-pubspec.yaml dart-api-examples/streaming-asr/pubspec.yaml 109 cp scripts/dart/streaming-asr-pubspec.yaml dart-api-examples/streaming-asr/pubspec.yaml
110 cp scripts/dart/tts-pubspec.yaml dart-api-examples/tts/pubspec.yaml 110 cp scripts/dart/tts-pubspec.yaml dart-api-examples/tts/pubspec.yaml
  111 + cp scripts/dart/kws-pubspec.yaml dart-api-examples/keyword-spotter/pubspec.yaml
111 cp scripts/dart/sherpa-onnx-pubspec.yaml flutter/sherpa_onnx/pubspec.yaml 112 cp scripts/dart/sherpa-onnx-pubspec.yaml flutter/sherpa_onnx/pubspec.yaml
112 113
113 .github/scripts/test-dart.sh 114 .github/scripts/test-dart.sh
1 ## 1.10.17 1 ## 1.10.17
2 2
3 * Support SenseVoice CTC models. 3 * Support SenseVoice CTC models.
  4 +* Add Dart API for keyword spotter.
4 5
5 ## 1.10.16 6 ## 1.10.16
6 7
1 !run*.sh 1 !run*.sh
  2 +# See https://www.dartlang.org/guides/libraries/private-files
  3 +
  4 +# Files and directories created by pub
  5 +.dart_tool/
  6 +.packages
  7 +build/
  8 +# If you're building an application, you may want to check-in your pubspec.lock
  9 +pubspec.lock
  10 +
  11 +# Directory created by dartdoc
  12 +# If you don't generate documentation locally you can remove this line.
  13 +doc/api/
  14 +
  15 +# dotenv environment variables file
  16 +.env*
  17 +
  18 +# Avoid committing generated Javascript files:
  19 +*.dart.js
  20 +*.info.json # Produced by the --dump-info flag.
  21 +*.js # When generated by dart2js. Don't specify *.js if your
  22 + # project includes source files written in JavaScript.
  23 +*.js_
  24 +*.js.deps
  25 +*.js.map
  26 +
  27 +.flutter-plugins
  28 +.flutter-plugins-dependencies
  1 +# https://dart.dev/guides/libraries/private-files
  2 +# Created by `dart pub`
  3 +.dart_tool/
  1 +## 1.0.0
  2 +
  3 +- Initial version.
  1 +# Introduction
  2 +
  3 +This directory contains keyword spotting examples using
  4 +Dart API from [sherpa-onnx](https://github.com/k2-fsa/sherpa-onnx)
  1 +# This file configures the static analysis results for your project (errors,
  2 +# warnings, and lints).
  3 +#
  4 +# This enables the 'recommended' set of lints from `package:lints`.
  5 +# This set helps identify many issues that may lead to problems when running
  6 +# or consuming Dart code, and enforces writing Dart using a single, idiomatic
  7 +# style and format.
  8 +#
  9 +# If you want a smaller set of lints you can change this to specify
  10 +# 'package:lints/core.yaml'. These are just the most critical lints
  11 +# (the recommended set includes the core lints).
  12 +# The core lints are also what is used by pub.dev for scoring packages.
  13 +
  14 +include: package:lints/recommended.yaml
  15 +
  16 +# Uncomment the following section to specify additional rules.
  17 +
  18 +# linter:
  19 +# rules:
  20 +# - camel_case_types
  21 +
  22 +# analyzer:
  23 +# exclude:
  24 +# - path/to/excluded/files/**
  25 +
  26 +# For more information about the core and recommended set of lints, see
  27 +# https://dart.dev/go/core-lints
  28 +
  29 +# For additional information about configuring this file, see
  30 +# https://dart.dev/guides/language/analysis-options
  1 +// Copyright (c) 2024 Xiaomi Corporation
  2 +import 'dart:io';
  3 +import 'dart:typed_data';
  4 +
  5 +import 'package:args/args.dart';
  6 +import 'package:sherpa_onnx/sherpa_onnx.dart' as sherpa_onnx;
  7 +
  8 +import './init.dart';
  9 +
  10 +void main(List<String> arguments) async {
  11 + await initSherpaOnnx();
  12 +
  13 + final parser = ArgParser()
  14 + ..addOption('encoder', help: 'Path to the encoder model')
  15 + ..addOption('decoder', help: 'Path to decoder model')
  16 + ..addOption('joiner', help: 'Path to joiner model')
  17 + ..addOption('tokens', help: 'Path to tokens.txt')
  18 + ..addOption('keywords-file', help: 'Path to keywords.txt')
  19 + ..addOption('input-wav', help: 'Path to input.wav to transcribe');
  20 +
  21 + final res = parser.parse(arguments);
  22 + if (res['encoder'] == null ||
  23 + res['decoder'] == null ||
  24 + res['joiner'] == null ||
  25 + res['tokens'] == null ||
  26 + res['keywords-file'] == null ||
  27 + res['input-wav'] == null) {
  28 + print(parser.usage);
  29 + exit(1);
  30 + }
  31 +
  32 + final encoder = res['encoder'] as String;
  33 + final decoder = res['decoder'] as String;
  34 + final joiner = res['joiner'] as String;
  35 + final tokens = res['tokens'] as String;
  36 + final keywordsFile = res['keywords-file'] as String;
  37 + final inputWav = res['input-wav'] as String;
  38 +
  39 + final transducer = sherpa_onnx.OnlineTransducerModelConfig(
  40 + encoder: encoder,
  41 + decoder: decoder,
  42 + joiner: joiner,
  43 + );
  44 +
  45 + final modelConfig = sherpa_onnx.OnlineModelConfig(
  46 + transducer: transducer,
  47 + tokens: tokens,
  48 + debug: true,
  49 + numThreads: 1,
  50 + );
  51 + final config = sherpa_onnx.KeywordSpotterConfig(
  52 + model: modelConfig,
  53 + keywordsFile: keywordsFile,
  54 + );
  55 + final spotter = sherpa_onnx.KeywordSpotter(config);
  56 +
  57 + final waveData = sherpa_onnx.readWave(inputWav);
  58 + var stream = spotter.createStream();
  59 +
  60 + // simulate streaming. You can choose an arbitrary chunk size.
  61 + // chunkSize of a single sample is also ok, i.e, chunkSize = 1
  62 + final chunkSize = 1600; // 0.1 second for 16kHz
  63 + final numChunks = waveData.samples.length ~/ chunkSize;
  64 +
  65 + for (int i = 0; i != numChunks; ++i) {
  66 + int start = i * chunkSize;
  67 + stream.acceptWaveform(
  68 + samples:
  69 + Float32List.sublistView(waveData.samples, start, start + chunkSize),
  70 + sampleRate: waveData.sampleRate,
  71 + );
  72 + while (spotter.isReady(stream)) {
  73 + spotter.decode(stream);
  74 + final result = spotter.getResult(stream);
  75 + if (result.keyword != '') {
  76 + print('Detected: ${result.keyword}');
  77 + }
  78 + }
  79 + }
  80 +
  81 + // 0.5 seconds, assume sampleRate is 16kHz
  82 + final tailPaddings = Float32List(8000);
  83 + stream.acceptWaveform(
  84 + samples: tailPaddings,
  85 + sampleRate: waveData.sampleRate,
  86 + );
  87 +
  88 + while (spotter.isReady(stream)) {
  89 + spotter.decode(stream);
  90 + final result = spotter.getResult(stream);
  91 + if (result.keyword != '') {
  92 + print('Detected: ${result.keyword}');
  93 + }
  94 + }
  95 +
  96 + stream.free();
  97 + spotter.free();
  98 +}
  1 +name: keyword_spotter
  2 +
  3 +description: >
  4 + This example demonstrates how to use the Dart API for keyword spotting
  5 +
  6 +version: 1.0.0
  7 +
  8 +environment:
  9 + sdk: ^3.4.0
  10 +
  11 +dependencies:
  12 + sherpa_onnx: ^1.10.17
  13 + # sherpa_onnx:
  14 + # path: ../../flutter/sherpa_onnx
  15 + path: ^1.9.0
  16 + args: ^2.5.0
  17 +
  18 +dev_dependencies:
  19 + lints: ^3.0.0
  1 +#!/usr/bin/env bash
  2 +
  3 +set -ex
  4 +
  5 +dart pub get
  6 +
  7 +if [ ! -f ./sherpa-onnx-kws-zipformer-wenetspeech-3.3M-2024-01-01/tokens.txt ]; then
  8 + curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/kws-models/sherpa-onnx-kws-zipformer-wenetspeech-3.3M-2024-01-01.tar.bz2
  9 + tar xvf sherpa-onnx-kws-zipformer-wenetspeech-3.3M-2024-01-01.tar.bz2
  10 + rm sherpa-onnx-kws-zipformer-wenetspeech-3.3M-2024-01-01.tar.bz2
  11 +fi
  12 +
  13 +dart run \
  14 + ./bin/zipformer-transducer.dart \
  15 + --encoder ./sherpa-onnx-kws-zipformer-wenetspeech-3.3M-2024-01-01/encoder-epoch-12-avg-2-chunk-16-left-64.onnx \
  16 + --decoder ./sherpa-onnx-kws-zipformer-wenetspeech-3.3M-2024-01-01/decoder-epoch-12-avg-2-chunk-16-left-64.onnx \
  17 + --joiner ./sherpa-onnx-kws-zipformer-wenetspeech-3.3M-2024-01-01/joiner-epoch-12-avg-2-chunk-16-left-64.onnx \
  18 + --tokens ./sherpa-onnx-kws-zipformer-wenetspeech-3.3M-2024-01-01/tokens.txt \
  19 + --keywords-file ./sherpa-onnx-kws-zipformer-wenetspeech-3.3M-2024-01-01/test_wavs/test_keywords.txt \
  20 + --input-wav ./sherpa-onnx-kws-zipformer-wenetspeech-3.3M-2024-01-01/test_wavs/3.wav
  21 +
@@ -3,6 +3,7 @@ import 'dart:io'; @@ -3,6 +3,7 @@ import 'dart:io';
3 import 'dart:ffi'; 3 import 'dart:ffi';
4 4
5 export 'src/feature_config.dart'; 5 export 'src/feature_config.dart';
  6 +export 'src/keyword_spotter.dart';
6 export 'src/offline_recognizer.dart'; 7 export 'src/offline_recognizer.dart';
7 export 'src/offline_stream.dart'; 8 export 'src/offline_stream.dart';
8 export 'src/online_recognizer.dart'; 9 export 'src/online_recognizer.dart';
  1 +// Copyright (c) 2024 Xiaomi Corporation
  2 +import 'dart:convert';
  3 +import 'dart:ffi';
  4 +
  5 +import 'package:ffi/ffi.dart';
  6 +
  7 +import './feature_config.dart';
  8 +import './online_stream.dart';
  9 +import './online_recognizer.dart';
  10 +import './sherpa_onnx_bindings.dart';
  11 +import './utils.dart';
  12 +
  13 +class KeywordSpotterConfig {
  14 + const KeywordSpotterConfig({
  15 + this.feat = const FeatureConfig(),
  16 + required this.model,
  17 + this.maxActivePaths = 4,
  18 + this.numTrailingBlanks = 1,
  19 + this.keywordsScore = 1.0,
  20 + this.keywordsThreshold = 0.25,
  21 + this.keywordsFile = '',
  22 + });
  23 +
  24 + @override
  25 + String toString() {
  26 + return 'KeywordSpotterConfig(feat: $feat, model: $model, maxActivePaths: $maxActivePaths, numTrailingBlanks: $numTrailingBlanks, keywordsScore: $keywordsScore, keywordsThreshold: $keywordsThreshold, keywordsFile: $keywordsFile)';
  27 + }
  28 +
  29 + final FeatureConfig feat;
  30 + final OnlineModelConfig model;
  31 +
  32 + final int maxActivePaths;
  33 + final int numTrailingBlanks;
  34 +
  35 + final double keywordsScore;
  36 + final double keywordsThreshold;
  37 + final String keywordsFile;
  38 +}
  39 +
  40 +class KeywordResult {
  41 + KeywordResult({required this.keyword});
  42 +
  43 + @override
  44 + String toString() {
  45 + return 'KeywordResult(keyword: $keyword)';
  46 + }
  47 +
  48 + final String keyword;
  49 +}
  50 +
  51 +class KeywordSpotter {
  52 + KeywordSpotter._({required this.ptr, required this.config});
  53 +
  54 + /// The user is responsible to call the OnlineRecognizer.free()
  55 + /// method of the returned instance to avoid memory leak.
  56 + factory KeywordSpotter(KeywordSpotterConfig config) {
  57 + final c = calloc<SherpaOnnxKeywordSpotterConfig>();
  58 + c.ref.feat.sampleRate = config.feat.sampleRate;
  59 + c.ref.feat.featureDim = config.feat.featureDim;
  60 +
  61 + // transducer
  62 + c.ref.model.transducer.encoder =
  63 + config.model.transducer.encoder.toNativeUtf8();
  64 + c.ref.model.transducer.decoder =
  65 + config.model.transducer.decoder.toNativeUtf8();
  66 + c.ref.model.transducer.joiner =
  67 + config.model.transducer.joiner.toNativeUtf8();
  68 +
  69 + // paraformer
  70 + c.ref.model.paraformer.encoder =
  71 + config.model.paraformer.encoder.toNativeUtf8();
  72 + c.ref.model.paraformer.decoder =
  73 + config.model.paraformer.decoder.toNativeUtf8();
  74 +
  75 + // zipformer2Ctc
  76 + c.ref.model.zipformer2Ctc.model =
  77 + config.model.zipformer2Ctc.model.toNativeUtf8();
  78 +
  79 + c.ref.model.tokens = config.model.tokens.toNativeUtf8();
  80 + c.ref.model.numThreads = config.model.numThreads;
  81 + c.ref.model.provider = config.model.provider.toNativeUtf8();
  82 + c.ref.model.debug = config.model.debug ? 1 : 0;
  83 + c.ref.model.modelType = config.model.modelType.toNativeUtf8();
  84 + c.ref.model.modelingUnit = config.model.modelingUnit.toNativeUtf8();
  85 + c.ref.model.bpeVocab = config.model.bpeVocab.toNativeUtf8();
  86 +
  87 + c.ref.maxActivePaths = config.maxActivePaths;
  88 + c.ref.numTrailingBlanks = config.numTrailingBlanks;
  89 + c.ref.keywordsScore = config.keywordsScore;
  90 + c.ref.keywordsThreshold = config.keywordsThreshold;
  91 + c.ref.keywordsFile = config.keywordsFile.toNativeUtf8();
  92 +
  93 + final ptr = SherpaOnnxBindings.createKeywordSpotter?.call(c) ?? nullptr;
  94 +
  95 + calloc.free(c.ref.keywordsFile);
  96 + calloc.free(c.ref.model.bpeVocab);
  97 + calloc.free(c.ref.model.modelingUnit);
  98 + calloc.free(c.ref.model.modelType);
  99 + calloc.free(c.ref.model.provider);
  100 + calloc.free(c.ref.model.tokens);
  101 + calloc.free(c.ref.model.zipformer2Ctc.model);
  102 + calloc.free(c.ref.model.paraformer.encoder);
  103 + calloc.free(c.ref.model.paraformer.decoder);
  104 +
  105 + calloc.free(c.ref.model.transducer.encoder);
  106 + calloc.free(c.ref.model.transducer.decoder);
  107 + calloc.free(c.ref.model.transducer.joiner);
  108 + calloc.free(c);
  109 +
  110 + return KeywordSpotter._(ptr: ptr, config: config);
  111 + }
  112 +
  113 + void free() {
  114 + SherpaOnnxBindings.destroyKeywordSpotter?.call(ptr);
  115 + ptr = nullptr;
  116 + }
  117 +
  118 + /// The user has to invoke stream.free() on the returned instance
  119 + /// to avoid memory leak
  120 + OnlineStream createStream({String keywords = ''}) {
  121 + if (keywords == '') {
  122 + final p = SherpaOnnxBindings.createKeywordStream?.call(ptr) ?? nullptr;
  123 + return OnlineStream(ptr: p);
  124 + }
  125 +
  126 + final utf8 = keywords.toNativeUtf8();
  127 + final p =
  128 + SherpaOnnxBindings.createKeywordStreamWithKeywords?.call(ptr, utf8) ??
  129 + nullptr;
  130 + calloc.free(utf8);
  131 + return OnlineStream(ptr: p);
  132 + }
  133 +
  134 + bool isReady(OnlineStream stream) {
  135 + int ready =
  136 + SherpaOnnxBindings.isKeywordStreamReady?.call(ptr, stream.ptr) ?? 0;
  137 +
  138 + return ready == 1;
  139 + }
  140 +
  141 + KeywordResult getResult(OnlineStream stream) {
  142 + final json =
  143 + SherpaOnnxBindings.getKeywordResultAsJson?.call(ptr, stream.ptr) ??
  144 + nullptr;
  145 + if (json == nullptr) {
  146 + return KeywordResult(keyword: '');
  147 + }
  148 +
  149 + final parsedJson = jsonDecode(toDartString(json));
  150 +
  151 + SherpaOnnxBindings.freeKeywordResultJson?.call(json);
  152 +
  153 + return KeywordResult(
  154 + keyword: parsedJson['keyword'],
  155 + );
  156 + }
  157 +
  158 + void decode(OnlineStream stream) {
  159 + SherpaOnnxBindings.decodeKeywordStream?.call(ptr, stream.ptr);
  160 + }
  161 +
  162 + Pointer<SherpaOnnxKeywordSpotter> ptr;
  163 + KeywordSpotterConfig config;
  164 +}
@@ -283,6 +283,28 @@ final class SherpaOnnxSpeakerEmbeddingExtractorConfig extends Struct { @@ -283,6 +283,28 @@ final class SherpaOnnxSpeakerEmbeddingExtractorConfig extends Struct {
283 external Pointer<Utf8> provider; 283 external Pointer<Utf8> provider;
284 } 284 }
285 285
  286 +final class SherpaOnnxKeywordSpotterConfig extends Struct {
  287 + external SherpaOnnxFeatureConfig feat;
  288 +
  289 + external SherpaOnnxOnlineModelConfig model;
  290 +
  291 + @Int32()
  292 + external int maxActivePaths;
  293 +
  294 + @Int32()
  295 + external int numTrailingBlanks;
  296 +
  297 + @Float()
  298 + external double keywordsScore;
  299 +
  300 + @Float()
  301 + external double keywordsThreshold;
  302 +
  303 + external Pointer<Utf8> keywordsFile;
  304 +}
  305 +
  306 +final class SherpaOnnxKeywordSpotter extends Opaque {}
  307 +
286 final class SherpaOnnxOfflineTts extends Opaque {} 308 final class SherpaOnnxOfflineTts extends Opaque {}
287 309
288 final class SherpaOnnxCircularBuffer extends Opaque {} 310 final class SherpaOnnxCircularBuffer extends Opaque {}
@@ -301,6 +323,48 @@ final class SherpaOnnxSpeakerEmbeddingExtractor extends Opaque {} @@ -301,6 +323,48 @@ final class SherpaOnnxSpeakerEmbeddingExtractor extends Opaque {}
301 323
302 final class SherpaOnnxSpeakerEmbeddingManager extends Opaque {} 324 final class SherpaOnnxSpeakerEmbeddingManager extends Opaque {}
303 325
  326 +typedef CreateKeywordSpotterNative = Pointer<SherpaOnnxKeywordSpotter> Function(
  327 + Pointer<SherpaOnnxKeywordSpotterConfig>);
  328 +
  329 +typedef CreateKeywordSpotter = CreateKeywordSpotterNative;
  330 +
  331 +typedef DestroyKeywordSpotterNative = Void Function(
  332 + Pointer<SherpaOnnxKeywordSpotter>);
  333 +
  334 +typedef DestroyKeywordSpotter = void Function(
  335 + Pointer<SherpaOnnxKeywordSpotter>);
  336 +
  337 +typedef CreateKeywordStreamNative = Pointer<SherpaOnnxOnlineStream> Function(
  338 + Pointer<SherpaOnnxKeywordSpotter>);
  339 +
  340 +typedef CreateKeywordStream = CreateKeywordStreamNative;
  341 +
  342 +typedef CreateKeywordStreamWithKeywordsNative = Pointer<SherpaOnnxOnlineStream>
  343 + Function(Pointer<SherpaOnnxKeywordSpotter>, Pointer<Utf8>);
  344 +
  345 +typedef CreateKeywordStreamWithKeywords = CreateKeywordStreamWithKeywordsNative;
  346 +
  347 +typedef IsKeywordStreamReadyNative = Int32 Function(
  348 + Pointer<SherpaOnnxKeywordSpotter>, Pointer<SherpaOnnxOnlineStream>);
  349 +
  350 +typedef IsKeywordStreamReady = int Function(
  351 + Pointer<SherpaOnnxKeywordSpotter>, Pointer<SherpaOnnxOnlineStream>);
  352 +
  353 +typedef DecodeKeywordStreamNative = Void Function(
  354 + Pointer<SherpaOnnxKeywordSpotter>, Pointer<SherpaOnnxOnlineStream>);
  355 +
  356 +typedef DecodeKeywordStream = void Function(
  357 + Pointer<SherpaOnnxKeywordSpotter>, Pointer<SherpaOnnxOnlineStream>);
  358 +
  359 +typedef GetKeywordResultAsJsonNative = Pointer<Utf8> Function(
  360 + Pointer<SherpaOnnxKeywordSpotter>, Pointer<SherpaOnnxOnlineStream>);
  361 +
  362 +typedef GetKeywordResultAsJson = GetKeywordResultAsJsonNative;
  363 +
  364 +typedef FreeKeywordResultJsonNative = Void Function(Pointer<Utf8>);
  365 +
  366 +typedef FreeKeywordResultJson = void Function(Pointer<Utf8>);
  367 +
304 typedef SherpaOnnxCreateOfflineTtsNative = Pointer<SherpaOnnxOfflineTts> 368 typedef SherpaOnnxCreateOfflineTtsNative = Pointer<SherpaOnnxOfflineTts>
305 Function(Pointer<SherpaOnnxOfflineTtsConfig>); 369 Function(Pointer<SherpaOnnxOfflineTtsConfig>);
306 370
@@ -735,6 +799,15 @@ typedef SherpaOnnxFreeWaveNative = Void Function(Pointer<SherpaOnnxWave>); @@ -735,6 +799,15 @@ typedef SherpaOnnxFreeWaveNative = Void Function(Pointer<SherpaOnnxWave>);
735 typedef SherpaOnnxFreeWave = void Function(Pointer<SherpaOnnxWave>); 799 typedef SherpaOnnxFreeWave = void Function(Pointer<SherpaOnnxWave>);
736 800
737 class SherpaOnnxBindings { 801 class SherpaOnnxBindings {
  802 + static CreateKeywordSpotter? createKeywordSpotter;
  803 + static DestroyKeywordSpotter? destroyKeywordSpotter;
  804 + static CreateKeywordStream? createKeywordStream;
  805 + static CreateKeywordStreamWithKeywords? createKeywordStreamWithKeywords;
  806 + static IsKeywordStreamReady? isKeywordStreamReady;
  807 + static DecodeKeywordStream? decodeKeywordStream;
  808 + static GetKeywordResultAsJson? getKeywordResultAsJson;
  809 + static FreeKeywordResultJson? freeKeywordResultJson;
  810 +
738 static SherpaOnnxCreateOfflineTts? createOfflineTts; 811 static SherpaOnnxCreateOfflineTts? createOfflineTts;
739 static SherpaOnnxDestroyOfflineTts? destroyOfflineTts; 812 static SherpaOnnxDestroyOfflineTts? destroyOfflineTts;
740 static SherpaOnnxOfflineTtsSampleRate? offlineTtsSampleRate; 813 static SherpaOnnxOfflineTtsSampleRate? offlineTtsSampleRate;
@@ -879,6 +952,46 @@ class SherpaOnnxBindings { @@ -879,6 +952,46 @@ class SherpaOnnxBindings {
879 static SherpaOnnxFreeWave? freeWave; 952 static SherpaOnnxFreeWave? freeWave;
880 953
881 static void init(DynamicLibrary dynamicLibrary) { 954 static void init(DynamicLibrary dynamicLibrary) {
  955 + createKeywordSpotter ??= dynamicLibrary
  956 + .lookup<NativeFunction<CreateKeywordSpotterNative>>(
  957 + 'CreateKeywordSpotter')
  958 + .asFunction();
  959 +
  960 + destroyKeywordSpotter ??= dynamicLibrary
  961 + .lookup<NativeFunction<DestroyKeywordSpotterNative>>(
  962 + 'DestroyKeywordSpotter')
  963 + .asFunction();
  964 +
  965 + createKeywordStream ??= dynamicLibrary
  966 + .lookup<NativeFunction<CreateKeywordStreamNative>>(
  967 + 'CreateKeywordStream')
  968 + .asFunction();
  969 +
  970 + createKeywordStreamWithKeywords ??= dynamicLibrary
  971 + .lookup<NativeFunction<CreateKeywordStreamWithKeywordsNative>>(
  972 + 'CreateKeywordStreamWithKeywords')
  973 + .asFunction();
  974 +
  975 + isKeywordStreamReady ??= dynamicLibrary
  976 + .lookup<NativeFunction<IsKeywordStreamReadyNative>>(
  977 + 'IsKeywordStreamReady')
  978 + .asFunction();
  979 +
  980 + decodeKeywordStream ??= dynamicLibrary
  981 + .lookup<NativeFunction<DecodeKeywordStreamNative>>(
  982 + 'DecodeKeywordStream')
  983 + .asFunction();
  984 +
  985 + getKeywordResultAsJson ??= dynamicLibrary
  986 + .lookup<NativeFunction<GetKeywordResultAsJsonNative>>(
  987 + 'GetKeywordResultAsJson')
  988 + .asFunction();
  989 +
  990 + freeKeywordResultJson ??= dynamicLibrary
  991 + .lookup<NativeFunction<FreeKeywordResultJsonNative>>(
  992 + 'FreeKeywordResultJson')
  993 + .asFunction();
  994 +
882 createOfflineTts ??= dynamicLibrary 995 createOfflineTts ??= dynamicLibrary
883 .lookup<NativeFunction<SherpaOnnxCreateOfflineTtsNative>>( 996 .lookup<NativeFunction<SherpaOnnxCreateOfflineTtsNative>>(
884 'SherpaOnnxCreateOfflineTts') 997 'SherpaOnnxCreateOfflineTts')
@@ -31,20 +31,24 @@ dependencies: @@ -31,20 +31,24 @@ dependencies:
31 sdk: flutter 31 sdk: flutter
32 32
33 sherpa_onnx_android: ^1.10.17 33 sherpa_onnx_android: ^1.10.17
34 - # path: ../sherpa_onnx_android 34 + # sherpa_onnx_android:
  35 + # path: ../sherpa_onnx_android
35 36
36 sherpa_onnx_macos: ^1.10.17 37 sherpa_onnx_macos: ^1.10.17
37 - # path: ../sherpa_onnx_macos 38 + # sherpa_onnx_macos:
  39 + # path: ../sherpa_onnx_macos
38 40
39 sherpa_onnx_linux: ^1.10.17 41 sherpa_onnx_linux: ^1.10.17
40 - # path: ../sherpa_onnx_linux 42 + # sherpa_onnx_linux:
  43 + # path: ../sherpa_onnx_linux
41 # 44 #
42 sherpa_onnx_windows: ^1.10.17 45 sherpa_onnx_windows: ^1.10.17
43 - # path: ../sherpa_onnx_windows 46 + # sherpa_onnx_windows:
  47 + # path: ../sherpa_onnx_windows
44 48
45 sherpa_onnx_ios: ^1.10.17 49 sherpa_onnx_ios: ^1.10.17
46 # sherpa_onnx_ios: 50 # sherpa_onnx_ios:
47 - # path: ../sherpa_onnx_ios 51 + # path: ../sherpa_onnx_ios
48 52
49 dev_dependencies: 53 dev_dependencies:
50 flutter_lints: ^3.0.0 54 flutter_lints: ^3.0.0
  1 +name: keyword_spotter
  2 +
  3 +description: >
  4 + This example demonstrates how to use the Dart API for keyword spotting
  5 +
  6 +version: 1.0.0
  7 +
  8 +environment:
  9 + sdk: ^3.4.0
  10 +
  11 +dependencies:
  12 + # sherpa_onnx: ^1.10.17
  13 + sherpa_onnx:
  14 + path: ../../flutter/sherpa_onnx
  15 + path: ^1.9.0
  16 + args: ^2.5.0
  17 +
  18 +dev_dependencies:
  19 + lints: ^3.0.0