Unterstützung für 13 Sound-Systeme (also auch das "neue" 10.2 Surround)



  • Hallo,

    zunächst zu meinen Arbeitsumfeld:

    Unter Windows verwende ich via WinApi ausschließlich die wavout anweisungen für meine neue kleine Sound-Library. Da muss ich dann nicht umständlicherweiße irgendeine teure DLL oder ein 300 MB SDK zum Kompilieren herunterladen.

    Andere Soundlibs gibt es zwar auch noch (inkl. Sources), sind in ihrere Lizenz aber eingeschränkt oder benötigen ein 300 MB Platform SDK von Microsoft. Alles Dinge denen ich Programmierer meiner Libs sowieso nicht unbedingt antun will.

    Normalerweise schreibe ich extrem wenig oder benötige extrem selten wirklich tatkräftige Hilfe, aber nun ist es wieder so weit.

    Ich habe mich mal über verschiedene Sound-Systeme schlau gemacht, weil ich eine WAV-Stram-Klasse (natürlich mit Befehlen wie globalAlloc, globalLock, wavout etc.) so weit fertig gestellt habe, dass ich über das übliche Stereo hinaus kann. Ich möchte auch über das übliche Stero hinaus und aus der folgenden Liste Unterstützung für das Sound-System 5.1 einbauen:

    Nun könnte ich einfach eine WAV-Datei entsprechend aus der Audio-Software encodieren lassen, durch den Streamer durchjagen und fertig. Eben nicht.

    Nach der Situationserklärung werde ich nun verschiedene Fragen stellen, die in sich nochmals kurz eine kleinen Sachverhalt erläutern.

    Frage 1: Eine WAV-Datei soll via Stereo zu hören sein bzw. abgespielt werden. Also muss während dem abspielen ein Ton beispielsweise von 10.2 Surround, 14-Kanal in z.B. 2.1 Stereo, 3-Kanal (.1 ist mit Bass) umgewandelt werden.

    Nun kann ich zwar aus der WAV-Datei bzw. anhand der Anzahl der abzuspielenden Kanäle in etwa das Sound-System erkennen, aber gibt es dafür nicht auch festgelegte/standardisierte/übliche/gängige Richtlinien an die ich mich als Programmierer halten kann?

    Gibt es eventuell sogar einen "Pin" (auch wieder winapi), womit ich solch eine Anfrage direkt an die Soundkarte mitsamt Wav-Header (und ein paar weiteren Daten) zur Auswertung hinschicken kann? In wie weit sähe es hier mit der Soundkarten-Kompatibilität aus? Wäre imho nicht so verkehrt, nachdem bereits die Soundkarte sowieso aktiv via Treiber mitwirkt (speziell Sound-Karten die auf 3D-Klang ausgelegt sind).

    Natürlich könnte ich dem User in den Spieloptionen auswählen lassen, ob gerade Bass-Boxen angeschlossen sind. Allerdings erkenne ich dann immer noch nicht, ob bei einen 5-Kanal-Sound-System nun 4.1 Surround oder 4.1 gemeint ist. Die Bezeichnungen sind zwar ähnlich, betrachtet man aber beispielsweise das Thema Surround bei Wikipedia, Tabelle: "Standard speaker channels", dann erkennt man gravierende Unterschiede. Es ist unklar, welche Kanäle für den Fall des jeweils anderen genutzt werden sollen.

    Natürlich könnte ich Vorgaben machen, aber das möchte ich nicht. Deshalb benötige ich bei dieser Frage hilfe.

    Frage 2: Jede mit wavOutOpen erstellte Instanz kann in der Lautstärke eingestellt werden (siehe waveOutSetVolume). Jetzt ist hier aber auffällig, dass nur ein DWORD übergeben werden kann. Das heißt: Die Lautstärke kann nur für die zwei vorderen Boxen eingestellt werden.

    Natürlich könnte ich mehrere wavOutOpen-Instanzen erstellen um das Problem zu lösen, dann müsste ich allerdings eine weitere Abstraktionssicht der Streaming-Klasse erzeugen. Das möchte ich aber nicht.

    Wie kann ich bei einer wavOutOpen-Instanz die Lautstärke für alle Kanäle einstellen, ohne dynamisch an den Samples der einzelnen Kanälen etwas ändern zu müssen (manuelle Änderung der Sound-Laustärke)?

    Beim manuellen Ändern der Sound-Lautstärke müssten die Gegebenheiten von Subwoofern, Ausrichtungen der Sound-Boxen usw. des jeweiligen Sound-Systems beachtet werden. Es könnte also passieren, dass für die Bass-Box bei 4.1 Surround, 5-Kanal die Lautstärke anders berechnet werden muss, als wie für die Bass-Box bei 4.1, 5-Kanal.

    Das wäre kein Problem, aber dafür würde ich eine System-Erkennung benötigen. Sowohl von der encoder als auch von der decoder seite her. Oder aber ich gebe einfach die Lautstärke via waveOutSetVolume an, aber da kann ich nur zwei Kanäle einstellen.

    Gibt es vielleicht eine undokumentierte WinAPI Funktion die hier gerade jemand zufällig kennt?



  • Bereits 44 Leser hat dieser Beitrag gehabt und normalerweise bin ich kein Pusher (Pfuscher auch nicht 😉 ), aber wenn fragen sind oder irgendetwas unklar ist, dann wüsste ich gerne darüber bescheid.

    Ich habe mir mein Beitrag auch nochmal durchgelesen und musste feststellen, dass es sich ließt als wollte ein Student seine Arbeit vorgemacht bekommen.

    Ich muss zugeben, dass ich zwar schon Lösungsansätze da rein verzapft habe (also im ersten Post), aber andererseits bezweifle ich sehr stark, dass meine Lösungansätze gut sind.

    Ich versuche hier eine halbwegs gescheite Sound-Lib. auf die Beine zu stellen, welches wirklich vollkommen frei und einfach zu implementieren ist, weil ich keine lust habe 10.000$ für eine DLL zu zahlen, sobald ein Game auf verschiedene Portale zum verkauf angeboten werden soll. Daran ist nämlich mein Projekt letztes Jahr gescheitert. Publisher wollte nicht zahlen bzw. das Risiko eingehen.

    Falls mich hier niemand kennen sollte, dem sei gesagt, dass ich meine Libs immer mit Sources veröffentlich habe. Ohne irgendeine Bindung an eine bescheuerte Lizenz.

    Natürlich könnte ich von Open-Source Projekten abschauen, aber das ist nicht mein Ziel. Zwar mag das Themenumfeld groß sein, aber nicht komplex, als ob das abschauen von Open-Source Projekten wirklich nötig wäre.
    Wobei ich noch dazu schreiben muss, dass mir einige Projekten ziemlich überladen vorgekommen sind.

    Ich möchte jediglich MIDI und WAV unterstüzen. Dinge wie MP3-Support soll man bei meiner Lib. selbst implementieren müssen, weil es dafür bereits ausgereifte decoder gibt, die auch Streaming-Fähig sind. Gepaart mit einen guten WAV-Streamer lüppt alles AAA-Quality-Mäßig zu den Boxen.

    Es wäre also sehr Nett, wenn sich jemand erbarmen würde um mir zu helfen und aus dem nähkästchen plaudern könnte. ➡



  • gut Ding will Weile haben, vor allem weil du sehr spezifische Fragen hast. Soviel WinAPI-Sound-Genies gibts hier wohl nicht. Wenn du auch in 1-2 Tagen noch keine Antwort hast, frag im Spiele- und Grafikforum nochmal, da treiben sich womoeglich mehr Multimedia-Spezialisten rum 🙂



  • Vielen dank für die Antwort,

    aber in meinen Fall betrifft es im Moment noch die Programmierung bzw. die mehr technische Seite als die direkte Multimediale Anwendung.

    Ich glaube meine Idee könnte doch tatsächlich funktionieren, habe schließlich die Mixer API entdeckt mit vielen netten Konstanten wie z.B. MIXERCONTROL_CONTROLTYPE_BASS und die interessante Information, dass für jede neue waveOutOpen-Instanz auch so etwas wie ein neuer Mixer angelegt werden kann.

    Da muss ich mich aber erst noch richtig rein finden. Sollte ein paar Fragen offen bleiben, dann werde ich das nächste mal in der richtigen Abteilung ein Thread erstellen.



  • Mit dem Mixer wäre ich vorsichtig. Der steuert z.T. direkt die Hardware an, und die macht z.T. was sie will. Manche Chips meinen dass sie Lautstärke linear zu verstehen haben, andere logarithmisch und vermutlich gibt es sogar welche die noch ganz was anderes machen.

    Es gibt zwar z.T. Standards (z.B. USB Audio), aber daran halten sich leider nicht alle Geräte. Ich hab' selbst zwei verschiedene USB Audio Geräte, und die verhalten sich krass unterschiedlich. Ich vermute dass eines linear regelt (weil es bei 10% Master-Volume-Slider Einstellung bereits fast volle gehörte Lautstärke hat), und das andere logarithmisch (weil die gehörte Lautstärke sich "stimmig" mit dem Master-Volume-Slider ändert). Was korrekt ist weiss ich nicht genau (ich vermute logarithmisch). Es können aber nicht beide richtig machen, sonst wäre ja kein Unterschied 🙂

    Wenn du also die relative Lautsträke der einzelnen Kanäle zueinander exakt steuern willst, dann multipliziere einfach. Oder verwende eine API die es garantierterweise in Software macht.

    Ich glaube auch, dass es kein nennenswerter Vorteil wäre, die relative Lautstärke über etwas zu steuern was die Multiplikation analog durchführen kann. Wenn du intern mit 24 Bit Integer rechnest (oder gleich 32 Bit Float), und in der "Ausgangsstufe" (nach der Lautsträke-Multiplikation) ggf. auf 16 Bit runter-ditherst wird der Qualitätsverlust durch die Lautsträke-Multiplikation vermutlich nicht hörbar sein. (Ausser natürlich von GOFs (Goldohrfraktion), die hören aber auch das Gras wachsen)

    (Falls das Ausgabegerät 24 Bit wiedergeben kann macht das Dithern IMO keinen Sinn mehr, auch nicht wenn du vorher mit 32 Bit Float rechnest. Kein mir bekanntes Ausgabegerät schafft auch nur annähernd einen Rauschabstand, bei dem man den Unterschied zwischen geditherten und ungeditherten 24 Bit noch messen geschweige denn hören könnte.)



  • Ich habe mir alles nochmals genau angeschaut, ein wenig experimentiert und alles genau überlegt.

    Von den Mixern werde ich sowieso die finger weg lassen. Einzig werde ich die Devices ermitteln und gegebenenfalls via waveOutOpen öffnen. Das war es auch schon.

    Sollen mehrere Sound gleichzeitig gespielt werden, dann werden diese in ein Stream verfrachtet. Den Stream selbst lasse ich das Doublebuffering beibehalten.

    Weil ich später eventuell Multithreading einbauen werde bzw. die Möglichkeit dafür erschaffen, werde ich ein weiteres Buffer einbauen. So habe ich folgende Aufteilung:

    1. Thread = Loader & Memory manager
    2. Thread = Core bzw. Mischer & Sender

    Man könnte noch für ein 3. Thread vorbauen, welches nur für die Umwandlung zuständig wäre. Da müsste man aber einen weiteren Buffer einbauen. Deshalb denke ich, dass da eine direkte Umwandlung in der Ausführung schneller wäre.

    Der mit waveOutOpen erstellte Ausgabe-Kanal wird genau die Anzahl an Kanäle haben, wie das weilige Device dies auch unterstützt. Auf diesen Weg kann ich dann auch direkt ganz einfach die Sound-Boxen ansprechen (eben durch die entsprechende Kanäle) und eventuell sogar ganz einfach 2.1 Stereo, 3-Kanal in Mono umwandeln.

    Lautstärke werde ich mit einfacher Multiplikation realisieren. Dazu gepaart mit einen kleinen Rauschentferner (eventuell wieder einmal Perlin Noise alg.) und Nebeneffekte sind kaum hörbar (dafür wohl aber auch ganze leise Töne).

    Echtes Threading werde ich nicht einbauen (der eigenständige Thread von wavOutOpen genügt) aber, wie bereits beschrieben, die Aufteilung in diese Ebenen tätigen.

    Ansonsten werde ich wohl mit 16 Bit als Standard Soundausgabe arbeiten. 32 Bit würde demnach in 16 Bit umgewandelt bzw. zugleich der Sound wohl auch "etwas normalisiert" werden.

    Ganz klar werde ich das genannte 32 Bit Float nicht verwenden, weil am Ende zu ungenau. 😉

    In der Umsetzung würde ich Vollzeit (8h am Tag) etwa ein oder zwei Wochen benötigen, speziell weil ich noch nicht genau weiß was mich bzgl. Rauschentfernung erwartet.



  • Ich habe jetzt mal einen kleinen Anfang gemacht. Mit drei Befehlen können nun schon einmal die Devices abgefragt werden. Bis der Rest drin ist ("device starten" usw.), kann es aber noch dauern...

    Edit: Wenn sich jemand fragt, wieso ich die Strings so extrabescheuert behandle, dann liegt das daran, dass ich nicht möchte, dass meine Soundlib anfällig für Fake-Sound-Treiber (Trojaner getarnt als Treiber) wird.

    MAIN_SOUND_FUNC void soundManager_refreshSoundDevicesList() {
    		if ( soundManager_soundDeviceListCount > 0 ) {
    			for(int i = 0; i < soundManager_soundDeviceListCount; i++) delete soundManager_soundDeviceListEntry[i];
    			soundManager_soundDeviceListCount = 0;
    		}
    
    		HMIXER hMixer = NULL;
    		MMRESULT mmResult;
    		MIXERLINE Line;
    		int stringLen;
    		int numDevices = mixerGetNumDevs();
    		for( int deviceId = 0; deviceId < numDevices; deviceId++) {
    			mmResult = mixerOpen(&hMixer, deviceId, 0, 0, MIXER_OBJECTF_MIXER);
    			if ( mmResult == MMSYSERR_NOERROR ) {
    				Line.cbStruct = sizeof(MIXERLINE);
    				Line.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
    				mmResult = mixerGetLineInfo( (HMIXEROBJ)hMixer, &Line, MIXER_OBJECTF_HMIXER | MIXER_GETLINEINFOF_COMPONENTTYPE );
    				if ( mmResult == MMSYSERR_NOERROR ) {
    					soundDeviceListEntry *newEntry = new soundDeviceListEntry;
    					newEntry->channels = (int)Line.cChannels;
    					newEntry->controls = (int)Line.cControls;
    
    					memset(newEntry->productName, 0, sizeof(newEntry->productName) );
    					memset(newEntry->shortName, 0, sizeof(newEntry->shortName) );
    					memset(newEntry->fullName, 0, sizeof(newEntry->fullName) );
    
    					stringLen = 0; while( stringLen++ < 254 && (char)Line.Target.szPname[stringLen] != 0 ) { }
    					if ( stringLen > 0 ) memcpy(newEntry->productName, Line.Target.szPname, stringLen - 1);
    
    					stringLen = 0; while( stringLen++ < 254 && (char)Line.szShortName[stringLen] != 0 ) { }
    					if ( stringLen > 0 ) memcpy(newEntry->shortName, Line.szShortName, stringLen - 1);
    
    					stringLen = 0; while( stringLen++ < 254 && (char)Line.szName[stringLen] != 0 ) { }
    					if ( stringLen > 0 ) memcpy(newEntry->fullName, Line.szName, stringLen - 1);
    
    					soundManager_soundDeviceListEntry[soundManager_soundDeviceListCount] = newEntry;
    					soundManager_soundDeviceListCount++;
    				}
    
    				mixerClose(hMixer);
    			}
    		}
    	}
    
    	MAIN_SOUND_FUNC int soundManager_getSoundDeviceCount() {
    		return( soundManager_soundDeviceListCount );
    	}
    
    	MAIN_SOUND_FUNC std::string soundManager_getSoundDeviceTitle_bySoundIndex( int soundDeviceIndex ) {
    		if ( soundDeviceIndex > -1 && soundDeviceIndex < soundManager_soundDeviceListCount ) return( std::string(soundManager_soundDeviceListEntry[soundDeviceIndex]->productName) );
    		else return std::string("");
    	}
    


  • ShadowTurtle schrieb:

    Fake-Sound-Treiber (Trojaner getarnt als Treiber)

    Du weisst schon dass Treiber sowieso mehr können als irgendein popeliges Programm, selbst wenn es mit Admin-Rechten läuft?

    Und wo wäre das Problem bei std::string(Line.Target.szPname, strnlen(Line.Target.szPname, 254)) ?



  • Und eins kann ich dir auch gleich sagen: ICH würde deine Lib nicht mit der Kneifzange angreifen, wenn sie Funktionen + globalen State verwendet 😉



  • Dann bist du selbst schuld

    Edit: Oder um mal sachlich zu bleiben ^^ : Wie würdest du das lösen und gleichzeitig möglichst kompatiblen C Code erzeugen? Würdest du so eine art Master-Struktur erzeugen und verwenden?
    Wenn du denkst, dass ich alles via extern regle und somit auf eine jeweilige Unit beschränkt bin, dann irrst du dich. Die Header File habe ich schon soweit vorbereitet entsprechend flexibel zu sein.

    Ansonsten muss ich zugeben, dass ich wohl propitären C und/oder C++ Quellcode erzeuge, dass aber immerhin zumindest einwandfrei funktioniert. Und sollte es nicht funktionieren, dann nehme ich gerne ein Tipp an.

    Aber die Sache mit den String lasse ich jetzt erst einmal so. Ich schätze das ich später nicht einmal mehr std::string verwenden werde (weil in C einfach nicht da) und meine lib später auch Fit für Homebrew zeugs machen möchte.

    In der Not kann ich auch eine vorkompilierte .lib-File erstellen mit einen schönen Header. 🙂



  • Ok, es war nie mein Ziel diverse Bestandteile universal verfügbar zu halten, weil diese Library relativ wenige global deklarierte Variablen hat (gerade einmal 😎 und somit noch Übersicht und Verwaltung gewährleistet bleibt. Für größeres ist diese Lib auch nicht angedacht und falls doch, dann wäre es für den Kern in Ordnung.
    Erweiterungen (beispielsweise Soundfilter) würden über Plugins gemacht und der Maintainer wäre wohl dann doch eine Klasse inkl. zusätzlichen PluginObject. Das ist aber noch zu arg Zukunftsmusik und auch nicht für eine Umsetzung gedacht (es existiert eben nur die Idee).

    Trotzdem muss ich eingestehen, dass falls ich wirklich Threading nutzen möchte, dass ich dann zumindest eine Master-Struktur haben sollte. Die Funktionen bleiben aber statisch deklariert bzw. member gibt es keine.

    Edit: Wenn die Nachfrage besteht, dann kann ich nach der Fertigstellung einen Wrapper für Objektorientiertes Programmieren erstellen. Ich möchte gar nicht einmal wissen, wieviele Programmierer schon flach programmiert und erst zum Schluss einfach nur die entsprechenden Funktionsaufrufen in Klassen untergebracht haben.

    Das ist nicht die feine Art, wird aber so praktiziert. Ich gehe damit zumindest offen um.



  • ShadowTurtle schrieb:

    Edit: Wenn die Nachfrage besteht, dann kann ich nach der Fertigstellung einen Wrapper für Objektorientiertes Programmieren erstellen. Ich möchte gar nicht einmal wissen, wieviele Programmierer schon flach programmiert und erst zum Schluss einfach nur die entsprechenden Funktionsaufrufen in Klassen untergebracht haben.

    Das geht nachträglich aber nur sehr bedingt. Das wird dann vermutlich auf ein Singleton hinauslaufen, und dann gewinnst quasi gar nichts gegenüber freien Funktionen und globalem State. Ein Vorteil bei Objekten wäre zum Beispiel, dass man mehr als ein Objekt erstellen kann, um mehrere Audio-Ausgabe-Geräte parallel anzusteuern.

    Ich weiß aber nicht, ob hustbaer auf diesen Nachteil hinauswollte oder auf etwas anderes.



  • Christoph schrieb:

    Ein Vorteil bei Objekten wäre zum Beispiel, dass man mehr als ein Objekt erstellen kann, um mehrere Audio-Ausgabe-Geräte parallel anzusteuern.

    Das stimmt vollkommen. Da ich aber mit meiner Soundlib nicht in irgendeiner Form in Zeitdruck stehe, kann ich auch Zeit darauf verwenden alles erdenkliche in Strukturen zu verpacken. Nach dem öffnen eines Devices erhält man einen Pointer zu einer instanzierten Struktur zurück. Diese Instanz muss man dann auch fleißig weiter geben (z.B. bei ...playSound).

    Aber vielen Dank für die beherzte Erinnerung!

    Ansonsten werde ich schon darauf achten, dass ich Objekte, Strukturen oder ähnliches schon nicht falsch Initialisiere. Eventuell lade ich nach der Fertigstellung einfach alles auf GIT hoch und mache ein Eintrag auf Codeproject. Der Rest ist Sache der Community und kann aus dem Framework dann machen was es möchte:

    • Plugin Funktionalität integrieren und entsprechendes SDK anbieten
    • Echtes Threading (was abhängig vom System ist) einbauen
    • Weitere Sound-Formate
    • Codeoptimierung (verwendung von Bitshifting)
    • Sound-Engine auf ein freien Prozessor-Kern laufen lassen
    • Und so weiter

    Das ganze geht meiner Meinung nach nun mal mit Funktional geschriebenen Quellcode am besten, wegen der einfacheren Struktur. Und eventuell auch wegen der einfacheren Verwaltung.

    Den Quellcode würde ich selbstverständlich dann auch für das Gemeinschaftsprojekt OS-Development PrettyOS zur Verfügung stellen. Dann könnte man nämlich mal eine richtige Applikation zusammenbauen (WAV Player).



  • ShadowTurtle schrieb:

    (...) alles erdenkliche in Strukturen zu verpacken. Nach dem öffnen eines Devices erhält man einen Pointer zu einer instanzierten Struktur zurück. Diese Instanz muss man dann auch fleißig weiter geben (z.B. bei ...playSound).

    Wenn du das überall durchziehst, so dass es überhaupt keine globalen Variablen mehr gibt, dann ist es OK.

    Also auch das Enumerieren von Sound-Devices etc. sollte keine globalen Variablen verwenden. Wenn doch, dann sicher' es mit Mutexen ab. Sonst bekommt man Probleme wenn man z.B. in zwei Threads gleichzeitig die Sound-Devices enumerieren will, weil sich deine Library dann die internen globalen Datenstrukturen zerschiesst.



  • Soooo. Da habe ich mir also etwas vorgenommen, als ich nicht genau wusste, dass waveOutOpen so ziemlich eingescrhänkt ist (ein Stream pro Prozess). Deshalb erstelle ich für jeden Stream aus dem Speicher ein neuer Prozess welches ausgeführt wird und fertig.

    Ne quatsch, dass wäre schließlich zu einfach und konnte sicherlich nicht schon früher zu DOS-Zeiten angewandt werden.

    Jetzt habe ich also ein Prozess (das Programm oder das Spiel) und einen einzigen Ausgabe-Kanal (mit waveOutOpen) je Device.
    Während der Laufzeit wird die Sound-Datei als Sound-Objekt in den Speicher geladen.
    Wird ...playSound aufgerufen, dann wird eine neue Instanz geschaffen für einen Sound-Player geschaffen. Der Sound-Player sammelt die benötigten WAV-Daten zum abspielen via streaming (Datenmaterial wird vom Sound-Objekt geklaut).

    Dann gibt es die andere Abteilung: Ein WAV-Objekt sorgt dafür, dass Datenmaterial für z.B. 1 Sek. gesammelt und an den Main-WAV-Sound-Streamer gesendet wird. Jede Sekunde wird im sogenannten SoundFrame alle abzuspielenden WAV-Objekte übereinander gelegt und zum abspielen gebracht.

    Kein Problem. Was ist aber, wenn etwa nach 10.5 Sekunden plötzlich die Lautstärke abgeändert werden soll? Im Buffer wäre dann noch die berechneten Daten der anderen 0.5 Sekunden gespeichert. Dies könnte zu einen Problem führen.

    Ich habe folgenden Lösungsansatz: Restlichen Buffer löschen und eine Neuanforderung der Daten senden. Genau ab hier macht das so genannte Quad-Buffering Sinn, sollte wirklich mal exzessives Threading eingebaut werden wollen.

    Gibt es für das spezielle Problem eventuell bessere Lösungen?


Anmelden zum Antworten