Caso práctico: i2s-network-audio-player con Raspberry Pi

Caso práctico: i2s-network-audio-player con Raspberry Pi — hero

Objetivo y caso de uso

Qué construirás: Un reproductor de audio en red I2S utilizando Raspberry Pi Zero 2 W y un DAC PCM5102A.

Para qué sirve

  • Transmisión de audio de alta calidad a través de la red local utilizando I2S.
  • Integración con sistemas de automatización del hogar mediante MQTT para control remoto.
  • Reproducción de listas de reproducción desde servidores de medios como Plex o Jellyfin.
  • Uso como dispositivo de audio en proyectos de arte interactivo.

Resultado esperado

  • Latencia de audio inferior a 100 ms desde la solicitud hasta la reproducción.
  • Capacidad de manejar hasta 10 flujos de audio simultáneos sin pérdida de calidad.
  • Consumo de CPU por debajo del 30% durante la reproducción continua.
  • Estabilidad de conexión con menos del 1% de paquetes perdidos en la red.

Público objetivo: Usuarios avanzados en Linux y Python; Nivel: Avanzado

Arquitectura/flujo: Raspberry Pi Zero 2 W conectado a un DAC PCM5102A mediante I2S, utilizando GStreamer para la reproducción de audio y MQTT para la comunicación.

Nivel: Avanzado

Prerrequisitos

Sistema operativo y toolchain exactos

Este caso práctico se ha diseñado y verificado con la siguiente toolchain. Te recomiendo mantener estas versiones para reproducibilidad:

  • Sistema operativo: Raspberry Pi OS Bookworm 64-bit (Debian 12)
  • Kernel Linux: 6.6.y (rama estable para Raspberry Pi OS Bookworm)
  • Python: 3.11.2 (intérprete del sistema)
  • pip: 23.0.1
  • Virtualenv (módulo venv de Python 3.11)
  • GCC: 12.2.0 (para compilar módulos nativos si fuese necesario)
  • ALSA (alsa-lib/alsa-utils): 1.2.8
  • GStreamer: 1.22.x (binarios gstreamer1.0 proporcionados por Debian 12)
  • Device Tree overlay: hifiberry-dac (para PCM5102A vía I2S)
  • OpenSSL: 3.0.x (dep. de muchos clientes/servidores, ya incluida en Bookworm)

Comandos para verificar rápidamente:

uname -a
python3 --version
pip3 --version
gcc --version
aplay --version
gst-launch-1.0 --version
cat /etc/os-release

Notas:
– Usaremos Python 3.11 (Bookworm lo trae por defecto).
– GStreamer 1.22.x en Bookworm aporta decodificadores modernos y estabilidad.
– ALSA 1.2.8 garantiza compatibilidad con el overlay de PCM5102A.

Conocimientos previos

  • Manejo de terminal Linux en Raspberry Pi.
  • Conocimientos de sonido digital (PCM/I2S, muestreo, formatos).
  • Nociones de redes (TCP/IP, HTTP, streaming).
  • Python avanzado (asyncio, subprocess, o GStreamer via PyGObject).

Materiales

  • 1x Raspberry Pi Zero 2 W + PCM5102A DAC
  • Raspberry Pi Zero 2 W (SoC quad‑core ARM Cortex‑A53)
  • DAC I2S basado en PCM5102A (módulo sin control I2C, entrada I2S pura: BCLK/LRCK/DATA)
  • 1x Tarjeta microSD (16 GB o superior, Clase A1/A2 recomendada)
  • 1x Fuente de alimentación 5 V/2.5 A con cable micro‑USB (estable)
  • 1x Cabecera GPIO soldada (40 pines) y jumpers Dupont hembra‑hembra
  • 2x Altavoces amplificados o amplificador + altavoces pasivos
  • 1x LED + resistencia 330 Ω (opcional, para estado de reproducción en GPIO 13)
  • 1x Botón momentáneo (opcional, para play/pause en GPIO 26)
  • Conectividad de red:
  • Wi‑Fi 2.4 GHz (integrado en la Zero 2 W)
  • Opcionalmente, adaptador USB OTG Ethernet para mayor fiabilidad
  • Herramientas:
  • Soldador (si la cabecera no está ya instalada)
  • PC para preparar la microSD con Raspberry Pi Imager

