Riesenringpuffer

  • Themenstarter Gelöschtes Mitglied 9001
  • Beginndatum

insert2020

Aktives Mitglied
Die API ergibt sich dann eigentlich daraus, wie sie genutzt wird. Er wird ja auch nicht Byte für Byte aus der Quelle erhalten, sondern chunkweise mehrere Kilobytes oder Megabytes.
Stimmt es macht eigentlich kaum Sinn immer das zuletzt geschriebene Byte zu returnen. Andererseits würde so ein natürlicher Ringpuffer funktionieren nach dem ja gefragt wurde...
 

mrBrown

Super-Moderator
Mitarbeiter
Im Zentrum steht - siehe Threadtitel - ein Ringpuffer und nicht der Raspi, auch keine Kodierung, und auch das Thema Audio habe ich nur beispielhaft erwähnt.
Ich bitte einen Moderator, den Thread zu schließen, da der Thread vom Thema abgekommen ist.
Also Moderator finde sich sowas übrigens völlig Okay - ein Forum ist eben mehr als nur en reines Nachschlagewerk, und angestoßene Diskussionen bieten oft deutlich mehr als ein reines "hier ist die Lösung" :)
 

httpdigest

Top Contributor
ByteBuffer.allocateDirect() bzw. "off-heap"-Speicher ist einzig alleine dazu da, um effizient (zero-copy) Speicher zwischen der Java Plattform und nativem Code auszutauschen bzw. effizient Speicherbereiche an nativen Code zu übergeben und von diesem entgegenzunehmen.
Tatsächlich gilt per Default für off-heap Speicher eine sehr viel striktere Grössenbeschränkung als für on-heap Speicher (gesteuert durch -XX:MaxDirectMemorySize).
Sowohl für on-heap als auch für off-heap Speicher gilt: Die JVM wird den ihr durch das Betriebssystem (und durch -Xmx) bereitgestellten virtuellen Speicher (was anderes sieht die JVM ja nicht) nutzen, auch, wenn das die Nutzung des SWAP Festplattenspeichers bedeutet. Aktuell zielt noch nichts in der vorgeschlagenen Lösung auf eine effiziente bzw. zielgerichtete Nutzung des Dateisystems für Bereiche des Ringbuffers ab.
Und darüber, was ein "natürlicher" Ringbuffer ist, lässt sich natürlich streiten. :)
 
G

Gelöschtes Mitglied 9001

Gast
buffer_intern.clear(); braucht es nicht, und wie gesagt.... nicht intensiv getestet :D

... Du solltest konstruktive Kritikvorschläge auch annehmen... wir reden nicht alle Blödsinn. :)
Nicht alle, nein ;) Es gab ja auch schon zu Beginn gute Ideen. Nur wenn dann jemand daher kommt und erstmal alles „sch...“ findet und man 5x erklären muss, dass mp3 nicht geht, kann man schonmal konstantieren, dass der Thread das Thema verlässt. Aber jetzt wird’s ja glücklicherweise interessant :)
 

temi

Top Contributor
Nicht alle, nein ;) Es gab ja auch schon zu Beginn gute Ideen. Nur wenn dann jemand daher kommt und erstmal alles „sch...“ findet und man 5x erklären muss, dass mp3 nicht geht, kann man schonmal konstantieren, dass der Thread das Thema verlässt. Aber jetzt wird’s ja glücklicherweise interessant :)
Wobei "Kompression" nicht zwangsweise mit mp3 gleichzusetzen ist und, sofern die Rechenleistung des PI ausreichend wäre, in doppelter Hinsicht eine sehr gute Lösung darstellen würde, nämlich was Speicherbedarf und WLAN-Datenvolumen angeht. Darum würde ich sie auch nicht unbesehen verwerfen.
 

White_Fox

Top Contributor
Ich würde mal gerne noch etwas Hirnschmalz in die Anforderungen stecken.

Daß das professionell sein soll (auch wenn die Anforderung RPi dem völlig widerspricht) und du Rohdaten haben willst- ok. Aber 96kS/s?

Das Frequenzspektrum, wo die meisten Menschen noch wahrnehmen können, hört irgendwo bei 16kHz oder so auf. Kinder schaffen noch 20kHz. Mit 48kS/s kannst du immer noch Frequenzen bis 24kHz darstellen, und das liegt immer noch gut über dem, was wahrnehmbar ist.

Mit 96kS/s löst du Frequenzen bis 48kHz auf. Das liegt weit außerhalb dessen, was jeder Zuhörer wahrnehmen kann.

Die Probe kannst du selber machen: Schnapp dir einen Funktionsgenerator, häng einen Lautsprecher rein, und wobbel mal das Frequenzspektrum durch. Alternativ gibt es Programme, die einen Funktionsgenerator simulieren und dessen Ausgang an die Soundkarte legen. Teste aber bitte mit einem Sinus, kein Dreieck- oder Rechtecksignal oder so.

Folglich würde ich an deiner Stelle mal darüber nachdenken, die Ansprüche etwas herunterzuschrauben. Mit 96kS/s produzierst du/dein Auftraggeber einen riesigen Datenoverhead, der aber keinen Mehrwert mehr bringt. Es mag Leute geben die das anders sehen, aber die hören ob das Netzsteckerkabel aus sauerstoffreiem Kupfer gemacht wurde.

Wenn du die Datenrate auf 48kS/s runterschrauben kannst ist dein Ringbufferproblem zwar noch nicht gelöst, aber immerhin schon nur noch halb so groß.

Ansonsten würde ich mal darüber nachdenken, ob man dem RPi nicht noch etwas zusätzlichen Speicher spendieren kann. (Ich bin aber auch eher Hardwareentwickler, und kein Programmierer. ;)) Soweit ich weiß verfügt der RPi doch noch über viele Pins nach draußen, da könnte man bestimmt noch eine Speicherkarte bauen.
Dann wäre aber gleich ein kleiner Rechner mit mehr RAM deutlich billiger.

Edit:
Wo kommen die gesampelten Daten eigentlich her? Ich hoffe, das vor dem sampelnden AD-Wandler ein anständiger Filter liegt, sonst ist jegliche Nachverarbeitung dank Spiegelfrequenz völlig obsolet.
 

mihe7

Top Contributor
Gefragt ist nun ein Algorithmus, der möglichst selten den langsamen Speicher berührt.
Hier stellt sich mir eine Frage. Bei einem FIFO-Buffer werden die ältesten Daten zuerst zurückgegeben. Sobald die Größe der Daten im Puffer über seinen RAM-Anteil wächst, müssen die Daten also sowieso auf die Platte geschrieben und von dieser gelesen werden. Das I/O muss folglich in der Lage sein, ausreichend schnell "parallel" zu lesen und zu schreiben. Wenn das aber der Fall ist und man nicht gerade meint, das Medium schonen zu müssen, worin besteht dann der Vorteil, zusätzlich im RAM zu puffern?
 
G

Gelöschtes Mitglied 9001

Gast
Man kann natürlich das Problem mit immer mehr Hardware (sprich mehr Ram) angehen, aber das verschiebt die Fragestellung nur: was ist, wenn als nächstes mit 8,10,12 Kanälen, 32bit float und 192 kHz aufgenommen werden soll? Eine Komprimierung hilft da auch nur unwesentlich.

