AirVinyl : transformez un Raspberry Pi en « platine vinyle » AirPlay pour vos HomePod

Écrit par

dans

,

Et si votre iPhone pouvait envoyer n’importe quelle musique vers une sorte de platine vinyle virtuelle, avant que le son n’arrive sur vos HomePod ?

C’est exactement ce que nous allons construire avec un Raspberry Pi.

Le principe est assez réjouissant : depuis l’iPhone, on choisit simplement une nouvelle destination AirPlay appelée AirVinyl. Le Raspberry Pi reçoit alors le morceau, le traite en temps réel pour lui ajouter les petites imperfections qui font le charme du vinyle — souffle, craquements, légère instabilité de hauteur, coloration — puis renvoie le résultat vers les HomePod.

À l’usage, cela donne simplement :

iPhone
   │
   │ AirPlay
   ▼
AirVinyl
   │
   │ traitement « vinyle »
   ▼
HomePod

Et une fois l’installation terminée, tout démarre automatiquement avec le Raspberry Pi. Pas de commande à saisir avant d’écouter de la musique.

Sous le capot, notre petite chaîne audio ressemble à ceci :

iPhone
   │
   │ AirPlay
   ▼
Shairport Sync
   │
   │ PCM 16 bits / 44,1 kHz
   ▼
FFmpeg
   │
   │ wow + flutter
   │ saturation
   │ souffle
   │ craquements
   ▼
OwnTone
   │
   │ AirPlay 2
   ▼
HomePod(s)

J’ai réalisé le projet avec un Raspberry Pi 3, preuve qu’il n’est même pas nécessaire de lui consacrer une machine récente.

L’idée de ce tutoriel est de progresser tranquillement : à chaque étape importante, on vérifie que tout fonctionne avant d’ajouter la suivante. Si quelque chose coince, on sait donc immédiatement où regarder.


1. Préparer le Raspberry Pi

J’ai choisi Raspberry Pi OS Lite 64 bits.

Nous n’avons besoin d’aucune interface graphique : AirVinyl va fonctionner comme un petit appareil audio autonome.

Dans Raspberry Pi Imager, configurez directement le Wi-Fi, SSH, votre utilisateur et un nom d’hôte. J’ai choisi :

AirVinyl

Après le premier démarrage, connectez-vous en SSH :

ssh alex@airvinyl.local

Évidemment, remplacez alex par votre utilisateur.

Petit contrôle avant de commencer

Les Raspberry Pi, surtout les anciens modèles, sont assez sensibles à leur alimentation. Autant vérifier tout de suite :

vcgencmd get_throttled

Le résultat parfait est :

throttled=0x0

Si vous l’obtenez, le Pi est correctement alimenté et nous pouvons continuer.

Mettez ensuite le système à jour :

sudo apt update
sudo apt full-upgrade -y

Puis installez déjà FFmpeg et les outils Avahi :

sudo apt install -y ffmpeg avahi-utils

2. Transformer le Raspberry Pi en récepteur AirPlay

La première brique s’appelle Shairport Sync.

Son rôle est simple :

iPhone
   ↓
AirPlay
   ↓
Raspberry Pi

Nous allons utiliser sa sortie pipe, qui permet de récupérer le son sous forme de données PCM brutes et de l’envoyer ensuite où nous voulons.

Installez les dépendances :

sudo apt install --no-install-recommends -y \
  build-essential git autoconf automake libtool \
  libpopt-dev libconfig-dev libasound2-dev \
  avahi-daemon libavahi-client-dev \
  libssl-dev libsoxr-dev \
  libavutil-dev libavcodec-dev libavformat-dev \
  systemd-dev

Téléchargez ensuite Shairport Sync :

cd ~
git clone https://github.com/mikebrady/shairport-sync.git
cd shairport-sync

autoreconf -fi

Configurez la compilation :

./configure \
  --sysconfdir=/etc \
  --with-alsa \
  --with-soxr \
  --with-avahi \
  --with-ssl=openssl \
  --with-systemd-startup \
  --with-ffmpeg \
  --with-pipe \
  --with-metadata \
  --with-metadata-pipe

Compilez :

make -j$(nproc)
sudo make install

Puis activez le service :

sudo systemctl enable shairport-sync

Vérification n°1 : avons-nous bien le bon Shairport ?

shairport-sync -V

Puis :

shairport-sync -h

Dans la sortie, vous devez notamment retrouver :

Available audio backends:
    pipe

Parfait : notre Raspberry sait maintenant recevoir de l’AirPlay et nous laisser récupérer le signal audio.