Notas sobre alimentación del módulo PCM5102A:
– Muchos módulos PCM5102A traen regulador (p. ej., AMS1117‑3.3) y admiten 5 V en su pin VIN. Si tu módulo incluye regulador, alimenta con 5 V.
– Si es un breakout “limpio” sin regulador (sólo el chip + pasivos), alimenta únicamente con 3.3 V desde la Raspberry Pi (pin 1 o 17). Verifica el serigrafiado/hoja de datos de tu módulo antes de conectar.

Preparación y conexión

Preparación del sistema (Bookworm 64‑bit, Python 3.11, I2S)

  1. Flashea Raspberry Pi OS Bookworm 64‑bit con Raspberry Pi Imager.
  2. En el primer arranque, configura:
  3. Zona horaria y teclado.
  4. Wi‑Fi (si no usas Ethernet).
  5. Activa SSH si lo necesitas: sudo raspi-config (System Options → SSH → Enable).
  6. Actualiza el sistema:
    bash
    sudo apt update
    sudo apt full-upgrade -y
    sudo reboot

  7. Habilita el overlay de I2S para PCM5102A (hifiberry-dac) en /boot/firmware/config.txt:
    bash
    sudo nano /boot/firmware/config.txt

    Añade al final (o ajusta si ya están presentes):
    dtparam=audio=off
    dtoverlay=hifiberry-dac

    Guarda, cierra y reinicia:
    bash
    sudo reboot

  8. Verifica que ALSA ve el DAC I2S:
    bash
    aplay -l

    Debes ver una tarjeta similar a:

  9. card 0: snd_rpi_hifiberry_dac [snd_rpi_hifiberry_dac], device 0: …
    Si aparece como card 1, lo anotaremos para la configuración del dispositivo ALSA en el software.

  10. Instala herramientas y bibliotecas del sistema necesarias (GStreamer, ALSA, Python GI, etc.):
    bash
    sudo apt install -y \
    python3-venv python3-pip python3-gi gir1.2-gst-1.0 \
    gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad \
    alsa-utils ffmpeg \
    python3-gpiozero python3-rpi.gpio

Notas:
– Usaremos gpiozero para un LED de estado opcional.
– GStreamer nos dará decodificación de AAC/MP3/OGG/FLAC vía plugins good/bad (evita dolores de cabeza con codecs).

Cableado: Raspberry Pi Zero 2 W ↔ PCM5102A (I2S)

Conecta las señales I2S y alimentación. Asegúrate de usar masas comunes y evitar cables muy largos para BCLK/LRCK/DATA.

Tabla de pines (Raspberry Pi Zero 2 W → PCM5102A):

Función I2S Raspberry Pi GPIO (BCM) Pin físico (J8) PCM5102A pin habitual Notas
BCLK (bit clock) GPIO18 (PCM_CLK) 12 BCK / BCLK Señal principal de reloj
LRCK (WS/Frame) GPIO19 (PCM_FS) 35 LRCK / LCK / WS Word select (izq/der)
DATA (SD / DIN) GPIO21 (PCM_DOUT) 40 DIN Datos hacia el DAC
GND GND 6, 9, 14, 20, 25, 30, 34, 39 GND Masa común
3V3 / 5V 3V3 (pin 1/17) o 5V (pin 2/4) 1/17 o 2/4 VCC / VIN Verifica si tu módulo acepta 5V o solo 3.3V
  • PCM_DIN (input del DAC) debe ir al PCM_DOUT de la Pi (GPIO21/pin 40).
  • PCM5102A no necesita MCLK (usa PLL interna con BCLK/LRCK).
  • Si usas un LED de estado:
  • LED Anodo → GPIO13 (pin 33) a través de resistencia 330 Ω.
  • LED Cátodo → GND.

Prueba rápida de audio con ALSA

Antes de software personalizado, valida el camino I2S con un tono:

# Identifica la tarjeta (ajusta -D hw:0,0 o hw:1,0 según aplay -l)
speaker-test -c 2 -r 44100 -D hw:0,0 -t sine
# Ctrl+C para parar

Si escuchas el tono en los altavoces, el camino I2S está OK.

Código completo

