Seite 1 von 1

Mikrofon überwachen und wiedergeben

Verfasst: Freitag 27. November 2020, 12:20
von MpOnE
Guten Mittag liebe Python Freunde,

hab mal wieder ein neues Problem unzwar wenn ich diesen Code ausführe:

Code: Alles auswählen

from io import BytesIO
import pyaudio
import math
import struct
import wave
import time
import os

Threshold = 10

SHORT_NORMALIZE = (1.0 / 32768.0)
chunk = 1024
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 16000
swidth = 2

TIMEOUT_LENGTH = 5

f_name_directory = r'./records'


class Recorder:

    @staticmethod
    def rms(frame):
        count = len(frame) / swidth
        format = "%dh" % (count)
        shorts = struct.unpack(format, frame)

        sum_squares = 0.0
        for sample in shorts:
            n = sample * SHORT_NORMALIZE
            sum_squares += n * n
        rms = math.pow(sum_squares / count, 0.5)

        return rms * 1000

    def __init__(self):
        self.p = pyaudio.PyAudio()
        self.stream = self.p.open(format=FORMAT,
                                  channels=CHANNELS,
                                  rate=RATE,
                                  input=True,
                                  output=True,
                                  frames_per_buffer=chunk)

    def record(self):
        print('Noise detected, recording beginning')
        rec = []
        current = time.time()
        end = time.time() + TIMEOUT_LENGTH

        while current <= end:

            data = self.stream.read(chunk)
            if self.rms(data) >= Threshold: end = time.time() + TIMEOUT_LENGTH

            current = time.time()
            rec.append(data)
        # self.write(b''.join(rec))
        self._play(b''.join(rec))

    def write(self, recording):
        n_files = len(os.listdir(f_name_directory))

        filename = os.path.join(f_name_directory, '{}.wav'.format(n_files))

        wf = wave.open(filename, 'wb')
        wf.setnchannels(CHANNELS)
        wf.setsampwidth(self.p.get_sample_size(FORMAT))
        wf.setframerate(RATE)
        wf.writeframes(recording)
        wf.close()
        print('Written to file: {}'.format(filename))
        print('Returning to listening')

    def _play(self, recording):
        # open the file for reading.
        wf = wave.open(BytesIO(recording), 'rb')
        wf.setnchannels(CHANNELS)
        wf.setsampwidth(self.p.get_sample_size(FORMAT))
        wf.setframerate(RATE)

        # read data (based on the chunk size)
        data = wf.readframes(chunk)

        # play stream (looping from beginning of file to the end)
        print('Play listened')
        while data != '':
            # writing to the stream is what *actually* plays the sound.
            self.p.write(data)
            data = wf.readframes(chunk)

        # cleanup stuff.
        self.p.close()
        self.p.terminate()

    def listen(self):
        print('Listening beginning')
        while True:
            input = self.stream.read(chunk)
            rms_val = self.rms(input)
            if rms_val > Threshold:
                self.record()


x = Recorder()

x.listen()
Bekomme ich leider nur eine Fehlermeldung:

Code: Alles auswählen

 File "./test2.py", line 112, in <module>
    x.listen()
  File "./test2.py", line 107, in listen
    self.record()
  File "./test2.py", line 64, in record
    self._play(b''.join(rec))
  File "./test2.py", line 82, in _play
    wf = wave.open(BytesIO(recording), 'rb')
  File "/usr/lib/python3.7/wave.py", line 510, in open
    return Wave_read(f)
  File "/usr/lib/python3.7/wave.py", line 164, in __init__
    self.initfp(f)
  File "/usr/lib/python3.7/wave.py", line 131, in initfp
    raise Error('file does not start with RIFF id')
wave.Error: file does not start with RIFF id
Weis jemandworan dies liegt bzw könnte mir weiterhelfen?
Bin für jede Hilfe dankbar.

MfG
MpOnE

Re: Mikrofon überwachen und wiedergeben

