feat: integrate Sherpa-ONNX local offline speech recognition engine
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"provider": "compatible",
|
||||
"endpoint": "https://codex.slcydia.fun/v1/responses",
|
||||
"endpoint": "https://kmwq8ckvr0ehsgqyudcer1.slcydia.fun/v1/responses",
|
||||
"model": "gemini-3.7-flash-high",
|
||||
"reasoningEffort": "low",
|
||||
"apiKey": "sk-242EMNuXYjxSEktp91E8QqS8ejGs9XImrDddIA5JHXdeCKLSUcB91vrSmhyv45pf",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,502 @@
|
||||
<blk> 0
|
||||
<sos/eos> 1
|
||||
<unk> 2
|
||||
S 3
|
||||
▁THE 4
|
||||
▁A 5
|
||||
T 6
|
||||
▁AND 7
|
||||
ED 8
|
||||
▁OF 9
|
||||
▁TO 10
|
||||
E 11
|
||||
D 12
|
||||
N 13
|
||||
ING 14
|
||||
▁IN 15
|
||||
Y 16
|
||||
M 17
|
||||
C 18
|
||||
▁I 19
|
||||
A 20
|
||||
P 21
|
||||
▁HE 22
|
||||
R 23
|
||||
O 24
|
||||
L 25
|
||||
RE 26
|
||||
I 27
|
||||
U 28
|
||||
ER 29
|
||||
▁IT 30
|
||||
LY 31
|
||||
▁THAT 32
|
||||
▁WAS 33
|
||||
▁ 34
|
||||
▁S 35
|
||||
AR 36
|
||||
▁BE 37
|
||||
F 38
|
||||
▁C 39
|
||||
IN 40
|
||||
B 41
|
||||
▁FOR 42
|
||||
OR 43
|
||||
LE 44
|
||||
' 45
|
||||
▁HIS 46
|
||||
▁YOU 47
|
||||
AL 48
|
||||
▁RE 49
|
||||
V 50
|
||||
▁B 51
|
||||
G 52
|
||||
RI 53
|
||||
▁E 54
|
||||
▁WITH 55
|
||||
▁T 56
|
||||
▁AS 57
|
||||
LL 58
|
||||
▁P 59
|
||||
▁HER 60
|
||||
ST 61
|
||||
▁HAD 62
|
||||
▁SO 63
|
||||
▁F 64
|
||||
W 65
|
||||
CE 66
|
||||
▁IS 67
|
||||
ND 68
|
||||
▁NOT 69
|
||||
TH 70
|
||||
▁BUT 71
|
||||
EN 72
|
||||
▁SHE 73
|
||||
▁ON 74
|
||||
VE 75
|
||||
ON 76
|
||||
SE 77
|
||||
▁DE 78
|
||||
UR 79
|
||||
▁G 80
|
||||
CH 81
|
||||
K 82
|
||||
TER 83
|
||||
▁AT 84
|
||||
IT 85
|
||||
▁ME 86
|
||||
RO 87
|
||||
NE 88
|
||||
RA 89
|
||||
ES 90
|
||||
IL 91
|
||||
NG 92
|
||||
IC 93
|
||||
▁NO 94
|
||||
▁HIM 95
|
||||
ENT 96
|
||||
IR 97
|
||||
▁WE 98
|
||||
H 99
|
||||
▁DO 100
|
||||
▁ALL 101
|
||||
▁HAVE 102
|
||||
LO 103
|
||||
▁BY 104
|
||||
▁MY 105
|
||||
▁MO 106
|
||||
▁THIS 107
|
||||
LA 108
|
||||
▁ST 109
|
||||
▁WHICH 110
|
||||
▁CON 111
|
||||
▁THEY 112
|
||||
CK 113
|
||||
TE 114
|
||||
▁SAID 115
|
||||
▁FROM 116
|
||||
▁GO 117
|
||||
▁WHO 118
|
||||
▁TH 119
|
||||
▁OR 120
|
||||
▁D 121
|
||||
▁W 122
|
||||
VER 123
|
||||
LI 124
|
||||
▁SE 125
|
||||
▁ONE 126
|
||||
▁CA 127
|
||||
▁AN 128
|
||||
▁LA 129
|
||||
▁WERE 130
|
||||
EL 131
|
||||
▁HA 132
|
||||
▁MAN 133
|
||||
▁FA 134
|
||||
▁EX 135
|
||||
AD 136
|
||||
▁SU 137
|
||||
RY 138
|
||||
▁MI 139
|
||||
AT 140
|
||||
▁BO 141
|
||||
▁WHEN 142
|
||||
AN 143
|
||||
THER 144
|
||||
PP 145
|
||||
ATION 146
|
||||
▁FI 147
|
||||
▁WOULD 148
|
||||
▁PRO 149
|
||||
OW 150
|
||||
ET 151
|
||||
▁O 152
|
||||
▁THERE 153
|
||||
▁HO 154
|
||||
ION 155
|
||||
▁WHAT 156
|
||||
▁FE 157
|
||||
▁PA 158
|
||||
US 159
|
||||
MENT 160
|
||||
▁MA 161
|
||||
UT 162
|
||||
▁OUT 163
|
||||
▁THEIR 164
|
||||
▁IF 165
|
||||
▁LI 166
|
||||
▁K 167
|
||||
▁WILL 168
|
||||
▁ARE 169
|
||||
ID 170
|
||||
▁RO 171
|
||||
DE 172
|
||||
TION 173
|
||||
▁WA 174
|
||||
PE 175
|
||||
▁UP 176
|
||||
▁SP 177
|
||||
▁PO 178
|
||||
IGHT 179
|
||||
▁UN 180
|
||||
RU 181
|
||||
▁LO 182
|
||||
AS 183
|
||||
OL 184
|
||||
▁LE 185
|
||||
▁BEEN 186
|
||||
▁SH 187
|
||||
▁RA 188
|
||||
▁SEE 189
|
||||
KE 190
|
||||
UL 191
|
||||
TED 192
|
||||
▁SA 193
|
||||
UN 194
|
||||
UND 195
|
||||
ANT 196
|
||||
▁NE 197
|
||||
IS 198
|
||||
▁THEM 199
|
||||
CI 200
|
||||
GE 201
|
||||
▁COULD 202
|
||||
▁DIS 203
|
||||
OM 204
|
||||
ISH 205
|
||||
HE 206
|
||||
EST 207
|
||||
▁SOME 208
|
||||
ENCE 209
|
||||
ITY 210
|
||||
IVE 211
|
||||
▁US 212
|
||||
▁MORE 213
|
||||
▁EN 214
|
||||
ARD 215
|
||||
ATE 216
|
||||
▁YOUR 217
|
||||
▁INTO 218
|
||||
▁KNOW 219
|
||||
▁CO 220
|
||||
ANCE 221
|
||||
▁TIME 222
|
||||
▁WI 223
|
||||
▁YE 224
|
||||
AGE 225
|
||||
▁NOW 226
|
||||
TI 227
|
||||
FF 228
|
||||
ABLE 229
|
||||
▁VERY 230
|
||||
▁LIKE 231
|
||||
AM 232
|
||||
HI 233
|
||||
Z 234
|
||||
▁OTHER 235
|
||||
▁THAN 236
|
||||
▁LITTLE 237
|
||||
▁DID 238
|
||||
▁LOOK 239
|
||||
TY 240
|
||||
ERS 241
|
||||
▁CAN 242
|
||||
▁CHA 243
|
||||
▁AR 244
|
||||
X 245
|
||||
FUL 246
|
||||
UGH 247
|
||||
▁BA 248
|
||||
▁DAY 249
|
||||
▁ABOUT 250
|
||||
TEN 251
|
||||
IM 252
|
||||
▁ANY 253
|
||||
▁PRE 254
|
||||
▁OVER 255
|
||||
IES 256
|
||||
NESS 257
|
||||
ME 258
|
||||
BLE 259
|
||||
▁M 260
|
||||
ROW 261
|
||||
▁HAS 262
|
||||
▁GREAT 263
|
||||
▁VI 264
|
||||
TA 265
|
||||
▁AFTER 266
|
||||
PER 267
|
||||
▁AGAIN 268
|
||||
HO 269
|
||||
SH 270
|
||||
▁UPON 271
|
||||
▁DI 272
|
||||
▁HAND 273
|
||||
▁COM 274
|
||||
IST 275
|
||||
TURE 276
|
||||
▁STA 277
|
||||
▁THEN 278
|
||||
▁SHOULD 279
|
||||
▁GA 280
|
||||
OUS 281
|
||||
OUR 282
|
||||
▁WELL 283
|
||||
▁ONLY 284
|
||||
MAN 285
|
||||
▁GOOD 286
|
||||
▁TWO 287
|
||||
▁MAR 288
|
||||
▁SAY 289
|
||||
▁HU 290
|
||||
TING 291
|
||||
▁OUR 292
|
||||
RESS 293
|
||||
▁DOWN 294
|
||||
IOUS 295
|
||||
▁BEFORE 296
|
||||
▁DA 297
|
||||
▁NA 298
|
||||
QUI 299
|
||||
▁MADE 300
|
||||
▁EVERY 301
|
||||
▁OLD 302
|
||||
▁EVEN 303
|
||||
IG 304
|
||||
▁COME 305
|
||||
▁GRA 306
|
||||
▁RI 307
|
||||
▁LONG 308
|
||||
OT 309
|
||||
SIDE 310
|
||||
WARD 311
|
||||
▁FO 312
|
||||
▁WHERE 313
|
||||
MO 314
|
||||
LESS 315
|
||||
▁SC 316
|
||||
▁MUST 317
|
||||
▁NEVER 318
|
||||
▁HOW 319
|
||||
▁CAME 320
|
||||
▁SUCH 321
|
||||
▁RU 322
|
||||
▁TAKE 323
|
||||
▁WO 324
|
||||
▁CAR 325
|
||||
UM 326
|
||||
AK 327
|
||||
▁THINK 328
|
||||
▁MUCH 329
|
||||
▁MISTER 330
|
||||
▁MAY 331
|
||||
▁JO 332
|
||||
▁WAY 333
|
||||
▁COMP 334
|
||||
▁THOUGHT 335
|
||||
▁STO 336
|
||||
▁MEN 337
|
||||
▁BACK 338
|
||||
▁DON 339
|
||||
J 340
|
||||
▁LET 341
|
||||
▁TRA 342
|
||||
▁FIRST 343
|
||||
▁JUST 344
|
||||
▁VA 345
|
||||
▁OWN 346
|
||||
▁PLA 347
|
||||
▁MAKE 348
|
||||
ATED 349
|
||||
▁HIMSELF 350
|
||||
▁WENT 351
|
||||
▁PI 352
|
||||
GG 353
|
||||
RING 354
|
||||
▁DU 355
|
||||
▁MIGHT 356
|
||||
▁PART 357
|
||||
▁GIVE 358
|
||||
▁IMP 359
|
||||
▁BU 360
|
||||
▁PER 361
|
||||
▁PLACE 362
|
||||
▁HOUSE 363
|
||||
▁THROUGH 364
|
||||
IAN 365
|
||||
▁SW 366
|
||||
▁UNDER 367
|
||||
QUE 368
|
||||
▁AWAY 369
|
||||
▁LOVE 370
|
||||
QUA 371
|
||||
▁LIFE 372
|
||||
▁GET 373
|
||||
▁WITHOUT 374
|
||||
▁PASS 375
|
||||
▁TURN 376
|
||||
IGN 377
|
||||
▁HEAD 378
|
||||
▁MOST 379
|
||||
▁THOSE 380
|
||||
▁SHALL 381
|
||||
▁EYES 382
|
||||
▁COL 383
|
||||
▁STILL 384
|
||||
▁NIGHT 385
|
||||
▁NOTHING 386
|
||||
ITION 387
|
||||
HA 388
|
||||
▁TELL 389
|
||||
▁WORK 390
|
||||
▁LAST 391
|
||||
▁NEW 392
|
||||
▁FACE 393
|
||||
▁HI 394
|
||||
▁WORD 395
|
||||
▁FOUND 396
|
||||
▁COUNT 397
|
||||
▁OB 398
|
||||
▁WHILE 399
|
||||
▁SHA 400
|
||||
▁MEAN 401
|
||||
▁SAW 402
|
||||
▁PEOPLE 403
|
||||
▁FRIEND 404
|
||||
▁THREE 405
|
||||
▁ROOM 406
|
||||
▁SAME 407
|
||||
▁THOUGH 408
|
||||
▁RIGHT 409
|
||||
▁CHILD 410
|
||||
▁FATHER 411
|
||||
▁ANOTHER 412
|
||||
▁HEART 413
|
||||
▁WANT 414
|
||||
▁TOOK 415
|
||||
OOK 416
|
||||
▁LIGHT 417
|
||||
▁MISSUS 418
|
||||
▁OPEN 419
|
||||
▁JU 420
|
||||
▁ASKED 421
|
||||
PORT 422
|
||||
▁LEFT 423
|
||||
▁JA 424
|
||||
▁WORLD 425
|
||||
▁HOME 426
|
||||
▁WHY 427
|
||||
▁ALWAYS 428
|
||||
▁ANSWER 429
|
||||
▁SEEMED 430
|
||||
▁SOMETHING 431
|
||||
▁GIRL 432
|
||||
▁BECAUSE 433
|
||||
▁NAME 434
|
||||
▁TOLD 435
|
||||
▁NI 436
|
||||
▁HIGH 437
|
||||
IZE 438
|
||||
▁WOMAN 439
|
||||
▁FOLLOW 440
|
||||
▁RETURN 441
|
||||
▁KNEW 442
|
||||
▁EACH 443
|
||||
▁KIND 444
|
||||
▁JE 445
|
||||
▁ACT 446
|
||||
▁LU 447
|
||||
▁CERTAIN 448
|
||||
▁YEARS 449
|
||||
▁QUITE 450
|
||||
▁APPEAR 451
|
||||
▁BETTER 452
|
||||
▁HALF 453
|
||||
▁PRESENT 454
|
||||
▁PRINCE 455
|
||||
SHIP 456
|
||||
▁ALSO 457
|
||||
▁BEGAN 458
|
||||
▁HAVING 459
|
||||
▁ENOUGH 460
|
||||
▁PERSON 461
|
||||
▁LADY 462
|
||||
▁WHITE 463
|
||||
▁COURSE 464
|
||||
▁VOICE 465
|
||||
▁SPEAK 466
|
||||
▁POWER 467
|
||||
▁MORNING 468
|
||||
▁BETWEEN 469
|
||||
▁AMONG 470
|
||||
▁KEEP 471
|
||||
▁WALK 472
|
||||
▁MATTER 473
|
||||
▁TEA 474
|
||||
▁BELIEVE 475
|
||||
▁SMALL 476
|
||||
▁TALK 477
|
||||
▁FELT 478
|
||||
▁HORSE 479
|
||||
▁MYSELF 480
|
||||
▁SIX 481
|
||||
▁HOWEVER 482
|
||||
▁FULL 483
|
||||
▁HERSELF 484
|
||||
▁POINT 485
|
||||
▁STOOD 486
|
||||
▁HUNDRED 487
|
||||
▁ALMOST 488
|
||||
▁SINCE 489
|
||||
▁LARGE 490
|
||||
▁LEAVE 491
|
||||
▁PERHAPS 492
|
||||
▁DARK 493
|
||||
▁SUDDEN 494
|
||||
▁REPLIED 495
|
||||
▁ANYTHING 496
|
||||
▁WONDER 497
|
||||
▁UNTIL 498
|
||||
Q 499
|
||||
#0 500
|
||||
#1 501
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'sherpa_stt_service.dart';
|
||||
import 'dart:io';
|
||||
import 'dart:convert';
|
||||
|
||||
@@ -118,6 +119,12 @@ class AiService {
|
||||
required String endpoint,
|
||||
required String model,
|
||||
}) async {
|
||||
if (filePath.toLowerCase().endsWith('.wav')) {
|
||||
final localText = await SherpaSttService.instance.transcribeWav(filePath);
|
||||
if (localText != null && localText.trim().isNotEmpty) {
|
||||
return localText.trim();
|
||||
}
|
||||
}
|
||||
if (provider == AiProviderType.mock) return null;
|
||||
final file = File(filePath);
|
||||
if (!await file.exists()) return null;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
@@ -11,6 +12,7 @@ import 'local_store.dart';
|
||||
import 'seed_courses.dart';
|
||||
import 'ai_config.dart';
|
||||
import 'ai_service.dart';
|
||||
import 'sherpa_stt_service.dart';
|
||||
|
||||
class AppState extends ChangeNotifier {
|
||||
static const _storageKey = 'learning_state_v1';
|
||||
@@ -270,6 +272,7 @@ class AppState extends ChangeNotifier {
|
||||
|
||||
Future<void> load() async {
|
||||
try {
|
||||
unawaited(SherpaSttService.instance.initialize());
|
||||
final config = await AiConfigFile.loadFromAsset();
|
||||
if (config != null) {
|
||||
if (config.apiKey != null && config.apiKey!.trim().isNotEmpty) {
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:sherpa_onnx/sherpa_onnx.dart' as sherpa_onnx;
|
||||
|
||||
class SherpaSttService {
|
||||
SherpaSttService._();
|
||||
static final instance = SherpaSttService._();
|
||||
|
||||
sherpa_onnx.OfflineRecognizer? _recognizer;
|
||||
bool _isInitialized = false;
|
||||
bool _isInitializing = false;
|
||||
|
||||
bool get isReady => _isInitialized && _recognizer != null;
|
||||
|
||||
/// Initializes Sherpa-ONNX bindings and unpacks bundled model assets to local disk if needed.
|
||||
Future<bool> initialize({String? nativeLibDir}) async {
|
||||
if (_isInitialized) return true;
|
||||
if (_isInitializing) return false;
|
||||
_isInitializing = true;
|
||||
|
||||
try {
|
||||
try {
|
||||
sherpa_onnx.initBindings(nativeLibDir);
|
||||
} catch (e) {
|
||||
debugPrint('[SherpaSttService] initBindings warning: $e');
|
||||
}
|
||||
|
||||
final docDir = await getApplicationDocumentsDirectory();
|
||||
final modelDir = Directory('${docDir.path}/sherpa_models');
|
||||
if (!await modelDir.exists()) {
|
||||
await modelDir.create(recursive: true);
|
||||
}
|
||||
|
||||
final modelFiles = [
|
||||
'encoder-epoch-99-avg-1.int8.onnx',
|
||||
'decoder-epoch-99-avg-1.int8.onnx',
|
||||
'joiner-epoch-99-avg-1.int8.onnx',
|
||||
'tokens.txt',
|
||||
];
|
||||
|
||||
for (final filename in modelFiles) {
|
||||
final targetFile = File('${modelDir.path}/$filename');
|
||||
if (!await targetFile.exists() || (await targetFile.length()) == 0) {
|
||||
final ByteData data = await rootBundle.load('assets/models/sherpa/$filename');
|
||||
final Uint8List bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
|
||||
await targetFile.writeAsBytes(bytes, flush: true);
|
||||
}
|
||||
}
|
||||
|
||||
final modelConfig = sherpa_onnx.OfflineModelConfig(
|
||||
transducer: sherpa_onnx.OfflineTransducerModelConfig(
|
||||
encoder: '${modelDir.path}/encoder-epoch-99-avg-1.int8.onnx',
|
||||
decoder: '${modelDir.path}/decoder-epoch-99-avg-1.int8.onnx',
|
||||
joiner: '${modelDir.path}/joiner-epoch-99-avg-1.int8.onnx',
|
||||
),
|
||||
tokens: '${modelDir.path}/tokens.txt',
|
||||
numThreads: 2,
|
||||
debug: false,
|
||||
);
|
||||
|
||||
final recognizerConfig = sherpa_onnx.OfflineRecognizerConfig(
|
||||
model: modelConfig,
|
||||
feat: const sherpa_onnx.FeatureConfig(sampleRate: 16000, featureDim: 80),
|
||||
);
|
||||
|
||||
_recognizer = sherpa_onnx.OfflineRecognizer(recognizerConfig);
|
||||
_isInitialized = true;
|
||||
_isInitializing = false;
|
||||
debugPrint('[SherpaSttService] Local ONNX ASR engine initialized successfully.');
|
||||
return true;
|
||||
} catch (e, stack) {
|
||||
debugPrint('[SherpaSttService] Failed to initialize local ASR engine: $e\n$stack');
|
||||
_isInitializing = false;
|
||||
_isInitialized = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Transcribes a local 16kHz mono WAV audio file.
|
||||
Future<String?> transcribeWav(String wavPath) async {
|
||||
try {
|
||||
if (!_isInitialized) {
|
||||
final ready = await initialize();
|
||||
if (!ready || _recognizer == null) return null;
|
||||
}
|
||||
|
||||
final file = File(wavPath);
|
||||
if (!await file.exists()) {
|
||||
debugPrint('[SherpaSttService] Audio file does not exist: $wavPath');
|
||||
return null;
|
||||
}
|
||||
|
||||
final wave = sherpa_onnx.readWave(wavPath);
|
||||
if (wave.samples.isEmpty) {
|
||||
debugPrint('[SherpaSttService] Read 0 wave samples from: $wavPath');
|
||||
return null;
|
||||
}
|
||||
|
||||
final stream = _recognizer!.createStream();
|
||||
stream.acceptWaveform(samples: wave.samples, sampleRate: wave.sampleRate);
|
||||
_recognizer!.decode(stream);
|
||||
final result = _recognizer!.getResult(stream);
|
||||
stream.free();
|
||||
|
||||
final rawText = result.text.trim();
|
||||
if (rawText.isEmpty) return null;
|
||||
|
||||
return _cleanText(rawText);
|
||||
} catch (e) {
|
||||
debugPrint('[SherpaSttService] Transcribe error: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Cleans and formats raw recognized text into natural English casing.
|
||||
String _cleanText(String text) {
|
||||
if (text.isEmpty) return text;
|
||||
// Lowercase first to normalize uppercase model output
|
||||
final lower = text.toLowerCase().trim();
|
||||
if (lower.isEmpty) return lower;
|
||||
// Capitalize the first letter
|
||||
return lower[0].toUpperCase() + lower.substring(1);
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
try {
|
||||
_recognizer?.free();
|
||||
} catch (_) {}
|
||||
_recognizer = null;
|
||||
_isInitialized = false;
|
||||
}
|
||||
}
|
||||
@@ -77,8 +77,12 @@ class VoiceService {
|
||||
if (!await recordings.exists()) await recordings.create(recursive: true);
|
||||
final timestamp = DateTime.now().microsecondsSinceEpoch;
|
||||
await _recorder.start(
|
||||
const RecordConfig(encoder: AudioEncoder.aacLc),
|
||||
path: '${recordings.path}/practice_$timestamp.m4a',
|
||||
const RecordConfig(
|
||||
encoder: AudioEncoder.wav,
|
||||
sampleRate: 16000,
|
||||
numChannels: 1,
|
||||
),
|
||||
path: '${recordings.path}/practice_$timestamp.wav',
|
||||
);
|
||||
return true;
|
||||
}
|
||||
@@ -118,7 +122,7 @@ class VoiceService {
|
||||
if (!await recordings.exists()) return const [];
|
||||
final files = await recordings
|
||||
.list()
|
||||
.where((item) => item is File && item.path.endsWith('.m4a'))
|
||||
.where((item) => item is File && (item.path.endsWith('.wav') || item.path.endsWith('.m4a')))
|
||||
.cast<File>()
|
||||
.toList();
|
||||
files.sort((left, right) => right.path.compareTo(left.path));
|
||||
|
||||
@@ -624,6 +624,86 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
sherpa_onnx:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sherpa_onnx
|
||||
sha256: b7e65d5956f8c9213fd339c68ea058cdec7ad24bd35f9bfc5c18178d3d73b2f9
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.13.8"
|
||||
sherpa_onnx_android_arm64:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sherpa_onnx_android_arm64
|
||||
sha256: "29db5572735afb1bc29f0f95ab160f7fb38b4b3f926f48e9113457fd740264f4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.13.8"
|
||||
sherpa_onnx_android_armeabi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sherpa_onnx_android_armeabi
|
||||
sha256: fe81837bd12f67d136a456c6d2b39876caaba09edd77761cb527cb170dac2d2a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.13.8"
|
||||
sherpa_onnx_android_x86:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sherpa_onnx_android_x86
|
||||
sha256: "8ddbd35982d4bb15648fc818253f2711be18448db50a2edb05c56a6c745fcea7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.13.8"
|
||||
sherpa_onnx_android_x86_64:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sherpa_onnx_android_x86_64
|
||||
sha256: d20c06c0edc92286609f256d04c295cad510bbb270068c0c79877f4a10f00581
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.13.8"
|
||||
sherpa_onnx_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sherpa_onnx_ios
|
||||
sha256: "044e3a614d1847c0ff9b6475201c6e5d023cd6ca6cd17f14c16332081c01e1d2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.13.8"
|
||||
sherpa_onnx_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sherpa_onnx_linux
|
||||
sha256: "22fb91a1c50b7bc24d93d0b67c87cf2a3a38f7564d7f14343d704a1201848960"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.13.8"
|
||||
sherpa_onnx_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sherpa_onnx_macos
|
||||
sha256: bdedc34ac0acccee7ae73a5599f66e0081137c00467c565a92f72b67428837bb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.13.8"
|
||||
sherpa_onnx_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sherpa_onnx_web
|
||||
sha256: e25a3813eb080636280b23dd4b0098252902675158b66dceda1efba061adf5d7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.13.8"
|
||||
sherpa_onnx_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sherpa_onnx_windows
|
||||
sha256: d91fb9c4baac2594cf1ae6955f5e9f51113d354de8c75543ea8481d431040a71
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.13.8"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
||||
@@ -22,6 +22,7 @@ dependencies:
|
||||
shared_preferences: ^2.5.4
|
||||
sqlite3_flutter_libs: ^0.5.39
|
||||
speech_to_text: ^7.3.0
|
||||
sherpa_onnx: ^1.13.8
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@@ -32,3 +33,4 @@ flutter:
|
||||
uses-material-design: true
|
||||
assets:
|
||||
- assets/config/
|
||||
- assets/models/sherpa/
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:kouyu_english/core/sherpa_stt_service.dart';
|
||||
import 'package:sherpa_onnx/sherpa_onnx.dart' as sherpa_onnx;
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
test('SherpaSttService transcribes WAV audio file accurately', () async {
|
||||
const macosDir = '/Users/shen/.pub-cache/hosted/pub.dev/sherpa_onnx_macos-1.13.8/macos';
|
||||
|
||||
// Test direct Sherpa ASR initialization and decoding with bundled test audio
|
||||
sherpa_onnx.initBindings(macosDir);
|
||||
|
||||
final modelConfig = sherpa_onnx.OfflineModelConfig(
|
||||
transducer: const sherpa_onnx.OfflineTransducerModelConfig(
|
||||
encoder: 'assets/models/sherpa/encoder-epoch-99-avg-1.int8.onnx',
|
||||
decoder: 'assets/models/sherpa/decoder-epoch-99-avg-1.int8.onnx',
|
||||
joiner: 'assets/models/sherpa/joiner-epoch-99-avg-1.int8.onnx',
|
||||
),
|
||||
tokens: 'assets/models/sherpa/tokens.txt',
|
||||
numThreads: 2,
|
||||
debug: false,
|
||||
);
|
||||
|
||||
final recognizerConfig = sherpa_onnx.OfflineRecognizerConfig(
|
||||
model: modelConfig,
|
||||
feat: const sherpa_onnx.FeatureConfig(sampleRate: 16000, featureDim: 80),
|
||||
);
|
||||
|
||||
final recognizer = sherpa_onnx.OfflineRecognizer(recognizerConfig);
|
||||
|
||||
const testWave = '/tmp/sherpa_test/sherpa-onnx-zipformer-small-en-2023-06-26/test_wavs/0.wav';
|
||||
if (File(testWave).existsSync()) {
|
||||
final wave = sherpa_onnx.readWave(testWave);
|
||||
expect(wave.samples.isNotEmpty, isTrue);
|
||||
|
||||
final stream = recognizer.createStream();
|
||||
stream.acceptWaveform(samples: wave.samples, sampleRate: wave.sampleRate);
|
||||
recognizer.decode(stream);
|
||||
final result = recognizer.getResult(stream);
|
||||
print("Transcribed result: ${result.text}");
|
||||
expect(result.text.contains("NIGHTFALL"), isTrue);
|
||||
stream.free();
|
||||
}
|
||||
recognizer.free();
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user