# Riesenringpuffer



## Gelöschtes Mitglied 9001 (24. Jul 2020)

Hallo Forum,

wie man einen Ringpuffer implementiert, ist mir klar, schon gefühlt hundertmal gemacht. Nun sind die Bedingungen aber etwas ungünstig: der Puffer soll bis zu einige Gigabyte umfassen (~5GB), der verfügbare Arbeitsspeicher ist < 1GB, Festplatte ist eine Micro-SD-Karte (ja, es ist ein Raspi).
Bei jedem Schreib- und Lesevorgang werden Datenblöcke von ca 1-2 MB in den Puffer eingefügt/herausgeholt. Mein erster Gedanke war natürlich, FileChannel zu verwenden. Dann erinnerte ich mich, dass SD-Karten es nicht so sehr mögen, häufig beschrieben zu werden. Also müßte man wenigstens teilweise den Puffer im Arbeitsspeicher vorhalten. 
Die (Audio-)Eingabedaten kommen von einem Audiointerface in schneller Folge in den Puffer. Die Daten werden von einem Server-Prozess aus dem Puffer herausgeholt und an einen Client ausgeliefert, der per Wlan verbunden ist. Diese Verbindung kann sehr gut sein - in dem Fall würden wohl nur einige MB des Ringpuffers belegt sein. Die Verbindung kann aber auch sehr schlecht sein und gelegentlich unterbrochen werden. Für genau diesen Fall muß der Puffer sehr groß sein, um sicherzugehen, dass keine Daten verloren gehen. (Dass die Audiolatenz dann enorm ist, ist richtig, spielt aber bei meinem Anwendungszweck keine Rolle).

Mich würden eure Gedanken dazu interessieren. Ist das mit den SD-Karten immer noch so? Gibt es bereits vorhandene Ansätze für irgendwie geartete zweistufige Ringpuffer, die teils im Ram, teils auf dem Dateisystem agieren?


----------



## Thallius (24. Jul 2020)

Ich frage mich wie du auf 5GB kommst. Das sind ja fast 20 Stunden unkomprimiertes Audio in CD Qualität..... So schlecht kann die Lazenz ja wohl gar nicht sein....


----------



## Gelöschtes Mitglied 9001 (24. Jul 2020)

6 Kanäle, 24bit, 96 kHz. 5GB sind da ca. 51 Minuten. Da ich den Aufnahmeprozess nicht überwachen kann, da ich selbst spiele, muss das System einen ausreichend großen Puffer haben für den Fall, dass unbemerkt die Verbindung abbrach, was bei einer Entfernung von 30m durchaus passieren kann.
Der Raspi hat 1 GB Ram, der natürlich dem Programm nicht in Gänze zur Verfügung steht, da ja auch noch andere Programme laufen. Gehen wir mal von 500MB benutzbarem Speicher aus, sind das dann nur noch 5 Minuten Puffer. Für den Ernstfall zu wenig, wenn z.B. das aufzunehmende Stück 12 Minuten dauert und die Verbindung nach 4 Minuten unbemerkt abbrach.
Daher suche ich also eine effiziente Möglichkeit, einen Ringpuffer teilweise auf der SD-Karte liegen zu haben.


----------



## Thallius (24. Jul 2020)

und warum kompremierst du die daten nicht vorm speichern? Oder brauchst du unbedingt raw format?


----------



## Gelöschtes Mitglied 9001 (24. Jul 2020)

Ja, ich brauche unbedingt raw.
Ich würd mich wirklich gerne eher über das Thema Ringpuffer unterhalten als über Sinn und Zweck, warum und wieso ich meine Tonaufnahme mache.


----------



## LimDul (24. Jul 2020)

Was du probieren könntest, anstelle das Problem in Java zu lösen - einfach 10 GB Swap Space anlegen und der JVM entsprechend RAM zur Verfügung zu stellen. Dann löst das OS das Problem für dich..


----------



## Thallius (24. Jul 2020)

Rajmund hat gesagt.:


> Ja, ich brauche unbedingt raw.
> Ich würd mich wirklich gerne eher über das Thema Ringpuffer unterhalten als über Sinn und Zweck, warum und wieso ich meine Tonaufnahme mache.



Ich mag es wenn Leute so überheblich sind, daß sie nicht mal darüber nachdenken das ihr Konzept vielleicht scheiße sein könnte.


----------



## LimDul (24. Jul 2020)

Naja, ich kann die Anforderung schon verstehen, dass man die RAW-Daten übertragen möchte. Und 5 GB sind jetzt auch nicht viele Daten.

Wenn man es in Java umsetzen würde, würde ich es wie folgt machen:

* Einen Ringpuffer der tatsächlich komplett über FileChannel geht. 
* Da eine CachedRingpuffer drum wrappen, der einen Cache von X Megabyte hat und wie folgt arbeitet:

*Beim Schreiben*
- Wenn der FileChannelPuffer nicht leer ist => FileChannel Schreiben
- Wenn der FileChannelPuffer leer ist und Platz im Cache => In den Cache hinten anhängen
- Wenn  der FileChannelPuffer leer ist und zuwenig Platz im Cache => FileChannel Schreiben

*Beim Lesen*
* Aus dem Cache lesen
* Danach, wenn der FileChannelPuffer nicht leer ist, den Cache aus dem FileChannelPuffer wieder auffüllen bis entweder Cache voll oder FileChannelPuffer leer

Etwas aufpassen muss man mit MultiThreading, dass das klappt. Ggf. muss man da mit einer gemeinsamen Semaphore arbeiten.


----------



## Gelöschtes Mitglied 9001 (24. Jul 2020)

LimDul hat gesagt.:


> Was du probieren könntest, anstelle das Problem in Java zu lösen - einfach 10 GB Swap Space anlegen und der JVM entsprechend RAM zur Verfügung zu stellen. Dann löst das OS das Problem für dich..


Ja, darüber habe ich nachgedacht. Jedoch: Gängige Ringpufferimplementationen arbeiten ja auf einem linearen Speicher und wandern durch den von vorn bis hinten durch. Selbst wenn die Nutzdaten nur einen Bruchteil der Kapazität des Speichers betragen, wird so der gesamte Speicher genutzt, was dazu führt, dass das Betriebssystem permanent auch die ungenutzten Daten zwischen Harddisk und Ram hin und herschaufeln wird. Ich glaube nicht, dass das effizient sein wird.


----------



## LimDul (24. Jul 2020)

Rajmund hat gesagt.:


> Ja, darüber habe ich nachgedacht. Jedoch: Gängige Ringpufferimplementationen arbeiten ja auf einem linearen Speicher und wandern durch den von vorn bis hinten durch. Selbst wenn die Nutzdaten nur einen Bruchteil der Kapazität des Speichers betragen, wird so der gesamte Speicher genutzt, was dazu führt, dass das Betriebssystem permanent auch die ungenutzten Daten zwischen Harddisk und Ram hin und herschaufeln wird. Ich glaube nicht, dass das effizient sein wird.


Stimmt, du bräuchtest dann eine Implementierung, die den Speicher dynamisch verwaltet z.B. in Form einer doppelt verketteten Liste (damit man hinten anhängen kann und vorne wegnehmen kann). Das wäre dann allerdings kein Ringpuffer mehr sondern ein potentiell unendlich großer Puffer.


----------



## Gelöschtes Mitglied 9001 (24. Jul 2020)

LimDul hat gesagt.:


> * Einen Ringpuffer der tatsächlich komplett über FileChannel geht.
> * Da eine CachedRingpuffer drum wrappen, der einen Cache von X Megabyte hat und wie folgt arbeitet:
> 
> *Beim Schreiben*
> ...


Das klingt vielversprechend! Werde ich ausprobieren.


----------



## Gelöschtes Mitglied 9001 (24. Jul 2020)

LimDul hat gesagt.:


> Stimmt, du bräuchtest dann eine Implementierung, die den Speicher dynamisch verwaltet z.B. in Form einer doppelt verketteten Liste (damit man hinten anhängen kann und vorne wegnehmen kann). Das wäre dann allerdings kein Ringpuffer mehr sondern ein potentiell unendlich großer Puffer.


Das würde gehen. Man müßte die ankommenden Daten in Blöcke aufteilen, die nicht zu groß und nicht zu klein sind. Und damit das ganze nicht ins Unendliche wächst, die Menge mit einem Zähler überwachen und ggf. Datenblöcke verwerfen. Oder man fragt den verfügbaren Speicher ab, das könnte aber evtl. etwas teurer sein.


----------



## LimDul (24. Jul 2020)

Ja, ich denke die einzelnen Listen-Elemente könnten z.B. 1 MB Blöcke sein, die dann als Cache funktionieren. Und ein Listenelement wird erst entfernt, wenn es komplett geleert ist.


----------



## Gelöschtes Mitglied 9001 (24. Jul 2020)

Praktischerweise überläßt man das Auslagern, wie du schon schriebst, dem Betriebssystem oder aber man lagert die frischsten Blöcke manuell aus, wenn der Speicher knapp wird, vermerkt im Block den Verweis und holt die Daten wieder ein, wenn sie gelesen werden sollen.


----------



## Gelöschtes Mitglied 9001 (24. Jul 2020)

Gerade festgestellt: man kann beim Starten eines Java-Programmes den Heap-Speicher nicht auf einen größeren Wert einstellen als physikalisch vorhanden ist. Also muss man das Auslagern selbst übernehmen.


----------



## insert2020 (24. Jul 2020)

1 Minute MP3 entsprechen 2,33 MB...
5 GB reichen also für fast 36 Stunden...
Ich denke nicht, dass du einen 36-Stunden-Puffer haben willst. Das Problem ist hier also ganz klar beim encoding zu sehen.

Ansonsten is ByteBuffer.allocateDirect() dein Freund: https://stackoverflow.com/questions...use-swap-no-matter-how-big-the-memory-require


----------



## temi (24. Jul 2020)

insert2020 hat gesagt.:


> 1 Minute MP3 entsprechen 2,33 MB...
> 5 GB reichen also für fast 36 Stunden...
> Ich denke nicht, dass du einen 36-Stunden-Puffer haben willst. Das Problem ist hier also ganz klar beim encoding zu sehen.





Rajmund hat gesagt.:


> Ja, ich brauche unbedingt raw.


----------



## insert2020 (24. Jul 2020)

@temi In 99% der Fälle liegt es an der fehlenden Komprimierung.


----------



## temi (24. Jul 2020)

insert2020 hat gesagt.:


> @temi In 99% der Fälle liegt es an der fehlenden Komprimierung.





Rajmund hat gesagt.:


> Ja, ich brauche unbedingt raw.


----------



## Gelöschtes Mitglied 9001 (24. Jul 2020)

Es geht hier um eine professionelle Tonaufnahme mit 6 Mikrofonen = 6 Kanäle in 24bit und 96kHz Auflösung. MP3 hat im professionellen Umfeld nichts zu suchen und ist vom Fraunhofer Institut (=Erfinder von MP3) schon lange als veraltet bezeichnet worden. Bei Tonaufnahmen wird einzig und allein unkomprimierter Ton verwendet.


----------



## kneitzel (25. Jul 2020)

Nur einmal ganz kurz als Anmerkung ohne viel Text:
MP3 (mpeg-1/2 Audio Layer 3) ist eine verlustbehaftete Komprimierung.

Verlustbehaftete Komprimierungen haben im professionellen Bereich generell nie etwas verloren.

Man könnte sich maximal überlegen, da statt raw eine verlustfrei Komprimierung zu verwenden. Aber ich bezweifle, dass der PI da 6 Kanäle gleichzeitig ver/entschlüsseln kann.

Generell würde ich da die Hardwarebasis in Frage stellen. Was spricht gegen einen kleinen PC? NuCs und Co lassen grüßen. Da hast du dann problemlos 8GB Speicher und deutlich mehr Performance.
Kosten sind auch nicht so wild - zur Not holt man sich Leasing Rückläufig denn 2-3 Jahre alte Geräte dürften dem pi überlegen sein ...


----------



## Gelöschtes Mitglied 9001 (25. Jul 2020)