Es ist richtig, dass man den menschlichen Hörbereich mit 44,1kHz abdeckt. Die Problematik sind die Intersample-Peaks: die durch die Samples approximierte Kurve kann höhere Amplituden erreichen als die Stützstellen (die Samples). Dadurch kann ein Signal clippen, obwohl die Samplewerte alle unter 0dB sind. Deshalb ist eine höhere Abtastfrequnz sinnvoll, weil sie die Kurve genauer abbildet und Clips besser erkannt werden.
Einen Raspi zu verteufeln macht wenig Sinn. Die Hardwareausstattung gängiger Fieldrecorder dürfte ähnlich sein (und da werden im übrigen auch SD-Karten verwendet). Der Tascam DR 44WL hat übrigens eine Wlan-Funktion, mit der sich Audiodaten (unkomprimiert) übertragen lassen, nur ist seine Wlan-Antenne viel zu schwach, um 30m zu überbrücken.
 
G

Gelöschtes Mitglied 9001

Gast
Hier stellt sich mir eine Frage. Bei einem FIFO-Buffer werden die ältesten Daten zuerst zurückgegeben. Sobald die Größe der Daten im Puffer über seinen RAM-Anteil Das I/O muss folglich in der Lage sein, ausreichend schnell "parallel" zu lesen und zu schreiben. Wenn das aber der Fall ist und man nicht gerade meint, das Medium schonen zu müssen, worin besteht dann der Vorteil, zusätzlich im RAM zu puffern?
Die Daten werden von einem Client per Wlan abgeholt. D.h., die Auslesegeschwindigkeit kann stark schwanken und sogar zeitweilig 0 sein. Im besten Fall agiert der Puffer nur auf dem Ram, bei schwacher Verbindung puffert er notfalls auf der SD-Karte. So ist zumindest die Zielsetzung.
 

White_Fox

Top Contributor
Man kann natürlich das Problem mit immer mehr Hardware (sprich mehr Ram) angehen, aber das verschiebt die Fragestellung nur: was ist, wenn als nächstes mit 8,10,12 Kanälen, 32bit float und 192 kHz aufgenommen werden soll?
Man geht nicht mit einem Messer zu einer Schießerei.
Du kannst halt nicht mit einem schwachbrüstigen Taschenrechner kommen, wenn du große, zeitlich fein aufgelöste Simulationen rendern oder Nachrichten mit komplexen Chiffrieralgorithmen ver/entschlüsseln willst.

Eine Komprimierung hilft da auch nur unwesentlich.
Das sehe ich anders: 4GB Daten sind weniger aufwändig zu behandeln als 8GB. Aber gut, wenn eine Reduktion der Daten auch nicht in Frage kommt (auch wenn ich mögliches Clippen eher als ein Problem durch schlechte Hardware ansehen würde, ich bin aber auch kein Experte für Signalverarbeitung und will mich da nicht zu weit aus dem Fenster lehnen), hat sich das wohl erledigt.

Den RPi will ich gar nicht verteufeln, es soll ein klasse Teil sein. Nur wird er leider anscheinend oft für völlig ungeeignete Probleme angewendet, und das sehe ich hier halt.

Der Tascam DR 44WL hat übrigens eine Wlan-Funktion, mit der sich Audiodaten (unkomprimiert) übertragen lassen, nur ist seine Wlan-Antenne viel zu schwach, um 30m zu überbrücken.
Hoppla - da ist ja noch ein weiterer Lösungsansatz. Ich kann mir kaum vorstellen daß ein WLAN-Sender zu schwach sein soll, um 30m zu überbrücken, solange es da Sichtverbindung gibt. Könnte es eher sein, daß sich da zu viele WLAN-Geräte gegenseitig im Frequenzband blockieren?

Anderer Vorschlag: Kannst du deinen Tascam - was immer es ist, es scheint deine Datenquelle zu sein - mit einer Richtantenne ausstatten und damit auf den Empfänger der Daten draufhalten? Oder einen Signalverstärker vor dessen Antenne schalten?
 

temi

Top Contributor
Man kann natürlich das Problem mit immer mehr Hardware (sprich mehr Ram) angehen, aber das verschiebt die Fragestellung nur: was ist, wenn als nächstes mit 8,10,12 Kanälen, 32bit float und 192 kHz aufgenommen werden soll? Eine Komprimierung hilft da auch nur unwesentlich.
Naja, die Antwort ist leicht: Wenn die Anforderungen die Hardware überfordern, dann muss die Hardware besser auf das Problem abgestimmt sein. Du kannst nun mal keine Microsoft (oder beliebig andere) Cloud mit einem C64 (falls du den noch kennst) betreiben.

Aber nicht falsch verstehen: Ich bin auch nicht dafür, Mängel der Software, durch bessere Hardware auszugleichen. Aber irgendwann sind halt die Möglichkeiten der Optimierung erschöpft.

Einen Raspi zu verteufeln macht wenig Sinn.
Das ist auch nicht so gemeint. Der Raspi ist sicher toll, aber eben begrenzt in seinen Möglichkeiten. Es ist also weniger "verteufeln", als "möglicherweise nicht geeignet".
 

mihe7

Top Contributor
Die Daten werden von einem Client per Wlan abgeholt. D.h., die Auslesegeschwindigkeit kann stark schwanken und sogar zeitweilig 0 sein. Im besten Fall agiert der Puffer nur auf dem Ram, bei schwacher Verbindung puffert er notfalls auf der SD-Karte. So ist zumindest die Zielsetzung.
Das war eine eher theoretische Frage, ob es einen bestimmten Grund gibt, den Puffer im RAM halten zu wollen. Darum habe ich die Schonung des Speichermediums ausgeklammert. Anders gefragt: soll der Client ggf. schneller als mit 96 kSamples/s bedient werden können?
 

temi

Top Contributor
was ist, wenn als nächstes mit 8,10,12 Kanälen, 32bit float und 192 kHz aufgenommen werden soll?
Da fällt mir doch gleich auch noch eine weitere Lösungsmöglichkeit für dein Problem ein: Du kannst ja auch die Zahl der Kanäle auf dem Raspi reduzieren und stattdessen dann zwei oder drei Raspis parallel einsetzen. Die lassen sich ja ganz gut "stacken".
 
Zuletzt bearbeitet:

White_Fox

Top Contributor
Und die Datenrate, die du schaffst, gibst du dann in mBit/s an...ja?

Das erinnert mich an die Powerbank, die wir mal als Werbegeschenk von der Firma bekommen haben: Das Ding hat laut Typenschild saftige 2500MAh gespeichert.
 
G

Gelöschtes Mitglied 9001

Gast
Wie gesagt, im Moment kann der Raspi die 6 Kanäle mit 24bit bei 96kHz problemlos an den Client ausliefern.
Einzig der Puffer sollte größer sein, um evtl. Verbindungsabbrüche zu überbrücken.
 

insert2020

Aktives Mitglied
Nicht alle, nein ;) Es gab ja auch schon zu Beginn gute Ideen. Nur wenn dann jemand daher kommt und erstmal alles „sch...“ findet und man 5x erklären muss, dass mp3 nicht geht, kann man schonmal konstantieren, dass der Thread das Thema verlässt. Aber jetzt wird’s ja glücklicherweise interessant :)
Der Thread verlässt nicht das Thema... Es entwickelt sich nur nicht in eine von dir gewünschte, aber abwegige Richtung. Es fragt sich also, wer wem nicht zuhören kann. ;)
 

temi

