MiniAudio-Wrapper mit Callback stürzt ab.
Übersicht Andere Programmiersprachen Allgemein
MidimasterBetreff: MiniAudio-Wrapper mit Callback stürzt ab. |
Mo, Apr 19, 2021 10:32 Antworten mit Zitat |
|
---|---|---|
Ich habe begonnen meinen ersten Wrapper für eine C-Blibliothek zu schreiben und mir dafür das Projekt MiniAudio ausgewählt.
https://miniaud.io/ Dieser Wrapper würde, wenn er denn mal funktioniert, das WASAPI Device für BlitzMax ermöglichen. ch bin auch schon ganz schön weit gekommen. Momentan startet MiniAudio bereits und spielt auch Daten ab, die es sich ich über eine Callback-Funktion abholt. Allerdings geht dass immer nur ca 30sec gut. Dann stürzt das Ganze ab mit diesen beiden Meldungen: "Fatal error in GC: collecting from unknwon thread" or "Windows exception: EXCEPTION_BREAKPOINT" Jetzt wollte ich Euch mal fragen, ob überhaupt jemand im Forum ist, der sich bei solchen Problemen auskennt. Dann würde ich nämlich in einem zweiten Schritt alle Informationen und code-Snipplets hier reinstellen. Der CallBack wird in MiniAudio so definiert: Code: [AUSKLAPPEN] DATA_CALLBACK ()
//at line 36 void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) { // my code } and the caller in midiaudio.h: // at line 3125 typedef void (* ma_device_callback_proc)(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount); (die Zeilennummern beziehen sich auf der Quellcode "miniaudio.h") Die Adresse der eigene Callback-Function wird über eine STRUCT an mniaudio.h übergeben. Die Adresse der STRUCT speichere ich wiederum als BYTE PTR ab. Das ist der komplette Wrapper-Code: Code: [AUSKLAPPEN] #define MINIAUDIO_IMPLEMENTATION
#define MA_API extern #define MA_DEBUG_OUTPUT //printf() logging=on #define MA_LOG_LEVEL_INFO 3 #define MA_LOG_LEVEL MA_LOG_LEVEL_INFO #include "miniaudio.h" #include <stdio.h> struct ma_device_config *ma_device_config_init_glue(int DeviceTyp) { printf("START_DEVICE_GLUE \n"); struct ma_device_config *instance; int length = sizeof(struct ma_device_config); instance = (struct ma_device_config*) malloc(length); *instance= ma_device_config_init(DeviceTyp); printf("set sample rate= %i \n", instance->sampleRate); return instance; } // Device Filler: void MM_SetDeviceConfig (struct ma_device_config *config, int format, int channels, int hertz, ma_device_callback_proc *Callback) { printf("FILL_VALUE_B \n"); config->sampleRate=hertz; config->playback.format=format; config->playback.channels=channels; printf("set sample rate= %i \n", config->sampleRate); config->dataCallback= Callback; printf("set callback pointer= %i \n", config->dataCallback); } struct ma_device *MM_GetDevice(struct ma_device_config *config) { printf("CREATE A DEVICE \n"); struct ma_device *instance; instance->sampleRate=222; int length = sizeof(struct ma_device); instance = (struct ma_device*) malloc(length); ma_device_init( NULL, config, instance); printf("set sample rate= %i \n", instance->sampleRate); return instance; } BlitzMax: [AUSKLAPPEN] Global MiniAudio:TMiniAudio=New TMiniAudio as könnte die Ursache für so eine Fehlemeldung sein? Wenn ich keine Commands in die MyCallBack_II reinschreibe stürzt das Ganze nicht ab. Wenn ixh in den Main-Loop eine Delay 1 reinsetze duaert es deutlich länger, bis der Absturz kommt, aber er kommt! Muss ich vielleicht eine CreateStaticBank im Type um den Pointer "Device" anlegen, damit der RAM-Bereich wirklich geschützt ist? BlitzMax: [AUSKLAPPEN] Type TMiniAudio |
||
Gewinner des BCC #53 mit "Gitarrist vs Fussballer" http://www.midimaster.de/downl...ssball.exe |
CO2ehemals "SirMO" |
Mo, Apr 19, 2021 20:40 Antworten mit Zitat |
|
---|---|---|
Hallo,
ich kenne mich wohl mit C++ aus, mit C habe ich wenig gemacht. Speziell eine Verbindung zwischen BlitzMax und C habe ich noch nie geschrieben (glaube ich). Ich will trotzdem meinen Senf dazugeben. Die Meldung könnte so aufs erste bedeuten, das der BlitzMax-GC versucht, Speicher eines anderen Prozesses aufzuräumen. Vielleicht ist es diese Zeile: Code: [AUSKLAPPEN] Local Sample:TBank=CreateStaticBank(Buffer,Frames*4)
Buffer ist ein Zeiger auf ein Objekt, das mit C allokiert wurde. Kann es ein, dass die StaticBank beim aufräumen diesen Speicher versucht, freizuräumen? Um das herauszufinden, könntest Du mal spasseshalber den Buffer vor dem Aufruf kopieren und mit dem Kopierten die Bank zu erzeugen. Ich vermute, dann würde Dein Programm zwar nicht mehr richtig funktionieren, da es zwei Objekte im Speicher gibt, aber so könntest Du über eine längere Laufzeit des Programms ermitteln, ob der Absturz noch kommt. Alternativ müsste man in der Doku von BlitzMax schauen, ob es eine Funktion gibt, die z.B. nicht den Speicher freiräumt. Zu dem C-Code: 1.) Code: [AUSKLAPPEN] struct ma_device *MM_GetDevice(struct ma_device_config *config) {
printf("CREATE A DEVICE \n"); struct ma_device *instance; // HIER instance->sampleRate=222; int length = sizeof(struct ma_device); instance = (struct ma_device*) malloc(length); ma_device_init( NULL, config, instance); printf("set sample rate= %i \n", instance->sampleRate); return instance; } In der Zeile mit dem Kommentar "HIER" wird auf dem Stack ein neuer Zeiger auf ein Objekt vom Typ struct ma_device gelegt, welcher nicht initialisiert wurde. D.h. der zeigt irgendwo in den Speicher. In der nächsten Zeile wird in den Member sampleRate geschrieben. Das könnte zu Abstürzen führen, da der Zeiger nicht auf ein valides Objekt zeigt. 2.) Meine Syntaxkenntnisse bezüglich der alten Funktionszeiger sind etwas eingerostet, aber ich glaube, diese Definition ist komisch: Code: [AUSKLAPPEN] void MM_SetDeviceConfig (struct ma_device_config *config, int format, int channels, int hertz, ma_device_callback_proc *Callback) . ma_device_callback_proc ist schon ein Zeiger auf eine Funktion, d.h. an dieser Stelle wird ein Zeiger auf einen Zeiger erwartet, was glaube ich nicht richtig ist. Es müsste ausreichen, an dieser Stelle ein ma_device_callback_proc zu erwarten.
EDIT: Ich habe das nochmal ganz schnell in einem Online-Compiler runtergeschrieben https://onlinegdb.com/r1ks1LoLu 3.) Was gibt diese Funktion zurück? Code: [AUSKLAPPEN] ma_device_config_init(DeviceTyp) Die wird nämlich folgendermaßen aufgerufen Code: [AUSKLAPPEN] *instance= ma_device_config_init(DeviceTyp); was eine Wertekopie darstellt. Es kann sein, dass das passt, meine Erfahrung lehrt mich aber, das soetwas in C selten gemacht wird und eher das Objekt als Zeiger an die Funktion übergeben wird.
EDIT: Würde zumindest gehen: https://onlinegdb.com/SJPKNIoLu |
||
mfG, CO²
Sprachen: BlitzMax, C, C++, C#, Java Hardware: Windows 7 Ultimate 64-Bit, AMX FX-6350 (6x3,9 GHz), 32 GB RAM, Nvidia GeForce GTX 750 Ti |
Midimaster |
Di, Apr 20, 2021 10:57 Antworten mit Zitat |
|
---|---|---|
neuester Stand der Dinge:
Es scheint sich wohl um zwei Probleme zu handeln. Ich bin noch kein Stück weiter aber es gibt "workarounds". 1. Wenn ich per GCSupspend den Garbage-Collector ausschalte ist das Problem weg. Allerdings genügt es nicht das GCSuspend innerhalb des Callbacks zu setzen und am Ende des Callbacks wieder mit GCResume den Garbage-Collector einzuschalten. Wenn ich es so mache wird das Programm länger laufen, aber dennoch nach 90-120sec abstürzen. Erst mit einer kompletten Abschaltung des Garbage-Collector unmittelbar vor der Main REPEAT-Schleife scheint das Programm dauerhaft zu laufen. Test sind aber bisher nur max 200sec lang. Mir ist aber klar, dass ich durch diesen Trick den Progammier-Fehler nicht behebe, sondern eine seiner Folgen unterbinde. 2. Das zweite Problem scheint die Auslastung des Rechners durch die Main REPEAT-Scheife zu sein. Dort wird unbedingt eine Wartepunkt benötigt, wo der Programmcounter ins SYSTEM zurückspringt . also eine DELAY 1 oder der "FLIP ohne 0", oder beides. Steigt die Performance der App im Taskmanager auf Werte über 20% an, crashed das Programm. Es ist auch zu beobachten, dass der Wert GCMemAlloced() dramatisch ansteigt. Bei gesetzem DELAY steigt er auch laufend doch viel langsamer. Jetzt zu CO2 Danke, dass Du hier mithilfst. Deine Antworten übersteigen meine C-Kenntnisse, aber ich versuche mich schlauer zu machen.... Das Ausschalten des Garbage-Collector wie Du es vorgeschlagen hast hat gewirkt. Damit läuft dass Programm. Die Ursache liegt aber nicht an der TBANK. Der Fehler kommt auch, wenn ich nicht die Bank erstelle. Ein simples.... BlitzMax: [AUSKLAPPEN] Function MyCallBack(a%, Buffer:Byte Ptr, RecordingBuffer:Byte Ptr, Frames%) ...reicht bereits aus um abzustürzen. Sehr spannend scheint die falsche Übergabe des CallBack-Zeigers zu sein. Hierzu werde ich ein paar Experimente machen und im englischen Forum nachhaken. Zitat: ... 3.) Was gibt diese Funktion zurück? Code:
ma_device_config_init(DeviceTyp)... Es ist eine große STRUCT. In ihr legt man die gewünschten Parameter und den Callback-Pointer in aller Ruhe an und übergibt die Adresse *config der STRUCT einmalig an die miniaudio.h-Funktion ma_device_init(); Code: [AUSKLAPPEN] struct ma_device *MM_GetDevice(struct ma_device_config *config) {
printf("CREATE A DEVICE \n"); struct ma_device *instance; int length = sizeof(struct ma_device); instance = (struct ma_device*) malloc(length); ma_device_init( NULL, config, instance); //*********************hier printf("set sample rate= %i \n", instance->sampleRate); printf("sizeof struct %i \n", length); return instance; } danach wird sie nicht mehr von miniaudio.h benötigt. Hier sind mal alle drei Teile meines Wrappers. Darüber hinaus wird nur noch die miniaudio.h benötigt die man hier findet: https://raw.githubusercontent....iniaudio.h Main.bmx BlitzMax: [AUSKLAPPEN] SuperStrict MiniAudioWrapper.bmx BlitzMax: [AUSKLAPPEN] Import "MiniAudio.c" MiniAudio.c Code: [AUSKLAPPEN] #define MINIAUDIO_IMPLEMENTATION
#define MA_API extern #define MA_DEBUG_OUTPUT //printf() logging=on #define MA_LOG_LEVEL_INFO 3 #define MA_LOG_LEVEL MA_LOG_LEVEL_INFO #include "miniaudio.h" #include <stdio.h> //#include "simulate.h" struct ma_device_config *ma_device_config_init_glue(int DeviceTyp) { printf("START_DEVICE_GLUE \n"); struct ma_device_config *instance; int length = sizeof(struct ma_device_config); instance = (struct ma_device_config*) malloc(length); *instance= ma_device_config_init(DeviceTyp); printf("set sample rate= %i \n", instance->sampleRate); return instance; } // Device Filler: void MM_SetDeviceConfig (struct ma_device_config *config, int format, int channels, int hertz, ma_device_callback_proc *Callback) { printf("FILL_VALUE_B \n"); config->sampleRate=hertz; config->playback.format=format; config->playback.channels=channels; printf("set sample rate= %i \n", config->sampleRate); config->dataCallback= Callback; printf("set callback pointer= %i \n", config->dataCallback); } struct ma_device *MM_GetDevice(struct ma_device_config *config) { printf("CREATE A DEVICE \n"); struct ma_device *instance; int length = sizeof(struct ma_device); instance = (struct ma_device*) malloc(length); ma_device_init( NULL, config, instance); printf("set sample rate= %i \n", instance->sampleRate); printf("sizeof struct %i \n", length); return instance; } |
||
Gewinner des BCC #53 mit "Gitarrist vs Fussballer" http://www.midimaster.de/downl...ssball.exe |
- Zuletzt bearbeitet von Midimaster am Di, Apr 20, 2021 13:01, insgesamt einmal bearbeitet
Thunder |
Di, Apr 20, 2021 12:51 Antworten mit Zitat |
|
---|---|---|
EinFehler in MM_GetDevice ist der Zugriff auf sampleRate durch den nicht initialisierten Pointer instance. Hier wird ein potentiell zufälliger Pointer dereferenziert. Die Zeile instance->sampleRate muss unter instance = malloc verschoben werden.
Ein weiteres Problem ist in MM_GetDevice dass der return value von ma_device_init nicht überprüft wird. Stattdessen gibt die Funktion einen Pointer zurück, der von malloc erzeugt wurde (dieser Pointer enthält keine Information darüber, ob die ma_device_init Funktion gescheitert ist). Der return wert von ma_device_init muss auf MA_SUCCESS geprüft werden. |
||
Meine Sachen: https://bitbucket.org/chtisgit https://github.com/chtisgit |
Midimaster |
Di, Apr 20, 2021 13:08 Antworten mit Zitat |
|
---|---|---|
Thunder hat Folgendes geschrieben: ...EinFehler in MM_GetDevice... instance->sampleRate....
hab ich gleich rausgenommen. War eh nur während der Simulation drin und blieb versehentlich im Code. Zitat: ...Ein weiteres Problem ist in MM_GetDevice dass der return value von ma_device_init nicht überprüft wird. Das ist richtig. Das werde ich gleich nachholen. Während der Simulation habe ich das noch nicht prüfen können und anschließend vergessen es für die "echte" ma_device_init() aufzunehmen. Danke |
||
Gewinner des BCC #53 mit "Gitarrist vs Fussballer" http://www.midimaster.de/downl...ssball.exe |
CO2ehemals "SirMO" |
Di, Apr 20, 2021 18:46 Antworten mit Zitat |
|
---|---|---|
Zitat: Sehr spannend scheint die falsche Übergabe des CallBack-Zeigers zu sein. Hierzu werde ich ein paar Experimente machen und im englischen Forum nachhaken. - Sie ist formal nicht falsch, nur komisch
Ich habe den Code der miniaudio.h mal überflogen, und die Structs selber halten keine Zeiger auf Zeiger. Ich bin es aber nicht im Detail durchgegangen... Ich packe hier mal einen Code-Ausschnitt rein, da ich um die Lizenz nicht weiß Code: [AUSKLAPPEN] struct ma_device_config
{ /*...*/ ma_device_callback_proc dataCallback; /*...*/ }; Zu 2.) In C können Memoryleaks auftreten, diese seien der Vollständigkeit halber hier erwähnt: Alle Objekte, die mit malloc allokiert wurden (z.B. in MM_GetDevice, oder in ma_device_config_init_glue) müssen früher oder später per free() wieder freigegeben werden und zwar spätestens wenn der letzte Zeiger auf das Objekt seinen Gültigkeitsbereich verlässt. Ist der Wrapper von Dir geschrieben, oder kommt der wo anders her? Weiter noch die Frage, was genau meinst Du mit Zitat: (...) Steigt die Performance der App im Taskmanager auf Werte über 20% an, crashed das Programm (...) welche Auslastung? RAM oder CPU? Ich vermute mal einfach RAM, da Du bei einer Single-Core-Anwendung auf einem modernen Rechner vermutlich keine 20% CPU-Auslastung hinbekommst
[To be continued...] |
||
mfG, CO²
Sprachen: BlitzMax, C, C++, C#, Java Hardware: Windows 7 Ultimate 64-Bit, AMX FX-6350 (6x3,9 GHz), 32 GB RAM, Nvidia GeForce GTX 750 Ti |
Midimaster |
Di, Apr 20, 2021 19:39 Antworten mit Zitat |
|
---|---|---|
Der Wrapper ist ja das, worum es mir hier geht.
Ich wollte mal lernen, wie man einen Wrapper schreibt und gleichzeitig muss ich mir dafür aber C beibringen. Gleichzeitig wäre es eine Bereicherung für BlitzMax, denn der Wrapper ermöglicht erstmals WASAPI in BlitzMax zu nutzen und Realtime Zugriff auf das AudioDevice ohne diesen sperrigen Zugang via TSound() . Die Latenzzeit sinkt dann auf 30msec und weniger. Also ist der Wrapper von mir. Und er besteht ja auch erst aus wenigen Zeilen und ist erst der Einstieg in die MiniAudio. Und eben wahrscheinlich noch nicht fehlerfrei. Die Library kann noch vieles mehr. Allerding ist mit dem Initialisieren der anstrengenste Teil der Arbeit auch schon getan. Sie tut ja grob schon, was ich will. Mein Beispielprogramm erstellt die Samples nur 30msec bevor sie auf dem Speaker zu hören sind. Die 20% Auslastung kriegst Du mit einem Streßtest schon hin: BlitzMax: [AUSKLAPPEN] Repeat erzeugt bei mir 24% in einem der Kerne. Ohne ein DELAY 0 stürzt hier leider der Wrapper noch ab. Mit einem DELAY 0 kann man das App-Fenster verschieben, ohne dass die Sounds abbrechen. |
||
Gewinner des BCC #53 mit "Gitarrist vs Fussballer" http://www.midimaster.de/downl...ssball.exe |
CO2ehemals "SirMO" |
Di, Apr 20, 2021 20:09 Antworten mit Zitat |
|
---|---|---|
Okay... Das ist alles komisch
Ich habe auch mal versucht, ein solches Verhalten zu provozieren, "leider" funktioniert bei mir alles. Hier der C-Code Code: [AUSKLAPPEN] #include <stdlib.h>
struct CMyStruct { int m_Member1; int m_Member2; char m_Array[10000]; }; typedef void(* callback_function)(struct CMyStruct *pStruct, int test); struct CMyStruct *pGlobalObject = NULL; callback_function globalCallback = NULL; void Init(callback_function callback) { globalCallback = callback; pGlobalObject = (struct CMyStruct *)malloc(sizeof(struct CMyStruct)); } void CallCallback() { if (globalCallback) { globalCallback(pGlobalObject, 123); } } struct CMyStruct *CreateStruct() { return (struct CMyStruct *)malloc(sizeof(struct CMyStruct)); } struct CMyStruct *GetGlobalObject() { return pGlobalObject; } void Shutdown() { if (pGlobalObject) { free(pGlobalObject); } } Und hier die BMX Code: [AUSKLAPPEN] import "Test.c"
extern "C" function Init(callback:byte ptr) = "Init" function CallCallback() = "CallCallback" function Shutdown() = "Shutdown" function GetGlobalObject:Byte ptr() = "GetGlobalObject" function CreateStruct:Byte ptr() = "CreateStruct" end extern function MyCallback(obj:byte ptr, num:int) CreateBank(100000) end function ' Programm: graphics 800, 600 Init(MyCallback) repeat Cls CallCallback() DrawText "memory=" + GCMemAlloced(), 100,130 flip 1 until appterminate() Shutdown() end Das einzige, was ich mir noch vorstellen kann ist, dass es irgendetwas mit dem asynchronen Ablauf zu tun hat. Kannst Du Dir mal in Deinen Callback loggen, wann Du rein gekommen bist und wann die letzte Zeile erreicht wurde? Sind diese Aufrufe immer streng synchron? Also hast Du immer eine solche Ausgabe Zitat: Einstieg
Ausstieg Einstieg Ausstieg Einstieg Ausstieg ... oder sieht das zwischendurch mal so aus Code: [AUSKLAPPEN] Einstieg
Einstieg Ausstieg Ausstieg Einstieg Ausstieg ... Vielleicht kommt man so dem Problem auf die Schliche... |
||
mfG, CO²
Sprachen: BlitzMax, C, C++, C#, Java Hardware: Windows 7 Ultimate 64-Bit, AMX FX-6350 (6x3,9 GHz), 32 GB RAM, Nvidia GeForce GTX 750 Ti |
Midimaster |
Mi, Apr 21, 2021 0:04 Antworten mit Zitat |
|
---|---|---|
genauso habe ich das auch getestet und dabei kam heraus, dass die CallBack in immer der gleichen Reihenfolge durchlaufen wird.
Ausgabe Code: [AUSKLAPPEN] Start
Frames=480 Adresse Buffer=56789123 Ende Im Moment des Abbruchs steht dann manchmal Start als letztes, manchmal Frames, manchmal Adresse, manchmal Ende. Ich hatte ja auch schon wie du einen Simulator geschrieben, der die miniaudio.h mit deutlich übersichtlichen STRUCTs nachbildet. Hier kommt es zu keinerlei Abstürzen. |
||
Gewinner des BCC #53 mit "Gitarrist vs Fussballer" http://www.midimaster.de/downl...ssball.exe |
MidimasterBetreff: MiniAudio Wrapper |
Do, Apr 22, 2021 14:45 Antworten mit Zitat |
|
---|---|---|
Der Fehler ist behoben, der Wrapper läuft wieder stabil. Ursache war dass man in BlitzMax-CallBacks, die von C aufgerufen werden einen dort als Parameter gesendeten Rambereich nicht mit einer lokalen TBANK ansprechen darf, sondern besser der als Parameter vorhandene Pointer zum Durchforsten des RAMs benutz werden sollte.
vorher: BlitzMax: [AUSKLAPPEN] Function MyCallBack_II(a%, Buffer:Byte Ptr, RecordingBuffer:Byte Ptr, Frames%) besser: BlitzMax: [AUSKLAPPEN] Function MyCallBack_II(a%, Buffer:Short Ptr, RecordingBuffer:Byte Ptr, Frames%) Wer übrigens mal Audio-Ausgabe via WASAPI auf BlitzMax testen möchte... hier findet ihr alle Sourcecodes und erste Beispielprogramme: https://www.syntaxbomb.com/ind...419.0.html Beispiele: - nur 30msec Latenz - Realtime Stereo Audio Creation - time stretching ohne Frequenzänderung - Real time Reverb - Easy Capture/Recording - Audio Duplex etc... es folgen jetzt laufend Beispiele dort und der nächste Schritt wird "Equalizer in eigenen Audio-Files" |
||
Gewinner des BCC #53 mit "Gitarrist vs Fussballer" http://www.midimaster.de/downl...ssball.exe |
- Zuletzt bearbeitet von Midimaster am Fr, Apr 23, 2021 18:24, insgesamt einmal bearbeitet
Thunder |
Do, Apr 22, 2021 18:02 Antworten mit Zitat |
|
---|---|---|
Ich finde es cool, dass du die Lösung rein postest und auf das Forum verweist!
Den Pointer direkt zu benutzen ist auf jeden Fall das Beste (kein Overhead). Die Bank hätte mit Größe Frames*2 erstellt werden müssen (1 Short = 2 Byte), dann hätte der Code vielleicht funktioniert. |
||
Meine Sachen: https://bitbucket.org/chtisgit https://github.com/chtisgit |
Midimaster |
Do, Apr 22, 2021 20:15 Antworten mit Zitat |
|
---|---|---|
war nur ein Tippfehler hier. Im Code war es "Frames*2" | ||
Gewinner des BCC #53 mit "Gitarrist vs Fussballer" http://www.midimaster.de/downl...ssball.exe |
Thunder |
Fr, Apr 23, 2021 2:52 Antworten mit Zitat |
|
---|---|---|
Alles klar! dann hab ich offiziell keine Ahnung. Schön dass es funktioniert! | ||
Meine Sachen: https://bitbucket.org/chtisgit https://github.com/chtisgit |
Übersicht Andere Programmiersprachen Allgemein
Powered by phpBB © 2001 - 2006, phpBB Group