web-dev-qa-db-fra.com

Sous-échantillonnage d'un fichier audio wav

Je dois sous-échantillonner un fichier wav de 44100 Hz à 16 000 Hz sans utiliser de bibliothèques externes Python, donc de préférence wave et/ou audioop. J'ai essayé de changer simplement le les fichiers wav framerate à 16000 en utilisant la fonction setframerate mais cela ralentit simplement tout l'enregistrement. Comment puis-je simplement sous-échantillonner le fichier audio à 16 kHz et conserver la même longueur d'audio?

13
d3cr1pt0r

Vous pouvez utiliser la fonction load () de Librosa,

import librosa    
y, s = librosa.load('test.wav', sr=8000) # Downsample 44.1kHz to 8kHz

L'effort supplémentaire pour installer Librosa vaut probablement la tranquillité d'esprit.

Astuce: lors de l'installation de Librosa sur Anaconda, vous devez également installer ffmpeg , donc

pip install librosa
conda install -c conda-forge ffmpeg

Cela vous évite l'erreur NoBackendError ().

17
wafflecat

Merci à tous pour vos réponses. J'ai déjà trouvé une solution et cela fonctionne très bien. Voici toute la fonction.

def downsampleWav(src, dst, inrate=44100, outrate=16000, inchannels=2, outchannels=1):
    if not os.path.exists(src):
        print 'Source not found!'
        return False

    if not os.path.exists(os.path.dirname(dst)):
        os.makedirs(os.path.dirname(dst))

    try:
        s_read = wave.open(src, 'r')
        s_write = wave.open(dst, 'w')
    except:
        print 'Failed to open files!'
        return False

    n_frames = s_read.getnframes()
    data = s_read.readframes(n_frames)

    try:
        converted = audioop.ratecv(data, 2, inchannels, inrate, outrate, None)
        if outchannels == 1:
            converted = audioop.tomono(converted[0], 2, 1, 0)
    except:
        print 'Failed to downsample wav'
        return False

    try:
        s_write.setparams((outchannels, 2, outrate, 0, 'NONE', 'Uncompressed'))
        s_write.writeframes(converted)
    except:
        print 'Failed to write wav'
        return False

    try:
        s_read.close()
        s_write.close()
    except:
        print 'Failed to close wav files'
        return False

    return True
6
d3cr1pt0r

Vous pouvez utiliser resample dans scipy. C'est un peu un casse-tête à faire, car il y a une conversion de type à faire entre le bytestring natif en python et les tableaux nécessaires dans scipy. Il y a un autre casse-tête, car dans le module wave en Python, il n'y a aucun moyen de savoir si les données sont signées ou non (seulement si elles sont 8 ou 16 bits). Cela pourrait (devrait) fonctionner pour les deux, mais je ne l'ai pas testé .

Voici un petit programme qui convertit (non signé) 8 et 16 bits mono de 44,1 à 16. Si vous avez de la stéréo ou utilisez d'autres formats, cela ne devrait pas être si difficile à adapter. Modifiez les noms d'entrée/sortie au début du code. Je n'ai jamais pu utiliser les arguments de la ligne de commande.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  downsample.py
#  
#  Copyright 2015 John Coppens <[email protected]>
#  
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#  
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#  
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#  MA 02110-1301, USA.
#  
#

inwave = "sine_44k.wav"
outwave = "sine_16k.wav"

import wave
import numpy as np
import scipy.signal as sps

class DownSample():
    def __init__(self):
        self.in_rate = 44100.0
        self.out_rate = 16000.0

    def open_file(self, fname):
        try:
            self.in_wav = wave.open(fname)
        except:
            print("Cannot open wav file (%s)" % fname)
            return False

        if self.in_wav.getframerate() != self.in_rate:
            print("Frame rate is not %d (it's %d)" % \
                  (self.in_rate, self.in_wav.getframerate()))
            return False

        self.in_nframes = self.in_wav.getnframes()
        print("Frames: %d" % self.in_wav.getnframes())

        if self.in_wav.getsampwidth() == 1:
            self.nptype = np.uint8
        Elif self.in_wav.getsampwidth() == 2:
            self.nptype = np.uint16

        return True

    def resample(self, fname):
        self.out_wav = wave.open(fname, "w")
        self.out_wav.setframerate(self.out_rate)
        self.out_wav.setnchannels(self.in_wav.getnchannels())
        self.out_wav.setsampwidth (self.in_wav.getsampwidth())
        self.out_wav.setnframes(1)

        print("Nr output channels: %d" % self.out_wav.getnchannels())

        audio = self.in_wav.readframes(self.in_nframes)
        nroutsamples = round(len(audio) * self.out_rate/self.in_rate)
        print("Nr output samples: %d" %  nroutsamples)

        audio_out = sps.resample(np.fromstring(audio, self.nptype), nroutsamples)
        audio_out = audio_out.astype(self.nptype)

        self.out_wav.writeframes(audio_out.copy(order='C'))

        self.out_wav.close()