Top Contributor
Wie gesagt, im Moment kann der Raspi die 6 Kanäle mit 24bit bei 96kHz problemlos an den Client ausliefern.
Einzig der Puffer sollte größer sein, um evtl. Verbindungsabbrüche zu überbrücken.
Du kannst ja auch die Zahl der Kanäle auf dem Raspi reduzieren und stattdessen dann zwei oder drei Raspis parallel einsetzen. Die lassen sich ja ganz gut "stacken".
Das ist ausdrücklich nicht als Scherz gemeint. Damit erschaffst du dir eine skalierbare Lösung, die später auch das Problem mit 8, 10 oder 12 Kanälen abdeckt.
 

temi

Top Contributor
Wie gesagt, im Moment kann der Raspi die 6 Kanäle mit 24bit bei 96kHz problemlos an den Client ausliefern.
Einzig der Puffer sollte größer sein, um evtl. Verbindungsabbrüche zu überbrücken.
Ist denn der Schreib-/Lesezugriff auf die SD-Karte generell schnell genug, um das zu schaffen? Auch wenn es nur für "Notfälle" sein soll, müssen die Daten ja in der gewünschten Rate weggeschrieben werden können, weil sie in dieser Rate geliefert werden und irgendwohin müssen.
 

White_Fox

Top Contributor
Oder du ziehst eine vernünftige WLAN-Verbindung auf, und wenn ich dich richtig verstanden habe, sparst du dir damit das Gehampel mit den PIs.

Wie gesagt, 30m mit Sichtverbindung sollten kein Problem sein, so schlecht kann auch ein billiger WLAN-Empfänger eigentlich nicht sein.
 
G

Gelöschtes Mitglied 9001

Gast
Als ich mal einen Vortrag über Bildverarbeitungsalgorithmen hielt (es ging um Musiknoten), stellte ich - als Test - die Frage in die Runde, wie man den gerade prinzipiell besprochenen Algorithmus beschleunigen könnte. Als Antwort kam: mehr Rechner installieren, parallelrechnen. Das geht natürlich, aber als Programmierer schaut man natürlich erstmal nach algorithmischen Lösungen. Und ich glaube nicht, dass die Möglichkeiten des Raspis schon erschöpft sind, zumal es ja prinzipiell bereits funktioniert. Nur eine geschickte Auslagerung bei Bedarf muss noch hinzukommen.
 
G

Gelöschtes Mitglied 9001

Gast
Nein, die Frage ist nicht theoretischer Natur. Man handelt sich durch diese Architektur freilich eine ganze Menge anderer Herausforderungen ein. Und: auch bei dieser Lösung muss 1 Raspi grundsätzlich in der Lage sein, 12 Kanäle zu verarbeiten. Er verteilt sie also nur um des Pufferns Willen an seine Kollegen. Ein Client muss dann die Kanäle von allen Raspis einsammeln und das auch noch synchronisiert.

Ich arbeite gerade an einer Umsetzung mit verketteten Datenblöcken, die bei Speicherknappheit ausgelagert werden.
 

temi

Top Contributor
Und: auch bei dieser Lösung muss 1 Raspi grundsätzlich in der Lage sein, 12 Kanäle zu verarbeiten. Er verteilt sie also nur um des Pufferns Willen an seine Kollegen.
Damit hast du wohl Recht. Da die Teile eigentlich nichts anderes mit den Daten tun, ist das ziemlich unsinnig.
Ich arbeite gerade an einer Umsetzung mit verketteten Datenblöcken, die bei Speicherknappheit ausgelagert werden.
Schau dir mal den Link aus #50 an, falls noch nicht geschehen. Ich denke mit ByteBuffer und FileChannel lässt sich da was anfangen.
 
G

Gelöschtes Mitglied 9001

Gast
Man geht nicht mit einem Messer zu einer Schießerei.
Du kannst halt nicht mit einem schwachbrüstigen Taschenrechner kommen, wenn du große, zeitlich fein aufgelöste Simulationen rendern oder Nachrichten mit komplexen Chiffrieralgorithmen ver/entschlüsseln willst.
Es geht hier lediglich um's Puffern, nicht um's Rendern/Umkodieren/Verschlüsseln. Wie schon gesagt: der vorhandene Computer kann mit dem Audiosignal problemlos umgehen.

Aber gut, wenn eine Reduktion der Daten auch nicht in Frage kommt (auch wenn ich mögliches Clippen eher als ein Problem durch schlechte Hardware ansehen würde, ich bin aber auch kein Experte für Signalverarbeitung und will mich da nicht zu weit aus dem Fenster lehnen), hat sich das wohl erledigt.
Clippen ist etwas anderes als kurze Aussetzer (die durch schwache Hardware entstehen können). Clippen heißt: das Signal geht über 0dB hinaus, was zu hörbarer Verzerrung führt. Um das zu vermeiden, muß man den Aufnahmepegel entsprechend einstellen. Nun kann die Audiokurve zwischen den Samplewerten höher ansteigen, als die Samplewerte. Wenn man nicht permanent mithören kann (weil man z.B. selbst das Instrument spielt), können Clips so unerkannt bleiben. Je höher die Abtastfrequenz, desto genauer wird die Kurve digital nachgezeichnet, desto genauer kann der Peak ermittelt und demzufolge der Aufnahmepegel eingestellt werden und desto unwahrscheinlicher wird ein unerkanntes Clippen. Wenn das Mastering fertig ist, kann man dann für den Consumer auf 44,1 kHz herunterrechnen.

Hoppla - da ist ja noch ein weiterer Lösungsansatz. Ich kann mir kaum vorstellen daß ein WLAN-Sender zu schwach sein soll, um 30m zu überbrücken, solange es da Sichtverbindung gibt. Könnte es eher sein, daß sich da zu viele WLAN-Geräte gegenseitig im Frequenzband blockieren?
Anderer Vorschlag: Kannst du deinen Tascam - was immer es ist, es scheint deine Datenquelle zu sein - mit einer Richtantenne ausstatten und damit auf den Empfänger der Daten draufhalten? Oder einen Signalverstärker vor dessen Antenne schalten?

Es gibt portable (Handheld-)Recorder, die mit internen und/oder externen Mikrofonen direkt auf eine SD-Karte aufzeichnen und meist noch einige Funktionen für's Mixing, Limiting und dergleichen bieten. Kaum größer als ein Raspi. Da sieht man, dass Minicomputer problemlos mit großen Audiomengen umgehen können.
Die Firma Tascam bietet einige an. Nein, man kann keine Antenne anschließen und ich habe es mehrfach probiert: bereits 20m Sichtverbindung waren für den Tascam nicht mehr schaffbar. Und es war kein anderes WLAN in Reichweite. Das ist der Grund, weshalb ich einer eigenen Lösung arbeite. Der Raspi hat offensichtlich ein erheblich besseres Wlan-Signal.

Jetzt sind wir freilich schon wieder weit vom Thema abgekommen.
Ich habe zwischenzeitlich einen Puffer mit eigenem Auslagerungsmechanismus geschrieben und werde jetzt testen testen testen.
 
Zuletzt bearbeitet von einem Moderator:

temi

Top Contributor
Es geht hier lediglich um's Puffern, nicht um's Rendern/Umkodieren/Verschlüsseln. Wie schon gesagt: der vorhandene Computer kann mit dem Audiosignal problemlos umgehen.
Das ist eigentlich egal. Wenn die vorhandene Hardware zu wenig Speicher hat, um die Anforderungen an das Puffern zu erfüllen, erfüllt die Hardware diese Anforderungen nicht, auch wenn sie von der Geschwindigkeit ausreichend wäre.