Ich würde gerne beim Thema Puffer bleiben und hier nicht weiter über Audiotechnik diskutieren. Algorithmen zur Pufferung können ja auch in anderen Bereich verwendet werden. Das Thema Ringpuffer ist also unabhängig von Einsatzzweck interessant.

Wenn jemand hier im Forum was zum Thema Ringpuffer sucht und diesen Thread anklickt, würde er dann hier auf einen Thread zum Thema Audio stoßen, das wäre nicht sehr hilfreich. Da bitte ich alle Diskussionsteilnehmer um Verständnis.

Mein derzeitiger RAM-only Ringpuffer funktioniert einwandfrei und ich bekomme die Daten per Wlan ohne weitere Probleme. Die eingangs gestellte Fragestellung lautete, wie man einen effizienten Puffer erstellt, der deutlich größer als der verfügbare RAM ist.

Einfach auf den Swap-Mechanismus des OS vertrauen funktioniert nicht, wie schon dargestellt. LimDul hat hier bereits zwei gute Impulse geliefert.


----------



## Meniskusschaden (25. Jul 2020)

Rajmund hat gesagt.:


> Ich würde gerne beim Thema Puffer bleiben und hier nicht weiter über Audiotechnik diskutieren. Algorithmen zur Pufferung können ja auch in anderen Bereich verwendet werden. Das Thema Ringpuffer ist also unabhängig von Einsatzzweck interessant.
> 
> Wenn jemand hier im Forum was zum Thema Ringpuffer sucht und diesen Thread anklickt, würde er dann hier auf einen Thread zum Thema Audio stoßen, das wäre nicht sehr hilfreich. Da bitte ich alle Diskussionsteilnehmer um Verständnis.


Diese Bemerkungen finde ich schon etwas seltsam, zumal du das Thema Audiotechnik selbst im Thread eingeführt hast, und zwar bereits im Eröffnungsposting. Ist doch logisch, dass man darauf eingeht. Wer später mal auf diesen Thread stößt, könnte auch durchaus an den Lösungsalternativen interessiert sein.
Zudem betonst du einerseits den professionellen Anspruch, stellst aber einen schlappen Raspi in's Zentrum des Ganzen, an dem nun alles hängt. Da ist es ja nicht allzu abwegig, hier ein XY-Problem als mögliche Ursache in Betracht zu ziehen.


----------



## Gelöschtes Mitglied 9001 (25. Jul 2020)

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.


----------



## temi (25. Jul 2020)

Sorry, aber du löst ein leichtes kopfschüttelndes Unverständnis bei mir aus.

Natürlich ist das Thema der Ringpuffer, aber genauso ist es häufig so, dass der Lösungssuchende für sein konkretes Problem einen ungünstigen Weg eingeschlagen hat. Deshalb ist es durchaus angebracht und hier auch üblich, an dieser Stelle einmal nachzufragen, um ggf. eine bessere Lösung zu finden. Und vielleicht ist auch ein Vorschlag dabei, den du bisher noch nicht in Betracht gezogen hattest. Das du dir schon viele Gedanken darüber gemacht hast und deine Lösung vielleicht auch schon die beste ist, sei dir da unbenommen.

Die Art und Weise, wie du hier kommunizierst finde ich, sagen wir mal, gewöhnungsbedürftig.


----------



## mihe7 (25. Jul 2020)

Mal zum Ringbuffer: ich verstehe das Problem ehrlich gesagt nicht. Im Endeffekt brauchst Du doch nur einen Ringbuffer auf Platte. Je nachdem könnte man sich überlegen, das intern als Ringbuffer von Ringbuffern zu realisieren, wobei jeder persistente Ringbuffer dann z. B. durch ein Memory Mapped File realisiert wird. Aber grundsätzlich sehe ich da keinen Unterschied zum In-Memory-Ringbuffer.


----------



## Gelöschtes Mitglied 9001 (25. Jul 2020)

Man kann sich als Programmierer die Bedingungen nicht immer aussuchen. Wenn die Deutsche Bahn einen Java-Programmierer um einen Fahrplanalgorithmus bittet, kann dieser schlecht antworten: Schienenverkehr ist generell ungeeignet, nehmt selbstfahrende Autos.


----------



## temi (25. Jul 2020)

Rajmund hat gesagt.:


> Man kann sich als Programmierer die Bedingungen nicht immer aussuchen.


Damit hast du völlig Recht, aber das Eine hat mit dem Anderen nichts zu tun, weil wir nur Vorschläge machen und Ideen äußern können, aber nicht wissen, ob und welche Vorgaben du hast. Du kannst das ja einfach verwerfen, wenn es für dich nicht in Frage kommt.


----------



## LimDul (25. Jul 2020)

Umgekehrt gilt aber auch - man sollte nicht alles blind akzeptieren, was gefordert wird. Den oft kommt der Auftraggeber (hier du) mit einer Lösungsidee (hier Ringpuffer) an, obwohl eigentlich eine andere Lösung (hier Intel NUC) besser ist. Und da ist es durchaus sinnvoll drauf hinzuweisen. Das unterscheidet einen fähigen Software-Entwickler von einem einfachen Programmierer. Er hinterfragt die Anforderungen des Kunden und bietet Alternativen.


----------



## kneitzel (25. Jul 2020)

Rajmund hat gesagt.:


> Man kann sich als Programmierer die Bedingungen nicht immer aussuchen. Wenn die Deutsche Bahn einen Java-Programmierer um einen Fahrplanalgorithmus bittet, kann dieser schlecht antworten: Schienenverkehr ist generell ungeeignet, nehmt selbstfahrende Autos.


Der Vergleich hinkt total! In der Praxis ist es leider mehr als notwendig, das man Anforderungen auch überprüft. Und das ist leider oft genug notwendig und Kunden freuen sich über kompetente Beratung.

Wie wurde das genannt? XY Problem? Du hast das Problem X, bist dann auf Y als Lösung gekommen und hast damit Probleme? Y ist aber absoluter Quatsch und nicht wirklich eine Lösung für X!

Du hast selbst schon erkannt, dass der PI da schlicht ungeeignet ist um Deine Anforderungen zu erfüllen. Denn sowohl die Pufferung rein im Hauptspeicher funktioniert nicht und Puffern auf SD Karte beim PI? Also eine SD Karte ist diesbezüglich extrem ungeeignet. Sowohl die Schreib/Lesegeschwindigkeit ist schon durchaus kritisch aber auch die Anzahl der Schreibzyklen pro Speicherzelle ist zu gering aus meiner Sicht.

Und natürlich ist ein Wechsel auf eine alternative Hardware möglich. Denn einen Pi durch einen anderen Rechner zu ersetzen ist ein minimaler Aufwand (<200€).

Aber wenn man sich das eigentliche Problem X von Dir anschauen würde und nicht auf Y rumreiten würde:
Wer verarbeitet denn die Daten? Sicher, das man da das nicht mit integrieren kann? So eine Pufferung ist 08/15 und findet an mehreren Stellen im Betriebssystem statt. Das Betriebssystem kann also sehr wohl Daten empfangen auch wenn eine Applikation da nicht hinterher kommt. Und dann hätte man da ggf. genug Speicher oder eben entsprechende Plattensysteme.


----------



## insert2020 (25. Jul 2020)

Rajmund hat gesagt.:


> MP3 hat im professionellen Umfeld nichts zu suchen und ist vom Fraunhofer Institut (=Erfinder von MP3) schon lange als veraltet bezeichnet worden


Ich könnte dir MP3 und Flac vorspielen und du würdest den Unterschied nicht einmal merken. So viel zu "veraltet". Weiterhin bedeutet Kompression nicht gleich Informationsverlust ... Also überleg einfach ein bisschen was du schreibst.


----------



## LimDul (25. Jul 2020)

insert2020 hat gesagt.:


> Ich könnte dir MP3 und Flac vorspielen und du würdest den Unterschied nicht einmal merken. So viel zu "veraltet". Weiterhin bedeutet Kompression nicht gleich Informationsverlust ... Also überleg einfach ein bisschen was du schreibst.


Wenn es um Aufnahmen aus einem Tonstudio geht ist MP3 Schwachsinn. Es ist verlustbehaftet und man will die Originaldaten behalten - zur Weiterverarbeitung und Co. Sorry, MP3 ist bei der Anforderung schlicht Unfug.


----------



## Gelöschtes Mitglied 9001 (25. Jul 2020)

Mir gefallen die Ausgangsbedingungen auch nicht, aber sie sind nunmal gegeben und ich kann an denen leider nichts ändern. Meine Aufgabe ist es, das System so stabil wie möglich gegen Datenverlust zu machen. Wer der Meinung ist, dass das mit den Bedingungen grundsätzlich nicht geht, ok. Wer noch Vorschläge für Algorithmen hat - ich freue mich über fachlichen Austausch.


----------



## insert2020 (25. Jul 2020)

LimDul hat gesagt.:


> Wenn es um Aufnahmen aus einem Tonstudio geht ist MP3 Schwachsinn. Es ist verlustbehaftet und man will die Originaldaten behalten - zur Weiterverarbeitung und Co. Sorry, MP3 ist bei der Anforderung schlicht Unfug.


A wenn es um Tonstudioaufnahmen geht, wieso setzt man dann Raspi ein?
B wie ich schon geschrieben habe gibt es auch verlustfreie Komprimierungsformate...


----------



## insert2020 (25. Jul 2020)

Rajmund hat gesagt.:


> ich freue mich über fachlichen Austausch


siehe #16, den du bisher gekonnt ignoriert hast


----------



## Gelöschtes Mitglied 9001 (25. Jul 2020)

insert2020 hat gesagt.:


> siehe #16, den du bisher gekonnt ignoriert hast


...weil bereits viel früher klar war, um welches Audioformat es geht und du immer noch mit Stereo und mp3 ankamst


----------



## kneitzel (25. Jul 2020)

Die Anforderung selbst ist bisher noch nicht im Detail erläutert worden. Es fallen Daten aus 6 Quellen an, die dann über einen pi laufen sollen?

Pi ist in meinen Augen eine reine Frickellösung. Zu Basteln nett und billig aber im professionellen Bereich? Da sehe ich den pi nicht.

Was für ein System nimmt die Daten entgegen? Über welchen Weg? Da würde ich ggf. ansetzen um es zu optimieren. Betriebssysteme lassen sich oft für so Dinge optimieren und/oder eben der höher priorisierte Prozess, der das dann macht. Dann braucht man keinen pi.

Und wenn pi / kleineres embedded Gerät: da wäre mein Ansatz eher ein RTOS mit c/c++ Lösung. Sonst hast du das Risiko, das andere Dinge, die du nicht brauchst, dir dazwischen funken...


----------



## Gelöschtes Mitglied 9001 (25. Jul 2020)

Das Programm holt sich die Daten von einem Audiointerface und puffert sie (der Puffer der drunterliegenden Schicht ist definitiv zu klein: 1 Sekunde). Ein Serverprozeß liefert die Daten an einen Client aus, der per Wlan verbunden ist. Da der Client in einer Entfernung von 10-30m steht, ist die Verbindung potenziell schwach, daher ist ein sehr großer Puffer notwendig. Das funktioniert alles bislang einwandfrei. Puffergröße aktuell ca. 5min. Das Betriebssystem ist bereits auf ein Minimum reduziert. Die Geschwindigkeit des Computers ist kein Problem und reicht aus.


----------



## Gelöschtes Mitglied 9001 (25. Jul 2020)

temi hat gesagt.:


> Damit hast du völlig Recht, aber das Eine hat mit dem Anderen nichts zu tun, weil wir nur Vorschläge machen und Ideen äußern können, aber nicht wissen, ob und welche Vorgaben du hast. Du kannst das ja einfach verwerfen, wenn es für dich nicht in Frage kommt.


Die Vorgaben stehen im Eingangspost.


----------



## temi (25. Jul 2020)

Nachteil des PI ist halt immer noch der kleine Speicher. Muss es der PI sein, dann ist es halt so, aber es gibt zig Boards mit Onboard-CPU mit sehr kleinem Formfaktor, die sowohl größeren Speicher, als auch eine SATA-Schnittstelle bieten und für um die 50€ zu haben sind.