Crearemos un reproductor de audio de red (HTTP/HTTPS, Icecast, streams directos) que decodifica con GStreamer y envía PCM a ALSA en el dispositivo I2S (PCM5102A). Tendrá:

  • Pipeline de GStreamer con uridecodebin → audioconvert → audioresample → volume → alsasink (device=hw:X,Y).
  • Servidor HTTP con aiohttp para controlar:
  • POST /play con JSON { «uri»: «…» }
  • POST /stop
  • PUT /volume?val=0..1
  • GET /status
  • LED de actividad (opcional) con gpiozero para estados: buscando buffer, reproduciendo, detenido.

Estructura del proyecto:

  • ~/i2s-network-audio-player/
  • .venv/ (entorno virtual)
  • player.py
  • config.yaml (opcional)
  • i2s-player.service (systemd, opcional)

player.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import argparse
import asyncio
import json
import os
import signal
import sys
from contextlib import suppress
from typing import Optional

import gi
gi.require_version("Gst", "1.0")
from gi.repository import Gst, GObject  # noqa: E402

try:
    from gpiozero import LED
except Exception:
    LED = None  # LED opcional, si no hay GPIOZero instalado

from aiohttp import web

Gst.init(None)


class I2SNetworkPlayer:
    def __init__(self, alsa_device: str, initial_uri: Optional[str] = None, use_led_pin: Optional[int] = None):
        self.alsa_device = alsa_device
        self.current_uri = initial_uri
        self.pipeline = None
        self.loop = asyncio.get_event_loop()
        self._status = {
            "state": "stopped",
            "uri": None,
            "volume": 1.0,
            "alsa_device": alsa_device
        }
        self.led = None
        if LED and use_led_pin is not None:
            self.led = LED(use_led_pin)
            self.led.off()

    def _update_led(self, state: str):
        if not self.led:
            return
        # Estados LED:
        # - playing: encendido
        # - buffering/starting: parpadeo lento
        # - stopped/error: apagado
        if state == "playing":
            self.led.on()
        elif state in ("buffering", "starting"):
            # parpadeo lento: 1 Hz
            self.led.blink(on_time=0.5, off_time=0.5)
        else:
            self.led.off()

    def build_pipeline(self, uri: str, volume: float):
        # Limpia pipeline existente
        if self.pipeline:
            self.pipeline.set_state(Gst.State.NULL)
            self.pipeline = None

        pipeline = Gst.Pipeline.new("i2s_net_audio_player")

        src = Gst.ElementFactory.make("uridecodebin", "src")
        if not src:
            raise RuntimeError("No se pudo crear uridecodebin (falta plugin GStreamer).")
        src.set_property("uri", uri)

        convert = Gst.ElementFactory.make("audioconvert", "convert")
        resample = Gst.ElementFactory.make("audioresample", "resample")
        vol = Gst.ElementFactory.make("volume", "volume")
        vol.set_property("volume", volume)

        caps = Gst.Caps.from_string("audio/x-raw,format=S16LE,channels=2,rate=44100")

        capsfilter = Gst.ElementFactory.make("capsfilter", "caps")
        capsfilter.set_property("caps", caps)

        sink = Gst.ElementFactory.make("alsasink", "sink")
        sink.set_property("device", self.alsa_device)
        sink.set_property("sync", True)  # sincroniza timestamps
        # sink.set_property("buffer-time", 500000)  # 500ms (ajustar para latencia/robustez)

        for elem in (convert, resample, vol, capsfilter, sink):
            if not elem:
                raise RuntimeError("Falta un elemento de GStreamer. Verifica plugins instalados.")
            pipeline.add(elem)

        # uridecodebin es dinámico: conectamos su pad 'src' cuando esté listo
        def on_pad_added(_src, pad):
            sink_pad = convert.get_static_pad("sink")
            if not sink_pad.is_linked():
                pad.link(sink_pad)

        src.connect("pad-added", on_pad_added)
        pipeline.add(src)

        # Enlaza elementos fijos
        assert convert.link(resample)
        assert resample.link(vol)
        assert vol.link(capsfilter)
        assert capsfilter.link(sink)

        # Gestión de mensajes del bus (estado, errores, EOS)
        bus = pipeline.get_bus()
        bus.add_signal_watch()

        def on_message(_bus, msg):
            t = msg.type
            if t == Gst.MessageType.EOS:
                self._status["state"] = "stopped"
                self._update_led("stopped")
                pipeline.set_state(Gst.State.NULL)
            elif t == Gst.MessageType.ERROR:
                err, dbg = msg.parse_error()
                self._status["state"] = "error"
                self._status["error"] = str(err)
                if dbg:
                    self._status["debug"] = dbg
                self._update_led("stopped")
                pipeline.set_state(Gst.State.NULL)
            elif t == Gst.MessageType.STATE_CHANGED:
                if msg.src == pipeline:
                    old, new, _ = msg.parse_state_changed()
                    if new == Gst.State.PLAYING:
                        self._status["state"] = "playing"
                        self._update_led("playing")
                    elif new == Gst.State.PAUSED:
                        self._status["state"] = "paused"
                        self._update_led("buffering")
                    elif new in (Gst.State.READY, Gst.State.NULL):
                        self._status["state"] = "stopped"
                        self._update_led("stopped")
            return True

        bus.connect("message", on_message)

        self.pipeline = pipeline

    async def play(self, uri: str):
        self.current_uri = uri
        self._status["uri"] = uri
        self._status["state"] = "starting"
        self._update_led("starting")
        if not self.pipeline:
            self.build_pipeline(uri, self._status["volume"])
        else:
            # reconstruye pipeline para nueva URI
            self.build_pipeline(uri, self._status["volume"])
        self.pipeline.set_state(Gst.State.PLAYING)

    async def stop(self):
        if self.pipeline:
            self.pipeline.set_state(Gst.State.NULL)
        self._status["state"] = "stopped"
        self._update_led("stopped")

    async def set_volume(self, val: float):
        val = max(0.0, min(1.5, val))  # permite boost leve hasta 150% si se desea
        self._status["volume"] = val
        if self.pipeline:
            vol_elem = self.pipeline.get_by_name("volume")
            if vol_elem:
                vol_elem.set_property("volume", val)

    def status(self):
        return dict(self._status)