Du hast jetzt einige mögliche Lösungsvorschläge erhalten. Wenn das Puffern auf SD-Karte die Lösung deiner Wahl ist, dann mach das so. Falls die SD-Karte nicht ausreicht, kannst du immer noch, mit der selben Lösung, eine SSD per USB-SATA-Adapter verwenden.


Vielleicht magst du ja diese Lösung (zumindest mittels einer kurzen Beschreibung) mit uns teilen?
 
G

Gelöschtes Mitglied 9001

Gast
Das ist eigentlich egal. Wenn die vorhandene Hardware zu wenig Speicher hat, um die Anforderungen an das Puffern zu erfüllen, erfüllt die Hardware diese Anforderungen nicht, auch wenn sie von der Geschwindigkeit ausreichend wäre.
Die Hardware erfüllt die Anforderungen, gesucht war lediglich ein Algorithmus, der die Hardware adäquat ausnutzt.

Du hast jetzt einige mögliche Lösungsvorschläge erhalten. Wenn das Puffern auf SD-Karte die Lösung deiner Wahl ist, dann mach das so.

Puffern im Ram und nötigenfalls auf SD-Karte.

Vielleicht magst du ja diese Lösung (zumindest mittels einer kurzen Beschreibung) mit uns teilen?
Kommt, nach dem ich getestet habe.
 

temi

Top Contributor
Die Hardware erfüllt die Anforderungen, gesucht war lediglich ein Algorithmus, der die Hardware adäquat ausnutzt.
Die einen sagen so, die anderen so. ;)

Nee, ist schon in Ordnung. Wenn es mit dem Puffern auf SD funktioniert ist ja alles in Ordnung. Wir sprechen dann wieder drüber, wenn du mit 12 Kanälen ankommst ;)
 
G

Gelöschtes Mitglied 9001

Gast
Die einen sagen so, die anderen so. ;)

Der Markt ist voll von Audiointerfaces, -recordern, -effektgeräten, -mixern aller coloeur. Sie allesamt sind in der Lage, große Audiomengen zu verarbeiten und da stecken keine PCs mit 8, 16, 32 GB Arbeitsspeicher drin. Man kann natürlich auch einfach ein Dante-Audiointerface kaufen, welches Netzwerkanschluß hat und außerdem auch noch niedrige Latenz bietet. Die 1500 Euro, die man dafür hinlegen muß, spiegeln aber nicht nur die verwendete Hardware wieder, sondern auch die gedanklichen Fähigkeiten der Entwickler, die die in der Realität nunmal begrenzten Hardwaremöglichkeiten geschickt auszunutzen wissen. Wenn niedrige Latenz (und nur die stellt wirklich Ansprüche an die Hardware) aber keine Anforderung ist, ergibt es wirtschaftlich keinen Sinn, dafür Geld zu investieren.
Die Vorstellung, dass ein kleiner Computer für Audioverarbeitung nicht ausreichend sein könnte, rührt möglicherweise aus der Gamerszene her, wo Hardware eine sehr gewichtige Rolle spielt. Tatsächlich gibt es digitale Audioverarbeitung bereits seit Jahrzehnten. MP3 wurde in den 80er Jahren entwickelt, damals wäre der Raspi von heute ein Hochleistungscomputer gewesen.
 

White_Fox

Top Contributor
Der Markt ist voll von Audiointerfaces, -recordern, -effektgeräten, -mixern aller coloeur. Sie allesamt sind in der Lage, große Audiomengen zu verarbeiten und da stecken keine PCs mit 8, 16, 32 GB Arbeitsspeicher drin.

Das ist zwar richtig, aber dafür steckt da sicherlich hochspezialisierte HW drin, die wirklich nur eines macht: Audiosignale verarbeiten. Da werkelt dann i.d.R. auch kein Linux drin (und wenn, dann ist es sehr stark angepasst das für andere Zwecke nicht mehr zu gebrauchen ist), sondern z.B. irgendein spezielles RTOS für Mikrocontroller. Und Flashspeicher ist dann z.B. nicht über ein lahmes serielles Interface angebunden wie eine SD-Karte, sondern über irgendwas paralleles. Und dafür fehlt allerhand anderes Gedöns, kein nichtbenötigter USB-Controller gammelt mit einem Dämon im Speicher herum und tut ständig seinen unveränderten Status kund und dafür wird die eigentliche Aufgabe unterbrochen...
Falls da überhaupt ein Betriebssystem läuft.

Und deswegen ist ein RPi mit spezialisierter Hardware nicht zu vergleichen.

Die Vorstellung, dass ein kleiner Computer für Audioverarbeitung nicht ausreichend sein könnte, rührt möglicherweise aus der Gamerszene her, wo Hardware eine sehr gewichtige Rolle spielt.
Nein...zumindest hier nicht. Ich z.B. habe von Programmierung eigentlich kaum Ahnung, weil ich eigentlich viel mehr mit HW zu tun habe. :)

Tatsächlich gibt es digitale Audioverarbeitung bereits seit Jahrzehnten. MP3 wurde in den 80er Jahren entwickelt, damals wäre der Raspi von heute ein Hochleistungscomputer gewesen.
Und damals fand Audiosignalverarbeitung, jedenfalls soweit ich weiß, entweder komplett analog statt oder hat ewig gedauert. ;)
 
K

kneitzel

Gast
Der Markt ist voll von Audiointerfaces, -recordern, -effektgeräten, -mixern aller coloeur. Sie allesamt sind in der Lage, große Audiomengen zu verarbeiten und da stecken keine PCs mit 8, 16, 32 GB Arbeitsspeicher drin. Man kann natürlich auch einfach ein Dante-Audiointerface kaufen, welches Netzwerkanschluß hat und außerdem auch noch niedrige Latenz bietet. Die 1500 Euro, die man dafür hinlegen muß, spiegeln aber nicht nur die verwendete Hardware wieder, sondern auch die gedanklichen Fähigkeiten der Entwickler, die die in der Realität nunmal begrenzten Hardwaremöglichkeiten geschickt auszunutzen wissen. Wenn niedrige Latenz (und nur die stellt wirklich Ansprüche an die Hardware) aber keine Anforderung ist, ergibt es wirtschaftlich keinen Sinn, dafür Geld zu investieren.
Die Vorstellung, dass ein kleiner Computer für Audioverarbeitung nicht ausreichend sein könnte, rührt möglicherweise aus der Gamerszene her, wo Hardware eine sehr gewichtige Rolle spielt. Tatsächlich gibt es digitale Audioverarbeitung bereits seit Jahrzehnten. MP3 wurde in den 80er Jahren entwickelt, damals wäre der Raspi von heute ein Hochleistungscomputer gewesen.

Also es ist klar: Wenn man die Spects betrachtet, dann hat der PI die notwendige Leistung.

Aber die Frage ist, in wie weit es zu worst case Fällen kommen kann. Was für ein Betriebssystem setzt Du ein? Üblich ist oft ein Linux System (Raspian ist wohl sehr weit verbreitet), was aber zur Konsequenz hat, dass das System sehr viel mehr macht, als eben nur Deine Applikation.
Desweiteren setzt Du auf Java, d.h. selbst Deine Applikation macht einiges mehr....