Verfasst: Freitag 27. November 2020, 13:19
von __blackjack__
@MpOnE: Das liegt daran das PyAudio rohe Audiodaten liefert und keine WAV-Datei.

Re: Mikrofon überwachen und wiedergeben

Verfasst: Freitag 27. November 2020, 13:52
von MpOnE
Wie kann ich dies ändern?
Ich möchte verhindern für jede Aufnahme eine WAV zu schreiben ist nicht grade HDD freundlich und deswegen wollte ich dies direkt über die Variable ausgeben.

Re: Mikrofon überwachen und wiedergeben

Verfasst: Freitag 27. November 2020, 14:31
von __deets__
Wenn du schon Sample-Daten hast, musst du sie doch nicht versuchen, als Wave-File zu behandeln, aus dem du dann auch nichts anderes als wieder Sample-Daten bekommst.

Re: Mikrofon überwachen und wiedergeben

Verfasst: Freitag 27. November 2020, 14:58
von MpOnE
__deets__ hat geschrieben: Freitag 27. November 2020, 14:31 Wenn du schon Sample-Daten hast, musst du sie doch nicht versuchen, als Wave-File zu behandeln, aus dem du dann auch nichts anderes als wieder Sample-Daten bekommst.
Heist also das wave.open weglassen und direkt abspielen? Oder wie darf ich das verstehen?

Re: Mikrofon überwachen und wiedergeben

Verfasst: Freitag 27. November 2020, 15:21
von __deets__
So darfst du das verstehen.

Re: Mikrofon überwachen und wiedergeben

Verfasst: Sonntag 29. November 2020, 14:27
von MpOnE
__deets__ hat geschrieben: Freitag 27. November 2020, 15:21 So darfst du das verstehen.
Ich find die Lösung vor lauter zeichen nicht, hab nun etliche Beispiele und Verssuche durch doch leider immer wieder mit Errorcodes...

Code: Alles auswählen

# Quelle: https://stackoverflow.com/questions/18406570/python-record-audio-on-detected-sound

from io import BytesIO
from io import StringIO

import pyaudio
import math
import struct
import wave
import time
import os

Threshold = 10

SHORT_NORMALIZE = (1.0 / 32768.0)
chunk = 1024
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 16000
swidth = 2

TIMEOUT_LENGTH = 5

f_name_directory = r'./records'


class Recorder:

    @staticmethod
    def rms(frame):
        count = len(frame) / swidth
        format = "%dh" % (count)
        shorts = struct.unpack(format, frame)

        sum_squares = 0.0
        for sample in shorts:
            n = sample * SHORT_NORMALIZE
            sum_squares += n * n
        rms = math.pow(sum_squares / count, 0.5)

        return rms * 1000

    def __init__(self):
        self.p = pyaudio.PyAudio()
        self.stream = self.p.open(format=FORMAT,
                                  channels=CHANNELS,
                                  rate=RATE,
                                  input=True,
                                  output=True,
                                  frames_per_buffer=chunk,
                                  # stream_callback=self._fill_buffer,
                                  )

    def record(self):
        print('Noise detected, recording beginning')
        rec = []
        current = time.time()
        end = time.time() + TIMEOUT_LENGTH

        while current <= end:

            data = self.stream.read(chunk)
            if self.rms(data) >= Threshold: end = time.time() + TIMEOUT_LENGTH

            current = time.time()
            rec.append(data)
        # self.write(b''.join(rec))
        self._play(b''.join(rec))

    def write(self, recording):
        n_files = len(os.listdir(f_name_directory))

        filename = os.path.join(f_name_directory, '{}.wav'.format(n_files))

        wf = wave.open(filename, 'wb')
        wf.setnchannels(CHANNELS)
        wf.setsampwidth(self.p.get_sample_size(FORMAT))
        wf.setframerate(RATE)
        wf.writeframes(recording)
        wf.close()
        print('Written to file: {}'.format(filename))
        print('Returning to listening')

    def _play(self, recording):
        # open the file for reading.
        # wf = wave.open(BytesIO(recording), 'rb')
        # wf.setnchannels(CHANNELS)
        # wf.setsampwidth(FORMAT)
        # wf.setframerate(RATE)

        # read data (based on the chunk size)
        # data = wf.readframes(chunk)

        # play stream (looping from beginning of file to the end)
        print('Play listened')
        #while len(data) > 0:
        while len(recording) > 0:
            # writing to the stream is what *actually* plays the sound.
            self.stream.read(BytesIO(recording))
            # = wf.readframes(chunk)
            self.stream.readframes(chunk)

        # cleanup stuff.
        self.p.close()
        self.p.terminate()

    def listen(self):
        print('Listening beginning')
        while True:
            input = self.stream.read(chunk)
            rms_val = self.rms(input)
            if rms_val > Threshold:
                self.record()


