Realizzare un misuratore di picco audio con le sole funzioni esterne di Alsa
Un misuratore di picco (Peack Meter) è un tipo di strumento di misura che indica visivamente il livello istantaneo di un segnale audio che lo attraversa (un misuratore di livello sonoro). Nella riproduzione sonora, il misuratore, che sia di picco o meno, è solitamente inteso come corrispondente all'intensità percepita di un particolare segnale.
Esempio in ambiente grafico
Mostriamo di seguito un misuratore di picco dei segnali audio digitali, usando alcune funzioni esterne del API del sistema sonoro ALSA, per catturare i segnali audio digitali, e una DrawingArea, sulla quale disegnare il misuratore di picco audio a sfumature di colore. [nota 1] [nota 2]
Private DrawingArea1 As DrawingArea Private Button1 As Button Private Button2 As Button Private Const SIZE As Long = 512 Private handle As Pointer Private $bStop As Boolean Private hVol As Float Private $hPic As Picture Library "libasound:2" Private Const SND_PCM_STREAM_CAPTURE As Byte = 1 Private Const SND_PCM_FORMAT_S16_LE As Byte = 2 Private Const SND_PCM_ACCESS_RW_INTERLEAVED As Byte = 3 ' int snd_pcm_open(snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, int mode) Private Extern snd_pcm_open(pcm As Pointer, name As String, _stream As Integer, mode As Integer) As Integer ' 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) Private Extern snd_pcm_set_params(pcm As Pointer, _format As Integer, _access As Integer, channels As Integer, rate As Integer, soft_resample As Integer, latency As Integer) As Integer ' snd_pcm_sframes_t snd_pcm_readi (snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size) Private Extern snd_pcm_readi(pcm As Pointer, buffer As Pointer, size As Long) As Long ' const char * snd_strerror (int errnum) Private Extern snd_strerror(errnum As Integer) As String ' int snd_pcm_close(snd_pcm_t *pcm) Private Extern snd_pcm_close(pcm As Pointer) Public Sub _New() With Me .W = 300 .H = 380 .Center End With With DrawingArea1 = New DrawingArea(Me) As "DrawingArea1" .W = 40 .H = 340 .X = (Me.W *0.5) - (.W * 0.5) .Y = 14 End With With Button1 = New Button(Me) As "Button1" .X = 42 .Y = 20 .W = 75 .H = 28 .Text = "Avvio" .Picture = Picture["icon:/22/microphone"] End With With Button2 = New Button(Me) As "Button2" .X = 176 .Y = 20 .W = 75 .H = 28 .Text = "Stop" .Picture = Picture["icon:/22/stop"] End With End Public Sub Button1_Click() Dim err, fc, channel, iVol, i As Integer Dim buffer As New Short[SIZE] ' Imposta la frequenza a 44.1 kHz: fc = 44100 ' Imposta i canali audio (1=mono, 2=stereo): channel = 2 ' Apre il subsistema "PCM" di ALSA: err = snd_pcm_open(VarPtr(handle), "default", SND_PCM_STREAM_CAPTURE, 0) If err < 0 Then Error.Raise("Errore ! " & snd_strerror(err)) ' Imposta i parametri PCM audio: err = snd_pcm_set_params(handle, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED, channel, fc, 1, 0) If err < 0 Then Error.Raise("Fehler beim Einstellen der PCM Parameter: " & snd_strerror(err)) $bStop = True While $bStop ' Funziona finché non viene premuto il tasto "Stop" ' Trasferimento dei dati nell'array abuffer: err = snd_pcm_readi(handle, buffer.Data, SIZE / 2) If err < 0 Then Error.Raise("Fehler bei der Audiodatenaufzeichnung " & snd_strerror(err)) ' Calcolo della somma dei valori del volume: For i = 0 To SIZE - 1 iVol += CInt(buffer[i]) Next hVol = iVol * 5 DrawingArea1.Refresh() iVol = 0 ' Attesa per poter effettuare inserimenti nel programma: Wait 0.005 Wend buffer.Clear() End Public Sub Button2_Click() $bStop = False snd_pcm_close(handle) ' Si assicura che il Puntatore "handle" non punti a un indirizzo di memoria rilevante, assegnandogli il valore predefinito "0": handle = 0 End Public Sub Form_Close() ' Se viene arbitrariamente chiuso il "Form" $bStop = False If handle > 0 Then snd_pcm_close(handle) End Public Sub DrawingArea1_Draw() Dim aColors As Integer[] = [Color.DarkGreen, Color.Green, Color.Yellow, Color.red] ' Scala di colori per le barre del volume Dim aPositions As Float[] = [0, 0.6, 0.75, 0.99, 1] ' Imposta la scala di sfumatura dei colori With Paint .Translate(3, DrawingArea1.H) ' Spostamento di 3 pixel a destra e in altezza dell'area di disegno .Scale(1, -1) ' Spostamento verso l'alto .Brush = Paint.LinearGradient(DrawingArea1.w / 2, 0, DrawingArea1.w / 2, DrawingArea1.H, aColors, aPositions) ' Imposta il rettangolo ove verrà disegnata la scala di sfumatura dei colori previsti: .Rectangle(0, 0, DrawingArea1.W - 5, DrawingArea1.H) .Fill() ' Max. Il valore del volume è 5 inferiore a DrawingArea1.H hVol = Abs(hVol / 2 ^ 15 / SIZE * DrawingArea1.H - 5) .Brush = .Color(Color.ButtonBackground) ' Rettangolo che espone solo l'area della barra del volume da visualizzare: .Rectangle(0, hvol, DrawingArea1.W, DrawingArea1.H) .Fill() .End End With End
Esempio con programma a riga di comando
Nel seguente esempio a riga di comando saranno mostrati in console in modo un po' approssimativo i picchi di entrambi i canali di un file wav.
A tal proposito si consiglia di estendere sufficientemente l'altezza della console dell'IDE di Gambas.
Private QUANTUM As Integer = 1024 Library "libasound:2.0.0" 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 ' 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_drain (snd_pcm_t *pcm) ' Stop a PCM preserving pending frames. Private Extern snd_pcm_drain(pcm 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 s As String Dim fl As File Dim handle As Pointer Dim buff As Short[] Dim err, rBit, frequenza, obr As Integer Dim stato, dim_dati, frames As Integer Dim tipo, canali, bits, dati, c As Short ' Carica un file audio Wav: s = "/percorso/del/file.wav" If InStr(File.Load(s), "RIFF") == 0 Then Error.Raise("Non è un file WAV !") fl = Open s For Read ' Legge il tipo di formato (legge 2 byte): Seek #fl, 20 Read #fl, tipo If tipo <> 1 Then fl.Close Error.Raise("Non è un file PCM !") Endif ' 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, 34 Read #fl, bits ' Verifica la risoluzione in bit dei campioni audio del file Wav: Select Case bits Case 8 rBit = SND_PCM_FORMAT_U8 Case 16 rBit = SND_PCM_FORMAT_S16_LE Case Else Error.Raise("Risoluzione " & bits & " non prevista dal presente applicativo !") End Select Print "File audio: "; s Print "Numero canali: "; canali obr = frequenza * bits * canali ' I successivi byte rappresentano il blocco dei dati audio grezzi del file: Seek #fl, 0 Read #fl, s, 256 dati = InStr(s, "data") dim_dati = Lof(fl) - (dati + 7) ' Avendo queste informazioni generali, posiamo ricavare la durata in secondi del brano audio: Print "Durata: "; Str(Time(0, 0, 0, ((dim_dati * 8) / obr) * 1000)) buff = New Short[QUANTUM] ' Legge mediante "Seek" soltanto i dati audio grezzi da inviare successivamente all'interfaccia PCM di Alsa: Seek #fl, dati + 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 fl.Close Error.Raise("Errore nell'apertura del subsistema PCM: " & snd_strerror(err)) Endif err = snd_pcm_set_params(handle, rBit, SND_PCM_ACCESS_RW_INTERLEAVED, canali, frequenza, 1, 500000) If err < 0 Then fl.Close Error.Raise("Errore nell'impostazione dei parametri audio: " & snd_strerror(err)) Endif c = bits / (8 / canali) ' Scrive i dati nell'interfaccia PCM. While stato < dim_dati buff.Read(fl, 0, QUANTUM / SizeOf(gb.Short)) frames = snd_pcm_writei(handle, buff.Data, QUANTUM / c) If frames < 0 Then Break ' Mostra in console e in rosso il tempo trascorso: Write "\e[5;0f\e[0mTempo trascorso: \e[32m" & Time(0, 0, 0, ((stato * 8) / obr) * 1000) ' Raccoglie i volori di picco per entrambi i canali da 4 punto diversi dei dati presenti nell'array. ' Il picco di ciascun canale sarà mostrato di colore diverso (rosso e blu). Write "\e[31m\e[7;0f]]" & String(Abs(buff[0]) \ 300, String.Chr(&2593)) & String(110, " ") ' [Nota 2] ' La riga appena prima stampata in console viene cancellata da una riga di caratteri "spazio", formata da un numero pari a: ' &h7FFF / 300 = 109,22 Write "\e[34m\e[9;0f]]" & String(Abs(buff[1]) \ 300, String.Chr(&2593)) & String(110, " ") Write "\e[31m\e[7;0f]]" & String(Abs(buff[128]) \ 300, String.Chr(&2593)) & String(110, " ") Write "\e[34m\e[9;0f]]" & String(Abs(buff[129]) \ 300, String.Chr(&2593)) & String(110, " ") Write "\e[31m\e[7;0f]]" & String(Abs(buff[172]) \ 300, String.Chr(&2593)) & String(110, " ") Write "\e[34m\e[9;0f]]" & String(Abs(buff[173]) \ 300, String.Chr(&2593)) & String(110, " ") Write "\e[31m\e[7;0f]]" & String(Abs(buff[216]) \ 300, String.Chr(&2593)) & String(110, " ") Write "\e[34m\e[9;0f]]" & String(Abs(buff[217]) \ 300, String.Chr(&2593)) & String(110, " ") Write "\e[31m\e[7;0f]]" & String(Abs(buff[256]) \ 300, String.Chr(&2593)) & String(110, " ") Write "\e[34m\e[9;0f]]" & String(Abs(buff[257]) \ 300, String.Chr(&2593)) & String(110, " ") Write "\e[31m\e[7;0f]]" & String(Abs(buff[298]) \ 300, String.Chr(&2593)) & String(110, " ") Write "\e[34m\e[9;0f]]" & String(Abs(buff[299]) \ 300, String.Chr(&2593)) & String(110, " ") Write "\e[31m\e[7;0f]]" & String(Abs(buff[340]) \ 300, String.Chr(&2593)) & String(110, " ") Write "\e[34m\e[9;0f]]" & String(Abs(buff[341]) \ 300, String.Chr(&2593)) & String(110, " ") Write "\e[31m\e[7;0f]]" & String(Abs(buff[384]) \ 300, String.Chr(&2593)) & String(110, " ") Write "\e[34m\e[9;0f]]" & String(Abs(buff[385]) \ 300, String.Chr(&2593)) & String(110, " ") stato += QUANTUM If dim_dati - stato < 1024 Then QUANTUM = dim_dati - stato Wend ' Va in chiusura: Write "\e[7;0f]]" & String(110, " ") Write "\e[9;0f]]" & String(110, " ") fl.Close snd_pcm_drain(handle) Print "\n\e[0mEsecuzione terminata !" ' Alla fine dell'esecuzione del file audio chiude il subsistema PCM ed il file: err = snd_pcm_close(handle) If err == 0 Then Print "\nChiusura dell'interfaccia PCM: regolare." End
Note
[1] Il codice è il risultato di diverse modifiche apportate a un progetto originale creato da gambi, membro del forum tedesco dei programmatori Gambas.
[2] Vedere anche la seguente pagina: Analizzare il valore RMS, il picco e il decay dei dati audio mediante gb.media