3. Pourquoi utiliser AirPlay classique à l’entrée ?

AirVinyl utilise volontairement deux générations d’AirPlay :

iPhone
   ↓
AirPlay classique
   ↓
Shairport

puis :

OwnTone
   ↓
AirPlay 2
   ↓
HomePod

Ce choix nous simplifie beaucoup la vie.

AirPlay classique fournit parfaitement ce dont notre chaîne a besoin : un flux stéréo 16 bits / 44,1 kHz, que nous pouvons envoyer directement vers FFmpeg.

Il permet aussi de réserver le mécanisme de synchronisation PTP d’AirPlay 2 à OwnTone, qui en aura besoin pour communiquer avec les HomePod.


4. Installer OwnTone

Notre Raspberry sait recevoir de la musique. Il doit maintenant savoir la renvoyer vers des enceintes AirPlay.

C’est le rôle d’OwnTone.

Installez ses dépendances :

sudo apt install -y \
  build-essential git autotools-dev autoconf automake libtool \
  gettext gawk gperf bison flex \
  libconfuse-dev libunistring-dev libsqlite3-dev \
  libavcodec-dev libavformat-dev libavfilter-dev \
  libswscale-dev libavutil-dev libasound2-dev \
  libxml2-dev libgcrypt20-dev libavahi-client-dev \
  zlib1g-dev libevent-dev libplist-dev libsodium-dev \
  libjson-c-dev libwebsockets-dev \
  libcurl4-openssl-dev libprotobuf-c-dev

Puis :

cd ~

git clone https://github.com/owntone/owntone-server.git
cd owntone-server

autoreconf -i

./configure \
  --prefix=/usr \
  --sysconfdir=/etc \
  --localstatedir=/var \
  --enable-install-user

make -j$(nproc)
sudo make install

5. Créer le « câble audio virtuel »

Shairport et OwnTone vont communiquer au moyen d’un FIFO, autrement dit un pipe Unix.

On peut vraiment le voir comme un câble audio virtuel :

Shairport ────────► OwnTone

Créons son dossier :

sudo mkdir -p /srv/airvinyl/library

Puis le pipe audio :

sudo mkfifo /srv/airvinyl/library/AirVinyl
sudo chmod 666 /srv/airvinyl/library/AirVinyl

Et celui des métadonnées :

sudo mkfifo /srv/airvinyl/library/AirVinyl.metadata
sudo chmod 666 /srv/airvinyl/library/AirVinyl.metadata

Vérification n°2 : nos pipes existent-ils ?

ls -l /srv/airvinyl/library/AirVinyl*

Les lignes doivent commencer par :

p

Par exemple :

prw-rw-rw-

Ce p indique qu’il s’agit bien de pipes.


6. Configurer OwnTone

Ouvrez :

sudo nano /etc/owntone.conf

Dans la section :