x = Recorder()

x.listen()
Gibt mir:

Code: Alles auswählen

    return pa.read_stream(self._stream, num_frames, exception_on_overflow)
TypeError: an integer is required (got type bytes)
aus.

Könnte mir jemand bei meinem Problem weitehelfen?

Re: Mikrofon überwachen und wiedergeben

Verfasst: Sonntag 29. November 2020, 15:06
von Sirius3
Konstanten schreibt man komplett GROSS. Du hast aber drei verschiedene Schreibweisen, die zum Teil sehr verwirrend sind beim Lesen.
Was soll das `f` bei f_name_directory? Und sind in dem Verzeichnis Namen oder doch eher Sounds?
In rms benutzt Du swidth, was eine globale Konstante ist, aber lokal verwendest Du das Format "h", da hast Du eine Dopplung, die zu Inkonsistenzen führen kann.
Um die Wurzel zu ziehen, wäre math.sqrt offensichtlicher als pow. Am allgemeinsten ist aber ** 0.5
Wenn Du aber nicht auf Endiness achtest, ist array.array besser als struct.

Code: Alles auswählen

FRAME_FORMAT = "h"
NORMALIZE = 1 / 32768

def rms(frame):
    shorts = array.array(FRAME_FORMAT, frame)
    sum_squares = sum(
        (sample * NORMALIZE) ** 2
        for sample in shorts
    )
    return (sum_squaeres / len(shorts)) ** 0.5 * 1000
`p` ist ein sehr schlechter Name für eine PyAudio-Instanz.
Ein Block startet immer nach einem : in einer neuen Zeile.
Warum schreibst Du im Kommentar in `play` "writing to the stream" benutzt dann aber stream.read?
Und was denkst Du erwartet read als Parameter?
Du hast eine Endlos-Scheife rufst aber beim ersten play p.terminate auf?

Re: Mikrofon überwachen und wiedergeben

Verfasst: Sonntag 29. November 2020, 16:23
von MpOnE
Zur Erklärung:
- Wie zu sehen ist ist dies eine Ansammlung beispiel Codes womit ich momentan experimentiere um gewünschte Ergebnisse zu erzielen.
- Dadurch ergeben sich auch unterschiedliche schreibweisen sowie Variablen (zB. f_name_directory) die derzeit nicht genutzt werden.
f_name_directory soll in hierbei der Aufnahmepfad (evtl. für zukünftigen Gebrauch) darstellen.
- Variablen wie p sowie wf ergaben sich durch besagte Code Beispiele die aber (nach erreichen des Ziels) zutreffender und einzigartiger geschrieben werden.
- Der Codevorschlag hört sich vielversprechend an vielen dank, bin selbst noch nicht bewandert was alle Funktionen angeht lerne aber stetig dazu.
- Was bringen mir hier die Endiness sprich "endians" in diesem Beispiel?
- Das terminate nach der Dauerschleife in er play funktion soll ein sauberes schlisen sein um Fehler im Dauerbetrieb zu vermeiden, ob diies dort an richtiger stelle ist konnte ich aufgrund des Vorhandenen Error bisher leider noch nicht ausloten.

