mercoledì 7 dicembre 2011

Un semplice programma in C per leggere i file RIFF WAVE

Il file Wave è senza dubbio il formato più semplice per memorizzare un file audio; essendo non compresso, non prevede particolari algoritmi per la codifica e quindi molto veloce. Di contro c'è che occupa molta memoria e quindi è poco maneggevole.

Prima di mettere un po' di codice, vediamo intanto di capire il formato RIFF Wave, in particolare capire come è strutturato.

La struttura

Il file Wave si compone di un header a cui fanno seguito i dati veri e propri dell'audio (chunks). L'header, di grandezza variabile, si compone a sua volta di tre parti: RIFF, fmt e data. Vediamoli separatamente:

1)RIFF
E' grande 12 byte, di cui i primi 4 sono le lettere ASCII di "RIFF", i seguenti 4 byte (Chunk Size) rappresentano la grandezza in byte di tutto il file compresi gli header.
Gli ultimi 4 byte sono le lettere ASCII di "WAVE".
Sintetizzando:

|R|I|F|F|    |?|?|?|?|      |W|A|V|E|

Dove a ogni singola casella corrisponde un byte.

2)fmt

I primi 4 byte di questo blocco sono i caratteri ASCII di "fmt " (compreso lo spazio).
A seguire ci sono 4 byte per il SubChunk1Size, che sarebbe la grandezza del blocco fmt esclusi questi primi 8 byte. Per i file Microsoft Wave, il valore di questo campo è 16 byte che analizzeremo qui appresso.

2 byte per AudioFormat, ovvero il Formato Audio.
2 byte per NumChannels, ovvero il numero di canali.
4 byte per SampleRate, ovvero la frequenza di campionamento.
4 byte per ByteRate, ovvero la frequenza di byte (byte/s)
2 byte per BlockAlign, ovver l'allineamento del blocco, che ci permette di leggere la giusta quantità di byte nei dati raw
2 byte per BitsPerSample, ovvero il numero di bit che usiamo per rappresentare un singolo campione.

Sintetizzando:

|f|m|t| |   |?|?|?|?|   |?|?|   |?|?|   |?|?|?|?|   |?|?|?|?|   |?|?|   |?|?|

3)data
Queste sono le informazioni finali che chiudono l'header.
I primi 4 byte sono i caratteri ASCII "data", i seguenti 4 byte rappresentano la grandezza in byte di tutto il blocco data (quindi anche i campioni).
Finalmente, adesso ci saranno file interminabili di byte che rappresentano i campioni.


Come viene rappresentato un campione?
Un campione non è altro che un numero che se viene rappresentato in un diagramma tempo-valore(di questo numero) insieme agli altri campioni forma un'onda. L'onda è più approssimata quanto più questi campioni sono "vicini" tra di loro, quindi, quanto più è maggiore la frequenza di campionamento vista poco fa (SampleRate).
Un singolo campione può essere a 8 bit o a 16 bit, quindi i suoi valori possono "spaziare" tra 0 e 127 o tra 0 e 65535: l'informazione che ci dice quanti bit vengono utilizzati per un campione è il BitPerSample già visto. 

Un po' di codice
Bene, era ora...posto un programmino che non fa altro che caricare un file wav e leggere il suo header e stampare i risultati a schermo. Utile per capire meglio il formato wave. Nota: il programma non legge affatto i campioni, comunque più avanti posterò anche un programma che legge i campioni e li esporta in formato csv per poi magari rappresentarli con un foglio di calcolo e vedere l'onda, o magari inviarli alla scheda audio e praticamente: riprodurre un file wave!
Ecco qui il codice:


#include <stdio.h>
#include <stdint.h>


struct T_RIFF {
       uint8_t ChunkID[4];
       uint32_t ChunkSize;
       uint8_t Format[4];
};


struct T_fmt {
       uint8_t Subchunk1ID[4];
       uint32_t Subchunk1Size;
       uint16_t AudioFormat;
       uint16_t NumChannels;
       uint32_t SampleRate;
       uint32_t ByteRate;
       uint16_t BlockAlign;
       uint16_t BitsPerSample;
};
struct T_data_nosamples {
       uint8_t Subchunk2ID[4];
       uint32_t Subchunk2Size;
};
struct T_data {
       uint8_t Subchunk2ID[4];
       uint32_t Subchunk2Size;
};
struct T_WAVE {
       struct T_RIFF RIFFheader;
       struct T_fmt fmtheader;
       struct T_data dataheader;
};


main() {
       struct T_WAVE WAVE;
       printf("Inserisci il percorso del file: ");
       char percorso[50];
       scanf("%s",percorso);
       FILE* stream = fopen(percorso,"r");
       if (stream==NULL)
          printf ("Il file non esiste! \n");
       else {
         
           fread(&WAVE,1,44,stream);
           uint32_t Samplesize, Sample;
           printf ("Grandezza blocco: %d byte\n\n",WAVE.RIFFheader.ChunkSize);
           printf ("Grandezza blocco fmt: %d byte\n",WAVE.fmtheader.Subchunk1Size);
           printf ("Formato audio: %d\n",WAVE.fmtheader.AudioFormat);
           printf ("Numero di canali: %d\n",WAVE.fmtheader.NumChannels);
           printf ("Frequenza di campionamento: %d Hz\n",WAVE.fmtheader.SampleRate);
           printf ("Frequenza di byte: %d byte/s\n",WAVE.fmtheader.ByteRate);
           printf ("Allineamento blocco: %d\n",WAVE.fmtheader.BlockAlign);
           printf ("Bits per campione: %d\n\n",WAVE.fmtheader.BitsPerSample);
           printf ("Grandezza blocco data: %d byte\n\n",WAVE.dataheader.Subchunk2Size);
           float durata =  (float) WAVE.dataheader.Subchunk2Size/WAVE.fmtheader.ByteRate;
           int durataAppr = WAVE.dataheader.Subchunk2Size/WAVE.fmtheader.ByteRate;
           printf ("Durata in secondi: %d s\n",durataAppr);
           uint32_t SampleSize = WAVE.fmtheader.BitsPerSample / 8;
           uint32_t numcampioni = (uint32_t) durata * WAVE.fmtheader.SampleRate;
           //processa16bit(WAVE.fmtheader.NumChannels,SampleSize,numcampioni,stream);
           fclose(stream);
       }
}


Mi ripeto: commentate se ci sono errori, o anche per consigli. Bye!
Alberto

Nessun commento:

Posta un commento