speech_sherpa 0.1.0 copy "speech_sherpa: ^0.1.0" to clipboard
speech_sherpa: ^0.1.0 copied to clipboard

Cross-platform speech provider over sherpa-onnx - streaming and batch STT, VAD, diarization, and speaker embeddings.

speech_sherpa #

Cross-platform implementation of the provider-neutral speech contracts, backed by sherpa-onnx. This is the inference floor for every platform without an on-device Apple runtime: the same models run on macOS, Windows, Linux, iOS, and Android through ONNX Runtime.

final registry = SherpaModelRegistry(root: modelsDirectory);
await registry.installRecognition(SherpaRecognitionModel.defaultModel);

final provider = SherpaSpeechProvider(registry: registry);
final result = await provider.transcribe(
  BatchRecognitionRequest(audio: recordedFile),
);
print(result.text);
await provider.close();

Capabilities #

Capability Status
Batch speech-to-text Implemented
Streaming speech-to-text Implemented (streaming Zipformer) — see below
Voice-activity detection Implemented (Silero)
Diarization Implemented (pyannote + WeSpeaker)
Speaker embeddings Implemented, provenance-tagged
Text-to-speech Not implemented

Recognition capability is declared per model, not provider-wide. A streaming Zipformer cannot be batch-decoded and Parakeet cannot be streamed, so each model descriptor advertises exactly one of the two and transcribe and prepareStreamingRecognition resolve from disjoint halves of the catalog.

Streaming recognition #

await registry.installRecognition(SherpaRecognitionModel.streamingZipformerEn);

final session = await provider.prepareStreamingRecognition(
  StreamingRecognitionRequest(inputFormat: captureFormat),
);
session.results.listen((event) {
  switch (event) {
    case RecognitionPartial(:final transcript):
      renderVolatile(transcript.text); // replaces the previous partial
    case RecognitionFinal(:final transcript):
      appendConfirmed(transcript.text); // never revised again
    default:
      break;
  }
});
await session.write(frame);
await session.finish();

Each accepted chunk yields the hypothesis for the segment being decoded, emitted as a volatile RecognitionPartial that replaces the last one; when the segment closes, the same hypothesis is re-emitted as a confirmed RecognitionFinal and the decoder resets. An unchanged hypothesis is not republished, so a chunk of silence costs a consumer nothing.

Two caveats worth reading before choosing this over batch.

Accuracy is an open question. Streaming Zipformer against batch Parakeet v3 has not been evaluated here. Streaming buys latency by decoding with a fixed chunk and a bounded left context — it cannot revise a word once the encoder has moved past it, and it is a smaller English-only model besides. If a transcript is produced after the fact, batch remains the better answer; if you need words on screen while someone is speaking, this is the path. The alternative for the in-between case is speech_pipeline (in the sibling conversation-kit-dart repository), which cuts rolling windows and gives a live transcript from any batch provider at higher latency and batch accuracy.

The endpointer is rule-based, not semantic. enableEndpoint runs sherpa's three silence rules — trailing silence before speech, trailing silence after speech, and a maximum utterance length — over decoder state. It decides where one confirmed segment ends. It cannot tell a thinking pause from a finished thought, so it is not an end-of-utterance model, and it must not be used to decide that a speaker has yielded the turn. speech_pipeline and dedicated EOU providers remain the answer there; the rules are tunable through SherpaStreamingRecognitionOptions.

Platforms #

Platform Notes
macOS x64 and arm64
Windows x64
Linux x64
iOS arm64
Android arm64, armeabi-v7a, x86, x86_64

Native libraries ship with the sherpa_onnx platform packages. A host that cannot resolve the library by leaf name can pass a directory to SherpaNativeRuntime(libraryDirectory: ...).

Models #

Nothing is bundled. The registry downloads on demand into a directory you supply, so the SDK never guesses at an application's storage layout.

Model Kind Approx. size Languages
Parakeet TDT 0.6B v3 int8 (batch default) transducer 600 MB 25 European
Parakeet TDT 0.6B v2 int8 transducer 480 MB English
Whisper large-v3-turbo int8 whisper 626 MB 99+
Whisper base.en int8 whisper 198 MB English
Streaming Zipformer 2023-06-26 (streaming default) streaming transducer 296 MB English
Streaming Zipformer 20M 2023-02-17 streaming transducer 122 MB English
Silero VAD vad 2 MB
pyannote segmentation 3.0 diarization 9 MB
WeSpeaker ResNet34 (VoxCeleb) embedding 26 MB English

The streaming entries use the chunk-16-left-128 export with an int8 encoder and joiner and an fp32 decoder, matching the upstream Flutter streaming example. The 20M variant is the low-power option for phones and single-board hosts, at correspondingly lower accuracy.

Recognition weights are hundreds of megabytes. Call SherpaSpeechProvider.unloadRecognizer() when a recording session ends to release them; the next transcribe reloads lazily.

Two gotchas worth knowing #

Both are encoded in this package, and both are easy to reintroduce by "cleaning up" the code that looks redundant.

modelType stays empty. sherpa auto-routes NeMo-Parakeet versus k2-Zipformer from the model metadata. Setting modelType: 'transducer' — which reads like the obviously correct value — makes every Parakeet model fail on a vocab_size metadata lookup.

Bindings are per-isolate. sherpa resolves its native symbols into isolate-local statics, so every isolate touching the API must call ensureSherpaBindings() for itself. Skipping it in a worker surfaces as Please initialize sherpa-onnx first, which reads like a missing model rather than a missing symbol.

Threading #

A batch decode is a synchronous FFI call lasting hundreds of milliseconds to seconds. Recognition therefore runs on a long-lived worker isolate, with audio crossing as TransferableTypedData; diarization uses a throwaway isolate, because it runs once per recording rather than once per window. Audio stays Float32List end to end — sherpa consumes float32 natively, so there is no PCM16 detour.

Streaming gets a second worker of the same shape, not a mode of the batch one. A streaming step is short, but it is still synchronous FFI and still belongs off the main isolate; the reason it is a separate worker is that a streaming session is stateful across commands — the OnlineStream is the session, so reset and finish only mean anything relative to the stream a previous accept fed, where the batch worker creates and frees its stream inside one command. Sharing one isolate would also queue a multi-second batch decode ahead of a 100 ms chunk that has to keep up with real time. Driver calls are serialized by the session, because each one mutates the decode stream the next one reads.

Testing #

Unit tests inject a fake SherpaRuntime and run without ONNX Runtime, model files, or isolates. Tests that exercise the real native path are gated behind an environment variable so CI never downloads 600 MB:

SPEECH_SHERPA_MODELS=~/models flutter test --tags integration

Attribution #

The worker-isolate transcriber, model registry, and diarization configuration are derived from Control Center, MIT © 2026 Samuel Alev. See NOTICE. sherpa-onnx itself is Apache-2.0, © k2-fsa.

1
likes
150
points
11
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Cross-platform speech provider over sherpa-onnx - streaming and batch STT, VAD, diarization, and speaker embeddings.

Repository (GitHub)
View/report issues

Topics

#audio #speech #onnx #transcription

License

MIT (license)

Dependencies

archive, audio_core, audio_processing, flutter, http, path, sherpa_onnx, speech_core

More

Packages that depend on speech_sherpa