Building a high-fidelity Raspberry Pi sound recorder requires bypassing the Pi's lack of onboard analog-to-digital audio conversion. The direct answer for a low-noise, high-sample-rate build: use a Raspberry Pi 4 Model B paired with an I2S MEMS microphone (Adafruit SPH0645LM4H, Product ID 3421) and the Python sounddevice library. This setup captures 24kHz to 48kHz audio directly via the I2S bus, avoiding the noisy ADCs and ground-loop hum found on cheap $10 USB sound cards.

This guide targets the Raspberry Pi 4 Model B (Rev 1.4/1.5) running Raspberry Pi OS Bookworm (64-bit). We will cover the exact I2S pin mapping, the updated Bookworm boot configuration, a complete Python recording script with error handling, and how to debug the inevitable ALSA buffer errors.

Hardware Specs and I2S Pin Mapping

Unlike analog microphones that require an ADC, the SPH0645LM4H has a built-in PDM-to-I2S converter. It outputs digital audio directly to the Pi's I2S peripheral pins (PCM). Below is the hardware specification sheet and the exact pin mapping you need for your breadboard.

Adafruit SPH0645LM4H Specification Sheet

Parameter Value Notes
Signal-to-Noise Ratio (SNR) 65 dB(A) Comparable to entry-level studio condenser mics
Sensitivity -26 dBFS Measured at 1kHz, 94dB SPL
Operating Voltage 2.5V to 3.3V Do NOT connect to Pi 5V pin; use 3V3
Sample Rate Support 8kHz to 48kHz Hardware clock dependent; 44.1kHz/48kHz ideal
Output Type I2S (24-bit data, 18-bit resolution) Lower 6 bits are zero-padded by the sensor

Pi 4 to I2S MEMS Pin Mapping

Use 24 AWG solid-core jumper wires for breadboard connections to ensure reliable contact with the MEMS breakout castellations. The I2S bus on the Pi uses specific BCM (Broadcom) GPIO pins, not physical pin numbers.

Mic Breakout Pin Pi 4 BCM GPIO Pi 4 Physical Pin Function
VIN N/A 1 (3V3) Power (3.3V)
GND N/A 6 (Ground) Common Ground
BCLK GPIO 18 12 Bit Clock (PCM_CLK)
DOUT GPIO 20 38 Data Out (PCM_DIN)
LRCL GPIO 21 40 Left/Right Clock (PCM_FS)
Bench Tip: The SPH0645LM4H is a mono microphone. However, the I2S protocol expects a stereo stream. The microphone will output data on the Left channel, and the Right channel will be silent (zeroed). The Python script below handles this by extracting only the active channel.

OS Configuration and Wiring Steps

Before writing code, you must enable the I2S hardware overlay in the Pi's boot configuration. In Raspberry Pi OS Bookworm, the configuration file moved from /boot/config.txt to /boot/firmware/config.txt. Many outdated tutorials miss this, resulting in silent failures.

  1. Wire the hardware: Connect the 5 pins from the breakout board to the Pi 4 as defined in the pin mapping table above. Double-check that VCC is on 3.3V. Feeding 5V to the SPH0645 will permanently brick the MEMS capsule.
  2. Edit the boot config: Open a terminal and run sudo nano /boot/firmware/config.txt.
  3. Enable I2S: Add the following lines to the bottom of the file:
    dtparam=i2s=on
    dtoverlay=i2s-mmap
  4. Blacklist the onboard audio (optional but recommended): To prevent ALSA from prioritizing the HDMI audio jack, comment out the onboard audio line by adding a #: #dtparam=audio=on.
  5. Reboot: Run sudo reboot.
  6. Install Python dependencies: After rebooting, install PortAudio and the Python libraries:
    sudo apt update
    sudo apt install libportaudio2 python3-pip
    pip3 install sounddevice numpy wave
  7. Verify the device: Run python3 -c 'import sounddevice as sd; print(sd.query_devices())'. You should see a device named i2s-mmap or snd_rpi_hifiberry_dac in the list. Note its index number.

The Python Recording Script

This script targets the Raspberry Pi 4 Model B running Bookworm. It uses sounddevice to capture audio into a NumPy array, extracts the mono channel, and writes it to a 16-bit WAV file. It includes explicit error handling for ALSA buffer underruns and device selection failures.

import sounddevice as sd
import numpy as np
import wave
import sys
import time
from datetime import datetime

# --- Hardware & Pin Definitions ---
# Target: Raspberry Pi 4 Model B
# Mic: Adafruit I2S MEMS (SPH0645LM4H)
# Pins: BCLK=GPIO18, DOUT=GPIO20, LRCL=GPIO21

SAMPLE_RATE = 44100  # Hz
CHANNELS = 2         # I2S outputs stereo, mic is on Left channel
DURATION = 10        # Recording length in seconds
BLOCKSIZE = 4096     # Larger blocksize prevents ALSA underruns on Pi
OUTPUT_FILE = f'recording_{datetime.now().strftime("%Y%m%d_%H%M%S")}.wav'

def find_i2s_device():
    """Finds the I2S microphone index by name."""
    devices = sd.query_devices()
    for i, dev in enumerate(devices):
        # The i2s-mmap overlay usually registers as 'i2s' or 'hifiberry'
        if 'i2s' in dev['name'].lower() or 'hifiberry' in dev['name'].lower():
            if dev['max_input_channels'] > 0:
                return i
    return None

