Getting reliable audio out of a Raspberry Pi 5 is a rite of passage for embedded makers. Unlike the Pi 4, the Pi 5 completely dropped the analog PWM headphone jack, routing all audio through the RP1 southbridge chip. This means your options are strictly digital: HDMI, USB, or I2S. If you are building a kiosk, an interactive art piece, or a retro console where USB dongles are too bulky and HDMI audio routing is a nightmare, I2S is the only professional choice.
This guide cuts through the outdated Bullseye-era tutorials and gives you the exact wiring, PipeWire configuration, and Python code to get I2S audio working on the Raspberry Pi 5 running the latest 64-bit OS.
The Decision Matrix: Which Audio Output for Your Pi?
Before you solder a single header, you need to pick the right audio architecture. Here is the decision path based on real-world project constraints:
| Project Constraint | Audio Method | Latency / Quality | Verdict |
|---|---|---|---|
| Need zero-latency GPIO sync & custom enclosure | I2S DAC (MAX98357A) | Low latency / 16-bit 44.1kHz | Default Pick for Embedded |
| Need 24-bit/192kHz audiophile line-out | HiFiBerry DAC+ Pro HAT | Ultra-low jitter / High-res | Choose for high-end audio projects |
| Just need basic system beeps, no GPIO wiring | USB Audio Adapter (e.g., Sabrent) | High latency / 16-bit | Choose for simple desktop/kiosk use |
| Video and audio over a single cable | HDMI Audio | Variable / Depends on display | Choose only if display has built-in speakers |
Parts List & Pin Mapping for Pi 5 I2S Audio
The Raspberry Pi 5 uses the RP1 chip to handle peripherals. The I2S (Inter-IC Sound) pins are fixed to specific BCM GPIO numbers. Do not attempt to move these to other pins; the RP1 hardware PWM/I2S blocks are hardwired to these traces.
Bill of Materials
- Compute: Raspberry Pi 5 (4GB or 8GB variant) — ~$60-$80
- DAC/Amp: Adafruit MAX98357A I2S Amplifier Breakout (Product ID: 3006) — ~$7.50
- Transducer: 3W 4-Ohm Speaker with JST-PH 2.0 connector — ~$5.00
- Wiring: Silicone Female-to-Female jumper wires (26 AWG) — ~$4.00
- Power: Raspberry Pi 27W USB-C PD Power Supply — ~$12.00
- Indicator: Standard 5mm LED with 330Ω resistor (for GPIO sync testing) — ~$0.10
Hardware Pin Mapping Table
| Pi 5 BCM GPIO | Physical Pin # | MAX98357A Pad | Function / Signal |
|---|---|---|---|
| BCM 18 (PCM_CLK) | 12 | BCLK | Bit Clock (I2S timing) |
| BCM 19 (PCM_FS) | 35 | LRC | Left/Right Clock (Word Select) |
| BCM 21 (PCM_DOUT) | 40 | DIN | Serial Audio Data Out |
| 5V Power | 2 or 4 | VIN | Power In (Do NOT use 3.3V) |
| GND | 6 | GND | Common Ground |
| BCM 17 (GPIO) | 11 | N/A (LED Anode) | Status LED Sync (via 330Ω resistor) |
Wiring and Software Configuration (Step-by-Step)
- De-energize and Wire: Unplug the Pi 5. Connect the 5 I2S/power wires according to the table above. Connect the speaker to the JST port on the MAX98357A. Connect the LED anode to BCM 17 via the 330Ω resistor, and cathode to GND.
- Enable the I2S Overlay: Boot the Pi. In Raspberry Pi OS Bookworm (and newer), the boot partition is mounted at
/boot/firmware/. Open the config file:sudo nano /boot/firmware/config.txt
Add these lines at the bottom to load the MAX98357A device tree overlay and disable the default HDMI audio routing:dtparam=i2s=ondtoverlay=max98357adtparam=audio=off - Reboot and Verify Hardware: Run
sudo reboot. Once back in, verify the kernel loaded the codec:dmesg | grep max98357a
You should see the ASOC (ALSA System on Chip) driver binding to the I2C/I2S bus. - Install Python Audio Dependencies: Modern Pi OS uses PipeWire instead of PulseAudio. The Python
sounddevicelibrary relies on PortAudio. Install the system bindings:sudo apt updatesudo apt install portaudio19-dev python3-pip python3-gpiozeropip3 install sounddevice numpy --break-system-packages - Set Default PipeWire Sink: List your audio sinks using
pw-cli ls | grep node.name. Find the string for the MAX98357A (usuallyalsa_output.platform-sound.card0or similar). Set it as default in~/.config/pipewire/pipewire.confor via the desktop GUI to ensure system-wide routing.
Python Playback Code with Error Handling
This script targets the Raspberry Pi 5 (64-bit OS). It generates a 440Hz sine wave using NumPy, streams it to the I2S DAC via PortAudio, and toggles a GPIO LED in perfect sync to prove the hardware timing. It includes robust error handling for ALSA device mapping failures.
import sounddevice as sd
import numpy as np
from gpiozero import LED
import time
import sys
# ==========================================
# PIN & HARDWARE DEFINITIONS
# ==========================================
LED_SYNC_PIN = 17 # BCM 17 (Physical Pin 11)
I2S_SAMPLE_RATE = 44100 # MAX98357A native optimal rate
FREQUENCY_HZ = 440.0 # A4 Note
DURATION_SEC = 3.0 # Playback length
AMPLITUDE = 0.5 # 50% volume to prevent speaker clipping
# Initialize GPIO for embedded sync indicator
status_led = LED(LED_SYNC_PIN)
def generate_sine_wave(freq, sr, duration, amp):
"""Generates a mono 16-bit PCM sine wave array."""
t = np.linspace(0, duration, int(sr * duration), False)
wave = np.sin(2 * np.pi * freq * t) * amp
return wave.astype(np.float32)
def play_audio_with_sync():
wave_data = generate_sine_wave(FREQUENCY_HZ, I2S_SAMPLE_RATE, DURATION_SEC, AMPLITUDE)
# Turn on LED exactly when audio buffer starts streaming
status_led.on()
print(f"[INFO] Streaming {DURATION_SEC}s of {FREQUENCY_HZ}Hz to I2S DAC...")
try:
# Block=False allows us to manage the LED timing, but we must wait for completion
sd.play(wave_data, samplerate=I2S_SAMPLE_RATE, device='default')
sd.wait() # Blocks until audio buffer is empty
except sd.PortAudioError as e:
status_led.off()
print(f"[FATAL] PortAudio Error: {e}")
print("[FIX] Check 'aplay -l' to ensure MAX98357A is recognized.")
sys.exit(1)
except Exception as e:
status_led.off()
print(f"[FATAL] Unexpected playback error: {e}")
sys.exit(1)
finally:
# Turn off LED exactly when audio finishes
status_led.off()
print("[INFO] Playback complete. LED synced.")
if __name__ == "__main__":
print("[BOOT] Initializing Pi 5 I2S Audio & GPIO Sync...")
time.sleep(0.5) # Brief debounce for power rail stabilization
play_audio_with_sync()
Debugging: Exact Error Strings and First Three Checks
Audio on Linux is a layered stack: Hardware → Device Tree → ALSA → PipeWire → PortAudio → Python. When it fails, do not guess. Follow this exact diagnostic path.
The First Three Things to Check
- Is the hardware mapped? Run
aplay -l. You must seecard X: sndrpihifiberry [snd_rpi_hifiberry_dac]ormax98357a. If it only showsvc4hdmi, yourconfig.txtoverlay failed to load. - Did the Device Tree compile? Run
vcdbg log msg 2>&1 | grep dtdebugor checkdmesg | grep i2s. If you see 'pinmux conflict', another HAT or overlay is stealing BCM 18/19/21. - Is PipeWire hoarding the sink? Run
pw-cli info all | grep state. If the node is 'suspended' or 'running' but silent, PipeWire's echo-cancel module might be hijacking the stream.
Ranked Causes for Common Exact Error Strings
| Exact Error String | Ranked Causes (Most Likely First) | The Fix |
|---|---|---|
ALSA lib pcm_dmix.c:1032:(snd_pcm_dmix_open) unable to open slave |
1. PipeWire is holding exclusive lock. 2. Wrong hw:X,Y mapping in .asoundrc. |
Stop PipeWire temporarily (systemctl --user stop pipewire) to test raw ALSA, or use device='default' in Python to let PipeWire route it. |
sounddevice.PortAudioError: Error opening OutputStream: Invalid device [PaErrorCode -9996] |
1. Python script hardcoded an ALSA string (e.g., hw:1,0) that shifted on reboot.2. PortAudio cache is stale. |
Change the device parameter in sd.play() to 'default'. Never hardcode hw:X,Y in embedded scripts; USB devices and I2S hats swap card numbers randomly. |
gpiozero.exc.GPIOPinInUse: pin 17 is already in use |
1. A previous Python script crashed and didn't release the pin. 2. Another overlay is using BCM 17. |
Run sudo killall python3 to clear hung processes. Ensure no SPI/UART overlays are claiming BCM 17 in config.txt. |
Extending and Simplifying the Build
Once you have the baseline I2S audio working, you will inevitably need to adapt it for production or scale it down for a quick prototype.
How to Simplify (The 'I Just Need It Working' Path)
If the Device Tree overlays and PipeWire sinks are eating up your development time, abandon I2S and buy a $12 USB Audio Dongle (like the StarTech ICUSBAUDIO). Plug it in, delete the dtoverlay lines from config.txt, and the Pi OS desktop will automatically route audio to it. You lose GPIO sync and some latency, but you save 3 hours of Linux audio debugging. Use this for digital signage where audio timing doesn't matter.
How to Extend (The Production Path)
- Add a DSP: If you need EQ, crossover, or volume limiting to protect your speaker, insert an ADAU1701 SigmaDSP between the Pi's I2S out and the MAX98357A's I2S in. You program the DSP via Analog Devices' SIGMA Studio on a PC, and it processes the audio in hardware before amplification.
- Stereo Output: The MAX98357A is mono. For stereo, use the HiFiBerry DAC+ (Product ID: 2368). It uses the exact same I2S pins (BCM 18, 19, 21) but includes a Burr-Brown PCM5102A DAC chip and dual RCA line-outs. Note that it outputs line-level audio, so you will need to add an external Class-D amp board like the TPA3116 if you are driving raw speakers.
- Hardware Mute: The MAX98357A breakout has a
GAINpad and anSD(Shutdown) pad. Wire theSDpad to a spare GPIO (e.g., BCM 27). Pulling it LOW physically mutes the amp at the silicon level, eliminating the 'pop' sound that occurs when the Pi boots and the I2S bus initializes.
For deeper reading on Pi 5 peripheral routing, consult the official Raspberry Pi config.txt documentation. For the specific amplifier specs and gain pad configurations, reference the Adafruit MAX98357A learning guide.






