Alzi la mano chi non si è mai messo le mani nei capelli dovendo indicare a squeezelite i parametri alsa (opzione -a).

Le cose sono semplici in se, ma – a mio avviso – rese complicate da ALSA ed ancor più da squeezelite, purtroppo.

Vediamo di capirle meglio.

Le opzioni sono quattro:

Buffer time (or size)
Period count (or size)
Sample format
Use mmap
Prima di vederne l’utilizzo in squeezelite, vediamo di comprenderne il significato per ALSA, lo scopo e la relazione che le lega.

Partiamo dal fondo:

MMAP (https://en.wikipedia.org/wiki/Mmap) è un metodo per muovere una porzione di dati  da una posizione di memoria ad un’altra, che in alcuni casi può aiutare a ridurre la latenza. E’ molto dubbio che in effetti lo faccia nel caso di nostro interesse e di certo NON lo fa quando l’unica cosa da fare è muovere da un buffer in ingresso ad un buffer in uscita, senza trascodifiche, che è esattamente lo spirito del nostro progetto.

Sample format specifica il formato da utilizzare per ‘aprire’ la comunicazione con la scheda audio (o il dac), le opzioni corrispondono all’organizzazione dei bit di profondità, tranne il caso particolare dei 24 e 24/3, ove 24 bit = 3 Bytes, ma solo i formati 24_3 usano effettivamente 3 bytes, quelli con 24 ne usano 4 (32 bit) scartando il meno significativo.

E’ una caratteristica del (driver della) scheda audio. Se non specificato, ALSA determina in autonomia il valore da utilizzare per la scheda audio in uso, se specificato e non corrispondente, semplicemente non funziona.

Per comprendere i rimanenti parametri, strettamente lagati tra loro, occorre inquadrare il concetto di FRAME che corrisponde all’insieme dei Bytes che rappresentano un SAMPLE (campione) nello specifico  Sample format  e per tutti i canali dello stream (2 per lo stereo, nel nostro caso).

Es.

Uno stream STEREO a 44100 Hz su 16 bit,  corrisponde ad un frame di 4 Byte, 384KHz, 32 bit a 8 Bytes, 48000 Hz / 16 5.1 canali a 12 Bytes…

Come si vede il FRAME è indipendente dal sample rate.

Il buffer di Alsa è circolare, cioè man mano che i dati vengono prelevati, nuovi li sostituiscono occupando la stessa porzione di memoria, il processo è per sua natura ASINCRONO, cioè la velocità e periodicità con cui il buffer viene svuotato e riempito possono essere diverse, il che può provocare errori di XRUN nel caso in cui il buffer non contenga abbastanza dati al momento in cui la scheda audio richiede di essere alimentata o, al contrario, se è pieno quando la sorgente tenta di scriverne di nuovi.

Nei casi di sola riproduzione, la seconda eventualità è scongiurata, quindi si parla di ‘buffer underrun‘ quando il buffer non contiene i dati richiesti.

Per minimizzare tali errori, occorre che il buffer sia sufficientemente ampio da poter alimentare l’output senza interruzioni ‘facendo fronte’ alle possibili irregolarità dell’input, ad una sua minore velocità o larghezza di banda, ricoprendo esattamente il ruolo di un serbatoio in idraulica, ma il prezzo da pagare è la ‘LATENZA‘, cioè il tempo in cui  il segnale emesso dalla sorgente impiega ad arrivare al dispositivo di output. Più grande è il buffer a parità di velocità di prelievo, più alta sarà la latenza.

Con BUFFER SIZE alsa indica la dimensione dl buffer, che può essere qualiasi, ma in pratica è sempre un multiplo del PERIOD, termine con cui alsa indica l’intervallo tra due successivi interrupt al sistema affinchè questo attivi il processo che ‘riempe’ il buffer. Essendo un processo asincrono, è misurato in FRAMES (bytes in squeezelite), per questo si parla di PEROD SIZE.

Ipotizzando -per semplicità – che i dati vengano prelevati in output con velocità costante corrispondente al sample rate, è possibile dimensionare il buffer in termini di frames e quindi di bytes ‘erogabili’ per secondo, quindi di stabilire il PERIOD SIZE in funzione della frequenza degli interrupt di sistema desiderata e sostenibile.

Es.

44100/16 -> frame 4 Bytes -> 176,4 KB/sec

384000/32 -> frame 8 Bytes -> 3072 KB/sec

Se si desidera che il sistema venga interrotto 2 volte al secondo, il PERIOD SIZE sarà rispettivamente di 88.20 e 1536 KB, 10 17,6 e 307,2 ecc.

Più elevata è la frequenza di interrupt, è più alto è il carico di CPU e più bassa la latenza.

Il rapporto tra dimensione del buffer e del period è definito PERIOD COUNT.

In sistemi minimamente moderni, non c’è nessuna reale necessità di PERIOD COUNT maggiori di 2, valori leggermente superiori rappresentano un margine di sicurezza.

E’ evidente, quindi, che il corretto dimensionamneto del buffer dipende in sostanza da due sole variabili:

a. formato dello stream (canali, bit depth e sample rate).

b. frequenza di interrupt .

Peccato che non ci sia permesso di esprimerla in questi termini…

In squeezelite, abbiamo a disposizione due coppie di parametri:

buffer size
period.
dove Buffer size può essere espresso in bytes o in ms, valori fino a 499 sono considerati ms, superiori bytes, mentre per Period valori fino a 49 sono considerati relativi al count, superiori come size.

Mentre la prima cosa è intuitiva, trattandosi solo – coosciuto il formato – di una conversione di unità di misura, la seconda è completamente diversa, cambia proprio la ‘natura’ del dato e l’effetto è diametralmente opposto:

Ponendo Buffer Size = 100 ms, a 44100/16 stereo abbiamo un PERIOD SIZE di 17,64 Kb, che avrei potuto esprimere direttamente come 17640, esprimerlo in ms ha il vantaggio di essere indipendente dal formato, quindi è a mio avviso preferibile.

Ponendo period a 49,  viene interpretato come count,  da cui il period size risulta essere 17640/49= 360 byte, inserendo, invece,  51, viene interpretato come bytes, il che porta ad un period count di 17640/51 = 346 !!!

Fortunatamente, nessuno di questi valori è effettivamente usato da ALSA come tale, che calcola il valore accettabile ‘più vicino’ e lo utilizza,   squeezelite ne fornisce feedback nel log.

Se non si specifica nulla, squeezelite utilizza i seguenti valori di default:

Buffer Size: 40 ms
Period Count: 4
che sono etremamente conservativi.

Sconsiglando vivamente di indicare la dimensione del buffer e/o del period in bytes, così da rimanere indipendenti dal formato in ingresso ed in uscita, lascio a voi la sperimentazione dei valori più indicati per il vostro impianto ed il vostro gusto, segnalando che c’è una generale concordanza nel considerare valori bassi di buffer size / alti di perido count come tendendi a produrre un suono più brillante e veloce, mentre valori più elevati buffer size / ridotti di period count risulterebbero più smooth,  ma molto dipende da altri parametri, buffer applicativi in primis, per i quali valgono analoghe considerazioni.

Concludo con una nota sulla latenza.

In sistemi di acquisizione audio multicanale, la latenza è un parametro fondamentale, immaginate il caos che si produrrebbe con latenze di quache decimo di secondo tra i diversi strumenti, la voce o anche solo le spie sul palco… Stesso dicasi per sistemi di riproduzione multicanale ma indipendenti, anche se in questo caso non è tanto la latenza in se ad essere importante, quanto il ‘delta’ di sincronizzazione, comunque prodotto, di cui la latenza è solo una delle cause.

Ma in un sistema mono o multicanale su uno stesso flusso (come nel nostro caso) l’unico effetto della latenza è un minimo ritardo COMUNE a tutti i canali che rimangono perfettamente sincronizzati, in che misura è un male per se? A mio avviso praticamente nulla e scambio volentieri qualche decimo di secondo di latenza per la virtuale immunità da xRun ed un minore utilizzo della CPU.

Squeezebox server è un sistema multiroom che consente di sincronizzare diversi players, in questa applicazione – teoricamente – la latenza assume un significato maggiore, ma le irregolarità dei diversi clock provocano differenze di ordine maggiore, per contrastare le quali LMS si è dotato di un protocollo specifico che garantisce l’allineamento temporale di diversi player, minimizzandone  (per quanto possibile) l’effetto.

RImane comunque un problema irrisolto ed irrisolvibile a priori senza l’adozione di un ‘master clock’ di rete condiviso (v. Ethernet AVB), preoccuparsi della latenza in questo contesto è come cercare la mitica pagliuzza avendo una trave nell’occhio.

p.s.

Per chi fosse interessato, la mia attuale configurazione dei parametri di alsa è la seguente:

40:2::0.