paint-brush
Python FIFO-Pufferklasse für Audio – ein Algorithmusvon@giwyni
441 Lesungen
441 Lesungen

Python FIFO-Pufferklasse für Audio – ein Algorithmus

von GIWYNI5m2024/06/27
Read on Terminal Reader

Zu lang; Lesen

Ein FIFO-Puffer ist eine häufig benötigte Datenstruktur für viele Anwendungsfälle. Elemente werden in den Puffer gelegt und in der Reihenfolge abgerufen, in der sie abgelegt wurden, mit der Ausnahme, dass sie in beliebigen Mengen abgerufen werden. Dieser Artikel beschreibt einen Algorithmus für diesen Puffer.
featured image - Python FIFO-Pufferklasse für Audio – ein Algorithmus
GIWYNI HackerNoon profile picture
0-item
1-item
2-item

Ein FIFO-Puffer ist eine häufig benötigte Datenstruktur für viele Anwendungsfälle. Elemente werden in den Puffer eingefügt und in der Reihenfolge abgerufen, in der sie eingefügt wurden. Dies ist eine ähnliche Funktionalität wie eine Warteschlange. Der Unterschied zwischen einer Warteschlange und einem Puffer besteht darin, dass die in den Puffer eingefügten Daten alle vom gleichen Datentyp sind. Darüber hinaus kann eine beliebige Datenmenge eingefügt werden und die abgerufene Menge ist ebenfalls beliebig.


Was sind die Anwendungsfälle, in denen Sie beliebig viele Elemente desselben Typs ablegen und abrufen möchten? Um in andere Bereiche als die Datenverarbeitung abzuschweifen: Denken Sie an Bankkonten: Geld wird in unterschiedlichen Beträgen eingezahlt und in Mengen entnommen, je nach Bedarf. Dasselbe gilt für die Getreidelagerung auf einem Bauernhof. Aber die Banken und Getreidespeicher erledigen das. Was uns in der IT bleibt, sind Audiodaten, die sehr schnell ankommen, aber mit einer langsameren spezifischen Geschwindigkeit abreisen müssen, damit sie gehört werden können. Die heutigen Schuldigen daran sind die Text-to-Speech-Engines, die die interaktive Kommunikation zwischen Maschine und Mensch ermöglichen. Die Maschine erhält Text (wahrscheinlich von einer KI-Engine), den sie in Audiobytes umwandelt, um ihn mit einer bestimmten Geschwindigkeit an den Menschen zu senden, die der Mensch hören kann. Wie erwartet generiert die Maschine Audiobytes in einem schnellen Tempo, die dann gepuffert werden müssen, um die Übermittlung an den Menschen mit einer viel langsameren Geschwindigkeit durchführen zu können. Eine Analogie ist Ihre örtliche Tankstelle. Der Text-to-Speech-Dienst ist ein Benzintanker, der mit hoher Geschwindigkeit große Mengen Benzin pumpt und die Benzintanks im Inneren der Tankstelle füllt. Diese werden dann mit viel geringerer Geschwindigkeit in die Autos oder andere Fahrzeuge der Kunden befördert.


Zusammenfassend kann die Umwandlung von Text in Sprache (Audio) viel schneller erfolgen. Es wird ein Audiopuffer benötigt, der Audio von der Text-to-Speech-Konvertierung (TTS) empfängt, um den Puffer zu füllen. Dieser Puffer wird dann mit der Geschwindigkeit der menschlichen Sprache geleert und ist für den Menschen verständlich.


Audiodaten: Die Daten bestehen aus einer Zahlenfolge, die Werte eines Audiosignals in regelmäßigen Abständen (Abtastrate genannt) darstellt. Es gibt auch das Konzept von Kanälen, also mehrere Audiosignale, die eine Folge mehrerer Werte ergeben.


Für die Zwecke dieses Artikels betrachten wir nur einen Kanal auf der Eingangsseite und einen Kanal auf der Ausgangsseite.


Numpy: Hierbei handelt es sich um eine Software, die das Speichern/Abrufen von Zahlenfeldern erleichtert und auf Leistung optimiert ist.

Design:

