Tonaufnahme einlesen und Komprimieren

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
yakari9
User
Beiträge: 6
Registriert: Dienstag 7. November 2017, 17:22

Guten Abend miteinander

folgendes: Ich baue ein Messgerät mit einem Raspberry Pi. Am Raspberry Pi ist ein Piezosensor angeschlossen welcher an einem Objekt befestigt wird. Nun mache ich eine Tonaufnahme (arecord) und erstelle daraus ein wave-File. Als nächstes muss ich prüfen, ob überhaupt etwas "spannendes" aufgenommen wurde. Dazu soll überprüft werden, ob ein gewisser Amplituden-Wert überschritten wird. Falls nein, kann die Aufnahme wieder gelöscht werden. Falls der Wert erreicht wird sollen Alle Werte die diesen Wert überschritten haben mit ihrem "Timestamp"/Index in eine Datei gepeichert werden. Also so ala:
Sekunden | Wert
1.2 | 0.2
1.3 | 0.5

(Diese Datei muss nachher über eine langsame Netzwerkverbindung mit Datenlimit verschickt werden und somit sollen "uninteressante" Messungen gar nicht verschickt werden)

Ich bin aktuell so vorgegangen, dass ich mit arecord eine .wav erstellt habe und sie nachher über "sox" in eine .dat Datei umgewandelt habe. Nun soll diese .dat eingelesen werden und in einer Schleife eine Grenzwert geprüft werden.
Problem ist jetzt, dass dieses .dat mit open(datei.dat) als String eingelesen wird, mit ganz vielen Leerschlägen und ";" usw.

Beispiel (eben mit ganz vielen Leerschlägen vor und nach den Zahlen:

0.0013605442 0.00012207031
0.0014058957 0.00015258789
0.0014512472 0
0.0014965986 6.1035156e-05
0.0015419501 0.00021362305

Wie kann ich diese Strings nun in ein sinnvolles Zahlenformat formatieren, damit ich dann Grenzwerte prüfen kann?

Ich hoffe jemand versteht was ich gerne möchte und kann mir helfen.
Lieber Gruss
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@yakari9: beim Umwandeln dieser Strings in Zahlen via `float` sind Leerzeichen egal. Noch besser machst das Einlesen per `numpy.loadtxt`.
__deets__
User
Beiträge: 14542
Registriert: Mittwoch 14. Oktober 2015, 14:29

Statt einen solch mühseligen mehrschrittigen Prozess benutze pyaudio und arbeite direkt auf den angelieferten Buffern.
yakari9
User
Beiträge: 6
Registriert: Dienstag 7. November 2017, 17:22

Hallo

vielen Dank für die Antworten.
Ich habe mich jetzt mal mit dem pyaudio befasst.

Code: Alles auswählen

 
Testrecording_Dauer = int(Rate / Chunk)*Dauer
t1=time.time()
for i in range(0, Testrecording_Dauer): #Eine Sekunde aufnehmen und Schauen was abgeht...
	data = stream.read(CHUNK, exception_on_overflow = False)
	aframe = np.asarray(data)
	npframes = np.vstack((npframes,aframe))
t2=time.time()
print ("finished testrecording. Dauer: ", t2-t1)
Wenn ich jetzt 1 Senkunde aufnehme, dann ergibt die Rechnung t2-t1 ca 1.6-2.3 Sekunden!
Wenn ich jetzt 20 Sekunden aufnehme, dann ergibt die Rechnung t2-t1 ca. 33 Sekunden... Warum ist das so?

Danke für eure Hilfe und Gruss
__deets__
User
Beiträge: 14542
Registriert: Mittwoch 14. Oktober 2015, 14:29

das sieht etwas knoedelig aus. Schau dir mal PEP8 zu Namenskonvention in Python an. So ist das ein ziemliches Chaos und schlecht lesbar.

Welche Werte haben die einzelnen Variablen? Warum mal Chunk und mal CHUNK?
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

yakari9 hat geschrieben:Als nächstes muss ich prüfen, ob überhaupt etwas "spannendes" aufgenommen wurde. Dazu soll überprüft werden, ob ein gewisser Amplituden-Wert überschritten wird. Falls nein, kann die Aufnahme wieder gelöscht werden.
Also die Werte aus einem Numpy-Array, die einen bestimmten Wert überschreiten, kriegt man so:

Code: Alles auswählen

# 10 Zufallswerte als Beispiel
values = np.random.rand(10)
print(values)
print(values[values >= 0.5])
yakari9
User
Beiträge: 6
Registriert: Dienstag 7. November 2017, 17:22

Also, natürlich ist das CHUNK immer gleich geschrieben. Ich habe hier mal den ganzen Code eingefügt.

Es wird folgendes gemacht:
1. Importieren von Modulen
2. Variablen definieren
3. Jetzt kommt eine While Schleife, mit dieser soll eine kontinuierliche Ausführung des Programmes möglich sein
a) Testrecording starten (Kurze Aufnahme die prüft, ob ein Geräusch auftritt = Grenzwert überschritten wird)
Falls nicht, beginnt die while Schleife wieder von vorne...
b) sobald "Lärm" detektiert wird (= Grenzwert überschritten) kommt, startet die eigentliche Messung (hier Dauer 10Sekunden siehe Variable Messdauer). DIese funktioniert wiefolgt:
i.) wieder Aufnehmen wie punkt 3.a
ii.) Gerenzwert prüfen wie von "snafu" vorgeschlagen
iii.) Gefundene Überschreitungen mit Ihren Indizes in ein Array schreiben und abspeichern
iv.) je nach Wert der Variable "go" wird jetzt der Prozess wieder bei Punkt 3.a gestartet oder beendet
4. Wird der Prozess beendet, wird noch die Mess-Aufnahme als .wav gespeichert. Damit soll überprüft werden können ob das Programm funktioniert

