whisper_ggml 2.4.0 copy "whisper_ggml: ^2.4.0" to clipboard
whisper_ggml: ^2.4.0 copied to clipboard

OpenAI Whisper ASR (Automatic Speech Recognition) for Flutter

Whisper GGML #

On-device speech-to-text for Flutter, powered by whisper.cpp v1.9.1.

pub whisper.cpp Buy Me A Coffee

Transcribe audio files or transcribe live while the user speaks — fully on-device, no server, no API keys.

Highlights #

  • 🎙 Live transcription — partial transcripts stream in while recording, refined as more audio arrives. The model loads once per session and an adaptive energy gate keeps silence from producing hallucinated text.
  • 📄 File transcription — one call to transcribe a recording.
  • 📦 Offline-first — models download once and are cached, or ship them in your app's assets for fully offline use (see the example app).
  • 🌍 99 languages — pick one ('en', 'fr', 'de', …) or use 'auto' to detect.
  • 🎛 Decoding controls — vocabulary biasing, context conditioning, and non-speech token suppression exposed from whisper.cpp.
  • Fast — whisper.cpp v1.9.1 with Accelerate on Apple platforms; an 11-second clip transcribes in ~0.4 s with the base model on an Apple Silicon Mac.

Supported platforms #

Platform Minimum version
Android API 21
iOS 15.6
macOS 10.15
Windows 10 (x64)
Linux x64

Installation #

dependencies:
  whisper_ggml: ^2.4.0

Quick start #

import 'package:whisper_ggml/whisper_ggml.dart';

final controller = WhisperController();

final result = await controller.transcribe(
  model: WhisperModel.tiny,
  audioPath: '/path/to/audio.wav',
  lang: 'en',
);

print(result?.transcription.text);

Pass onProgress: (percent) => ... to receive transcription progress as a 0–100 percentage (coarse steps) while inference runs.

Pass withSegments: true to also get per-segment timestamps in result.transcription.segments (each segment has fromTs/toTs Durations and its text); add splitOnWord: true for one segment per word instead of per phrase.

Speaker-turn detection (diarization) #

With the tinydiarize model (WhisperModel.smallEnTdrz, English only), diarize: true marks the segments after which the speaker changes:

final result = await controller.transcribe(
  model: WhisperModel.smallEnTdrz,
  audioPath: '/path/to/audio.wav',
  lang: 'en',
  diarize: true,
  withSegments: true,
);

for (final segment in result?.transcription.segments ?? []) {
  print('${segment.text}${segment.speakerTurnNext ? ' [speaker turn]' : ''}');
}

This detects turn boundaries; it does not label or count speakers. With regular models diarize has no effect.

The model is downloaded automatically on first use. Non-WAV input is converted with the bundled FFmpeg — except on Windows and Linux, where FFmpeg is not bundled: an ffmpeg executable on PATH is used when present (on Linux, apt install ffmpeg or equivalent), otherwise the input must already be a 16 kHz mono WAV (the format the record package produces with AudioEncoder.wav, sampleRate: 16000, numChannels: 1).

Live (streaming) transcription #

transcribeLive accepts any stream of 16 kHz mono little-endian PCM16 audio and emits progressively refined transcripts while the audio flows. With the record package:

final pcmStream = await recorder.startStream(const RecordConfig(
  encoder: AudioEncoder.pcm16bits,
  sampleRate: 16000,
  numChannels: 1,
));

final session = await controller.transcribeLive(
  model: WhisperModel.base,
  pcm16Stream: pcmStream,
  lang: 'en',
);

session.partials.listen((text) {
  print(text); // full transcript so far, not a delta
});

// Later — stop recording, finalize, and free the model:
await recorder.stop();
final finalText = await session.stop();

Good to know:

  • The model stays loaded for the whole session; inference runs on a background isolate and never blocks the UI.
  • An adaptive energy gate keeps silence away from the decoder, which otherwise hallucinates on silent audio. For unusually loud rooms or quiet speakers, tune gateNoiseFloorCap, gateVoiceRatio, and gateRmsMin.
  • Only one live session can run at a time.
  • Real non-speech sounds (knocks, clicks) may transcribe as bracketed annotations like [door slams].

Models #

Model Multilingual English-only
tiny WhisperModel.tiny WhisperModel.tinyEn
base WhisperModel.base WhisperModel.baseEn
small WhisperModel.small WhisperModel.smallEn
medium WhisperModel.medium WhisperModel.mediumEn
large-v3 WhisperModel.large

Smaller models are faster; larger models are more accurate. tiny and base are good defaults for live transcription; small is a strong accuracy/speed balance for file transcription on modern phones.

Decoding options #

Available on both transcribe and transcribeLive:

Option Default What it does
initialPrompt null Biases decoding toward the vocabulary, names, and punctuation it contains — useful for domain-specific terms that otherwise get misrecognised. Decoding also mimics the prompt's style: an unpunctuated prompt tends to produce unpunctuated output.
noContext false Stops whisper from conditioning on prior-segment transcripts (like Python whisper's condition_on_previous_text=False). Helps against hallucinated repetition on short, independent utterances.
suppressNonSpeechTokens false Suppresses bracketed annotations such as [BLANK_AUDIO] or [music]. Side effect: real sounds may decode as plausible-looking words instead, which is why the example keeps it off.

Performance notes #

  • The native engine is compiled with -O3 on all platforms, including debug builds on iOS/macOS, Windows (/O2), and Linux — transcription speed there is close to release.
  • Windows and Linux x64 builds target AVX2 by default, like upstream whisper.cpp's standard x64 binaries (supported by virtually every x64 CPU since ~2013). For very old CPUs, build with -DWHISPER_GGML_AVX2=OFF.
  • Android debug builds run the Dart layer in JIT mode; use --release for representative performance.
  • The bundled whisper.cpp v1.9.1 is roughly 15× faster than the engine in versions before 2.0.0.