Skip to content

Commit ac8223b

Browse files
authored
Add Dart API for keyword spotter (#1162)
1 parent 22a262f commit ac8223b

File tree

17 files changed

+518
-5
lines changed

17 files changed

+518
-5
lines changed

.github/scripts/test-dart.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ set -ex
44

55
cd dart-api-examples
66

7+
pushd keyword-spotter
8+
./run-zh.sh
9+
popd
10+
711
pushd non-streaming-asr
812

913
echo '----------SenseVoice----------'

.github/workflows/test-dart.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ jobs:
108108
cp scripts/dart/non-streaming-asr-pubspec.yaml dart-api-examples/non-streaming-asr/pubspec.yaml
109109
cp scripts/dart/streaming-asr-pubspec.yaml dart-api-examples/streaming-asr/pubspec.yaml
110110
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
111112
cp scripts/dart/sherpa-onnx-pubspec.yaml flutter/sherpa_onnx/pubspec.yaml
112113
113114
.github/scripts/test-dart.sh

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## 1.10.17
22

33
* Support SenseVoice CTC models.
4+
* Add Dart API for keyword spotter.
45

56
## 1.10.16
67

dart-api-examples/.gitignore

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,28 @@
11
!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
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# https://dart.dev/guides/libraries/private-files
2+
# Created by `dart pub`
3+
.dart_tool/
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## 1.0.0
2+
3+
- Initial version.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Introduction
2+
3+
This directory contains keyword spotting examples using
4+
Dart API from [sherpa-onnx](https://github.com/k2-fsa/sherpa-onnx)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../vad/bin/init.dart
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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+
}

0 commit comments

Comments
 (0)