Das ganze umwandeln, überprüfen, abspeichern und kopieren geht keine Sekunde! Die Aufnahme ist jedoch einfach immer langsamer als sie sollte. Die Dauer eines Testrecordings (welche 1s sein sollte) ist 1.6-2.3s
Die Dauer eines Messdurchgangs (welche 16s sein sollte) ist 16s

Komischerweise scheint der Faktor 1.6 irgendwie mitzuspielen. Die erstellte .wav Datei ist aber nicht um diesen Faktor verlängert!
Was meint ihr?


Code:

Code: Alles auswählen

import pyaudio
import wave
import time
import sys
import numpy as np
import utility
from shutil import copyfile

### Aufnahmevariablen ###
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 44100
CHUNK = 1024
RECORD_SECONDS = 1
WAVE_OUTPUT_FILENAME = "record.wav"
go=0
audio = pyaudio.PyAudio()
###-----###

### stream definieren ###
stream = audio.open(format=FORMAT, channels=CHANNELS,
	rate=RATE, input=True, input_device_index=2,
	frames_per_buffer=CHUNK)
###-----### 

##### Messvariablen ###########
Grenzwert=0.002
Testrecording_Dauer = 1 #Sekunden
Messdauer = 10 #Sekunden
###-----###


while go<2:

### Testrecording ###
	npframes=np.array([0]) # np.Array für die Aufnahme generieren
	print("Testrecording...")
	t1=time.time()
	for i in range(0, int(RATE / CHUNK * Testrecording_Dauer)): #Eine Sekunde aufnehmen und Schauen was abgeht...
		data = stream.read(CHUNK, exception_on_overflow = False)
		#frames.append(data)
		aframe = np.asarray(data)
		npframes = np.vstack((npframes,aframe))
	t2=time.time()
	print ("finished testrecording. Dauer: ", t2-t1)
### Daten umwandeln ###
	sig = np.frombuffer(npframes, dtype='<i2').reshape(-1, CHANNELS) #Das Signal wird konvertiert
	normalized = utility.pcm2float(sig, np.float32) #und normalisiert
### Überprüfen ob der Grenzwert überschritten wurde ###
	for i in range(0, len(normalized)):
		if normalized[i] > Grenzwert:
			go = 1 #Falls ja(go=1), wird das Flag go=1 gesetzt und die Schleife abgebrochen
			break
### Messung - Wird gestartet wenn ein Geräusch detektiert wurde ###
	if go == 1: #Falls go=1 dann soll eine lange Messung durchgeführt werden. Falls nicht werden die Testmessungen weitergeführt
		print("Messung starten")
		go=0 #Zurücksetzen des Go-Flags = nach dem Messdurchgang wieder mit Testrecording beginnen
		t1=time.time()
		frames=[]
		for i in range(0, int(RATE / CHUNK * Messdauer)): #Messdauer Sekunden lang messen
			data = stream.read(CHUNK, exception_on_overflow = False)
			aframe = np.asarray(data)
			npframes = np.vstack((npframes,aframe))
		t2=time.time()
		print ("finished recording. Dauer: ", t2-t1)
		frameswav=npframes #Variable für das Abspeichern der wav-Datei
### Daten umwanden ###
		sig = np.frombuffer(npframes, dtype='<i2').reshape(-1, CHANNELS)
		normalized = utility.pcm2float(sig, np.float32) 
		go=2 # go=2 Messung nur einmal durchführen und anschliessend Programm beenden
		t8=time.time()