def main():
    ds = DownSample()
    if not ds.open_file(inwave): return 1
    ds.resample(outwave)
    return 0

if __name__ == '__main__':
    main()
2
jcoppens

Pour sous-échantillonner (également appelé décimer ) votre signal (cela signifie réduire le taux d'échantillonnage), ou suréchantillonner (augmenter le taux d'échantillonnage), vous devez interpoler entre vos données.

L'idée est que vous devez en quelque sorte dessiner une courbe entre vos points, puis prendre les valeurs de cette courbe au nouveau taux d'échantillonnage. C'est parce que vous voulez connaître les valeurs de l'onde sonore à un moment qui n'a pas été échantillonné, vous devez donc deviner cette valeur d'une manière ou d'une autre. Le seul cas où le sous-échantillonnage serait facile est lorsque vous divisez le taux d'échantillonnage par un entier $ k $. Dans ce cas, il vous suffit de prendre des seaux d'échantillons $ k $ et de ne garder que le premier. Mais cela ne répondra pas à votre question. Voir l'image ci-dessous où vous avez une courbe échantillonnée à deux échelles différentes.

Signal

Vous pouvez le faire à la main si vous comprenez le principe, mais je vous recommande fortement d'utiliser une bibliothèque. La raison en est que l'interpolation dans le bon sens n'est ni facile ni évidente.

Vous pouvez utiliser une interpolation linéaire (connecter des points avec une ligne) ou une interpolation binomiale (connecter trois points avec un morceau de polynôme) ou (parfois la meilleure pour le son) utiliser une transformée de Fourier et interpoler dans l'espace de fréquence. Puisque la transformation de Fourier n'est pas quelque chose que vous voulez réécrire à la main, si vous voulez un bon sous-échantillonnage/suréchantillonnage, voir l'image suivante pour deux courbes de suréchantillonnage en utilisant un algorithme différent de scipy. La fonction "rééchantillonnage" utilise une transformée de Fourier. Resampling scipy

J'étais en effet dans le cas où je chargeais un fichier wave 44100Hz et nécessitais des données échantillonnées à 48000Hz, j'ai donc écrit les quelques lignes suivantes pour charger mes données:

    # Imports
    from scipy.io import wavfile
    import scipy.signal as sps

    # Your new sampling rate
    new_rate = 48000

    # Read file
    sampling_rate, data = wavfile.read(path)

    # Resample data
    number_of_samples = round(len(data) * float(new_rate) / sampling_rate))
    data = sps.resample(data, number_of_samples)

Notez que vous pouvez également utiliser la méthode décimer dans le cas où vous ne faites que le sous-échantillonnage et que vous voulez quelque chose de plus rapide que Fourier.

1
Jeremy Cochoy

J'ai essayé d'utiliser Librosa mais pour certaines raisons, même après avoir donné la ligne y, s = librosa.load('test.wav', sr=16000) et librosa.output.write_wav(filename, y, sr), les fichiers audio ne sont pas enregistrés avec la fréquence d'échantillonnage donnée (16000, sous-échantillonnés à partir de 44 kHz). Mais pydub fonctionne bien. Une bibliothèque géniale par jiaaro, j'ai utilisé les commandes suivantes:

from pydub import AudioSegment as am
sound = am.from_file(filepath, format='wav', frame_rate=22050)
sound = sound.set_frame_rate(16000)
sound.export(filepath, format='wav')

Le code ci-dessus indique que le fichier que je lis avec un frame_rate de 22050 est changé en taux de 16000 et la fonction export écrase les fichiers existants avec ce fichier avec un nouveau frame_rate. Cela fonctionne mieux que librosa mais je cherche des moyens de comparer la vitesse entre deux paquets mais je ne l'ai pas encore compris car j'ai très moins de données !!!

Refernce: https://github.com/jiaaro/pydub/issues/232

1
Gowtham S