Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions flutter/sherpa_onnx/lib/src/audio_tagging.dart
Original file line number Diff line number Diff line change
Expand Up @@ -172,20 +172,48 @@ class AudioTagging {
}

void free() {
if (SherpaOnnxBindings.sherpaOnnxDestroyAudioTagging == null) {
throw Exception("Please initialize sherpa-onnx first");
}

if (ptr == nullptr) {
return;
}
SherpaOnnxBindings.sherpaOnnxDestroyAudioTagging?.call(ptr);
ptr = nullptr;
}

/// The user has to invoke stream.free() on the returned instance
/// to avoid memory leak
OfflineStream createStream() {
if (SherpaOnnxBindings.sherpaOnnxAudioTaggingCreateOfflineStream == null) {
throw Exception("Please initialize sherpa-onnx first");
}

if (ptr == nullptr) {
throw Exception("Failed to create offline stream");
}
Comment on lines +193 to +195

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The exception message "Failed to create offline stream" is also used on line 202, but these two checks handle different error conditions. Using distinct messages would improve clarity and make debugging easier.

For example, here the error is that the AudioTagging instance itself is invalid. A more specific message could be:
throw Exception("AudioTagging instance is not valid. Cannot create stream.");

And for the check on line 202, where the native call fails:
throw Exception("Native offline stream creation failed.");


final p = SherpaOnnxBindings.sherpaOnnxAudioTaggingCreateOfflineStream
?.call(ptr) ??
nullptr;

if (p == nullptr) {
throw Exception("Failed to create offline stream");
}

return OfflineStream(ptr: p);
}

