119 votes

Lecture de fichiers *.wav en Python

J'ai besoin d'analyser un son écrit dans un fichier .wav. Pour cela, je dois transformer ce fichier en un ensemble de nombres (des tableaux, par exemple). Je pense que je dois utiliser le paquetage wave. Cependant, je ne sais pas comment il fonctionne exactement. Par exemple, j'ai fait ce qui suit :

import wave
w = wave.open('/usr/share/sounds/ekiga/voicemail.wav', 'r')
for i in range(w.getnframes()):
    frame = w.readframes(i)
    print frame

Grâce à ce code, je m'attendais à ce que la pression acoustique soit fonction du temps. En revanche, je vois un grand nombre de symboles étranges et mystérieux (qui ne sont pas des nombres hexadécimaux). Quelqu'un peut-il, s'il vous plaît, m'aider à résoudre ce problème ?

6voto

vokimon Points 66

Si vous souhaitez traiter un fichier audio bloc par bloc, certaines des solutions proposées sont assez horribles dans la mesure où elles impliquent de charger l'intégralité du fichier audio en mémoire, ce qui entraîne de nombreuses absences de cache et ralentit votre programme. python-fichier d'ondes fournit des constructions pythoniques pour effectuer des traitements NumPy bloc par bloc en utilisant une gestion efficace et transparente des blocs au moyen de générateurs. D'autres fonctionnalités pythoniques sont le gestionnaire de contexte pour les fichiers, les métadonnées en tant que propriétés... et si vous voulez l'interface fichier complète, parce que vous développez un prototype rapide et que vous ne vous souciez pas de l'efficacité, l'interface fichier complète est toujours là.

Un exemple simple de traitement serait le suivant :

import sys
from wavefile import WaveReader, WaveWriter

with WaveReader(sys.argv[1]) as r :
    with WaveWriter(
            'output.wav',
            channels=r.channels,
            samplerate=r.samplerate,
            ) as w :

        # Just to set the metadata
        w.metadata.title = r.metadata.title + " II"
        w.metadata.artist = r.metadata.artist

        # This is the prodessing loop
        for data in r.read_iter(size=512) :
            data[1] *= .8     # lower volume on the second channel
            w.write(data)

L'exemple réutilise le même bloc pour lire l'ensemble du fichier, même dans le cas du dernier bloc qui est généralement inférieur à la taille requise. Dans ce cas, vous obtenez une tranche du bloc. Faites donc confiance à la longueur du bloc retourné au lieu d'utiliser une taille de 512 codée en dur pour tout traitement ultérieur.

2voto

Si vous devez effectuer des transferts sur les données de la forme d'onde, vous devriez peut-être utiliser SciPy En particulier, il s'agit de scipy.io.wavfile .

2voto

Voici une solution Python 3 utilisant le module wave intégré [1], qui fonctionne pour n canaux, et 8,16,24... bits.

import sys
import wave

def read_wav(path):
    with wave.open(path, "rb") as wav:
        nchannels, sampwidth, framerate, nframes, _, _ = wav.getparams()
        print(wav.getparams(), "\nBits per sample =", sampwidth * 8)

        signed = sampwidth > 1  # 8 bit wavs are unsigned
        byteorder = sys.byteorder  # wave module uses sys.byteorder for bytes

        values = []  # e.g. for stereo, values[i] = [left_val, right_val]
        for _ in range(nframes):
            frame = wav.readframes(1)  # read next frame
            channel_vals = []  # mono has 1 channel, stereo 2, etc.
            for channel in range(nchannels):
                as_bytes = frame[channel * sampwidth: (channel + 1) * sampwidth]
                as_int = int.from_bytes(as_bytes, byteorder, signed=signed)
                channel_vals.append(as_int)
            values.append(channel_vals)

    return values, framerate

Vous pouvez transformer le résultat en un tableau NumPy.

import numpy as np

data, rate = read_wav(path)
data = np.array(data)

J'ai essayé de faire en sorte que ce soit plus lisible que rapide. J'ai constaté que la lecture de toutes les données en une seule fois était presque deux fois plus rapide. Par exemple

with wave.open(path, "rb") as wav:
    nchannels, sampwidth, framerate, nframes, _, _ = wav.getparams()
    all_bytes = wav.readframes(-1)

framewidth = sampwidth * nchannels
frames = (all_bytes[i * framewidth: (i + 1) * framewidth]
            for i in range(nframes))

for frame in frames:
    ...

Bien que python-soundfile est environ 2 ordres de grandeur plus rapide (il est difficile d'approcher cette vitesse avec CPython pur).

[1] https://docs.python.org/3/library/wave.html

2voto

Rubem Pacelli Points 49

Ma chère, si j'ai bien compris ce que vous recherchez, vous vous lancez dans un domaine théorique appelé Digital Signal Processing (DSP). Ce domaine d'ingénierie va de la simple analyse de signaux à temps discret à des filtres adaptatifs complexes. Une bonne idée est de considérer les signaux à temps discret comme un vecteur, où chaque élément de ce vecteur est une valeur échantillonnée du signal original à temps continu. Une fois que vous avez obtenu les échantillons sous forme de vecteur, vous pouvez appliquer différentes techniques de signaux numériques à ce vecteur.

Malheureusement, sous Python, le passage des fichiers audio aux tableaux NumPy est assez lourd, comme vous pouvez le constater... Si vous n'idolâtrez pas un langage de programmation plutôt qu'un autre, je vous suggère fortement d'essayer MatLab/Octave. Matlab facilite l'accès aux échantillons à partir de fichiers. audioread() vous confie cette tâche :) Et il existe de nombreuses boîtes à outils conçues spécifiquement pour le DSP.

