Differenze tra le versioni di "Eseguire un file audio WAV con le sole funzioni esterne di Alsa"
Riga 91: | Riga 91: | ||
'''Public''' Sub Main() | '''Public''' Sub Main() | ||
− | Dim fileWAV, | + | Dim fileWAV, s As String |
Dim fl As File | Dim fl As File | ||
Dim buffA, buf As Byte[] | Dim buffA, buf As Byte[] | ||
Riga 104: | Riga 104: | ||
fl = Open fileWAV For Read | fl = Open fileWAV For Read | ||
− | |||
− | |||
− | |||
− | |||
<FONT color=gray>' ''Inizia i controlli di verifica del file caricato:''</font> | <FONT color=gray>' ''Inizia i controlli di verifica del file caricato:''</font> | ||
− | + | Seek #fl, 12 | |
− | If fmt | + | Read #fl, s, 3 |
+ | If s <> "fmt" Then Error.Raise("File errato !") | ||
<FONT color=gray>' ''Legge il tipo di formato (legge 2 byte):''</font> | <FONT color=gray>' ''Legge il tipo di formato (legge 2 byte):''</font> |
Versione delle 04:23, 21 ott 2015
Per eseguire un file audio con le sole risorse dell'API di Alsa sono sufficienti sostanzialmente tre funzioni.
Si dovrà innanzitutto dichiarare la libreria di Alsa contenente le funzioni esterne che saranno utilizzate. Tale libreria è attualmente libasound.so.2.0.0, e che sarà in Gambas così dichiarata:
Library "libasound:2"
Si dovrà quindi aprire il subsistema PCM di Alsa e creare un handle per la sua gestione successiva. Questa operazione iniziale verrà compiuta con la funzione:
int snd_pcm_open(snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, int mode)
laddove i parametri rappresentano:
- **pcm: un puntatore ad un puntatore per la creazione dell'handle dell'interfaccia PCM;
- *name: una stringa che è il nome dell'handle, e che sarà inizialmente: "default";
- stream: un numero intero che è il tipo di flusso dati (SND_PCM_STREAM_PLAYBACK oppure SND_PCM_STREAM_CAPTURE);
- mode: un numero intero che è la modalità di apertura del subsistema PCM (SND_PCM_NONBLOCK oppure SND_PCM_ASYNC).
Questa funzione sarà così dichiarata in Gambas con la funzione Extern:
Private Extern snd_pcm_open(pcmP As Pointer, nome As String, stream As Integer, mode As Integer) As Integer
e nel codice sarà così invocata:
Private Const nomen As String = "default" Private Const SND_PCM_STREAM_PLAYBACK As Byte = 0 Private Const SND_PCM_NONBLOCK As Byte = 0 ...... ...... err = snd_pcm_open(VarPtr(handle), nomen, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)
La seconda funzione essenziale è:
int snd_pcm_set_params (snd_pcm_t *pcm, snd_pcm_format_t format, snd_pcm_access_t access, unsigned int channels, unsigned int rate, int soft_resample, unsigned int latency)
con la quale vengono impostati i parametri audio hardware e software.
In Gambas verrà così dichiarata:
Private Extern snd_pcm_set_params(pcmP As Pointer, formatB As Byte, accessB As Byte, channels As Integer, rate As Integer, soft_resample As Integer, latency As Integer) As Integer
e verrà così richiamata per essere utilizzata:
Private Const SND_PCM_ACCESS_RW_INTERLEAVED As Byte = 3 ...... ...... err = snd_pcm_set_params(handle, risolBit, SND_PCM_ACCESS_RW_INTERLEAVED, canali, frequenza, 1, 500000)
Il numero di canali e la frequenza del flusso dei dati audio verranno estrapolati dai dati stessi del file audio caricato. Nel caso di un file wav, ad esempio, tali dati sono contenuti nel primo blocco del file, ossia all'interno dei primi 44/46 dati.
La terza funzione esterna essenziale è:
snd_pcm_sframes_t snd_pcm_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size)
che scrive i dati audio, precedentemente raccolti dal file audio caricato (nel caso di un file wav essi sono contenuti nel secondo blocco di del file), nel subsistema PCM, e quindi così li invia ad Alsa.
In Gambas sarà così dichiarata:
Private Extern snd_pcm_writei(pcmP As Pointer, buffP As Pointer, uframes As Long) As Integer
laddove:
- pcmP: è l'handle per l'interfaccia PCM;
- buffP: è una variabile di tipo puntatore che contiene i dati da scrivere/inviare ad Alsa tramite l'handle del subsistema PCM. Questa variabile è un'area di memoria allocata della dimensione almeno pari alla quantità complessiva dei dati audio da inviare ad Alsa. Da sottolineare che questa variabile potrà, volendo, essere anche dichiarata come vettore (array) di tipo Byte[ ] che sarà ovviamente riempito dei dati audio da inviare ad Alsa.
- uframes: è un valore che rappresenta la quantità di dati, presenti nel buffer prima descritto, che devono essere inviati ad Alsa. Questo valore deve essere pari alla dimensione del buffer diviso la seguente operazione: risoluzione-bit / (8 / numero-canali).
La funzione sarà quindi richiamata in codice come segue:
frames = snd_pcm_writei(handle, buffP, BUFFER / (bits / (8 / canali)))
La costante UFRAMES, che rappresenta la quantità massima di dati per ciascun ciclo da da inviare al sub-sistema PCM, verrà istanziata con un valore molto basso, se si intende all'interno del ciclo medesimo mostrare il tempo trascorso dall'inizio dell'esecuzione del file audio wav. Nel caso in cui non si intende mostrare il tempo trascorso, il valore della costante UFRAMES può ovviamente assumere valori superiori.
Indice
Esempio pratico
Mostriamo di seguito un esempio di un semplice codice per eseguire un file WAV con un applicazione a riga di comando:
Private Const UFRAMES As Integer = 32 Private Const SND_PCM_STREAM_PLAYBACK As Byte = 0 Private Const SND_PCM_FORMAT_U8 As Byte = 1 Private Const SND_PCM_FORMAT_S16_LE As Byte = 2 Private Const SND_PCM_ACCESS_RW_INTERLEAVED As Byte = 3 Private Const SND_PCM_STATE_XRUN As Byte = 4 ' Fermato Library "libasound:2" ' int snd_pcm_open(snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, int mode) ' Opens a PCM. Private Extern snd_pcm_open(pcm As Pointer, nome As String, stream As Integer, mode As Integer) As Integer ' const char * snd_strerror (int errnum) ' Returns the message for an Error code. Private Extern snd_strerror(errnum As Integer) As String ' int snd_pcm_set_params (snd_pcm_t *pcm, snd_pcm_format_t format, snd_pcm_access_t access, unsigned int canali, unsigned int rate, int soft_resample, unsigned int latency) ' Set the hardware and software parameters in a simple way. Private Extern snd_pcm_set_params(pcm As Pointer, formatB As Byte, accessB As Byte, channels As Integer, rate As Integer, soft_resample As Integer, latency As Integer) As Integer ' snd_pcm_sframes_t snd_pcm_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size) ' Write interleaved frames to a PCM. Private Extern snd_pcm_writei(pcm As Pointer, buffer As Pointer, uframes As Long) As Integer ' int snd_pcm_status (snd_pcm_t *pcm, snd_pcm_status_t *status) ' Obtain status (runtime) information for PCM handle. Private Extern snd_pcm_status(pcm As Pointer, status As Pointer) As Integer ' int snd_pcm_close(snd_pcm_t *pcm) ' Close PCM handle. Private Extern snd_pcm_close(pcm As Pointer) As Integer Public Sub Main() Dim fileWAV, s As String Dim fl As File Dim buffA, buf As Byte[] Dim risolBit As Byte Dim handle As Pointer Dim err, frequenza, dim_buffer, frames, stato As Integer Dim fmt, pcm, canali, bits, dati As Short ' Carica un file audio Wav: fileWAV = "/percorso/del/file.wav" fl = Open fileWAV For Read ' Inizia i controlli di verifica del file caricato: Seek #fl, 12 Read #fl, s, 3 If s <> "fmt" Then Error.Raise("File errato !") ' Legge il tipo di formato (legge 2 byte): Seek #fl, fmt + 7 Read #fl, pcm If pcm <> 1 Then Error.Raise("Non è un file PCM !") ' Rileva il numero di canali (legge 2 byte): Read #fl, canali ' Rileva la frequenza di campionamento (legge 4 byte): Read #fl, frequenza ' Rileva la risoluzione del campionamento (legge 2 byte): Seek #fl, fmt + 21 Read #fl, bits ' Verifica la risoluzione in bit dei campioni audio del file Wav: Select Case bits Case 8 risolBit = SND_PCM_FORMAT_U8 Case 16 risolBit = SND_PCM_FORMAT_S16_LE Case Else Error.Raise("Risoluzione " & bits & " non prevista dal presente applicativo !") End Select Print "Numero canali: "; canali Print "Frequenza: Hz "; frequenza Print "Risoluzione: "; bits; " bit" ' I successivi byte rappresentano il blocco dei dati audio del campione: dati = InStr(inte$, "data") dim_buffer = Lof(fl) - (dati + 7) Print "Quantità dei dati audio: "; dim_buffer; " byte" ' Avendo queste informazioni generali, posiamo ricavare la durata in secondi del brano audio: Print "Durata del brano: "; CStr(Date(0, 0, 0, 0, 0, 0, (dim_buffer * 8) / (frequenza * bits * canali) * 1000)) Print ' Legge mediante "Seek" soltanto i dati audio da inviare successivamente all'interfaccia PCM di Alsa: buffA = New Byte[UFRAMES] Seek #fl, dati + 7 buffA.Read(fl, 0, buffA.Count) ' Finalmente vengono utilizzate le funzioni esterne essenziali di Alsa: err = snd_pcm_open(VarPtr(handle), "default", SND_PCM_STREAM_PLAYBACK, 0) If err < 0 Then Error.Raise("Errore nell'apertura del subsistema PCM: " & snd_strerror(err)) err = snd_pcm_set_params(handle, risolBit, SND_PCM_ACCESS_RW_INTERLEAVED, canali, frequenza, 1, 500000) If err < 0 Then Error.Raise("Errore nell'impostazione dei parametri audio: " & snd_strerror(err)) ' Scrive i dati nell'interfaccia PCM. While status < dim_buffer buffA.Read(fl, 0, 32) frames = snd_pcm_writei(handle, buffA.Data, UFRAMES / (bits / (8 / canali))) If frames < 0 Then Error.Raise("Errore nella funzione 'snd_pcm_writei()' !") ' La scrittura del tempo trascorso in console può determinare nei computer più lenti un errore nella funzione 'snd_pcm_writei()'. ' In tal caso sarà necessario eliminare la seguente linea di comando che scrive il tempo trascorso. Write #File.Out, "\rTempo trascorso: " & Date(0, 0, 0, 0, 0, 0, ((stato * 8) / obr) * 1000) status += UFRAMES Wend ' Esegue il ciclo finché lo status del PCM non è nella condizione "Fermato": Do ' Rileva lo status dell'interfaccia PCM: snd_pcm_status(handle, VarPtr(stato)) Loop Until stato = SND_PCM_STATE_XRUN ' Alla fine dell'esecuzione del file audio chiude il subsistema PCM ed il file: err = snd_pcm_close(handle) If err = 0 Then Print "Chiusura dell'interfaccia PCM: regolare." fl.Close End
Fermare l'esecuzione dei dati audio
Per arrestare l'esecuzione dei dati audio, si potrà utilizzare la seguente funzione di Alsa:
int snd_pcm_drop (snd_pcm_t * pcm)
In Gambas sarà così dichiarata:
Private Extern snd_pcm_drop(pcmP As Pointer) As Integer
ed utilizzata nella seguente maniera:
snd_pcm_drop(handle)
Se si chiama questa funzione snd_pcm_drop(), da un lato l'altra funzione snd_pcm_writei() restituisce un valore d'errore uguale a -77 |1|, dall'altro lo status dell'interfaccia PCM assume il valore 1 (identificatore: SND_PCM_STATE_SETUP). Pertanto potremo dichiarare il nuovo valore dello status come una costante:
Private Const SND_PCM_STATE_SETUP As Byte = 1
Di tali valori dovremo, dunque, tenere conto nel codice.
Se per arrestare l'esecuzione si utilizza, come prevedibile, un Button, poiché l'esecuzione provoca l'impossibilità di agire su eventuali oggetti posti sul Form, si dovrà impostare prima o dopo la funzione "snd_pcm_writei()" un Wait al minimo valore:
Wait 0.001
Il codice con Arresto dell'esecuzione dei dati audio
Mostriamo, di seguito, il precedente codice con l'aggiunta di un Button per attivare la funzione di Alsa di arresto dell'esecuzione dei dati audio:
Private Const SND_PCM_STREAM_PLAYBACK As Byte = 0 Private Const SND_PCM_ACCESS_RW_INTERLEAVED As Byte = 3 Private Const SND_PCM_FORMAT_U8 As Byte = 0 Private Const SND_PCM_FORMAT_S16_LE As Byte = 2 Private Const SND_PCM_FORMAT_S24_LE As Byte = 7 Private Const SND_PCM_STATE_XRUN As Byte = 4 ' Fermato Private Const SND_PCM_STATE_SETUP As Byte = 1 Private handle As Pointer Private finis As Boolean Library "libasound:2" ' int snd_pcm_open(snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, int mode) ' 'Apre il sub-sistema PCM. Private Extern snd_pcm_open(pcmP As Pointer, nome As String, stream As Integer, mode As Integer) As Integer ' const char * snd_strerror (int errnum) ' Returns the message For an Error code. Private Extern snd_strerror(errnum As Integer) As String ' int snd_pcm_set_params (snd_pcm_t *pcm, snd_pcm_format_t format, snd_pcm_access_t access, unsigned int canali, unsigned int rate, int soft_resample, unsigned int latency) ' Set the hardware and software parameters in a simple way. Private Extern snd_pcm_set_params(pcmP As Pointer, formatB As Byte, accessB As Byte, canali As Integer, rate As Integer, soft_resample As Integer, latency As Integer) As Integer ' snd_pcm_sframes_t snd_pcm_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size) ' Write interleaved frames to a PCM ' In questo esempio dichiariamo il secondo argomento come un vettore di tipo Byte: Private Extern snd_pcm_writei(pcmP As Pointer, buffA As Byte[], uframes As Long) As Integer ' int snd_pcm_status (snd_pcm_t *pcm, snd_pcm_status_t *status) ' Obtain status (runtime) information for PCM handle. Private Extern snd_pcm_status(pcmP As Pointer, statP As Pointer) As Integer ' int snd_pcm_drop (snd_pcm_t *pcm) ' Stop a PCM dropping pending frames. Private Extern snd_pcm_drop(pcmP As Pointer) As Integer ' int snd_pcm_close(snd_pcm_t *pcm) ' Chiude il sub-sistema PCM Private Extern snd_pcm_close(pcmP As Pointer) As Integer Public Sub Button1_Click() Dim percorsoWAV, inte$ As String Dim fl, fl2 As File Dim buffA, buf As Byte[] Dim risolBit As Byte Dim err, frequenza, quantitas, frames, status, somma_frames As Integer Dim fmt, pcm, canali, bits, da As Short Dim buffer As Short = 512 ' Carica un file audio Wav: percorsoWAV = "/percorso/del/file.wav" fl = Open percorsoWAV For Read buf = New Byte[](1024) buf.Read(fl, 0, buf.Count) inte$ = buf.ToString(0, buf.Count) ' Inizia i controlli di verifica del file caricato: fmt = InStr(inte$, "fmt ") If fmt = 0 Then Error.Raise("File errato !") Else ' Legge il tipo di formato (legge 2 byte): Seek #fl, fmt + 7 Read #fl, pcm If pcm <> 1 Then Error.Raise("Non è un file PCM !") ' Rileva il numero di canali (legge 2 byte): Read #fl, canali ' Rileva la frequenza di campionamento (legge 4 byte): Read #fl, frequenza ' Rileva la risoluzione del campionamento (legge 2 byte): Seek #fl, fmt + 21 Read #fl, bits Endif ' Verifica la risoluzione in bit dei campioni audio del file Wav: Select Case bits Case 8 risolBit = SND_PCM_FORMAT_U8 Case 16 risolBit = SND_PCM_FORMAT_S16_LE Case Else Error.Raise("Risoluzione " & bits & " non prevista dal presente applicativo !") End Select Print "Canali di uscita: "; canali Print "Frequenza: Hz "; frequenza Print "Risoluzione: "; bits; " bit" ' I successivi byte rappresentano il blocco dei dati audio del campione: da = InStr(inte$, "data") quantitas = Stat(percorsoWAV).Size - (da + 7) Print "Quantità dei dati audio: "; quantitas; " byte" ' Avendo queste informazioni generali, posiamo ricavare la durata in secondi del brano audio: Print "Durata del brano: "; CStr(Date(0, 0, 0, 0, 0, 0, (quantitas * 8) / (frequenza * bits * canali) * 1000)) ' Legge mediante "Seek" soltanto i dati audio da inviare successivamente all'interfaccia PCM di Alsa: buffA = New Byte[](buffer) Seek #fl, da + 7 ' Finalmente vengono utilizzate le funzioni esterne essenziali di Alsa: err = snd_pcm_open(VarPtr(handle), "default", SND_PCM_STREAM_PLAYBACK, 0) If err < 0 Then Error.Raise("Errore nell'apertura del subsistema PCM: " & snd_strerror(err)) err = snd_pcm_set_params(handle, risolBit, SND_PCM_ACCESS_RW_INTERLEAVED, canali, frequenza, 1, 500000) If err < 0 Then Error.Raise("Errore nell'impostazione dei parametri audio: " & snd_strerror(err)) fl2 = Open "/dev/stdout" For Write finis = False Do ' Carichiamo nella variabile vettore i dati audio: If (Lof(fl) - (da + 7)) - Seek(fl) < 512 Then buffer = (Lof(fl) - (da + 7)) - Seek(fl) buffA.Read(fl, 0, buffer) Wait 0.001 ' Scrive i dati nell'interfaccia PCM. ' In questo esempio il buffer dei dati è rappresentato da un variabile array di tipo "Byte". frames = snd_pcm_writei(handle, buffA, buffer / SizeOf(gb.Integer)) somma_frames += frames ' Mostriamo il trascorrere dei secondi sulla base dei frames letti e sino al termine del brano audio: Write #fl2, "\r" & Date(0, 0, 0, 0, 0, 0, (somma_frames * 8 * SizeOf(gb.Integer)) / (frequenza * bits * canali) * 1000) Loop Until frames = 0 Or finis ' Esegue il ciclo finché lo status del PCM non è nella condizione "Fermato" o uguale a "-77": Do ' Rileva lo status dell'interfaccia PCM: snd_pcm_status(handle, VarPtr(status)) Loop Until (status = SND_PCM_STATE_XRUN) Or (status = SND_PCM_STATE_SETUP) ' Alla fine dell'esecuzione del file audio chiude il file ed il subsistema PCM: fl2.Close fl.Close err = snd_pcm_close(handle) If err = 0 Then Print "\nChiusura dell'interfaccia PCM: regolare." End Public Sub Button2_Click() ' Arresta l'esecuzione Dim err As Integer err = snd_pcm_drop(handle) If err = 0 Then Print "\nEsecuzione fermata !" finis = True Else Error.Raise("Impossibile fermare l'esecuzione dei dati audio !") Endif End
Note
[1] il valore 77 nel file errno.h corrisponde all'identificatore: EBADFD /* File descriptor in bad state */.