Traitement du signal

Le but de la séance est de supprimer le bruit d’un signal sonore non compressé (.wav).

/dist/signal_bruite.wav

Signal bruité

/dist/signal_clean.wav

Signal filtré

Structure d’un fichier wav (Windows Audio)

Les fichiers wav, dans leur généralités, sont un format étrange changeant du tout au tout en fonction des versions et du contenu (pas forcement audio). Pour simplifier nous ne regarderons que les fichiers wav audio non compressés.

Les fichiers wav audios sont toujours composé d’un en-tête principal, les 441 premiers octets du fichier. Parmi les informations contenues dans l’en-tête on retrouve :

  • Le magic2 RIFF
  • La taille du fichier moins 8 octets (mais comment a-t-on pu en arriver là)
  • La deuxième partie du magic WAVE (pour les fichiers audio)

Dans le cas des fichiers audio on retrouve entre autre :

  • Encore un magic: fmt
  • Le nombre de canaux (ici: 1 pour mono)
  • La fréquence de l’échantillonnage (ici: 11025)
  • Le nombre de bits utilisé pour le codage des échantillons (8, 16, 24 etc…). A noter que la représentation est ‘little endian’

Vous retrouverez une plus ample description ici.

Pour ce TP nous avons simplifié au maximum les choses en prenant : PCM (Pulse Code Modulation, comme pour les CD), 11025 Hz, mono, 8bits.

Trucs et astuces

Sans rentrer dans les détails, il est possible de visualiser la “courbe” assez simplement. Pour cela on peut utiliser le programme/langage GnuPlot qui sert, comme son nom l’indique, à faire des graphiques.

Lancer gnuplot : gnuplot

Afficher la courbe :

plot 'signal_bruite.wav' using :1 binary format='%char' skip=44 with lines

Comme on y voit rien, se limiter à afficher les 100 premiers points

set xrange [0:100]

# Puis on retape la ligne précendente (i.e., on appuye sur fleche haut)

Explication

plot <nom de ficher>
Affiche une courbe
using :1
Il n’y a qu’une série de donnée, et elle est en y.
binary
Par défaut gnuplot travaille avec des fichiers textes, ici on lui dit que le fichier est binaire. Attention en mode binaire il faut donner un format ! (En mode texte, il se débrouille relativement bien seul).
format='%char'
Le format. Ici on dit que les données sont encodés seulement sur 1 octet (signé). Remarque: GnuPlot utilise les même types que C
skip=44
On saute les 44 premiers (en-tête)
with lines
Les points sont reliés les uns aux autres par des lignes.

Travail

Pour ce TP, on s’aidera de ce squelette de programme. Il se peut que les deux premiers points du tp soient traités par le squelette, vérifiez à quel point.

  • Ouvrir le fichier bruité en lecture — le nom pourra être passé en paramètre, en cas d’absence on utilisera l’entrée standard. Pensez à fermer les fichiers.
  • Ouvrir le fichier destination en écriture. Même remarque que précédemment avec la sortie standard.
  • Pour lancer un programme avec son entré standard et sa sortie standard redirigés : ./mon_programme < fichier_entre > fichier_sortie

Comme nous utilisons pour ce TP la sortie standard, il va s’en dire que faire un printf n’est pas une bonne idée. Pourquoi ? Comment faire autrement ?

  • Bien que C99 autorise la déclaration de tableaux dont la taille est fixé par une variable (int n=20; char tab[n];), la taille des tableaux déclaré de cette manière doit être très petite3. Il est difficile (voire impossible) de garantir cette propriété avec des fichier externes. Nous allouerons donc, via malloc, l’espace mémoire nécessaire. Comment déterminer l’espace mémoire nécessaire ? Proposez au moins 2 solutions (n’écrivez pas le code correspondant, ayez juste les solutions en tête).
  • Lire l’en-tête (44 octets) du fichier source et l’écrire dans le fichier destination tel quel. Pourrait-on utiliser une structure pour lire représenter l’en-tête ? Peut on la lire d’un seul coup ?
  • Écrivez la fonction wavefile_length qui étant donné un en-tête (char *), extrait la taille du fichier dans l’en-tête. Note: Vous pourrez utiliser la fonction suivante extract_le2n().
  • Pour la suite nous aurons besoin d’avoir des valeurs doubles, même si les données originales sont 8 bits. Peut on lire directement dans un tableau de double ? Pourquoi ? Lire les données grâce à fgetc !
  • Écrire les données lues dans le fichier destination. Remarque: le fichier source et destination doivent dont maintenant être identiques. Pour s’en assurer: essayez diff -s fichier1 fichier2

En route vers le traitement du signal proprement dit.

  • Écrire la fonction mean3 qui, en fonction du signal d’entrée, calculera pour chaque point la moyenne de lui et de ces deux voisins. Remarque: Pour les extrémités on recopiera les valeurs tels quelles.
  • Écouter, voire voir, le signal. Il est mieux non ?
  • Plus généralement, on va traiter le signal par un filtre moyenneur, c’est à dire qu’on va effectuer une convolution discrète. Celle-ci est définie par la formule : \(y_n = \Sigma x_{n-k}h_k\) où \(x\) et \(y\) représentent respectivement les signaux d’entrée et sortie et \(h\) le filtre défini par \(2K+1\) échantillons. Comme précédemment pour la moyenne, le calcul n’est pas possible sur les bords. On conservera donc les valeurs d’origine. Écrire la fonction filter_signal calculant le signal de sortie en appliquant un filtre au signal d’entrée.
  • Transformez votre fonction moyenne pour qu’elle utilise maintenant la forme générique filter_signal. Pourquoi les deux courbes (fonction mean3 originale vs la version générique) différent.
  • Le filtre gaussien \(G\sigma\) est un moyenneur à support infini caractérisé par son paramètre \(\sigma\) et est défini par la formule suivante : \(G\sigma = \frac{1}{\sigma\sqrt{2\pi}}e^{-\frac{t^2}{2\sigma^2}}\). C’est un des filtres les plus utilisés. Pour l’utiliser de façon discrète, on l’échantillonne sur l’intervalle \([−3\sigma; 3\sigma]\) pour garantir la représentativité du filtre obtenu. D’autre part, il faut que la somme des échantillons soit égale a 1 pour assurer la conservation de la moyenne du signal traité. La fonction gaussienne vous est donnée, appliquez votre filtre (en prenant \(\sigma = 3\)).
  • Écoutez le signal produit.

  1. En fait l’en-tête audio fait 36 octets + l’en-tête du bloc de données (8) = 44. 

  2. Les numéros magiques, ou plus simplement magic, sont des données servant principalement à identifier les fichiers — accessoirement elles servent aussi des fois à détecter l’endianess. Elles sont généralement placé en début de fichier. Parmi les magics bien connus, on retrouve le MZ des fichier .exe sous windows, le cafe babe (en hexa) des fichiers java, etc. Liste des signatures les plus fréquentes. L’utilitaire file possède une BD de magics, ce qui lui permet de déterminer le type probable d’un fichier. 

  3. Allocation sur la pile (taille réduite 1Mo ou 2 Mo tout au plus pour tout le programme).