Und dann kann es schnell passieren, dass es zu einem Worst Case kommen kann:
- Durch die suboptimale Hardware (Ich habe es nie im Detail analysiert, aber der Pi hat wohl was IO angeht gewisse Bottlenecks) sind die Leistungsreserven ggf. nicht so groß.
- Andere Prozesse benötigen dann einen Teil der verfügbaren Leistung (Ich nehme einfach einmal das 08/15 Raspian: Da startet X11, also eine grafische Ausgabe benötigt gewisse IO des Systems, ggf. laufen Dinge wie SSH weil man ja remote zugreifen möchte, ....)
- Dann kommt ggf. noch ein GC Lauf an ungünstiger Stelle ....

Daher mag es durchaus so zu sein, dass andere Systeme, die ähnliches machen, auch nicht mehr Leistung haben, aber diese nutzen die vorhandenen Ressourcen in der Regel besser. Früher wäre dann eine Embedded Lösung das, was ich empfohlen hätte (Gibt es heute immer noch - da kommen dann Dinge wie FreeRTOS und co ins Spiel) aber da die Hardware immer leistungsfähiger wird, wäre Linux auch heute meine Wahl - aber keine 08/15 Distribution sondern massiv abgespeckt. Einfaches Startup/Initialisierung (kein Systemd und co), minimiertes System und wohl auch einen entsprechend aufgebohrten Kernel.

Ich würde überlegen, die Schreibzugriffe auf die SD Karte zu minimieren. Also das System direkt in den Speicher zu laden und danach nichts mehr lesen/schreiben zu müssen. Das kostet zwar etwas Speicher, aber das kann gering gehalten werden (So man wirklich nicht mehr braucht, als eben diese Streaming Lösung!)
Das, was man da mehr verballert, gewinnt man massiv, durch den Verzicht auf Java und eben der Erstellung einer native Lösung mit z.B. C/C++.

(Das geht bei dem ein oder anderen embedded System so weit, dass dann das System nur als Source vorliegt und den Rahmen der Applikation bildet. Dann hat man halt am Ende einen übersetzten Kernel, der nur die benötigten Elemente beinhaltet. Das läuft dann teilweise auch auf einem Arduino. Also ein System, das um ein vielfaches kleiner ist als ein pi :) Da aber heute die Hardware immer leistungsfähiger wird, ist die Entwicklung eher so, dass 08/15 Systeme angepasst werden und die Entwicklung dann entsprechend laufen kann ... also meist ist dann Linux das Ziel und Entwickler können dann eine Distribution ihrer Wahl nutzen und auf dem System läuft dann am Ende ein spezielles angepasstes minimales Linux).

Aber das nur ganz am Rande als meine Sichtweise. Da Du Dich bezüglich Sprache und Plattform ja festgelegt hast, ist das erst einmal uninteressant. Außer evtl. als Hinweis, dass Du ggf. am Ende eine Art customized Linux haben willst, auf dem weniger läuft. Man kann ja ggf. einiges einfach nicht mitstartet und wenn alles funktioniert, dann ggf. Pakete löschen. Dann hat man am Ende seine eigene, minimierte Lösung.
 
G

Gelöschtes Mitglied 9001

Gast
Und Flashspeicher ist dann z.B. nicht über ein lahmes serielles Interface angebunden wie eine SD-Karte, sondern über irgendwas paralleles. Und dafür fehlt allerhand anderes Gedöns, kein nichtbenötigter USB-Controller

Die gängigen Field-Recorder - von Low-Budget bis hinzu High-End - schreiben alle auf SD-Karte. Und USB haben die allermeisten. Dann kommen neuerdings eben auch noch Wlan und Ethernetanschlüsse hinzu.

Ich würde überlegen, die Schreibzugriffe auf die SD Karte zu minimieren.
Genau deswegen habe ich diesen Thread erstellt: wie gelingt es, einen Ringpuffer zu implementieren, der hauptsächlich im Ram arbeitet und nur im worst case die SD-Karte benutzt. Das Swapping des OS hilft hier nicht und das wird auf dem Raspi ohnehin in der Regel deaktiviert, eben um unnötige Schreibzugriffe zu vermeiden.
Das, was ich hier vorhabe, hat letztendlich einen Bruchteil der Schreibzugriffe auf die SD-Karte als ein Fieldrecorder, der die gesamte Tonaufnahme auf die SD-Karte schreibt (und das sind etliche GB).

Da Du Dich bezüglich Sprache und Plattform ja festgelegt hast, ...

Mich interessierte eigentlich ganz abstrakt ein Algorithmus, der mit zwei verschiedenen Speichermedien umgehen kann, so wie man sich als Programmierer eben auch mal ganz abstrakt für Algorithmen interessieren kann, unabhängig von verwendeter Sprache und Hardware.

Die ganzen Linux-Administrationsarbeiten sind natürlich schon längst erledigt, aber wir müssen ja damit den Thread hier nicht auch noch füllen, habe ja ohnehin schon viel zu Signalverarbeitung geschrieben, obwohl das auch mit dem eigentlichen Algorithmus nichts zu tun hat.
 

mrBrown

Super-Moderator
Mitarbeiter
Genau deswegen habe ich diesen Thread erstellt: wie gelingt es, einen Ringpuffer zu implementieren, der hauptsächlich im Ram arbeitet und nur im worst case die SD-Karte benutzt.
Ganz naiver, aber simpler Ansatz:

Als eigentlichen Puffer selbst irgendeine Standard-Queue, zB ArrayBlockingQueue.
Die ankommenden Daten jeweils unabhängig davon Puffern, zB immer auf X MB oder X Sekunden warten
Wenn entsprechende Daten vorhanden sind, ob noch "RAM" frei ist
-> Wenn ja, Daten als zB Byte-Array in den normalen Puffer legen
-> Wenn nein, Daten auf die SD-Karte schreiben und Zeiger auf die Datei/Position/Whatever in den Puffer legen

Ein zweiter Thread holt sich jeweils das erste Element der Queue, schickt's an den Server und löscht die etwaigen Dateien


Das ist keineswegs die schneller/speichersparendste/whatever-Variante, sondern einfach nur "einfach" und vielleicht sogar schon ausreichend. Lässt sich theoretisch auch beliebig aufbohren, eigenes Queue-Interface drüber und nach außen eine Queue<Byte> draus machen, zwischen nio und io wechseln, Pools nutzen, ...
 
K

kneitzel

Gast
Genau deswegen habe ich diesen Thread erstellt: wie gelingt es, einen Ringpuffer zu implementieren, der hauptsächlich im Ram arbeitet und nur im worst case die SD-Karte benutzt.

Bei meinem Punkt geht es aber nicht um Deine Implementation sondern um genau das, was Du die ganze Zeit klar abgelehnt hast:
Das ganze drumherum!

Was den reinen Algorithmus angeht:
Wo ist das Problem? Die Thematik ist doch absolut trivial und ich verstehe da gerade nicht, wo da Dein Problem wirklich ist.

Du hast zum einen den eigentlichen Rahmen:
a) Ein Thread der Daten von der Quelle liest und merkt.
b) Ein Thread der ein Ziel beliefert und aus dem gemerkten die Daten holt.

Daten kommen immer in Blöcken so wie ich das verstanden habe. Also musst Du x Blöcke merken können. Dazu hast Du dann ein Array mit x Elementen. Jedes Element ist entweder ein Block mit Daten im Speicher (also z.B. ein byte Array) oder eben eine externe Referenz.