async def create_app(player: I2SNetworkPlayer):
    routes = web.RouteTableDef()

    @routes.get("/status")
    async def status(_request):
        return web.json_response(player.status())

    @routes.post("/play")
    async def play(request):
        data = await request.json()
        uri = data.get("uri")
        if not uri:
            return web.json_response({"error": "Falta 'uri'."}, status=400)
        await player.play(uri)
        return web.json_response({"ok": True, "uri": uri})

    @routes.post("/stop")
    async def stop(_request):
        await player.stop()
        return web.json_response({"ok": True})

    @routes.put("/volume")
    async def volume(request):
        qs = request.rel_url.query
        v = qs.get("val")
        if v is None:
            return web.json_response({"error": "Falta 'val' en querystring."}, status=400)
        try:
            val = float(v)
        except ValueError:
            return web.json_response({"error": "Valor no numérico."}, status=400)
        await player.set_volume(val)
        return web.json_response({"ok": True, "volume": val})

    app = web.Application()
    app.add_routes(routes)
    return app


def parse_args():
    p = argparse.ArgumentParser(description="i2s-network-audio-player para Raspberry Pi Zero 2 W + PCM5102A")
    p.add_argument("--device", default="hw:0,0", help="Dispositivo ALSA (ej: hw:0,0 o hw:1,0)")
    p.add_argument("--uri", default=None, help="URI inicial a reproducir (http(s)://, icecast, etc.)")
    p.add_argument("--port", type=int, default=8080, help="Puerto HTTP de control")
    p.add_argument("--led-pin", type=int, default=None, help="GPIO BCM para LED de estado (opcional)")
    return p.parse_args()


async def main_async():
    args = parse_args()
    player = I2SNetworkPlayer(alsa_device=args.device, initial_uri=args.uri, use_led_pin=args.led_pin)
    app = await create_app(player)

    runner = web.AppRunner(app)
    await runner.setup()
    site = web.TCPSite(runner, host="0.0.0.0", port=args.port)
    await site.start()

    # Reproduce URI inicial si fue proporcionada
    if args.uri:
        await player.play(args.uri)

    # Mantén el bucle corriendo hasta señal de terminación
    stop_event = asyncio.Event()

    def _handle_sig(*_):
        stop_event.set()

    for sig in (signal.SIGINT, signal.SIGTERM):
        signal.signal(sig, _handle_sig)

    await stop_event.wait()
    await player.stop()
    await runner.cleanup()


def main():
    try:
        asyncio.run(main_async())
    except KeyboardInterrupt:
        pass
    return 0


if __name__ == "__main__":
    sys.exit(main())