List<AudioEvent> compute({required OfflineStream stream, required int topK}) {
if (SherpaOnnxBindings.sherpaOnnxAudioTaggingCompute == null) {
throw Exception("Please initialize sherpa-onnx first");
}

if (ptr == nullptr || stream.ptr == nullptr) {
return <AudioEvent>[];
}
Comment on lines +213 to +215

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

For consistency with other methods like createStream that throw exceptions on invalid state, this method should also throw an exception instead of returning an empty list. Using an object that has been freed is a programmer error and should be surfaced as an exception to avoid hiding potential bugs.

Suggested change
if (ptr == nullptr || stream.ptr == nullptr) {
return <AudioEvent>[];
}
if (ptr == nullptr || stream.ptr == nullptr) {
throw Exception('AudioTagging or stream has been freed and cannot be used.');
}


final pp = SherpaOnnxBindings.sherpaOnnxAudioTaggingCompute
?.call(ptr, stream.ptr, topK) ??
nullptr;
Expand Down
59 changes: 59 additions & 0 deletions flutter/sherpa_onnx/lib/src/keyword_spotter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -171,15 +171,39 @@ class KeywordSpotter {
}

void free() {
if (SherpaOnnxBindings.destroyKeywordSpotter == null) {
throw Exception("Please initialize sherpa-onnx first");
}

if (ptr == nullptr) {
return;
}
SherpaOnnxBindings.destroyKeywordSpotter?.call(ptr);
ptr = nullptr;
}

/// The user has to invoke stream.free() on the returned instance
/// to avoid memory leak
OnlineStream createStream({String keywords = ''}) {
if (keywords == '') {
if (SherpaOnnxBindings.createKeywordStream == null) {
throw Exception("Please initialize sherpa-onnx first");
}
} else {
if (SherpaOnnxBindings.createKeywordStreamWithKeywords == null) {
throw Exception("Please initialize sherpa-onnx first");
}
}

if (ptr == nullptr) {
throw Exception("Failed to create online stream");
}

if (keywords == '') {
final p = SherpaOnnxBindings.createKeywordStream?.call(ptr) ?? nullptr;
if (p == nullptr) {
throw Exception("Failed to create online stream");
}
return OnlineStream(ptr: p);
}

Expand All @@ -188,17 +212,38 @@ class KeywordSpotter {
SherpaOnnxBindings.createKeywordStreamWithKeywords?.call(ptr, utf8) ??
nullptr;
calloc.free(utf8);

if (p == nullptr) {
throw Exception("Failed to create online stream");
}

return OnlineStream(ptr: p);
}

bool isReady(OnlineStream stream) {
if (SherpaOnnxBindings.isKeywordStreamReady == null) {
throw Exception("Please initialize sherpa-onnx first");
}

if (ptr == nullptr || stream.ptr == nullptr) {
return false;
}
Comment on lines +228 to +230

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using a freed object (ptr or stream.ptr is nullptr) should result in an exception rather than a silent failure. This helps in identifying incorrect API usage early. Please throw an exception here for consistency with methods like createStream.

Suggested change
if (ptr == nullptr || stream.ptr == nullptr) {
return false;
}
if (ptr == nullptr || stream.ptr == nullptr) {
throw Exception('KeywordSpotter or stream has been freed and cannot be used.');
}


int ready =
SherpaOnnxBindings.isKeywordStreamReady?.call(ptr, stream.ptr) ?? 0;

return ready == 1;
}

KeywordResult getResult(OnlineStream stream) {
if (SherpaOnnxBindings.getKeywordResultAsJson == null) {
throw Exception("Please initialize sherpa-onnx first");
}

if (ptr == nullptr || stream.ptr == nullptr) {
return KeywordResult(keyword: '');
}
Comment on lines +243 to +245

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Returning a default value when the object has been freed can hide bugs. It's better to throw an exception to signal that the object is in an invalid state and cannot be used.

Suggested change
if (ptr == nullptr || stream.ptr == nullptr) {
return KeywordResult(keyword: '');
}
if (ptr == nullptr || stream.ptr == nullptr) {
throw Exception('KeywordSpotter or stream has been freed and cannot be used.');
}


final json =
SherpaOnnxBindings.getKeywordResultAsJson?.call(ptr, stream.ptr) ??
nullptr;
Expand All @@ -216,10 +261,24 @@ class KeywordSpotter {
}

void decode(OnlineStream stream) {
if (SherpaOnnxBindings.decodeKeywordStream == null) {
throw Exception("Please initialize sherpa-onnx first");
}

if (ptr == nullptr || stream.ptr == nullptr) {
return;
}
Comment on lines +268 to +270

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This method should throw an exception if ptr or stream.ptr is nullptr, instead of returning silently. This ensures consistent error handling for invalid object states.

Suggested change
if (ptr == nullptr || stream.ptr == nullptr) {
return;
}
if (ptr == nullptr || stream.ptr == nullptr) {
throw Exception('KeywordSpotter or stream has been freed and cannot be used.');
}

SherpaOnnxBindings.decodeKeywordStream?.call(ptr, stream.ptr);
}

void reset(OnlineStream stream) {
if (SherpaOnnxBindings.resetKeywordStream == null) {
throw Exception("Please initialize sherpa-onnx first");
}

if (ptr == nullptr || stream.ptr == nullptr) {
return;
}
Comment on lines +279 to +281

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

To maintain consistency in error handling, please throw an exception here if the object or stream has been freed, instead of failing silently.

Suggested change
if (ptr == nullptr || stream.ptr == nullptr) {
return;
}
if (ptr == nullptr || stream.ptr == nullptr) {
throw Exception('KeywordSpotter or stream has been freed and cannot be used.');
}

SherpaOnnxBindings.resetKeywordStream?.call(ptr, stream.ptr);
}

Expand Down
15 changes: 15 additions & 0 deletions flutter/sherpa_onnx/lib/src/offline_punctuation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,26 @@ class OfflinePunctuation {
}

void free() {
if (SherpaOnnxBindings.sherpaOnnxDestroyOfflinePunctuation == null) {
throw Exception("Please initialize sherpa-onnx first");
}

if (ptr == nullptr) {
return;
}
SherpaOnnxBindings.sherpaOnnxDestroyOfflinePunctuation?.call(ptr);
ptr = nullptr;
}

String addPunct(String text) {
if (SherpaOnnxBindings.sherpaOfflinePunctuationAddPunct == null) {
throw Exception("Please initialize sherpa-onnx first");
}

if (ptr == nullptr) {
return '';
}
Comment on lines +119 to +121

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Returning an empty string when the object has been freed can mask issues. It's preferable to throw an exception to indicate that the OfflinePunctuation instance is in an invalid state.

Suggested change
if (ptr == nullptr) {
return '';
}
if (ptr == nullptr) {
throw Exception('OfflinePunctuation has been freed and cannot be used.');
}


final textPtr = text.toNativeUtf8();

final p = SherpaOnnxBindings.sherpaOfflinePunctuationAddPunct
Expand Down
51 changes: 51 additions & 0 deletions flutter/sherpa_onnx/lib/src/offline_recognizer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,13 @@ class OfflineRecognizer {
OfflineRecognizer._({required this.ptr, required this.config});

void free() {
if (SherpaOnnxBindings.destroyOfflineRecognizer == null) {
throw Exception("Please initialize sherpa-onnx first");
}

if (ptr == nullptr) {
return;
}
SherpaOnnxBindings.destroyOfflineRecognizer?.call(ptr);
ptr = nullptr;
}
Expand Down Expand Up @@ -813,6 +820,14 @@ class OfflineRecognizer {
}

void setConfig(OfflineRecognizerConfig config) {
if (SherpaOnnxBindings.offlineRecognizerSetConfig == null) {
throw Exception("Please initialize sherpa-onnx first");
}

if (ptr == nullptr) {
return;
}
Comment on lines +827 to +829

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Instead of returning silently, this method should throw an exception if ptr is nullptr. This makes the behavior consistent with other methods that handle invalid object states by throwing.

Suggested change
if (ptr == nullptr) {
return;
}
if (ptr == nullptr) {
throw Exception('OfflineRecognizer has been freed and cannot be used.');
}


final c = convertConfig(config);

SherpaOnnxBindings.offlineRecognizerSetConfig?.call(ptr, c);
Expand Down Expand Up @@ -1017,15 +1032,51 @@ class OfflineRecognizer {
/// The user has to invoke stream.free() on the returned instance
/// to avoid memory leak
OfflineStream createStream() {
if (SherpaOnnxBindings.createOfflineStream == null) {
throw Exception("Please initialize sherpa-onnx first");
}

if (ptr == nullptr) {
throw Exception("Failed to create offline stream");
}

final p = SherpaOnnxBindings.createOfflineStream?.call(ptr) ?? nullptr;

if (p == nullptr) {
throw Exception("Failed to create offline stream");
}

return OfflineStream(ptr: p);
}

void decode(OfflineStream stream) {
if (SherpaOnnxBindings.decodeOfflineStream == null) {
throw Exception("Please initialize sherpa-onnx first");
}

if (ptr == nullptr || stream.ptr == nullptr) {
return;
}
Comment on lines +1057 to +1059

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Silently returning when the recognizer or stream has been freed can hide programming errors. It's better to throw an exception to signal invalid API usage.

Suggested change
if (ptr == nullptr || stream.ptr == nullptr) {
return;
}
if (ptr == nullptr || stream.ptr == nullptr) {
throw Exception('OfflineRecognizer or stream has been freed and cannot be used.');
}


SherpaOnnxBindings.decodeOfflineStream?.call(ptr, stream.ptr);
}

OfflineRecognizerResult getResult(OfflineStream stream) {
if (SherpaOnnxBindings.getOfflineStreamResultAsJson == null) {
throw Exception("Please initialize sherpa-onnx first");
}

if (ptr == nullptr || stream.ptr == nullptr) {
return OfflineRecognizerResult(
text: '',
tokens: [],
timestamps: [],
lang: '',
emotion: '',
event: '',
);
}
Comment on lines +1069 to +1078

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Returning a default OfflineRecognizerResult can lead to unexpected behavior downstream. Throwing an exception when ptr or stream.ptr is null is a safer and more explicit way to handle this error condition.

    if (ptr == nullptr || stream.ptr == nullptr) {
      throw Exception('OfflineRecognizer or stream has been freed and cannot be used.');
    }


final json =
SherpaOnnxBindings.getOfflineStreamResultAsJson?.call(stream.ptr) ??
nullptr;
Expand Down
17 changes: 17 additions & 0 deletions flutter/sherpa_onnx/lib/src/offline_speaker_diarization.dart
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,13 @@ class OfflineSpeakerDiarization {
{required this.ptr, required this.config, required this.sampleRate});

void free() {
if (SherpaOnnxBindings.sherpaOnnxDestroyOfflineSpeakerDiarization == null) {
throw Exception("Please initialize sherpa-onnx first");
}

if (ptr == nullptr) {
return;
}
SherpaOnnxBindings.sherpaOnnxDestroyOfflineSpeakerDiarization?.call(ptr);
ptr = nullptr;
}
Expand Down Expand Up @@ -240,6 +247,10 @@ class OfflineSpeakerDiarization {

List<OfflineSpeakerDiarizationSegment> process(
{required Float32List samples}) {
if (SherpaOnnxBindings.sherpaOnnxOfflineSpeakerDiarizationProcess == null) {
throw Exception("Please initialize sherpa-onnx first");
}

if (ptr == nullptr) {
return <OfflineSpeakerDiarizationSegment>[];
}
Comment on lines 254 to 256

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This method should throw an exception if ptr is nullptr instead of returning an empty list. This makes the error handling consistent and prevents silent failures.

Suggested change
if (ptr == nullptr) {
return <OfflineSpeakerDiarizationSegment>[];
}
if (ptr == nullptr) {
throw Exception('OfflineSpeakerDiarization has been freed and cannot be used.');
}

Expand All @@ -266,6 +277,12 @@ class OfflineSpeakerDiarization {
required Float32List samples,
required int Function(int numProcessedChunks, int numTotalChunks) callback,
}) {
if (SherpaOnnxBindings
.sherpaOnnxOfflineSpeakerDiarizationProcessWithCallbackNoArg ==
null) {
throw Exception("Please initialize sherpa-onnx first");
}

if (ptr == nullptr) {
return <OfflineSpeakerDiarizationSegment>[];
}
Comment on lines 286 to 288

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Similar to other methods, please throw an exception here if the object has been freed (ptr == nullptr) instead of returning an empty list. This ensures consistent and robust error handling.

Suggested change
if (ptr == nullptr) {
return <OfflineSpeakerDiarizationSegment>[];
}
if (ptr == nullptr) {
throw Exception('OfflineSpeakerDiarization has been freed and cannot be used.');
}

Expand Down
53 changes: 41 additions & 12 deletions flutter/sherpa_onnx/lib/src/offline_speech_denoiser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ class OfflineSpeechDenoiser {
/// The user is responsible to call the OfflineSpeechDenoiser.free()
/// method of the returned instance to avoid memory leak.
factory OfflineSpeechDenoiser(OfflineSpeechDenoiserConfig config) {
if (SherpaOnnxBindings.sherpaOnnxCreateOfflineSpeechDenoiser == null) {
throw Exception("Please initialize sherpa-onnx first");
}

final c = calloc<SherpaOnnxOfflineSpeechDenoiserConfig>();
c.ref.model.gtcrn.model = config.model.gtcrn.model.toNativeUtf8();
c.ref.model.dpdfnet.model = config.model.dpdfnet.model.toNativeUtf8();
Expand All @@ -150,10 +154,6 @@ class OfflineSpeechDenoiser {
c.ref.model.debug = config.model.debug ? 1 : 0;
c.ref.model.provider = config.model.provider.toNativeUtf8();

if (SherpaOnnxBindings.sherpaOnnxCreateOfflineSpeechDenoiser == null) {
throw Exception("Please initialize sherpa-onnx first");
}

final ptr =
SherpaOnnxBindings.sherpaOnnxCreateOfflineSpeechDenoiser?.call(c) ??
nullptr;
Expand All @@ -172,11 +172,27 @@ class OfflineSpeechDenoiser {
}

void free() {
if (SherpaOnnxBindings.sherpaOnnxDestroyOfflineSpeechDenoiser == null) {
throw Exception("Please initialize sherpa-onnx first");
}

if (ptr == nullptr) {
return;
}

SherpaOnnxBindings.sherpaOnnxDestroyOfflineSpeechDenoiser?.call(ptr);
ptr = nullptr;
}

DenoisedAudio run({required Float32List samples, required int sampleRate}) {
if (SherpaOnnxBindings.sherpaOnnxOfflineSpeechDenoiserRun == null) {
throw Exception("Please initialize sherpa-onnx first");
}

if (ptr == nullptr) {
return DenoisedAudio(samples: Float32List(0), sampleRate: 0);
}
Comment on lines +192 to +194

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Returning a default DenoisedAudio object on a null pointer can hide bugs. It's better to throw an exception to indicate that the OfflineSpeechDenoiser instance is in an invalid state.

Suggested change
if (ptr == nullptr) {
return DenoisedAudio(samples: Float32List(0), sampleRate: 0);
}
if (ptr == nullptr) {
throw Exception('OfflineSpeechDenoiser has been freed and cannot be used.');
}


final n = samples.length;
final Pointer<Float> psamples = calloc<Float>(n);

Expand All @@ -193,19 +209,32 @@ class OfflineSpeechDenoiser {
return DenoisedAudio(samples: Float32List(0), sampleRate: 0);
}

final denoisedSamples = p.ref.samples.asTypedList(p.ref.n);
final denoisedSampleRate = p.ref.sampleRate;
final newSamples = Float32List.fromList(denoisedSamples);
final sampleRateOut = p.ref.sampleRate;
final nOut = p.ref.n;
Float32List newSamples = Float32List(0);
if (nOut > 0 && p.ref.samples != nullptr) {
newSamples = Float32List.fromList(p.ref.samples.asTypedList(nOut));
}

SherpaOnnxBindings.sherpaOnnxDestroyDenoisedAudio?.call(p);

return DenoisedAudio(samples: newSamples, sampleRate: denoisedSampleRate);
return DenoisedAudio(samples: newSamples, sampleRate: sampleRateOut);
}

int get sampleRate =>
SherpaOnnxBindings.sherpaOnnxOfflineSpeechDenoiserGetSampleRate
?.call(ptr) ??
0;
int get sampleRate {
if (SherpaOnnxBindings.sherpaOnnxOfflineSpeechDenoiserGetSampleRate ==
null) {
throw Exception("Please initialize sherpa-onnx first");
}

if (ptr == nullptr) {
return 0;
}
Comment on lines +230 to +232

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This getter should throw an exception if the object has been freed (ptr == nullptr) instead of returning 0. This makes the API more robust and predictable.

Suggested change
if (ptr == nullptr) {
return 0;
}
if (ptr == nullptr) {
throw Exception('OfflineSpeechDenoiser has been freed and cannot be used.');
}


return SherpaOnnxBindings.sherpaOnnxOfflineSpeechDenoiserGetSampleRate
?.call(ptr) ??
0;
}

Pointer<SherpaOnnxOfflineSpeechDenoiser> ptr;
OfflineSpeechDenoiserConfig config;
Expand Down
Loading