Da Speicher knapp ist, machen wir hier eine Totsünde und wir optimieren:
Das ist ein einfaches Array von Integern.
Du willst x Blöcke im Speicher behalten: Dann bedeutet das, dass Du etwas hast wie
- Ein Array von byte Arrays. Jedes Byte Array hat dann genau einen Block. Und 0 bis (x-1) Elemente sind dann im Speicher.
- Du hast nur ein byte Array - der Index ist dann berechenbar. Der x-te Block ist dann (x-1)*size bis x*size - 1.
Alle Blöcke mit x >= interner Buffer sind dann Dateien: In einem Verzeichnis Deiner Wahl legst Du z.B. buffer<x> an. Also sowas wie buffer000158.
Oder Alternativ eine Datei mit Random access Zugriff. Dann hast Du einmal eine Datei reserviert. Aber bei einer SD Karte weiss ich nicht, ob da eine Optimierung drin ist, dass Du nicht ständig auf die gleichen Speicherstellen schreibst. ==> Das könnte die SD Karte schnell killen! Daher: Analyse (Was Du ja gar nicht willst / magst so wie ich Dich verstanden habe bisher) .... also ggf. ständig neue Dateien erstellen und löschen könnte besser sein....

Nun hast Du also:
a) die eigentliche physikalische Speicherung der Daten.
b) eine Referenzierung über ein großes Array (wo die Integer Zahl gespeichert ist, die den Speicherort kennzeichnet - je nach Anzahl kann man hier Speicher sparen und man speichert nur char Werte, die nur 2 Bytes groß sind. Das reicht evtl. aus?).

Also fehlt nur noch die Verwaltung der freien Elemente. Das ist dann der typische Ringspeicher. Du musst Dir also für interne und externe Werte merken: Wo ist das erste und wo das letzte Element.

Beim merken schaust Du also dann: ist im internen Ringspeicher was frei? -> da speichern und gut ist es, ansonsten speichern wir im externen Ringspeicher.

Dann müssen wir das Speichern sinnvoll gestalten:

Der interne Speicher hat ja schon byte Arrays. Um Daten nicht unnötig kopieren zu müssen wird dann einfach die byte-Array Referenz getauscht. So vermeiden wir unnötiges kopieren und auch der GC muss nicht tätig werden. So Du nicht einzelne Byte-Arrays hast, ist es auch kein Problem. Dann muss das Einlesen der Daten schon an der richtigen Stelle erfolgen. Also ist im internen Speicher was frei, wird direkt in den Speicher dort gelesen. Ist intern nichts frei, wird in den Puffer (der einmal am Anfang deklariert wurde) gelesen um dann die Daten in eine Datei zu schreiben.

Also von der Problematik her ist der Algorithmus aus meiner Sicht trivial und lässt sich so herunter schreiben. Problematisch sind nur die Rahmenbedingungen. Da GC Aktivität gut vermieden werden kann und viel mit native Types (und Arrays von diesen) gearbeitet werden kann, ist Java noch nicht einmal so ein großes Problem. Mit Modulen und Co ist der Speicher-Overhead wohl vertretbar. Wobei ich für mich keinen Grund sehe, das in Java zu machen, oder ist das Lesen oder Schreiben der Daten irgend etwas spezielles, für das es nur Java Libraries geben sollte?
 
K

kneitzel

Gast
Ganz naiver, aber simpler Ansatz:

Ja, da warst Du etwas schneller. Ich habe aber sogar die "optimierte" Variante mal herunter geschrieben. Da kann man dann einmalig Speicher reservieren und dann ganz ohne GC weiter arbeiten. Dann ist es sogar denkbar, andere GC zu probieren. Wenn man sicher ist, dass man den GC nicht braucht, dann kann man die Variante ohne GC versuchen und verifizieren, dass die Annahme richtig ist. Minimaler Speicherzuwachs ist ggf. vertretbar, da ja so Aufnahmen nur begrenzte Zeit laufen dürften (Annahme von meiner Seite - müsste verifiziert werden).
 
G

Gelöschtes Mitglied 9001

Gast
Ganz naiver, aber simpler Ansatz:

Als eigentlichen Puffer selbst irgendeine Standard-Queue, zB ArrayBlockingQueue.
Die ankommenden Daten jeweils unabhängig davon Puffern, zB immer auf X MB oder X Sekunden warten
Wenn entsprechende Daten vorhanden sind, ob noch "RAM" frei ist
-> Wenn ja, Daten als zB Byte-Array in den normalen Puffer legen
-> Wenn nein, Daten auf die SD-Karte schreiben und Zeiger auf die Datei/Position/Whatever in den Puffer legen

Ein zweiter Thread holt sich jeweils das erste Element der Queue, schickt's an den Server und löscht die etwaigen Dateien
Japp, so hab ich's letztlich gemacht. Etwas feiner muß man es dann noch betrachten: die Blöcke, die das Audiointerface liefert, sind meist sehr kurz, ca. 1 Sekunde. Ich sammle mehrere solcher Blöcke zusammen in einen größeren Datenblock. Sobald der voll ist, kann er ggf. ausgelagert werden.
 
G

Gelöschtes Mitglied 9001

Gast
Was den reinen Algorithmus angeht:
Wo ist das Problem? Die Thematik ist doch absolut trivial und ich verstehe da gerade nicht, wo da Dein Problem wirklich ist.

Es ist doch nicht so, dass das für mich ein großartiges Problem ist. Ich bin nur an Austausch interessiert und es gibt ja mehrere Lösungsansätze. An denen war ich interessiert. Einen Algorithmus schreiben kann ich selbst (siehe oben), aber es ist eben auch interessant zu lesen, wie andere es machen. Es ist hingegen weniger interessant zu lesen, dass jemand in einem Wisch es „sch...“ findet.
 
G

Gelöschtes Mitglied 9001

Gast
Java:
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.StandardOpenOption;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * Puffer für eine große Anzahl binärer Daten.<br>
 * Die Daten werden auf Datenblocks aufgeteilt, die in einer Queue liegen. 
 * Ist ein Datenblock voll, wird ein weiterer angelegt. Leseoperationen bewirken, dass ein Datenblock peu à peu "leergelesen"
 * wird. Leere Datenblocks werden aus der Liste entfernt.<br>
 * Geht der verfügbare Arbeitsspeicher zur Neige, werden Datenblocks, beginnend vom Tail der Queue an, ausgelagert, bis ihre
 * Daten gebraucht werden.<br>
 * Nur vollbeschriebene Datenblocks werden ausgelagert.
 */
public class BigBuffer {

    /**
     * Enthält einen Teil der Daten. Kann diese auslagern und wieder einholen.
     */
    private static class DataBlock {
        private volatile byte [] data;

        private volatile int capacity;  // Die Kapazität kann man zwar durch data.length ermitteln,
                                        // wenn jedoch die Daten ausgelagert sind, ist data null
                                        // und wir wollen uns beim Wiedereinholen nicht auf die
                                        // Länge der Auslagerungsdatei verlassen
        private volatile int writepos, readpos, size; // Schreib/Leseposition, aktuelle Größe der Nutzdaten
        private volatile boolean externalized;  // true, wenn die Daten ausgelagert wurden

        private volatile File file;  // Auslagerungsdatei
        
        public DataBlock(int capacity) {
            this.capacity = capacity;
            data = new byte[capacity];
            writepos = 0; readpos = 0; size = 0;
            externalized = false;
        }
        
        /**
         * Ein Block kann nur ausgelagert werden, wenn er voll beschrieben ist und noch kein bißchen
         * ausgelesen wurde.
         */
        public boolean isExternalizeable() {
            return !externalized && isCompletelyFilled() && readpos == 0;
        }
        