Aber wie gesagt, wenn der PI als absolut gesetzt ist, dann soll es so sein.


----------



## temi (25. Jul 2020)

Rajmund hat gesagt.:


> Das Programm holt sich die Daten von einem Audiointerface und puffert sie (der Puffer der drunterliegenden Schicht ist definitiv zu klein: 1 Sekunde). Ein Serverprozeß liefert die Daten an einen Client aus, der per Wlan verbunden ist. Da der Client in einer Entfernung von 10-30m steht, ist die Verbindung potenziell schwach, daher ist ein sehr großer Puffer notwendig. Das funktioniert alles bislang einwandfrei. Puffergröße aktuell ca. 5min. Das Betriebssystem ist bereits auf ein Minimum reduziert. Die Geschwindigkeit des Computers ist kein Problem und reicht aus.


Das Problem ist also die zu kleine Puffergröße von 5 min? Der Rest scheint ja zu passen.

Und die WLAN-Verbindung ist offenbar zwingend erforderlich und der Grund für den PI in der beschriebenen Funktion?

Auslagern auf eine USB-Platte anstatt SD-Karte?


----------



## Gelöschtes Mitglied 9001 (25. Jul 2020)

Ja, ich hätte gerne einen deutlich größeren Puffer. Wlan ist deshalb zwingend, weil das Setup portabel sein muss und es extrem unpraktikabel ist, jedesmal 30m Kabel ab- und aufzuwickeln.

Vielleicht ist es gut, das Thema doch abstrakter zu betrachten, so wie ich mir das eigentlich erhofft hatte:
Ein Ringpuffer soll auf zwei Medien agieren. Eines ist klein und schnell, eines groß aber langsam. Nach Möglichkeit soll hauptsächlich der kleine schnelle verwendet werden. Wenn aber mehr Daten gepuffert werden müssen, der langsame. Wie schon beschrieben ist des Betriebssystems Swapping nicht hilfreich.
Gefragt ist nun ein Algorithmus, der möglichst selten den langsamen Speicher berührt.


----------



## temi (25. Jul 2020)

Evtl. solltest du doch über ein *verlustfreie* Audiokomprimierung nachdenken, so dass der schnelle Speicher ausreicht. Die Daten können ja vielleicht auch in der komprimierten Form an den Client weitergegeben werden (und nötigenfalls dort wieder dekomprimiert werden). Fragt sich halt, ob die Leistung des PI dafür ausreicht.

Hier mal ein Link, aber ich nehme an, dass du davon eh mehr Ahnung hast als ich.

EDIT: Ich möchte auch noch erwähnen, dass der Raspberry 4 mit 8 GB RAM erhältlich ist.


----------



## insert2020 (25. Jul 2020)

Das sollte eigentlich funktionieren, wenn nicht noch "gravierende" Fehler drin sind:

```
import java.nio.ByteBuffer;

public class MyBuffer {
	private static final int size_extern = 25;
	private static final int size_intern = 10;
	private ByteBuffer buffer_intern = ByteBuffer.allocate((int) size_intern);
	private ByteBuffer buffer_extern = ByteBuffer.allocateDirect((int) size_extern);
	private int i1 = 0;
	private int i2 = 0;

	public synchronized void write(byte b) {
		if (i1 == size_intern) {
			for (int i = 0; i < size_intern; i++) {
				if (i2 == size_extern) {
					i2 = 0;
				}
				buffer_extern.put(i2, buffer_intern.get(i));
				i2++;
			}
			buffer_intern.clear();
			i1 = 0;
		}
		buffer_intern.put(i1, b);
		i1++;
	}

	public synchronized byte read() {
		if (i1 == 0) {
			for (int i = 0; i < size_intern; i++) {
				if (i2 == 0) {
					i2 = size_extern;
				}
				i1++;
				i2--;
				buffer_intern.put(size_intern - i1, buffer_extern.get(i2));
			}
		}
		i1--;
		return buffer_intern.get(i1);
	}

	public static void main(String[] args) {
		MyBuffer buffer = new MyBuffer();
		for (int i = 0; i <= 35; i++) {
			buffer.write((byte) i);
			System.out.println((byte) i);
		}
		System.out.println(buffer.buffer_intern);
		System.out.println(buffer.buffer_extern);
		for (int i = 0; i <= 35; i++) {
			System.out.println(buffer.read());
		}
		System.out.println();
		for (int i = 0; i <= 10; i++) {
			buffer.write((byte) i);
			System.out.println((byte) i);
		}
		System.out.println(buffer.buffer_intern);
		System.out.println(buffer.buffer_extern);
		for (int i = 0; i <= 10; i++) {
			System.out.println(buffer.read());
		}
	}
}
```
Mir ist jetzt beim Testen kein Fehler aufgefallen.
statt 25 und 10 wählst du eben 5 GB... und wenn mehrere Byte auf einmal hinzugefügt werden sollen, dann würde ich noch zwei weitere Methoden dafür schreiben...


----------



## Gelöschtes Mitglied 9001 (25. Jul 2020)

Danke. Das schau ich mir heute Abend gleich mal genauer an.


----------



## temi (25. Jul 2020)

temi hat gesagt.:


> Evtl. solltest du doch über ein *verlustfreie* Audiokomprimierung nachdenken, so dass der schnelle Speicher ausreicht. Die Daten können ja vielleicht auch in der komprimierten Form an den Client weitergegeben werden (und nötigenfalls dort wieder dekomprimiert werden). Fragt sich halt, ob die Leistung des PI dafür ausreicht.


Falls es nicht sowieso schon offensichtlich ist: Dadurch reduziert sich gleichzeitig auch das Datenvolumen auf der WLAN-Verbindung, was sich ja durchaus positiv auswirken würde.


----------



## insert2020 (25. Jul 2020)

Rajmund hat gesagt.:


> Das schau ich mir heute Abend gleich mal genauer an


`buffer_intern.clear();` braucht es nicht, und wie gesagt.... nicht intensiv getestet 

... Du solltest konstruktive Kritikvorschläge auch annehmen... wir reden nicht alle Blödsinn.


----------



## httpdigest (25. Jul 2020)

Kurzer Hinweis noch: Du solltest nicht einen API Aufruf pro individuellem Byte tun (Hint: die NIO Buffer API ist sehr ineffizient, was API Aufrufe angeht, aufgrund der Indexrange-Checks bei jedem Aufruf). Du solltest lieber die Bulk-Operationen put(ByteBuffer) bzw. get(ByteBuffer) nutzen und dann entsprechend die Bereiche, aus denen im Buffer gelesen/geschrieben wird, per limit()/remaining()/position() steuern.
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.


----------



## insert2020 (25. Jul 2020)

So... da ist nämlich doch noch ein Fehler, die Buffer-Größe ist nicht zu jedem Zeitpunkt konstant 25+10=35:

```
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;

public class MyBuffer {
	private static final int size_extern = 25;
	private static final int size_intern = 10;
	private ByteBuffer buffer_intern = ByteBuffer.allocate((int) size_intern);
	private ByteBuffer buffer_extern = ByteBuffer.allocateDirect((int) size_extern);
	private int i1 = 0;
	private int i2 = 0;

	public synchronized void write(byte b) {
		if (i1 == size_intern) {
			for (int i = 0; i < size_intern; i++) {
				if (i2 == size_extern) {
					i2 = 0;
				}
				buffer_extern.put(i2, buffer_intern.get(i));
				i2++;
			}
			i1 = 0;
		}
		buffer_intern.put(i1, b);
		i1++;
	}

	public synchronized byte read() {
		if (i1 == 0) {
			for (int i = 0; i < size_intern; i++) {
				if (i2 == 0) {
					i2 = size_extern;
				}
				i1++;
				i2--;
				buffer_intern.put(size_intern - i1, buffer_extern.get(i2));
			}
		}
		i1--;
		return buffer_intern.get(i1);
	}

	public static void main(String[] args) {
		Random r = new Random(0);
		MyBuffer buffer = new MyBuffer();
		for (int i = 0; i < 10; i++) {
			int s = r.nextInt(36);
			ArrayList<Byte> l1 = new ArrayList<>();
			ArrayList<Byte> l2 = new ArrayList<>();
			for (int j = 0; j < s; j++) {
				System.out.println((byte) j);
				buffer.write((byte) j);
				l1.add((byte) j);
			}
			System.out.println(buffer.buffer_intern);
			System.out.println(buffer.buffer_extern);
			for (int j = 0; j < s; j++) {
				byte b = buffer.read();
				System.out.println(b);
				l2.add(b);
			}
			Collections.reverse(l2);
			System.out.println(l1.equals(l2));
			System.out.println();
			if (!l1.equals(l2)) {
				return;
			}
		}
	}
}
```


```
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]
java.nio.DirectByteBuffer[pos=0 lim=25 cap=25]
23
22
21
20
19
18
17
16
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
true

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]
java.nio.DirectByteBuffer[pos=0 lim=25 cap=25]
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
true

0
1
2
3
4
5
6
7
8
9
10
11
12
java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]
java.nio.DirectByteBuffer[pos=0 lim=25 cap=25]
12
11
10
9
8
7
6
5
4
3
2
1
0
true

0
1
2
3
4
5
6
7
8
9
10
java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]
java.nio.DirectByteBuffer[pos=0 lim=25 cap=25]
10
9
8
7
6
5
4
3
2
1
0
true

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]
java.nio.DirectByteBuffer[pos=0 lim=25 cap=25]
34
33
32
31
30
29
28
27
26
25
24
23
22
21
20
19
18
17
16
15
14
13
12
11
10
9
8
7
6
5
29
28
27
26
25
false
```
Vielleicht hat jemand dazu eine Lösung.

@httpdigest Danke für die Hinweise.


----------



## temi (25. Jul 2020)

Was bewirkt denn ByteBuffer.allocateDirect()?

Die Doku sagt dazu nur:


> Given a direct byte buffer, the Java virtual machine will make a best effort to perform native I/O operations directly upon it. That is, it will attempt to avoid copying the buffer's content to (or from) an intermediate buffer before (or after) each invocation of one of the underlying operating system's native I/O operations.


und


> The contents of direct buffers may reside outside of the normal garbage-collected heap, and so their impact upon the memory footprint of an application might not be obvious.


Aber das ist doch alles noch im RAM und löst damit das Problem nicht, oder?

Was hier gesucht ist, wäre doch eher das Mapping auf ein File.

Hier vielleicht ein paar Infos dazu:
http://www.kdgregory.com/index.php?page=java.byteBuffer


----------



## insert2020 (25. Jul 2020)

httpdigest hat gesagt.:


> 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...


----------



## insert2020 (25. Jul 2020)

temi hat gesagt.:


> Was bewirkt denn ByteBuffer.allocateDirect()?


Imho nutzt die VM dann alle Ressourcen die es kriegen kann.


----------



## temi (25. Jul 2020)

insert2020 hat gesagt.:


> Imho nutzt die VM dann alle Ressourcen die es kriegen kann.


Du meinst damit Swap? Ich weiß ja nicht...


----------



## mrBrown (25. Jul 2020)

Rajmund hat gesagt.:


> 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 (25. Jul 2020)

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.


----------



## Gelöschtes Mitglied 9001 (25. Jul 2020)

insert2020 hat gesagt.:


> `buffer_intern.clear();` braucht es nicht, und wie gesagt.... nicht intensiv getestet
> 
> ... 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 (25. Jul 2020)

Rajmund hat gesagt.:


> 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 (25. Jul 2020)

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 (25. Jul 2020)

Rajmund hat gesagt.:


> 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?


----------



## Gelöschtes Mitglied 9001 (25. Jul 2020)

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.


----------



## Gelöschtes Mitglied 9001 (25. Jul 2020)

mihe7 hat gesagt.:


> 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 (25. Jul 2020)

Rajmund hat gesagt.:


> 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.



Rajmund hat gesagt.:


> 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.



Rajmund hat gesagt.:


