Java Sound

Uso delle API di JAVA per la gestione di audio multi-speakers e multi-boards

Introduzione

Questi sono i concetti fondamentali che ritengo importanti fissare in seguito a uno studio sull'uso delle API di JAVA che permettono di gestire il segnale audio tramite un PC.

La sfida iniziale è stata quella di ottenere il playback programmatico da una selezionata scheda audio del PC, fra più schede presenti, e da un selezionato speaker fra più speakers presenti, usando il linguaggio di programmazione JAVA. Le diverse schede e i diversi speakers devono poter essere pilotati separatamente e contemporaneamente.

La soluzione è stata trovata nella realizzazione del prototipo "java-sound-prototype" (download di dist.zip in fondo alla pagina) che consiste di una libreria jar eseguibile, una cartella resources in cui sono contenuti i files audio da mandare in playback e una cartella etc in cui è presente il file properties di configurazione. Il programma può essere avviato da una shell tramite il comando:

java -jar java-sound-prototype

Le fonti

Informazioni vaste e dettagliate della gestione dell'audio tramite JAVA si possono trovare qui:

http://www.jsresources.org/faq_audio.html

Il paragrafo più interessante per lo scopo è stato il 15.2:

http://www.jsresources.org/faq_audio.html#multichannel

che è stato il punto di inizio dell'itinerario fino al raggiungimento della soluzione.

Altra fonte interessante è il sito dei driver ALSA:

http://www.alsa-project.org/main/index.php/Main_Page

Chiaramente sono stati usati anche vari altri spunti presi dal web in forum o siti amatoriali.

Le API di JAVA per la gestione dell'audio

La libreria utilizzata è stata la javax.sound.sampled.

Essa in particolare contiene l'implementazione di mixers di tipo "Direct Audio Device" che sono quelli che permettono di gestire files audio multi-canale. Direct Audio Device implementa le istanze di SourceDataLine, TergetDataLine e Clip. Il SourceDataLine va usato per gestire il playback mentre il TargetDataLine gestisce il recording, contrariamente a quanto il nome potrebbe far pensare. Anche il Clip gestisce il playback ma, a differenza del Source Data Line, necessita di un file audio che deve essere caricato tutto prima del playback.

Per il java-sound-prototype è stato scelto il SourceDataLine.

I mixers "Direct Audio Device" sono disponibili in Linux dalla versione Java 1.4.2, mentre lo sono anche su Windows e Solaris dalla versione 1.5.


I mixers e il pilotaggio di più schede audio

I mixers sono gli oggetti con cui si gestisce il segnale audio. Esistono diversi tipi di mixers che possono essere usati dalle API di JAVA, in dipendenza dal tipo di schede audio installate nel PC e dei drivers audio a disposizione.

Da tenere sempre presente che tutte le possibilità di gestione dell'audio sono strettamente legate a queste due risorse: scheda e drivers audio.

Una scheda audio può avere più di un mixer, ognuno dotato di particolari funzionalità. Così per esempio ci sono i mixers per il playback (con invio del segnale sugli speakers), quelli per la registrazione (con la cattura del segnale dal microfono) e quelli per effettuare i controlli del segnale catturato o riprodotto, come per esempio il controllo del volume.

Dunque se si seleziona un mixer piuttosto che un altro si potrebbe stare a selezionare una scheda piuttosto che un'altra se sono presenti diverse schede e quel mixer appartiene alla scheda che si voleva selezionare. Può accadere che una stessa scheda audio abbia più mixer che svolgono tutti le stesse funzionalità. In questo caso i vari mixers possono essere sfruttati per far fare più cose alla stessa scheda, per esempio per pilotare i vari speakers della stessa scheda in maniera indipendente.

E' difficile conoscere in maniera programmatica quale mixer sarebbe più conveniente pilotare. Se non si vuole far conoscere a priori al programma il particolare indice (nella lista dei mixer letti dalle API) del mixer da usare, occorre scorrere tutta la lista confrontando il nome del mixer con quello desiderato.

Pilotare più speakers indipendentemente

Un file audio multi-canale è un file che contiene un segnale con più canali audio, ossia può convogliare informazioni diverse su canali diversi, ogni canale avente sbocco su un certo altoparlante. Il segnale audio multicanale non contiene una separazione fisica del segnale ma è un segnale contenente informazioni tali per cui si comporta in maniera diversa in funzione degli speakers su cui viene convogliato.

C'è da considerare che su uno stesso speaker si possono convogliare più segnali audio diversi contemporanemanete, se la scheda audio lo permette, ossia se è munita di più devices di playback. Il risultato è la sovrapposizione di questi segnali in modo tale che lo speaker riesce a riprodurre contemporaneamente tutti i segnali pur lasciandoli separati.

Si supponga ora di avere un segnale mono canale. Questo significa che tale segnale si comporterà alla stessa maniera sia se va su uno speaker se va su un altro. Se quindi questo segnale è collegato per esempio a due speakers mediante lo stesso connettore, esso sarà convogliato su entrambi alla stessa maniera ed entrambi farebbero sentire la stessa cosa.

Sulla base di quanto detto precedentemente, se si riesce a creare un segnale multicanale a partire da quel segnale mono, così che su un canale si manda il segnale originale mentre sugli altri si manda il segnale muto, tutto va come se il segnale audio originale sia stato convogliato solo su uno speaker.

Se poi si riesce a mandare contemporaneamente più segnali così fatti ma relativi a speakers differenti si è ottenuto il pilotaggio di più speakers indipendentemente e contemporaneamente.

