To run reliable offline speech to text on Raspberry Pi, use the Vosk engine paired with a Seeed ReSpeaker 2-Mic I2S HAT on a Raspberry Pi 5 8GB. This combination delivers sub-200ms latency without relying on cloud APIs or saturating the CPU, which is a common failure point when attempting to run Whisper.cpp on ARM architectures without dedicated NPU acceleration.
The Decision Tree: Which Speech-to-Text Engine?
Before wiring the HAT, you must select the right inference engine. The choice dictates your RAM requirements, latency, and network dependency. Here is the decision matrix for embedded Raspberry Pi deployments in 2026:
| Engine | Internet Required? | Avg Latency (Pi 5) | RAM / CPU Load | Verdict |
|---|---|---|---|---|
| Google Cloud Speech API | Yes | 400-800ms | Low (Network bound) | Reject for offline/embedded edge nodes. |
| Whisper.cpp (base model) | No | 1.5 - 3.0s | High (Saturates all 4 cores) | Use only for batch transcription, not real-time streaming. |
| Vosk (Small US English) | No | < 200ms | Low (~400MB RAM, 15% CPU) | DEFAULT PICK: Best for real-time wake-word and command streaming. |
Decision Path: If your project requires real-time command parsing (e.g., home automation relays, robot navigation) and must function during network outages, choose Vosk. If you are building a post-processing dictation tool where a 3-second delay is acceptable and accuracy on heavy accents is paramount, choose Whisper.cpp. For this build, we terminate on Vosk.
Hardware Spec Sheet & Pin Mapping
The Seeed ReSpeaker 2-Mic HAT uses the WM8960 stereo codec, communicating via I2S for audio data and I2C for LED/button control. Because the Pi 5 routes peripherals through the RP1 chip, the physical 40-pin header maintains backward compatibility, but the device tree overlays must be Pi 5 specific.
| Component | Exact Variant | Approx Cost (2026) | Notes |
|---|---|---|---|
| SBC | Raspberry Pi 5 (8GB) | $80.00 | 4GB variant works, but 8GB prevents OOM if running MQTT + Vosk. |
| Audio HAT | Seeed ReSpeaker 2-Mics Pi HAT | $14.00 | Includes WM8960 codec and dual MEMS mics. |
| Storage | 32GB microSD (A2 Rated) | $12.00 | A2 rating required for random I/O during model loading. |
| Power | 27W USB-C PD PSU (Official) | $12.00 | Prevents brownouts when CPU spikes during inference. |
I2S and I2C Pin Mapping
The HAT uses the following BCM pins. Do not use these pins for GPIO in your Python script, or you will crash the audio bus.
| Function | BCM Pin | Physical Pin | Protocol |
|---|---|---|---|
| BCLK (Bit Clock) | 18 | 12 | I2S |
| LRCLK (Frame Sync) | 19 | 35 | I2S |
| DOUT (Data Out / Mic) | 20 | 38 | I2S |
| DIN (Data In / Speaker) | 21 | 40 | I2S |
| SDA (Codec Control) | 2 | 3 | I2C |
| SCL (Codec Control) | 3 | 5 | I2C |
Step-by-Step Build: Vosk Offline STT on Pi 5
- Flash the OS: Use Raspberry Pi Imager to flash Raspberry Pi OS Bookworm (64-bit). Enable SSH and configure WiFi in the Imager settings.
- Mount the HAT: Align the ReSpeaker 2-Mic HAT over the 40-pin header and press down firmly. Secure with the included standoffs.
- Install the Voicecard Driver: Bookworm uses PipeWire by default, which can conflict with raw ALSA I2S access. We must install the Seeed DKMS driver to properly register the WM8960 codec.
git clone https://github.com/HinTak/seeed-voicecard.git cd seeed-voicecard sudo ./install.sh sudo reboot - Verify ALSA Registration: After reboot, run
aplay -l. You must seecard 1: seeed2micvoicecard. If it shows as card 0, that is fine, but note the card number. - Set Up Python Environment: Never install audio libraries globally in Bookworm. Use a virtual environment.
python3 -m venv ~/stt_env source ~/stt_env/bin/activate pip install vosk sounddevice - Download the Vosk Model: Download the small US English model (~50MB). For higher accuracy, download the large 1.8GB model, but ensure your Pi 5 has active cooling.
wget https://alphacephei.com/vosk/models/vosk-model-small-en-us-0.15.zip unzip vosk-model-small-en-us-0.15.zip rm vosk-model-small-en-us-0.15.zip
The Python Implementation
This script uses a non-blocking audio callback via sounddevice to push raw PCM data into a thread-safe queue. The main thread reads the queue and feeds it to the Vosk Kaldi recognizer. This prevents audio buffer overruns during garbage collection pauses.
import sounddevice as sd
from vosk import Model, KaldiRecognizer
import queue
import json
import sys
import os
# Hardware mapping note: I2S pins (BCM 18, 19, 20, 21) are handled by the OS overlay.
# We address the HAT via its ALSA string name rather than hardcoded index.
DEVICE_NAME = 'seeed-2mic-voicecard'
MODEL_PATH = 'vosk-model-small-en-us-0.15'
SAMPLE_RATE = 16000
def find_audio_device(name_part):
"""Finds the ALSA device index for the ReSpeaker HAT."""
devices = sd.query_devices()
for i, dev in enumerate(devices):
if name_part.lower() in dev['name'].lower() and dev['max_input_channels'] > 0:
return i
return None
def main():
# 1. Verify Model Directory
if not os.path.exists(MODEL_PATH):
print(f'FATAL: Model directory "{MODEL_PATH}" not found. Did you extract the zip?')
sys.exit(1)
# 2. Load Vosk Model
try:
print('Loading Vosk model into RAM...')
model = Model(MODEL_PATH)
recognizer = KaldiRecognizer(model, SAMPLE_RATE)
except Exception as e:
print(f'FATAL: Failed to load model. Error: {e}')
sys.exit(1)
# 3. Locate Audio Device
dev_index = find_audio_device(DEVICE_NAME)
if dev_index is None:
print(f'FATAL: Could not find input device containing "{DEVICE_NAME}".')
print('Run "aplay -l" and "arecord -l" to check ALSA status.')
sys.exit(1)
# 4. Setup Thread-Safe Queue
audio_queue = queue.Queue()
def audio_callback(indata, frames, time, status):
"""Called by PortAudio in a separate thread."""
if status:
print(f'Audio Status Warning: {status}', file=sys.stderr)
audio_queue.put(bytes(indata))
# 5. Stream Audio
print(f'Starting STT stream on device {dev_index}... Speak now.')
try:
with sd.RawInputStream(
samplerate=SAMPLE_RATE,
blocksize=8000,
device=dev_index,
dtype='int16',
channels=1,
callback=audio_callback
):
while True:
data = audio_queue.get()
if recognizer.AcceptWaveform(data):
result = json.loads(recognizer.Result())
text = result.get('text', '').strip()
if text:
print(f'COMMAND RECOGNIZED: {text}')
# Add your GPIO/MQTT logic here
if 'shutdown' in text:
print('Shutdown command received. Exiting.')
break
else:
# Optional: print partial results for UI feedback
partial = json.loads(recognizer.PartialResult())
if partial.get('partial'):
pass # print(f"Listening: {partial['partial']}", end='\r')
except KeyboardInterrupt:
print('\nStream interrupted by user.')
except sd.PortAudioError as e:
print(f'FATAL: PortAudio stream failed to open.\nError: {e}')
sys.exit(1)
if __name__ == '__main__':
main()
Debugging: Exact Errors and Ranked Causes
Audio pipelines on Linux embedded systems are notoriously fragile. When the script fails, it will almost always throw one of two specific errors. Here is how to resolve them.
Error 1: PortAudio Device Unavailable
Exact Error String: sounddevice.PortAudioError: Error opening InputStream: Invalid device [PaErrorCode -9986] or Device unavailable [PaErrorCode -9985]
Ranked Causes & Fixes:
- PipeWire is hogging the ALSA device. Bookworm runs PipeWire by default. If PipeWire claims the WM8960 codec, raw ALSA access via PortAudio will fail. Fix: Run
systemctl --user stop pipewirebefore executing your script, or configure PipeWire to release the device. - I2S Overlay Missing. The RP1 chip didn't initialize the I2S bus. Fix: Check
/boot/firmware/config.txtfordtparam=i2s=onanddtoverlay=seeed-2mic-voicecard. If missing, the Seeed install script failed; re-run it. - Wrong Device Index. The script hardcoded an index that shifted after a USB device was plugged in. Fix: The provided code uses string matching (
find_audio_device) to prevent this. EnsureDEVICE_NAMEmatches the output ofarecord -l.
Error 2: Vosk Model Load Failure
Exact Error String: OSError: Cannot load model from 'vosk-model-small-en-us-0.15'
Ranked Causes & Fixes:
- Incorrect Working Directory. You ran the script from
~/stt_envinstead of the directory containing the extracted model folder. Fix:cdinto the directory containing both your.pyfile and thevosk-model-...folder. - Corrupt Extraction. The
unzipcommand created a nested folder (e.g.,vosk-model-small-en-us-0.15/vosk-model-small-en-us-0.15). Fix: Runls -linside the model folder. You must seeam,conf,graph, andivectordirectories at the root of the path specified inMODEL_PATH.
- Run
aplay -landarecord -l. If the ReSpeaker does not appear in both lists, the hardware or I2S overlay is not initializing. - Run
dmesg | grep i2s. Look forasoc-audio-graph-cardbinding errors. This indicates a device tree mismatch between the Pi 5 RP1 and the HAT. - Run
cat /boot/firmware/config.txt | grep dtoverlay. Verify the exact stringdtoverlay=seeed-2mic-voicecardis present and not commented out.
Extending and Simplifying the Pipeline
Depending on your project requirements, you may need to alter the architecture of the STT pipeline.
How to Simplify (For Basic Trigger Scripts)
If you do not need continuous, real-time streaming and only want to record 3-second audio clips to parse after the fact, drop the queue and callback architecture entirely. Use blocking recording:
audio_data = sd.rec(int(3 * SAMPLE_RATE), samplerate=SAMPLE_RATE, channels=1, dtype='int16')
sd.wait()
if recognizer.AcceptWaveform(audio_data.tobytes()):
print(json.loads(recognizer.Result())['text'])
This reduces code complexity by 60% but introduces a 3-second latency gap between speaking and processing.
How to Extend (For Smart Home Integration)
To turn this into a practical home automation node, extend the if text: block with an MQTT publisher. Install paho-mqtt in your virtual environment and publish the parsed string to a broker (e.g., Home Assistant Mosquitto).
Furthermore, to prevent the Pi from processing ambient noise continuously, integrate Porcupine Wake Word Engine (by Picovoice) upstream of Vosk. Porcupine uses less than 2% CPU to listen for a trigger phrase (like 'Computer'), and only activates the Vosk inference pipeline when triggered, saving thermal headroom and reducing false positives.
References: Vosk Model Registry (Alphacephei), Seeed Studio ReSpeaker Wiki, Raspberry Pi Device Tree Configuration Docs.