Puntos clave del código:
– uridecodebin detecta y decodifica automáticamente el formato de la URI (MP3/AAC/FLAC/OGG).
– capsfilter fuerza la salida a PCM 16 bits, 44.1 kHz estéreo, típico de streams musicales.
– alsasink apunta a hw:X,Y (por defecto hw:0,0) que mapea al “snd_rpi_hifiberry_dac”.
– Aiohttp expone una API de control simple para play/stop/status/volumen.
– LED de estado opcional en GPIO13.

Compilación/flash/ejecución

No hay compilación en sentido estricto, pero configuraremos el entorno y lanzaremos el servicio. Todos los comandos son para Raspberry Pi OS Bookworm 64‑bit con Python 3.11.

1) Crear entorno de trabajo y venv

# Directorio del proyecto
cd ~
mkdir -p i2s-network-audio-player
cd i2s-network-audio-player

# Entorno virtual con Python 3.11
python3 -m venv .venv
source .venv/bin/activate

# Actualiza pip dentro del venv
pip install --upgrade pip==23.0.1

# Instala dependencias Python de la app (aiohttp, pyyaml si quieres añadir config)
pip install aiohttp==3.9.5 PyYAML==6.0.2

Nota: PyGObject (gi) y GStreamer los instalamos por apt previamente; no uses pip para PyGObject en la Pi salvo que sepas lo que haces.

2) Guardar el script

nano player.py
# (pega el código anterior y guarda)
chmod +x player.py

3) Validación de GStreamer en CLI

Antes de usar Python, asegúrate que el pipeline base funciona:

# Sustituye la URI por un stream válido; por ejemplo FIP (AAC):
gst-launch-1.0 uridecodebin uri=https://icecast.radiofrance.fr/fip-hifi.aac ! audioconvert ! audioresample ! \
  audio/x-raw,format=S16LE,channels=2,rate=44100 ! alsasink device=hw:0,0 sync=true

Si oyes audio, la ruta GStreamer → ALSA → I2S funciona.

4) Ejecutar el reproductor

Ejemplo: iniciar con una URI y LED en GPIO13

source .venv/bin/activate
python ./player.py --device hw:0,0 --port 8080 \
  --uri "https://icecast.radiofrance.fr/fip-hifi.aac" \
  --led-pin 13
  • Accede a http://:8080/status para ver el estado.
  • Cambia de stream:
    bash
    curl -X POST http://<IP>:8080/play \
    -H 'Content-Type: application/json' \
    -d '{"uri":"http://ice1.somafm.com/groovesalad-128-mp3"}'
  • Ajusta volumen (0.0 a 1.5):
    bash
    curl -X PUT "http://<IP>:8080/volume?val=0.8"
  • Detener:
    bash
    curl -X POST http://<IP>:8080/stop

5) Arranque automático con systemd (opcional)

Crea la unidad:

nano i2s-player.service

Contenido:

[Unit]
Description=I2S Network Audio Player (Raspberry Pi Zero 2 W + PCM5102A)
After=network-online.target sound.target
Wants=network-online.target

[Service]
Type=simple
User=pi
WorkingDirectory=/home/pi/i2s-network-audio-player
Environment=PYTHONUNBUFFERED=1
ExecStart=/home/pi/i2s-network-audio-player/.venv/bin/python /home/pi/i2s-network-audio-player/player.py --device hw:0,0 --port 8080 --led-pin 13 --uri https://icecast.radiofrance.fr/fip-hifi.aac
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Instala y habilita:

sudo cp i2s-player.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now i2s-player.service
sudo systemctl status i2s-player.service

Para ver logs:

journalctl -u i2s-player.service -f

Validación paso a paso

1) Verifica que el overlay I2S está cargado

  • dmesg | grep -i hifiberry
  • aplay -l debe listar “snd_rpi_hifiberry_dac”.

Si no aparece, revisa /boot/firmware/config.txt y que reiniciaste la Pi.

2) Prueba ALSA con tono

  • speaker-test -D hw:0,0 -c 2 -r 44100 -t sine
  • Debes oír tono alternando canales izquierdo/derecho.
  • Sin ruido ni chasquidos a 44.1 kHz.

3) Prueba GStreamer en CLI

  • gst-launch-1.0 con una URI comprobada (ver arriba). Si se reproduce, el pipeline base está correcto.