Néanmoins, si vous avez vraiment l'intention de vous mettre à Python pour cela, je vais vous donner un pas-à-pas pour vous guider.


1. Obtenir les échantillons

La façon la plus simple d'obtenir les échantillons de la base de données .wav est :

from scipy.io import wavfile

sampling_rate, samples = wavfile.read(f'/path/to/file.wav')

Vous pouvez également utiliser la fonction wave y struct pour obtenir les échantillons :

import numpy as np
import wave, struct

wav_file = wave.open(f'/path/to/file.wav', 'rb')
# from .wav file to binary data in hexadecimal
binary_data = wav_file.readframes(wav_file.getnframes())
# from binary file to samples
s = np.array(struct.unpack('{n}h'.format(n=wav_file.getnframes()*wav_file.getnchannels()), binary_data))

Réponse à votre question : binary_data est un bytes qui n'est pas lisible par l'homme et ne peut avoir de sens que pour une machine. Vous pouvez valider cette déclaration en tapant type(binary_data) . Si vous voulez vraiment en savoir un peu plus sur cette bande de personnages bizarres, cliquez sur aquí .

Si votre audio est stéréo (c'est-à-dire qu'il a 2 canaux), vous pouvez remodeler ce signal pour obtenir le même format que celui obtenu avec scipy.io

s_like_scipy = s.reshape(-1, wav_file.getnchannels())

Chaque colonne est un canal. Dans les deux cas, les échantillons obtenus à partir des .wav peut être utilisé pour tracer et comprendre le comportement temporel du signal.

Dans les deux cas, les échantillons obtenus à partir des fichiers sont représentés dans le fichier Modulation linéaire par code d'impulsion (LPCM)


2. Effectuer un traitement numérique du signal sur les échantillons audio

Je vous laisse décider de cette partie :) Mais c'est un beau livre pour vous guider dans la DSP. Malheureusement, je ne connais pas de bons livres sur Python, ce sont généralement des livres horribles... Mais ne vous inquiétez pas, la théorie peut être appliquée de la même manière en utilisant n'importe quel langage de programmation, tant que vous maîtrisez ce langage.

Quel que soit le livre que vous choisirez, tenez-vous en aux auteurs classiques, tels que Proakis, Oppenheim, et ainsi de suite... Ne vous souciez pas du langage de programmation qu'ils utilisent. Pour un guide plus pratique de DPS pour l'audio utilisant Python, voir cette page.

3. Lire les échantillons audio filtrés

import pyaudio

p = pyaudio.PyAudio()
stream = p.open(format = p.get_format_from_width(wav_file.getsampwidth()),
                channels = wav_file.getnchannels(),
                rate = wav_file.getframerate(),
                output = True)
# from samples to the new binary file
new_binary_data = struct.pack('{}h'.format(len(s)), *s)
stream.write(new_binary_data)

wav_file.getsampwidth() est le nombre d'octets par échantillon, et wav_file.getframerate() est le taux d'échantillonnage. Il suffit d'utiliser les mêmes paramètres que ceux de l'audio d'entrée.


4. Enregistrer le résultat dans un nouveau .wav fichier

wav_file=wave.open('/phat/to/new_file.wav', 'w')

wav_file.setparams((nchannels, sampwidth, sampling_rate, nframes, "NONE", "not compressed"))

for sample in s:
   wav_file.writeframes(struct.pack('h', int(sample)))

nchannels est le nombre de canaux, sampwidth est le nombre d'octets par échantillon, sampling_rate est le taux d'échantillonnage, nframes est le nombre total d'échantillons.

1voto

ProgJos Points 11

J'avais besoin de lire un fichier WAV 24 bits à 1 canal. Le post ci-dessus par Nak a été très utile. Toutefois, comme l'a mentionné plus haut basj Le 24 bits n'est pas simple. J'ai finalement réussi à le faire fonctionner en utilisant l'extrait suivant :

from scipy.io import wavfile
TheFile = 'example24bit1channelFile.wav'
[fs, x] = wavfile.read(TheFile)

# convert the loaded data into a 24bit signal

nx = len(x)
ny = nx/3*4    # four 3-byte samples are contained in three int32 words

y = np.zeros((ny,), dtype=np.int32)    # initialise array

# build the data left aligned in order to keep the sign bit operational.
# result will be factor 256 too high

y[0:ny:4] = ((x[0:nx:3] & 0x000000FF) << 8) | \
  ((x[0:nx:3] & 0x0000FF00) << 8) | ((x[0:nx:3] & 0x00FF0000) << 8)
y[1:ny:4] = ((x[0:nx:3] & 0xFF000000) >> 16) | \
  ((x[1:nx:3] & 0x000000FF) << 16) | ((x[1:nx:3] & 0x0000FF00) << 16)
y[2:ny:4] = ((x[1:nx:3] & 0x00FF0000) >> 8) | \
  ((x[1:nx:3] & 0xFF000000) >> 8) | ((x[2:nx:3] & 0x000000FF) << 24)
y[3:ny:4] = (x[2:nx:3] & 0x0000FF00) | \
  (x[2:nx:3] & 0x00FF0000) | (x[2:nx:3] & 0xFF000000)

y = y/256   # correct for building 24 bit data left aligned in 32bit words

Une mise à l'échelle supplémentaire est nécessaire si vous avez besoin de résultats entre -1 et +1. Peut-être que certains d'entre vous trouveront cela utile.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X