        /**
         * Lagert die Daten aus. Liefert false, wenn fehlgeschlagen.
         */
        public void externalize() throws IOException {
            File F = File.createTempFile("audiobuffer", ".raw");

            try (FileChannel channel = FileChannel.open(F.toPath(), StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
                channel.write(ByteBuffer.wrap(data));
                file = F;
                externalized = true;
                data = null;               
            }
        }
        
        /**
         * Schreibt Daten in den Block. Es kann passieren, dass der Datenblock die Daten nicht
         * komplett aufnehmen kann (weil seine Kapazität erschöpft ist).
         * Die Methode liefert die Anzahl Bytes zurück, die geschrieben wurden.
         */
        public int offer(byte [] srcBuffer, int offset, int len) {
            int rest = data.length - writepos;   // Wieviel Daten passen in diesen Block überhaupt noch hinein?
            int actualLen = Math.min(len, rest);
            System.arraycopy(srcBuffer, offset, data, writepos, actualLen);
            writepos += actualLen;
            size     += actualLen;
            return actualLen;
        }
        
        /**
         * Liest Daten aus dem Block. Wenn nicht soviel Daten vorhanden sind, wie gewünscht, werden nur soviel Daten geliefert,
         * wie vorhanden. Die Anzahl der gelesenen Daten wird zurückgeliefert.
         */
        public int read(byte [] destBuffer, int offset, int len) throws IOException {
            if (externalized) {
                internalize();
            }
            
            int actualLen = Math.min(len, size);
            System.arraycopy(data, readpos, destBuffer, offset, actualLen);
            readpos += actualLen;
            size -= actualLen;
            return actualLen;
        }
        
        private void internalize() throws IOException {
            try (FileChannel channel = FileChannel.open(file.toPath(), StandardOpenOption.READ)) {
                data = new byte[capacity];
                int actuallyRead = channel.read(ByteBuffer.wrap(data));
                if (actuallyRead != capacity) {
                    throw new IOException("invalid buffer file: could not read all data");
                }
                externalized = false;
            }
        }
        
        /**
         * Bytes ohne Einzulesen verwerfen. Liefert die tatsächlich verworfenen Bytes zurück. Das
         * können weniger als die angeforderten bytes sein.
         *
         * Wenn der Datenblock ausgelagert ist und die zu verwerfenden Bytes mindestens der Länge des
         * Datenblocks entsprechen, kann die Auslagerungsdatei direkt gelöscht werden. Ist die Anzahl
         * zu verwerfender Bytes kleiner als die Datenlänge, müssen die Daten ggf. erst eingeholt werden.
         */
        public long discard(long bytes) throws IOException {
            if (externalized && bytes >= capacity) {
                dispose();
                return capacity;
            } else {
                internalize();
                long actualLen = Math.min(bytes, size);
                readpos += actualLen;
                size -= actualLen;
                return actualLen;
            }
        }
        
        /**
         * Liefert true, wenn der Datenblock leergelesen ist. Leergelesen meint nicht, wenn die Nutzdatenlänge 0 ist,
         * sondern wenn die Leseposition am Ende dieses Blocks angekommen ist.
         */
        public boolean beenReadCompletely() {
            return readpos >= capacity;
        }
        
        /**
         * Liefert true, wenn der Datenblock keine Daten mehr aufnehmen kann.
         * @return
         */
        public boolean isCompletelyFilled() {
            return writepos >= capacity;
        }
        
        public void dispose() {
            if (file != null && file.exists()) {
                file.delete();
            }
        }
        
        /**
         * Convenience-Method. Fragt auch null ab.
         */
        public static boolean isExternalizeable(DataBlock block) {
            return block != null && block.isExternalizeable();
        }
    }
    
    /**
     * Die Queue bietet einen Zugriff auf den Tail. Außerdem überschreib sie clear, damit die DataBlocks ggf. ihre
     * Auslagerungsdateien löschen können.
     */
    private static class DataBlockQueue extends ConcurrentLinkedQueue<DataBlock> {
        private DataBlock tail = null;
        
        @Override
        public boolean offer(DataBlock e) {
            tail = e;
            return super.offer(e);
        }
        
        public DataBlock getTail() {
            return tail;
        }
        
        @Override
        public void clear() {
            DataBlock block;
            while ((block = poll()) != null) {
                block.dispose();
            }
            super.clear();     // pro Forma. Eigentlich nicht notwendig, super.clear() poll() aufruft, bis null
        }
    }
    
    private DataBlockQueue queue;
    private volatile long currentSize;
    private final long capacity;

    private int dataBlockSize;
    
    private final static long MEMORYTHRESHOLD = 1024*1024*300; // wenn weniger als soviel Speicher frei, dann auslagern   
    
    public BigBuffer(long capacity, int dataBlockSize) {
        this.capacity = capacity;
        this.dataBlockSize = dataBlockSize;
        currentSize = 0;
        queue = createQueue();
    }
    
    private DataBlockQueue createQueue() {
        return new DataBlockQueue();
    }
    
    private boolean notEnoughMemory() {
        return Runtime.getRuntime().freeMemory() < MEMORYTHRESHOLD;
    }
    
    /**
     * Fügt len Daten aus srcBuffer ab offset
     * am Tail des Puffers an.
     * @param buffer Quelle der Daten
     * @param offset offset innerhalb des srcBuffer
     * @param len Anzahl bytes aus dem srcBuffer
     * @throws IOException 
     */
    public void offer(byte [] srcBuffer, int offset, int len) throws IOException  {
        if (len + offset > srcBuffer.length) { throw new IllegalArgumentException(String.format("%d (len) + %d (offset) exceeding buffer's length (%d)", len, offset, srcBuffer.length)); }
        if (len > availableCapacity()) { throw new IllegalArgumentException(String.format("Data too large. Remaining capacity: %d bytes, data length: %d bytes", availableCapacity(), len)); }
        
        int resultLen = 0;
        int usingLen = len;
        
        do {
            DataBlock block = queue.getTail();
            if (block == null || block.isCompletelyFilled()) {  // war die queue noch leer oder ist der Block schon gefüllt
                
                if (DataBlock.isExternalizeable(block) && notEnoughMemory()) {
                    block.externalize();
                }
                
                queue.offer(block = new DataBlock(dataBlockSize));  // neuen Block erzeugen und anhängen
            }
            
            int actualLen = block.offer(srcBuffer, offset, usingLen);
            resultLen += actualLen;
            offset    += actualLen;  // sollte es einen weiteren Schleifendurchgang geben, muss der offset verschoben werden
            usingLen  -= actualLen;  // und die noch zu schreibende Datenlänge entspr. verringert
        } while (resultLen < len);
        
        currentSize += resultLen;
    }
    