> 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 (25. Jul 2020)

Rajmund hat gesagt.:


> 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.



Rajmund hat gesagt.:


> 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 (25. Jul 2020)

Rajmund hat gesagt.:


> 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?


----------



## mihe7 (25. Jul 2020)

temi hat gesagt.:


> Du kannst nun mal keine Microsoft (oder beliebig andere) Cloud mit einem C64 (falls du den noch kennst) betreiben.


Pfff.... gut, mit der 1541 wird das viel Arbeit aber sonst...


----------



## temi (25. Jul 2020)

mihe7 hat gesagt.:


> Pfff.... gut, mit der 1541 wird das viel Arbeit aber sonst...


Sollen die Daten dann auf der Datasette gepuffert werden? Vielleicht, wenn man "Turbotape" verwendet


----------



## temi (25. Jul 2020)

Rajmund hat gesagt.:


> 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".


----------



## mihe7 (25. Jul 2020)

temi hat gesagt.:


> Sollen die Daten dann auf der Datasette gepuffert werden?


Alles eine Frage des richtigen Marketings. Man muss den Leuten ja nichts von Kassetten erzählen: our cloud solution uses stream buffers - implemented in hardware for high performance.


----------



## White_Fox (25. Jul 2020)

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



Spoiler



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


----------



## Gelöschtes Mitglied 9001 (25. Jul 2020)

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 (25. Jul 2020)

Rajmund hat gesagt.:


> 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 (25. Jul 2020)

Rajmund hat gesagt.:


> 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.





temi hat gesagt.:


> 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 (25. Jul 2020)

Rajmund hat gesagt.:


> 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 (25. Jul 2020)

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.


----------



## Gelöschtes Mitglied 9001 (25. Jul 2020)

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.


----------



## temi (25. Jul 2020)

temi hat gesagt.:


> Ist denn der Schreib-/Lesezugriff auf die SD-Karte generell schnell genug, um das zu schaffen?


Ok, das müsste auf jeden Fall zu machen sein: https://elinux.org/RPi_SD_cards#SD_card_performance


----------



## Gelöschtes Mitglied 9001 (25. Jul 2020)

temi hat gesagt.:


> 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.


Wie willst du 1 Audiointerface mit 1 USB-Anschluß auf mehrere Computer verteilen?


----------



## temi (25. Jul 2020)

Rajmund hat gesagt.:


> Wie willst du 1 Audiointerface mit 1 USB-Anschluß auf mehrere Computer verteilen?


Einer der Raspis nimmt die Daten per USB entgegen und verteilt sie an die anderen.

Wieviele Kanäle sind denn auf so einem Audiointerface realisierbar? Oder war deine Frage mit den 12 Kanälen nur theoretischer Natur?


----------



## Gelöschtes Mitglied 9001 (25. Jul 2020)

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 (25. Jul 2020)

Rajmund hat gesagt.:


> 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.


Rajmund hat gesagt.:


> 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.


----------



## Gelöschtes Mitglied 9001 (26. Jul 2020)

White_Fox hat gesagt.:


> 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.



White_Fox hat gesagt.:


> 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.



White_Fox hat gesagt.:


> 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.


----------



## temi (26. Jul 2020)

Rajmund hat gesagt.:


> 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?


----------



## Gelöschtes Mitglied 9001 (26. Jul 2020)

temi hat gesagt.:


> 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.



temi hat gesagt.:


> 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.



temi hat gesagt.:


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


Kommt, nach dem ich getestet habe.


----------



## temi (26. Jul 2020)

Rajmund hat gesagt.:


> 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


----------



## Gelöschtes Mitglied 9001 (26. Jul 2020)

temi hat gesagt.:


> 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.


----------



## temi (26. Jul 2020)

Rajmund hat gesagt.:


> damals wäre der Raspi von heute ein Hochleistungscomputer gewesen.


Der Raspi von heute hat auch 8 GB RAM...

Ansonsten, war das natürlich mit einem Augenzwinkern gemeint (erkennbar am Smiley). Wir sind hier meistenteils ein lustiges Forum.


----------



## White_Fox (26. Jul 2020)

Rajmund hat gesagt.:


> 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.



Rajmund hat gesagt.:


> 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. 



Rajmund hat gesagt.:


> 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.


----------



## kneitzel (26. Jul 2020)

Rajmund hat gesagt.:


> 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.


----------



## Gelöschtes Mitglied 9001 (26. Jul 2020)

White_Fox hat gesagt.:


> 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.



JustNobody hat gesagt.:


> 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).



JustNobody hat gesagt.:


> 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 (26. Jul 2020)

Rajmund hat gesagt.:


> 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, ...


----------



## kneitzel (26. Jul 2020)

Rajmund hat gesagt.:


> 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?


----------



## kneitzel (26. Jul 2020)

mrBrown hat gesagt.:


> 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).


----------



## Gelöschtes Mitglied 9001 (26. Jul 2020)

mrBrown hat gesagt.:


> 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
> ...


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.


----------



## Gelöschtes Mitglied 9001 (26. Jul 2020)

JustNobody hat gesagt.:


> 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.


----------



## Gelöschtes Mitglied 9001 (26. Jul 2020)

```
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 (26. Jul 2020)

JustNobody hat gesagt.:


> absolut trivial


Ganz so trivial ist es dann doch nicht, da der Buffer bei fast jedem Lesevorgang geflusht werden müsste:

```
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 (26. Jul 2020)

JustNobody hat gesagt.:


> 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 



Rajmund hat gesagt.:


> 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


----------



## Gelöschtes Mitglied 9001 (26. Jul 2020)

mrBrown hat gesagt.:


> 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
> 
> 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 (26. Jul 2020)

Rajmund hat gesagt.:


> 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.


----------



## Gelöschtes Mitglied 9001 (26. Jul 2020)

mrBrown hat gesagt.:


> 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.


----------



## Gelöschtes Mitglied 9001 (26. Jul 2020)

insert2020 hat gesagt.:


> Ganz so trivial ist es dann doch nicht, da der Buffer bei fast jedem Lesevorgang geflusht werden müsste:
> 
> ```
> import java.io.BufferedInputStream;
> ...


Solange die Daten insgesamt die RAM_BUFFER_SIZE nicht überschreiten, ist dies eine Lösung. Wenn mehr Daten anfallen, wird der Outputstream jedoch anfangen, die Daten die Platte zu schreiben, und zwar ab dem Zeitpunkt dann alle Daten, unabhängig davon, wie schnell der Client die Daten abholt. Dies ist aber nicht gewünscht.
So lange der Client die Daten schnell genug abholen kann, sollten die Daten im RAM bleiben. Erst wenn der Client die Daten nicht mehr schnell genug abholen kann, aber soviel Eingabedaten anfallen, dass der RAM sie nicht mehr fassen kann, soll ein Teil der Daten auf der Platte landen.
Prinzipiell ist die Datenmenge, die von der Eingabe zur Ausgabe weitergereicht wird, unendlich.


----------



## kneitzel (26. Jul 2020)

Rajmund hat gesagt.:


> 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.



Schön, dass Du Dir nur so Teile rauspickst. Sorry, aber Du kommst hier an und fragst nach einem Algorithmus ohne zu schreiben, was Du ggf. schon probiert hast und was nicht. Du schilderst auch nicht, wo genau das Problem ist.
Und bei Hinweisen zu der generellen Problematik kommt in erster Linie abwertende Antworten ...

Und was ist mit der skizzierten Lösung? Oder hast Du die gar nicht versucht zu verstehen? Ich habe da einen möglichen Algorithmus recht gut skizziert und der sollte so recht gut funktionieren.



insert2020 hat gesagt.:


> Ganz so trivial ist es dann doch nicht, da der Buffer bei fast jedem Lesevorgang geflusht werden müsste:



Also dem kann ich nicht wirklich folgen. Kannst Du das noch etwas ausführen? Denn in Deinem Code Beispiel ist es ja prinzipiell egal, ob er ohne flush blockiert, wenn er Daten in den buffered Stream schreibt (weil der interne Buffer voll ist) oder ob er bei dem flush Aufruf blockiert.

Etwas komplexer wird es maximal durch die Themen:
- Es gibt nichts zu schreiben. Dann muss sich der Thread, der die Daten raus gibt, schlafen legen. Und wenn der lesende Thread etwas gelesen hat, muss der schreibende Thread geweckt werden.... Sprich: Die Anfrage nach neuen Daten blockiert ggf. bis Daten vorhanden sind.
- Wenn Du gepufferte Ein-/Ausgabe hast, dann sind das eigenständige Buffer im Stream. Ob da nun Daten enthalten sind oder nicht (also ein flush erfolgreich war) ist für den Ablauf erst einmal egal. Es ist ja nur ein Transfer in eine Richtung. Da spielt es keine Rolle wenn Daten etwas verzögert geschrieben würden ... Es geht ja um den Fall, dass die Daten nicht schnell genug abgenommen werden....
- Was auch noch fehlte ist natürlich die korrekte Behandlung, wenn nichts mehr gelesen wird. Dann muss man halt alle noch vorhandenen Buffer abarbeiten und dann auch schließen. Aber das sollte keine besondere Herausforderung sein. Die einzige Herausforderung, die ich sehe: Man hat Blöcke einer bestimmten Größe, aber der letzte Block kann nur teilweise gefüllt sein. Also die Lösung grob skizziert: Der Lese-Thread setzt bei einem geschlossenen Eingang ein entsprechendes Ende Flag + Wert, wie voll der letzte Buffer ist. Wenn man den letzten Block nicht explizit als gelesen markiert, muss man da nicht einmal groß Prüfungen einbauen. Der Thread für die Ausgabe muss dann also bei nicht vorhandenen Blöcken nur prüfen: Ist er am Ende? Wenn die Übertragung beendet ist, wird der teilweise gefüllte Block noch gesendet und der Thread beendet sich. (Statt sich schlafen zu legen).


----------



## temi (26. Jul 2020)

Ich hab auch mal was ausprobiert, noch nicht vollständig funktionsfähig, aber ich habe jetzt auch keine Lust mehr. 
Es handelt sich um eine Queue mit fester Länge (capacity = Anzahl der Datenblocks) und einer maximalen internen Datenmenge (maxSize = Größe in Byte). Generell werden die zu puffernden Arrays so in der Queue gespeichert, wie sie kommen. Wenn die Datenblöcke eine relativ konstante Größe haben, sollte das ausreichend sein, ansonsten müsste man vielleicht eine Art Collector vorschalten, der die gewünschte Blockgröße sammelt. Sobald die maximale interne Datenmenge überschritten ist, wird das zu speichernde Array in eine Datei gespeichert. Der Name der Datei entspricht dem Index in der Queue. Vielleicht hilft es ja, ansonsten hat es mir Spaß gemacht 


Spoiler: ArrayQueue





```
public final class ArrayQueue implements Queue {

    private final static String PREFIX = "ix_";
    private final static String SUFFIX = ".queue";
    private final byte[][] queue;
    private final int maxSize;
    private int internalSize;
    private int externalSize;
    private final boolean[] isExternal;
    private long writePosition = 0;
    private long readPosition = 0;
    private boolean writeOverflow = false;
    private boolean readOverflow = false;

    public ArrayQueue(final int capacity, final int maxSize) {
        this.queue = new byte[capacity][];
        this.maxSize = maxSize;
        this.isExternal = new boolean[capacity];
    }

    @Override
    public void put(final byte[] data) {

        byte[] arr = new byte[data.length];
        System.arraycopy(data, 0, arr, 0, data.length);
        int position = scalePosition(writePosition);

        if (data.length > freeSize()) {
            writeToFile(arr);
            externalSize += data.length;
            isExternal[position] = true;
        } else {
            queue[scalePosition(writePosition)] = arr;
            internalSize += data.length;
            isExternal[position] = false;
        }

        incWritePosition();
    }

    @Override
    public byte[] poll() {

        byte[] result = new byte[0];
        int position = scalePosition(readPosition);

        if (isExternal[position]) {
            result = readFromFile(position);
            externalSize -= result.length;
            isExternal[position] = false;
        } else {
            result = queue[position];
            internalSize -= result.length;
        }

        incReadPosition();
        return result;
    }