Re: Mikrofon überwachen und wiedergeben

Verfasst: Dienstag 1. Dezember 2020, 22:31
von MpOnE
Nach weiteren etlichen Dokumentationen "trys & fails" hab ichs letzendlich geschafft.

So siehts derzeit aus:

Code: Alles auswählen

import array
import pyaudio
import time

# pyAudio settings
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 16000
CHUNK = 1024

SHORT_NORMALIZE = (1.0 / 32768.0)

THRESHOLD = 15
TIMEOUT_LENGTH = 1

FRAME_FORMAT = "h"
NORMALIZE = 1 / 32768


class Recorder:
    def __init__(self):
        self._pyAudio = pyaudio.PyAudio()
        self._stream = self._pyAudio.open(format=FORMAT,
                                          channels=CHANNELS,
                                          rate=RATE,
                                          input=True,
                                          output=True,
                                          frames_per_buffer=CHUNK
                                          )

    @staticmethod
    def rms(frame):
        shorts = array.array(FRAME_FORMAT, frame)
        sum_squares = sum(
            (sample * NORMALIZE) ** 2
            for sample in shorts
        )
        return (sum_squares / len(shorts)) ** 0.5 * 1000

    def record(self):
        print('Noise detected')
        rec = []
        current = time.time()
        end = time.time() + TIMEOUT_LENGTH

        while current <= end:

            data = self._stream.read(CHUNK)
            if self.rms(data) >= THRESHOLD:
                end = time.time() + TIMEOUT_LENGTH

            current = time.time()
            rec.append(data)
        self._play(b''.join(rec))

    def _play(self, recording):
        print('Play listened')
        self._stream.write(recording)

    def listen(self):
        print('Listening beginning')
        while True:
            mic_input = self._stream.read(CHUNK)
            rms_val = self.rms(mic_input)
            if rms_val > THRESHOLD:
                self.record()


if __name__ == '__main__':
    recorder = Recorder()
    recorder.listen()
Dennoch bekomme ich folgende Meldung:

Code: Alles auswählen

ALSA lib pcm.c:8424:(snd_pcm_recover) underrun occurred
Ist dies ein Fehler?

EDIT:
Taucht erst nach dem zweiten 'Play listened' auf

Die zweite Frage ist was haltet ihr erfahrenen Python Programmierer vom Code?
Python ist meine erste Sprache mit der ich mich intensiv befasse und da sind Feedback sowie Verbesserungsvorschläge erwünscht! :mrgreen:

Re: Mikrofon überwachen und wiedergeben

Verfasst: Dienstag 1. Dezember 2020, 22:46
von __deets__
Ein underrun bedeutet, dass nicht rechtzeitig ein neuer Audio buffer bereitgestellt werden konnte. Sowas führt zu Knacksen. Das kommt gerade am Start schonmal vor, und ist da zu ignorieren. Ob es dann später sporadisch stört musst du selbst entscheiden. Wenn ja, muss man optimieren.

Re: Mikrofon überwachen und wiedergeben

Verfasst: Dienstag 1. Dezember 2020, 23:08
von MpOnE
MpOnE hat geschrieben: Dienstag 1. Dezember 2020, 22:31 Dennoch bekomme ich folgende Meldung:

Code: Alles auswählen

ALSA lib pcm.c:8424:(snd_pcm_recover) underrun occurred
Ist dies ein Fehler?

EDIT:
Taucht erst nach dem zweiten 'Play listened' auf
Hab den Fehler gefunden, lag am falschen append aufruf.

Code: Alles auswählen

        self._play(b''.join(rec))
        rec.append(data)
So gibt es den Fehler nicht mehr.

Nächste Frage ist wie ich das aufgenommene "rec" weiter bearbeiten kann.
Z.B. wie ich die Anfangs- & Endpausen rausschneiden kann.

Oder die Aufnahme Allgemein zu verbessern (Echo- & Rauschunterdrückung), dies ja mit PulseAudio möglich sein