Détecter et enregistrer l'audio en Python

99

J'ai besoin de capturer des clips audio sous forme de fichiers WAV que je peux ensuite passer à un autre bit de python pour le traitement. Le problème est que je dois déterminer quand il y a de l'audio, puis l'enregistrer, arrêter quand il devient silencieux, puis transmettre ce fichier au module de traitement.

Je pense qu'il devrait être possible avec le module wave de détecter quand il y a un silence pur et de le rejeter, dès que quelque chose d'autre que le silence est détecté, commencez l'enregistrement, puis lorsque la ligne se tait à nouveau, arrêtez l'enregistrement.

Je ne peux pas vraiment comprendre, quelqu'un peut-il me lancer avec un exemple de base.

SilentGhost
la source

Réponses:

106

Pour faire suite à la réponse de Nick Fortescue, voici un exemple plus complet de la façon d'enregistrer à partir du microphone et de traiter les données résultantes:

from sys import byteorder
from array import array
from struct import pack

import pyaudio
import wave

THRESHOLD = 500
CHUNK_SIZE = 1024
FORMAT = pyaudio.paInt16
RATE = 44100

def is_silent(snd_data):
    "Returns 'True' if below the 'silent' threshold"
    return max(snd_data) < THRESHOLD

def normalize(snd_data):
    "Average the volume out"
    MAXIMUM = 16384
    times = float(MAXIMUM)/max(abs(i) for i in snd_data)

    r = array('h')
    for i in snd_data:
        r.append(int(i*times))
    return r

def trim(snd_data):
    "Trim the blank spots at the start and end"
    def _trim(snd_data):
        snd_started = False
        r = array('h')

        for i in snd_data:
            if not snd_started and abs(i)>THRESHOLD:
                snd_started = True
                r.append(i)

            elif snd_started:
                r.append(i)
        return r

    # Trim to the left
    snd_data = _trim(snd_data)

    # Trim to the right
    snd_data.reverse()
    snd_data = _trim(snd_data)
    snd_data.reverse()
    return snd_data

def add_silence(snd_data, seconds):
    "Add silence to the start and end of 'snd_data' of length 'seconds' (float)"
    silence = [0] * int(seconds * RATE)
    r = array('h', silence)
    r.extend(snd_data)
    r.extend(silence)
    return r

def record():
    """
    Record a word or words from the microphone and 
    return the data as an array of signed shorts.

    Normalizes the audio, trims silence from the 
    start and end, and pads with 0.5 seconds of 
    blank sound to make sure VLC et al can play 
    it without getting chopped off.
    """
    p = pyaudio.PyAudio()
    stream = p.open(format=FORMAT, channels=1, rate=RATE,
        input=True, output=True,
        frames_per_buffer=CHUNK_SIZE)

    num_silent = 0
    snd_started = False

    r = array('h')

    while 1:
        # little endian, signed short
        snd_data = array('h', stream.read(CHUNK_SIZE))
        if byteorder == 'big':
            snd_data.byteswap()
        r.extend(snd_data)

        silent = is_silent(snd_data)

        if silent and snd_started:
            num_silent += 1
        elif not silent and not snd_started:
            snd_started = True

        if snd_started and num_silent > 30:
            break

    sample_width = p.get_sample_size(FORMAT)
    stream.stop_stream()
    stream.close()
    p.terminate()

    r = normalize(r)
    r = trim(r)
    r = add_silence(r, 0.5)
    return sample_width, r

def record_to_file(path):
    "Records from the microphone and outputs the resulting data to 'path'"
    sample_width, data = record()
    data = pack('<' + ('h'*len(data)), *data)

    wf = wave.open(path, 'wb')
    wf.setnchannels(1)
    wf.setsampwidth(sample_width)
    wf.setframerate(RATE)
    wf.writeframes(data)
    wf.close()

if __name__ == '__main__':
    print("please speak a word into the microphone")
    record_to_file('demo.wav')
    print("done - result written to demo.wav")