library {

indiquez :

directories = { "/srv/airvinyl/library" }

Nous n’avons pas besoin que le Raspberry joue lui-même le son sur sa sortie audio.

Dans :

audio {

mettez :

type = "disabled"

Démarrez OwnTone :

sudo systemctl enable --now owntone
sudo systemctl restart owntone

Vérification n°3 : OwnTone voit-il vos HomePod ?

Ouvrez dans votre navigateur :

http://airvinyl.local:3689

Dans :

Settings → Remotes & Outputs

vos appareils AirPlay devraient apparaître.

Vous pouvez également les voir depuis le terminal :

curl -s http://localhost:3689/api/outputs | python3 -m json.tool

Sélectionnez le HomePod sur lequel vous souhaitez faire le premier test.

Un détail qui nous a surpris pendant l’installation : le volume de sortie OwnTone était initialement à 50. Le résultat semblait anormalement faible.

Si nécessaire, mettez la sortie à 100 depuis l’interface OwnTone.


7. Configurer Shairport

Ouvrez :

sudo nano /etc/shairport-sync.conf

Nous allons appeler notre nouveau récepteur :

AirVinyl

La configuration générale devient :

general =
{
    name = "AirVinyl";
    service_type = "classic";
    output_backend = "pipe";
};

Puis :

pipe =
{
    name = "/srv/airvinyl/library/AirVinyl";
    output_rate = 44100;
    output_format = "S16_LE";
    output_channels = 2;
};

Enfin :

metadata =
{
    enabled = "yes";
    include_cover_art = "yes";
    pipe_name = "/srv/airvinyl/library/AirVinyl.metadata";
};

Redémarrez Shairport :

sudo systemctl restart shairport-sync

Vérification n°4 : AirVinyl est-il apparu sur l’iPhone ?

Vérifiez :

sudo systemctl status shairport-sync --no-pager

Vous devez obtenir :

Active: active (running)

Prenez maintenant votre iPhone et ouvrez le menu AirPlay.

Et normalement, petit moment satisfaisant :

AirVinyl

apparaît parmi vos enceintes.

Sélectionnez-le et lancez un morceau.

À ce stade, nous n’avons encore ajouté aucun effet. C’est volontaire.

Nous voulons d’abord valider ceci :

iPhone
   ↓
Shairport
   ↓
OwnTone
   ↓
HomePod

Pendant que la musique joue :

curl -s http://localhost:3689/api/player

Vous devez voir :

"state": "play"

Si le compteur de progression avance et que vous entendez la musique sur le HomePod, félicitations : vous avez déjà fabriqué un relais AirPlay avec un Raspberry Pi.

Maintenant, on va le transformer en platine vinyle.


8. Ajouter FFmpeg au milieu

Notre architecture devient :

Shairport
    ↓
FFmpeg
    ↓
OwnTone

Arrêtez Shairport :

sudo systemctl stop shairport-sync

Créez un nouveau pipe :

sudo mkfifo /srv/airvinyl/input.pcm
sudo chmod 666 /srv/airvinyl/input.pcm

Puis revenez dans :

sudo nano /etc/shairport-sync.conf

Dans la section pipe, remplacez :

name = "/srv/airvinyl/library/AirVinyl";

par :

name = "/srv/airvinyl/input.pcm";

Les métadonnées restent, elles, envoyées directement vers :

/srv/airvinyl/library/AirVinyl.metadata

9. Tester FFmpeg sans modifier le son

Avant d’ajouter des effets, vérifions simplement que FFmpeg peut s’insérer dans la chaîne sans rien casser.

Lancez :

ffmpeg -y \
  -hide_banner \
  -loglevel warning \
  -f s16le \
  -ar 44100 \
  -ac 2 \
  -i /srv/airvinyl/input.pcm \
  -af "anull" \
  -f s16le \
  -ar 44100 \
  -ac 2 \
  /srv/airvinyl/library/AirVinyl

Dans un deuxième terminal :

sudo systemctl start shairport-sync

Relancez un morceau sur l’iPhone.

Vérification n°5

Si vous entendez exactement votre morceau comme avant, notre chaîne complète fonctionne :

iPhone
   ↓
Shairport
   ↓
FFmpeg
   ↓
OwnTone
   ↓
HomePod

Nous pouvons maintenant faire subir absolument ce que nous voulons au son.


10. Un petit test amusant avant le vrai vinyle

Pour être absolument certain que FFmpeg traite bien le signal, faisons quelque chose de volontairement spectaculaire.

Arrêtez Shairport :

sudo systemctl stop shairport-sync

Arrêtez FFmpeg avec :

Ctrl+C

Puis lancez :

ffmpeg -y \
  -hide_banner \
  -loglevel warning \
  -f s16le \
  -ar 44100 \
  -ac 2 \
  -i /srv/airvinyl/input.pcm \
  -af "lowpass=f=2500,aecho=0.8:0.88:250:0.8,tremolo=f=4:d=0.8" \
  -f s16le \
  -ar 44100 \
  -ac 2 \
  /srv/airvinyl/library/AirVinyl

Puis :

sudo systemctl start shairport-sync

Relancez la musique.

Cette fois, il est impossible de ne pas entendre la différence : écho énorme, son étouffé, volume qui pulse.

Si vous avez ce résultat, tout est parfait.

On peut passer aux choses intéressantes.


11. Fabriquer notre son vinyle

Pour évoquer un disque, nous allons mélanger plusieurs petites imperfections plutôt que d’appliquer un énorme filtre caricatural.

Le signal musical va recevoir :

coloration
    +
saturation douce
    +
wow lent
    +
flutter rapide
    +
souffle
    +
craquements aléatoires

Le wow est particulièrement intéressant : le pitch du morceau oscille très légèrement et lentement, comme avec une rotation imparfaite du disque.

Le flutter ajoute une fluctuation plus rapide et beaucoup plus petite.

Le souffle est généré directement par FFmpeg.

Les craquements sont eux aussi générés dynamiquement : ce n’est pas un fichier sonore qui tourne en boucle.

Résultat : ils n’apparaissent jamais exactement au même endroit.


12. Créer plusieurs presets

Créons d’abord le fichier qui mémorisera le preset choisi :

sudo mkdir -p /etc/airvinyl

Puis :

echo 'PRESET=vinyl' | sudo tee /etc/airvinyl/preset.conf

Nous allons proposer quatre modes :

bypass   → aucun effet
hifi     → vinyle très propre
vinyl    → notre effet principal
worn     → disque nettement plus usé

Créez :

sudo nano /usr/local/bin/airvinyl-dsp

Et collez :

#!/bin/bash

set -euo pipefail

INPUT="/srv/airvinyl/input.pcm"
OUTPUT="/srv/airvinyl/library/AirVinyl"

PRESET="${PRESET:-vinyl}"

ensure_fifo() {
    if [[ ! -p "$1" ]]; then
        rm -f "$1"
        mkfifo "$1"
        chmod 666 "$1"
    fi
}

ensure_fifo "$INPUT"
ensure_fifo "$OUTPUT"

echo "AirVinyl DSP - preset: $PRESET"

case "$PRESET" in

    bypass)
        exec ffmpeg -y \
            -hide_banner \
            -loglevel warning \
            -f s16le \
            -ar 44100 \
            -ac 2 \
            -i "$INPUT" \
            -af "anull" \
            -f s16le \
            -ar 44100 \
            -ac 2 \
            "$OUTPUT"
        ;;

    hifi)
        exec ffmpeg -y \
            -hide_banner \
            -loglevel warning \
            -f s16le \
            -ar 44100 \
            -ac 2 \
            -i "$INPUT" \
            -af "highpass=f=25,lowpass=f=16000,equalizer=f=120:t=q:w=0.8:g=1.0,asoftclip=type=tanh:threshold=0.90:output=0.98:oversample=4,vibrato=f=0.33:d=0.004,vibrato=f=5.8:d=0.001" \
            -f s16le \
            -ar 44100 \
            -ac 2 \
            "$OUTPUT"
        ;;

    vinyl)
        exec ffmpeg -y \
            -hide_banner \
            -loglevel warning \
            -f s16le \
            -ar 44100 \
            -ac 2 \
            -i "$INPUT" \
            -f lavfi \
            -i "anoisesrc=r=44100:a=0.0025:c=pink" \
            -f lavfi \
            -i "aevalsrc=exprs='if(lt(random(0)\,0.00004)\,(random(1)*2-1)*0.25\,0)+if(lt(random(2)\,0.000003)\,(random(3)*2-1)*0.70\,0)':s=44100" \
            -filter_complex \
            "[0:a]highpass=f=28,lowpass=f=14000,equalizer=f=130:t=q:w=0.8:g=1.5,equalizer=f=4500:t=q:w=1.0:g=-1.2,asoftclip=type=tanh:threshold=0.82:output=0.95:oversample=4,vibrato=f=0.38:d=0.016,vibrato=f=6.1:d=0.003[music]; \
             [1:a]highpass=f=150,lowpass=f=9000,pan=stereo|c0=c0|c1=c0[noise]; \
             [2:a]highpass=f=700,lowpass=f=12000,pan=stereo|c0=c0|c1=c0[crackle]; \
             [music][noise][crackle]amix=inputs=3:duration=first:normalize=0,alimiter=limit=0.97[out]" \
            -map "[out]" \
            -f s16le \
            -ar 44100 \
            -ac 2 \
            "$OUTPUT"
        ;;

    worn)
        exec ffmpeg -y \
            -hide_banner \
            -loglevel warning \
            -f s16le \
            -ar 44100 \
            -ac 2 \
            -i "$INPUT" \
            -f lavfi \
            -i "anoisesrc=r=44100:a=0.006:c=pink" \
            -f lavfi \
            -i "aevalsrc=exprs='if(lt(random(0)\,0.00010)\,(random(1)*2-1)*0.35\,0)+if(lt(random(2)\,0.000008)\,(random(3)*2-1)*0.85\,0)':s=44100" \
            -filter_complex \
            "[0:a]highpass=f=40,lowpass=f=10500,equalizer=f=150:t=q:w=0.8:g=2.5,equalizer=f=4000:t=q:w=1.0:g=-2.0,asoftclip=type=tanh:threshold=0.72:output=0.92:oversample=4,vibrato=f=0.42:d=0.030,vibrato=f=6.5:d=0.006[music]; \
             [1:a]highpass=f=120,lowpass=f=8500,pan=stereo|c0=c0|c1=c0[noise]; \
             [2:a]highpass=f=600,lowpass=f=12000,pan=stereo|c0=c0|c1=c0[crackle]; \
             [music][noise][crackle]amix=inputs=3:duration=first:normalize=0,alimiter=limit=0.97[out]" \
            -map "[out]" \
            -f s16le \
            -ar 44100 \
            -ac 2 \
            "$OUTPUT"
        ;;

    *)
        echo "Preset AirVinyl inconnu: $PRESET" >&2
        exit 1
        ;;