### Grenzwerte prüfen ###
		overgrenzwert=normalized[normalized >= Grenzwert] # Array mit allen Werten über dem Grenzwert
		overgrenzwert = np.transpose(overgrenzwert)
		indexes=np.nonzero(normalized >= Grenzwert) # Indexes der Werten über dem Grenzwert
		indexes = np.transpose(indexes)
		indexes = np.delete(indexes, 1,1)
		compressed = np.stack((indexes,overgrenzwert[:, None]),axis=1) # Zusammenführen der Arrays
### Datei speichern
		np.savetxt('compressed.dat',compressed,delimiter=';') #Speichern der Datei "Compressed.dat"
		copyfile('compressed.dat', '/home/shares/test/compressed.dat') # Kopieren in Share-Ordner
		print("Das hat gedauert, und zwar: ", time.time()-t1," Sekunden")

#stop Recording
stream.stop_stream()
stream.close()
audio.terminate()
 
### Wave Datei zur Überprüfung speichern 
waveFile = wave.open(WAVE_OUTPUT_FILENAME, 'wb')
waveFile.setnchannels(CHANNELS)
waveFile.setsampwidth(audio.get_sample_size(FORMAT))
waveFile.setframerate(RATE)
frameswav.tolist()
waveFile.writeframes(b''.join(frameswav))
waveFile.close()
copyfile(WAVE_OUTPUT_FILENAME, "/home/shares/test/wavaufnahme.wav")
yakari9
User
Beiträge: 6
Registriert: Dienstag 7. November 2017, 17:22

Hmm.. habe den Wert von CHUNK auf 512 reduziert... jetzt dauert eine 20-Sekunden-Messung, inkl. Grenzwerten prüfen, speichern und verschieben 21.2Sekunden.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Wozu brauchst du eigentlich vstack()? Falls du die Frames hintereinander weg schreiben willst, dann ist dafür hstack() gut geeignet:

Code: Alles auswählen

data = np.hstack(
    stream.read(chunk_size) for _ in range(num_chunks)
)
Aber grundsätzlich würde ich die Daten überhaupt nicht gesammelt speichern, sondern sofort verarbeiten.

Hier mal grob als Generator umgesetzt:

Code: Alles auswählen

def get_chunks(stream, chunk_size, num_chunks):
    for _ in range(num_chunks):
        yield np.asarray(stream.read(chunk_size))

def filter_chunks(stream):
    for chunk in get_chunks(...):
        if is_interesting(chunk):
            yield chunk
Oder gleich mit dem passenden Python-Builtin:

Code: Alles auswählen

chunks = filter(is_interesting, get_chunks(stream, chunk_size, num_chunks))
yakari9
User
Beiträge: 6
Registriert: Dienstag 7. November 2017, 17:22

Hallo zusammen

das funktioniert übrigens sehr gut!

Jetzt steht die nächste Herausforderung an. Ich möchte ein zweites Mikrofon hinzufügen. Da meine USB-Soundkarte nur einen Mono-Eingang hat, habe ich mir eine zweite gekauft... nun ist die Frage ob es eine Möglichkeit gibt, paralell von 2 Soundkarten aufzunehmen...?

Ich habe mal einen Hinweis auf pyjack gefunden... das Ding ist aber nicht Dokumentiert und so für mich nicht einsetzbar...

Jemand eine Idee...

Irgendwie sowas wie

Code: Alles auswählen

audio = pyaudio.PyAudio()
stream = [audio.open(input_device_index=1),audio.open(input_device_index=2)]
data =np.hstack(stream.read(CHUNK) for in range (num_CHUNK)
geht nicht, oder?

Lieber Gruss
__deets__
User
Beiträge: 14542
Registriert: Mittwoch 14. Oktober 2015, 14:29

Fuer mehrere Devices wirst du ueber callbacks gehen muessen, denn jedes von denen hat seine eigene Zeitbasis & ggf. andere Aspekte wie zB Buffergroesse etc.

Eine Alternative waere ein Audio-Device das mehrere Kanaele unterstuetzt, die bekommst du dann in einem Rutsch, und bist auch synchron.
yakari9
User
Beiträge: 6
Registriert: Dienstag 7. November 2017, 17:22

MIt callbacks?
Dann kriege ich sie aber nich parallel zum laufen, oder? also, dass ich dann verschiedene Tonspuren, welche gleichzeitig aufgenommen wurden, vergleichen kann... ?
Gruss
__deets__
User
Beiträge: 14542
Registriert: Mittwoch 14. Oktober 2015, 14:29

Doch, eben genau das bekommst du dann. Du oeffnest zwei Geraete, und bekommst fuer jedes einen Rueckruf wenn es einen Audio-Buffer fuer dich hat. Den kannst du zB in eine Queue pappen, und in deinem Haupthread Buffer beider Devices verarbeiten.
Antworten