    @Override
    public int internalSize() {
        return internalSize;
    }

    @Override
    public int externalSize() {
        return externalSize;
    }

    @Override
    public int size() {
        return internalSize + externalSize;
    }

    private int freeSize() {
        return maxSize - internalSize;
    }

    private void incWritePosition() {
        writePosition++;
        if (writePosition == Long.MIN_VALUE) {
            writeOverflow = true;
            writePosition = 0;
        }
    }

    private void incReadPosition() {
        readPosition++;
        if (readPosition == Long.MIN_VALUE) {
            readOverflow = true;
            readPosition = 0;
        }
    }

    private int scalePosition(final long position) {
        return (int)(position % queue.length);
    }

    private void writeToFile(final byte[] data) {
        try (FileChannel channel = new RandomAccessFile(scalePosition(writePosition) + SUFFIX, "rw").getChannel();) {
            channel.write(ByteBuffer.wrap(data));
        } catch (IOException e) {
            System.out.println(e.getStackTrace());
        }
    }

    private byte[] readFromFile(final int position) {
        byte[] result = null;
        try (FileChannel channel = new RandomAccessFile(scalePosition(position) + SUFFIX, "r").getChannel();) {
            ByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
            result = new byte[(int)channel.size()];
            buffer.get(result);
        } catch (IOException e) {
            System.out.println(e.getStackTrace());
        }

        return result;
    }
}
```




Was noch fehlt ist auf jeden Fall das Löschen der Dateien nachdem sie gelesen wurden.


----------



## Gelöschtes Mitglied 9001 (26. Jul 2020)

JustNobody hat gesagt.:


> Schön, dass Du Dir nur so Teile rauspickst. Sorry, aber Du kommst hier an und fragst nach einem Algorithmus ohne zu schreiben, was Du ggf. schon probiert hast und was nicht. Du schilderst auch nicht, wo genau das Problem ist.
> Und bei Hinweisen zu der generellen Problematik kommt in erster Linie abwertende Antworten ...



Es kamen hier mehrfach Antworten, die beinhalteten, dass ein Raspi nicht geht, dass man keine unkomprimierten Daten braucht, dass man 96kHz nicht braucht. Diese Antworten werte ich nicht ab, aber ich kann ihnen entgegenhalten, dass der Raspi seine Aufgabe erfüllt: er steht hier neben mir und ich teste ihn ausführlich (das habe ich mehrfach geschrieben), und ich habe z.B. ausführlich erklärt, warum 96kHz notwendig sind. Auch habe ich bereits zu Beginn geschrieben, dass ich bereits Ringpuffer implementiert habe und es mir um eine Art zweistufigen Ringpuffer geht, der ggf. Daten auslagert.
In dem Dschungel von Antworten, die sich lieber mit der Hardwarefrage beschäftigen, gibt es ein paar Lösungsvorschläge, die ich mir angeschaut habe. Und Vorschlag Nummero 2 ist in meine Lösung auch eingeflossen. Ich bin mir nicht sicher, was Du von mir hören willst. „Ja, ihr habt alle recht, ein Raspi ist ungeeignet“ vielleicht? Das wäre aber falsch, da ich ihn ja getestet habe und es funktioniert genau so, wie es soll. Übrigens dank der Hilfe von LimDu's Post.


----------



## Gelöschtes Mitglied 9001 (26. Jul 2020)

Doppelpost


----------



## insert2020 (26. Jul 2020)

Wie man an Beitrag #103 von @temi sehen kann, ist das absolut kein triviales Thema.  Und danke dafür. 


temi hat gesagt.:


> aber ich habe jetzt auch keine Lust mehr.


nachvollziehbar...


----------



## temi (26. Jul 2020)

insert2020 hat gesagt.:


> Wie man an Beitrag #103 von @temi sehen kann, ist das absolut kein triviales Thema.  Und danke dafür.
> 
> nachvollziehbar...


Liegt vor allem dran, dass das Bier kalt steht und das Essen fast fertig ist


----------



## kneitzel (26. Jul 2020)

Ja, ich muss mir eingebildet haben, dass ich einen Algorithmus wie den folgenden skizziert habe:


> 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.
> 
> ...



Sorry, aber ich bin raus, wenn Du klare Hinweise zu einem möglichen Algorithmus so ignorierst - wenn Du sowas nicht verstanden haben solltest, dann hättest Du nachfragen können...

Der Algorithmus um die Daten soweit möglich im Speicher zu halten und wo nicht möglich im Dateisystem dürfte recht sauber beschrieben sein. Die Problematik bezüglich dem Flushen ist für mich nicht nachvollziehbar (und leider scheint da auch keine Erläuterung mehr zu kommen).

Die Problematik von temi in #103bezüglich der Blockgröße ist richtig, aber das sollte mit einem Collector zur Not auch kein Thema sein: So man eine read Methode verwendet, die eine Referenz zu dem Buffer, Offset und Länge nimmt, lese ich zur not so lange in einer Schleife, bis die Verbindung geschlossen wurde oder der Buffer voll ist.
Damit hätte man dann die Blöcke jeweils komplett gelesen (so diese noch nicht so vorliegen - etwas das ist als Voraussetzung verstanden hatte: "Daten kommen immer in Blöcken so wie ich das verstanden habe." - aber so das nicht der Fall ist, ist das beschriebene Vorgehen eine Möglichkeit, das einfach aufzubauen.)


Die Idee aus 103 von @temi erzeugt recht viele neue Objekte (z.B. bei jedem put), was ich nicht ganz so gut finde. Denn eine interessante Optimierung ist in meinen Augen, den GC so gut wie möglich zu entlasten.

Aber damit bin ich jetzt hier raus und überlasse euch komplett das Spielfeld.


----------



## Gelöschtes Mitglied 9001 (26. Jul 2020)

JustNobody hat gesagt.:


> Ja, ich muss mir eingebildet haben, dass ich einen Algorithmus wie den folgenden skizziert habe:
> 
> Sorry, aber ich bin raus, wenn Du klare Hinweise zu einem möglichen Algorithmus so ignorierst - wenn Du sowas nicht verstanden haben solltest, dann hättest Du nachfragen können...


Es gab hier mehrere Vorschläge und es tut mir leid, wenn ich nicht auf Deinen Vorschlag eingegangen bin. Wenn Du Anmerkungen zu meinem geposteten Vorschlag hast, bin ich ganz Ohr.


----------



## temi (27. Jul 2020)

JustNobody hat gesagt.:


> Die Idee aus 103 von @temi erzeugt recht viele neue Objekte (z.B. bei jedem put)


Meinst du damit die Kopie des übergebenen Bytearrays? Die könnte man sich natürlich ersparen, wenn sichergestellt ist, dass immer eine "frische" Referenz übergeben wird.


----------



## kneitzel (27. Jul 2020)

Ja, wobei das im Normalfall egal wäre. Dann läuft halt der GC. Da aber die Ressourcen gut ausgenutzt werden sollten, würde ich darauf komplett verzichten.

Ich habe einfach einmal meine Idee einfach mal in Java herunter geschrieben. Nichts schönes und ich sehe da noch einige unschöne Dinge. Aber paar kleine Refactorings habe ich gemacht (so z.B. aufgeteilt in mehrere Klassen). Habe aber keine großen Tests geschrieben und auch nicht groß getestet.

Also als erstes habe ich nur für den Memory-Bereich eine Klasse MemoryBuffer geschrieben. Diese initialisiert einmalig alle Buffer. Wenn etwas gespeichert wird, dann ist es ein "Buffer-Tausch". Will halt möglichst wenig Objekte erzeugen, die dann eingesammelt werden müssen. Getter/Setter sieht man nicht - die macht bei mir Lombok aber die Annotations habe ich heraus genommen.

Ach ja: im Code habe ich generell ein paar System.err.println drin umdas etwas nachvollziehbarer zu machen und die Fehlerbehandlung ist sehr rudimentär.



Spoiler: MemoryBuffer





```
package de.kneitzel;

public class MemoryBuffer {

    /**
     * All buffers.
     */
    private byte buffers[][];

    /**
     * Number of buffers.
     */
    private int bufferNumber;

    /**
     * Size of a single buffer.
     */
    private int bufferSize;

    /**
     * Last buffer read.
     */
    private volatile int lastRead = -1;

    /**
     * Last buffer written.
     */
    private volatile int lastWritten = -1;

    /**
     * Creates a new instance of MemoryBuffer.
     * @param bufferSize Size of each buffer.
     * @param bufferNumber Number of buffers to store.
     */
    public MemoryBuffer(final int bufferSize, final int bufferNumber) {
        this.bufferNumber = bufferNumber;
        this.bufferSize = bufferSize;

        buffers = new byte[bufferNumber][];

        for (int index=0; index < bufferNumber; index++)
            buffers[index] = new byte[bufferSize];
    }

    /**
     * Gets the next Element for a given pointer.
     * @param pointer Pointer to an element.
     * @return Next pointer to an element.
     */
    protected int getNextElement(final int pointer) {
        int result = pointer+1;
        if (result == bufferNumber - 1) result = 0;
        return result;
    }

    /**
     * Checks if there is any space left.
     * @return true if another buffer can be inserted, else false.
     */
    public boolean hasSpace() {
        // If nothing was read so far: check if we wrote the last element.
        if (lastRead == -1) return lastWritten != bufferNumber - 1;

        return getNextElement(lastWritten) != lastRead;
    }

    /**
     * Puts the given buffer into the buffers list.
     * @param buffer Buffer to store.
     * @return Previously stored buffer to be reused.
     */
    public byte[] put(final byte[] buffer) {
        if (!hasSpace()) throw new IllegalStateException("No space available to add buffer!");
        int nextElement = getNextElement(lastWritten);
        byte[] temp = buffers[nextElement];
        buffers[nextElement] = buffer;
        lastWritten = nextElement;
        return temp;
    }

    /**
     * Checks if we have more elements to read.
     * @return true if elements to read are available.
     */
    public boolean hasElements() {
        return lastRead != lastWritten;
    }

    /**
     * Gets the next buffer if available.
     * @return Next buffer.
     */
    public byte[] get() {
        if (!hasElements()) throw new IllegalStateException("No Elements to get available!");

        lastRead = getNextElement(lastRead);
        return buffers[lastRead];
    }
}
```




Dann noch einmal für Disk. Dabei habe ich mir aber überlegt: Wozu eine feste Buffergröße? Immer, wenn im MemoryBuffer nichts frei ist, landet es im DiskBuffer. Also hat das keine Grenze. Wenn die Platte voll ist oder so, dann war es das aber.
Unschön: Zum löschen erzeuge ich derzeit eine File Instanz...



Spoiler: DiskBuffer





```
package de.kneitzel;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class DiskBuffer {
    /**
     * Package Size.
     */
    private int packageSize;

    /**
     * Disk folder to store files in.
     */
    private String diskFolder;

    /**
     * Last buffer written.
     */
    private int lastWritten;

    /**
     * Last buffer read.
     */
    private int lastRead;

    /**
     * FileBuffer to use.
     */
    private byte[] fileBuffer;

    /**
     * Creates a new instance of de.kneitzel.DiskBuffer.
     * @param packageSize Size of a package.
     */
    public DiskBuffer(final int packageSize, final String diskFolder) {
        this.packageSize = packageSize;
        this.diskFolder = diskFolder;
        fileBuffer = new byte[packageSize];
        lastRead = -1;
        lastWritten = -1;
    }

    /**
     * Reads the next buffer. The array reference returned is always the same - so use the data before calling this method again!
     * @return The byte Array with the read data.
     */
    public byte[] readBuffer() {
        if (lastRead == lastWritten) throw new IllegalStateException("No package to read!");

        String file = diskFolder + "/" + (lastRead + 1) + ".tmp";
        try (FileInputStream fileStream = new FileInputStream(file)){
            if (fileStream.read(fileBuffer) != packageSize) {
                throw new IllegalStateException("File read has wrong size!");
            }
        } catch (IOException ex) {
            throw new IllegalStateException("Unable to read package!", ex);
        }

        // TODO: Creating new object to delete file also adds work to GC!
        new File(file).delete();

        lastRead++;
        return fileBuffer;
    }

    /**
     * Writes the next buffer
     * @param buffer Buffer to write.
     */
    public void writeBuffer(final byte[] buffer) {
        try (FileOutputStream fileStream = new FileOutputStream(diskFolder + "/" + (lastWritten + 1) + ".tmp")) {
            fileStream.write(buffer);
        } catch (IOException ex) {
            // TODO: Better Error Handling.
            System.err.println("Unable to write data to file: " + diskFolder + "/" + (lastWritten + 1) + ".tmp");
            ex.printStackTrace();
            System.exit(-1);
        }
        lastWritten++;
    }
}
```




Dann baucht man natürlich noch den ThreadedBuffer oben drüber. Der arbeitet mit zwei Threads - einmal zum lesen und einmal zum schreiben.
Entgegen der beschreibung speichert er aber lediglich, ob der Block aus dem Memory oder aus dem Disk Buffer gelesen werden soll.


Spoiler: ThreadedBuffer





```
package de.kneitzel;