esac

Rendez le script exécutable :

sudo chmod +x /usr/local/bin/airvinyl-dsp

13. Faire fonctionner AirVinyl automatiquement

Nous voulons maintenant arriver au meilleur moment du projet :

allumer le Raspberry Pi et ne plus jamais avoir besoin de lancer FFmpeg manuellement.

Créons un service systemd :

sudo nano /etc/systemd/system/airvinyl-dsp.service

Contenu :

[Unit]
Description=AirVinyl DSP Processor
After=network-online.target owntone.service
Wants=network-online.target owntone.service
Before=shairport-sync.service

[Service]
Type=simple
EnvironmentFile=/etc/airvinyl/preset.conf
ExecStart=/usr/local/bin/airvinyl-dsp
Restart=always
RestartSec=1

[Install]
WantedBy=multi-user.target

Puis :

sudo systemctl daemon-reload
sudo systemctl enable airvinyl-dsp.service

Il faut maintenant indiquer à Shairport qu’il doit démarrer après notre moteur DSP :

sudo systemctl edit shairport-sync

Ajoutez :

[Unit]
Wants=airvinyl-dsp.service
After=airvinyl-dsp.service

Puis :

sudo systemctl daemon-reload

14. Ajouter une commande simple pour changer de preset

Créons :

sudo nano /usr/local/sbin/airvinyl-preset