Le API di Java Sound in pratica

Il programma java-sound-prototype è stato realizzato a partire dall'esempio descritto al seguente link:

http://mimic-the-game.googlecode.com/svn-history/r268/MimicFinal/src/AudioPlayer.java

ed utilizzando anche altri esempi presi qui:

http://www.jsresources.org/examples/index.html

A partire da un file si genera tramite AudioSystem uno stream audio da dare in ingresso al mixer. Siccome le API lavorano solo su stream audio di tipo PCM, è necessario verificare che tale stream sia PCM ed eventualmente convertirlo in tale formato prima di darlo in input al mixer.

Le API permettono di verificare, tramite AudioSystem.getMixerInfo(), la lista dei mixers che la scheda audio e i drivers audio insieme mettono a disposizione. Per fare il playback occorre scegliere fra questi mixers uno che supporti il SourceDataLine, tramite la primitiva AudioSystem.getMixer(<indice del mixer nella lista dei mixers>).

Con il mixer e le informazioni sul formato dello stream audio da riprodurre viene creato l'oggetto SourceDataLine, quindi viene aperto così da renderlo pronto per essere riprodotto sul particolare mixer selezionato. Il comando per ottenere la riproduzione è:

SourceDataLine.write(byte[], int, int)

Questo prende in input un buffer contenente lo stream audio, un offset e la lunghezza del buffer.

Se un certo stream audio vuole essere fatto sentire solo su un particolare speaker ma non sugli altri, allora è necessario modificare opportunamente lo stream prima di caricarlo sul mixer. La modifica consiste nel rendere il segnale audio di tipo multicanale così che il segnale audio vero vada solo sullo speaker selezionato mentre agli altri speakers vada il segnale muto. Questa modifica va fatta a livello software di alto livello, senza utilizzare le API.

Per essa l'esempio usato in java-sound-prototype è stato quello che si trova qui:

http://www.jsresources.org/examples/SingleChannelStereoConverter.html

In particolare fa riferimento solo al caso di segnale stereo. Il caso multicanale, con invio dell'audio solo a uno fra n canali, con n > 2 è trattato nel programma fornito come esempio nel sito ALSA, lo speaker-test:

http://www.alsa-project.org/~james/speaker-test.old/speaker-test.c

Si potrebbe prendere spunto da questo codice C per estendere il java-sound-prototype alla gestione anche di più di due speakers in maniera indipendente.

Alcune considerazioni sugli ALSA drivers

I drivers delle schede audio che usa Java Sound sono diversi a seconda dell'implementazione del mixer e del sistema operativo. La tabella seguente (presa qui http://www.jsresources.org/faq_audio.html#which_driver_possible) mostra le possibili combinazioni.

Ancora sui driver ALSA

Siccome per poter pilotare contemporanemanete e indipendentemente più mixers è necessario utilizzare il "Direct Audio Device", ne segue che, in ambiente Linux, sono fondamentali i drivers ALSA.

Per verificare se i driver ALSA sono presenti nel kernel di Linux basta verificare che sia presente la cartella:

/proc/asound/

Una volta che il sistema è dotato dei drivers ALSA, è possibile che sia necessario fare qualche altro intervento per mettere a punto l'ambiente di Java Sound, soprattutto dal punto di vista della configurazione di tali drivers così che possano riconoscere le schede audio installate nel PC.

A tale scopo consultare questa pagina:

https://wiki.archlinux.org/index.php/Advanced_Linux_Sound_Architecture

Da qui per esempio si hanno le istruzioni per far riconoscere una scheda audio USB, con il comando:

modprobe snd-usb-audio

I drivers ALSA presentano negli identificativi dei mixer una stringa con cui è facile indirizzare i vari mixers. Un esempio di tale stringa è il seguente:

[plughw:1,0]

Questa sta ad indicare il mixer di tipo plughw della card 1, del device 0 e del subdevice 0.

La card è strettamente legata uno a uno con la scheda audio, cioè si può dire che facendo riferimento alla card 1 si sta facendo riferimento alla scheda audio 1. Il device rappresenta il modo con cui i drivers ALSA accedono all'hardware. Differenti devices di una stessa scheda possono essere usati contemporaneamente e indipendentemente uno dall'altro.

Generalmente la coppia card:device è sufficiente a individuare il connettore o l'insieme di connettori su cui il segnale audio sarà convogliato o da cui questo può essere catturato. Per alcune schede i drivers ALSA distinguono anche dei subdevices. Questi possono essere pilotati indipendentemente ma solo uno alla volta, perchè mentre uno viene usato gli altri sono bloccati. Usando la stringa card:device, il subdevice è sottointeso essere lo 0. Se la scheda ne ha altri, allora saranno numerati progressivamente a partire da questi e la stringa sarà card:device:subdevice, essendo subdevice il numero del subdevice.

Ci sono vari comandi per stimolare i drivers ALSA e verificare l'ambiente su cui poggerà la struttura Java Sound.

Molto utili sono i comandi aplay e alsamixer. In particolare sono utili questi comandi:

  • aplay -l, per ottenere la lista dei devices disponibili per il playback.

  • aplay -L, per leggere la configurazione attuale dei drivers

  • aplay -Dplughw:1,0 nome_file.wav, per riprodurre il file audio nome_file.wav sul device 0 della scheda audio 1 (Attenzione: i driver ALSA lavorano solo su stream audio di tipo PCM)