import java.io.*;
import java.nio.BufferOverflowException;

/**
* A simple class which can be used to buffer a stream with a buffer that can be as big as required.
*/
public class ThreadedBuffer {

    /**
     * Package Size.
     */
    private int packageSize = 1024 * 1024; // 1 KB default package size.

    /**
     * Number of packages to store inside Memory.
     */
    private int numberRamBuffers;

    /**
     * Number of packages to store on disk;
     */
    private int numberDiskBuffers;

    /**
     * Stream to read from.
     */
    private InputStream inputStream;

    /**
     * Stream to write to.
     */
    private OutputStream outputStream;

    /**
     * Folder to store all files.
     */
    private String diskFolder = ".";

    /**
     * Thread that is reading from the inputStream.
     */
    private volatile Thread readThread;

    /**
     * Thread that is writing to the outputStream.
     */
    private volatile Thread writeThread;

    /**
     * Buffer that is used to read data to.
     */
    private byte[] readBuffer;

    /**
     * Stores the number of Bytes read from the thread.
     */
    private int readBufferBytesRead;

    /**
     * Array with references to used buffers. Positive number references a memory buffer, negative number references a disk buffer.
     */
    private boolean[] bufferReferenceInMemory;

    /**
     * Next buffer reference to write to.
     */
    int lastWrittenTo;

    /**
     * Next buffer reference to read.
     */
    int lastReadFrom;

    /**
     * Flag that indicates if the read thread was blocked.
     */
    private volatile boolean writeThreadBlocked = false;

    private MemoryBuffer memoryBuffer;
    private DiskBuffer diskBuffer;

    /**
     * Creates a new instance of ThreadedBuffer.
     * @param ramPackages Number of packages to store inside RAM.
     * @param diskPackages Number of packages to store on DISK.
     */
    public ThreadedBuffer(final int ramPackages, final int diskPackages, final InputStream inputStream, final OutputStream outputStream) {
        this.numberRamBuffers = ramPackages;
        this.numberDiskBuffers = diskPackages;
        this.inputStream = inputStream;
        this.outputStream = outputStream;
    }

    /**
     * Starts the read and write threads.
     */
    public void run() {
        initialize();
        readThread = new Thread(this::readTask);
        readThread.start();
        writeThread = new Thread(this::writeTask);
        writeThread.start();
    }

    /**
     * Waits for read and write thread to end.
     * @throws InterruptedException
     */
    public void waitForEnd() throws InterruptedException {
        if (readThread != null) readThread.join();
        if (writeThread != null) writeThread.join();
    }

    /**
     * Initializes the data structures:
     * <p>
     *     <ul>
     *         <li>buffers inside memory</li>
     *         <li>Array with references to used buffers.</li>
     *         <li>Pointers to used elements.</li>
     *     </ul>
     * </p>
     */
    private void initialize() {
        // Buffer to use for reference blocks.
        bufferReferenceInMemory = new boolean[numberDiskBuffers + numberRamBuffers];
        lastWrittenTo = -1;
        lastReadFrom = -1;

        // Buffer to read data into.
        readBuffer = new byte[packageSize];

        // Buffers
        memoryBuffer = new MemoryBuffer(packageSize, numberRamBuffers);
        diskBuffer = new DiskBuffer(packageSize, diskFolder);
    }

    private void readTask() {
        try {
            while (readFromInputTask()) {
                storeBuffer();

                if (writeThreadBlocked)
                    writeThread.interrupt();
            }
        } catch (IOException ex) {
            // TODO: Better handling missing.
            System.err.println("IOException occured: " + ex.getMessage());
            ex.printStackTrace();
        }
        System.err.println("ReadTask done.");
    }

    /**
     * Writes data from buffers to outputStream.
     */
    private void writeTask() {
        boolean running = true;
        while (running) {
            if (lastReadFrom == lastWrittenTo) {
                if (readThread.isAlive()) {
                    System.err.println("Write Thread: Nothing to read, sleeping!");
                    // Wait for new data to read.
                    try {
                        writeThreadBlocked = true;
                        Thread.sleep(1000);
                    } catch (InterruptedException ex) {
                        System.err.println("Write Thread: Sleep interrupted.");
                    }
                    writeThreadBlocked = false;
                } else {
                    try {
                        // Read Thread done so no more data incoming.
                        // We need to write the last block read and we are done.
                        outputStream.write(readBuffer, 0, readBufferBytesRead);
                        System.err.println("Write Thread: last buffer written. Stopping ...");
                        running = false;
                    } catch (IOException ex) {
                        // TODO Better error handling.
                        System.err.println("IOException when writing data ... exiting!");
                        ex.printStackTrace();
                        System.exit(-1);
                    }
                }
            } else {
                int nextBufferToRead = lastReadFrom + 1;
                if (nextBufferToRead == bufferReferenceInMemory.length)
                    nextBufferToRead = 0;

                if (bufferReferenceInMemory[nextBufferToRead]) {
                    System.err.println("Write Task: Getting buffer from Memory.");
                    // RAM Buffer
                    try {
                        outputStream.write(memoryBuffer.get());
                    } catch (Exception ex) {
                        // TODO: better data handling.
                        System.err.println("Unable to write data!");
                        ex.printStackTrace();
                        System.exit(-1);
                    }
                } else {
                    System.err.println("Write Task: Getting buffer from Disk.");
                    // Disk Buffer
                    try {
                        outputStream.write(diskBuffer.readBuffer());
                    } catch (IOException ex) {
                        // TODO: better data handling.
                        System.err.println(ex.getMessage());
                        ex.printStackTrace();
                        System.exit(-1);
                    }
                }
                lastReadFrom = nextBufferToRead;
            }
        }
        System.err.println("WriteTask done.");
    }

    /**
     * Reads a package of the inputStream.
     * @return true if a full packages was read, else false.
     * @throws IOException An IOException was thrown while reading from the input stream.
     */
    private boolean readFromInputTask() throws IOException {
        readBufferBytesRead = 0;
        int bytesLeftToRead = packageSize;
        int lastReadBytes = 0;

        while (bytesLeftToRead > 0 && (lastReadBytes = inputStream.read(readBuffer, readBufferBytesRead, bytesLeftToRead)) > 0) {
            bytesLeftToRead -= lastReadBytes;
            readBufferBytesRead += lastReadBytes;
        }

        System.err.println("Read Bytes from input Stream: " + readBufferBytesRead);
        return lastReadBytes != -1;
    }

    /**
     * Stores the readBuffer.
     * @return
     */
    private void storeBuffer() {
        // First we need to get the next Element to write to.
        int nextElement = lastWrittenTo + 1;
        if (nextElement == bufferReferenceInMemory.length)
            nextElement = 0;

        if (nextElement == lastReadFrom)
            throw new BufferOverflowException();

        if (memoryBuffer.hasSpace()) {
            System.err.println("Read Task: Putting buffer in Memory.");
            readBuffer = memoryBuffer.put(readBuffer);
            bufferReferenceInMemory[nextElement] = true;
        } else {
            System.err.println("Read Task: Putting buffer to disk.");
            diskBuffer.writeBuffer(readBuffer);
            bufferReferenceInMemory[nextElement] = false;
        }

        lastWrittenTo = nextElement;
    }
}
```




Und "last but not least": ThreadedBufferTest ist einfach eine kleine Testroutine. Nimmt zwei Parameter:
Eingabedatei -> diese wird gelesen
Ausgabedatei -> diese wird geschrieben.
Desweiteren ist ThreadedBufferTest ein OutputStream, der beim Schreiben eines Blocks eine Sekunde wartet. Also sozusagen eine schlechte Verbindung simuliert.



Spoiler: ThreadedBufferTest





```
package de.kneitzel;

import java.io.*;

public class ThreadedBufferTest extends OutputStream {

    private FileOutputStream outStream;

    public static void main(String[] args) throws IOException  {
        if (args.length != 2) {
            System.out.println("Aufruf mit 2 Argumenten - Source Datei und Ziel Datei!)");
            return;
        }

        InputStream inStream = new FileInputStream(args[0]);
        OutputStream outStream = new ThreadedBufferTest(args[1]);

        ThreadedBuffer threadedBuffer = new ThreadedBuffer(3, 10000, inStream, outStream);
        threadedBuffer.run();
    }

    public ThreadedBufferTest (final String filename) throws IOException {
        outStream = new FileOutputStream(filename);
    }

    @Override
    public void write(byte[] buffer) throws IOException {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException ex) {}
        outStream.write(buffer);
    }

    @Override
    public void write(byte[] buffer, int offset, int length) throws IOException {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException ex) {}
        outStream.write(buffer, offset, length);
    }

    @Override
    public void write(int b) throws IOException {
        outStream.write(b);
    }
}
```




Das wäre also einfach einmal stur die skizzierte Idee von mir herunter geschrieben. War zuerst alles in einer Klasse, weil ich es wirklich nur simpel runterschreiben wollte, aber so ganz ohne Refactoring war das nichts, was ich bereit wäre zu posten 

Der Algorithmus selbst ist - wie schon in meinem ersten Post in dem ich ihn beschrieben habe geschrieben - recht trivial und mit den Erläuterungen sollte er einfach zu verstehen sein. Die exakten Anforderungen müsste man durchgehen - aber diesbezüglich bin ich wirklich raus, denn ich sehe da nicht wirklich ein Entgegenkommen vom TE. Der Algorithmus selbst ist geliefert und kann angepasst werden:
- Ggf. benötigte Fehlerbehandlung
- ggf. überdenken der Dateistruktur und dem Löschen. Hier wäre denkbar, Dateien nicht zu löschen bzw. nur bei Bedarf. Denn ich sehe das Problem, dass je nach Filesystem ggf. die SD Karte schnell gekillt wird durch zu viele Schreibvorgänge auf einzelnen Speicherzellen. Aber ich muss gestehen, dass ich da nicht in den technischen Details stecke. Aber es ist die gleiche Problematik wie bei SSDs (und so manch einer hat am Anfang SSDs schnell gekillt weil nicht gleichmäßig alle Speicherzellen beschrieben wurden ...)

Edit: MemoryBuffer hatte zuerst auch ThreadedBuffer enthalten...
Edit2: Die diskPackages in ThreadedBuffer können natürlich auch bei der Implementation weg. Es gibt ja diesbezüglich kein Limit.


----------



## Gelöschtes Mitglied 9001 (27. Jul 2020)

JustNobody hat gesagt.:


> - ggf. überdenken der Dateistruktur und dem Löschen. Hier wäre denkbar, Dateien nicht zu löschen bzw. nur bei Bedarf. Denn ich sehe das Problem, dass je nach Filesystem ggf. die SD Karte schnell gekillt wird durch zu viele Schreibvorgänge auf einzelnen Speicherzellen. Aber ich muss gestehen, dass ich da nicht in den technischen Details stecke.



Das Löschen einer Datei bedeutet auf Dateisystemebene lediglich, einen Bereich als frei zu markieren (nicht, ihn zu überschreiben), d.h., der Schreibzugriff, der durch das Löschen entsteht, dürfte verhältnismäßig vernachlässigbar sein. Aber ich gebe dir recht, dass man das Löschen auch nur im Bedarfsfall machen könnte, das würde Zeit sparen.


----------



## mihe7 (27. Jul 2020)

Rajmund hat gesagt.:


> Das Löschen einer Datei bedeutet auf Dateisystemebene lediglich, einen Bereich als frei zu markieren (nicht, ihn zu überschreiben),


LOL, ich glaube nicht, dass Du das @JustNobody erklären musst  

Problem an der Geschichte ist etwas völlig anderes, wenn es um Flashspeicher geht (ich habe mir das mal vor Jahren angesehen). Soweit ich das noch in Erinnerung habe, müssen Blöcke gelöscht werden, bevor sie mit bestimmten Daten beschrieben werden können (je nach Speicher kann ein gesetztes/gelöschtes Bit nicht einfach gelöscht/gesetzt werden). Diese Löschzyklen sind begrenzt. D. h. es spielt ggf. gar keine Rolle, ob Du nur ein Dateiattribut setzt, wenn ein relativ großer Block gelöscht werden muss. Linderung verschafft Wear-Leveling. 

Allerdings weiß ich nicht, wie heute der Stand der Dinge bei den Flashdingern ist.


----------



## insert2020 (27. Jul 2020)

Nächste Runde, hier eine Version, die zu 100% funktioniert, die aber den Nachteil hat, dass die Datei auf der Festplatte immer größer wird:


Spoiler





```
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;