Avec :

#!/bin/bash

set -euo pipefail

PRESET="${1:-}"

case "$PRESET" in
    bypass|hifi|vinyl|worn)
        ;;
    *)
        echo "Usage: sudo airvinyl-preset {bypass|hifi|vinyl|worn}"
        exit 1
        ;;
esac

echo "PRESET=$PRESET" > /etc/airvinyl/preset.conf

systemctl stop shairport-sync
systemctl restart airvinyl-dsp
systemctl start shairport-sync

echo "AirVinyl utilise maintenant le preset: $PRESET"

Puis :

sudo chmod +x /usr/local/sbin/airvinyl-preset

À partir de maintenant :

sudo airvinyl-preset vinyl

active le preset principal.

sudo airvinyl-preset hifi

donne une coloration beaucoup plus discrète.

sudo airvinyl-preset worn

sort le vieux disque qui a beaucoup vécu.

Et :

sudo airvinyl-preset bypass

désactive tous les effets.


15. Vérification finale : le test du reboot

Choisissez par exemple :

sudo airvinyl-preset vinyl

Puis redémarrez :

sudo reboot

Une fois le Pi revenu :

systemctl is-active owntone
systemctl is-active airvinyl-dsp
systemctl is-active shairport-sync

Les trois doivent répondre :

active

Et maintenant, plus aucune commande à saisir.

Prenez simplement votre iPhone, choisissez :

AirVinyl

et lancez votre morceau.

Le Raspberry Pi fait tout le reste.


Et le résultat ?

C’est probablement ma partie préférée du projet : l’effet fonctionne surtout grâce à l’accumulation de petites imperfections.

Avec le preset vinyl, on perçoit le souffle, les petits craquements se promènent dans l’espace stéréo, le pitch n’est plus tout à fait aussi mathématiquement stable et le signal est légèrement adouci.

Ce n’est pas un filtre « vieille radio ». La musique reste parfaitement écoutable.

Mais elle cesse d’être complètement parfaite.

Et c’est précisément là que le cerveau commence à entendre… un disque.

Le plus amusant reste que la source peut être absolument n’importe quoi : un serveur musical personnel, une application de streaming, un podcast ou un morceau récent.

Pour l’iPhone, AirVinyl n’est qu’une enceinte AirPlay supplémentaire.

Pour le Raspberry Pi, c’est un flux PCM sur lequel nous pouvons faire à peu près tout ce que FFmpeg permet.

Et pour les HomePod…

c’est comme si quelqu’un avait glissé une platine vinyle invisible au milieu du réseau.

La prochaine évolution naturelle serait d’ajouter une petite interface Web avec quatre gros boutons Bypass / Hi-Fi / Vinyl / Worn, puis quelques curseurs pour régler le souffle, les craquements, le wow et le flutter sans même ouvrir SSH.

Commentaires

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *