giovedì 12 dicembre 2013

Linux: come riprodurre un'onda sonora tramite l'interfaccia ALSA

Un'onda sinusoidale PCM
Qualche tempo fa (= due anni fa :D) vi ho mostrato come riprodurre un'onda sonora tramite l'interfaccia WaveOut di Windows, oggi vi mostrerò come riprodurre un'onda sonora su Linux tramite l'interfaccia ALSA, la libreria di basso livello divenuta uno standard de facto per il sonoro su Linux, tant'è che oggi viene distribuita col kernel di Linux direttamente.

Cos'è ALSA?

ALSA (Advanced Linux Sound Architecture) è un modulo del kernel di linux, ovvero un programma che estende le funzionalità del kernel. Per la sua vicinanza al kernel, è molto usata dalle applicazioni che vogliono comunicare direttamente con la scheda audio, anche se esistono molte altre comode librerie (PulseAudio, Gstreamer) che si interfacciano a loro volta ad ALSA ma che semplificano di molto la vita ai programmatori.

Prima dell'avvento di ALSA (che supporta anche le periferiche MIDI) esisteva un altro “standard” su linux, OSS (Open Sound System), il quale aveva la particolarità di permettere al programmatore di comunicare direttamente con la scheda audio tramite il device a caratteri (un file che, nei sistemi Unix, comunica coi driver di un qualsiasi dispositivo) /dev/dsp, la sua forza stava nel fatto di scrivere dati su questo file come se fosse un qualunque altro file, e di leggerli: la prima operazione riproduceva un sonoro, la seconda permetteva la registrazione. Oggi OSS non viene più distribuito su Ubuntu e il device a caratteri /dev/dsp non c'è più fisicamente, ma se non sbaglio (devo informarmi di più su questo argomento) ALSA dovrebbe mantenere una retrocompatibilità con OSS.

Setting up ALSA (preparazione)

Per utilizzare ALSA dobbiamo scaricare i file di sviluppo (header e librerie), e quindi dobbiamo installare il pacchetto libasound2-dev scaricando i sorgenti dal sito ufficiale e compilando oppure, più semplicemente, su Debian Ubuntu andare sul terminale e scrivere:
sudo apt-get install libasound2-dev
che farà tutto lui. Per compilare un'applicazione ALSA (tipo quella che verrà trattata in questo post) basta compilare da terminale scrivendo:
gcc esempio.c -o esempio -lasound
quindi lanciare il programma compilato con:
./esempio
Volendo si può compilare anche con pkg-config se l'avete configurato, ma visto che ALSA non vuole linkate molte librerie il suo uso anzi è più complicato che scrivere solamente -lasound.
  • Se avete installato il pacchetto con apt-get (come la riga sopra) allora avrete i file nei posti giusti e il compilatore troverà sia l'header che noi indichiamo nel file .c, che la libreria lasound
  • Se avete compilato e installato manualmente dovrete indicare a gcc sia la cartella dell'header di alsa che la libreria libasound2
A questo punto possiamo passare al codice.

Un po' di codice

Il codice è veramente breve e semplicissimo se avete già letto il vecchio post che spiega come creare l'onda sinusoidale. Creare l'onda è la difficoltà maggiore, perché una volta stabiliti i parametri del file wave, le funzioni di ALSA sono molto intuitive. Riporto per intero il codice:
#include <stdio.h>
#include <alsa/asoundlib.h>


typedef enum {
TIME = 5, // Durata della traccia (secondi)
CHANNELS = 1, // Numero dei canali
SAMPLE_RATE = 44100, // Frequenza di campionamento (Hz)
BITS_PER_SAMPLE = 8, // Bits per campione
BLOCK_ALIGN = CHANNELS * BITS_PER_SAMPLE / 8, // Allineamento del blocco
BYTES_PER_SECOND = SAMPLE_RATE * BLOCK_ALIGN, // Frequenza di campionamento in Hz
SAMPLES = CHANNELS * SAMPLE_RATE * TIME, // Numero di campioni richiesti per la durata scelta
} WAVE;




int main() {
/*
* Creo puntatore con l'onda sinusoidale
*/
char* ptr;
int tone = 440; //440 Hertz
float volume = 10.0f;
float pulsazione = ( 2.0f * 3.14f / (float) SAMPLE_RATE / CHANNELS ) * (float) tone;
int i;
if ((ptr = (char*)malloc(SAMPLES))==NULL){
fprintf (stderr,"Si e' verificato un errore!\n");
return EXIT_FAILURE;
}
for (i=0 ; i < SAMPLES; i++ )
ptr[i] = (char) ( cos( (float) i * pulsazione ) * volume + 128.0f );
/*
* Inzializzo ALSA
*/
snd_pcm_t *handle; //handle del device
int err;
err = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_ASYNC); //apro il device
if (err<0){
fprintf (stderr,"Si e' verificato un errore!\n");
return err;
}
err = snd_pcm_set_params(handle,SND_PCM_FORMAT_U8 ,SND_PCM_ACCESS_RW_INTERLEAVED,CHANNELS,SAMPLE_RATE,0,0);
if (err<0){
fprintf (stderr,"Si e' verificato un errore!\n");
return err;
}
/*
* invio dati
*/
snd_pcm_writei(handle,ptr,SAMPLES);
snd_pcm_close(handle);
}


Con l'istruzione
err = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_ASYNC);
assegniamo apriamo il device sonoro di default, conservando il suo handle in “handle”, specificando che apriamo il device per la riproduzione (SND_PCM_STREAM_PLAYBACK), in maniera asincrona (SND_PCM_ASYNC) anche se non ce ne facciamo niente del fatto che lo apriamo in maniera asincrona perché dovremmo specificare la funzione di callback per ricevere asincronicamente dei segnali (non è argomento di questo post).
Con l'instruzione
err = snd_pcm_set_params(handle,SND_PCM_FORMAT_U8 ,SND_PCM_ACCESS_RW_INTERLEAVED,CHANNELS,SAMPLE_RATE,0,0);
settiamo i parametri necessari per la riproduzione sul device “handle” in particolare gli diciamo che il formato dei samples PCM è unsigned 8 bit (ovvero il char), specificando il modo di accesso (SND_PCM_ACCESS_RW_INTERLEAVED, adatto per quello che dobbiamo fare), il numero di canali, la frequenza di campionamento, se ALSA deve ricampionare i campioni (0 per NO, 1 per SI), e la latenza della scheda audio, che supponiamo in questa sede che sia nulla.
Per l'invio dei campioni al device usiamo:
snd_pcm_writei(handle,ptr,SAMPLES);
dove specifichiamo il device (handle), il puntatore ai campioni (ptr) e il numero di sample da inviare (SAMPLES).


Rimando a un'altra volta (magari fra due anni :P) lo sfruttamento della modalità asincrona.

Fine.

Nessun commento:

Posta un commento