public class MyBuffer {
	private static final int RAM_BUFFER_SIZE = 8; // choose any size
	private ByteBuffer buffer1 = null;
	private FileOutputStream out1 = null;
	private BufferedInputStream in1 = null;
	private long out1written = 0;
	private int buffer1pos = 0;

	public MyBuffer() throws IOException {
		buffer1 = ByteBuffer.allocate(RAM_BUFFER_SIZE);
		out1 = new FileOutputStream(new File("my_backup_file.txt"));
		in1 = new BufferedInputStream(new FileInputStream(new File("my_backup_file.txt")), RAM_BUFFER_SIZE);
	}

	public synchronized void write(byte b) throws IOException {
		if (!buffer1.hasRemaining()) {
			if (buffer1pos > 0) {
				buffer1.position(buffer1.position() - buffer1pos);
				for (int i = 0; buffer1pos + i < buffer1.limit(); i++) {
					buffer1.put(i, buffer1.get(buffer1pos + i));
				}
				buffer1pos = 0;
			} else {
				out1.write(buffer1.array());
				out1.flush();
				out1written += buffer1.limit();
				buffer1.clear();
				buffer1pos = 0;
			}
		}
		buffer1.put(b);
	}

	public synchronized int read() throws IOException {
		if (out1written > 0) {
			out1written--;
			return in1.read();
		}
		if (buffer1pos == buffer1.position()) {
			return -1;
		}
		return buffer1.get(buffer1pos++);
	}

	public synchronized byte[] bulkRead() throws IOException {
		ByteArrayOutputStream list = new ByteArrayOutputStream();
		int b;
		while ((b = read()) != -1) {
			list.write(b);
		}
		return list.toByteArray();
	}