    /**
     * Holt len Daten vom Kopf des Puffers und schreibt sie in den destBuffer ab dem gegebenen offset.
     * Die tatsächliche Länge kann niedriger sein als len, wenn weniger Daten zur Verfügung stehen. Kann auch 0 sein.
     * Die Anzahl gelesener Bytes wird zurückgeliefert.<br>
     * Liefert -1, wenn beim Lesen der Daten ein Fehler auftrat.
     */
    public int read(byte [] destBuffer, int offset, int len) throws IOException {
        if (len + offset > destBuffer.length) { throw new IllegalArgumentException(String.format("%d (len) + %d (offset) exceeding buffer's length (%d)", len, offset, destBuffer.length)); }
        
        int resultLen = 0;
        int usingLen = len;
        
        do {
            DataBlock block = queue.peek();      // den Kopf holen, aber noch in der Schlange lassen, da er beim nächsten read evtl. noch gebraucht wird
            if (block == null) break;            // gibt es keine weiteren Daten, dann Ende
        
            int actualLen = block.read(destBuffer, offset, usingLen);
            
            resultLen += actualLen;
            offset    += actualLen; // sollte es einen weiteren Schleifendurchgang geben, muss der offset verschoben
            usingLen  -= actualLen; // und die noch zu lesende Datenmenge entspr. verringert werden
            currentSize -= resultLen;
            
            if (block.beenReadCompletely()) {  // wenn der Block leergelesen ist, aus der Schlange entfernen
                queue.poll();
                block.dispose();
            } else {
                break;     // wenn der Block nicht leergelesen ist, dann stehen offenbar keine weiteren Daten mehr zur Verfügung und wir müssen ausbrechen
            }
        } while (resultLen < len);  // konnte der Datenblock noch nicht soviel Daten zur Verfügung stellen wie gewünscht, dann neuer Durchgang mit dem nächsten Block

        return resultLen;
    }
    
    public void clear() {
        currentSize = 0;
        Queue<?> oldQueue = queue;

        // da das Leeren der Queue etwas teuer ist, wird eine neue,
        // leere Queue erzeugt und die alte im Hintergrund geleert. Das kann man nicht dem
        // GarbageCollector überlassen, da evtl. ausgelagerte Blöcke auch von der Harddisk
        // entfernt werden müssen.
        queue = createQueue();
        
        new Thread(() -> oldQueue.clear()).start();
    }

    public long capacity() {
        return capacity;
    }

    public long availableCapacity() {
        return capacity - currentSize;
    }

    /**
     * Verwirft die gegebene Anzahl Bytes, ohne sie zu lesen.
     * Liefert zurück, wieviel Bytes tatsächlich verworfen wurden. Das kann weniger sein.
     */
    public long discard(long bytes) throws IOException {
        long resultLen = 0;
        long usingLen = bytes;
        
        do {
            DataBlock block = queue.peek();      // den Kopf holen, aber noch in der Schlange lassen, da er beim nächsten read evtl. noch gebraucht wird
            if (block == null) break;            // gibt es keine weiteren Daten, dann Ende
        
            long actualLen = block.discard(usingLen);
            resultLen += actualLen;
            usingLen  -= actualLen;
            currentSize -= resultLen;
            
            if (block.beenReadCompletely()) {  // wenn der Block leergelesen ist, aus der Schlange entfernen
                queue.poll();
                block.dispose();  // ggf. die Auslagerungsdatei löschen.
            } else {
                break;     // wenn der Block nicht leergelesen ist, dann stehen offenbar keine weiteren Daten mehr zur Verfügung und wir müssen ausbrechen
            }
        } while (resultLen < bytes);  // konnte der Datenblock noch nicht soviel Daten zur Verfügung stellen wie gewünscht, dann neuer Durchgang mit dem nächsten Block

        return resultLen;
    }

    public long size() {
        return currentSize;
    }

}
 

insert2020

Aktives Mitglied
Ganz so trivial ist es dann doch nicht, da der Buffer bei fast jedem Lesevorgang geflusht werden müsste:
Java:
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;

public class MyBuffer {
	private static final int RAM_BUFFER_SIZE = 1024 * 1024 * 1024;
	private BufferedOutputStream buffer1 = null;
	private BufferedInputStream buffer2 = null;
	private boolean hasInput = false;

	public MyBuffer() throws IOException {
		buffer1 = new BufferedOutputStream(new FileOutputStream(new File("my_backup_file.txt")), RAM_BUFFER_SIZE);
		buffer2 = new BufferedInputStream(new FileInputStream(new File("my_backup_file.txt")), 1024);
	}

	public synchronized void write(byte b) throws IOException {
		buffer1.write(b);
		hasInput = true;
	}

	public synchronized int read() throws IOException {
		if (hasInput) {
			buffer1.flush();
			hasInput = false;
		}
		return buffer2.read();
	}

	public static void main(String[] args) throws IOException {
	}
}
 

mrBrown

Super-Moderator
Mitarbeiter
Ja, da warst Du etwas schneller. Ich habe aber sogar die "optimierte" Variante mal herunter geschrieben. Da kann man dann einmalig Speicher reservieren und dann ganz ohne GC weiter arbeiten. Dann ist es sogar denkbar, andere GC zu probieren. Wenn man sicher ist, dass man den GC nicht braucht, dann kann man die Variante ohne GC versuchen und verifizieren, dass die Annahme richtig ist. Minimaler Speicherzuwachs ist ggf. vertretbar, da ja so Aufnahmen nur begrenzte Zeit laufen dürften (Annahme von meiner Seite - müsste verifiziert werden).
Ja, das wäre das erste, was man da machen würde. Queue mit fester Größe und Pool für etwaige Arrays – hatte ich sogar erst da stehen :D

Japp, so hab ich's letztlich gemacht. Etwas feiner muß man es dann noch betrachten: die Blöcke, die das Audiointerface liefert, sind meist sehr kurz, ca. 1 Sekunde. Ich sammle mehrere solcher Blöcke zusammen in einen größeren Datenblock. Sobald der voll ist, kann er ggf. ausgelagert werden.
Das "zusammenfassen" ist der erste Schritt bei mir, eben Daten solange "vorpuffern", bis X MB oder X Sekunden da sind :)
 
G

Gelöschtes Mitglied 9001

Gast
Ja, das wäre das erste, was man da machen würde. Queue mit fester Größe und Pool für etwaige Arrays – hatte ich sogar erst da stehen :D

Das "zusammenfassen" ist der erste Schritt bei mir, eben Daten solange "vorpuffern", bis X MB oder X Sekunden da sind :)
Verstehe. Die vorgepufferten Daten sollten im besten Fall freilich aber auch schon zum Auslesen zu Verfügung stehen. Deshalb lege ich "unfertige" Datenblöcke auch schon in die Queue.
 

mrBrown

Super-Moderator
Mitarbeiter
Verstehe. Die vorgepufferten Daten sollten im besten Fall freilich aber auch schon zum Auslesen zu Verfügung stehen. Deshalb lege ich "unfertige" Datenblöcke auch schon in die Queue.

In der aktuellen Implementation gibts da wenn ich's richtig sehe Probleme, die unfertigen Blöcke sind nicht threadsafe und auch bei nur einem Thread gehen damit Daten verloren. Teste einfach mal mit halben Block schreiben, dann lesen, dann wieder halben Block schreiben.
 
G

Gelöschtes Mitglied 9001

Gast
In der aktuellen Implementation gibts da wenn ich's richtig sehe Probleme, die unfertigen Blöcke sind nicht threadsafe und auch bei nur einem Thread gehen damit Daten verloren. Teste einfach mal mit halben Block schreiben, dann lesen, dann wieder halben Block schreiben.
Ich habe vorhin gerade eine längere Tonaufnahme gemacht und den Client dabei unregelmäßig zufällig bei seinen Anfragen verzögert, um eine schlechte Verbindung zu simulieren. Die Aufnahme gelang einwandfrei.
Auch ein Test mit Server und Client auf ein und demselben Computer ohne simulierte Verzögerung gelang problemlos.
 

Oben