def record_audio():
    print(f'Starting {DURATION}s recording at {SAMPLE_RATE}Hz...')
    
    device_idx = find_i2s_device()
    if device_idx is None:
        print('ERROR: I2S microphone not found. Check dtoverlay in config.txt.')
        sys.exit(1)
    
    print(f'Using Device {device_idx}: {sd.query_devices(device_idx)["name"]}')
    
    try:
        # Record into a NumPy array
        # latency='high' and explicit blocksize prevent ALSA xrun errors
        audio_data = sd.rec(
            frames=int(DURATION * SAMPLE_RATE),
            samplerate=SAMPLE_RATE,
            channels=CHANNELS,
            dtype='int16',
            device=device_idx,
            blocking=True,
            latency='high'
        )
        
        # SPH0645 outputs on Left channel (index 0), Right is silent
        mono_data = audio_data[:, 0].reshape(-1, 1)
        
        # Write to WAV file
        with wave.open(OUTPUT_FILE, 'wb') as wf:
            wf.setnchannels(1)
            wf.setsampwidth(2) # 16-bit = 2 bytes
            wf.setframerate(SAMPLE_RATE)
            wf.writeframes(mono_data.tobytes())
            
        print(f'Successfully saved to {OUTPUT_FILE}')
        
    except sd.PortAudioError as e:
        print(f'PortAudio Error: {e}')
        print('Fix: Ensure no other process (like PulseAudio) is holding the ALSA device.')
    except Exception as e:
        print(f'Unexpected Error: {e}')
    finally:
        sd.stop()

if __name__ == '__main__':
    record_audio()

Debugging I2S Audio Failures

When your Raspberry Pi sound recorder fails to capture audio, the issue is almost always at the ALSA (Advanced Linux Sound Architecture) layer or the physical clock wiring. Before tearing apart your breadboard, check these first three things:

  1. Verify the Overlay Loaded: Run dtoverlay -l. If i2s-mmap is not listed, your /boot/firmware/config.txt syntax is wrong or you edited the legacy /boot/config.txt path.
  2. Check BCLK and LRCL Swap: The most common wiring error is swapping Bit Clock (GPIO 18) and Left/Right Clock (GPIO 21). If the recording is pure static or plays back at 10x speed, these two pins are reversed.
  3. Kill PulseAudio/PipeWire: Modern Raspberry Pi OS runs PipeWire or PulseAudio, which can lock the I2S device. Run systemctl --user stop pipewire pulseaudio before running the script.

Common Error Strings and Fixes

Error 1: ALSA lib pcm.c:8568:(snd_pcm_recover) underrun occurred

  • Cause: The CPU failed to read the I2S buffer fast enough, causing an ALSA xrun (buffer underrun). This is common on Pi 4 when background tasks spike.
  • Fix: Increase the BLOCKSIZE in the Python script from 1024 to 4096 or 8192. Alternatively, add latency='high' to the sd.rec() parameters as shown in the code above.

Error 2: Expression 'parameters->channelCount <= maxChannels' failed

  • Cause: The script requested 2 channels (stereo), but the ALSA device registered as mono-only due to a misconfigured asound.conf or incorrect overlay.
  • Fix: Change CHANNELS = 2 to CHANNELS = 1 in the script, or ensure you are using the i2s-mmap overlay which correctly exposes the stereo I2S bus to ALSA.

Error 3: sounddevice.PortAudioError: Error opening InputStream: Invalid device [PaErrorCode -9996]

  • Cause: The device index or name string passed to PortAudio does not match the loaded ALSA cards.
  • Fix: Run aplay -l to list hardware devices. Match the card number and subdevice to your Python script's device selection logic.

Extending and Simplifying the Build

Not every project requires bare-metal I2S configuration. Here is how to adjust the complexity based on your actual use case.

How to Simplify (The USB Route)

If you are building a simple voice memo recorder or a bird-feeder audio logger and do not want to deal with ALSA overlays, swap the MEMS breakout for a USB Mini Microphone (e.g., the generic CM108-based USB mics or a Samson Meteor).

  • Trade-off: You gain plug-and-play simplicity (it just shows up as a standard USB audio class device), but you lose SNR performance and introduce potential USB bus noise.
  • Code change: Simply change the device selection in the Python script to target the USB audio card name. No config.txt edits required.

How to Extend (Push-to-Talk and MQTT)

To turn this into an IoT audio node:

  • Add a Push-to-Talk Button: Wire a momentary tactile switch between GPIO 17 and GND. Enable the internal pull-up resistor in Python using the gpiozero library (button = Button(17, pull_up=True)), and wrap the sd.rec() call in a button.wait_for_press() loop.
  • Stream via MQTT: Instead of writing to a WAV file, use the sounddevice.InputStream callback to capture chunks of audio, encode them to Opus format using the opuslib Python wrapper, and publish the binary payload to an MQTT broker (like Mosquitto) for real-time monitoring on another machine.

Safety & Code Note: When wiring GPIO pins on the Pi 4, always de-energize the board (unplug the USB-C power supply) before moving jumper wires. A slipped 24 AWG wire bridging 3.3V and an I2S data line can instantly destroy the Pi's BCM2711 SoC. For further reading on Pi hardware interfaces, consult the official Raspberry Pi configuration documentation and the Adafruit I2S MEMS wiring guide. For deep ALSA debugging, the ALSA Project Wiki remains the definitive reference.