cryo
la source
17
Pour que cela fonctionne dans Python 3, remplacez simplement xrange par range.
Ben Elgar
1
Excellent exemple! Vraiment utile lorsque j'ai essayé de comprendre comment enregistrer la voix en utilisant Python. Une question rapide que j'ai eue est de savoir s'il existe un moyen de définir la période de temps de l'enregistrement. Maintenant, il enregistre un mot? Puis-je jouer avec et avoir une période d'enregistrement de 10 secondes par exemple? Merci!
Swan87
La détection et la normalisation ne sont pas correctes, car elles calculent sur des octets et non sur des courts-circuits. Ce tampon devrait être converti en un tableau numpy avant le traitement.
ArekBulski
Ni xrangeni rangeétait vraiment nécessaire dans add_silence(donc c'est parti maintenant). Je pense qu'Arek est peut-être sur quelque chose ici - la transition du silence au «mot» semble trop saccadée. Je pense qu'il y a d'autres réponses qui y répondent également.
Tomasz Gandor le
47

Je crois que le module WAVE ne prend pas en charge l'enregistrement, mais uniquement le traitement des fichiers existants. Vous voudrez peut-être regarder PyAudio pour enregistrer réellement. WAV est le format de fichier le plus simple au monde. Dans paInt16, vous obtenez juste un entier signé représentant un niveau, et plus proche de 0 est plus silencieux. Je ne me souviens pas si les fichiers WAV sont en premier octet haut ou octet bas, mais quelque chose comme ça devrait fonctionner (désolé, je ne suis pas vraiment un programmeur python:

from array import array

# you'll probably want to experiment on threshold
# depends how noisy the signal
threshold = 10 
max_value = 0

as_ints = array('h', data)
max_value = max(as_ints)
if max_value > threshold:
    # not silence

Code PyAudio pour l'enregistrement conservé pour référence:

import pyaudio
import sys

chunk = 1024
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 44100
RECORD_SECONDS = 5

p = pyaudio.PyAudio()

stream = p.open(format=FORMAT,
                channels=CHANNELS, 
                rate=RATE, 
                input=True,
                output=True,
                frames_per_buffer=chunk)

print "* recording"
for i in range(0, 44100 / chunk * RECORD_SECONDS):
    data = stream.read(chunk)
    # check for silence here by comparing the level with 0 (or some threshold) for 
    # the contents of data.
    # then write data or not to a file

print "* done"

stream.stop_stream()
stream.close()
p.terminate()
Nick Fortescue
la source
Merci Nick, Oui, j'aurais dû dire que j'utilise également portaudio pour la capture, le bit sur lequel je suis bloqué est la vérification du silence, comment obtenir le niveau dans le bloc de données?
J'ai ajouté un code très simple non testé ci-dessus, mais il devrait faire le travail que vous voulez
Nick Fortescue
Ma version précédente avait un bug, ne traitait pas correctement le signe. J'ai utilisé la fonction de bibliothèque array () pour analyser correctement maintenant
Nick Fortescue
Le format de fichier WAV est un conteneur, il peut contenir de l'audio encodé via divers codecs (comme GSM ou MP3), certains loin d'être «les plus simples au monde».
Jacek Konieczny
2
Je crois que l'option "output = True" lors de l'ouverture du flux n'est pas nécessaire pour l'enregistrement et en outre, elle semble provoquer "IOError: [Errno Input overflowed] -9981" sur mon appareil. Sinon merci pour l'exemple de code, c'était super utile.
Binus
19

Merci à cryo pour la version améliorée sur laquelle j'ai basé mon code testé ci-dessous:

#Instead of adding silence at start and end of recording (values=0) I add the original audio . This makes audio sound more natural as volume is >0. See trim()
#I also fixed issue with the previous code - accumulated silence counter needs to be cleared once recording is resumed.

from array import array
from struct import pack
from sys import byteorder
import copy
import pyaudio
import wave

THRESHOLD = 500  # audio levels not normalised.
CHUNK_SIZE = 1024
SILENT_CHUNKS = 3 * 44100 / 1024  # about 3sec
FORMAT = pyaudio.paInt16
FRAME_MAX_VALUE = 2 ** 15 - 1
NORMALIZE_MINUS_ONE_dB = 10 ** (-1.0 / 20)
RATE = 44100
CHANNELS = 1
TRIM_APPEND = RATE / 4

def is_silent(data_chunk):
    """Returns 'True' if below the 'silent' threshold"""
    return max(data_chunk) < THRESHOLD

def normalize(data_all):
    """Amplify the volume out to max -1dB"""
    # MAXIMUM = 16384
    normalize_factor = (float(NORMALIZE_MINUS_ONE_dB * FRAME_MAX_VALUE)
                        / max(abs(i) for i in data_all))

    r = array('h')
    for i in data_all:
        r.append(int(i * normalize_factor))
    return r

def trim(data_all):
    _from = 0
    _to = len(data_all) - 1
    for i, b in enumerate(data_all):
        if abs(b) > THRESHOLD:
            _from = max(0, i - TRIM_APPEND)
            break

    for i, b in enumerate(reversed(data_all)):
        if abs(b) > THRESHOLD:
            _to = min(len(data_all) - 1, len(data_all) - 1 - i + TRIM_APPEND)
            break

    return copy.deepcopy(data_all[_from:(_to + 1)])

def record():
    """Record a word or words from the microphone and 
    return the data as an array of signed shorts."""

    p = pyaudio.PyAudio()
    stream = p.open(format=FORMAT, channels=CHANNELS, rate=RATE, input=True, output=True, frames_per_buffer=CHUNK_SIZE)

    silent_chunks = 0
    audio_started = False
    data_all = array('h')

    while True:
        # little endian, signed short
        data_chunk = array('h', stream.read(CHUNK_SIZE))
        if byteorder == 'big':
            data_chunk.byteswap()
        data_all.extend(data_chunk)

        silent = is_silent(data_chunk)

        if audio_started:
            if silent:
                silent_chunks += 1
                if silent_chunks > SILENT_CHUNKS:
                    break
            else: 
                silent_chunks = 0
        elif not silent:
            audio_started = True              

    sample_width = p.get_sample_size(FORMAT)
    stream.stop_stream()
    stream.close()
    p.terminate()

    data_all = trim(data_all)  # we trim before normalize as threshhold applies to un-normalized wave (as well as is_silent() function)
    data_all = normalize(data_all)
    return sample_width, data_all

def record_to_file(path):
    "Records from the microphone and outputs the resulting data to 'path'"
    sample_width, data = record()
    data = pack('<' + ('h' * len(data)), *data)

    wave_file = wave.open(path, 'wb')
    wave_file.setnchannels(CHANNELS)
    wave_file.setsampwidth(sample_width)
    wave_file.setframerate(RATE)
    wave_file.writeframes(data)
    wave_file.close()

if __name__ == '__main__':
    print("Wait in silence to begin recording; wait in silence to terminate")
    record_to_file('demo.wav')
    print("done - result written to demo.wav")
Eugène
la source
merci, fonctionne très bien. Dans mon cas, je dois modifier return copy.deepcopy(data_all[_from:(_to + 1)])àcopy.deepcopy(data_all[int(_from):(int(_to) + 1)])
lukassliacky
6
import pyaudio
import wave
from array import array

FORMAT=pyaudio.paInt16
CHANNELS=2
RATE=44100
CHUNK=1024
RECORD_SECONDS=15
FILE_NAME="RECORDING.wav"

audio=pyaudio.PyAudio() #instantiate the pyaudio

#recording prerequisites
stream=audio.open(format=FORMAT,channels=CHANNELS, 
                  rate=RATE,
                  input=True,
                  frames_per_buffer=CHUNK)

#starting recording
frames=[]

for i in range(0,int(RATE/CHUNK*RECORD_SECONDS)):
    data=stream.read(CHUNK)
    data_chunk=array('h',data)
    vol=max(data_chunk)
    if(vol>=500):
        print("something said")
        frames.append(data)
    else:
        print("nothing")
    print("\n")


#end of recording
stream.stop_stream()
stream.close()
audio.terminate()
#writing to file
wavfile=wave.open(FILE_NAME,'wb')
wavfile.setnchannels(CHANNELS)
wavfile.setsampwidth(audio.get_sample_size(FORMAT))
wavfile.setframerate(RATE)
wavfile.writeframes(b''.join(frames))#append frames recorded to file
wavfile.close()

Je pense que cela aidera. C'est un simple script qui vérifiera s'il y a un silence ou non. Si un silence est détecté, il n'enregistrera pas sinon il enregistrera.

deenu
la source
3

Le site Web pyaudio contient de nombreux exemples assez courts et clairs: http://people.csail.mit.edu/hubert/pyaudio/

Mise à jour du 14 décembre 2019 - Exemple principal du site Web lié ci-dessus à partir de 2017:


"""PyAudio Example: Play a WAVE file."""

import pyaudio
import wave
import sys

CHUNK = 1024

if len(sys.argv) < 2:
    print("Plays a wave file.\n\nUsage: %s filename.wav" % sys.argv[0])
    sys.exit(-1)

wf = wave.open(sys.argv[1], 'rb')

p = pyaudio.PyAudio()

stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
                channels=wf.getnchannels(),
                rate=wf.getframerate(),
                output=True)

data = wf.readframes(CHUNK)

while data != '':
    stream.write(data)
    data = wf.readframes(CHUNK)

stream.stop_stream()
stream.close()

p.terminate()
Simjega
la source
0

Vous voudrez peut-être aussi regarder csounds . Il a plusieurs API, y compris Python. Il pourra peut-être interagir avec une interface AD ​​et collecter des échantillons sonores.

S.Lott
la source