Die Anforderungen sind: Möglichkeit, eine beliebige Anzahl von Audiodaten-Frames einzugeben (ein Frame ist eine Zahl, die einen Audiodatenpunkt darstellt), also ein geordnetes Array von Audiosignalwerten. Möglichkeit, auf der Ausgabeseite eine beliebige Anzahl dieser Frames abzurufen. Wir müssen natürlich praktische Funktionen hinzufügen, um die Einschränkungen zu handhaben, nämlich eine begrenzte Puffergröße (führt zu vollem Puffer bei der Eingabe), keine verfügbaren Audiodaten (Puffer auf der Ausgabeseite leer). Zu den weiteren praktischen Funktionen gehört das Auffüllen von Audiodaten mit Nullen, falls mehr Audiodaten angefordert werden, als beim Abrufen des Puffers verfügbar sind.

Implementierung:

Im Folgenden wird eine Implementierung eines solchen Puffers in Python beschrieben:


Eingehende Audiobytes werden im Puffer gespeichert. Der Puffer hat einen „unteren“ Zeiger, der angibt, wie weit der Puffer gefüllt ist. Er hat auch einen „Startzeiger“, der den Anfang des Puffers markiert, in den neue Daten geschoben werden können. Der Startzeiger ist auf den Anfang des Puffers fixiert. Der untere Zeiger ist „dynamisch“ und bewegt sich „auf und ab“: nach oben, wenn Daten extrahiert werden, und „ab“, wenn Daten eingefügt werden. Die Daten werden immer oben (Startzeiger) des Puffers eingefügt, was dazu führt, dass vorhandene Daten im Puffer „nach unten“ geschoben werden, wodurch der Wert des unteren Zeigers erhöht wird.


Der Zustand „Puffer leer“ tritt ein, wenn der untere Zeiger gleich dem Startzeiger ist. Der Zustand „Puffer voll“ tritt ein, wenn der untere Zeiger gleich der Länge des Puffers ist.


Wir können auch „graceful fails“ einschließen, um die Bedingungen zu behandeln, bei denen der Puffer voll ist.


Wenn der Puffer voll ist und Daten eingefügt werden müssen, wird eine Ausnahme ausgelöst. Wenn der Puffer leer ist (auch wenn mehr Daten angefordert werden, als im Puffer verfügbar sind), werden für die fehlenden Daten Nullen zurückgegeben. Dies ist das Audio-Äquivalent zu „Stille“, wenn keine Worte gesprochen werden.


Ein Diagramm dieses Puffers sieht folgendermaßen aus:


Kodierung: (Haftungsausschluss: Zur Generierung des untenstehenden Codes wurde keine KI verwendet. Alle Schuld (Lob ist besser) gebührt dem Autor.)


Gemäß den objektorientierten Prinzipien wird der Code als Klasse/Objekt geschrieben und ist einfach zu verwenden. Der gesamte Code lautet:


 import numpy as np #numpy is the standard package or numerical array processing class AudioBuf: def __init__(self,bufSize:int,name:str='',dtype=np.int16): self.buffer = np.zeros((bufSize), dtype=dtype) self.bufSize=bufSize self.dtype=dtype self.idx=0 self.name=name #give a name to the buffer. def putInBuf(self,inData:np.ndarray[np.dtype[np.int16]]): inData=inData[:, 0] #Get the 1st col = channel 0 - mono remainder = self.bufSize - self.idx #space available for insertion if remainder < len(inData): msg=f'Error: Buffer {self.name} is full' print(msg) self.showBuf() raise ValueError(msg) self.buffer[self.idx:self.idx + len(inData)] = inData self.idx += len(inData) def getFromBuf(self,outDataLen:int = None)->np.ndarray: if not outDataLen: outDataLen=self.idx # return entire data of length is not specified if self.idx >= outDataLen: retVal = self.buffer[:outDataLen] self.buffer[0:self.idx-outDataLen]=self.buffer[outDataLen:self.idx] #move buffer up self.idx -= outDataLen else: retVal=np.zeros((outDataLen), dtype=self.dtype) retVal[0:self.idx] = self.buffer[0:self.idx] self.idx=0 return np.reshape(retVal,(-1,1)) #The -1 value automatically calculates to the number of elements def showBuf(self): print(f'AudioBuf : {self.name} has length= {self.idx} with capacity {self.bufSize}')

Abschluss:

Audiopufferung ist aufgrund zahlreicher Audioverarbeitungsanwendungen unerlässlich und heutzutage wichtiger. Der oben dargestellte Audiopufferalgorithmus ist eine praktische Python-Klasse.