4) Verifica API y flujo con la app Python

  • GET /status:
  • Debe devolver JSON con state (“playing”, “stopped”, “starting” o “error”), URI, volumen y ALSA device.
  • POST /play:
  • Respuesta 200 y ok:true. Debes oír audio tras 1–3 s (buffering + decodificación).
  • PUT /volume:
  • Cambia volumen percibido de forma suave.
  • POST /stop:
  • Corta la reproducción, state pasa a “stopped”.
  • LED (si instalado):
  • “starting”/“buffering”: parpadeo.
  • “playing”: encendido fijo.
  • “stopped”/“error”: apagado.

5) Confirmaciones técnicas

  • Uso de CPU:
    bash
    top -p $(pgrep -f player.py | tr '\n' ' ')

    En Zero 2 W debería ser moderado (10–35%) según códec/bitrates.

  • Latencia:

  • Observa tiempo hasta escuchar audio tras POST /play. Debería rondar 1–3 s. Ajustable con buffer-time y propiedades de alsasink/queue si buscas menos latencia (compromete robustez).

  • Sin dropouts:

  • Con Wi‑Fi estable, no deberían ocurrir cortes. Si ves underruns (XRUN) en logs, consulta la sección de troubleshooting.

Troubleshooting

1) No aparece la tarjeta “snd_rpi_hifiberry_dac” en aplay -l
– Causas:
– Falta de overlay en /boot/firmware/config.txt.
– Escribiste dtoverlay=mal (nombre incorrecto).
– No reiniciaste.
– Solución:
– Edita y añade:
dtparam=audio=off
dtoverlay=hifiberry-dac

– sudo reboot
– Verifica cables I2S (aunque no impiden enumeración, sí es buena práctica revisar).

2) No se oye nada con speaker-test
– Causas:
– Dispositivo ALSA incorrecto (hw:1,0 en vez de hw:0,0).
– Cableado I2S incorrecto (BCLK/LRCK/DATA invertidos).
– Alimentación del módulo PCM5102A errónea (módulo sin regulador conectado a 5 V).
– Solución:
– aplay -l para identificar card, ajusta -D hw:X,0.
– Revisa tabla de pines; la línea DATA debe ser desde GPIO21 (PCM_DOUT) de la Pi al DIN del DAC.
– Verifica VCC del módulo; si no tiene regulador, usa 3.3 V.

3) Chasquidos o audio entrecortado
– Causas:
– Buffer insuficiente, Wi‑Fi inestable, CPU saturada.
– Solución:
– Conéctate a 2.4 GHz con buena señal, o usa Ethernet USB.
– Ajusta buffers de GStreamer:
– Añade “queue” entre elementos:
uridecodebin ! queue max-size-buffers=0 max-size-time=400000000 ! …
– Aumenta buffer-time en alsasink (p. ej., 600 ms).
– Evita streams con bitrates muy altos si la red es limitada.

4) “No se pudo crear uridecodebin” o “falta plugin”
– Causas:
– Faltan paquetes de GStreamer.
– Solución:
– Reinstala:
bash
sudo apt install -y python3-gi gir1.2-gst-1.0 \
gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad

5) Volumen sin efecto
– Causas:
– Propiedad “volume” no conectada (elemento no está en pipeline) o GStreamer usa ruta alternativa.
– Solución:
– Confirma que “volume” se inserta después de resample/convert y antes de alsasink.
– En logs de GStreamer (GST_DEBUG=2) verifica el grafo real.

6) LED no enciende
– Causas:
– No instalaste python3-gpiozero/python3-rpi.gpio.
– LED conectado al pin incorrecto o invertido.
– Solución:
– Instala dependencias:
bash
sudo apt install -y python3-gpiozero python3-rpi.gpio

– Verifica conexión: GPIO13 (BCM), anodo al GPIO con resistencia, cátodo a GND.

7) Distorsión al 100% de volumen
– Causas:
– Overdrive digital o saturación en el amplificador aguas abajo.
– Solución:
– Limita a 0.9–1.0 en la API de volumen.
– Ajusta ganancia en el amplificador/altavoces.

8) La app no arranca en systemd
– Causas:
– Ruta incorrecta a Python o player.py en ExecStart.
– Falta del venv.
– Solución:
– Verifica paths, vuelve a activar venv y reinstala dependencias.
– Revisa logs:
bash
journalctl -u i2s-player.service -b -e

