Speech-to-Text (STT)
The Speech-to-Text extension transcribes spoken audio into text directly on-device using OpenAI's Whisper or Alibaba's Qwen3-ASR paired with an integrated FSMN Voice Activity Detector (VAD).
The pipeline supports two primary workflows:
- Live Microphone Streaming: Streams real-time audio straight from the microphone. As the user speaks, Whisper continuously returns draft transcripts and automatically commits finalized sentences upon pauses.
- Pre-recorded Audio Transcription: Transcribes pre-recorded audio buffers or audio files in a single pass, with optional token-by-token streaming callbacks.
The showcase streams a fixed speech-and-silence sample through the on-device model for repeatable results.
Quick Start
The useSpeechToText hook manages downloading model weights, the tokenizer, and the bundled VAD model. To capture live audio from the microphone, feed PCM chunks into streamInsert() using a microphone recorder such as react-native-audio-api:
import { useState } from 'react';
import { models, useSpeechToText, WHISPER_SAMPLE_RATE_HZ } from 'react-native-executorch';
import { useAudioRecorder } from './hooks/useAudioRecorder'; // Custom helper built with react-native-audio-api
function TranscriptionComponent() {
const [committedText, setCommittedText] = useState('');
const [nonCommittedText, setNonCommittedText] = useState('');
const stt = useSpeechToText(models.speechToText.WHISPER.EN.TINY.DEFAULT);
const recorder = useAudioRecorder();
// Hook state:
// stt.isReady — true once Whisper model, tokenizer, and VAD are loaded
// stt.downloadProgress — 0 to 100 download progress across all files
// stt.error — Error instance if download or load failed
// stt.resource — resolved config with all URLs replaced by local file paths
const handleToggleRecording = async () => {
if (recorder.isRecording) {
await recorder.stopRecording();
stt.streamStop?.(); // Signal stream to finalize and close
return;
}
if (!stt.isReady || !stt.stream || !stt.streamInsert) return;
setCommittedText('');
setNonCommittedText('');
// 1. Consume the live transcription stream in the background
(async () => {
const textStream = stt.stream!({ language: 'en' });
for await (const update of textStream) {
setCommittedText(update.committed);
setNonCommittedText(update.nonCommitted);
}
})();
// 2. Start microphone recording (16 kHz mono Float32 PCM)
await recorder.startRecording(WHISPER_SAMPLE_RATE_HZ, (samples) => {
stt.streamInsert?.(samples);
});
};
}
See src/app/(screens)/speech-to-text.tsx in the React Native ExecuTorch Gallery for a complete, runnable screen featuring microphone controls, live audio streaming, and animated transcription UI.
Output Format & Live Streaming
When streaming live microphone audio with stream(), the generator yields transcription updates on every voice activity event:
type WhisperStreamUpdate = {
/** Finalized transcript of completed sentences and clauses */
readonly committed: string;
/** Live in-progress transcript of the active speech segment that may still update */
readonly nonCommitted: string;
};
How Live Streaming Works
- Committed vs Non-Committed Text: As the user speaks, Whisper continuously transcribes the active speech window into
nonCommittedtext. Once the speaker pauses or completes a clause (detected by the integrated Voice Activity Detector), that segment is finalized and appended tocommittedtext. - Background Audio Buffer: Audio chunks fed via
streamInsert(pcmSamples)are accumulated in an internal audio ring buffer on a background thread without blocking the JavaScript UI. - Graceful Termination: Calling
streamStop()signals the stream to process any remaining speech in the buffer, commit the final clause, and close the generator.
Pre-Recorded Audio Transcription
To transcribe an existing audio recording or batch audio buffer all at once, use transcribe():
// audioData: Float32Array PCM samples at 16000 Hz
const transcript = await stt.transcribe(audioData, {
language: 'en',
});
console.log('Full transcript:', transcript);
You can also pass an optional onToken callback to receive decoded word/subword tokens in real time as Whisper generates them:
const transcript = await stt.transcribe(audioData, { language: 'en' }, (token) => {
console.log('Decoded token:', token);
});
To abort an in-flight transcription prematurely, call transcribeStop():
// Cancels active transcribe() execution and rejects the pending promise
stt.transcribeStop?.();
Imperative API
For background services, offline audio processors, or non-React component logic, instantiate the pipeline imperatively using createWhisperSpeechToText:
import { createWhisperSpeechToText, download, models } from 'react-native-executorch';
// Download and cache Whisper weights, tokenizer, and bundled VAD
const model = await download(models.speechToText.WHISPER.EN.BASE.DEFAULT);
const stt = await createWhisperSpeechToText(model);
try {
const transcript = await stt.transcribe(audioData, { language: 'en' });
console.log('Transcript:', transcript);
} finally {
// Always release native resources when finished
stt.dispose();
}
Synchronous Execution
For synchronous worklet execution contexts or frame-by-frame audio processors, createWhisperSpeechToText exposes a synchronous transcribeWorklet function:
// Called synchronously inside a worklet runtime without Promise scheduling overhead
const transcript = stt.transcribeWorklet(audioData, { language: 'en' });
See Worklets & Threading for details on worklet execution contexts and zero-copy host objects.
Available Models
The library provides ready-to-use Whisper models from the Software Mansion HuggingFace Whisper Collection, available in models.speechToText:
| Model Family | Variants | Size Range | Supported Backends | Languages | Notes |
|---|---|---|---|---|---|
| Whisper Tiny | Multilingual, English | 57.1 MB – 221.8 MB | XNNPACK (CPU), Core ML (Apple), MLX (Apple), Vulkan (Android) | English / WHISPER_LANGUAGES (99+ languages) | Ultra-fast transcription with minimal RAM usage. |
| Whisper Base | Multilingual, English | 97.8 MB – 380.2 MB | XNNPACK (CPU), Core ML (Apple), MLX (Apple), Vulkan (Android) | English / WHISPER_LANGUAGES (99+ languages) | Balanced accuracy and speed for general voice dictation. |
| Whisper Small | Multilingual, English | 276.0 MB – 1.05 GB | XNNPACK (CPU), Core ML (Apple), MLX (Apple), Vulkan (Android) | English / WHISPER_LANGUAGES (99+ languages) | High-capacity model for complex, noisy, or multi-speaker audio. |
| Qwen3-ASR 0.6B | 0.6B | 707.7 MB – 808.1 MB | XNNPACK (CPU), Core ML + MLX (Apple) | QWEN3_ASR_LANGUAGES (30 languages) | Detects the language; pass language only to force one. |
API Reference
Hooks & Pipelines
useSpeechToText()— React hook for Whisper model loading, downloading, and live transcription state.createWhisperSpeechToText()— Imperative factory for Whisper Speech-to-Text pipelines.createQwen3AsrSpeechToText()— Imperative factory for Qwen3-ASR Speech-to-Text pipelines.
Types & Options
WhisperSpeechToText— Whisper runner interface (transcribe,transcribeWorklet,transcribeStop,stream,streamInsert,streamStop,dispose).WhisperSttModel— Whisper model spec including model path, tokenizer path, and bundled VAD model.WhisperSttOptions— Per-call transcription options (language).WhisperStreamOptions— Live microphone streaming options (language,vadOptions).WhisperLanguage— Union of supported Whisper language codes.Qwen3AsrSttModel,Qwen3AsrSttOptions,Qwen3AsrLanguage— Qwen3-ASR counterparts.
Constants & Model Presets
WHISPER_SAMPLE_RATE_HZ— Target audio sample rate expected by Whisper models (16000 Hz).WHISPER_LANGUAGES— Array of 99+ supported language codes.QWEN3_ASR_LANGUAGES— Language codes Qwen3-ASR can be forced to.models.speechToText— Pre-configured Whisper models registry.
View the implementation on GitHub: