feat: upgrade local speech recognition engine to SenseVoice-Small with hardware audio enhancements
This commit is contained in:
@@ -15,7 +15,7 @@ class SherpaSttService {
|
||||
|
||||
bool get isReady => _isInitialized && _recognizer != null;
|
||||
|
||||
/// Initializes Sherpa-ONNX bindings and unpacks bundled model assets to local disk if needed.
|
||||
/// Initializes SenseVoice-Small 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;
|
||||
@@ -29,32 +29,30 @@ class SherpaSttService {
|
||||
}
|
||||
|
||||
final docDir = await getApplicationDocumentsDirectory();
|
||||
final modelDir = Directory('${docDir.path}/sherpa_models');
|
||||
final modelDir = Directory('${docDir.path}/sense_voice_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',
|
||||
'model.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 ByteData data = await rootBundle.load('assets/models/sense_voice/$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',
|
||||
senseVoice: sherpa_onnx.OfflineSenseVoiceModelConfig(
|
||||
model: '${modelDir.path}/model.int8.onnx',
|
||||
language: 'auto',
|
||||
useInverseTextNormalization: true,
|
||||
),
|
||||
tokens: '${modelDir.path}/tokens.txt',
|
||||
numThreads: 2,
|
||||
@@ -69,17 +67,17 @@ class SherpaSttService {
|
||||
_recognizer = sherpa_onnx.OfflineRecognizer(recognizerConfig);
|
||||
_isInitialized = true;
|
||||
_isInitializing = false;
|
||||
debugPrint('[SherpaSttService] Local ONNX ASR engine initialized successfully.');
|
||||
debugPrint('[SherpaSttService] SenseVoice-Small ONNX ASR engine initialized successfully.');
|
||||
return true;
|
||||
} catch (e, stack) {
|
||||
debugPrint('[SherpaSttService] Failed to initialize local ASR engine: $e\n$stack');
|
||||
debugPrint('[SherpaSttService] Failed to initialize SenseVoice ASR engine: $e\n$stack');
|
||||
_isInitializing = false;
|
||||
_isInitialized = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Transcribes a local 16kHz mono WAV audio file.
|
||||
/// Transcribes a local 16kHz mono WAV audio file using SenseVoice-Small.
|
||||
Future<String?> transcribeWav(String wavPath) async {
|
||||
try {
|
||||
if (!_isInitialized) {
|
||||
@@ -115,14 +113,19 @@ class SherpaSttService {
|
||||
}
|
||||
}
|
||||
|
||||
/// Cleans and formats raw recognized text into natural English casing.
|
||||
/// Cleans and formats raw recognized SenseVoice text (strips emotion/event tags, normalizes 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);
|
||||
// Strip SenseVoice special tags like <|zh|>, <|en|>, <|NEUTRAL|>, <|HAPPY|>, <|Speech|>, <|withitn|>, <|woitn|>, etc.
|
||||
var cleaned = text.replaceAll(RegExp(r'<\|[a-zA-Z0-9_\-\s]+\|>'), '').trim();
|
||||
if (cleaned.isEmpty) return cleaned;
|
||||
// Normalize consecutive spaces
|
||||
cleaned = cleaned.replaceAll(RegExp(r'\s+'), ' ').trim();
|
||||
// Capitalize first character if it's a letter
|
||||
if (cleaned.isNotEmpty && cleaned[0].toLowerCase() != cleaned[0].toUpperCase()) {
|
||||
cleaned = cleaned[0].toUpperCase() + cleaned.substring(1);
|
||||
}
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
|
||||
@@ -81,6 +81,9 @@ class VoiceService {
|
||||
encoder: AudioEncoder.wav,
|
||||
sampleRate: 16000,
|
||||
numChannels: 1,
|
||||
noiseSuppress: true,
|
||||
echoCancel: true,
|
||||
autoGain: true,
|
||||
),
|
||||
path: '${recordings.path}/practice_$timestamp.wav',
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user