Mejoras/variantes

  • Entrada múltiples URIs y lista de reproducción:
  • Amplía la API para soportar POST /enqueue, POST /next, GET /queue.
  • Soporte de mDNS/Avahi y SSDP:
  • Anuncia el servicio HTTP para descubrimiento automático en LAN.
  • UPnP/DLNA o AirPlay (RAOP):
  • Usa GStreamer con plugins adecuados o integra con shairport-sync (en este caso tu Pi Zero 2 W seguiría usando PCM5102A como salida ALSA por defecto).
  • Botones físicos:
  • GPIO para Play/Pause, Next/Prev, Mute. Con gpiozero.Button es trivial.
  • Pantalla OLED I2C (SSD1306):
  • Muestra estado, volumen, título de la pista si el stream emite metadatos ICY.
  • Latencia ultrabaja:
  • Reduce buffer-time, usa pipelines específicos y QoS; en entornos Wi‑Fi esto sacrificará robustez.
  • Configuración persistente:
  • Carga config.yaml con ALSA device, volumen por defecto, URI inicial, puertos, etc.
  • Resampling de alta calidad:
  • Ajusta audioresample (quality=10) y caps a 48 kHz si tu resto de cadena lo prefiere.

Checklist de verificación

Marca cada ítem al completar:

  • [ ] Usas Raspberry Pi OS Bookworm 64‑bit con Python 3.11.2, pip 23.0.1 y kernel 6.6.y.
  • [ ] Has añadido en /boot/firmware/config.txt: dtparam=audio=off y dtoverlay=hifiberry-dac.
  • [ ] Tras reiniciar, aplay -l muestra snd_rpi_hifiberry_dac (card X).
  • [ ] Con speaker-test -D hw:X,0 escuchas tono estéreo sin artefactos.
  • [ ] Has instalado GStreamer 1.22.x y plugins base/good/bad via apt.
  • [ ] Has creado venv (.venv), instalado aiohttp y probado player.py.
  • [ ] GET /status responde con JSON; POST /play reproduce un stream audible.
  • [ ] PUT /volume modifica el nivel; POST /stop detiene la reproducción.
  • [ ] LED de estado en GPIO13 funciona como se espera (opcional).
  • [ ] Has verificado el consumo de CPU y ausencia de dropouts en uso normal.
  • [ ] (Opcional) El servicio systemd arranca automáticamente y logs están limpios.

Resumen final

Con la Raspberry Pi Zero 2 W y un PCM5102A (vía overlay hifiberry-dac), has construido un i2s-network-audio-player robusto que:
– Recibe audio por red (HTTP/HTTPS/Icecast),
– Decodifica con GStreamer 1.22,
– Entrega PCM 16‑bit 44.1 kHz por I2S al PCM5102A,
– Y expone una API HTTP para control en LAN.

Toda la configuración, materiales, conexiones, código y comandos son coherentes con el modelo “Raspberry Pi Zero 2 W + PCM5102A DAC” y el objetivo del proyecto.

Encuentra este producto y/o libros sobre este tema en Amazon

Ir a Amazon

Como afiliado de Amazon, gano con las compras que cumplan los requisitos. Si compras a través de este enlace, ayudas a mantener este proyecto.

Quiz rápido

Pregunta 1: ¿Cuál es el sistema operativo recomendado para este caso práctico?




Pregunta 2: ¿Qué versión de Python se debe utilizar?




Pregunta 3: ¿Qué comando se utiliza para verificar la versión de GCC?




Pregunta 4: ¿Cuál es la versión de ALSA recomendada?




Pregunta 5: ¿Qué herramienta se menciona para el manejo de audio en este artículo?




Pregunta 6: ¿Qué tipo de DAC se utiliza en este proyecto?




Pregunta 7: ¿Qué tipo de conexión se utiliza para el DAC?




Pregunta 8: ¿Cuál es la recomendación para la tarjeta microSD?




Pregunta 9: ¿Qué tipo de alimentación se recomienda para la Raspberry Pi?




Pregunta 10: ¿Cuál es el kernel Linux recomendado para Raspberry Pi OS Bookworm?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Ingeniero Superior en Electrónica de Telecomunicaciones e Ingeniero en Informática (titulaciones oficiales en España).

Sígueme:
Scroll al inicio