	public static void main(String[] args) throws IOException {
		// TEST
		Random r = new Random(0);
		MyBuffer buffer = new MyBuffer();
		buffer.write((byte) 3);
		buffer.write((byte) 2);
		buffer.write((byte) 1);
		System.out.println(Arrays.toString(buffer.bulkRead()));
		for (int i = 0; i < 100; i++) {
			int s = r.nextInt(RAM_BUFFER_SIZE);
			ArrayList<Byte> l1 = new ArrayList<>();
			ArrayList<Byte> l2 = new ArrayList<>();
			for (int j = 0; j < s * 2; j++) {
				System.out.println("w: " + (byte) j);
				buffer.write((byte) j);
				l1.add((byte) j);
			}
			for (int j = 0; j < s; j++) {
				byte b = (byte) buffer.read();
				System.out.println("r: " + b);
				l2.add(b);
			}
			for (int j = 0; j < s; j++) {
				System.out.println("w: " + (byte) j);
				buffer.write((byte) j);
				l1.add((byte) j);
			}
			for (int j = 0; j < s; j++) {
				byte b = (byte) buffer.read();
				System.out.println("r: " + b);
				l2.add(b);
			}
			for (int j = 0; j < s; j++) {
				byte b = (byte) buffer.read();
				System.out.println("r: " + b);
				l2.add(b);
			}
			System.out.println("r: " + buffer.read());
			System.out.println(l1.equals(l2));
			System.out.println();
			if (!l1.equals(l2)) {
				return;
			}
		}
	}
}
```



Ich wäre dankbar wenn nicht danach gefragt wird, wie es funktioniert.


----------



## White_Fox (27. Jul 2020)

Also, jetzt muß ich ja doch mal fragen...wie funktioniert das?


----------



## insert2020 (27. Jul 2020)

White_Fox hat gesagt.:


> wie funktioniert das?


Es gibt einen ByteBuffer == Speicher im RAM, einen FileOutputStream == Speicher auf der Festplatte und einen BufferedInputStream == sowohl Speicher im RAM wie auch Speicher auf der Festplatte...
Nur wenn der Speicher im RAM voll ist, wird auf die Festplatte geschrieben und gelesen.
Der Rest ist Index schubsen...


----------



## kneitzel (27. Jul 2020)

Rajmund hat gesagt.:


> Das Löschen einer Datei bedeutet auf Dateisystemebene lediglich, einen Bereich als frei zu markieren (nicht, ihn zu überschreiben), d.h., der Schreibzugriff, der durch das Löschen entsteht, dürfte verhältnismäßig vernachlässigbar sein. Aber ich gebe dir recht, dass man das Löschen auch nur im Bedarfsfall machen könnte, das würde Zeit sparen.


Es geht hier nicht um irgendwelches Zeit sparen. Es geht um die Lebenszeit der SD-Karte. Speicherzellen lassen sich nur begrenzt oft beschreiben, weshalb man ggf. verhindern möchte, dass der gleiche Speicherbereich wiederholt geschrieben wird.
Aber auch bei einer SD Karte kommen ähnliche mapping Mechanismen wie bei einer SSD zum tragen wenn ich das richtig verstanden habe. Aber ich bin kein Hardware Spezialist, daher kann ich dazu nicht viel sagen. Ich habe aber halt die Befürchtung, dass bei SD Karten das weniger optimiert wurde als bei SSDs.


----------



## insert2020 (27. Jul 2020)

JustNobody hat gesagt.:


> Speicherzellen lassen sich nur begrenzt oft beschreiben, weshalb man ggf. verhindern möchte, dass der gleiche Speicherbereich wiederholt geschrieben wird.


Das stimmt. Es wäre ggf. sogar sinnvoll, das File während der Programmlaufzeit zu "akkumulieren", anstatt immer wieder die gleiche/selbe Stelle zu beschreiben. Insofern wäre mein "MyBuffer"-Vorschlag gar nicht so abwegig.

Aber mir fehlt da leider auch das Hintergrundwissen über den Anwendungsfall und über Speicherkarten...


----------



## Gelöschtes Mitglied 9001 (27. Jul 2020)

JustNobody hat gesagt.:


> Es geht hier nicht um irgendwelches Zeit sparen.



Das kommt drauf an. Ein Audiointerface liefert die Daten unerbittlich und wartet nicht, bis jemand sie abgeholt hat. Wenn der Abholthread nun plötzlich einen Datenblock auf die Karte schreiben muss oder einen Block löschen, braucht er etwas mehr Zeit. Er muss das in der Zeit schaffen, wie der Buffer des Audiointerface lang ist. Diese Zeit (in Sekunden) errechnet sich aus Buffergröße in Bytes / (Kanäle x Bits/8 x Samplerate). Ist das Schreiben in dem Zeitraum nicht geschafft, hat das Audiointerface die Daten in seinem Buffer schon mit neuen Daten überschrieben und es kommt zu Aussetzern.
Man kann diese Gefahr abmildern, in dem man einen Ringpuffer vorlagert, der tatsächlich nur im RAM liegt.


----------



## kneitzel (28. Jul 2020)

Ähm: Du hattest Du einen Punkt bei mir aufgegriffen. Und da ging es um den DiskBuffer, den ich Dir sogar geschrieben habe. Und da ging es mir wie schon einmal geschrieben explizit nicht um Performance. (Was auch relativ wenig Sinn machen würde, denn wir haben auf dem hohen Level, auf dem wir in der Java VM nun einmal agieren, kaum Optionen. In einem Low Level Bereich könnte man da einiges mehr machen. Da könnte man sich dann mit dem Verhalten von SD Karten auseinander setzen und ggf. auf Low Level Bereichen was optimieren. Da gäbe es dann so MMC_ERASE Befehle und so ...

Das was Du jetzt aber schreibst umfasst ja wieder die ganze Lösung. Und das ist auch etwas, das Du doch auch nicht bereden willst und alles, was wir da vorab gebracht hatten, wurde von Dir abgelehnt. Das verwirrt mich jetzt etwas ...

Aber ich wollte mich hier ja verabschieden aus dem Thread, daher ist es nicht wild. Du hast nach möglichen Algorithmen gefragt und ich habe einen Algorithmus beschrieben, der aus meiner Sicht einfach umsetzbar sein sollte. Und Ich habe sogar einmal eine schnelle Implementation nachgereicht. Damit ist die Sache für mich an der Stelle gegessen - so keine Nachfragen zu meinen Beiträgen kommen.


----------



## Gelöschtes Mitglied 9001 (28. Jul 2020)

Die Betrachtung der Zeit spielt bei Algorithmen schon eine wesentliche Rolle. Ich möchte eben mich nur ungern verteidigen müssen, warum ich nicht MP3 oder einen stärkeren Rechner verwende und ob ich das Linux denn auch schlank genug konfiguriert hätte, denn das sind Dinge, die keine Antwort auf die Frage nach einem effizienten Algorithmus liefern. Die Fragen danach sind sicher legitim, aber ich denke, wenn ich das 1x beantwortet habe, ist es genug. Wenn die Antwort immer lauten würde: „Nimm einen stärkeren Rechner.“, dann bräuchten wir keine vernünftige Informatikausbildung mehr, weil die erstbeste Lösung, die irgendwie funktioniert, schon akzeptiert werden würde und die Performance soll dann die Hardware liefern. Dann könnte man auch sagen: warum brauchen Betriebssystem Swap-Speicher - offensichtlich ist der RAM zu klein, nimm einen größeren. Und dann bräuchte man das durchaus sehr interessante Thema Swapping-Methoden gar nicht mehr diskutieren - Problem vermeintlich gelöst. Es wäre der schon oft thematisierte (natürlich überzeichnete) Aspekt: was die Hardware-Ingenieure an Performance entwickeln, machen die Software-Ingenieure wieder zunichte. Aus diesem Grund gebe ich mich mit der Antwort: „kauf einen Rechner mit mehr RAM“ nicht zufrieden und finde es auch etwas befremdlich, dass eine solche Antwort in einem Programmiererforum kommt.


----------



## user30 (28. Jul 2020)

Rajmund hat gesagt.:


> keine vernünftige Informatikausbildung mehr


Erstmal ist das ein Studium und zweitens bedarf es diesem sehr wohl, um von vornherein Dinge ausschließen zu können, die schlicht theoretisch und praktisch nicht möglich sind... Dazu zählt zB ein riesiger Buffer mit unsinnigen Daten auf einer dürftigen Hardware... Oder geht es dir darum, möglichst effizient Speicherkarten zu schrotten?


----------



## Gelöschtes Mitglied 9001 (28. Jul 2020)

user30 hat gesagt.:


> Erstmal ist das ein Studium


Ja, da hast Du recht.


user30 hat gesagt.:


> und zweitens bedarf es diesem sehr wohl, um von vornherein Dinge ausschließen zu können, die schlicht theoretisch und praktisch nicht möglich sind...


Ich kann es nicht wissen, nur vermuten, dass Du möglicherweise zu der jüngeren Generation gehörst, die - ich sag es mal so - in einem Hardware-Schlaraffenland lebt, wo sich jeder noch so ineffiziente Algorithmus durch einen schnelleren Prozessor und mehr RAM „beheben“ läßt. 
Das Auslagern von Daten aus dem RAM in einen langsameren, aber größeren Speicher gehört mit zu den Aufgaben eines jeden Betriebssystems. Erst damit wurde es möglich, dem Benutzer echtes/virtuelles Multitasking anzubieten, größere Datenmengen zu verarbeiten und und und. Und diese Thematik wurde in Fachbüchern behandelt, die erschienen sind, als die Hardware deutlich dürftiger war als ein heutiger Raspi.

Ich schrieb es schon: gängige Fieldrecorder (das sind Tonaufnahmegeräte) beschreiben SD-Karten zig-Gigabyteweise.  In meinem gesuchten Fall geht es um einige wenige GB im worst case. Im best case wird die SD-Karte gar nicht angerührt. Es gab hier ein paar Vorschläge, die darauf abzielten, dass die Daten grundsätzlich auf der SD-Karte gespeichert werden sollen. Das wäre tatsächlich nicht so günstig.



user30 hat gesagt.:


> mit unsinnigen Daten


Woher nimmst Du die Gewissheit, dass es sich hier um unsinnige Daten handelt?


----------



## Gelöschtes Mitglied 9001 (28. Jul 2020)

So, meine lieben Freunde, ein Wort zum Schluß. Mein Anliegen war es, den Thread nah beim Thema Programmierung zu lassen. Es tut mir sehr leid, wenn sich dadurch jemand gekränkt gefühlt hat, das war ganz sicher nicht meine Absicht. Ich selbst empfand es als unangemessen, das Projekt als „scheiße“ bezeichnet zu sehen, mir vehement die Notwendigkeit abzusprechen, dass ich hochaufgelöste Audiodaten unkomprimiert brauche, und in Frage zu stellen, was sich hier bei mir durch Tests schon lange bewährt hat. Ich schrieb das ja mehrfach. Ich habe nicht nur bei diesem Thread beobachtet, dass Themenstarter gern von oben herab behandelt werden im Sinne von: lern du erstmal richtig programmieren, bevor du hier bei uns ankommst. Das ist nicht bei allen Postern so, aber doch bei einigen. Das so als Feedback.
Ich bedanke mich sehr für alle Programmier-Beiträge, die mir Impulse gegeben haben. Das ein oder andere fließt bestimmt mit ein.
Ich verabschiede mich jetzt aus diesem Forum. Macht's gut.


----------



## mihe7 (28. Jul 2020)

@Rajmund das darfst Du nicht so eng sehen. Das Forum ist einfach eine Mischung aus Stackoverflow und Stammtisch-Diskussionen  Da wird sich auch über Alternativen unterhalten oder einfach ums Thema, weil es jemand interessant findet.

Inhaltlich ist die Sache ja scheinbar schnell erklärt: Du hast einen ankommenden, kontinuierlichen Datenstrom von etwa 2 MB/s und die Frage ist, wie sich das derart puffern lässt, dass der Client, der diese Daten abholt, auch mal eine halbe Stunde offline sein darf. Bislang bekannte Einschränkungen sind:

1. das RAM des Systems reicht nicht aus, um die Daten vollständig darin abzulegen
2. als Sekundärspeicher wird (billiger) Flashspeicher benutzt

Ich sehe 2. übrigens nicht unbedingt als Nachteil an. Genauso, wie man sich ein teureres System mit mehr RAM anschaffen kann, kann man auch alle heiligen Zeiten die SD-Karte ersetzen. Kassetten hielten auch nicht ewig 

Müsste man das Medium nicht schonen, wäre mein erster Ansatz, das Zeug einfach auf die Karte zu schreiben und von dort wieder zu lesen. Hier ergibt sich dann das erste Problem:


Rajmund hat gesagt.:


> Wenn der Abholthread nun plötzlich einen Datenblock auf die Karte schreiben muss oder einen Block löschen, braucht er etwas mehr Zeit.


Es können sich also beim Schreiben Schwankungen ergeben, so dass man einen Empfangspuffer im RAM vorschaltet, um diese auszugleichen. Sagen wir mal 10 MB, dann könnte man 5 Sekunden überbrücken.

Da der Client nicht zeitkritisch zu sein scheint, wäre das Problem auch schon gelöst, wäre da nicht noch die Sache mit der Lebensdauer der SD-Karte... Dabei geht es nicht ums Wear-Leveling, das ist Aufgabe der Treiber/Karte, sondern schlicht darum, die Zahl der Schreibzugriffe auf die Karte zu reduzieren.

Also wird ein weiterer Puffer benötigt. Der erfüllt seinen Zweck aber nur, so lange er nicht voll ist. Danach muss jede Operation auf der Karte ausgeführt werden.

Spätestens jetzt kommt auch der Client ins Spiel: wie schnell kann an den Client ausgeliefert werden? Wenn der Client die Daten ebenfalls nur mit max. 2 MB/s abruft, dann wird der Puffer niemals leerer und das System arbeitet, sobald der Puffer einmal voll ist, als ob der ursprüngliche Ansatz implementiert worden wäre (direkt auf Karte schreiben). Es könnte auch sein, dass der Client in unregelmäßigen Abständen Daten abruft, dann aber mit höchstmöglicher Datenrate. In dem Fall könnte es sinnvoll sein, die Daten ggf. bereits im RAM vorzuhalten werden (eine Art Sendepuffer). Das sind Dinge, die müsste man sich eben ansehen.

Das alles hat jetzt nichts mit der Umsetzung eines Ringbuffers zu tun (das sollte ja nun nicht das Problem sein), sind aber Überlegungen, die man m. E. anstellen muss.

EDIT: nu isser weg.


----------



## Meniskusschaden (28. Jul 2020)

Oh, da hat wohl jemand Probleme mit Meinungstoleranz. So kann es eben ausgehen, wenn man in jedem zweiten Posting seinen eigenen Thread torpediert. Andererseits vielleicht verständlich, wenn man den Anspruch hat, selbst zwar schon über den gesamten Problemkontext reden zu dürfen, anderen das aber nicht zugestehen möchte. Wenn man sich dann - wie die Fahrplananalogie nahelegt - noch in der Rolle des Arbeitgebers sieht, dem die Forenmitglieder zuarbeiten, wird man natürlich schnell von den Reaktionen eines offenen Diskussionsforums enttäuscht. Andere Meinungen können aber auch wirklich lästig sein. Da verdreht man dann schon mal gerne, wer hier eigentlich respektlos gepostet hat. Psychologisch äußerst interessant.



Gelöschtes Mitglied 9001 hat gesagt.:


> in einem Hardware-Schlaraffenland lebt, wo sich jeder noch so ineffiziente Algorithmus durch einen schnelleren Prozessor und mehr RAM „beheben“ läßt.


Wenn man aus prinzipiellen Gründen immer eine Softwarelösung vorzieht, ist das genauso schlecht, als wenn man aus Bequemlichkeit auf Optimierung verzichtet und lieber in Hardware investiert. So etwas ist doch immer einfach eine Abwägung der Verhältnismässigkeiten. Ideologie ist da fehl am Platz.


----------



## kneitzel (28. Jul 2020)

@mihe7 Also bei 2MBit/s stream ist es problematisch, wenn gleichzeitig gelesen und geschrieben werden muss, denn teilweise hat die Karte nur um 5MBit/s - also etwas knapp.

Aber es war von einer Verbesserung die Rede: und die erreicht man mit einem Ansatz wie z.B. dem von mir skizzierten und dann auch implementieren.
Oder habe ich da was vergessen/übersehen?

Und das wear Leveling kümmert sich bei allen Schreibzugriffen gut um die Problematik? Dann waren meine Sorgen diesbezüglich wohl falsch. Ich habe aber halt beim pi schon mehrere Karten gehämmert, so dass dies zumindest menschlich meine Sorgen erläutert (und ich arbeite fast nur mit Raspian) ....


----------



## mihe7 (28. Jul 2020)

JustNobody hat gesagt.:


> Also bei 2MBit/s stream ist es problematisch, wenn gleichzeitig gelesen und geschrieben werden muss, denn teilweise hat die Karte nur um 5MBit/s - also etwas knapp.


S. #59 



JustNobody hat gesagt.:


> Aber es war von einer Verbesserung die Rede: und die erreicht man mit einem Ansatz wie z.B. dem von mir skizzierten und dann auch implementieren.


Was meinst Du mit Verbesserung? Ich wollte Deinem Ansatz gar nicht widersprechen, sondern im Hinblick auf #124 mich der Problematik nochmals nähern, wobei deutlich werden sollte, dass es weit mehr Überlegungen/Probleme gibt, als eine (triviale) Implementierung eines Ringbuffers.



JustNobody hat gesagt.:


> Und das wear Leveling kümmert sich bei allen Schreibzugriffen gut um die Problematik?


Das weiß ich nicht, davon muss ich ausgehen dürfen bzw. muss mich nicht interessieren; die Karte ist Verbrauchsmaterial. Wenn ich mal von 3000 Zyklen ohne Wear-Leveling ausgehe, dann könnte man eine 6 GB große Datei (~ 1 h der Audiodaten) 3000 x überschreiben. das wären 3000 Stunden oder 125 Tage. Mal davon ausgehend, dass das Ding 24 Stunden am Tag zu 100 % puffert, sollte es kein Problem darstellen, die Karte alle 1, 2 oder 3 Monate zu wechseln 

Ich würde das Dateisystem noch mit noatime mounten und die Datei(en) in der gewünschten Größe einmal erstellen. Das hätte den Vorteil, dass das Dateisystem nicht mehr aktualisiert werden muss und hierfür keine Schreibzugriffe anfallen.


----------



## kneitzel (28. Jul 2020)

Ok, das mit #59 kann ich nachvollziehen. Aber wie gesagt: Bei einem pi traue ich der IO Leistung nicht wirklich ... 

Bezüglich Verbesserung: Das war eine Aussage vom TE, die ich jetzt nicht nachgeschlagen habe. Aber ich erinnere mich an einen Post, in dem er seine Aufgabe so beschrieb, dass er die Situation auf jeden Fall verbessern solle. Und ich habe das von Dir auch nicht als Widerspruch gesehen sondern ich bin halt nur am überlegen, ob ich wichtige Punkte einfach übersehen habe. Und die Lösung kommt mir teilweise auch verkompliziert vor und ich bin halt immer noch unsicher, ob dies überhaupt so Sinn macht.

Dein Ansatz, da einfach komplett möglichst optimiert auf der Karte zu arbeiten ist eine gute Vereinfachung und die gefällt mir nun eigentlich immer besser (jetzt wo der TE weg ist, muss man ja auch nicht mehr seine ursprüngliche Anforderung so fest halten ... das Du böse bist, weil Du da ständig sowas bringst, obwohl er doch mehrfach .... *lol*)

Wobei ich halt auch das eigentliche Problem angehen würde und das ist die Netzverbindung. Wenn die WLAN Verbindung so problematisch ist, dann wäre evtl. ein kleiner WLAN Repeater interessant, der auch nicht mehr kosten würde als ein pi. Und dann hat man eine stabile Verbindung, die die 2Mbit/s relativ sicher transportiert.

Die Vermeidung der Schreibzugriffe beim Verzeichnis ist auch noch ein interessanter Punkt. Ebenso die Kalkulation bezüglich Haltbarkeit - das hätte ich eigentlich auch schon früher machen können, um diese bessere Bewertung zu bekommen - wobei ich da irgendwie noch nicht die Abstraktion hatte von FileSystem Blöcken und den Medien-Blöcken... und da Unsinniges optimieren wollte (Ja, Optimierung ist böse, vor allem ohne grundsätzliche Analyse ... q.e.d. würde ich sagen.)


----------

