Réparer un fichier .wav trop long ou comment réussir sa captation audio en 24/7
Sommaire
RIFF WAVE est un format de fichier sonore simple à étudier, largement utilisé et bien documenté. Il possède quelques limitations dont une taille maximum de fichier. Certains programmes d'enregistrement ne se formalisent pas de cette contrainte et peuvent générer des fichiers corrompus. Étudions ce cas particulier en examinnt leur entête et essayons de les réparer.
Les signaux de l'espace
En cette semaine du 21 juin 2021, la Station spatiale internationale 🛰️ via son programme radio amateur ARISS 📡 émet en continu une série de 12 photos 🖼️ rendant hommage aux astronautes de l'ISS, Mir et Shuttle. On peut s'amuser à les collectionner à la manière des albums Panini. Les images sont transmises via le protocole SSTV 📺. Chaque transmission dure 2 minutes 🐌, alternée de 2 minutes de pause. Sachant que la station 🛰️ vole à 400km au dessus de nos têtes et à 28000km/h 🚀 (d'ouest en est) on ne reçoit le signal que durant 6 à 8 minutes par passage, et encore uniquement quand les conditions d'élévation et de météo sont satisfaisantes ☀️. On peut anticiper la trajectoire et les passages via des sites comme Heavens Above ou le logiciel gpredict. Le signal est émis sur la fréquence 145.800MHz (± effet doppler), on peut le recevoir avec une clé rtl-sdr et un logiciel de réception / démodulation FM des signaux RF comme GQRX.
Une des méthodes d'enregistrement est de lancer le programme juste avant le passage et de l'arrêter juste après, mais pour celà il faut être présent à l'instant T. Une autre méthode est d'enregistrer le signal en continu, il y aura 99% de bruit blanc mais on est assuré de ne louper aucun passage. On traite l'enregistrement après coup quand on est disponible. La conséquence est que si le programme ne scinde pas l'enregistrement à intervalle régulier, on peut se retrouver avec un énorme fichier .wav de taille potentiellement infinie.
Voici un exemple de réception/enregistrement d'un signal SSTV et de son décodage laissant apparaitre l'image transmise.
Vous avez maintenant le contexte, on a enregistré une longue pige dans un fichier unique et on veut le traiter.
Le problème de la lecture d'un gros fichier
Après une captation nocturne, réalisée avec le logiciel GQRX, je me retrouve avec un fichier .wav
🔈 de 7Go 🐘 .
17111568168 gros-fichier.wav
Je connais ses caractéristiques :
- stéréo (2 pistes)
- 16 bits de résolution
- 48000 Hz de fréquence d'échantillonage
Le format RIFF WAVE
est linéaire et sans compression. Après une "entête" de quelques octets, les échantillons sont ajoutés au fur et à mesure en fin de fichier dans le "bloc de données". Un compteur dans l'entête stockant la taille du bloc de données est alors incrémenté. S'il y a plusieurs pistes, les échantillons sont entrelacés G / D / G / D ...
Format simplifié d'un fichier .wav :
1| entête | ...données... |
Il est possible de calculer la taille théorique du fichier à partir de sa durée d'enregistrement et ses caractéristiques, et réciproquement. Faisons le calcul pour estimer la durée à partir de la taille du fichier (on néglige la taille de l'entête).
17111568168 # taille du fichier .wav en octets (7Go)
2/ 2 # 2 pistes (stéréo)
3/ 2 # 16 bits par échantillon (2 octets)
4/ 48000 # nombre d'échantillons par seconde
5/ 60 # conversion des secondes en minutes
6/ 60 # conversion des minutes en heures
7= 10.29h
Notre fichier .wav
fait donc théoriquement plus de 10h, ce qui correspond à la durée entre laquelle j'ai débuté et terminé l'enregistrement, bref rien d'anormal.
Essayons maintenant d'ouvrir le fichier avec Audacity
Saperlipopette 🤦, le son est coupé après environ 4 heures ! Et évidemment c'était les dernières heures d'enregistrement qui m'intéressaient. vlc
, mpv
, ffprobe
, tous ces outils de confiance me disent la même chose: le fichier fait environ 4 heures. Alors ? Notre enregistrement est définitivement perdu ? Pas si sur 🕵️♂️ ...
Lançons la commande hexdump -C gros-fichier.wav
pour voir si le fichier était réellement vide après 4 heures. Ça ne semble pas le cas. On remarque au passage que les 2 pistes gauche et droite sont rigoureusement identiques. Voici un extrait du dump, il est intéressant car on peut voir que chaque échantillon de 16 bits (ex: 06 4c
) est en double. On aurait pu enregistrer en mono plutôt que stéréo pour gagner 50% d'espace et/ou doubler le temps d'enregistrement sans corruption de données...
100165450 06 4c 06 4c fc 16 fc 16 ce 12 ce 12 13 e9 13 e9 |.L.L............|
200165460 17 82 17 82 00 80 00 80 2a 9e 2a 9e 2f ad 2f ad |........*.*././.|
300165470 92 b9 92 b9 30 c2 30 c2 05 bf 05 bf ab 94 ab 94 |....0.0.........|
400165480 d3 82 d3 82 f9 c0 f9 c0 03 fc 03 fc 0a 0a 0a 0a |................|
500165490 62 02 62 02 b6 6c b6 6c ff 7f ff 7f ff 7f ff 7f |b.b..l.l........|
Je me souviens alors que le champ de l'entête d'un fichier RIFF WAVE
stockant la taille en octets du bloc de données est codé sur 32 bits, ce qui signifie qu'on peut y stocker une valeur entre 0 et 4294967295. Le bloc de données ne peut donc dépasser 4Go ! 😨 Je passe mon fichier .wav
à la moulinette de mon parser maison qui décode l'entête :
1$ WAVEDump.php gros-fichier.wav
2array(2) {
3 ["FileSize"]=>
4 int(2816600872)
5 ["RealFileSize"]=>
6 int(7111568168)
7}
Il y a bien une incohérence. Les 2 valeurs devraient être identiques. L'hypothèse la plus probable est donc bien que gqrx l'enregistreur n'a pas incrémenté le compteur arrivé à un certain stade, ce qui a corrompu le fichier.
Couper / Regénérer les entêtes
La technique de réparation est la suivante:
- On coupe ✂️ aux ciseaux le fichier en plusieurs morceaux de 2Go
- On supprime 🧹 l'entête du 1er morceau
- On regénère les entêtes 🏷 de tous les morceaux
avant le découpage :
1| gros-fichier.wav |
2| entête | données... |
La commande pour couper les fichiers en blocs de 2Go :
1split -b 2147483648 gros-fichier.wav decoupe-
Voici les tailles et noms des fichiers après découpage :
12147483648 decoupe-aa
22147483648 decoupe-ab
32147483648 decoupe-ac
4 669117224 decoupe-ad
La somme des tailles des 4 fichiers est bien la taille du fichier initial. Par ailleurs si on lance la commande suivante, on va retrouver le même fichier qu'à l'origine :
1cat decoupe-aa decoupe-ab decoupe-ac decoupe-ad > gros-fichier-réassemblé.wav
Oui mais la taille du bloc de données étant dans l'entête, il faudra recalculer le compteur de l'entête de decoupe-aa
. Les 3 autres fichiers n'étant plus que des données brutes (format dit raw
ou pcm
), il faudra regénérer leurs entêtes pour les re-rendre valide au sens .wav
.
Retirons l'entête de petit-fichier-aa
(conversion wav -> pcm) et renommons le fichier en .pcm
1ffmpeg -i decoupe-aa -f s16le -acodec pcm_s16le decoupe-aa.pcm
Renommons les nouveaux fichiers en .pcm
pour plus de cohérence car ce ne sont plus que des données brutes
1mv decoupe-ab decoupe-ab.pcm
2mv decoupe-ac decoupe-ac.pcm
3mv decoupe-ad decoupe-ad.pcm
Examinons à nouveau la taille des fichiers, on remarque que le 1er fichier ayant perdu son entête a aussi perdu 44 octets, et cela correspond à la taille (minimale) d'une entête RIFF WAVE.
12147483604 decoupe-aa.pcm
22147483648 decoupe-ab.pcm
32147483648 decoupe-ac.pcm
4 669117224 decoupe-ad.pcm
Maintenant regénérons toutes les entêtes (conversion pcm -> wav) avec les caractéristiques de l'enregistrement d'origine
1ffmpeg -f s16le -ar 48k -ac 2 -i decoupe-aa.pcm decoupe-aa.wav
2ffmpeg -f s16le -ar 48k -ac 2 -i decoupe-ab.pcm decoupe-ab.wav
3ffmpeg -f s16le -ar 48k -ac 2 -i decoupe-ac.pcm decoupe-ac.wav
4ffmpeg -f s16le -ar 48k -ac 2 -i decoupe-ad.pcm decoupe-ad.wav
Nous avons désormais 4 fichiers .wav
de 2Go max et parfaitement valides et lisibles partout. Recheckons la cohérence des entêtes :
1$ WAVEDump.php decoupe-aa.wav
2array(2) {
3 ["FileSize"]=>
4 int(2147483682)
5 ["RealFileSize"]=>
6 int(2147483682)
7}
8
9$ WAVEDump.php decoupe-ab.wav
10array(2) {
11 ["FileSize"]=>
12 int(2147483726)
13 ["RealFileSize"]=>
14 int(2147483726)
15}
16
17$ WAVEDump.php decoupe-ac.wav
18array(2) {
19 ["FileSize"]=>
20 int(2147483726)
21 ["RealFileSize"]=>
22 int(2147483726)
23}
24
25$ WAVEDump.php decoupe-ad.wav
26array(2) {
27 ["FileSize"]=>
28 int(669117302)
29 ["RealFileSize"]=>
30 int(669117302)
31}
Fin de l'histoire
Avant de mettre votre enregistrement à la poubelle par ce qu'il n'est pas lisible, essayez de comprendre où se situe la panne. Si le format est libre et ouvert (ou au moins documenté), vous trouverez surement des outils pour les analyser.
La conclusion de tout ceci est que mon album panini n'est pas complet même après une semaine de captation, mais que la prochaine fois je serai prêt à capter les piges en continu, et avec découpage horaire des fichiers captés 😎.